merge with default for code freeze stable 3.5-rc
authorMatt Mackall <mpm@selenic.com>
Sat, 18 Jul 2015 17:32:38 -0500
branchstable
changeset 25855 96a38d44ba09
parent 25745 501c51d60792 (current diff)
parent 25854 eabba9c75061 (diff)
child 25856 98a852a44673
merge with default for code freeze
contrib/revsetbenchmarks.txt
mercurial/ignore.py
tests/printenv.py
tests/test-hup.t
--- a/Makefile	Fri Jul 03 18:10:58 2015 +0100
+++ b/Makefile	Sat Jul 18 17:32:38 2015 -0500
@@ -157,6 +157,16 @@
 	N=`cd dist && echo mercurial-*.mpkg | sed 's,\.mpkg$$,,'` && hdiutil create -srcfolder dist/$$N.mpkg/ -scrub -volname "$$N" -ov packages/osx/$$N.dmg
 	rm -rf dist/mercurial-*.mpkg
 
+debian-jessie:
+	mkdir -p packages/debian-jessie
+	contrib/builddeb
+	mv debbuild/*.deb packages/debian-jessie
+	rm -rf debbuild
+
+docker-debian-jessie:
+	mkdir -p packages/debian/jessie
+	contrib/dockerdeb jessie
+
 fedora20:
 	mkdir -p packages/fedora20
 	contrib/buildrpm
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/all-revsets.txt	Sat Jul 18 17:32:38 2015 -0500
@@ -0,0 +1,137 @@
+# All revsets ever used with revsetbenchmarks.py script
+#
+# The goal of this file is to gather all revsets ever used for benchmarking
+# revset's performance. It should be used to gather revsets that test a
+# specific usecase or a specific implementation of revset predicates.
+# If you are working on the smartset implementation itself, check
+# 'base-revsets.txt'.
+#
+# Please update this file with any revsets you use for benchmarking a change so
+# that future contributors can easily find and retest it when doing further
+# modification. Feel free to highlight interresting variants if needed.
+
+
+## Revset from this section are all extracted from changelog when this file was
+# created. Feel free to dig and improve documentation.
+
+# Used in revision da05fe01170b
+(20000::) - (20000)
+# Used in revision 95af98616aa7
+parents(20000)
+# Used in revision 186fd06283b4
+(_intlist('20000\x0020001')) and merge()
+# Used in revision 911f5a6579d1
+p1(20000)
+p2(10000)
+# Used in revision b6dc3b79bb25
+0::
+# Used in revision faf4f63533ff
+bookmark()
+# Used in revision 22ba2c0825da
+tip~25
+# Used in revision 0cf46b8298fe
+bisect(range)
+# Used in revision 5b65429721d5
+divergent()
+# Used in revision 6261b9c549a2
+file(COPYING)
+# Used in revision 44f471102f3a
+follow(COPYING)
+# Used in revision 8040a44aab1c
+origin(tip)
+# Used in revision bbf4f3dfd700
+rev(25)
+# Used in revision a428db9ab61d
+p1()
+# Used in revision c1546d7400ef
+min(0::)
+# Used in revision 546fa6576815
+author(lmoscovicz) or author(mpm)
+author(mpm) or author(lmoscovicz)
+# Used in revision 9bfe68357c01
+public() and id("d82e2223f132")
+# Used in revision ba89f7b542c9
+rev(25)
+# Used in revision eb763217152a
+rev(210000)
+# Used in revision 69524a05a7fa
+10:100
+parents(10):parents(100)
+# Used in revision 6f1b8b3f12fd
+100~5
+parents(100)~5
+(100~5)~5
+# Used in revision 7a42e5d4c418
+children(tip~100)
+# Used in revision 7e8737e6ab08
+100^1
+parents(100)^1
+(100^1)^1
+# Used in revision 30e0dcd7c5ff
+matching(100)
+matching(parents(100))
+# Used in revision aafeaba22826
+0|1|2|3|4|5|6|7|8|9
+# Used in revision 33c7a94d4dd0
+tip:0
+# Used in revision 7d369fae098e
+(0:100000)
+# Used in revision b333ca94403d
+0 + 1 + 2 + ... + 200
+0 + 1 + 2 + ... + 1000
+sort(0 + 1 + 2 + ... + 200)
+sort(0 + 1 + 2 + ... + 1000)
+# Used in revision 7fbef7932af9
+first(0 + 1 + 2 + ... + 1000)
+# Used in revision ceaf04bb14ff
+0:1000
+# Used in revision 262e6ad93885
+not public()
+(tip~1000::) - public()
+not public() and branch("default")
+# Used in revision 15412bba5a68
+0::tip
+
+## all the revsets from this section have been taken from the former central file
+# for revset's benchmarking, they are undocumented for this reason.
+all()
+draft()
+::tip
+draft() and ::tip
+::tip and draft()
+author(lmoscovicz)
+author(mpm)
+::p1(p1(tip))::
+public()
+:10000 and public()
+:10000 and draft()
+(not public() - obsolete())
+
+# The one below is used by rebase
+(children(ancestor(tip~5, tip)) and ::(tip~5))::
+
+# those two `roots(...)` inputs are close to what phase movement use.
+roots((tip~100::) - (tip~100::tip))
+roots((0::) - (0::tip))
+
+# more roots testing
+roots(tip~100:)
+roots(:42)
+roots(not public())
+roots((0:tip)::)
+roots(0::tip)
+42:68 and roots(42:tip)
+# Used in revision f140d6207cca
+roots(0:tip)
+# test disjoint set with multiple roots
+roots((:42) + (tip~42:))
+
+# Testing the behavior of "head()" in various situations
+head()
+head() - public()
+draft() and head()
+head() and author("mpm")
+
+# testing the mutable phases set
+draft()
+secret()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/base-revsets.txt	Sat Jul 18 17:32:38 2015 -0500
@@ -0,0 +1,48 @@
+# Base Revsets to be used with revsetbenchmarks.py script
+#
+# The goal of this file is to gather a limited amount of revsets that allow a
+# good coverage of the internal revsets mechanisms.  Revsets included should not
+# be selected for their individual implementation, but for what they reveal of
+# the internal implementation of smartsets classes (and their interactions).
+#
+# Use and update this file when you change internal implementation of these
+# smartsets classes. Please include a comment explaining what each of your
+# addition is testing. Also check if your changes to the smartset class makes
+# some of the tests inadequate and replace them with a new one testing the same
+# behavior.
+#
+# If you want to benchmark revsets predicate itself, check 'all-revsets.txt'.
+#
+# The current content of this file is currently likely not reaching this goal
+# entirely, feel free, to audit its content and comment on each revset to
+# highlight what internal mechanisms they test.
+
+all()
+draft()
+::tip
+draft() and ::tip
+::tip and draft()
+0::tip
+roots(0::tip)
+author(lmoscovicz)
+author(mpm)
+author(lmoscovicz) or author(mpm)
+author(mpm) or author(lmoscovicz)
+tip:0
+0::
+# those two `roots(...)` inputs are close to what phase movement use.
+roots((tip~100::) - (tip~100::tip))
+roots((0::) - (0::tip))
+42:68 and roots(42:tip)
+::p1(p1(tip))::
+public()
+:10000 and public()
+draft()
+:10000 and draft()
+roots((0:tip)::)
+(not public() - obsolete())
+(_intlist('20000\x0020001')) and merge()
+parents(20000)
+(20000::) - (20000)
+# The one below is used by rebase
+(children(ancestor(tip~5, tip)) and ::(tip~5))::
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/builddeb	Sat Jul 18 17:32:38 2015 -0500
@@ -0,0 +1,62 @@
+#!/bin/sh -e
+#
+# Build a Mercurial debian package from the current repo
+#
+# Tested on Jessie (stable as of original script authoring.)
+
+. $(dirname $0)/packagelib.sh
+
+BUILD=1
+DEBBUILDDIR="$PWD/debbuild"
+while [ "$1" ]; do
+    case "$1" in
+    --prepare )
+        shift
+        BUILD=
+        ;;
+    --debbuilddir )
+        shift
+        DEBBUILDDIR="$1"
+        shift
+        ;;
+    * )
+        echo "Invalid parameter $1!" 1>&2
+        exit 1
+        ;;
+    esac
+done
+
+set -u
+
+rm -rf $DEBBUILDDIR
+mkdir -p $DEBBUILDDIR
+
+if [ ! -d .hg ]; then
+    echo 'You are not inside a Mercurial repository!' 1>&2
+    exit 1
+fi
+
+gethgversion
+
+cp -r $PWD/contrib/debian $DEBBUILDDIR/DEBIAN
+chmod -R 0755 $DEBBUILDDIR/DEBIAN
+
+control=$DEBBUILDDIR/DEBIAN/control
+
+# This looks like sed -i, but sed -i behaves just differently enough
+# between BSD and GNU sed that I gave up and did the dumb thing.
+sed "s/__VERSION__/$version/" < $control > $control.tmp
+mv $control.tmp $control
+
+if [ "$BUILD" ]; then
+    dpkg-deb --build $DEBBUILDDIR
+    mv $DEBBUILDDIR.deb $DEBBUILDDIR/mercurial-$version-$release.deb
+    if [ $? = 0 ]; then
+        echo
+        echo "Built packages for $version-$release:"
+        find $DEBBUILDDIR/ -type f -newer $control
+    fi
+else
+    echo "Prepared sources for $version-$release $control are in $DEBBUILDDIR - use like:"
+    echo "dpkg-deb --build $DEBBUILDDIR"
+fi
--- a/contrib/buildrpm	Fri Jul 03 18:10:58 2015 +0100
+++ b/contrib/buildrpm	Sat Jul 18 17:32:38 2015 -0500
@@ -7,6 +7,8 @@
 # - CentOS 5
 # - centOS 6
 
+. $(dirname $0)/packagelib.sh
+
 BUILD=1
 RPMBUILDDIR="$PWD/rpmbuild"
 while [ "$1" ]; do
@@ -45,25 +47,8 @@
     exit 1
 fi
 
-# build local hg and use it
-python setup.py build_py -c -d .
-HG="$PWD/hg"
-PYTHONPATH="$PWD/mercurial/pure"
-export PYTHONPATH
-
-mkdir -p $RPMBUILDDIR/SOURCES $RPMBUILDDIR/SPECS $RPMBUILDDIR/RPMS $RPMBUILDDIR/SRPMS $RPMBUILDDIR/BUILD
-
-hgversion=`$HG version | sed -ne 's/.*(version \(.*\))$/\1/p'`
+gethgversion
 
-if echo $hgversion | grep -- '-' > /dev/null 2>&1; then
-    # nightly build case, version is like 1.3.1+250-20b91f91f9ca
-    version=`echo $hgversion | cut -d- -f1`
-    release=`echo $hgversion | cut -d- -f2 | sed -e 's/+.*//'`
-else
-    # official tag, version is like 1.3.1
-    version=`echo $hgversion | sed -e 's/+.*//'`
-    release='0'
-fi
 if [ "$PYTHONVER" ]; then
     release=$release+$PYTHONVER
     RPMPYTHONVER=$PYTHONVER
--- a/contrib/check-code.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/contrib/check-code.py	Sat Jul 18 17:32:38 2015 -0500
@@ -114,6 +114,7 @@
     (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
     (r'^stop\(\)', "don't use 'stop' as a shell function name"),
     (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
+    (r'\[\[\s+[^\]]*\]\]', "don't use '[[ ]]', use '[ ]'"),
     (r'^alias\b.*=', "don't use alias, use a function"),
     (r'if\s*!', "don't use '!' to negate exit status"),
     (r'/dev/u?random', "don't use entropy, use /dev/zero"),
@@ -217,14 +218,6 @@
     (r'(\w|\)),\w', "missing whitespace after ,"),
     (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
     (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
-    (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)(\1except.*?:\n'
-     r'((?:\n|\1\s.*\n)+?))+\1finally:',
-     'no try/except/finally in Python 2.4'),
-    (r'(?<!def)(\s+|^|\()next\(.+\)',
-     'no next(foo) in Python 2.4 and 2.5, use foo.next() instead'),
-    (r'(\s+)try:\n((?:\n|\1\s.*\n)*?)\1\s*yield\b.*?'
-     r'((?:\n|\1\s.*\n)+?)\1finally:',
-     'no yield inside try/finally in Python 2.4'),
     (r'.{81}', "line too long"),
     (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
     (r'[^\n]\Z', "no trailing newline"),
@@ -237,8 +230,9 @@
      "linebreak after :"),
     (r'class\s[^( \n]+:', "old-style class, use class foo(object)"),
     (r'class\s[^( \n]+\(\):',
-     "class foo() not available in Python 2.4, use class foo(object)"),
-    (r'\b(%s)\(' % '|'.join(keyword.kwlist),
+     "class foo() creates old style object, use class foo(object)"),
+    (r'\b(%s)\(' % '|'.join(k for k in keyword.kwlist
+                            if k not in ('print', 'exec')),
      "Python keyword is not a function"),
     (r',]', "unneeded trailing ',' in list"),
 #    (r'class\s[A-Z][^\(]*\((?!Exception)',
@@ -246,14 +240,7 @@
 #    (r'in range\(', "use xrange"),
 #    (r'^\s*print\s+', "avoid using print in core and extensions"),
     (r'[\x80-\xff]', "non-ASCII character literal"),
-    (r'("\')\.format\(', "str.format() not available in Python 2.4"),
-    (r'^\s*with\s+', "with not available in Python 2.4"),
-    (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"),
-    (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
-    (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
-    (r'(?<!def)\s+(any|all|format)\(',
-     "any/all/format not available in Python 2.4", 'no-py24'),
-    (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
+    (r'("\')\.format\(', "str.format() has no bytes counterpart, use %"),
     (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
      "gratuitous whitespace after Python keyword"),
     (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
@@ -280,8 +267,6 @@
      'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
     (r'opener\([^)]*\).read\(',
      "use opener.read() instead"),
-    (r'BaseException', 'not in Python 2.4, use Exception'),
-    (r'os\.path\.relpath', 'os.path.relpath is not in Python 2.5'),
     (r'opener\([^)]*\).write\(',
      "use opener.write() instead"),
     (r'[\s\(](open|file)\([^)]*\)\.read\(',
@@ -296,6 +281,8 @@
     (r'\.debug\(\_', "don't mark debug messages for translation"),
     (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
     (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
+    (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,',
+     'legacy exception syntax; use "as" instead of ","'),
     (r':\n(    )*( ){1,3}[^ ]', "must indent 4 spaces"),
     (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
      "missing _() in ui message (use () to hide false-positives)"),
@@ -303,6 +290,7 @@
     (r'\b__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
     (r'os\.path\.join\(.*, *(""|\'\')\)',
      "use pathutil.normasprefix(path) instead of os.path.join(path, '')"),
+    (r'\s0[0-7]+\b', 'legacy octal syntax; use "0o" prefix instead of "0"'),
   ],
   # warnings
   [
@@ -469,7 +457,7 @@
 
     try:
         fp = open(f)
-    except IOError, e:
+    except IOError as e:
         print "Skipping %s, %s" % (f, str(e).split(':', 1)[0])
         return result
     pre = post = fp.read()
--- a/contrib/check-commit	Fri Jul 03 18:10:58 2015 +0100
+++ b/contrib/check-commit	Sat Jul 18 17:32:38 2015 -0500
@@ -27,9 +27,10 @@
     (r"^# .*\n[A-Z][a-z]\S+", "don't capitalize summary lines"),
     (r"^# .*\n[^\n]*: *[A-Z][a-z]\S+", "don't capitalize summary lines"),
     (r"^# .*\n.*\.\s+$", "don't add trailing period on summary line"),
-    (r"^# .*\n.{78,}", "summary line too long"),
+    (r"^# .*\n.{78,}", "summary line too long (limit is 78)"),
     (r"^\+\n \n", "adds double empty line"),
-    (r"\+\s+def [a-z]+_[a-z]", "adds a function with foo_bar naming"),
+    (r"^ \n\+\n", "adds double empty line"),
+    (r"^\+[ \t]+def [a-z]+_[a-z]", "adds a function with foo_bar naming"),
 ]
 
 node = os.environ.get("HG_NODE")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/check-config.py	Sat Jul 18 17:32:38 2015 -0500
@@ -0,0 +1,93 @@
+#!/usr/bin/env python
+#
+# check-config - a config flag documentation checker for Mercurial
+#
+# Copyright 2015 Matt Mackall <mpm@selenic.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+import re
+import sys
+
+foundopts = {}
+documented = {}
+
+configre = (r"""ui\.config(|int|bool|list)\(['"](\S+)['"], ?"""
+            r"""['"](\S+)['"](,\s(?:default=)?(\S+?))?\)""")
+
+def main(args):
+    for f in args:
+        sect = ''
+        prevname = ''
+        confsect = ''
+        for l in open(f):
+
+            # check topic-like bits
+            m = re.match('\s*``(\S+)``', l)
+            if m:
+                prevname = m.group(1)
+            if re.match('^\s*-+$', l):
+                sect = prevname
+                prevname = ''
+
+            if sect and prevname:
+                name = sect + '.' + prevname
+                documented[name] = 1
+
+            # check docstring bits
+            m = re.match(r'^\s+\[(\S+)\]', l)
+            if m:
+                confsect = m.group(1)
+                continue
+            m = re.match(r'^\s+(?:#\s*)?([a-z._]+) = ', l)
+            if m:
+                name = confsect + '.' + m.group(1)
+                documented[name] = 1
+
+            # like the bugzilla extension
+            m = re.match(r'^\s*([a-z]+\.[a-z]+)$', l)
+            if m:
+                documented[m.group(1)] = 1
+
+            # quoted in help or docstrings
+            m = re.match(r'.*?``([-a-z_]+\.[-a-z_]+)``', l)
+            if m:
+                documented[m.group(1)] = 1
+
+            # look for ignore markers
+            m = re.search(r'# (?:internal|experimental|deprecated|developer)'
+                          ' config: (\S+.\S+)$', l)
+            if m:
+                documented[m.group(1)] = 1
+
+            # look for code-like bits
+            m = re.search(configre, l)
+            if m:
+                ctype = m.group(1)
+                if not ctype:
+                    ctype = 'str'
+                name = m.group(2) + "." + m.group(3)
+                default = m.group(5)
+                if default in (None, 'False', 'None', '0', '[]', '""', "''"):
+                    default = ''
+                if re.match('[a-z.]+$', default):
+                    default = '<variable>'
+                if name in foundopts and (ctype, default) != foundopts[name]:
+                    print l
+                    print "conflict on %s: %r != %r" % (name, (ctype, default),
+                                                        foundopts[name])
+                foundopts[name] = (ctype, default)
+
+    for name in sorted(foundopts):
+        if name not in documented:
+            if not (name.startswith("devel.") or
+                    name.startswith("experimental.") or
+                    name.startswith("debug.")):
+                ctype, default = foundopts[name]
+                if default:
+                    default = ' [%s]' % default
+                print "undocumented: %s (%s)%s" % (name, ctype, default)
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv[1:]))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/debian/control	Sat Jul 18 17:32:38 2015 -0500
@@ -0,0 +1,9 @@
+Package: mercurial
+Version: __VERSION__
+Section: vcs
+Priority: optional
+Architecture: all
+Depends: python
+Conflicts: mercurial-common
+Maintainer: Mercurial Developers <mercurial-devel@selenic.com>
+Description: Mercurial (probably nightly) package built by upstream.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/docker/debian-jessie	Sat Jul 18 17:32:38 2015 -0500
@@ -0,0 +1,11 @@
+FROM debian:jessie
+RUN apt-get update && apt-get install -y \
+  build-essential \
+  debhelper \
+  dh-python \
+  devscripts \
+  python \
+  python-all-dev \
+  python-docutils \
+  zip \
+  unzip
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/dockerdeb	Sat Jul 18 17:32:38 2015 -0500
@@ -0,0 +1,39 @@
+#!/bin/bash -eu
+
+. $(dirname $0)/dockerlib.sh
+. $(dirname $0)/packagelib.sh
+
+BUILDDIR=$(dirname $0)
+export ROOTDIR=$(cd $BUILDDIR/..; pwd)
+
+checkdocker
+
+PLATFORM="debian-$1"
+shift # extra params are passed to build process
+
+initcontainer $PLATFORM
+
+DEBBUILDDIR=$ROOTDIR/packages/$PLATFORM
+contrib/builddeb --debbuilddir $DEBBUILDDIR/staged --prepare
+
+DSHARED=/mnt/shared/
+if [ $(uname) = "Darwin" ] ; then
+    $DOCKER run -u $DBUILDUSER --rm -v $DEBBUILDDIR:$DSHARED -v $PWD:/mnt/hg $CONTAINER \
+            sh -c "cd /mnt/hg && make clean && make local"
+fi
+$DOCKER run -u $DBUILDUSER --rm -v $DEBBUILDDIR:$DSHARED -v $PWD:/mnt/hg $CONTAINER \
+  sh -c "cd /mnt/hg && make PREFIX=$DSHARED/staged/usr install"
+$DOCKER run -u $DBUILDUSER --rm -v $DEBBUILDDIR:$DSHARED $CONTAINER \
+  dpkg-deb --build $DSHARED/staged
+if [ $(uname) = "Darwin" ] ; then
+    $DOCKER run -u $DBUILDUSER --rm -v $DEBBUILDDIR:$DSHARED -v $PWD:/mnt/hg $CONTAINER \
+            sh -c "cd /mnt/hg && make clean"
+fi
+
+gethgversion
+
+rm -r $DEBBUILDDIR/staged
+mv $DEBBUILDDIR/staged.deb $DEBBUILDDIR/mercurial-$version-$release.deb
+
+echo
+echo "Build complete - results can be found in $DEBBUILDDIR"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/dockerlib.sh	Sat Jul 18 17:32:38 2015 -0500
@@ -0,0 +1,42 @@
+#!/bin/sh -eu
+
+# This function exists to set up the DOCKER variable and verify that
+# it's the binary we expect. It also verifies that the docker service
+# is running on the system and we can talk to it.
+function checkdocker() {
+  if which docker.io >> /dev/null 2>&1 ; then
+    DOCKER=docker.io
+  elif which docker >> /dev/null 2>&1 ; then
+    DOCKER=docker
+  else
+    echo "Error: docker must be installed"
+    exit 1
+  fi
+
+  $DOCKER -h 2> /dev/null | grep -q Jansens && { echo "Error: $DOCKER is the Docking System Tray - install docker.io instead"; exit 1; }
+  $DOCKER version | grep -q "^Client version:" || { echo "Error: unexpected output from \"$DOCKER version\""; exit 1; }
+  $DOCKER version | grep -q "^Server version:" || { echo "Error: could not get docker server version - check it is running and your permissions"; exit 1; }
+}
+
+# Construct a container and leave its name in $CONTAINER for future use.
+function initcontainer() {
+  [ "$1" ] || { echo "Error: platform name must be specified"; exit 1; }
+
+  DFILE="$ROOTDIR/contrib/docker/$1"
+  [ -f "$DFILE" ] || { echo "Error: docker file $DFILE not found"; exit 1; }
+
+  CONTAINER="hg-dockerrpm-$1"
+  DBUILDUSER=build
+  (
+    cat $DFILE
+    if [ $(uname) = "Darwin" ] ; then
+        # The builder is using boot2docker on OS X, so we're going to
+        # *guess* the uid of the user inside the VM that is actually
+        # running docker. This is *very likely* to fail at some point.
+        echo RUN useradd $DBUILDUSER -u 1000
+    else
+        echo RUN groupadd $DBUILDUSER -g `id -g`
+        echo RUN useradd $DBUILDUSER -u `id -u` -g $DBUILDUSER
+    fi
+  ) | $DOCKER build --tag $CONTAINER -
+}
--- a/contrib/dockerrpm	Fri Jul 03 18:10:58 2015 +0100
+++ b/contrib/dockerrpm	Sat Jul 18 17:32:38 2015 -0500
@@ -1,36 +1,16 @@
 #!/bin/bash -e
 
+. $(dirname $0)/dockerlib.sh
+
 BUILDDIR=$(dirname $0)
-ROOTDIR=$(cd $BUILDDIR/..; pwd)
+export ROOTDIR=$(cd $BUILDDIR/..; pwd)
 
-if which docker.io >> /dev/null 2>&1 ; then
-  DOCKER=docker.io
-elif which docker >> /dev/null 2>&1 ; then
-  DOCKER=docker
-else
-  echo "Error: docker must be installed"
-  exit 1
-fi
-
-$DOCKER -h 2> /dev/null | grep -q Jansens && { echo "Error: $DOCKER is the Docking System Tray - install docker.io instead"; exit 1; }
-$DOCKER version | grep -q "^Client version:" || { echo "Error: unexpected output from \"$DOCKER version\""; exit 1; }
-$DOCKER version | grep -q "^Server version:" || { echo "Error: could not get docker server version - check it is running and your permissions"; exit 1; }
+checkdocker
 
 PLATFORM="$1"
-[ "$PLATFORM" ] || { echo "Error: platform name must be specified"; exit 1; }
 shift # extra params are passed to buildrpm
 
-DFILE="$ROOTDIR/contrib/docker/$PLATFORM"
-[ -f "$DFILE" ] || { echo "Error: docker file $DFILE not found"; exit 1; }
-
-CONTAINER="hg-dockerrpm-$PLATFORM"
-
-DBUILDUSER=build
-(
-cat $DFILE
-echo RUN groupadd $DBUILDUSER -g `id -g`
-echo RUN useradd $DBUILDUSER -u `id -u` -g $DBUILDUSER
-) | $DOCKER build --tag $CONTAINER -
+initcontainer $PLATFORM
 
 RPMBUILDDIR=$ROOTDIR/packages/$PLATFORM
 contrib/buildrpm --rpmbuilddir $RPMBUILDDIR --prepare $*
--- a/contrib/hg-ssh	Fri Jul 03 18:10:58 2015 +0100
+++ b/contrib/hg-ssh	Sat Jul 18 17:32:38 2015 -0500
@@ -64,7 +64,7 @@
             if readonly:
                 cmd += [
                     '--config',
-                    'hooks.prechangegroup.hg-ssh=python:__main__.rejectpush',
+                    'hooks.pretxnopen.hg-ssh=python:__main__.rejectpush',
                     '--config',
                     'hooks.prepushkey.hg-ssh=python:__main__.rejectpush'
                     ]
--- a/contrib/import-checker.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/contrib/import-checker.py	Sat Jul 18 17:32:38 2015 -0500
@@ -8,6 +8,33 @@
 import BaseHTTPServer
 import zlib
 
+# Whitelist of modules that symbols can be directly imported from.
+allowsymbolimports = (
+    '__future__',
+    'mercurial.i18n',
+    'mercurial.node',
+)
+
+# Modules that must be aliased because they are commonly confused with
+# common variables and can create aliasing and readability issues.
+requirealias = {
+    'ui': 'uimod',
+}
+
+def usingabsolute(root):
+    """Whether absolute imports are being used."""
+    if sys.version_info[0] >= 3:
+        return True
+
+    for node in ast.walk(root):
+        if isinstance(node, ast.ImportFrom):
+            if node.module == '__future__':
+                for n in node.names:
+                    if n.name == 'absolute_import':
+                        return True
+
+    return False
+
 def dotted_name_of_path(path, trimpure=False):
     """Given a relative path to a source file, return its dotted module name.
 
@@ -26,6 +53,85 @@
         return '.'.join(p for p in parts if p != 'pure')
     return '.'.join(parts)
 
+def fromlocalfunc(modulename, localmods):
+    """Get a function to examine which locally defined module the
+    target source imports via a specified name.
+
+    `modulename` is an `dotted_name_of_path()`-ed source file path,
+    which may have `.__init__` at the end of it, of the target source.
+
+    `localmods` is a dict (or set), of which key is an absolute
+    `dotted_name_of_path()`-ed source file path of locally defined (=
+    Mercurial specific) modules.
+
+    This function assumes that module names not existing in
+    `localmods` are ones of Python standard libarary.
+
+    This function returns the function, which takes `name` argument,
+    and returns `(absname, dottedpath, hassubmod)` tuple if `name`
+    matches against locally defined module. Otherwise, it returns
+    False.
+
+    It is assumed that `name` doesn't have `.__init__`.
+
+    `absname` is an absolute module name of specified `name`
+    (e.g. "hgext.convert"). This can be used to compose prefix for sub
+    modules or so.
+
+    `dottedpath` is a `dotted_name_of_path()`-ed source file path
+    (e.g. "hgext.convert.__init__") of `name`. This is used to look
+    module up in `localmods` again.
+
+    `hassubmod` is whether it may have sub modules under it (for
+    convenient, even though this is also equivalent to "absname !=
+    dottednpath")
+
+    >>> localmods = {'foo.__init__': True, 'foo.foo1': True,
+    ...              'foo.bar.__init__': True, 'foo.bar.bar1': True,
+    ...              'baz.__init__': True, 'baz.baz1': True }
+    >>> fromlocal = fromlocalfunc('foo.xxx', localmods)
+    >>> # relative
+    >>> fromlocal('foo1')
+    ('foo.foo1', 'foo.foo1', False)
+    >>> fromlocal('bar')
+    ('foo.bar', 'foo.bar.__init__', True)
+    >>> fromlocal('bar.bar1')
+    ('foo.bar.bar1', 'foo.bar.bar1', False)
+    >>> # absolute
+    >>> fromlocal('baz')
+    ('baz', 'baz.__init__', True)
+    >>> fromlocal('baz.baz1')
+    ('baz.baz1', 'baz.baz1', False)
+    >>> # unknown = maybe standard library
+    >>> fromlocal('os')
+    False
+    >>> fromlocal(None, 1)
+    ('foo', 'foo.__init__', True)
+    >>> fromlocal2 = fromlocalfunc('foo.xxx.yyy', localmods)
+    >>> fromlocal2(None, 2)
+    ('foo', 'foo.__init__', True)
+    """
+    prefix = '.'.join(modulename.split('.')[:-1])
+    if prefix:
+        prefix += '.'
+    def fromlocal(name, level=0):
+        # name is None when relative imports are used.
+        if name is None:
+            # If relative imports are used, level must not be absolute.
+            assert level > 0
+            candidates = ['.'.join(modulename.split('.')[:-level])]
+        else:
+            # Check relative name first.
+            candidates = [prefix + name, name]
+
+        for n in candidates:
+            if n in localmods:
+                return (n, n, False)
+            dottedpath = n + '.__init__'
+            if dottedpath in localmods:
+                return (n, dottedpath, True)
+        return False
+    return fromlocal
 
 def list_stdlib_modules():
     """List the modules present in the stdlib.
@@ -86,9 +192,11 @@
         # of any().
         if not any(libpath.startswith(p) for p in stdlib_prefixes): # no-py24
             continue
-        if 'site-packages' in libpath:
-            continue
         for top, dirs, files in os.walk(libpath):
+            for i, d in reversed(list(enumerate(dirs))):
+                if (not os.path.exists(os.path.join(top, d, '__init__.py'))
+                    or top == libpath and d in ('hgext', 'mercurial')):
+                    del dirs[i]
             for name in files:
                 if name == '__init__.py':
                     continue
@@ -96,48 +204,255 @@
                         or name.endswith('.pyd')):
                     continue
                 full_path = os.path.join(top, name)
-                if 'site-packages' in full_path:
-                    continue
                 rel_path = full_path[len(libpath) + 1:]
                 mod = dotted_name_of_path(rel_path)
                 yield mod
 
 stdlib_modules = set(list_stdlib_modules())
 
-def imported_modules(source, ignore_nested=False):
+def imported_modules(source, modulename, localmods, ignore_nested=False):
     """Given the source of a file as a string, yield the names
     imported by that file.
 
     Args:
       source: The python source to examine as a string.
+      modulename: of specified python source (may have `__init__`)
+      localmods: dict of locally defined module names (may have `__init__`)
       ignore_nested: If true, import statements that do not start in
                      column zero will be ignored.
 
     Returns:
-      A list of module names imported by the given source.
+      A list of absolute module names imported by the given source.
 
+    >>> modulename = 'foo.xxx'
+    >>> localmods = {'foo.__init__': True,
+    ...              'foo.foo1': True, 'foo.foo2': True,
+    ...              'foo.bar.__init__': True, 'foo.bar.bar1': True,
+    ...              'baz.__init__': True, 'baz.baz1': True }
+    >>> # standard library (= not locally defined ones)
+    >>> sorted(imported_modules(
+    ...        'from stdlib1 import foo, bar; import stdlib2',
+    ...        modulename, localmods))
+    []
+    >>> # relative importing
     >>> sorted(imported_modules(
-    ...         'import foo ; from baz import bar; import foo.qux'))
-    ['baz.bar', 'foo', 'foo.qux']
+    ...        'import foo1; from bar import bar1',
+    ...        modulename, localmods))
+    ['foo.bar.__init__', 'foo.bar.bar1', 'foo.foo1']
+    >>> sorted(imported_modules(
+    ...        'from bar.bar1 import name1, name2, name3',
+    ...        modulename, localmods))
+    ['foo.bar.bar1']
+    >>> # absolute importing
+    >>> sorted(imported_modules(
+    ...        'from baz import baz1, name1',
+    ...        modulename, localmods))
+    ['baz.__init__', 'baz.baz1']
+    >>> # mixed importing, even though it shouldn't be recommended
+    >>> sorted(imported_modules(
+    ...        'import stdlib, foo1, baz',
+    ...        modulename, localmods))
+    ['baz.__init__', 'foo.foo1']
+    >>> # ignore_nested
     >>> sorted(imported_modules(
     ... '''import foo
     ... def wat():
     ...     import bar
-    ... ''', ignore_nested=True))
-    ['foo']
+    ... ''', modulename, localmods))
+    ['foo.__init__', 'foo.bar.__init__']
+    >>> sorted(imported_modules(
+    ... '''import foo
+    ... def wat():
+    ...     import bar
+    ... ''', modulename, localmods, ignore_nested=True))
+    ['foo.__init__']
     """
+    fromlocal = fromlocalfunc(modulename, localmods)
     for node in ast.walk(ast.parse(source)):
         if ignore_nested and getattr(node, 'col_offset', 0) > 0:
             continue
         if isinstance(node, ast.Import):
             for n in node.names:
-                yield n.name
+                found = fromlocal(n.name)
+                if not found:
+                    # this should import standard library
+                    continue
+                yield found[1]
         elif isinstance(node, ast.ImportFrom):
-            prefix = node.module + '.'
+            found = fromlocal(node.module, node.level)
+            if not found:
+                # this should import standard library
+                continue
+
+            absname, dottedpath, hassubmod = found
+            yield dottedpath
+            if not hassubmod:
+                # examination of "node.names" should be redundant
+                # e.g.: from mercurial.node import nullid, nullrev
+                continue
+
+            prefix = absname + '.'
             for n in node.names:
-                yield prefix + n.name
+                found = fromlocal(prefix + n.name)
+                if not found:
+                    # this should be a function or a property of "node.module"
+                    continue
+                yield found[1]
+
+def verify_import_convention(module, source):
+    """Verify imports match our established coding convention.
+
+    We have 2 conventions: legacy and modern. The modern convention is in
+    effect when using absolute imports.
+
+    The legacy convention only looks for mixed imports. The modern convention
+    is much more thorough.
+    """
+    root = ast.parse(source)
+    absolute = usingabsolute(root)
+
+    if absolute:
+        return verify_modern_convention(module, root)
+    else:
+        return verify_stdlib_on_own_line(root)
+
+def verify_modern_convention(module, root):
+    """Verify a file conforms to the modern import convention rules.
+
+    The rules of the modern convention are:
+
+    * Ordering is stdlib followed by local imports. Each group is lexically
+      sorted.
+    * Importing multiple modules via "import X, Y" is not allowed: use
+      separate import statements.
+    * Importing multiple modules via "from X import ..." is allowed if using
+      parenthesis and one entry per line.
+    * Only 1 relative import statement per import level ("from .", "from ..")
+      is allowed.
+    * Relative imports from higher levels must occur before lower levels. e.g.
+      "from .." must be before "from .".
+    * Imports from peer packages should use relative import (e.g. do not
+      "import mercurial.foo" from a "mercurial.*" module).
+    * Symbols can only be imported from specific modules (see
+      `allowsymbolimports`). For other modules, first import the module then
+      assign the symbol to a module-level variable. In addition, these imports
+      must be performed before other relative imports. This rule only
+      applies to import statements outside of any blocks.
+    * Relative imports from the standard library are not allowed.
+    * Certain modules must be aliased to alternate names to avoid aliasing
+      and readability problems. See `requirealias`.
+    """
+    topmodule = module.split('.')[0]
+
+    # Whether a local/non-stdlib import has been performed.
+    seenlocal = False
+    # Whether a relative, non-symbol import has been seen.
+    seennonsymbolrelative = False
+    # The last name to be imported (for sorting).
+    lastname = None
+    # Relative import levels encountered so far.
+    seenlevels = set()
+
+    for node in ast.walk(root):
+        if isinstance(node, ast.Import):
+            # Disallow "import foo, bar" and require separate imports
+            # for each module.
+            if len(node.names) > 1:
+                yield 'multiple imported names: %s' % ', '.join(
+                    n.name for n in node.names)
+
+            name = node.names[0].name
+            asname = node.names[0].asname
+
+            # Ignore sorting rules on imports inside blocks.
+            if node.col_offset == 0:
+                if lastname and name < lastname:
+                    yield 'imports not lexically sorted: %s < %s' % (
+                           name, lastname)
+
+                lastname = name
 
-def verify_stdlib_on_own_line(source):
+            # stdlib imports should be before local imports.
+            stdlib = name in stdlib_modules
+            if stdlib and seenlocal and node.col_offset == 0:
+                yield 'stdlib import follows local import: %s' % name
+
+            if not stdlib:
+                seenlocal = True
+
+            # Import of sibling modules should use relative imports.
+            topname = name.split('.')[0]
+            if topname == topmodule:
+                yield 'import should be relative: %s' % name
+
+            if name in requirealias and asname != requirealias[name]:
+                yield '%s module must be "as" aliased to %s' % (
+                       name, requirealias[name])
+
+        elif isinstance(node, ast.ImportFrom):
+            # Resolve the full imported module name.
+            if node.level > 0:
+                fullname = '.'.join(module.split('.')[:-node.level])
+                if node.module:
+                    fullname += '.%s' % node.module
+            else:
+                assert node.module
+                fullname = node.module
+
+                topname = fullname.split('.')[0]
+                if topname == topmodule:
+                    yield 'import should be relative: %s' % fullname
+
+            # __future__ is special since it needs to come first and use
+            # symbol import.
+            if fullname != '__future__':
+                if not fullname or fullname in stdlib_modules:
+                    yield 'relative import of stdlib module'
+                else:
+                    seenlocal = True
+
+            # Direct symbol import is only allowed from certain modules and
+            # must occur before non-symbol imports.
+            if node.module and node.col_offset == 0:
+                if fullname not in allowsymbolimports:
+                    yield 'direct symbol import from %s' % fullname
+
+                if seennonsymbolrelative:
+                    yield ('symbol import follows non-symbol import: %s' %
+                           fullname)
+
+            if not node.module:
+                assert node.level
+                seennonsymbolrelative = True
+
+                # Only allow 1 group per level.
+                if node.level in seenlevels and node.col_offset == 0:
+                    yield 'multiple "from %s import" statements' % (
+                           '.' * node.level)
+
+                # Higher-level groups come before lower-level groups.
+                if any(node.level > l for l in seenlevels):
+                    yield 'higher-level import should come first: %s' % (
+                           fullname)
+
+                seenlevels.add(node.level)
+
+            # Entries in "from .X import ( ... )" lists must be lexically
+            # sorted.
+            lastentryname = None
+
+            for n in node.names:
+                if lastentryname and n.name < lastentryname:
+                    yield 'imports from %s not lexically sorted: %s < %s' % (
+                           fullname, n.name, lastentryname)
+
+                lastentryname = n.name
+
+                if n.name in requirealias and n.asname != requirealias[n.name]:
+                    yield '%s from %s must be "as" aliased to %s' % (
+                          n.name, fullname, requirealias[n.name])
+
+def verify_stdlib_on_own_line(root):
     """Given some python source, verify that stdlib imports are done
     in separate statements from relative local module imports.
 
@@ -145,14 +460,14 @@
     annoying lib2to3 bug in relative import rewrites:
     http://bugs.python.org/issue19510.
 
-    >>> list(verify_stdlib_on_own_line('import sys, foo'))
+    >>> list(verify_stdlib_on_own_line(ast.parse('import sys, foo')))
     ['mixed imports\\n   stdlib:    sys\\n   relative:  foo']
-    >>> list(verify_stdlib_on_own_line('import sys, os'))
+    >>> list(verify_stdlib_on_own_line(ast.parse('import sys, os')))
     []
-    >>> list(verify_stdlib_on_own_line('import foo, bar'))
+    >>> list(verify_stdlib_on_own_line(ast.parse('import foo, bar')))
     []
     """
-    for node in ast.walk(ast.parse(source)):
+    for node in ast.walk(root):
         if isinstance(node, ast.Import):
             from_stdlib = {False: [], True: []}
             for n in node.names:
@@ -171,8 +486,6 @@
     while visit:
         path = visit.pop(0)
         for i in sorted(imports.get(path[-1], [])):
-            if i not in stdlib_modules and not i.startswith('mercurial.'):
-                i = mod.rsplit('.', 1)[0] + '.' + i
             if len(path) < shortest.get(i, 1000):
                 shortest[i] = len(path)
                 if i in path:
@@ -194,10 +507,12 @@
 def find_cycles(imports):
     """Find cycles in an already-loaded import graph.
 
-    >>> imports = {'top.foo': ['bar', 'os.path', 'qux'],
-    ...            'top.bar': ['baz', 'sys'],
-    ...            'top.baz': ['foo'],
-    ...            'top.qux': ['foo']}
+    All module names recorded in `imports` should be absolute one.
+
+    >>> imports = {'top.foo': ['top.bar', 'os.path', 'top.qux'],
+    ...            'top.bar': ['top.baz', 'sys'],
+    ...            'top.baz': ['top.foo'],
+    ...            'top.qux': ['top.foo']}
     >>> print '\\n'.join(sorted(find_cycles(imports)))
     top.bar -> top.baz -> top.foo -> top.bar
     top.foo -> top.qux -> top.foo
@@ -206,7 +521,7 @@
     for mod in sorted(imports.iterkeys()):
         try:
             checkmod(mod, imports)
-        except CircularImport, e:
+        except CircularImport as e:
             cycle = e.args[0]
             cycles.add(" -> ".join(rotatecycle(cycle)))
     return cycles
@@ -215,18 +530,24 @@
     return len(c), c
 
 def main(argv):
-    if len(argv) < 2:
-        print 'Usage: %s file [file] [file] ...'
+    if len(argv) < 2 or (argv[1] == '-' and len(argv) > 2):
+        print 'Usage: %s {-|file [file] [file] ...}'
         return 1
+    if argv[1] == '-':
+        argv = argv[:1]
+        argv.extend(l.rstrip() for l in sys.stdin.readlines())
+    localmods = {}
     used_imports = {}
     any_errors = False
     for source_path in argv[1:]:
+        modname = dotted_name_of_path(source_path, trimpure=True)
+        localmods[modname] = source_path
+    for modname, source_path in sorted(localmods.iteritems()):
         f = open(source_path)
-        modname = dotted_name_of_path(source_path, trimpure=True)
         src = f.read()
         used_imports[modname] = sorted(
-            imported_modules(src, ignore_nested=True))
-        for error in verify_stdlib_on_own_line(src):
+            imported_modules(src, modname, localmods, ignore_nested=True))
+        for error in verify_import_convention(modname, src):
             any_errors = True
             print source_path, error
         f.close()
@@ -243,7 +564,7 @@
             print 'Import cycle:', c
             firstmods.add(first)
         any_errors = True
-    return not any_errors
+    return any_errors != 0
 
 if __name__ == '__main__':
     sys.exit(int(main(sys.argv)))
--- a/contrib/mercurial.spec	Fri Jul 03 18:10:58 2015 +0100
+++ b/contrib/mercurial.spec	Sat Jul 18 17:32:38 2015 -0500
@@ -37,8 +37,8 @@
 %if "%{?withpython}"
 BuildRequires: readline-devel, openssl-devel, ncurses-devel, zlib-devel, bzip2-devel
 %else
-BuildRequires: python >= 2.4, python-devel, python-docutils >= 0.5
-Requires: python >= 2.4
+BuildRequires: python >= 2.6, python-devel, python-docutils >= 0.5
+Requires: python >= 2.6
 %endif
 # The hgk extension uses the wish tcl interpreter, but we don't enforce it
 #Requires: tk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packagelib.sh	Sat Jul 18 17:32:38 2015 -0500
@@ -0,0 +1,19 @@
+gethgversion() {
+    make clean
+    make local || make local PURE=--pure
+    HG="$PWD/hg"
+
+    $HG version > /dev/null || { echo 'abort: hg version failed!'; exit 1 ; }
+
+    hgversion=`$HG version | sed -ne 's/.*(version \(.*\))$/\1/p'`
+
+    if echo $hgversion | grep -- '-' > /dev/null 2>&1; then
+        # nightly build case, version is like 1.3.1+250-20b91f91f9ca
+        version=`echo $hgversion | cut -d- -f1`
+        release=`echo $hgversion | cut -d- -f2 | sed -e 's/+.*//'`
+    else
+        # official tag, version is like 1.3.1
+        version=`echo $hgversion | sed -e 's/+.*//'`
+        release='0'
+    fi
+}
--- a/contrib/perf.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/contrib/perf.py	Sat Jul 18 17:32:38 2015 -0500
@@ -6,6 +6,8 @@
 import time, os, sys
 import functools
 
+formatteropts = commands.formatteropts
+
 cmdtable = {}
 command = cmdutil.command(cmdtable)
 
@@ -16,6 +18,7 @@
     place instead of duplicating it in all performance command."""
 
     # enforce an idle period before execution to counteract power management
+    # experimental config: perf.presleep
     time.sleep(ui.configint("perf", "presleep", 1))
 
     if opts is None:
@@ -60,9 +63,9 @@
     fm.write('count',  ' (best of %d)', count)
     fm.plain('\n')
 
-@command('perfwalk')
-def perfwalk(ui, repo, *pats):
-    timer, fm = gettimer(ui)
+@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))))
@@ -74,27 +77,27 @@
             timer(lambda: len(list(cmdutil.walk(repo, pats, {}))))
     fm.end()
 
-@command('perfannotate')
-def perfannotate(ui, repo, f):
-    timer, fm = gettimer(ui)
+@command('perfannotate', formatteropts)
+def perfannotate(ui, repo, f, **opts):
+    timer, fm = gettimer(ui, opts)
     fc = repo['.'][f]
     timer(lambda: len(fc.annotate(True)))
     fm.end()
 
 @command('perfstatus',
          [('u', 'unknown', False,
-           'ask status to look for unknown files')])
+           'ask status to look for unknown files')] + formatteropts)
 def perfstatus(ui, repo, **opts):
     #m = match.always(repo.root, repo.getcwd())
     #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
     #                                                False))))
-    timer, fm = gettimer(ui)
-    timer(lambda: sum(map(len, repo.status(**opts))))
+    timer, fm = gettimer(ui, **opts)
+    timer(lambda: sum(map(len, repo.status(unknown=opts['unknown']))))
     fm.end()
 
-@command('perfaddremove')
-def perfaddremove(ui, repo):
-    timer, fm = gettimer(ui)
+@command('perfaddremove', formatteropts)
+def perfaddremove(ui, repo, **opts):
+    timer, fm = gettimer(ui, opts)
     try:
         oldquiet = repo.ui.quiet
         repo.ui.quiet = True
@@ -113,9 +116,9 @@
         cl._nodecache = {nullid: nullrev}
         cl._nodepos = None
 
-@command('perfheads')
-def perfheads(ui, repo):
-    timer, fm = gettimer(ui)
+@command('perfheads', formatteropts)
+def perfheads(ui, repo, **opts):
+    timer, fm = gettimer(ui, opts)
     cl = repo.changelog
     def d():
         len(cl.headrevs())
@@ -123,11 +126,11 @@
     timer(d)
     fm.end()
 
-@command('perftags')
-def perftags(ui, repo):
+@command('perftags', formatteropts)
+def perftags(ui, repo, **opts):
     import mercurial.changelog
     import mercurial.manifest
-    timer, fm = gettimer(ui)
+    timer, fm = gettimer(ui, opts)
     def t():
         repo.changelog = mercurial.changelog.changelog(repo.svfs)
         repo.manifest = mercurial.manifest.manifest(repo.svfs)
@@ -136,9 +139,9 @@
     timer(t)
     fm.end()
 
-@command('perfancestors')
-def perfancestors(ui, repo):
-    timer, fm = gettimer(ui)
+@command('perfancestors', formatteropts)
+def perfancestors(ui, repo, **opts):
+    timer, fm = gettimer(ui, opts)
     heads = repo.changelog.headrevs()
     def d():
         for a in repo.changelog.ancestors(heads):
@@ -146,9 +149,9 @@
     timer(d)
     fm.end()
 
-@command('perfancestorset')
-def perfancestorset(ui, repo, revset):
-    timer, fm = gettimer(ui)
+@command('perfancestorset', formatteropts)
+def perfancestorset(ui, repo, revset, **opts):
+    timer, fm = gettimer(ui, opts)
     revs = repo.revs(revset)
     heads = repo.changelog.headrevs()
     def d():
@@ -158,9 +161,9 @@
     timer(d)
     fm.end()
 
-@command('perfdirs')
-def perfdirs(ui, repo):
-    timer, fm = gettimer(ui)
+@command('perfdirs', formatteropts)
+def perfdirs(ui, repo, **opts):
+    timer, fm = gettimer(ui, opts)
     dirstate = repo.dirstate
     'a' in dirstate
     def d():
@@ -169,9 +172,9 @@
     timer(d)
     fm.end()
 
-@command('perfdirstate')
-def perfdirstate(ui, repo):
-    timer, fm = gettimer(ui)
+@command('perfdirstate', formatteropts)
+def perfdirstate(ui, repo, **opts):
+    timer, fm = gettimer(ui, opts)
     "a" in repo.dirstate
     def d():
         repo.dirstate.invalidate()
@@ -179,9 +182,9 @@
     timer(d)
     fm.end()
 
-@command('perfdirstatedirs')
-def perfdirstatedirs(ui, repo):
-    timer, fm = gettimer(ui)
+@command('perfdirstatedirs', formatteropts)
+def perfdirstatedirs(ui, repo, **opts):
+    timer, fm = gettimer(ui, opts)
     "a" in repo.dirstate
     def d():
         "a" in repo.dirstate._dirs
@@ -189,9 +192,9 @@
     timer(d)
     fm.end()
 
-@command('perffilefoldmap')
-def perffilefoldmap(ui, repo):
-    timer, fm = gettimer(ui)
+@command('perfdirstatefoldmap', formatteropts)
+def perffilefoldmap(ui, repo, **opts):
+    timer, fm = gettimer(ui, opts)
     dirstate = repo.dirstate
     'a' in dirstate
     def d():
@@ -200,9 +203,9 @@
     timer(d)
     fm.end()
 
-@command('perfdirfoldmap')
-def perfdirfoldmap(ui, repo):
-    timer, fm = gettimer(ui)
+@command('perfdirfoldmap', formatteropts)
+def perfdirfoldmap(ui, repo, **opts):
+    timer, fm = gettimer(ui, opts)
     dirstate = repo.dirstate
     'a' in dirstate
     def d():
@@ -212,9 +215,9 @@
     timer(d)
     fm.end()
 
-@command('perfdirstatewrite')
-def perfdirstatewrite(ui, repo):
-    timer, fm = gettimer(ui)
+@command('perfdirstatewrite', formatteropts)
+def perfdirstatewrite(ui, repo, **opts):
+    timer, fm = gettimer(ui, opts)
     ds = repo.dirstate
     "a" in ds
     def d():
@@ -224,9 +227,9 @@
     fm.end()
 
 @command('perfmergecalculate',
-         [('r', 'rev', '.', 'rev to merge against')])
-def perfmergecalculate(ui, repo, rev):
-    timer, fm = gettimer(ui)
+         [('r', 'rev', '.', 'rev to merge against')] + formatteropts)
+def perfmergecalculate(ui, repo, rev, **opts):
+    timer, fm = gettimer(ui, opts)
     wctx = repo[None]
     rctx = scmutil.revsingle(repo, rev, rev)
     ancestor = wctx.ancestor(rctx)
@@ -242,8 +245,8 @@
     fm.end()
 
 @command('perfpathcopies', [], "REV REV")
-def perfpathcopies(ui, repo, rev1, rev2):
-    timer, fm = gettimer(ui)
+def perfpathcopies(ui, repo, rev1, rev2, **opts):
+    timer, fm = gettimer(ui, opts)
     ctx1 = scmutil.revsingle(repo, rev1, rev1)
     ctx2 = scmutil.revsingle(repo, rev2, rev2)
     def d():
@@ -252,8 +255,8 @@
     fm.end()
 
 @command('perfmanifest', [], 'REV')
-def perfmanifest(ui, repo, rev):
-    timer, fm = gettimer(ui)
+def perfmanifest(ui, repo, rev, **opts):
+    timer, fm = gettimer(ui, opts)
     ctx = scmutil.revsingle(repo, rev, rev)
     t = ctx.manifestnode()
     def d():
@@ -263,9 +266,9 @@
     timer(d)
     fm.end()
 
-@command('perfchangeset')
-def perfchangeset(ui, repo, rev):
-    timer, fm = gettimer(ui)
+@command('perfchangeset', formatteropts)
+def perfchangeset(ui, repo, rev, **opts):
+    timer, fm = gettimer(ui, opts)
     n = repo[rev].node()
     def d():
         repo.changelog.read(n)
@@ -273,10 +276,10 @@
     timer(d)
     fm.end()
 
-@command('perfindex')
-def perfindex(ui, repo):
+@command('perfindex', formatteropts)
+def perfindex(ui, repo, **opts):
     import mercurial.revlog
-    timer, fm = gettimer(ui)
+    timer, fm = gettimer(ui, opts)
     mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
     n = repo["tip"].node()
     def d():
@@ -285,18 +288,18 @@
     timer(d)
     fm.end()
 
-@command('perfstartup')
-def perfstartup(ui, repo):
-    timer, fm = gettimer(ui)
+@command('perfstartup', formatteropts)
+def perfstartup(ui, repo, **opts):
+    timer, fm = gettimer(ui, opts)
     cmd = sys.argv[0]
     def d():
         os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
     timer(d)
     fm.end()
 
-@command('perfparents')
-def perfparents(ui, repo):
-    timer, fm = gettimer(ui)
+@command('perfparents', formatteropts)
+def perfparents(ui, repo, **opts):
+    timer, fm = gettimer(ui, opts)
     nl = [repo.changelog.node(i) for i in xrange(1000)]
     def d():
         for n in nl:
@@ -304,41 +307,45 @@
     timer(d)
     fm.end()
 
-@command('perfctxfiles')
-def perfparents(ui, repo, x):
+@command('perfctxfiles', formatteropts)
+def perfparents(ui, repo, x, **opts):
     x = int(x)
-    timer, fm = gettimer(ui)
+    timer, fm = gettimer(ui, opts)
     def d():
         len(repo[x].files())
     timer(d)
     fm.end()
 
-@command('perfrawfiles')
-def perfparents(ui, repo, x):
+@command('perfrawfiles', formatteropts)
+def perfparents(ui, repo, x, **opts):
     x = int(x)
-    timer, fm = gettimer(ui)
+    timer, fm = gettimer(ui, opts)
     cl = repo.changelog
     def d():
         len(cl.read(x)[3])
     timer(d)
     fm.end()
 
-@command('perflookup')
-def perflookup(ui, repo, rev):
-    timer, fm = gettimer(ui)
+@command('perflookup', formatteropts)
+def perflookup(ui, repo, rev, **opts):
+    timer, fm = gettimer(ui, opts)
+
+@command('perflookup', formatteropts)
+def perflookup(ui, repo, rev, **opts):
+    timer, fm = gettimer(ui, opts)
     timer(lambda: len(repo.lookup(rev)))
     fm.end()
 
-@command('perfrevrange')
-def perfrevrange(ui, repo, *specs):
-    timer, fm = gettimer(ui)
+@command('perfrevrange', formatteropts)
+def perfrevrange(ui, repo, *specs, **opts):
+    timer, fm = gettimer(ui, opts)
     revrange = scmutil.revrange
     timer(lambda: len(revrange(repo, specs)))
     fm.end()
 
-@command('perfnodelookup')
-def perfnodelookup(ui, repo, rev):
-    timer, fm = gettimer(ui)
+@command('perfnodelookup', formatteropts)
+def perfnodelookup(ui, repo, rev, **opts):
+    timer, fm = gettimer(ui, opts)
     import mercurial.revlog
     mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
     n = repo[rev].node()
@@ -350,22 +357,22 @@
     fm.end()
 
 @command('perflog',
-         [('', 'rename', False, 'ask log to follow renames')])
+         [('', 'rename', False, 'ask log to follow renames')] + formatteropts)
 def perflog(ui, repo, **opts):
-    timer, fm = gettimer(ui)
+    timer, fm = gettimer(ui, opts)
     ui.pushbuffer()
     timer(lambda: commands.log(ui, repo, rev=[], date='', user='',
                                copies=opts.get('rename')))
     ui.popbuffer()
     fm.end()
 
-@command('perfmoonwalk')
-def perfmoonwalk(ui, repo):
+@command('perfmoonwalk', formatteropts)
+def perfmoonwalk(ui, repo, **opts):
     """benchmark walking the changelog backwards
 
     This also loads the changelog data for each revision in the changelog.
     """
-    timer, fm = gettimer(ui)
+    timer, fm = gettimer(ui, opts)
     def moonwalk():
         for i in xrange(len(repo), -1, -1):
             ctx = repo[i]
@@ -373,9 +380,9 @@
     timer(moonwalk)
     fm.end()
 
-@command('perftemplating')
-def perftemplating(ui, repo):
-    timer, fm = gettimer(ui)
+@command('perftemplating', formatteropts)
+def perftemplating(ui, repo, **opts):
+    timer, fm = gettimer(ui, opts)
     ui.pushbuffer()
     timer(lambda: commands.log(ui, repo, rev=[], date='', user='',
                                template='{date|shortdate} [{rev}:{node|short}]'
@@ -383,24 +390,24 @@
     ui.popbuffer()
     fm.end()
 
-@command('perfcca')
-def perfcca(ui, repo):
-    timer, fm = gettimer(ui)
+@command('perfcca', formatteropts)
+def perfcca(ui, repo, **opts):
+    timer, fm = gettimer(ui, opts)
     timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
     fm.end()
 
-@command('perffncacheload')
-def perffncacheload(ui, repo):
-    timer, fm = gettimer(ui)
+@command('perffncacheload', formatteropts)
+def perffncacheload(ui, repo, **opts):
+    timer, fm = gettimer(ui, opts)
     s = repo.store
     def d():
         s.fncache._load()
     timer(d)
     fm.end()
 
-@command('perffncachewrite')
-def perffncachewrite(ui, repo):
-    timer, fm = gettimer(ui)
+@command('perffncachewrite', formatteropts)
+def perffncachewrite(ui, repo, **opts):
+    timer, fm = gettimer(ui, opts)
     s = repo.store
     s.fncache._load()
     def d():
@@ -409,9 +416,9 @@
     timer(d)
     fm.end()
 
-@command('perffncacheencode')
-def perffncacheencode(ui, repo):
-    timer, fm = gettimer(ui)
+@command('perffncacheencode', formatteropts)
+def perffncacheencode(ui, repo, **opts):
+    timer, fm = gettimer(ui, opts)
     s = repo.store
     s.fncache._load()
     def d():
@@ -420,10 +427,10 @@
     timer(d)
     fm.end()
 
-@command('perfdiffwd')
-def perfdiffwd(ui, repo):
+@command('perfdiffwd', formatteropts)
+def perfdiffwd(ui, repo, **opts):
     """Profile diff of working directory changes"""
-    timer, fm = gettimer(ui)
+    timer, fm = gettimer(ui, opts)
     options = {
         'w': 'ignore_all_space',
         'b': 'ignore_space_change',
@@ -441,10 +448,10 @@
     fm.end()
 
 @command('perfrevlog',
-         [('d', 'dist', 100, 'distance between the revisions')],
+         [('d', 'dist', 100, 'distance between the revisions')] + formatteropts,
          "[INDEXFILE]")
 def perfrevlog(ui, repo, file_, **opts):
-    timer, fm = gettimer(ui)
+    timer, fm = gettimer(ui, opts)
     from mercurial import revlog
     dist = opts['dist']
     def d():
@@ -456,15 +463,15 @@
     fm.end()
 
 @command('perfrevset',
-         [('C', 'clear', False, 'clear volatile cache between each call.')],
-         "REVSET")
-def perfrevset(ui, repo, expr, clear=False):
+         [('C', 'clear', False, 'clear volatile cache between each call.')]
+         + formatteropts, "REVSET")
+def perfrevset(ui, repo, expr, clear=False, **opts):
     """benchmark the execution time of a revset
 
     Use the --clean option if need to evaluate the impact of build volatile
     revisions set cache on the revset execution. Volatile cache hold filtered
     and obsolete related cache."""
-    timer, fm = gettimer(ui)
+    timer, fm = gettimer(ui, opts)
     def d():
         if clear:
             repo.invalidatevolatilesets()
@@ -472,12 +479,12 @@
     timer(d)
     fm.end()
 
-@command('perfvolatilesets')
-def perfvolatilesets(ui, repo, *names):
+@command('perfvolatilesets', formatteropts)
+def perfvolatilesets(ui, repo, *names, **opts):
     """benchmark the computation of various volatile set
 
     Volatile set computes element related to filtering and obsolescence."""
-    timer, fm = gettimer(ui)
+    timer, fm = gettimer(ui, opts)
     repo = repo.unfiltered()
 
     def getobs(name):
@@ -510,13 +517,13 @@
 @command('perfbranchmap',
          [('f', 'full', False,
            'Includes build time of subset'),
-         ])
-def perfbranchmap(ui, repo, full=False):
+         ] + formatteropts)
+def perfbranchmap(ui, repo, full=False, **opts):
     """benchmark the update of a branchmap
 
     This benchmarks the full repo.branchmap() call with read and write disabled
     """
-    timer, fm = gettimer(ui)
+    timer, fm = gettimer(ui, opts)
     def getbranchmap(filtername):
         """generate a benchmark function for the filtername"""
         if filtername is None:
--- a/contrib/revsetbenchmarks.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/contrib/revsetbenchmarks.py	Sat Jul 18 17:32:38 2015 -0500
@@ -4,21 +4,22 @@
 # defined by parameter. Checkout one by one and run perfrevset with every
 # revset in the list to benchmark its performance.
 #
-# - First argument is a revset of mercurial own repo to runs against.
-# - Second argument is the file from which the revset array will be taken
-#   If second argument is omitted read it from standard input
-#
 # You should run this from the root of your mercurial repository.
 #
-# This script also does one run of the current version of mercurial installed
-# to compare performance.
+# call with --help for details
 
 import sys
 import os
+import re
+import math
 from subprocess import check_call, Popen, CalledProcessError, STDOUT, PIPE
 # cannot use argparse, python 2.7 only
 from optparse import OptionParser
 
+DEFAULTVARIANTS = ['plain', 'min', 'max', 'first', 'last',
+                   'reverse', 'reverse+first', 'reverse+last',
+                   'sort', 'sort+first', 'sort+last']
+
 def check_output(*args, **kwargs):
     kwargs.setdefault('stderr', PIPE)
     kwargs.setdefault('stdout', PIPE)
@@ -32,69 +33,225 @@
     """update the repo to a revision"""
     try:
         check_call(['hg', 'update', '--quiet', '--check', str(rev)])
-    except CalledProcessError, exc:
+    except CalledProcessError as exc:
         print >> sys.stderr, 'update to revision %s failed, aborting' % rev
         sys.exit(exc.returncode)
 
+
+def hg(cmd, repo=None):
+    """run a mercurial command
+
+    <cmd> is the list of command + argument,
+    <repo> is an optional repository path to run this command in."""
+    fullcmd = ['./hg']
+    if repo is not None:
+        fullcmd += ['-R', repo]
+    fullcmd += ['--config',
+                'extensions.perf=' + os.path.join(contribdir, 'perf.py')]
+    fullcmd += cmd
+    return check_output(fullcmd, stderr=STDOUT)
+
 def perf(revset, target=None):
     """run benchmark for this very revset"""
     try:
-        cmd = ['./hg',
-               '--config',
-               'extensions.perf='
-               + os.path.join(contribdir, 'perf.py'),
-               'perfrevset',
-               revset]
-        if target is not None:
-            cmd.append('-R')
-            cmd.append(target)
-        output = check_output(cmd, stderr=STDOUT)
-        output = output.lstrip('!') # remove useless ! in this context
-        return output.strip()
-    except CalledProcessError, exc:
-        print >> sys.stderr, 'abort: cannot run revset benchmark'
-        sys.exit(exc.returncode)
+        output = hg(['perfrevset', revset], repo=target)
+        return parseoutput(output)
+    except CalledProcessError as exc:
+        print >> sys.stderr, 'abort: cannot run revset benchmark: %s' % exc.cmd
+        if exc.output is None:
+            print >> sys.stderr, '(no ouput)'
+        else:
+            print >> sys.stderr, exc.output
+        return None
+
+outputre = re.compile(r'! wall (\d+.\d+) comb (\d+.\d+) user (\d+.\d+) '
+                      'sys (\d+.\d+) \(best of (\d+)\)')
+
+def parseoutput(output):
+    """parse a textual output into a dict
+
+    We cannot just use json because we want to compare with old
+    versions of Mercurial that may not support json output.
+    """
+    match = outputre.search(output)
+    if not match:
+        print >> sys.stderr, 'abort: invalid output:'
+        print >> sys.stderr, output
+        sys.exit(1)
+    return {'comb': float(match.group(2)),
+            'count': int(match.group(5)),
+            'sys': float(match.group(3)),
+            'user': float(match.group(4)),
+            'wall': float(match.group(1)),
+            }
 
 def printrevision(rev):
     """print data about a revision"""
-    sys.stdout.write("Revision: ")
+    sys.stdout.write("Revision ")
     sys.stdout.flush()
     check_call(['hg', 'log', '--rev', str(rev), '--template',
-               '{desc|firstline}\n'])
+                '{if(tags, " ({tags})")} '
+                '{rev}:{node|short}: {desc|firstline}\n'])
+
+def idxwidth(nbidx):
+    """return the max width of number used for index
+
+    This is similar to log10(nbidx), but we use custom code here
+    because we start with zero and we'd rather not deal with all the
+    extra rounding business that log10 would imply.
+    """
+    nbidx -= 1 # starts at 0
+    idxwidth = 0
+    while nbidx:
+        idxwidth += 1
+        nbidx //= 10
+    if not idxwidth:
+        idxwidth = 1
+    return idxwidth
+
+def getfactor(main, other, field, sensitivity=0.05):
+    """return the relative factor between values for 'field' in main and other
+
+    Return None if the factor is insignicant (less than <sensitivity>
+    variation)."""
+    factor = 1
+    if main is not None:
+        factor = other[field] / main[field]
+    low, high = 1 - sensitivity, 1 + sensitivity
+    if (low < factor < high):
+        return None
+    return factor
+
+def formatfactor(factor):
+    """format a factor into a 4 char string
+
+     22%
+    156%
+    x2.4
+     x23
+    x789
+    x1e4
+    x5x7
+
+    """
+    if factor is None:
+        return '    '
+    elif factor < 2:
+        return '%3i%%' % (factor * 100)
+    elif factor < 10:
+        return 'x%3.1f' % factor
+    elif factor < 1000:
+        return '%4s' % ('x%i' % factor)
+    else:
+        order = int(math.log(factor)) + 1
+        while 1 < math.log(factor):
+            factor //= 0
+        return 'x%ix%i' % (factor, order)
+
+def formattiming(value):
+    """format a value to strictly 8 char, dropping some precision if needed"""
+    if value < 10**7:
+        return ('%.6f' % value)[:8]
+    else:
+        # value is HUGE very unlikely to happen (4+ month run)
+        return '%i' % value
+
+_marker = object()
+def printresult(variants, idx, data, maxidx, verbose=False, reference=_marker):
+    """print a line of result to stdout"""
+    mask = '%%0%ii) %%s' % idxwidth(maxidx)
+
+    out = []
+    for var in variants:
+        if data[var] is None:
+            out.append('error   ')
+            out.append(' ' * 4)
+            continue
+        out.append(formattiming(data[var]['wall']))
+        if reference is not _marker:
+            factor = None
+            if reference is not None:
+                factor = getfactor(reference[var], data[var], 'wall')
+            out.append(formatfactor(factor))
+        if verbose:
+            out.append(formattiming(data[var]['comb']))
+            out.append(formattiming(data[var]['user']))
+            out.append(formattiming(data[var]['sys']))
+            out.append('%6d'    % data[var]['count'])
+    print mask % (idx, ' '.join(out))
+
+def printheader(variants, maxidx, verbose=False, relative=False):
+    header = [' ' * (idxwidth(maxidx) + 1)]
+    for var in variants:
+        if not var:
+            var = 'iter'
+        if 8 < len(var):
+            var = var[:3] + '..' + var[-3:]
+        header.append('%-8s' % var)
+        if relative:
+            header.append('    ')
+        if verbose:
+            header.append('%-8s' % 'comb')
+            header.append('%-8s' % 'user')
+            header.append('%-8s' % 'sys')
+            header.append('%6s' % 'count')
+    print ' '.join(header)
 
 def getrevs(spec):
     """get the list of rev matched by a revset"""
     try:
         out = check_output(['hg', 'log', '--template={rev}\n', '--rev', spec])
-    except CalledProcessError, exc:
+    except CalledProcessError as exc:
         print >> sys.stderr, "abort, can't get revision from %s" % spec
         sys.exit(exc.returncode)
     return [r for r in out.split() if r]
 
 
-parser = OptionParser(usage="usage: %prog [options] <revs>")
+def applyvariants(revset, variant):
+    if variant == 'plain':
+        return revset
+    for var in variant.split('+'):
+        revset = '%s(%s)' % (var, revset)
+    return revset
+
+helptext="""This script will run multiple variants of provided revsets using
+different revisions in your mercurial repository. After the benchmark are run
+summary output is provided. Use itto demonstrate speed improvements or pin
+point regressions. Revsets to run are specified in a file (or from stdin), one
+revsets per line. Line starting with '#' will be ignored, allowing insertion of
+comments."""
+parser = OptionParser(usage="usage: %prog [options] <revs>",
+                      description=helptext)
 parser.add_option("-f", "--file",
                   help="read revset from FILE (stdin if omitted)",
                   metavar="FILE")
 parser.add_option("-R", "--repo",
                   help="run benchmark on REPO", metavar="REPO")
 
+parser.add_option("-v", "--verbose",
+                  action='store_true',
+                  help="display all timing data (not just best total time)")
+
+parser.add_option("", "--variants",
+                  default=','.join(DEFAULTVARIANTS),
+                  help="comma separated list of variant to test "
+                       "(eg: plain,min,sorted) (plain = no modification)")
+
 (options, args) = parser.parse_args()
 
-if len(sys.argv) < 2:
+if not args:
     parser.print_help()
     sys.exit(255)
 
 # the directory where both this script and the perf.py extension live.
 contribdir = os.path.dirname(__file__)
 
-target_rev = args[0]
-
 revsetsfile = sys.stdin
 if options.file:
     revsetsfile = open(options.file)
 
 revsets = [l.strip() for l in revsetsfile if not l.startswith('#')]
+revsets = [l for l in revsets if l]
 
 print "Revsets to benchmark"
 print "----------------------------"
@@ -105,8 +262,11 @@
 print "----------------------------"
 print
 
+revs = []
+for a in args:
+    revs.extend(getrevs(a))
 
-revs = getrevs(target_rev)
+variants = options.variants.split(',')
 
 results = []
 for r in revs:
@@ -116,10 +276,16 @@
     update(r)
     res = []
     results.append(res)
+    printheader(variants, len(revsets), verbose=options.verbose)
     for idx, rset in enumerate(revsets):
-        data = perf(rset, target=options.repo)
-        res.append(data)
-        print "%i)" % idx, data
+        varres = {}
+        for var in variants:
+            varrset = applyvariants(rset, var)
+            data = perf(varrset, target=options.repo)
+            varres[var] = data
+        res.append(varres)
+        printresult(variants, idx, varres, len(revsets),
+                    verbose=options.verbose)
         sys.stdout.flush()
     print "----------------------------"
 
@@ -130,7 +296,7 @@
 ================
 """
 
-print 'Revision:', revs
+print 'Revision:'
 for idx, rev in enumerate(revs):
     sys.stdout.write('%i) ' % idx)
     sys.stdout.flush()
@@ -142,6 +308,10 @@
 for ridx, rset in enumerate(revsets):
 
     print "revset #%i: %s" % (ridx, rset)
+    printheader(variants, len(results), verbose=options.verbose, relative=True)
+    ref = None
     for idx, data in enumerate(results):
-        print '%i) %s' % (idx, data[ridx])
+        printresult(variants, idx, data[ridx], len(results),
+                    verbose=options.verbose, reference=ref)
+        ref = data[ridx]
     print
--- a/contrib/revsetbenchmarks.txt	Fri Jul 03 18:10:58 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-all()
-draft()
-::tip
-draft() and ::tip
-::tip and draft()
-0::tip
-roots(0::tip)
-author(lmoscovicz)
-author(mpm)
-author(lmoscovicz) or author(mpm)
-author(mpm) or author(lmoscovicz)
-tip:0
-max(tip:0)
-min(0:tip)
-0::
-min(0::)
-# those two `roots(...)` inputs are close to what phase movement use.
-roots((tip~100::) - (tip~100::tip))
-roots((0::) - (0::tip))
-::p1(p1(tip))::
-public()
-:10000 and public()
-draft()
-:10000 and draft()
-max(::(tip~20) - obsolete())
-roots((0:tip)::)
-(not public() - obsolete())
-(_intlist('20000\x0020001')) and merge()
-parents(20000)
-(20000::) - (20000)
-# The one below is used by rebase
-(children(ancestor(tip~5, tip)) and ::(tip~5))::
--- a/contrib/synthrepo.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/contrib/synthrepo.py	Sat Jul 18 17:32:38 2015 -0500
@@ -41,6 +41,10 @@
 from mercurial.i18n import _
 from mercurial.node import nullrev, nullid, short
 
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 cmdtable = {}
@@ -249,7 +253,7 @@
     '''
     try:
         fp = hg.openpath(ui, descpath)
-    except Exception, err:
+    except Exception as err:
         raise util.Abort('%s: %s' % (descpath, err[0].strerror))
     desc = json.load(fp)
     fp.close()
@@ -281,7 +285,7 @@
     dictfile = opts.get('dict') or '/usr/share/dict/words'
     try:
         fp = open(dictfile, 'rU')
-    except IOError, err:
+    except IOError as err:
         raise util.Abort('%s: %s' % (dictfile, err.strerror))
     words = fp.read().splitlines()
     fp.close()
--- a/contrib/wix/dist.wxs	Fri Jul 03 18:10:58 2015 +0100
+++ b/contrib/wix/dist.wxs	Sat Jul 18 17:32:38 2015 -0500
@@ -7,24 +7,28 @@
   <Fragment>
     <DirectoryRef Id="INSTALLDIR" FileSource="$(var.SourceDir)">
       <Component Id="distOutput" Guid="$(var.dist.guid)" Win64='$(var.IsX64)'>
-        <File Name="library.zip" KeyPath="yes" />
-        <File Name="mercurial.base85.pyd" />
-        <File Name="mercurial.bdiff.pyd" />
-        <File Name="mercurial.diffhelpers.pyd" />
-        <File Name="mercurial.mpatch.pyd" />
-        <File Name="mercurial.osutil.pyd" />
-        <File Name="mercurial.parsers.pyd" />
-        <File Name="pyexpat.pyd" />
-        <File Name="python27.dll" />
-        <File Name="bz2.pyd" />
-        <File Name="select.pyd" />
-        <File Name="unicodedata.pyd" />
-        <File Name="_ctypes.pyd" />
-        <File Name="_elementtree.pyd" />
-        <File Name="_hashlib.pyd" />
-        <File Name="_socket.pyd" />
-        <File Name="_ssl.pyd" />
+        <File Name="python27.dll" KeyPath="yes" />
       </Component>
+      <Directory Id="libdir" Name="lib" FileSource="$(var.SourceDir)/lib">
+        <Component Id="libOutput" Guid="$(var.lib.guid)" Win64='$(var.IsX64)'>
+          <File Name="library.zip" KeyPath="yes" />
+          <File Name="mercurial.base85.pyd" />
+          <File Name="mercurial.bdiff.pyd" />
+          <File Name="mercurial.diffhelpers.pyd" />
+          <File Name="mercurial.mpatch.pyd" />
+          <File Name="mercurial.osutil.pyd" />
+          <File Name="mercurial.parsers.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="_hashlib.pyd" />
+          <File Name="_socket.pyd" />
+          <File Name="_ssl.pyd" />
+        </Component>
+      </Directory>
     </DirectoryRef>
   </Fragment>
 
--- a/contrib/wix/guids.wxi	Fri Jul 03 18:10:58 2015 +0100
+++ b/contrib/wix/guids.wxi	Sat Jul 18 17:32:38 2015 -0500
@@ -9,7 +9,8 @@
   <?define contrib.vim.guid = {BB04903A-652D-4C4F-9590-2BD07A2304F2} ?>
 
   <!-- dist.wxs -->
-  <?define dist.guid = {C3B634A4-1B05-4A40-94A9-38EE853CF693} ?>
+  <?define dist.guid = {CE405FE6-CD1E-4873-9C9A-7683AE5A3D90} ?>
+  <?define lib.guid = {91D53B14-E924-432A-ACA2-65F9B3F7C56A} ?>
 
   <!-- doc.wxs -->
   <?define doc.hg.1.html.guid = {AAAA3FDA-EDC5-4220-B59D-D342722358A2} ?>
@@ -28,6 +29,7 @@
   <?define templates.atom.guid = {D30E14A5-8AF0-4268-8B00-00BEE9E09E39} ?>
   <?define templates.coal.guid = {B63CCAAB-4EAF-43b4-901E-4BD13F5B78FC} ?>
   <?define templates.gitweb.guid = {827334AF-1EFD-421B-962C-5660A068F612} ?>
+  <?define templates.json.guid = {F535BE7A-EC34-46E0-B9BE-013F3DBAFB19} ?>
   <?define templates.monoblue.guid = {8060A1E4-BD4C-453E-92CB-9536DC44A9E3} ?>
   <?define templates.paper.guid = {61AB1DE9-645F-46ED-8AF8-0CF02267FFBB} ?>
   <?define templates.raw.guid = {834DF8D7-9784-43A6-851D-A96CE1B3575B} ?>
--- a/contrib/wix/mercurial.wxs	Fri Jul 03 18:10:58 2015 +0100
+++ b/contrib/wix/mercurial.wxs	Sat Jul 18 17:32:38 2015 -0500
@@ -118,6 +118,7 @@
              Level='1' Absent='disallow' >
         <ComponentRef Id='MainExecutable' />
         <ComponentRef Id='distOutput' />
+        <ComponentRef Id='libOutput' />
         <ComponentRef Id='ProgramMenuDir' />
         <ComponentRef Id='ReadMe' />
         <ComponentRef Id='COPYING' />
--- a/contrib/wix/templates.wxs	Fri Jul 03 18:10:58 2015 +0100
+++ b/contrib/wix/templates.wxs	Sat Jul 18 17:32:38 2015 -0500
@@ -12,6 +12,7 @@
       <ComponentRef Id="templates.atom" />
       <ComponentRef Id="templates.coal" />
       <ComponentRef Id="templates.gitweb" />
+      <ComponentRef Id="templates.json" />
       <ComponentRef Id="templates.monoblue" />
       <ComponentRef Id="templates.paper" />
       <ComponentRef Id="templates.raw" />
@@ -36,6 +37,13 @@
           <File Name="map-cmdline.phases" />
         </Component>
 
+        <Directory Id="templates.jsondir" Name="json">
+          <Component Id="templates.json" Guid="$(var.templates.json.guid)" Win64='$(var.IsX64)'>
+            <File Id="json.changelist.tmpl" Name="changelist.tmpl" KeyPath="yes" />
+            <File Id="json.map"             Name="map" />
+          </Component>
+        </Directory>
+
         <Directory Id="templates.atomdir" Name="atom">
           <Component Id="templates.atom" Guid="$(var.templates.atom.guid)" Win64='$(var.IsX64)'>
             <File Id="atom.changelog.tmpl"      Name="changelog.tmpl" KeyPath="yes" />
--- a/hgext/acl.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/acl.py	Sat Jul 18 17:32:38 2015 -0500
@@ -195,6 +195,10 @@
 from mercurial import util, match
 import getpass, urllib
 
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 def _getusers(ui, group):
@@ -282,6 +286,7 @@
 
     ui.debug('acl: checking access for user "%s"\n' % user)
 
+    # deprecated config: acl.config
     cfg = ui.config('acl', 'config')
     if cfg:
         ui.readconfig(cfg, sections=['acl.groups', 'acl.allow.branches',
--- a/hgext/blackbox.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/blackbox.py	Sat Jul 18 17:32:38 2015 -0500
@@ -35,6 +35,10 @@
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 lastblackbox = None
 
@@ -48,14 +52,14 @@
             def rotate(oldpath, newpath):
                 try:
                     os.unlink(newpath)
-                except OSError, err:
+                except OSError as err:
                     if err.errno != errno.ENOENT:
                         self.debug("warning: cannot remove '%s': %s\n" %
                                    (newpath, err.strerror))
                 try:
                     if newpath:
                         os.rename(oldpath, newpath)
-                except OSError, err:
+                except OSError as err:
                     if err.errno != errno.ENOENT:
                         self.debug("warning: cannot rename '%s' to '%s': %s\n" %
                                    (newpath, oldpath, err.strerror))
@@ -88,7 +92,7 @@
             elif util.safehasattr(self, '_bbopener'):
                 try:
                     self._blackbox = self._openlogfile()
-                except (IOError, OSError), err:
+                except (IOError, OSError) as err:
                     self.debug('warning: cannot write to blackbox.log: %s\n' %
                                err.strerror)
                     del self._bbopener
@@ -106,7 +110,7 @@
                 formattedmsg = msg[0] % msg[1:]
                 try:
                     blackbox.write('%s %s> %s' % (date, user, formattedmsg))
-                except IOError, err:
+                except IOError as err:
                     self.debug('warning: cannot write to blackbox.log: %s\n' %
                                err.strerror)
                 lastblackbox = blackbox
--- a/hgext/bugzilla.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/bugzilla.py	Sat Jul 18 17:32:38 2015 -0500
@@ -279,9 +279,13 @@
 
 from mercurial.i18n import _
 from mercurial.node import short
-from mercurial import cmdutil, mail, templater, util
+from mercurial import cmdutil, mail, util
 import re, time, urlparse, xmlrpclib
 
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 class bzaccess(object):
@@ -353,7 +357,7 @@
         try:
             import MySQLdb as mysql
             bzmysql._MySQLdb = mysql
-        except ImportError, err:
+        except ImportError as err:
             raise util.Abort(_('python mysql support not available: %s') % err)
 
         bzaccess.__init__(self, ui)
@@ -876,8 +880,6 @@
         if not mapfile and not tmpl:
             tmpl = _('changeset {node|short} in repo {root} refers '
                      'to bug {bug}.\ndetails:\n\t{desc|tabindent}')
-        if tmpl:
-            tmpl = templater.parsestring(tmpl, quoted=False)
         t = cmdutil.changeset_templater(self.ui, self.repo,
                                         False, None, tmpl, mapfile, False)
         self.ui.pushbuffer()
@@ -908,5 +910,5 @@
             for bug in bugs:
                 bz.update(bug, bugs[bug], ctx)
             bz.notify(bugs, util.email(ctx.user()))
-    except Exception, e:
+    except Exception as e:
         raise util.Abort(_('Bugzilla error: %s') % e)
--- a/hgext/censor.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/censor.py	Sat Jul 18 17:32:38 2015 -0500
@@ -31,6 +31,10 @@
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 @command('censor',
@@ -43,6 +47,12 @@
     if not rev:
         raise util.Abort(_('must specify revision to censor'))
 
+    wctx = repo[None]
+
+    m = scmutil.match(wctx, (path,))
+    if m.anypats() or len(m.files()) != 1:
+        raise util.Abort(_('can only specify an explicit filename'))
+    path = m.files()[0]
     flog = repo.file(path)
     if not len(flog):
         raise util.Abort(_('cannot censor file with no history'))
@@ -66,7 +76,6 @@
         raise util.Abort(_('cannot censor file in heads (%s)') % headlist,
             hint=_('clean/delete and commit first'))
 
-    wctx = repo[None]
     wp = wctx.parents()
     if ctx.node() in [p.node() for p in wp]:
         raise util.Abort(_('cannot censor working directory'),
@@ -143,7 +152,7 @@
             # Immediate children of censored node must be re-added as fulltext.
             try:
                 revdata = flog.revision(srev)
-            except error.CensoredNodeError, e:
+            except error.CensoredNodeError as e:
                 revdata = e.tombstone
             dlen = rewrite(srev, offset, revdata)
         else:
--- a/hgext/children.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/children.py	Sat Jul 18 17:32:38 2015 -0500
@@ -20,6 +20,10 @@
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 @command('children',
--- a/hgext/churn.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/churn.py	Sat Jul 18 17:32:38 2015 -0500
@@ -9,21 +9,24 @@
 '''command to display statistics about repository history'''
 
 from mercurial.i18n import _
-from mercurial import patch, cmdutil, scmutil, util, templater, commands
+from mercurial import patch, cmdutil, scmutil, util, commands
 from mercurial import encoding
 import os
 import time, datetime
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 def maketemplater(ui, repo, tmpl):
-    tmpl = templater.parsestring(tmpl, quoted=False)
     try:
         t = cmdutil.changeset_templater(ui, repo, False, None, tmpl,
                                         None, False)
-    except SyntaxError, inst:
+    except SyntaxError as inst:
         raise util.Abort(inst.args[0])
     return t
 
--- a/hgext/color.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/color.py	Sat Jul 18 17:32:38 2015 -0500
@@ -84,7 +84,7 @@
   resolve.unresolved = red bold
   resolve.resolved = green bold
 
-  bookmarks.current = green
+  bookmarks.active = green
 
   branches.active = none
   branches.closed = black bold
@@ -162,6 +162,10 @@
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 # start and stop parameters for effects
@@ -190,7 +194,7 @@
 
     try:
         curses.setupterm()
-    except curses.error, e:
+    except curses.error as e:
         _terminfo_params = {}
         return
 
@@ -309,7 +313,7 @@
            'grep.filename': 'magenta',
            'grep.user': 'magenta',
            'grep.date': 'magenta',
-           'bookmarks.current': 'green',
+           'bookmarks.active': 'green',
            'branches.active': 'none',
            'branches.closed': 'black bold',
            'branches.current': 'green',
@@ -492,14 +496,14 @@
     # etc. don't need to be quoted
     mapping.update(dict([(k, k) for k in _effects]))
 
-    thing = templater._evalifliteral(args[1], context, mapping)
+    thing = args[1][0](context, mapping, args[1][1])
 
     # apparently, repo could be a string that is the favicon?
     repo = mapping.get('repo', '')
     if isinstance(repo, str):
         return thing
 
-    label = templater._evalifliteral(args[0], context, mapping)
+    label = args[0][0](context, mapping, args[0][1])
 
     thing = templater.stringify(thing)
     label = templater.stringify(label)
@@ -527,6 +531,7 @@
         return orig(gitsub, commands, env, stream, cwd)
     extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
     extensions.wrapfunction(subrepo.gitsubrepo, '_gitnodir', colorgit)
+    templatelabel.__doc__ = templater.funcs['label'].__doc__
     templater.funcs['label'] = templatelabel
 
 def extsetup(ui):
--- a/hgext/convert/__init__.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/convert/__init__.py	Sat Jul 18 17:32:38 2015 -0500
@@ -15,6 +15,10 @@
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 # Commands definition was moved elsewhere to ease demandload job.
@@ -25,7 +29,7 @@
       _('FILE')),
     ('s', 'source-type', '', _('source repository type'), _('TYPE')),
     ('d', 'dest-type', '', _('destination repository type'), _('TYPE')),
-    ('r', 'rev', '', _('import up to source revision REV'), _('REV')),
+    ('r', 'rev', [], _('import up to source revision REV'), _('REV')),
     ('A', 'authormap', '', _('remap usernames using this file'), _('FILE')),
     ('', 'filemap', '', _('remap file names using contents of file'),
      _('FILE')),
@@ -305,6 +309,10 @@
         is very expensive for large projects, and is only effective when
         ``convert.git.similarity`` is greater than 0. The default is False.
 
+    :convert.git.remoteprefix: remote refs are converted as bookmarks with
+        ``convert.git.remoteprefix`` as a prefix followed by a /. The default
+        is 'remote'.
+
     Perforce Source
     ###############
 
@@ -324,6 +332,23 @@
     Mercurial Destination
     #####################
 
+    The Mercurial destination will recognize Mercurial subrepositories in the
+    destination directory, and update the .hgsubstate file automatically if the
+    destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
+    Converting a repository with subrepositories requires converting a single
+    repository at a time, from the bottom up.
+
+    .. container:: verbose
+
+       An example showing how to convert a repository with subrepositories::
+
+         # so convert knows the type when it sees a non empty destination
+         $ hg init converted
+
+         $ hg convert orig/sub1 converted/sub1
+         $ hg convert orig/sub2 converted/sub2
+         $ hg convert orig converted
+
     The following options are supported:
 
     :convert.hg.clonebranches: dispatch source branches in separate
@@ -334,6 +359,17 @@
 
     :convert.hg.usebranchnames: preserve branch names. The default is
         True.
+
+    :convert.hg.sourcename: records the given string as a 'convert_source' extra
+        value on each commit made in the target repository. The default is None.
+
+    All Destinations
+    ################
+
+    All destination types accept the following options:
+
+    :convert.skiptags: does not convert tags from the source repo to the target
+        repo. The default is False.
     """
     return convcmd.convert(ui, src, dest, revmapfile, **opts)
 
--- a/hgext/convert/bzr.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/convert/bzr.py	Sat Jul 18 17:32:38 2015 -0500
@@ -33,8 +33,8 @@
 class bzr_source(converter_source):
     """Reads Bazaar repositories by using the Bazaar Python libraries"""
 
-    def __init__(self, ui, path, rev=None):
-        super(bzr_source, self).__init__(ui, path, rev=rev)
+    def __init__(self, ui, path, revs=None):
+        super(bzr_source, self).__init__(ui, path, revs=revs)
 
         if not os.path.exists(os.path.join(path, '.bzr')):
             raise NoRepo(_('%s does not look like a Bazaar repository')
@@ -95,20 +95,20 @@
         return self.sourcerepo.find_branches(using=True)
 
     def getheads(self):
-        if not self.rev:
+        if not self.revs:
             # Set using=True to avoid nested repositories (see issue3254)
             heads = sorted([b.last_revision() for b in self._bzrbranches()])
         else:
             revid = None
             for branch in self._bzrbranches():
                 try:
-                    r = RevisionSpec.from_string(self.rev)
+                    r = RevisionSpec.from_string(self.revs[0])
                     info = r.in_history(branch)
                 except errors.BzrError:
                     pass
                 revid = info.rev_id
             if revid is None:
-                raise util.Abort(_('%s is not a valid revision') % self.rev)
+                raise util.Abort(_('%s is not a valid revision') % self.revs[0])
             heads = [revid]
         # Empty repositories return 'null:', which cannot be retrieved
         heads = [h for h in heads if h != 'null:']
--- a/hgext/convert/common.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/convert/common.py	Sat Jul 18 17:32:38 2015 -0500
@@ -7,7 +7,7 @@
 
 import base64, errno, subprocess, os, datetime, re
 import cPickle as pickle
-from mercurial import util
+from mercurial import phases, util
 from mercurial.i18n import _
 
 propertycache = util.propertycache
@@ -44,7 +44,7 @@
 
 class commit(object):
     def __init__(self, author, date, desc, parents, branch=None, rev=None,
-                 extra={}, sortkey=None):
+                 extra={}, sortkey=None, saverev=True, phase=phases.draft):
         self.author = author or 'unknown'
         self.date = date or '0 0'
         self.desc = desc
@@ -53,16 +53,18 @@
         self.rev = rev
         self.extra = extra
         self.sortkey = sortkey
+        self.saverev = saverev
+        self.phase = phase
 
 class converter_source(object):
     """Conversion source interface"""
 
-    def __init__(self, ui, path=None, rev=None):
+    def __init__(self, ui, path=None, revs=None):
         """Initialize conversion source (or raise NoRepo("message")
         exception if path is not a valid repository)"""
         self.ui = ui
         self.path = path
-        self.rev = rev
+        self.revs = revs
 
         self.encoding = 'utf-8'
 
@@ -425,7 +427,7 @@
             return
         try:
             fp = open(self.path, 'r')
-        except IOError, err:
+        except IOError as err:
             if err.errno != errno.ENOENT:
                 raise
             return
@@ -449,7 +451,7 @@
         if self.fp is None:
             try:
                 self.fp = open(self.path, 'a')
-            except IOError, err:
+            except IOError as err:
                 raise util.Abort(_('could not open map file %r: %s') %
                                  (self.path, err.strerror))
         self.fp.write('%s %s\n' % (key, value))
--- a/hgext/convert/convcmd.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/convert/convcmd.py	Sat Jul 18 17:32:38 2015 -0500
@@ -29,6 +29,39 @@
     else:
         return s.decode('utf-8').encode(orig_encoding, 'replace')
 
+def mapbranch(branch, branchmap):
+    '''
+    >>> bmap = {'default': 'branch1'}
+    >>> for i in ['', None]:
+    ...     mapbranch(i, bmap)
+    'branch1'
+    'branch1'
+    >>> bmap = {'None': 'branch2'}
+    >>> for i in ['', None]:
+    ...     mapbranch(i, bmap)
+    'branch2'
+    'branch2'
+    >>> bmap = {'None': 'branch3', 'default': 'branch4'}
+    >>> for i in ['None', '', None, 'default', 'branch5']:
+    ...     mapbranch(i, bmap)
+    'branch3'
+    'branch4'
+    'branch4'
+    'branch4'
+    'branch5'
+    '''
+    # If branch is None or empty, this commit is coming from the source
+    # repository's default branch and destined for the default branch in the
+    # destination repository. For such commits, using a literal "default"
+    # in branchmap below allows the user to map "default" to an alternate
+    # default branch in the destination repository.
+    branch = branchmap.get(branch or 'default', branch)
+    # 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)
+    return branch
+
 source_converters = [
     ('cvs', convert_cvs, 'branchsort'),
     ('git', convert_git, 'branchsort'),
@@ -46,15 +79,15 @@
     ('svn', svn_sink),
     ]
 
-def convertsource(ui, path, type, rev):
+def convertsource(ui, path, type, revs):
     exceptions = []
     if type and type not in [s[0] for s in source_converters]:
         raise util.Abort(_('%s: invalid source repository type') % type)
     for name, source, sortmode in source_converters:
         try:
             if not type or name == type:
-                return source(ui, path, rev), sortmode
-        except (NoRepo, MissingTool), inst:
+                return source(ui, path, revs), sortmode
+        except (NoRepo, MissingTool) as inst:
             exceptions.append(inst)
     if not ui.quiet:
         for inst in exceptions:
@@ -68,9 +101,9 @@
         try:
             if not type or name == type:
                 return sink(ui, path)
-        except NoRepo, inst:
+        except NoRepo as inst:
             ui.note(_("convert: %s\n") % inst)
-        except MissingTool, inst:
+        except MissingTool as inst:
             raise util.Abort('%s\n' % inst)
     raise util.Abort(_('%s: unknown repository type') % path)
 
@@ -377,12 +410,7 @@
     def cachecommit(self, rev):
         commit = self.source.getcommit(rev)
         commit.author = self.authors.get(commit.author, commit.author)
-        # If commit.branch is None, this commit is coming from the source
-        # repository's default branch and destined for the default branch in the
-        # destination repository. For such commits, passing a literal "None"
-        # string to branchmap.get() below allows the user to map "None" to an
-        # alternate default branch in the destination repository.
-        commit.branch = self.branchmap.get(str(commit.branch), commit.branch)
+        commit.branch = mapbranch(commit.branch, self.branchmap)
         self.commitcache[rev] = commit
         return commit
 
@@ -460,22 +488,23 @@
                 self.copy(c)
             self.ui.progress(_('converting'), None)
 
-            tags = self.source.gettags()
-            ctags = {}
-            for k in tags:
-                v = tags[k]
-                if self.map.get(v, SKIPREV) != SKIPREV:
-                    ctags[k] = self.map[v]
+            if not self.ui.configbool('convert', 'skiptags'):
+                tags = self.source.gettags()
+                ctags = {}
+                for k in tags:
+                    v = tags[k]
+                    if self.map.get(v, SKIPREV) != SKIPREV:
+                        ctags[k] = self.map[v]
 
-            if c and ctags:
-                nrev, tagsparent = self.dest.puttags(ctags)
-                if nrev and tagsparent:
-                    # write another hash correspondence to override the previous
-                    # one so we don't end up with extra tag heads
-                    tagsparents = [e for e in self.map.iteritems()
-                                   if e[1] == tagsparent]
-                    if tagsparents:
-                        self.map[tagsparents[0][0]] = nrev
+                if c and ctags:
+                    nrev, tagsparent = self.dest.puttags(ctags)
+                    if nrev and tagsparent:
+                        # write another hash correspondence to override the
+                        # previous one so we don't end up with extra tag heads
+                        tagsparents = [e for e in self.map.iteritems()
+                                       if e[1] == tagsparent]
+                        if tagsparents:
+                            self.map[tagsparents[0][0]] = nrev
 
             bookmarks = self.source.getbookmarks()
             cbookmarks = {}
--- a/hgext/convert/cvs.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/convert/cvs.py	Sat Jul 18 17:32:38 2015 -0500
@@ -15,8 +15,8 @@
 import cvsps
 
 class convert_cvs(converter_source):
-    def __init__(self, ui, path, rev=None):
-        super(convert_cvs, self).__init__(ui, path, rev=rev)
+    def __init__(self, ui, path, revs=None):
+        super(convert_cvs, self).__init__(ui, path, revs=revs)
 
         cvs = os.path.join(path, "CVS")
         if not os.path.exists(cvs):
@@ -41,14 +41,17 @@
         self.changeset = {}
 
         maxrev = 0
-        if self.rev:
+        if self.revs:
+            if len(self.revs) > 1:
+                raise util.Abort(_('cvs source does not support specifying '
+                                   'multiple revs'))
             # TODO: handle tags
             try:
                 # patchset number?
-                maxrev = int(self.rev)
+                maxrev = int(self.revs[0])
             except ValueError:
                 raise util.Abort(_('revision %s is not a patchset number')
-                                 % self.rev)
+                                 % self.revs[0])
 
         d = os.getcwd()
         try:
@@ -136,7 +139,7 @@
                                 passw = part2
                                 break
                         pf.close()
-                    except IOError, inst:
+                    except IOError as inst:
                         if inst.errno != errno.ENOENT:
                             if not getattr(inst, 'filename', None):
                                 inst.filename = cvspass
--- a/hgext/convert/cvsps.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/convert/cvsps.py	Sat Jul 18 17:32:38 2015 -0500
@@ -179,7 +179,7 @@
                     break
 
             ui.note(_('cache has %d log entries\n') % len(oldlog))
-        except Exception, e:
+        except Exception as e:
             ui.note(_('error reading cache: %r\n') % e)
 
         if oldlog:
@@ -824,7 +824,7 @@
                 log += createlog(ui, d, root=opts["root"], cache=cache)
         else:
             log = createlog(ui, root=opts["root"], cache=cache)
-    except logerror, e:
+    except logerror as e:
         ui.write("%r\n"%e)
         return
 
--- a/hgext/convert/darcs.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/convert/darcs.py	Sat Jul 18 17:32:38 2015 -0500
@@ -27,8 +27,8 @@
                 pass
 
 class darcs_source(converter_source, commandline):
-    def __init__(self, ui, path, rev=None):
-        converter_source.__init__(self, ui, path, rev=rev)
+    def __init__(self, ui, path, revs=None):
+        converter_source.__init__(self, ui, path, revs=revs)
         commandline.__init__(self, ui, 'darcs')
 
         # check for _darcs, ElementTree so that we can easily skip
@@ -197,11 +197,11 @@
         try:
             data = util.readfile(path)
             mode = os.lstat(path).st_mode
-        except IOError, inst:
+        except IOError as inst:
             if inst.errno == errno.ENOENT:
                 return None, None
             raise
-        mode = (mode & 0111) and 'x' or ''
+        mode = (mode & 0o111) and 'x' or ''
         return data, mode
 
     def gettags(self):
--- a/hgext/convert/filemap.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/convert/filemap.py	Sat Jul 18 17:32:38 2015 -0500
@@ -156,6 +156,9 @@
         self.origparents = {}
         self.children = {}
         self.seenchildren = {}
+        # experimental config: convert.ignoreancestorcheck
+        self.ignoreancestorcheck = self.ui.configbool('convert',
+                                                      'ignoreancestorcheck')
 
     def before(self):
         self.base.before()
@@ -306,7 +309,7 @@
 
     def getchanges(self, rev, full):
         parents = self.commits[rev].parents
-        if len(parents) > 1:
+        if len(parents) > 1 and not self.ignoreancestorcheck:
             self.rebuild()
 
         # To decide whether we're interested in rev we:
@@ -332,9 +335,11 @@
             mp1 = self.parentmap[p1]
             if mp1 == SKIPREV or mp1 in knownparents:
                 continue
-            isancestor = util.any(p2 for p2 in parents
-                                  if p1 != p2 and mp1 != self.parentmap[p2]
-                                  and mp1 in self.wantedancestors[p2])
+
+            isancestor = (not self.ignoreancestorcheck and
+                          any(p2 for p2 in parents
+                              if p1 != p2 and mp1 != self.parentmap[p2]
+                                 and mp1 in self.wantedancestors[p2]))
             if not isancestor and not hasbranchparent and len(parents) > 1:
                 # This could be expensive, avoid unnecessary calls.
                 if self._cachedcommit(p1).branch == branch:
--- a/hgext/convert/git.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/convert/git.py	Sat Jul 18 17:32:38 2015 -0500
@@ -7,7 +7,7 @@
 
 import os
 import subprocess
-from mercurial import util, config
+from mercurial import util, config, error
 from mercurial.node import hex, nullid
 from mercurial.i18n import _
 
@@ -86,8 +86,8 @@
         data = fh.read()
         return data, fh.close()
 
-    def __init__(self, ui, path, rev=None):
-        super(convert_git, self).__init__(ui, path, rev=rev)
+    def __init__(self, ui, path, revs=None):
+        super(convert_git, self).__init__(ui, path, revs=revs)
 
         if os.path.isdir(path + "/.git"):
             path += "/.git"
@@ -119,14 +119,18 @@
             f.close()
 
     def getheads(self):
-        if not self.rev:
+        if not self.revs:
             heads, ret = self.gitread('git rev-parse --branches --remotes')
             heads = heads.splitlines()
+            if ret:
+                raise util.Abort(_('cannot retrieve git heads'))
         else:
-            heads, ret = self.gitread("git rev-parse --verify %s" % self.rev)
-            heads = [heads[:-1]]
-        if ret:
-            raise util.Abort(_('cannot retrieve git heads'))
+            heads = []
+            for rev in self.revs:
+                rawhead, ret = self.gitread("git rev-parse --verify %s" % rev)
+                heads.append(rawhead[:-1])
+                if ret:
+                    raise util.Abort(_('cannot retrieve git head "%s"') % rev)
         return heads
 
     def catfile(self, rev, type):
@@ -174,8 +178,9 @@
         """
         self.submodules = []
         c = config.config()
-        # Each item in .gitmodules starts with \t that cant be parsed
-        c.parse('.gitmodules', content.replace('\t',''))
+        # Each item in .gitmodules starts with whitespace that cant be parsed
+        c.parse('.gitmodules', '\n'.join(line.strip() for line in
+                               content.split('\n')))
         for sec in c.sections():
             s = c[sec]
             if 'url' in s and 'path' in s:
@@ -184,9 +189,19 @@
     def retrievegitmodules(self, version):
         modules, ret = self.gitread("git show %s:%s" % (version, '.gitmodules'))
         if ret:
-            raise util.Abort(_('cannot read submodules config file in %s') %
-                             version)
-        self.parsegitmodules(modules)
+            # This can happen if a file is in the repo that has permissions
+            # 160000, but there is no .gitmodules file.
+            self.ui.warn(_("warning: cannot read submodules config file in "
+                           "%s\n") % version)
+            return
+
+        try:
+            self.parsegitmodules(modules)
+        except error.ParseError:
+            self.ui.warn(_("warning: unable to parse .gitmodules in %s\n")
+                         % version)
+            return
+
         for m in self.submodules:
             node, ret = self.gitread("git rev-parse %s:%s" % (version, m.path))
             if ret:
@@ -361,8 +376,9 @@
         prefixlen = len(prefix)
 
         # factor two commands
-        gitcmd = { 'remote/': 'git ls-remote --heads origin',
-                          '': 'git show-ref'}
+        remoteprefix = self.ui.config('convert', 'git.remoteprefix', 'remote')
+        gitcmd = { remoteprefix + '/': 'git ls-remote --heads origin',
+                                   '': 'git show-ref'}
 
         # Origin heads
         for reftype in gitcmd:
--- a/hgext/convert/gnuarch.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/convert/gnuarch.py	Sat Jul 18 17:32:38 2015 -0500
@@ -27,8 +27,8 @@
             self.ren_files = {}
             self.ren_dirs = {}
 
-    def __init__(self, ui, path, rev=None):
-        super(gnuarch_source, self).__init__(ui, path, rev=rev)
+    def __init__(self, ui, path, revs=None):
+        super(gnuarch_source, self).__init__(ui, path, revs=revs)
 
         if not os.path.exists(os.path.join(path, '{arch}')):
             raise NoRepo(_("%s does not look like a GNU Arch repository")
@@ -215,7 +215,7 @@
                 mode = ''
         else:
             data = open(os.path.join(self.tmppath, name), 'rb').read()
-            mode = (mode & 0111) and 'x' or ''
+            mode = (mode & 0o111) and 'x' or ''
         return data, mode
 
     def _exclude(self, name):
--- a/hgext/convert/hg.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/convert/hg.py	Sat Jul 18 17:32:38 2015 -0500
@@ -22,8 +22,9 @@
 from mercurial.i18n import _
 from mercurial.node import bin, hex, nullid
 from mercurial import hg, util, context, bookmarks, error, scmutil, exchange
+from mercurial import phases
 
-from common import NoRepo, commit, converter_source, converter_sink
+from common import NoRepo, commit, converter_source, converter_sink, mapfile
 
 import re
 sha1re = re.compile(r'\b[0-9a-f]{12,40}\b')
@@ -41,7 +42,7 @@
                 if not self.repo.local():
                     raise NoRepo(_('%s is not a local Mercurial repository')
                                  % path)
-            except error.RepoError, err:
+            except error.RepoError as err:
                 ui.traceback()
                 raise NoRepo(err.args[0])
         else:
@@ -59,6 +60,7 @@
         self.lock = None
         self.wlock = None
         self.filemapmode = False
+        self.subrevmaps = {}
 
     def before(self):
         self.ui.debug('run hg sink pre-conversion action\n')
@@ -135,6 +137,45 @@
             fp.write('%s %s\n' % (revid, s[1]))
         return fp.getvalue()
 
+    def _rewritesubstate(self, source, data):
+        fp = cStringIO.StringIO()
+        for line in data.splitlines():
+            s = line.split(' ', 1)
+            if len(s) != 2:
+                continue
+
+            revid = s[0]
+            subpath = s[1]
+            if revid != hex(nullid):
+                revmap = self.subrevmaps.get(subpath)
+                if revmap is None:
+                    revmap = mapfile(self.ui,
+                                     self.repo.wjoin(subpath, '.hg/shamap'))
+                    self.subrevmaps[subpath] = revmap
+
+                    # It is reasonable that one or more of the subrepos don't
+                    # need to be converted, in which case they can be cloned
+                    # into place instead of converted.  Therefore, only warn
+                    # once.
+                    msg = _('no ".hgsubstate" updates will be made for "%s"\n')
+                    if len(revmap) == 0:
+                        sub = self.repo.wvfs.reljoin(subpath, '.hg')
+
+                        if self.repo.wvfs.exists(sub):
+                            self.ui.warn(msg % subpath)
+
+                newid = revmap.get(revid)
+                if not newid:
+                    if len(revmap) > 0:
+                        self.ui.warn(_("%s is missing from %s/.hg/shamap\n") %
+                                     (revid, subpath))
+                else:
+                    revid = newid
+
+            fp.write('%s %s\n' % (revid, subpath))
+
+        return fp.getvalue()
+
     def putcommit(self, files, copies, parents, commit, source, revmap, full,
                   cleanp2):
         files = dict(files)
@@ -152,6 +193,8 @@
                 return None
             if f == '.hgtags':
                 data = self._rewritetags(source, revmap, data)
+            if f == '.hgsubstate':
+                data = self._rewritesubstate(source, data)
             return context.memfilectx(self.repo, f, data, 'l' in mode,
                                       'x' in mode, copies.get(f))
 
@@ -182,7 +225,12 @@
 
         extra = commit.extra.copy()
 
-        for label in ('source', 'transplant_source', 'rebase_source'):
+        sourcename = self.repo.ui.config('convert', 'hg.sourcename')
+        if sourcename:
+            extra['convert_source'] = sourcename
+
+        for label in ('source', 'transplant_source', 'rebase_source',
+                      'intermediate-source'):
             node = extra.get(label)
 
             if node is None:
@@ -201,7 +249,7 @@
 
         if self.branchnames and commit.branch:
             extra['branch'] = commit.branch
-        if commit.rev:
+        if commit.rev and commit.saverev:
             extra['convert_revision'] = commit.rev
 
         while parents:
@@ -216,9 +264,31 @@
                 fileset.update(self.repo[p2])
             ctx = context.memctx(self.repo, (p1, p2), text, fileset,
                                  getfilectx, commit.author, commit.date, extra)
-            self.repo.commitctx(ctx)
+
+            # We won't know if the conversion changes the node until after the
+            # commit, so copy the source's phase for now.
+            self.repo.ui.setconfig('phases', 'new-commit',
+                                   phases.phasenames[commit.phase], 'convert')
+
+            tr = self.repo.transaction("convert")
+
+            try:
+                node = hex(self.repo.commitctx(ctx))
+
+                # If the node value has changed, but the phase is lower than
+                # draft, set it back to draft since it hasn't been exposed
+                # anywhere.
+                if commit.rev != node:
+                    ctx = self.repo[node]
+                    if ctx.phase() < phases.draft:
+                        phases.retractboundary(self.repo, tr, phases.draft,
+                                               [ctx.node()])
+                tr.close()
+            finally:
+                tr.release()
+
             text = "(octopus merge fixup)\n"
-            p2 = hex(self.repo.changelog.tip())
+            p2 = node
 
         if self.filemapmode and nparents == 1:
             man = self.repo.manifest
@@ -278,8 +348,8 @@
         ctx = context.memctx(self.repo, (tagparent, None), "update tags",
                              [".hgtags"], getfilectx, "convert-repo", date,
                              extra)
-        self.repo.commitctx(ctx)
-        return hex(self.repo.changelog.tip()), hex(tagparent)
+        node = self.repo.commitctx(ctx)
+        return hex(node), hex(tagparent)
 
     def setfilemapmode(self, active):
         self.filemapmode = active
@@ -306,8 +376,11 @@
         return rev in self.repo
 
 class mercurial_source(converter_source):
-    def __init__(self, ui, path, rev=None):
-        converter_source.__init__(self, ui, path, rev)
+    def __init__(self, ui, path, revs=None):
+        converter_source.__init__(self, ui, path, revs)
+        if revs and len(revs) > 1:
+            raise util.Abort(_("mercurial source does not support specifying "
+                               "multiple revisions"))
         self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
         self.ignored = set()
         self.saverev = ui.configbool('convert', 'hg.saverev', False)
@@ -341,12 +414,12 @@
                 self.keep = children.__contains__
             else:
                 self.keep = util.always
-            if rev:
-                self._heads = [self.repo[rev].node()]
+            if revs:
+                self._heads = [self.repo[revs[0]].node()]
             else:
                 self._heads = self.repo.heads()
         else:
-            if rev or startnode is not None:
+            if revs or startnode is not None:
                 raise util.Abort(_('hg.revs cannot be combined with '
                                    'hg.startrev or --rev'))
             nodes = set()
@@ -421,7 +494,7 @@
                 copies[name] = copysource
             except TypeError:
                 pass
-            except error.LookupError, e:
+            except error.LookupError as e:
                 if not self.ignoreerrors:
                     raise
                 self.ignored.add(name)
@@ -431,15 +504,14 @@
     def getcommit(self, rev):
         ctx = self.changectx(rev)
         parents = [p.hex() for p in self.parents(ctx)]
-        if self.saverev:
-            crev = rev
-        else:
-            crev = None
+        crev = rev
+
         return commit(author=ctx.user(),
                       date=util.datestr(ctx.date(), '%Y-%m-%d %H:%M:%S %1%2'),
                       desc=ctx.description(), rev=crev, parents=parents,
                       branch=ctx.branch(), extra=ctx.extra(),
-                      sortkey=ctx.rev())
+                      sortkey=ctx.rev(), saverev=self.saverev,
+                      phase=ctx.phase())
 
     def gettags(self):
         # This will get written to .hgtags, filter non global tags out.
--- a/hgext/convert/monotone.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/convert/monotone.py	Sat Jul 18 17:32:38 2015 -0500
@@ -13,14 +13,17 @@
 from mercurial.i18n import _
 
 class monotone_source(converter_source, commandline):
-    def __init__(self, ui, path=None, rev=None):
-        converter_source.__init__(self, ui, path, rev)
+    def __init__(self, ui, path=None, revs=None):
+        converter_source.__init__(self, ui, path, revs)
+        if revs and len(revs) > 1:
+            raise util.Abort(_('monotone source does not support specifying '
+                               'multiple revs'))
         commandline.__init__(self, ui, 'mtn')
 
         self.ui = ui
         self.path = path
         self.automatestdio = False
-        self.rev = rev
+        self.revs = revs
 
         norepo = NoRepo(_("%s does not look like a monotone repository")
                         % path)
@@ -219,10 +222,10 @@
     # implement the converter_source interface:
 
     def getheads(self):
-        if not self.rev:
+        if not self.revs:
             return self.mtnrun("leaves").splitlines()
         else:
-            return [self.rev]
+            return self.revs
 
     def getchanges(self, rev, full):
         if full:
--- a/hgext/convert/p4.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/convert/p4.py	Sat Jul 18 17:32:38 2015 -0500
@@ -23,9 +23,23 @@
     except EOFError:
         pass
 
+def decodefilename(filename):
+    """Perforce escapes special characters @, #, *, or %
+    with %40, %23, %2A, or %25 respectively
+
+    >>> decodefilename('portable-net45%252Bnetcore45%252Bwp8%252BMonoAndroid')
+    'portable-net45%2Bnetcore45%2Bwp8%2BMonoAndroid'
+    >>> decodefilename('//Depot/Directory/%2525/%2523/%23%40.%2A')
+    '//Depot/Directory/%25/%23/#@.*'
+    """
+    replacements = [('%2A', '*'), ('%23', '#'), ('%40', '@'), ('%25', '%')]
+    for k, v in replacements:
+        filename = filename.replace(k, v)
+    return filename
+
 class p4_source(converter_source):
-    def __init__(self, ui, path, rev=None):
-        super(p4_source, self).__init__(ui, path, rev=rev)
+    def __init__(self, ui, path, revs=None):
+        super(p4_source, self).__init__(ui, path, revs=revs)
 
         if "/" in path and not path.startswith('//'):
             raise NoRepo(_('%s does not look like a P4 repository') % path)
@@ -36,11 +50,13 @@
         self.heads = {}
         self.changeset = {}
         self.files = {}
+        self.copies = {}
         self.tags = {}
         self.lastbranch = {}
         self.parent = {}
         self.encoding = "latin_1"
         self.depotname = {}           # mapping from local name to depot name
+        self.localname = {} # mapping from depot name to local name
         self.re_type = re.compile(
             "([a-z]+)?(text|binary|symlink|apple|resource|unicode|utf\d+)"
             "(\+\w+)?$")
@@ -49,6 +65,9 @@
             r":[^$\n]*\$")
         self.re_keywords_old = re.compile("\$(Id|Header):[^$\n]*\$")
 
+        if revs and len(revs) > 1:
+            raise util.Abort(_("p4 source does not support specifying "
+                               "multiple revisions"))
         self._parse(ui, path)
 
     def _parse_view(self, path):
@@ -99,7 +118,7 @@
         startrev = self.ui.config('convert', 'p4.startrev', default=0)
         self.p4changes = [x for x in self.p4changes
                           if ((not startrev or int(x) >= int(startrev)) and
-                              (not self.rev or int(x) <= int(self.rev)))]
+                              (not self.revs or int(x) <= int(self.revs[0])))]
 
         # now read the full changelists to get the list of file revisions
         ui.status(_('collecting p4 changelists\n'))
@@ -121,24 +140,65 @@
             date = (int(d["time"]), 0)     # timezone not set
             c = commit(author=self.recode(d["user"]),
                        date=util.datestr(date, '%Y-%m-%d %H:%M:%S %1%2'),
-                       parents=parents, desc=desc, branch='',
+                       parents=parents, desc=desc, branch=None,
                        extra={"p4": change})
 
             files = []
+            copies = {}
+            copiedfiles = []
             i = 0
             while ("depotFile%d" % i) in d and ("rev%d" % i) in d:
                 oldname = d["depotFile%d" % i]
                 filename = None
                 for v in vieworder:
-                    if oldname.startswith(v):
-                        filename = views[v] + oldname[len(v):]
+                    if oldname.lower().startswith(v.lower()):
+                        filename = decodefilename(views[v] + oldname[len(v):])
                         break
                 if filename:
                     files.append((filename, d["rev%d" % i]))
                     self.depotname[filename] = oldname
+                    if (d.get("action%d" % i) == "move/add"):
+                        copiedfiles.append(filename)
+                    self.localname[oldname] = filename
                 i += 1
+
+            # Collect information about copied files
+            for filename in copiedfiles:
+                oldname = self.depotname[filename]
+
+                flcmd = 'p4 -G filelog %s' \
+                      % util.shellquote(oldname)
+                flstdout = util.popen(flcmd, mode='rb')
+
+                copiedfilename = None
+                for d in loaditer(flstdout):
+                    copiedoldname = None
+
+                    i = 0
+                    while ("change%d" % i) in d:
+                        if (d["change%d" % i] == change and
+                            d["action%d" % i] == "move/add"):
+                            j = 0
+                            while ("file%d,%d" % (i, j)) in d:
+                                if d["how%d,%d" % (i, j)] == "moved from":
+                                    copiedoldname = d["file%d,%d" % (i, j)]
+                                    break
+                                j += 1
+                        i += 1
+
+                    if copiedoldname and copiedoldname in self.localname:
+                        copiedfilename = self.localname[copiedoldname]
+                        break
+
+                if copiedfilename:
+                    copies[filename] = copiedfilename
+                else:
+                    ui.warn(_("cannot find source for copied file: %s@%s\n")
+                            % (filename, change))
+
             self.changeset[change] = c
             self.files[change] = files
+            self.copies[change] = copies
             lastid = change
 
         if lastid:
@@ -150,38 +210,54 @@
     def getfile(self, name, rev):
         cmd = 'p4 -G print %s' \
             % util.shellquote("%s#%s" % (self.depotname[name], rev))
-        stdout = util.popen(cmd, mode='rb')
 
-        mode = None
-        contents = ""
-        keywords = None
+        lasterror = None
+        while True:
+            stdout = util.popen(cmd, mode='rb')
+
+            mode = None
+            contents = ""
+            keywords = None
 
-        for d in loaditer(stdout):
-            code = d["code"]
-            data = d.get("data")
+            for d in loaditer(stdout):
+                code = d["code"]
+                data = d.get("data")
 
-            if code == "error":
-                raise IOError(d["generic"], data)
+                if code == "error":
+                    # if this is the first time error happened
+                    # re-attempt getting the file
+                    if not lasterror:
+                        lasterror = IOError(d["generic"], data)
+                        # this will exit inner-most for-loop
+                        break
+                    else:
+                        raise lasterror
 
-            elif code == "stat":
-                action = d.get("action")
-                if action in ["purge", "delete", "move/delete"]:
-                    return None, None
-                p4type = self.re_type.match(d["type"])
-                if p4type:
-                    mode = ""
-                    flags = (p4type.group(1) or "") + (p4type.group(3) or "")
-                    if "x" in flags:
-                        mode = "x"
-                    if p4type.group(2) == "symlink":
-                        mode = "l"
-                    if "ko" in flags:
-                        keywords = self.re_keywords_old
-                    elif "k" in flags:
-                        keywords = self.re_keywords
+                elif code == "stat":
+                    action = d.get("action")
+                    if action in ["purge", "delete", "move/delete"]:
+                        return None, None
+                    p4type = self.re_type.match(d["type"])
+                    if p4type:
+                        mode = ""
+                        flags = ((p4type.group(1) or "")
+                               + (p4type.group(3) or ""))
+                        if "x" in flags:
+                            mode = "x"
+                        if p4type.group(2) == "symlink":
+                            mode = "l"
+                        if "ko" in flags:
+                            keywords = self.re_keywords_old
+                        elif "k" in flags:
+                            keywords = self.re_keywords
 
-            elif code == "text" or code == "binary":
-                contents += data
+                elif code == "text" or code == "binary":
+                    contents += data
+
+                lasterror = None
+
+            if not lasterror:
+                break
 
         if mode is None:
             return None, None
@@ -196,7 +272,7 @@
     def getchanges(self, rev, full):
         if full:
             raise util.Abort(_("convert from p4 do not support --full"))
-        return self.files[rev], {}, set()
+        return self.files[rev], self.copies[rev], set()
 
     def getcommit(self, rev):
         return self.changeset[rev]
--- a/hgext/convert/subversion.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/convert/subversion.py	Sat Jul 18 17:32:38 2015 -0500
@@ -126,7 +126,7 @@
     except IOError:
         # Caller may interrupt the iteration
         pickle.dump(None, fp, protocol)
-    except Exception, inst:
+    except Exception as inst:
         pickle.dump(str(inst), fp, protocol)
     else:
         pickle.dump(None, fp, protocol)
@@ -216,7 +216,7 @@
         opener = urllib2.build_opener()
         rsp = opener.open('%s://%s/!svn/ver/0/.svn' % (proto, path))
         data = rsp.read()
-    except urllib2.HTTPError, inst:
+    except urllib2.HTTPError as inst:
         if inst.code != 404:
             # Except for 404 we cannot know for sure this is not an svn repo
             ui.warn(_('svn: cannot probe remote repository, assume it could '
@@ -268,8 +268,8 @@
 # the parent module. A revision has at most one parent.
 #
 class svn_source(converter_source):
-    def __init__(self, ui, url, rev=None):
-        super(svn_source, self).__init__(ui, url, rev=rev)
+    def __init__(self, ui, url, revs=None):
+        super(svn_source, self).__init__(ui, url, revs=revs)
 
         if not (url.startswith('svn://') or url.startswith('svn+ssh://') or
                 (os.path.exists(url) and
@@ -325,11 +325,15 @@
                            "to libsvn version %s")
                          % (self.url, svnversion))
 
-        if rev:
+        if revs:
+            if len(revs) > 1:
+                raise util.Abort(_('subversion source does not support '
+                                   'specifying multiple revisions'))
             try:
-                latest = int(rev)
+                latest = int(revs[0])
             except ValueError:
-                raise util.Abort(_('svn: revision %s is not an integer') % rev)
+                raise util.Abort(_('svn: revision %s is not an integer') %
+                                 revs[0])
 
         self.trunkname = self.ui.config('convert', 'svn.trunk',
                                         'trunk').strip('/')
@@ -944,7 +948,8 @@
                             firstcset.parents.append(latest)
                 except SvnPathNotFound:
                     pass
-        except SubversionException, (inst, num):
+        except SubversionException as xxx_todo_changeme:
+            (inst, num) = xxx_todo_changeme.args
             if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
                 raise util.Abort(_('svn: branch has no revision %s')
                                  % to_revnum)
@@ -970,7 +975,7 @@
                 info = info[-1]
             mode = ("svn:executable" in info) and 'x' or ''
             mode = ("svn:special" in info) and 'l' or mode
-        except SubversionException, e:
+        except SubversionException as e:
             notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
                 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
             if e.apr_err in notfound: # File not found
--- a/hgext/convert/transport.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/convert/transport.py	Sat Jul 18 17:32:38 2015 -0500
@@ -87,7 +87,8 @@
                 self.ra = svn.client.open_ra_session(
                     self.svn_url,
                     self.client, self.pool)
-            except SubversionException, (inst, num):
+            except SubversionException as xxx_todo_changeme:
+                (inst, num) = xxx_todo_changeme.args
                 if num in (svn.core.SVN_ERR_RA_ILLEGAL_URL,
                            svn.core.SVN_ERR_RA_LOCAL_REPOS_OPEN_FAILED,
                            svn.core.SVN_ERR_BAD_URL):
--- a/hgext/eol.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/eol.py	Sat Jul 18 17:32:38 2015 -0500
@@ -95,6 +95,10 @@
 from mercurial import util, config, extensions, match, error
 import re, os
 
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 # Matches a lone LF, i.e., one that is not part of CRLF.
@@ -214,7 +218,7 @@
                 return eolfile(ui, repo.root, data)
             except (IOError, LookupError):
                 pass
-    except error.ParseError, inst:
+    except error.ParseError as inst:
         ui.warn(_("warning: ignoring .hgeol file due to parse error "
                   "at %s: %s\n") % (inst.args[1], inst.args[0]))
     return None
--- a/hgext/extdiff.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/extdiff.py	Sat Jul 18 17:32:38 2015 -0500
@@ -63,13 +63,18 @@
 from mercurial.i18n import _
 from mercurial.node import short, nullid
 from mercurial import cmdutil, scmutil, util, commands, encoding, filemerge
+from mercurial import archival
 import os, shlex, shutil, tempfile, re
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
-def snapshot(ui, repo, files, node, tmproot):
+def snapshot(ui, repo, files, node, tmproot, listsubrepos):
     '''snapshot files as of some revision
     if not using snapshot, -I/-X does not work and recursive diff
     in tools like kdiff3 and meld displays too many files.'''
@@ -80,33 +85,35 @@
         dirname = '%s.%s' % (dirname, short(node))
     base = os.path.join(tmproot, dirname)
     os.mkdir(base)
+    fns_and_mtime = []
+
     if node is not None:
         ui.note(_('making snapshot of %d files from rev %s\n') %
                 (len(files), short(node)))
     else:
         ui.note(_('making snapshot of %d files from working directory\n') %
             (len(files)))
-    wopener = scmutil.opener(base)
-    fns_and_mtime = []
-    ctx = repo[node]
-    for fn in sorted(files):
-        wfn = util.pconvert(fn)
-        if wfn not in ctx:
-            # File doesn't exist; could be a bogus modify
-            continue
-        ui.note('  %s\n' % wfn)
-        dest = os.path.join(base, wfn)
-        fctx = ctx[wfn]
-        data = repo.wwritedata(wfn, fctx.data())
-        if 'l' in fctx.flags():
-            wopener.symlink(data, wfn)
-        else:
-            wopener.write(wfn, data)
-            if 'x' in fctx.flags():
-                util.setflags(dest, False, True)
-        if node is None:
-            fns_and_mtime.append((dest, repo.wjoin(fn),
-                                  os.lstat(dest).st_mtime))
+
+    if files:
+        repo.ui.setconfig("ui", "archivemeta", False)
+
+        archival.archive(repo, base, node, 'files',
+                         matchfn=scmutil.matchfiles(repo, files),
+                         subrepos=listsubrepos)
+
+        ctx = repo[node]
+        for fn in sorted(files):
+            wfn = util.pconvert(fn)
+            if wfn not in ctx:
+                # File doesn't exist; could be a bogus modify
+                continue
+            ui.note('  %s\n' % wfn)
+
+            if node is None:
+                dest = os.path.join(base, wfn)
+
+                fns_and_mtime.append((dest, repo.wjoin(fn),
+                                      os.lstat(dest).st_mtime))
     return dirname, fns_and_mtime
 
 def dodiff(ui, repo, cmdline, pats, opts):
@@ -140,10 +147,14 @@
         if node1b == nullid:
             do3way = False
 
+    subrepos=opts.get('subrepos')
+
     matcher = scmutil.match(repo[node2], pats, opts)
-    mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3])
+    mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher,
+                                               listsubrepos=subrepos)[:3])
     if do3way:
-        mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher)[:3])
+        mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher,
+                                                   listsubrepos=subrepos)[:3])
     else:
         mod_b, add_b, rem_b = set(), set(), set()
     modadd = mod_a | add_a | mod_b | add_b
@@ -155,11 +166,12 @@
     try:
         # Always make a copy of node1a (and node1b, if applicable)
         dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
-        dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0]
+        dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot, subrepos)[0]
         rev1a = '@%d' % repo[node1a].rev()
         if do3way:
             dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
-            dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot)[0]
+            dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot,
+                             subrepos)[0]
             rev1b = '@%d' % repo[node1b].rev()
         else:
             dir1b = None
@@ -171,14 +183,15 @@
         dir2root = ''
         rev2 = ''
         if node2:
-            dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0]
+            dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0]
             rev2 = '@%d' % repo[node2].rev()
         elif len(common) > 1:
             #we only actually need to get the files to copy back to
             #the working dir in this case (because the other cases
             #are: diffing 2 revisions or single file -- in which case
             #the file is already directly passed to the diff tool).
-            dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot)
+            dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot,
+                                           subrepos)
         else:
             # This lets the diff tool open the changed file directly
             dir2 = ''
@@ -246,7 +259,7 @@
      _('pass option to comparison program'), _('OPT')),
     ('r', 'rev', [], _('revision'), _('REV')),
     ('c', 'change', '', _('change made by revision'), _('REV')),
-    ] + commands.walkopts,
+    ] + commands.walkopts + commands.subrepoopts,
     _('hg extdiff [OPT]... [FILE]...'),
     inferrepo=True)
 def extdiff(ui, repo, *pats, **opts):
--- a/hgext/factotum.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/factotum.py	Sat Jul 18 17:32:38 2015 -0500
@@ -67,21 +67,20 @@
     while True:
         fd = os.open('%s/rpc' % _mountpoint, os.O_RDWR)
         try:
-            try:
-                os.write(fd, 'start %s' % params)
-                l = os.read(fd, ERRMAX).split()
-                if l[0] == 'ok':
-                    os.write(fd, 'read')
-                    status, user, passwd = os.read(fd, ERRMAX).split(None, 2)
-                    if status == 'ok':
-                        if passwd.startswith("'"):
-                            if passwd.endswith("'"):
-                                passwd = passwd[1:-1].replace("''", "'")
-                            else:
-                                raise util.Abort(_('malformed password string'))
-                        return (user, passwd)
-            except (OSError, IOError):
-                raise util.Abort(_('factotum not responding'))
+            os.write(fd, 'start %s' % params)
+            l = os.read(fd, ERRMAX).split()
+            if l[0] == 'ok':
+                os.write(fd, 'read')
+                status, user, passwd = os.read(fd, ERRMAX).split(None, 2)
+                if status == 'ok':
+                    if passwd.startswith("'"):
+                        if passwd.endswith("'"):
+                            passwd = passwd[1:-1].replace("''", "'")
+                        else:
+                            raise util.Abort(_('malformed password string'))
+                    return (user, passwd)
+        except (OSError, IOError):
+            raise util.Abort(_('factotum not responding'))
         finally:
             os.close(fd)
         getkey(self, params)
--- a/hgext/fetch.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/fetch.py	Sat Jul 18 17:32:38 2015 -0500
@@ -15,6 +15,10 @@
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 @command('fetch',
--- a/hgext/gpg.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/gpg.py	Sat Jul 18 17:32:38 2015 -0500
@@ -12,6 +12,10 @@
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 class gpg(object):
@@ -213,6 +217,9 @@
     If no revision is given, the parent of the working directory is used,
     or tip if no revision is checked out.
 
+    The ``gpg.cmd`` config setting can be used to specify the command
+    to run. A default key can be specified with ``gpg.key``.
+
     See :hg:`help dates` for a list of formats valid for -d/--date.
     """
 
@@ -255,7 +262,7 @@
 
     if not opts["force"]:
         msigs = match.exact(repo.root, '', ['.hgsigs'])
-        if util.any(repo.status(match=msigs, unknown=True, ignored=True)):
+        if any(repo.status(match=msigs, unknown=True, ignored=True)):
             raise util.Abort(_("working copy of .hgsigs is changed "),
                              hint=_("please commit .hgsigs manually"))
 
@@ -279,7 +286,7 @@
         editor = cmdutil.getcommiteditor(editform='gpg.sign', **opts)
         repo.commit(message, opts['user'], opts['date'], match=msigs,
                     editor=editor)
-    except ValueError, inst:
+    except ValueError as inst:
         raise util.Abort(str(inst))
 
 def shortkey(ui, key):
--- a/hgext/graphlog.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/graphlog.py	Sat Jul 18 17:32:38 2015 -0500
@@ -20,6 +20,10 @@
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 @command('glog',
--- a/hgext/hgcia.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/hgcia.py	Sat Jul 18 17:32:38 2015 -0500
@@ -43,11 +43,15 @@
 
 from mercurial.i18n import _
 from mercurial.node import bin, short
-from mercurial import cmdutil, patch, templater, util, mail
+from mercurial import cmdutil, patch, util, mail
 import email.Parser
 
 import socket, xmlrpclib
 from xml.sax import saxutils
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 socket_timeout = 30 # seconds
@@ -206,7 +210,6 @@
                 template = self.dstemplate
             else:
                 template = self.deftemplate
-        template = templater.parsestring(template, quoted=False)
         t = cmdutil.changeset_templater(self.ui, self.repo, False, None,
                                         template, style, False)
         self.templater = t
--- a/hgext/hgk.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/hgk.py	Sat Jul 18 17:32:38 2015 -0500
@@ -22,7 +22,7 @@
 the path to hgk in your configuration file::
 
   [hgk]
-  path=/location/of/hgk
+  path = /location/of/hgk
 
 hgk can make use of the extdiff extension to visualize revisions.
 Assuming you had already configured extdiff vdiff command, just add::
@@ -41,6 +41,10 @@
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 @command('debug-diff-tree',
--- a/hgext/highlight/__init__.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/highlight/__init__.py	Sat Jul 18 17:32:38 2015 -0500
@@ -24,9 +24,13 @@
 import highlight
 from mercurial.hgweb import webcommands, webutil, common
 from mercurial import extensions, encoding
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
-def filerevision_highlight(orig, web, tmpl, fctx):
+def filerevision_highlight(orig, web, req, tmpl, fctx):
     mt = ''.join(tmpl('mimetype', encoding=encoding.encoding))
     # only pygmentize for mimetype containing 'html' so we both match
     # 'text/html' and possibly 'application/xhtml+xml' in the future
@@ -38,7 +42,7 @@
     if 'html' in mt:
         style = web.config('web', 'pygments_style', 'colorful')
         highlight.pygmentize('fileline', fctx, style, tmpl)
-    return orig(web, tmpl, fctx)
+    return orig(web, req, tmpl, fctx)
 
 def annotate_highlight(orig, web, req, tmpl):
     mt = ''.join(tmpl('mimetype', encoding=encoding.encoding))
--- a/hgext/histedit.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/histedit.py	Sat Jul 18 17:32:38 2015 -0500
@@ -181,6 +181,10 @@
 cmdtable = {}
 command = cmdutil.command(cmdtable)
 
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 # i18n: command names and abbreviations must remain untranslated
@@ -218,7 +222,7 @@
         """Load histedit state from disk and set fields appropriately."""
         try:
             fp = self.repo.vfs('histedit-state', 'r')
-        except IOError, err:
+        except IOError as err:
             if err.errno != errno.ENOENT:
                 raise
             raise util.Abort(_('no histedit in progress'))
@@ -381,7 +385,7 @@
 
     - Add a 'histedit_source' entry in extra.
 
-    Note that fold have its own separated logic because its handling is a bit
+    Note that fold has its own separated logic because its handling is a bit
     different and not easily factored out of the fold method.
     """
     phasemin = src.phase()
@@ -429,6 +433,10 @@
     ctxs = list(repo.set('%d::%d', first, last))
     if not ctxs:
         return None
+    for c in ctxs:
+        if not c.mutable():
+            raise util.Abort(
+                _("cannot fold into public change %s") % node.short(c.node()))
     base = first.parents()[0]
 
     # commit a new version of the old changeset, including the update
@@ -707,15 +715,15 @@
     if force and not outg:
         raise util.Abort(_('--force only allowed with --outgoing'))
     if cont:
-        if util.any((outg, abort, revs, freeargs, rules, editplan)):
+        if any((outg, abort, revs, freeargs, rules, editplan)):
             raise util.Abort(_('no arguments allowed with --continue'))
         goal = 'continue'
     elif abort:
-        if util.any((outg, revs, freeargs, rules, editplan)):
+        if any((outg, revs, freeargs, rules, editplan)):
             raise util.Abort(_('no arguments allowed with --abort'))
         goal = 'abort'
     elif editplan:
-        if util.any((outg, revs, freeargs)):
+        if any((outg, revs, freeargs)):
             raise util.Abort(_('only --commands argument allowed with '
                                '--edit-plan'))
         goal = 'edit-plan'
@@ -732,6 +740,7 @@
         else:
             revs.extend(freeargs)
             if len(revs) == 0:
+                # experimental config: histedit.defaultrev
                 histeditdefault = ui.config('histedit', 'defaultrev')
                 if histeditdefault:
                     revs.append(histeditdefault)
@@ -742,6 +751,7 @@
 
     replacements = []
     state.keep = opts.get('keep', False)
+    supportsmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
 
     # rebuild state
     if goal == 'continue':
@@ -788,8 +798,13 @@
                 break
         else:
             pass
-        cleanupnode(ui, repo, 'created', tmpnodes)
-        cleanupnode(ui, repo, 'temp', leafs)
+        if supportsmarkers:
+            obsolete.createmarkers(repo,
+                                   ((repo[t],()) for t in sorted(tmpnodes)))
+            obsolete.createmarkers(repo, ((repo[t],()) for t in sorted(leafs)))
+        else:
+            cleanupnode(ui, repo, 'created', tmpnodes)
+            cleanupnode(ui, repo, 'temp', leafs)
         state.clear()
         return
     else:
@@ -873,7 +888,7 @@
         if mapping:
             movebookmarks(ui, repo, mapping, state.topmost, ntm)
             # TODO update mq state
-        if obsolete.isenabled(repo, obsolete.createmarkersopt):
+        if supportsmarkers:
             markers = []
             # sort by revision number because it sound "right"
             for prec in sorted(mapping, key=repo.changelog.rev):
@@ -884,8 +899,10 @@
                 obsolete.createmarkers(repo, markers)
         else:
             cleanupnode(ui, repo, 'replaced', mapping)
-
-    cleanupnode(ui, repo, 'temp', tmpnodes)
+    if supportsmarkers:
+        obsolete.createmarkers(repo, ((repo[t],()) for t in sorted(tmpnodes)))
+    else:
+        cleanupnode(ui, repo, 'temp', tmpnodes)
     state.clear()
     if os.path.exists(repo.sjoin('undo')):
         os.unlink(repo.sjoin('undo'))
@@ -924,7 +941,8 @@
             raise util.Abort(_('cannot edit history that contains merges'))
         root = ctxs[0] # list is already sorted by repo.set
         if not root.mutable():
-            raise util.Abort(_('cannot edit immutable changeset: %s') % root)
+            raise util.Abort(_('cannot edit public changeset: %s') % root,
+                             hint=_('see "hg help phases" for details'))
     return [c.node() for c in ctxs]
 
 def makedesc(repo, action, rev):
--- a/hgext/keyword.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/keyword.py	Sat Jul 18 17:32:38 2015 -0500
@@ -83,7 +83,7 @@
 '''
 
 from mercurial import commands, context, cmdutil, dispatch, filelog, extensions
-from mercurial import localrepo, match, patch, templatefilters, templater, util
+from mercurial import localrepo, match, patch, templatefilters, util
 from mercurial import scmutil, pathutil
 from mercurial.hgweb import webcommands
 from mercurial.i18n import _
@@ -91,6 +91,10 @@
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 # hg commands that do not act on keywords
@@ -191,8 +195,7 @@
 
         kwmaps = self.ui.configitems('keywordmaps')
         if kwmaps: # override default templates
-            self.templates = dict((k, templater.parsestring(v, False))
-                                  for k, v in kwmaps)
+            self.templates = dict(kwmaps)
         else:
             self.templates = _defaultkwmaps(self.ui)
 
@@ -457,9 +460,7 @@
     repo.commit(text=msg)
     ui.status(_('\n\tkeywords expanded\n'))
     ui.write(repo.wread(fn))
-    for root, dirs, files in os.walk(tmpdir):
-        for f in files:
-            util.unlinkpath(repo.vfs.reljoin(root, f))
+    repo.wvfs.rmtree(repo.root)
 
 @command('kwexpand',
     commands.walkopts,
--- a/hgext/largefiles/__init__.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/largefiles/__init__.py	Sat Jul 18 17:32:38 2015 -0500
@@ -112,6 +112,10 @@
 import reposetup
 import uisetup as uisetupmod
 
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 reposetup = reposetup.reposetup
--- a/hgext/largefiles/basestore.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/largefiles/basestore.py	Sat Jul 18 17:32:38 2015 -0500
@@ -96,7 +96,7 @@
 
         try:
             gothash = self._getfile(tmpfile, filename, hash)
-        except StoreError, err:
+        except StoreError as err:
             self.ui.warn(err.longmessage())
             gothash = ""
         tmpfile.close()
--- a/hgext/largefiles/lfcommands.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/largefiles/lfcommands.py	Sat Jul 18 17:32:38 2015 -0500
@@ -16,6 +16,9 @@
 from mercurial.i18n import _
 from mercurial.lock import release
 
+from hgext.convert import convcmd
+from hgext.convert import filemap
+
 import lfutil
 import basestore
 
@@ -70,12 +73,6 @@
     success = False
     dstwlock = dstlock = None
     try:
-        # Lock destination to prevent modification while it is converted to.
-        # Don't need to lock src because we are just reading from its history
-        # which can't change.
-        dstwlock = rdst.wlock()
-        dstlock = rdst.lock()
-
         # Get a list of all changesets in the source.  The easy way to do this
         # is to simply walk the changelog, using changelog.nodesbetween().
         # Take a look at mercurial/revlog.py:639 for more details.
@@ -84,6 +81,12 @@
             rsrc.heads())[0])
         revmap = {node.nullid: node.nullid}
         if tolfile:
+            # Lock destination to prevent modification while it is converted to.
+            # Don't need to lock src because we are just reading from its
+            # history which can't change.
+            dstwlock = rdst.wlock()
+            dstlock = rdst.lock()
+
             lfiles = set()
             normalfiles = set()
             if not pats:
@@ -118,74 +121,60 @@
                 rdst.requirements.add('largefiles')
                 rdst._writerequirements()
         else:
-            for ctx in ctxs:
-                ui.progress(_('converting revisions'), ctx.rev(),
-                    unit=_('revision'), total=rsrc['tip'].rev())
-                _addchangeset(ui, rsrc, rdst, ctx, revmap)
+            class lfsource(filemap.filemap_source):
+                def __init__(self, ui, source):
+                    super(lfsource, self).__init__(ui, source, None)
+                    self.filemapper.rename[lfutil.shortname] = '.'
+
+                def getfile(self, name, rev):
+                    realname, realrev = rev
+                    f = super(lfsource, self).getfile(name, rev)
+
+                    if (not realname.startswith(lfutil.shortnameslash)
+                            or f[0] is None):
+                        return f
+
+                    # Substitute in the largefile data for the hash
+                    hash = f[0].strip()
+                    path = lfutil.findfile(rsrc, hash)
+
+                    if path is None:
+                        raise util.Abort(_("missing largefile for \'%s\' in %s")
+                                          % (realname, realrev))
+                    fp = open(path, 'rb')
 
-            ui.progress(_('converting revisions'), None)
+                    try:
+                        return (fp.read(), f[1])
+                    finally:
+                        fp.close()
+
+            class converter(convcmd.converter):
+                def __init__(self, ui, source, dest, revmapfile, opts):
+                    src = lfsource(ui, source)
+
+                    super(converter, self).__init__(ui, src, dest, revmapfile,
+                                                    opts)
+
+            found, missing = downloadlfiles(ui, rsrc)
+            if missing != 0:
+                raise util.Abort(_("all largefiles must be present locally"))
+
+            orig = convcmd.converter
+            convcmd.converter = converter
+
+            try:
+                convcmd.convert(ui, src, dest)
+            finally:
+                convcmd.converter = orig
         success = True
     finally:
-        rdst.dirstate.clear()
-        release(dstlock, dstwlock)
+        if tolfile:
+            rdst.dirstate.clear()
+            release(dstlock, dstwlock)
         if not success:
             # we failed, remove the new directory
             shutil.rmtree(rdst.root)
 
-def _addchangeset(ui, rsrc, rdst, ctx, revmap):
-    # Convert src parents to dst parents
-    parents = _convertparents(ctx, revmap)
-
-    # Generate list of changed files
-    files = _getchangedfiles(ctx, parents)
-
-    def getfilectx(repo, memctx, f):
-        if lfutil.standin(f) in files:
-            # if the file isn't in the manifest then it was removed
-            # or renamed, raise IOError to indicate this
-            try:
-                fctx = ctx.filectx(lfutil.standin(f))
-            except error.LookupError:
-                return None
-            renamed = fctx.renamed()
-            if renamed:
-                renamed = lfutil.splitstandin(renamed[0])
-
-            hash = fctx.data().strip()
-            path = lfutil.findfile(rsrc, hash)
-
-            # If one file is missing, likely all files from this rev are
-            if path is None:
-                cachelfiles(ui, rsrc, ctx.node())
-                path = lfutil.findfile(rsrc, hash)
-
-                if path is None:
-                    raise util.Abort(
-                        _("missing largefile \'%s\' from revision %s")
-                         % (f, node.hex(ctx.node())))
-
-            data = ''
-            fd = None
-            try:
-                fd = open(path, 'rb')
-                data = fd.read()
-            finally:
-                if fd:
-                    fd.close()
-            return context.memfilectx(repo, f, data, 'l' in fctx.flags(),
-                                      'x' in fctx.flags(), renamed)
-        else:
-            return _getnormalcontext(repo, ctx, f, revmap)
-
-    dstfiles = []
-    for file in files:
-        if lfutil.isstandin(file):
-            dstfiles.append(lfutil.splitstandin(file))
-        else:
-            dstfiles.append(file)
-    # Commit
-    _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
-
 def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles,
         matcher, size, lfiletohash):
     # Convert src parents to dst parents
@@ -380,9 +369,7 @@
     matches the revision ID).  With --all, check every changeset in
     this repository.'''
     if all:
-        # Pass a list to the function rather than an iterator because we know a
-        # list will work.
-        revs = range(len(repo))
+        revs = repo.revs('all()')
     else:
         revs = ['.']
 
@@ -404,7 +391,7 @@
     for lfile in lfiles:
         try:
             expectedhash = repo[node][lfutil.standin(lfile)].data().strip()
-        except IOError, err:
+        except IOError as err:
             if err.errno == errno.ENOENT:
                 continue # node must be None and standin wasn't found in wctx
             raise
--- a/hgext/largefiles/lfutil.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/largefiles/lfutil.py	Sat Jul 18 17:32:38 2015 -0500
@@ -238,24 +238,28 @@
     if path:
         link(storepath(repo, hash), path)
 
-def getstandinmatcher(repo, pats=[], opts={}):
-    '''Return a match object that applies pats to the standin directory'''
+def getstandinmatcher(repo, rmatcher=None):
+    '''Return a match object that applies rmatcher to the standin directory'''
     standindir = repo.wjoin(shortname)
-    if pats:
-        pats = [os.path.join(standindir, pat) for pat in pats]
+
+    # no warnings about missing files or directories
+    badfn = lambda f, msg: None
+
+    if rmatcher and not rmatcher.always():
+        pats = [os.path.join(standindir, pat) for pat in rmatcher.files()]
+        match = scmutil.match(repo[None], pats, badfn=badfn)
+        # if pats is empty, it would incorrectly always match, so clear _always
+        match._always = False
     else:
         # no patterns: relative to repo root
-        pats = [standindir]
-    # no warnings about missing files or directories
-    match = scmutil.match(repo[None], pats, opts)
-    match.bad = lambda f, msg: None
+        match = scmutil.match(repo[None], [standindir], badfn=badfn)
     return match
 
 def composestandinmatcher(repo, rmatcher):
     '''Return a matcher that accepts standins corresponding to the
     files accepted by rmatcher. Pass the list of files in the matcher
     as the paths specified by the user.'''
-    smatcher = getstandinmatcher(repo, rmatcher.files())
+    smatcher = getstandinmatcher(repo, rmatcher)
     isstandin = smatcher.matchfn
     def composedmatchfn(f):
         return isstandin(f) and rmatcher.matchfn(splitstandin(f))
@@ -364,10 +368,10 @@
 
 def islfilesrepo(repo):
     if ('largefiles' in repo.requirements and
-            util.any(shortnameslash in f[0] for f in repo.store.datafiles())):
+            any(shortnameslash in f[0] for f in repo.store.datafiles())):
         return True
 
-    return util.any(openlfdirstate(repo.ui, repo, False))
+    return any(openlfdirstate(repo.ui, repo, False))
 
 class storeprotonotcapable(Exception):
     def __init__(self, storetypes):
--- a/hgext/largefiles/overrides.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/largefiles/overrides.py	Sat Jul 18 17:32:38 2015 -0500
@@ -27,7 +27,7 @@
     m = copy.copy(match)
     lfile = lambda f: lfutil.standin(f) in manifest
     m._files = filter(lfile, m._files)
-    m._fmap = set(m._files)
+    m._fileroots = set(m._files)
     m._always = False
     origmatchfn = m.matchfn
     m.matchfn = lambda f: lfile(f) and origmatchfn(f)
@@ -42,7 +42,7 @@
     notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
             manifest or f in excluded)
     m._files = filter(notlfile, m._files)
-    m._fmap = set(m._files)
+    m._fileroots = set(m._files)
     m._always = False
     origmatchfn = m.matchfn
     m.matchfn = lambda f: notlfile(f) and origmatchfn(f)
@@ -51,8 +51,8 @@
 def installnormalfilesmatchfn(manifest):
     '''installmatchfn with a matchfn that ignores all largefiles'''
     def overridematch(ctx, pats=[], opts={}, globbed=False,
-            default='relpath'):
-        match = oldmatch(ctx, pats, opts, globbed, default)
+            default='relpath', badfn=None):
+        match = oldmatch(ctx, pats, opts, globbed, default, badfn=badfn)
         return composenormalfilematcher(match, manifest)
     oldmatch = installmatchfn(overridematch)
 
@@ -100,10 +100,10 @@
             lfmatcher = match_.match(repo.root, '', list(lfpats))
 
     lfnames = []
-    m = copy.copy(matcher)
-    m.bad = lambda x, y: None
+    m = matcher
+
     wctx = repo[None]
-    for f in repo.walk(m):
+    for f in repo.walk(match_.badmatch(m, lambda x, y: None)):
         exact = m.exact(f)
         lfile = lfutil.standin(f) in wctx
         nfile = f in wctx
@@ -288,13 +288,14 @@
 
 def overridelog(orig, ui, repo, *pats, **opts):
     def overridematchandpats(ctx, pats=[], opts={}, globbed=False,
-            default='relpath'):
+            default='relpath', badfn=None):
         """Matcher that merges root directory with .hglf, suitable for log.
         It is still possible to match .hglf directly.
         For any listed files run log on the standin too.
         matchfn tries both the given filename and with .hglf stripped.
         """
-        matchandpats = oldmatchandpats(ctx, pats, opts, globbed, default)
+        matchandpats = oldmatchandpats(ctx, pats, opts, globbed, default,
+                                       badfn=badfn)
         m, p = copy.copy(matchandpats)
 
         if m.always():
@@ -358,7 +359,7 @@
                     and repo.wvfs.isdir(standin):
                 m._files.append(standin)
 
-        m._fmap = set(m._files)
+        m._fileroots = set(m._files)
         m._always = False
         origmatchfn = m.matchfn
         def lfmatchfn(f):
@@ -377,9 +378,9 @@
     # (2) to determine what files to print out diffs for.
     # The magic matchandpats override should be used for case (1) but not for
     # case (2).
-    def overridemakelogfilematcher(repo, pats, opts):
+    def overridemakelogfilematcher(repo, pats, opts, badfn=None):
         wctx = repo[None]
-        match, pats = oldmatchandpats(wctx, pats, opts)
+        match, pats = oldmatchandpats(wctx, pats, opts, badfn=badfn)
         return lambda rev: match
 
     oldmatchandpats = installmatchandpatsfn(overridematchandpats)
@@ -578,14 +579,13 @@
     nolfiles = False
     installnormalfilesmatchfn(repo[None].manifest())
     try:
-        try:
-            result = orig(ui, repo, pats, opts, rename)
-        except util.Abort, e:
-            if str(e) != _('no files to copy'):
-                raise e
-            else:
-                nonormalfiles = True
-            result = 0
+        result = orig(ui, repo, pats, opts, rename)
+    except util.Abort as e:
+        if str(e) != _('no files to copy'):
+            raise e
+        else:
+            nonormalfiles = True
+        result = 0
     finally:
         restorematchfn()
 
@@ -608,86 +608,85 @@
             os.makedirs(makestandin(dest))
 
     try:
-        try:
-            # When we call orig below it creates the standins but we don't add
-            # them to the dir state until later so lock during that time.
-            wlock = repo.wlock()
+        # When we call orig below it creates the standins but we don't add
+        # them to the dir state until later so lock during that time.
+        wlock = repo.wlock()
 
-            manifest = repo[None].manifest()
-            def overridematch(ctx, pats=[], opts={}, globbed=False,
-                    default='relpath'):
-                newpats = []
-                # The patterns were previously mangled to add the standin
-                # directory; we need to remove that now
-                for pat in pats:
-                    if match_.patkind(pat) is None and lfutil.shortname in pat:
-                        newpats.append(pat.replace(lfutil.shortname, ''))
-                    else:
-                        newpats.append(pat)
-                match = oldmatch(ctx, newpats, opts, globbed, default)
-                m = copy.copy(match)
-                lfile = lambda f: lfutil.standin(f) in manifest
-                m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
-                m._fmap = set(m._files)
-                origmatchfn = m.matchfn
-                m.matchfn = lambda f: (lfutil.isstandin(f) and
-                                    (f in manifest) and
-                                    origmatchfn(lfutil.splitstandin(f)) or
-                                    None)
-                return m
-            oldmatch = installmatchfn(overridematch)
-            listpats = []
+        manifest = repo[None].manifest()
+        def overridematch(ctx, pats=[], opts={}, globbed=False,
+                default='relpath', badfn=None):
+            newpats = []
+            # The patterns were previously mangled to add the standin
+            # directory; we need to remove that now
             for pat in pats:
-                if match_.patkind(pat) is not None:
-                    listpats.append(pat)
+                if match_.patkind(pat) is None and lfutil.shortname in pat:
+                    newpats.append(pat.replace(lfutil.shortname, ''))
                 else:
-                    listpats.append(makestandin(pat))
+                    newpats.append(pat)
+            match = oldmatch(ctx, newpats, opts, globbed, default, badfn=badfn)
+            m = copy.copy(match)
+            lfile = lambda f: lfutil.standin(f) in manifest
+            m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
+            m._fileroots = set(m._files)
+            origmatchfn = m.matchfn
+            m.matchfn = lambda f: (lfutil.isstandin(f) and
+                                (f in manifest) and
+                                origmatchfn(lfutil.splitstandin(f)) or
+                                None)
+            return m
+        oldmatch = installmatchfn(overridematch)
+        listpats = []
+        for pat in pats:
+            if match_.patkind(pat) is not None:
+                listpats.append(pat)
+            else:
+                listpats.append(makestandin(pat))
 
-            try:
-                origcopyfile = util.copyfile
-                copiedfiles = []
-                def overridecopyfile(src, dest):
-                    if (lfutil.shortname in src and
-                        dest.startswith(repo.wjoin(lfutil.shortname))):
-                        destlfile = dest.replace(lfutil.shortname, '')
-                        if not opts['force'] and os.path.exists(destlfile):
-                            raise IOError('',
-                                _('destination largefile already exists'))
-                    copiedfiles.append((src, dest))
-                    origcopyfile(src, dest)
-
-                util.copyfile = overridecopyfile
-                result += orig(ui, repo, listpats, opts, rename)
-            finally:
-                util.copyfile = origcopyfile
-
-            lfdirstate = lfutil.openlfdirstate(ui, repo)
-            for (src, dest) in copiedfiles:
+        try:
+            origcopyfile = util.copyfile
+            copiedfiles = []
+            def overridecopyfile(src, dest):
                 if (lfutil.shortname in src and
                     dest.startswith(repo.wjoin(lfutil.shortname))):
-                    srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
-                    destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
-                    destlfiledir = os.path.dirname(repo.wjoin(destlfile)) or '.'
-                    if not os.path.isdir(destlfiledir):
-                        os.makedirs(destlfiledir)
-                    if rename:
-                        os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
+                    destlfile = dest.replace(lfutil.shortname, '')
+                    if not opts['force'] and os.path.exists(destlfile):
+                        raise IOError('',
+                            _('destination largefile already exists'))
+                copiedfiles.append((src, dest))
+                origcopyfile(src, dest)
+
+            util.copyfile = overridecopyfile
+            result += orig(ui, repo, listpats, opts, rename)
+        finally:
+            util.copyfile = origcopyfile
 
-                        # The file is gone, but this deletes any empty parent
-                        # directories as a side-effect.
-                        util.unlinkpath(repo.wjoin(srclfile), True)
-                        lfdirstate.remove(srclfile)
-                    else:
-                        util.copyfile(repo.wjoin(srclfile),
-                                      repo.wjoin(destlfile))
+        lfdirstate = lfutil.openlfdirstate(ui, repo)
+        for (src, dest) in copiedfiles:
+            if (lfutil.shortname in src and
+                dest.startswith(repo.wjoin(lfutil.shortname))):
+                srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
+                destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
+                destlfiledir = os.path.dirname(repo.wjoin(destlfile)) or '.'
+                if not os.path.isdir(destlfiledir):
+                    os.makedirs(destlfiledir)
+                if rename:
+                    os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
 
-                    lfdirstate.add(destlfile)
-            lfdirstate.write()
-        except util.Abort, e:
-            if str(e) != _('no files to copy'):
-                raise e
-            else:
-                nolfiles = True
+                    # The file is gone, but this deletes any empty parent
+                    # directories as a side-effect.
+                    util.unlinkpath(repo.wjoin(srclfile), True)
+                    lfdirstate.remove(srclfile)
+                else:
+                    util.copyfile(repo.wjoin(srclfile),
+                                  repo.wjoin(destlfile))
+
+                lfdirstate.add(destlfile)
+        lfdirstate.write()
+    except util.Abort as e:
+        if str(e) != _('no files to copy'):
+            raise e
+        else:
+            nolfiles = True
     finally:
         restorematchfn()
         wlock.release()
@@ -724,8 +723,8 @@
         oldstandins = lfutil.getstandinsstate(repo)
 
         def overridematch(mctx, pats=[], opts={}, globbed=False,
-                default='relpath'):
-            match = oldmatch(mctx, pats, opts, globbed, default)
+                default='relpath', badfn=None):
+            match = oldmatch(mctx, pats, opts, globbed, default, badfn=badfn)
             m = copy.copy(match)
 
             # revert supports recursing into subrepos, and though largefiles
@@ -744,7 +743,7 @@
                 return f
             m._files = [tostandin(f) for f in m._files]
             m._files = [f for f in m._files if f is not None]
-            m._fmap = set(m._files)
+            m._fileroots = set(m._files)
             origmatchfn = m.matchfn
             def matchfn(f):
                 if lfutil.isstandin(f):
@@ -879,11 +878,24 @@
         repo._lfstatuswriters.pop()
         repo._lfcommithooks.pop()
 
+def overridearchivecmd(orig, ui, repo, dest, **opts):
+    repo.unfiltered().lfstatus = True
+
+    try:
+        return orig(ui, repo.unfiltered(), dest, **opts)
+    finally:
+        repo.unfiltered().lfstatus = False
+
 def overridearchive(orig, repo, dest, node, kind, decode=True, matchfn=None,
             prefix='', mtime=None, subrepos=None):
+    if not repo.lfstatus:
+        return orig(repo, dest, node, kind, decode, matchfn, prefix, mtime,
+                    subrepos)
+
     # No need to lock because we are only reading history and
     # largefile caches, neither of which are modified.
-    lfcommands.cachelfiles(repo.ui, repo, node)
+    if node is not None:
+        lfcommands.cachelfiles(repo.ui, repo, node)
 
     if kind not in archival.archivers:
         raise util.Abort(_("unknown archive type '%s'") % kind)
@@ -908,18 +920,23 @@
     archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
 
     if repo.ui.configbool("ui", "archivemeta", True):
-        write('.hg_archival.txt', 0644, False,
+        write('.hg_archival.txt', 0o644, False,
               lambda: archival.buildmetadata(ctx))
 
     for f in ctx:
         ff = ctx.flags(f)
         getdata = ctx[f].data
         if lfutil.isstandin(f):
-            path = lfutil.findfile(repo, getdata().strip())
-            if path is None:
-                raise util.Abort(
-                    _('largefile %s not found in repo store or system cache')
-                    % lfutil.splitstandin(f))
+            if node is not None:
+                path = lfutil.findfile(repo, getdata().strip())
+
+                if path is None:
+                    raise util.Abort(
+                       _('largefile %s not found in repo store or system cache')
+                       % lfutil.splitstandin(f))
+            else:
+                path = lfutil.splitstandin(f)
+
             f = lfutil.splitstandin(f)
 
             def getdatafn():
@@ -932,22 +949,27 @@
                         fd.close()
 
             getdata = getdatafn
-        write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
+        write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
 
     if subrepos:
         for subpath in sorted(ctx.substate):
-            sub = ctx.sub(subpath)
+            sub = ctx.workingsub(subpath)
             submatch = match_.narrowmatcher(subpath, matchfn)
+            sub._repo.lfstatus = True
             sub.archive(archiver, prefix, submatch)
 
     archiver.done()
 
 def hgsubrepoarchive(orig, repo, archiver, prefix, match=None):
+    if not repo._repo.lfstatus:
+        return orig(repo, archiver, prefix, match)
+
     repo._get(repo._state + ('hg',))
     rev = repo._state[1]
     ctx = repo._repo[rev]
 
-    lfcommands.cachelfiles(repo.ui, repo._repo, ctx.node())
+    if ctx.node() is not None:
+        lfcommands.cachelfiles(repo.ui, repo._repo, ctx.node())
 
     def write(name, mode, islink, getdata):
         # At this point, the standin has been replaced with the largefile name,
@@ -962,11 +984,16 @@
         ff = ctx.flags(f)
         getdata = ctx[f].data
         if lfutil.isstandin(f):
-            path = lfutil.findfile(repo._repo, getdata().strip())
-            if path is None:
-                raise util.Abort(
-                    _('largefile %s not found in repo store or system cache')
-                    % lfutil.splitstandin(f))
+            if ctx.node() is not None:
+                path = lfutil.findfile(repo._repo, getdata().strip())
+
+                if path is None:
+                    raise util.Abort(
+                       _('largefile %s not found in repo store or system cache')
+                       % lfutil.splitstandin(f))
+            else:
+                path = lfutil.splitstandin(f)
+
             f = lfutil.splitstandin(f)
 
             def getdatafn():
@@ -980,12 +1007,13 @@
 
             getdata = getdatafn
 
-        write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata)
+        write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
 
     for subpath in sorted(ctx.substate):
-        sub = ctx.sub(subpath)
+        sub = ctx.workingsub(subpath)
         submatch = match_.narrowmatcher(subpath, match)
-        sub.archive(archiver, os.path.join(prefix, repo._path) + '/', submatch)
+        sub._repo.lfstatus = True
+        sub.archive(archiver, prefix + repo._path + '/', submatch)
 
 # If a largefile is modified, the change is not reflected in its
 # standin until a commit. cmdutil.bailifchanged() raises an exception
--- a/hgext/largefiles/proto.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/largefiles/proto.py	Sat Jul 18 17:32:38 2015 -0500
@@ -31,17 +31,16 @@
     tmpfp = util.atomictempfile(path, createmode=repo.store.createmode)
 
     try:
-        try:
-            proto.getfile(tmpfp)
-            tmpfp._fp.seek(0)
-            if sha != lfutil.hexsha1(tmpfp._fp):
-                raise IOError(0, _('largefile contents do not match hash'))
-            tmpfp.close()
-            lfutil.linktousercache(repo, sha)
-        except IOError, e:
-            repo.ui.warn(_('largefiles: failed to put %s into store: %s\n') %
-                         (sha, e.strerror))
-            return wireproto.pushres(1)
+        proto.getfile(tmpfp)
+        tmpfp._fp.seek(0)
+        if sha != lfutil.hexsha1(tmpfp._fp):
+            raise IOError(0, _('largefile contents do not match hash'))
+        tmpfp.close()
+        lfutil.linktousercache(repo, sha)
+    except IOError as e:
+        repo.ui.warn(_('largefiles: failed to put %s into store: %s\n') %
+                     (sha, e.strerror))
+        return wireproto.pushres(1)
     finally:
         tmpfp.discard()
 
--- a/hgext/largefiles/remotestore.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/largefiles/remotestore.py	Sat Jul 18 17:32:38 2015 -0500
@@ -36,13 +36,12 @@
         self.ui.debug('remotestore: sendfile(%s, %s)\n' % (filename, hash))
         fd = None
         try:
-            try:
-                fd = lfutil.httpsendfile(self.ui, filename)
-            except IOError, e:
-                raise util.Abort(
-                    _('remotestore: could not open file %s: %s')
-                    % (filename, str(e)))
+            fd = lfutil.httpsendfile(self.ui, filename)
             return self._put(hash, fd)
+        except IOError as e:
+            raise util.Abort(
+                _('remotestore: could not open file %s: %s')
+                % (filename, str(e)))
         finally:
             if fd:
                 fd.close()
@@ -50,17 +49,17 @@
     def _getfile(self, tmpfile, filename, hash):
         try:
             chunks = self._get(hash)
-        except urllib2.HTTPError, e:
+        except urllib2.HTTPError as e:
             # 401s get converted to util.Aborts; everything else is fine being
             # turned into a StoreError
             raise basestore.StoreError(filename, hash, self.url, str(e))
-        except urllib2.URLError, e:
+        except urllib2.URLError as e:
             # This usually indicates a connection problem, so don't
             # keep trying with the other files... they will probably
             # all fail too.
             raise util.Abort('%s: %s' %
                              (util.hidepassword(self.url), e.reason))
-        except IOError, e:
+        except IOError as e:
             raise basestore.StoreError(filename, hash, self.url, str(e))
 
         return lfutil.copyandhash(chunks, tmpfile)
--- a/hgext/largefiles/reposetup.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/largefiles/reposetup.py	Sat Jul 18 17:32:38 2015 -0500
@@ -365,7 +365,7 @@
     repo.prepushoutgoinghooks.add("largefiles", prepushoutgoinghook)
 
     def checkrequireslfiles(ui, repo, **kwargs):
-        if 'largefiles' not in repo.requirements and util.any(
+        if 'largefiles' not in repo.requirements and any(
                 lfutil.shortname+'/' in f[0] for f in repo.store.datafiles()):
             repo.requirements.add('largefiles')
             repo._writerequirements()
--- a/hgext/largefiles/uisetup.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/largefiles/uisetup.py	Sat Jul 18 17:32:38 2015 -0500
@@ -114,6 +114,8 @@
     entry = 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)
--- a/hgext/mq.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/mq.py	Sat Jul 18 17:32:38 2015 -0500
@@ -76,6 +76,10 @@
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 # force load strip extension formerly included in mq and import some utility
@@ -298,7 +302,7 @@
         self.haspatch = diffstart > 1
         self.plainmode = (plainmode or
                           '# HG changeset patch' not in self.comments and
-                          util.any(c.startswith('Date: ') or
+                          any(c.startswith('Date: ') or
                                    c.startswith('From: ')
                                    for c in self.comments))
 
@@ -376,14 +380,17 @@
         if repo.ui.configbool('mq', 'secret', False):
             phase = phases.secret
     if phase is not None:
-        backup = repo.ui.backupconfig('phases', 'new-commit')
+        phasebackup = repo.ui.backupconfig('phases', 'new-commit')
+    allowemptybackup = repo.ui.backupconfig('ui', 'allowemptycommit')
     try:
         if phase is not None:
             repo.ui.setconfig('phases', 'new-commit', phase, 'mq')
+        repo.ui.setconfig('ui', 'allowemptycommit', True)
         return repo.commit(*args, **kwargs)
     finally:
+        repo.ui.restoreconfig(allowemptybackup)
         if phase is not None:
-            repo.ui.restoreconfig(backup)
+            repo.ui.restoreconfig(phasebackup)
 
 class AbortNoCleanup(error.Abort):
     pass
@@ -423,7 +430,9 @@
             else:
                 self.gitmode = 'no'
         except error.ConfigError:
-            self.gitmode = ui.config('mq', 'git', 'auto').lower()
+            # let's have check-config ignore the type mismatch
+            self.gitmode = ui.config(r'mq', 'git', 'auto').lower()
+        # deprecated config: mq.plain
         self.plainmode = ui.configbool('mq', 'plain', False)
         self.checkapplied = True
 
@@ -441,7 +450,7 @@
         try:
             lines = self.opener.read(self.statuspath).splitlines()
             return list(parselines(lines))
-        except IOError, e:
+        except IOError as e:
             if e.errno == errno.ENOENT:
                 return []
             raise
@@ -450,7 +459,7 @@
     def fullseries(self):
         try:
             return self.opener.read(self.seriespath).splitlines()
-        except IOError, e:
+        except IOError as e:
             if e.errno == errno.ENOENT:
                 return []
             raise
@@ -567,7 +576,7 @@
             self.activeguards = []
             try:
                 guards = self.opener.read(self.guardspath).split()
-            except IOError, err:
+            except IOError as err:
                 if err.errno != errno.ENOENT:
                     raise
                 guards = []
@@ -668,7 +677,7 @@
             return
         try:
             os.unlink(undo)
-        except OSError, inst:
+        except OSError as inst:
             self.ui.warn(_('error removing undo: %s\n') % str(inst))
 
     def backup(self, repo, files, copy=False):
@@ -797,7 +806,7 @@
             fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1,
                                   files=files, eolmode=None)
             return (True, list(files), fuzz)
-        except Exception, inst:
+        except Exception as inst:
             self.ui.note(str(inst) + '\n')
             if not self.ui.verbose:
                 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
@@ -807,9 +816,10 @@
     def apply(self, repo, series, list=False, update_status=True,
               strict=False, patchdir=None, merge=None, all_files=None,
               tobackup=None, keepchanges=False):
-        wlock = lock = tr = None
+        wlock = dsguard = lock = tr = None
         try:
             wlock = repo.wlock()
+            dsguard = cmdutil.dirstateguard(repo, 'mq.apply')
             lock = repo.lock()
             tr = repo.transaction("qpush")
             try:
@@ -818,21 +828,22 @@
                                   tobackup=tobackup, keepchanges=keepchanges)
                 tr.close()
                 self.savedirty()
+                dsguard.close()
                 return ret
             except AbortNoCleanup:
                 tr.close()
                 self.savedirty()
+                dsguard.close()
                 raise
             except: # re-raises
                 try:
                     tr.abort()
                 finally:
                     repo.invalidate()
-                    repo.dirstate.invalidate()
                     self.invalidate()
                 raise
         finally:
-            release(tr, lock, wlock)
+            release(tr, lock, dsguard, wlock)
             self.removeundo(repo)
 
     def _apply(self, repo, series, list=False, update_status=True,
@@ -950,7 +961,7 @@
             for p in patches:
                 try:
                     os.unlink(self.join(p))
-                except OSError, inst:
+                except OSError as inst:
                     if inst.errno != errno.ENOENT:
                         raise
 
@@ -1093,9 +1104,9 @@
             if name.startswith(prefix):
                 raise util.Abort(_('patch name cannot begin with "%s"')
                                  % prefix)
-        for c in ('#', ':'):
+        for c in ('#', ':', '\r', '\n'):
             if c in name:
-                raise util.Abort(_('"%s" cannot be used in the name of a patch')
+                raise util.Abort(_('%r cannot be used in the name of a patch')
                                  % c)
 
     def checkpatchname(self, name, force=False):
@@ -1129,12 +1140,11 @@
         if inclsubs:
             substatestate = repo.dirstate['.hgsubstate']
         if opts.get('include') or opts.get('exclude') or pats:
-            match = scmutil.match(repo[None], pats, opts)
             # detect missing files in pats
             def badfn(f, msg):
                 if f != '.hgsubstate': # .hgsubstate is auto-created
                     raise util.Abort('%s: %s' % (f, msg))
-            match.bad = badfn
+            match = scmutil.match(repo[None], pats, opts, badfn=badfn)
             changes = repo.status(match=match)
         else:
             changes = self.checklocalchanges(repo, force=True)
@@ -1151,7 +1161,7 @@
             try:
                 # if patch file write fails, abort early
                 p = self.opener(patchfn, "w")
-            except IOError, e:
+            except IOError as e:
                 raise util.Abort(_('cannot write patch "%s": %s')
                                  % (patchfn, e.strerror))
             try:
@@ -1517,7 +1527,7 @@
                                    "managed by this patch queue"))
             if not repo[self.applied[-1].node].mutable():
                 raise util.Abort(
-                    _("popping would remove an immutable revision"),
+                    _("popping would remove a public revision"),
                     hint=_('see "hg help phases" for details'))
 
             # we know there are no local changes, so we can make a simplified
@@ -1588,7 +1598,7 @@
             if repo.changelog.heads(top) != [top]:
                 raise util.Abort(_("cannot refresh a revision with children"))
             if not repo[top].mutable():
-                raise util.Abort(_("cannot refresh immutable revision"),
+                raise util.Abort(_("cannot refresh public revision"),
                                  hint=_('see "hg help phases" for details'))
 
             cparents = repo.changelog.parents(top)
@@ -1681,8 +1691,9 @@
 
             bmlist = repo[top].bookmarks()
 
+            dsguard = None
             try:
-                repo.dirstate.beginparentchange()
+                dsguard = cmdutil.dirstateguard(repo, 'mq.refresh')
                 if diffopts.git or diffopts.upgrade:
                     copies = {}
                     for dst in a:
@@ -1735,13 +1746,12 @@
 
                 # assumes strip can roll itself back if interrupted
                 repo.setparents(*cparents)
-                repo.dirstate.endparentchange()
                 self.applied.pop()
                 self.applieddirty = True
                 strip(self.ui, repo, [top], update=False, backup=False)
-            except: # re-raises
-                repo.dirstate.invalidate()
-                raise
+                dsguard.close()
+            finally:
+                release(dsguard)
 
             try:
                 # might be nice to attempt to roll back strip after this
@@ -1808,7 +1818,7 @@
             raise util.Abort(_("patch queue directory already exists"))
         try:
             os.mkdir(self.path)
-        except OSError, inst:
+        except OSError as inst:
             if inst.errno != errno.EEXIST or not create:
                 raise
         if create:
--- a/hgext/notify.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/notify.py	Sat Jul 18 17:32:38 2015 -0500
@@ -134,13 +134,14 @@
 '''
 
 import email, socket, time
-# On python2.4 you have to import this by name or they fail to
-# load. This was not a problem on Python 2.7.
-import email.Parser
 from mercurial.i18n import _
-from mercurial import patch, cmdutil, templater, util, mail
+from mercurial import patch, cmdutil, util, mail
 import fnmatch
 
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 # template for single changeset can include email headers.
@@ -190,8 +191,6 @@
                     self.ui.config('notify', 'template'))
         if not mapfile and not template:
             template = deftemplates.get(hooktype) or single_template
-        if template:
-            template = templater.parsestring(template, quoted=False)
         self.t = cmdutil.changeset_templater(self.ui, self.repo, False, None,
                                              template, mapfile, False)
 
@@ -277,7 +276,7 @@
         p = email.Parser.Parser()
         try:
             msg = p.parsestr(data)
-        except email.Errors.MessageParseError, inst:
+        except email.Errors.MessageParseError as inst:
             raise util.Abort(inst)
 
         # store sender and subject
--- a/hgext/pager.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/pager.py	Sat Jul 18 17:32:38 2015 -0500
@@ -55,40 +55,16 @@
 
 '''
 
-import atexit, sys, os, signal, subprocess, errno, shlex
+import atexit, sys, os, signal, subprocess
 from mercurial import commands, dispatch, util, extensions, cmdutil
 from mercurial.i18n import _
 
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
-def _pagerfork(ui, p):
-    if not util.safehasattr(os, 'fork'):
-        sys.stdout = util.popen(p, 'wb')
-        if ui._isatty(sys.stderr):
-            sys.stderr = sys.stdout
-        return
-    fdin, fdout = os.pipe()
-    pid = os.fork()
-    if pid == 0:
-        os.close(fdin)
-        os.dup2(fdout, sys.stdout.fileno())
-        if ui._isatty(sys.stderr):
-            os.dup2(fdout, sys.stderr.fileno())
-        os.close(fdout)
-        return
-    os.dup2(fdin, sys.stdin.fileno())
-    os.close(fdin)
-    os.close(fdout)
-    try:
-        os.execvp('/bin/sh', ['/bin/sh', '-c', p])
-    except OSError, e:
-        if e.errno == errno.ENOENT:
-            # no /bin/sh, try executing the pager directly
-            args = shlex.split(p)
-            os.execvp(args[0], args)
-        else:
-            raise
-
 def _pagersubprocess(ui, p):
     pager = subprocess.Popen(p, shell=True, bufsize=-1,
                              close_fds=util.closefds, stdin=subprocess.PIPE,
@@ -110,13 +86,7 @@
         pager.wait()
 
 def _runpager(ui, p):
-    # The subprocess module shipped with Python <= 2.4 is buggy (issue3533).
-    # The compat version is buggy on Windows (issue3225), but has been shipping
-    # with hg for a long time.  Preserve existing functionality.
-    if sys.version_info >= (2, 5):
-        _pagersubprocess(ui, p)
-    else:
-        _pagerfork(ui, p)
+    _pagersubprocess(ui, p)
 
 def uisetup(ui):
     if '--debugger' in sys.argv or not ui.formatted():
--- a/hgext/patchbomb.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/patchbomb.py	Sat Jul 18 17:32:38 2015 -0500
@@ -59,10 +59,6 @@
 
 import os, errno, socket, tempfile, cStringIO
 import email
-# On python2.4 you have to import these by name or they fail to
-# load. This was not a problem on Python 2.7.
-import email.Generator
-import email.MIMEMultipart
 
 from mercurial import cmdutil, commands, hg, mail, patch, util
 from mercurial import scmutil
@@ -71,6 +67,10 @@
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 def prompt(ui, prompt, default=None, rest=':'):
@@ -514,6 +514,7 @@
     def genmsgid(id):
         return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())
 
+    # deprecated config: patchbomb.from
     sender = (opts.get('from') or ui.config('email', 'from') or
               ui.config('patchbomb', 'from') or
               prompt(ui, 'From', ui.username()))
@@ -628,14 +629,14 @@
             try:
                 generator.flatten(m, 0)
                 fp.write('\n')
-            except IOError, inst:
+            except IOError as inst:
                 if inst.errno != errno.EPIPE:
                     raise
             if fp is not ui:
                 fp.close()
         else:
             if not sendmail:
-                verifycert = ui.config('smtp', 'verifycert')
+                verifycert = ui.config('smtp', 'verifycert', 'strict')
                 if opts.get('insecure'):
                     ui.setconfig('smtp', 'verifycert', 'loose', 'patchbomb')
                 try:
--- a/hgext/progress.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/progress.py	Sat Jul 18 17:32:38 2015 -0500
@@ -5,316 +5,8 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-"""show progress bars for some actions
-
-This extension uses the progress information logged by hg commands
-to draw progress bars that are as informative as possible. Some progress
-bars only offer indeterminate information, while others have a definite
-end point.
-
-The following settings are available::
-
-  [progress]
-  delay = 3 # number of seconds (float) before showing the progress bar
-  changedelay = 1 # changedelay: minimum delay before showing a new topic.
-                  # If set to less than 3 * refresh, that value will
-                  # be used instead.
-  refresh = 0.1 # time in seconds between refreshes of the progress bar
-  format = topic bar number estimate # format of the progress bar
-  width = <none> # if set, the maximum width of the progress information
-                 # (that is, min(width, term width) will be used)
-  clear-complete = True # clear the progress bar after it's done
-  disable = False # if true, don't show a progress bar
-  assume-tty = False # if true, ALWAYS show a progress bar, unless
-                     # disable is given
-
-Valid entries for the format field are topic, bar, number, unit,
-estimate, speed, and item. item defaults to the last 20 characters of
-the item, but this can be changed by adding either ``-<num>`` which
-would take the last num characters, or ``+<num>`` for the first num
-characters.
-"""
-
-import sys
-import time
-import threading
-
-from mercurial.i18n import _
-testedwith = 'internal'
-
-from mercurial import encoding
-
-def spacejoin(*args):
-    return ' '.join(s for s in args if s)
-
-def shouldprint(ui):
-    return not ui.plain() and (ui._isatty(sys.stderr) or
-                               ui.configbool('progress', 'assume-tty'))
-
-def fmtremaining(seconds):
-    if seconds < 60:
-        # i18n: format XX seconds as "XXs"
-        return _("%02ds") % (seconds)
-    minutes = seconds // 60
-    if minutes < 60:
-        seconds -= minutes * 60
-        # i18n: format X minutes and YY seconds as "XmYYs"
-        return _("%dm%02ds") % (minutes, seconds)
-    # we're going to ignore seconds in this case
-    minutes += 1
-    hours = minutes // 60
-    minutes -= hours * 60
-    if hours < 30:
-        # i18n: format X hours and YY minutes as "XhYYm"
-        return _("%dh%02dm") % (hours, minutes)
-    # we're going to ignore minutes in this case
-    hours += 1
-    days = hours // 24
-    hours -= days * 24
-    if days < 15:
-        # i18n: format X days and YY hours as "XdYYh"
-        return _("%dd%02dh") % (days, hours)
-    # we're going to ignore hours in this case
-    days += 1
-    weeks = days // 7
-    days -= weeks * 7
-    if weeks < 55:
-        # i18n: format X weeks and YY days as "XwYYd"
-        return _("%dw%02dd") % (weeks, days)
-    # we're going to ignore days and treat a year as 52 weeks
-    weeks += 1
-    years = weeks // 52
-    weeks -= years * 52
-    # i18n: format X years and YY weeks as "XyYYw"
-    return _("%dy%02dw") % (years, weeks)
-
-class progbar(object):
-    def __init__(self, ui):
-        self.ui = ui
-        self._refreshlock = threading.Lock()
-        self.resetstate()
-
-    def resetstate(self):
-        self.topics = []
-        self.topicstates = {}
-        self.starttimes = {}
-        self.startvals = {}
-        self.printed = False
-        self.lastprint = time.time() + float(self.ui.config(
-            'progress', 'delay', default=3))
-        self.curtopic = None
-        self.lasttopic = None
-        self.indetcount = 0
-        self.refresh = float(self.ui.config(
-            'progress', 'refresh', default=0.1))
-        self.changedelay = max(3 * self.refresh,
-                               float(self.ui.config(
-                                   'progress', 'changedelay', default=1)))
-        self.order = self.ui.configlist(
-            'progress', 'format',
-            default=['topic', 'bar', 'number', 'estimate'])
+"""show progress bars for some actions (DEPRECATED)
 
-    def show(self, now, topic, pos, item, unit, total):
-        if not shouldprint(self.ui):
-            return
-        termwidth = self.width()
-        self.printed = True
-        head = ''
-        needprogress = False
-        tail = ''
-        for indicator in self.order:
-            add = ''
-            if indicator == 'topic':
-                add = topic
-            elif indicator == 'number':
-                if total:
-                    add = ('% ' + str(len(str(total))) +
-                           's/%s') % (pos, total)
-                else:
-                    add = str(pos)
-            elif indicator.startswith('item') and item:
-                slice = 'end'
-                if '-' in indicator:
-                    wid = int(indicator.split('-')[1])
-                elif '+' in indicator:
-                    slice = 'beginning'
-                    wid = int(indicator.split('+')[1])
-                else:
-                    wid = 20
-                if slice == 'end':
-                    add = encoding.trim(item, wid, leftside=True)
-                else:
-                    add = encoding.trim(item, wid)
-                add += (wid - encoding.colwidth(add)) * ' '
-            elif indicator == 'bar':
-                add = ''
-                needprogress = True
-            elif indicator == 'unit' and unit:
-                add = unit
-            elif indicator == 'estimate':
-                add = self.estimate(topic, pos, total, now)
-            elif indicator == 'speed':
-                add = self.speed(topic, pos, unit, now)
-            if not needprogress:
-                head = spacejoin(head, add)
-            else:
-                tail = spacejoin(tail, add)
-        if needprogress:
-            used = 0
-            if head:
-                used += encoding.colwidth(head) + 1
-            if tail:
-                used += encoding.colwidth(tail) + 1
-            progwidth = termwidth - used - 3
-            if total and pos <= total:
-                amt = pos * progwidth // total
-                bar = '=' * (amt - 1)
-                if amt > 0:
-                    bar += '>'
-                bar += ' ' * (progwidth - amt)
-            else:
-                progwidth -= 3
-                self.indetcount += 1
-                # mod the count by twice the width so we can make the
-                # cursor bounce between the right and left sides
-                amt = self.indetcount % (2 * progwidth)
-                amt -= progwidth
-                bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
-                       ' ' * int(abs(amt)))
-            prog = ''.join(('[', bar , ']'))
-            out = spacejoin(head, prog, tail)
-        else:
-            out = spacejoin(head, tail)
-        sys.stderr.write('\r' + encoding.trim(out, termwidth))
-        self.lasttopic = topic
-        sys.stderr.flush()
-
-    def clear(self):
-        if not shouldprint(self.ui):
-            return
-        sys.stderr.write('\r%s\r' % (' ' * self.width()))
-
-    def complete(self):
-        if not shouldprint(self.ui):
-            return
-        if self.ui.configbool('progress', 'clear-complete', default=True):
-            self.clear()
-        else:
-            sys.stderr.write('\n')
-        sys.stderr.flush()
-
-    def width(self):
-        tw = self.ui.termwidth()
-        return min(int(self.ui.config('progress', 'width', default=tw)), tw)
-
-    def estimate(self, topic, pos, total, now):
-        if total is None:
-            return ''
-        initialpos = self.startvals[topic]
-        target = total - initialpos
-        delta = pos - initialpos
-        if delta > 0:
-            elapsed = now - self.starttimes[topic]
-            if elapsed > float(
-                self.ui.config('progress', 'estimate', default=2)):
-                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', default=2)):
-            return _('%d %s/sec') % (delta / elapsed, unit)
-        return ''
-
-    def _oktoprint(self, now):
-        '''Check if conditions are met to print - e.g. changedelay elapsed'''
-        if (self.lasttopic is None # first time we printed
-            # not a topic change
-            or self.curtopic == self.lasttopic
-            # it's been long enough we should print anyway
-            or now - self.lastprint >= self.changedelay):
-            return True
-        else:
-            return False
-
-    def progress(self, topic, pos, item='', unit='', total=None):
-        now = time.time()
-        self._refreshlock.acquire()
-        try:
-            if pos is None:
-                self.starttimes.pop(topic, None)
-                self.startvals.pop(topic, None)
-                self.topicstates.pop(topic, None)
-                # reset the progress bar if this is the outermost topic
-                if self.topics and self.topics[0] == topic and self.printed:
-                    self.complete()
-                    self.resetstate()
-                # truncate the list of topics assuming all topics within
-                # this one are also closed
-                if topic in self.topics:
-                    self.topics = self.topics[:self.topics.index(topic)]
-                    # reset the last topic to the one we just unwound to,
-                    # so that higher-level topics will be stickier than
-                    # lower-level topics
-                    if self.topics:
-                        self.lasttopic = self.topics[-1]
-                    else:
-                        self.lasttopic = None
-            else:
-                if topic not in self.topics:
-                    self.starttimes[topic] = now
-                    self.startvals[topic] = pos
-                    self.topics.append(topic)
-                self.topicstates[topic] = pos, item, unit, total
-                self.curtopic = topic
-                if now - self.lastprint >= self.refresh and self.topics:
-                    if self._oktoprint(now):
-                        self.lastprint = now
-                        self.show(now, topic, *self.topicstates[topic])
-        finally:
-            self._refreshlock.release()
-
-_singleton = None
-
-def uisetup(ui):
-    global _singleton
-    class progressui(ui.__class__):
-        _progbar = None
-
-        def _quiet(self):
-            return self.debugflag or self.quiet
-
-        def progress(self, *args, **opts):
-            if not self._quiet():
-                self._progbar.progress(*args, **opts)
-            return super(progressui, self).progress(*args, **opts)
-
-        def write(self, *args, **opts):
-            if not self._quiet() and self._progbar.printed:
-                self._progbar.clear()
-            return super(progressui, self).write(*args, **opts)
-
-        def write_err(self, *args, **opts):
-            if not self._quiet() and self._progbar.printed:
-                self._progbar.clear()
-            return super(progressui, self).write_err(*args, **opts)
-
-    # Apps that derive a class from ui.ui() can use
-    # setconfig('progress', 'disable', 'True') to disable this extension
-    if ui.configbool('progress', 'disable'):
-        return
-    if shouldprint(ui) and not ui.debugflag and not ui.quiet:
-        ui.__class__ = progressui
-        # we instantiate one globally shared progress bar to avoid
-        # competing progress bars when multiple UI objects get created
-        if not progressui._progbar:
-            if _singleton is None:
-                _singleton = progbar(ui)
-            progressui._progbar = _singleton
-
-def reposetup(ui, repo):
-    uisetup(repo.ui)
+This extension has been merged into core, you can remove it from your config.
+See hg help config.progress for configuration options.
+"""
--- a/hgext/purge.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/purge.py	Sat Jul 18 17:32:38 2015 -0500
@@ -30,6 +30,10 @@
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 @command('purge|clean',
--- a/hgext/rebase.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/rebase.py	Sat Jul 18 17:32:38 2015 -0500
@@ -29,6 +29,10 @@
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 def _savegraft(ctx, extra):
@@ -67,7 +71,7 @@
     ('e', 'edit', False, _('invoke editor on commit messages')),
     ('l', 'logfile', '',
      _('read collapse commit message from file'), _('FILE')),
-    ('', 'keep', False, _('keep original changesets')),
+    ('k', 'keep', False, _('keep original changesets')),
     ('', 'keepbranches', False, _('keep original branch names')),
     ('D', 'detach', False, _('(DEPRECATED)')),
     ('i', 'interactive', False, _('(DEPRECATED)')),
@@ -326,7 +330,7 @@
 
             root = min(rebaseset)
             if not keepf and not repo[root].mutable():
-                raise util.Abort(_("can't rebase immutable changeset %s")
+                raise util.Abort(_("can't rebase public changeset %s")
                                  % repo[root],
                                  hint=_('see "hg help phases" for details'))
 
@@ -358,9 +362,9 @@
 
         # Keep track of the current bookmarks in order to reset them later
         currentbookmarks = repo._bookmarks.copy()
-        activebookmark = activebookmark or repo._bookmarkcurrent
+        activebookmark = activebookmark or repo._activebookmark
         if activebookmark:
-            bookmarks.unsetcurrent(repo)
+            bookmarks.deactivate(repo)
 
         extrafn = _makeextrafn(extrafns)
 
@@ -498,7 +502,7 @@
 
         if (activebookmark and
             repo['.'].node() == repo._bookmarks[activebookmark]):
-                bookmarks.setcurrent(repo, activebookmark)
+                bookmarks.activate(repo, activebookmark)
 
     finally:
         release(lock, wlock)
@@ -530,10 +534,9 @@
     '''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 = cmdutil.dirstateguard(repo, 'rebase')
     try:
-        repo.dirstate.beginparentchange()
         repo.setparents(repo[p1].node(), repo[p2].node())
-        repo.dirstate.endparentchange()
         ctx = repo[rev]
         if commitmsg is None:
             commitmsg = ctx.description()
@@ -552,11 +555,10 @@
             repo.ui.restoreconfig(backup)
 
         repo.dirstate.setbranch(repo[newnode].branch())
+        dsguard.close()
         return newnode
-    except util.Abort:
-        # Invalidate the previous setparents
-        repo.dirstate.invalidate()
-        raise
+    finally:
+        release(dsguard)
 
 def rebasenode(repo, rev, p1, base, state, collapse, target):
     'Rebase a single revision rev on top of p1 using base as merge ancestor'
@@ -836,7 +838,7 @@
         _setrebasesetvisibility(repo, state.keys())
         return (originalwd, target, state, skipped,
                 collapse, keep, keepbranches, external, activebookmark)
-    except IOError, err:
+    except IOError as err:
         if err.errno != errno.ENOENT:
             raise
         raise util.Abort(_('no rebase in progress'))
@@ -867,7 +869,7 @@
     immutable = [d for d in dstates if not repo[d].mutable()]
     cleanup = True
     if immutable:
-        repo.ui.warn(_("warning: can't clean up immutable changesets %s\n")
+        repo.ui.warn(_("warning: can't clean up public changesets %s\n")
                      % ', '.join(str(repo[r]) for r in immutable),
                      hint=_('see "hg help phases" for details'))
         cleanup = False
@@ -893,7 +895,7 @@
             repair.strip(repo.ui, repo, strippoints)
 
     if activebookmark and activebookmark in repo._bookmarks:
-        bookmarks.setcurrent(repo, activebookmark)
+        bookmarks.activate(repo, activebookmark)
 
     clearstatus(repo)
     repo.ui.warn(_('rebase aborted\n'))
@@ -1057,7 +1059,7 @@
                 hg.update(repo, dest)
                 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
                     ui.status(_("updating bookmark %s\n")
-                              % repo._bookmarkcurrent)
+                              % repo._activebookmark)
     else:
         if opts.get('tool'):
             raise util.Abort(_('--tool can only be used with --rebase'))
@@ -1118,4 +1120,3 @@
          _("use 'hg rebase --continue' or 'hg rebase --abort'")])
     # ensure rebased rev are not hidden
     extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
-
--- a/hgext/record.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/record.py	Sat Jul 18 17:32:38 2015 -0500
@@ -13,6 +13,10 @@
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 
@@ -49,8 +53,17 @@
 
     This command is not available when committing a merge.'''
 
+    if not ui.interactive():
+        raise util.Abort(_('running non-interactively, use %s instead') %
+                         'commit')
+
     opts["interactive"] = True
-    commands.commit(ui, repo, *pats, **opts)
+    backup = ui.backupconfig('experimental', 'crecord')
+    try:
+        ui.setconfig('experimental', 'crecord', False, 'record')
+        commands.commit(ui, repo, *pats, **opts)
+    finally:
+        ui.restoreconfig(backup)
 
 def qrefresh(origfn, ui, repo, *pats, **opts):
     if not opts['interactive']:
@@ -66,7 +79,7 @@
         mq.refresh(ui, repo, **opts)
 
     # backup all changed files
-    cmdutil.dorecord(ui, repo, committomq, 'qrefresh', True,
+    cmdutil.dorecord(ui, repo, committomq, None, True,
                     cmdutil.recordfilter, *pats, **opts)
 
 # This command registration is replaced during uisetup().
@@ -80,7 +93,9 @@
     See :hg:`help qnew` & :hg:`help record` for more information and
     usage.
     '''
+    return _qrecord('qnew', ui, repo, patch, *pats, **opts)
 
+def _qrecord(cmdsuggest, ui, repo, patch, *pats, **opts):
     try:
         mq = extensions.find('mq')
     except KeyError:
@@ -92,12 +107,17 @@
         opts['checkname'] = False
         mq.new(ui, repo, patch, *pats, **opts)
 
-    cmdutil.dorecord(ui, repo, committomq, 'qnew', False,
-                    cmdutil.recordfilter, *pats, **opts)
+    backup = ui.backupconfig('experimental', 'crecord')
+    try:
+        ui.setconfig('experimental', 'crecord', False, 'record')
+        cmdutil.dorecord(ui, repo, committomq, cmdsuggest, False,
+                         cmdutil.recordfilter, *pats, **opts)
+    finally:
+        ui.restoreconfig(backup)
 
 def qnew(origfn, ui, repo, patch, *args, **opts):
     if opts['interactive']:
-        return qrecord(ui, repo, patch, *args, **opts)
+        return _qrecord(None, ui, repo, patch, *args, **opts)
     return origfn(ui, repo, patch, *args, **opts)
 
 
--- a/hgext/relink.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/relink.py	Sat Jul 18 17:32:38 2015 -0500
@@ -13,6 +13,10 @@
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 @command('relink', [], _('[ORIGIN]'))
@@ -174,7 +178,7 @@
             ui.progress(_('relinking'), pos, f, _('files'), total)
             relinked += 1
             savedbytes += sz
-        except OSError, inst:
+        except OSError as inst:
             ui.warn('%s: %s\n' % (tgt, str(inst)))
 
     ui.progress(_('relinking'), None)
--- a/hgext/schemes.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/schemes.py	Sat Jul 18 17:32:38 2015 -0500
@@ -44,6 +44,10 @@
 from mercurial import extensions, hg, templater, util
 from mercurial.i18n import _
 
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 
--- a/hgext/share.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/share.py	Sat Jul 18 17:32:38 2015 -0500
@@ -3,15 +3,51 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-'''share a common history between several working directories'''
+'''share a common history between several working directories
+
+Automatic Pooled Storage for Clones
+-----------------------------------
+
+When this extension is active, :hg:`clone` can be configured to
+automatically share/pool storage across multiple clones. This
+mode effectively converts :hg:`clone` to :hg:`clone` + :hg:`share`.
+The benefit of using this mode is the automatic management of
+store paths and intelligent pooling of related repositories.
+
+The following ``share.`` config options influence this feature:
+
+``share.pool``
+    Filesystem path where shared repository data will be stored. When
+    defined, :hg:`clone` will automatically use shared repository
+    storage instead of creating a store inside each clone.
+
+``share.poolnaming``
+    How directory names in ``share.pool`` are constructed.
+
+    "identity" means the name is derived from the first changeset in the
+    repository. In this mode, different remotes share storage if their
+    root/initial changeset is identical. In this mode, the local shared
+    repository is an aggregate of all encountered remote repositories.
+
+    "remote" means the name is derived from the source repository's
+    path or URL. In this mode, storage is only shared if the path or URL
+    requested in the :hg:`clone` command matches exactly to a repository
+    that was cloned before.
+
+    The default naming mode is "identity."
+'''
 
 from mercurial.i18n import _
-from mercurial import cmdutil, hg, util, extensions, bookmarks
+from mercurial import cmdutil, commands, hg, util, extensions, bookmarks
 from mercurial.hg import repository, parseurl
 import errno
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 @command('share',
@@ -68,19 +104,33 @@
         destlock and destlock.release()
         lock and lock.release()
 
-    # update store, spath, sopener and sjoin of repo
+    # update store, spath, svfs and sjoin of repo
     repo.unfiltered().__init__(repo.baseui, repo.root)
 
+# Wrap clone command to pass auto share options.
+def clone(orig, ui, source, *args, **opts):
+    pool = ui.config('share', 'pool', None)
+    if pool:
+        pool = util.expandpath(pool)
+
+    opts['shareopts'] = dict(
+        pool=pool,
+        mode=ui.config('share', 'poolnaming', 'identity'),
+    )
+
+    return orig(ui, source, *args, **opts)
+
 def extsetup(ui):
     extensions.wrapfunction(bookmarks.bmstore, 'getbkfile', getbkfile)
     extensions.wrapfunction(bookmarks.bmstore, 'recordchange', recordchange)
     extensions.wrapfunction(bookmarks.bmstore, 'write', write)
+    extensions.wrapcommand(commands.table, 'clone', clone)
 
 def _hassharedbookmarks(repo):
     """Returns whether this repo has shared bookmarks"""
     try:
         shared = repo.vfs.read('shared').splitlines()
-    except IOError, inst:
+    except IOError as inst:
         if inst.errno != errno.ENOENT:
             raise
         return False
--- a/hgext/shelve.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/shelve.py	Sat Jul 18 17:32:38 2015 -0500
@@ -21,6 +21,8 @@
 shelve".
 """
 
+import collections
+import itertools
 from mercurial.i18n import _
 from mercurial.node import nullid, nullrev, bin, hex
 from mercurial import changegroup, cmdutil, scmutil, phases, commands
@@ -32,8 +34,14 @@
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
+backupdir = 'shelve-backup'
+
 class shelvedfile(object):
     """Helper for the file storing a single shelve
 
@@ -43,6 +51,7 @@
         self.repo = repo
         self.name = name
         self.vfs = scmutil.vfs(repo.join('shelved'))
+        self.backupvfs = scmutil.vfs(repo.join(backupdir))
         self.ui = self.repo.ui
         if filetype:
             self.fname = name + '.' + filetype
@@ -55,8 +64,22 @@
     def filename(self):
         return self.vfs.join(self.fname)
 
-    def unlink(self):
-        util.unlink(self.filename())
+    def backupfilename(self):
+        def gennames(base):
+            yield base
+            base, ext = base.rsplit('.', 1)
+            for i in itertools.count(1):
+                yield '%s-%d.%s' % (base, i, ext)
+
+        name = self.backupvfs.join(self.fname)
+        for n in gennames(name):
+            if not self.backupvfs.exists(n):
+                return n
+
+    def movetobackup(self):
+        if not self.backupvfs.isdir():
+            self.backupvfs.makedir()
+        util.rename(self.filename(), self.backupfilename())
 
     def stat(self):
         return self.vfs.stat(self.fname)
@@ -64,7 +87,7 @@
     def opener(self, mode='rb'):
         try:
             return self.vfs(self.fname, mode)
-        except IOError, err:
+        except IOError as err:
             if err.errno != errno.ENOENT:
                 raise
             raise util.Abort(_("shelved change '%s' not found") % self.name)
@@ -135,6 +158,27 @@
     def clear(cls, repo):
         util.unlinkpath(repo.join(cls._filename), ignoremissing=True)
 
+def cleanupoldbackups(repo):
+    vfs = scmutil.vfs(repo.join(backupdir))
+    maxbackups = repo.ui.configint('shelve', 'maxbackups', 10)
+    hgfiles = [f for f in vfs.listdir() if f.endswith('.hg')]
+    hgfiles = sorted([(vfs.stat(f).st_mtime, f) for f in hgfiles])
+    if 0 < maxbackups and maxbackups < len(hgfiles):
+        bordermtime = hgfiles[-maxbackups][0]
+    else:
+        bordermtime = None
+    for mtime, f in hgfiles[:len(hgfiles) - maxbackups]:
+        if mtime == bordermtime:
+            # keep it, because timestamp can't decide exact order of backups
+            continue
+        base = f[:-3]
+        for ext in 'hg patch'.split():
+            try:
+                vfs.unlink(base + '.' + ext)
+            except OSError as err:
+                if err.errno != errno.ENOENT:
+                    raise
+
 def createcmd(ui, repo, pats, opts):
     """subcommand that creates a new shelve"""
 
@@ -143,7 +187,7 @@
 
         Much faster than the revset ancestors(ctx) & draft()"""
         seen = set([nullrev])
-        visit = util.deque()
+        visit = collections.deque()
         visit.append(ctx)
         while visit:
             ctx = visit.popleft()
@@ -163,7 +207,7 @@
 
     # we never need the user, so we use a generic user for all shelve operations
     user = 'shelve@localhost'
-    label = repo._bookmarkcurrent or parent.branch() or 'default'
+    label = repo._activebookmark or parent.branch() or 'default'
 
     # slashes aren't allowed in filenames, therefore we rename it
     label = label.replace('/', '_')
@@ -235,7 +279,7 @@
         if not interactive:
             node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
         else:
-            node = cmdutil.dorecord(ui, repo, interactivecommitfunc, 'commit',
+            node = cmdutil.dorecord(ui, repo, interactivecommitfunc, None,
                                     False, cmdutil.recordfilter, *pats, **opts)
         if not node:
             stat = repo.status(match=scmutil.match(repo[None], pats, opts))
@@ -276,7 +320,8 @@
         for (name, _type) in repo.vfs.readdir('shelved'):
             suffix = name.rsplit('.', 1)[-1]
             if suffix in ('hg', 'patch'):
-                shelvedfile(repo, name).unlink()
+                shelvedfile(repo, name).movetobackup()
+            cleanupoldbackups(repo)
     finally:
         lockmod.release(wlock)
 
@@ -284,17 +329,16 @@
     """subcommand that deletes a specific shelve"""
     if not pats:
         raise util.Abort(_('no shelved changes specified!'))
-    wlock = None
+    wlock = repo.wlock()
     try:
-        wlock = repo.wlock()
-        try:
-            for name in pats:
-                for suffix in 'hg patch'.split():
-                    shelvedfile(repo, name, suffix).unlink()
-        except OSError, err:
-            if err.errno != errno.ENOENT:
-                raise
-            raise util.Abort(_("shelved change '%s' not found") % name)
+        for name in pats:
+            for suffix in 'hg patch'.split():
+                shelvedfile(repo, name, suffix).movetobackup()
+        cleanupoldbackups(repo)
+    except OSError as err:
+        if err.errno != errno.ENOENT:
+            raise
+        raise util.Abort(_("shelved change '%s' not found") % name)
     finally:
         lockmod.release(wlock)
 
@@ -302,7 +346,7 @@
     """return all shelves in repo as list of (time, filename)"""
     try:
         names = repo.vfs.readdir('shelved')
-    except OSError, err:
+    except OSError as err:
         if err.errno != errno.ENOENT:
             raise
         return []
@@ -363,6 +407,17 @@
         finally:
             fp.close()
 
+def singlepatchcmds(ui, repo, pats, opts, subcommand):
+    """subcommand that displays a single shelf"""
+    if len(pats) != 1:
+        raise util.Abort(_("--%s expects a single shelf") % subcommand)
+    shelfname = pats[0]
+
+    if not shelvedfile(repo, shelfname, 'patch').exists():
+        raise util.Abort(_("cannot find shelf %s") % shelfname)
+
+    listcmd(ui, repo, pats, opts)
+
 def checkparents(repo, state):
     """check parent while resuming an unshelve"""
     if state.parents != repo.dirstate.parents():
@@ -428,7 +483,8 @@
     """remove related files after an unshelve"""
     if not opts['keep']:
         for filetype in 'hg patch'.split():
-            shelvedfile(repo, name, filetype).unlink()
+            shelvedfile(repo, name, filetype).movetobackup()
+        cleanupoldbackups(repo)
 
 def unshelvecontinue(ui, repo, state, opts):
     """subcommand to continue an in-progress unshelve"""
@@ -491,18 +547,30 @@
     restore. If none is given, the most recent shelved change is used.
 
     If a shelved change is applied successfully, the bundle that
-    contains the shelved changes is deleted afterwards.
+    contains the shelved changes is moved to a backup location
+    (.hg/shelve-backup).
 
     Since you can restore a shelved change on top of an arbitrary
     commit, it is possible that unshelving will result in a conflict
     between your changes and the commits you are unshelving onto. If
     this occurs, you must resolve the conflict, then use
     ``--continue`` to complete the unshelve operation. (The bundle
-    will not be deleted until you successfully complete the unshelve.)
+    will not be moved until you successfully complete the unshelve.)
 
     (Alternatively, you can use ``--abort`` to abandon an unshelve
     that causes a conflict. This reverts the unshelved changes, and
-    does not delete the bundle.)
+    leaves the bundle in place.)
+
+    After a successful unshelve, the shelved changes are stored in a
+    backup directory. Only the N most recent backups are kept. N
+    defaults to 10 but can be overridden using the ``shelve.maxbackups``
+    configuration option.
+
+    .. container:: verbose
+
+       Timestamp in seconds is used to decide order of backups. More
+       than ``maxbackups`` backups are kept, if same timestamp
+       prevents from deciding exact order of them, for safety.
     """
     abortf = opts['abort']
     continuef = opts['continue']
@@ -518,7 +586,7 @@
 
         try:
             state = shelvedstate.load(repo)
-        except IOError, err:
+        except IOError as err:
             if err.errno != errno.ENOENT:
                 raise
             raise util.Abort(_('no unshelve operation underway'))
@@ -658,8 +726,7 @@
           ('p', 'patch', None,
            _('show patch')),
           ('i', 'interactive', None,
-           _('interactive mode, only works while creating a shelve'
-                   '(EXPERIMENTAL)')),
+           _('interactive mode, only works while creating a shelve')),
           ('', 'stat', None,
            _('output diffstat-style summary of changes'))] + commands.walkopts,
          _('hg shelve [OPTION]... [FILE]...'))
@@ -693,21 +760,21 @@
     cmdutil.checkunfinished(repo)
 
     allowables = [
-        ('addremove', 'create'), # 'create' is pseudo action
-        ('cleanup', 'cleanup'),
-#       ('date', 'create'), # ignored for passing '--date "0 0"' in tests
-        ('delete', 'delete'),
-        ('edit', 'create'),
-        ('list', 'list'),
-        ('message', 'create'),
-        ('name', 'create'),
-        ('patch', 'list'),
-        ('stat', 'list'),
+        ('addremove', set(['create'])), # 'create' is pseudo action
+        ('cleanup', set(['cleanup'])),
+#       ('date', set(['create'])), # ignored for passing '--date "0 0"' in tests
+        ('delete', set(['delete'])),
+        ('edit', set(['create'])),
+        ('list', set(['list'])),
+        ('message', set(['create'])),
+        ('name', set(['create'])),
+        ('patch', set(['patch', 'list'])),
+        ('stat', set(['stat', 'list'])),
     ]
     def checkopt(opt):
         if opts[opt]:
             for i, allowable in allowables:
-                if opts[i] and opt != allowable:
+                if opts[i] and opt not in allowable:
                     raise util.Abort(_("options '--%s' and '--%s' may not be "
                                        "used together") % (opt, i))
             return True
@@ -719,11 +786,11 @@
         return deletecmd(ui, repo, pats)
     elif checkopt('list'):
         return listcmd(ui, repo, pats, opts)
+    elif checkopt('patch'):
+        return singlepatchcmds(ui, repo, pats, opts, subcommand='patch')
+    elif checkopt('stat'):
+        return singlepatchcmds(ui, repo, pats, opts, subcommand='stat')
     else:
-        for i in ('patch', 'stat'):
-            if opts[i]:
-                raise util.Abort(_("option '--%s' may not be "
-                                   "used when shelving a change") % (i,))
         return createcmd(ui, repo, pats, opts)
 
 def extsetup(ui):
--- a/hgext/strip.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/strip.py	Sat Jul 18 17:32:38 2015 -0500
@@ -11,6 +11,10 @@
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 def checksubstate(repo, baserev=None):
@@ -60,8 +64,8 @@
 
         marks = repo._bookmarks
         if bookmark:
-            if bookmark == repo._bookmarkcurrent:
-                bookmarks.unsetcurrent(repo)
+            if bookmark == repo._activebookmark:
+                bookmarks.deactivate(repo)
             del marks[bookmark]
             marks.write()
             ui.write(_("bookmark '%s' deleted\n") % bookmark)
--- a/hgext/transplant.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/transplant.py	Sat Jul 18 17:32:38 2015 -0500
@@ -26,6 +26,10 @@
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 class transplantentry(object):
@@ -268,7 +272,7 @@
                 files = set()
                 patch.patch(self.ui, repo, patchfile, files=files, eolmode=None)
                 files = list(files)
-            except Exception, inst:
+            except Exception as inst:
                 seriespath = os.path.join(self.path, 'series')
                 if os.path.exists(seriespath):
                     os.unlink(seriespath)
@@ -604,8 +608,10 @@
     checkopts(opts, revs)
 
     if not opts.get('log'):
+        # deprecated config: transplant.log
         opts['log'] = ui.config('transplant', 'log')
     if not opts.get('filter'):
+        # deprecated config: transplant.filter
         opts['filter'] = ui.config('transplant', 'filter')
 
     tp = transplanter(ui, repo, opts)
--- a/hgext/win32mbcs.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/win32mbcs.py	Sat Jul 18 17:32:38 2015 -0500
@@ -48,6 +48,10 @@
 import os, sys
 from mercurial.i18n import _
 from mercurial import util, encoding
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 _encoding = None                                # see extsetup
--- a/hgext/win32text.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/win32text.py	Sat Jul 18 17:32:38 2015 -0500
@@ -46,6 +46,10 @@
 from mercurial import util
 import re
 
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 # regexp for single LF without CR preceding.
@@ -167,6 +171,7 @@
         repo.adddatafilter(name, fn)
 
 def extsetup(ui):
+    # deprecated config: win32text.warn
     if ui.configbool('win32text', 'warn', True):
         ui.warn(_("win32text is deprecated: "
                   "http://mercurial.selenic.com/wiki/Win32TextExtension\n"))
--- a/hgext/zeroconf/__init__.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/hgext/zeroconf/__init__.py	Sat Jul 18 17:32:38 2015 -0500
@@ -31,6 +31,10 @@
 from mercurial import extensions
 from mercurial.hgweb import server as servermod
 
+# Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
 
 # publish
--- a/i18n/polib.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/i18n/polib.py	Sat Jul 18 17:32:38 2015 -0500
@@ -1276,7 +1276,7 @@
             (action, state) = self.transitions[(symbol, self.current_state)]
             if action():
                 self.current_state = state
-        except Exception, exc:
+        except Exception as exc:
             raise IOError('Syntax error in po file (line %s)' % linenum)
 
     # state handlers
--- a/mercurial/ancestor.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/ancestor.py	Sat Jul 18 17:32:38 2015 -0500
@@ -5,8 +5,8 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+import collections
 import heapq
-import util
 from node import nullrev
 
 def commonancestorsheads(pfunc, *nodes):
@@ -314,13 +314,16 @@
 
         parentrevs = self._parentrevs
         stoprev = self._stoprev
-        visit = util.deque(revs)
+        visit = collections.deque(revs)
+
+        see = seen.add
+        schedule = visit.append
 
         while visit:
             for parent in parentrevs(visit.popleft()):
                 if parent >= stoprev and parent not in seen:
-                    visit.append(parent)
-                    seen.add(parent)
+                    schedule(parent)
+                    see(parent)
                     yield parent
 
     def __contains__(self, target):
@@ -337,6 +340,7 @@
         stoprev = self._stoprev
         heappop = heapq.heappop
         heappush = heapq.heappush
+        see = seen.add
 
         targetseen = False
 
@@ -347,7 +351,7 @@
                 # We need to make sure we push all parents into the heap so
                 # that we leave it in a consistent state for future calls.
                 heappush(visit, -parent)
-                seen.add(parent)
+                see(parent)
                 if parent == target:
                     targetseen = True
 
--- a/mercurial/archival.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/archival.py	Sat Jul 18 17:32:38 2015 -0500
@@ -37,6 +37,10 @@
     prefix = util.pconvert(lpfx)
     if not prefix.endswith('/'):
         prefix += '/'
+    # Drop the leading '.' path component if present, so Windows can read the
+    # zip files (issue4634)
+    if prefix.startswith('./'):
+        prefix = prefix[2:]
     if prefix.startswith('../') or os.path.isabs(lpfx) or '/../' in prefix:
         raise util.Abort(_('archive prefix contains illegal components'))
     return prefix
@@ -50,7 +54,7 @@
 
 def guesskind(dest):
     for kind, extensions in exts.iteritems():
-        if util.any(dest.endswith(ext) for ext in extensions):
+        if any(dest.endswith(ext) for ext in extensions):
             return kind
     return None
 
@@ -63,20 +67,25 @@
 def buildmetadata(ctx):
     '''build content of .hg_archival.txt'''
     repo = ctx.repo()
+    hex = ctx.hex()
+    if ctx.rev() is None:
+        hex = ctx.p1().hex()
+        if ctx.dirty():
+            hex += '+'
+
     base = 'repo: %s\nnode: %s\nbranch: %s\n' % (
-        _rootctx(repo).hex(), ctx.hex(), encoding.fromlocal(ctx.branch()))
+        _rootctx(repo).hex(), hex, encoding.fromlocal(ctx.branch()))
 
     tags = ''.join('tag: %s\n' % t for t in ctx.tags()
                    if repo.tagtype(t) == 'global')
     if not tags:
         repo.ui.pushbuffer()
-        opts = {'template': '{latesttag}\n{latesttagdistance}',
+        opts = {'template': '{latesttag}\n{latesttagdistance}\n'
+                            '{changessincelatesttag}',
                 'style': '', 'patch': None, 'git': None}
         cmdutil.show_changeset(repo.ui, repo, opts).show(ctx)
-        ltags, dist = repo.ui.popbuffer().split('\n')
+        ltags, dist, changessince = repo.ui.popbuffer().split('\n')
         ltags = ltags.split(':')
-        # XXX: ctx.rev() needs to be handled differently with wdir()
-        changessince = len(repo.revs('only(%d,%s)', ctx.rev(), ltags[0]))
         tags = ''.join('latesttag: %s\n' % t for t in ltags)
         tags += 'latesttagdistance: %s\n' % dist
         tags += 'changessincelatesttag: %s\n' % changessince
@@ -148,7 +157,7 @@
         i.size = len(data)
         if islink:
             i.type = tarfile.SYMTYPE
-            i.mode = 0777
+            i.mode = 0o777
             i.linkname = data
             data = None
             i.size = 0
@@ -211,7 +220,7 @@
         i.create_system = 3
         ftype = _UNX_IFREG
         if islink:
-            mode = 0777
+            mode = 0o777
             ftype = _UNX_IFLNK
         i.external_attr = (mode | ftype) << 16L
         # add "extended-timestamp" extra block, because zip archives
@@ -293,7 +302,7 @@
     if repo.ui.configbool("ui", "archivemeta", True):
         name = '.hg_archival.txt'
         if not matchfn or matchfn(name):
-            write(name, 0644, False, lambda: buildmetadata(ctx))
+            write(name, 0o644, False, lambda: buildmetadata(ctx))
 
     if matchfn:
         files = [f for f in ctx.manifest().keys() if matchfn(f)]
@@ -305,14 +314,14 @@
         repo.ui.progress(_('archiving'), 0, unit=_('files'), total=total)
         for i, f in enumerate(files):
             ff = ctx.flags(f)
-            write(f, 'x' in ff and 0755 or 0644, 'l' in ff, ctx[f].data)
+            write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, ctx[f].data)
             repo.ui.progress(_('archiving'), i + 1, item=f,
                              unit=_('files'), total=total)
         repo.ui.progress(_('archiving'), None)
 
     if subrepos:
         for subpath in sorted(ctx.substate):
-            sub = ctx.sub(subpath)
+            sub = ctx.workingsub(subpath)
             submatch = matchmod.narrowmatcher(subpath, matchfn)
             total += sub.archive(archiver, prefix, submatch)
 
--- a/mercurial/bookmarks.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/bookmarks.py	Sat Jul 18 17:32:38 2015 -0500
@@ -8,7 +8,7 @@
 import os
 from mercurial.i18n import _
 from mercurial.node import hex, bin
-from mercurial import encoding, error, util, obsolete, lock as lockmod
+from mercurial import encoding, util, obsolete, lock as lockmod
 import errno
 
 class bmstore(dict):
@@ -22,7 +22,7 @@
     {hash}\s{name}\n (the same format as localtags) in
     .hg/bookmarks. The mapping is stored as {name: nodeid}.
 
-    This class does NOT handle the "current" bookmark state at this
+    This class does NOT handle the "active" bookmark state at this
     time.
     """
 
@@ -45,7 +45,7 @@
                     self[refspec] = repo.changelog.lookup(sha)
                 except LookupError:
                     pass
-        except IOError, inst:
+        except IOError as inst:
             if inst.errno != errno.ENOENT:
                 raise
 
@@ -54,7 +54,7 @@
         if 'HG_PENDING' in os.environ:
             try:
                 bkfile = repo.vfs('bookmarks.pending')
-            except IOError, inst:
+            except IOError as inst:
                 if inst.errno != errno.ENOENT:
                     raise
         if bkfile is None:
@@ -80,11 +80,12 @@
         '''
         repo = self._repo
         self._writerepo(repo)
+        repo.invalidatevolatilesets()
 
     def _writerepo(self, repo):
         """Factored out for extensibility"""
-        if repo._bookmarkcurrent not in self:
-            unsetcurrent(repo)
+        if repo._activebookmark not in self:
+            deactivate(repo)
 
         wlock = repo.wlock()
         try:
@@ -93,12 +94,6 @@
             self._write(file)
             file.close()
 
-            # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
-            try:
-                repo.svfs.utime('00changelog.i', None)
-            except OSError:
-                pass
-
         finally:
             wlock.release()
 
@@ -106,17 +101,16 @@
         for name, node in self.iteritems():
             fp.write("%s %s\n" % (hex(node), encoding.fromlocal(name)))
 
-def readcurrent(repo):
-    '''Get the current bookmark
-
-    If we use gittish branches we have a current bookmark that
-    we are on. This function returns the name of the bookmark. It
-    is stored in .hg/bookmarks.current
-    '''
+def readactive(repo):
+    """
+    Get the active bookmark. We can have an active bookmark that updates
+    itself as we commit. This function returns the name of that bookmark.
+    It is stored in .hg/bookmarks.current
+    """
     mark = None
     try:
         file = repo.vfs('bookmarks.current')
-    except IOError, inst:
+    except IOError as inst:
         if inst.errno != errno.ENOENT:
             raise
         return None
@@ -129,17 +123,17 @@
         file.close()
     return mark
 
-def setcurrent(repo, mark):
-    '''Set the name of the bookmark that we are currently on
-
-    Set the name of the bookmark that we are on (hg update <bookmark>).
+def activate(repo, mark):
+    """
+    Set the given bookmark to be 'active', meaning that this bookmark will
+    follow new commits that are made.
     The name is recorded in .hg/bookmarks.current
-    '''
+    """
     if mark not in repo._bookmarks:
         raise AssertionError('bookmark %s does not exist!' % mark)
 
-    current = repo._bookmarkcurrent
-    if current == mark:
+    active = repo._activebookmark
+    if active == mark:
         return
 
     wlock = repo.wlock()
@@ -149,42 +143,36 @@
         file.close()
     finally:
         wlock.release()
-    repo._bookmarkcurrent = mark
+    repo._activebookmark = mark
 
-def unsetcurrent(repo):
+def deactivate(repo):
+    """
+    Unset the active bookmark in this reposiotry.
+    """
     wlock = repo.wlock()
     try:
-        try:
-            repo.vfs.unlink('bookmarks.current')
-            repo._bookmarkcurrent = None
-        except OSError, inst:
-            if inst.errno != errno.ENOENT:
-                raise
+        repo.vfs.unlink('bookmarks.current')
+        repo._activebookmark = None
+    except OSError as inst:
+        if inst.errno != errno.ENOENT:
+            raise
     finally:
         wlock.release()
 
-def iscurrent(repo, mark=None, parents=None):
-    '''Tell whether the current bookmark is also active
+def isactivewdirparent(repo):
+    """
+    Tell whether the 'active' bookmark (the one that follows new commits)
+    points to one of the parents of the current working directory (wdir).
 
-    I.e., the bookmark listed in .hg/bookmarks.current also points to a
-    parent of the working directory.
-    '''
-    if not mark:
-        mark = repo._bookmarkcurrent
-    if not parents:
-        parents = [p.node() for p in repo[None].parents()]
+    While this is normally the case, it can on occasion be false; for example,
+    immediately after a pull, the active bookmark can be moved to point
+    to a place different than the wdir. This is solved by running `hg update`.
+    """
+    mark = repo._activebookmark
     marks = repo._bookmarks
+    parents = [p.node() for p in repo[None].parents()]
     return (mark in marks and marks[mark] in parents)
 
-def updatecurrentbookmark(repo, oldnode, curbranch):
-    try:
-        return update(repo, oldnode, repo.branchtip(curbranch))
-    except error.RepoLookupError:
-        if curbranch == "default": # no default branch!
-            return update(repo, oldnode, repo.lookup("tip"))
-        else:
-            raise util.Abort(_("branch %s not found") % curbranch)
-
 def deletedivergent(repo, deletefrom, bm):
     '''Delete divergent versions of bm on nodes in deletefrom.
 
@@ -207,33 +195,33 @@
     check out and where to move the active bookmark from, if needed.'''
     movemarkfrom = None
     if checkout is None:
-        curmark = repo._bookmarkcurrent
-        if iscurrent(repo):
+        activemark = repo._activebookmark
+        if isactivewdirparent(repo):
             movemarkfrom = repo['.'].node()
-        elif curmark:
-            ui.status(_("updating to active bookmark %s\n") % curmark)
-            checkout = curmark
+        elif activemark:
+            ui.status(_("updating to active bookmark %s\n") % activemark)
+            checkout = activemark
     return (checkout, movemarkfrom)
 
 def update(repo, parents, node):
     deletefrom = parents
     marks = repo._bookmarks
     update = False
-    cur = repo._bookmarkcurrent
-    if not cur:
+    active = repo._activebookmark
+    if not active:
         return False
 
-    if marks[cur] in parents:
+    if marks[active] in parents:
         new = repo[node]
         divs = [repo[b] for b in marks
-                if b.split('@', 1)[0] == cur.split('@', 1)[0]]
+                if b.split('@', 1)[0] == active.split('@', 1)[0]]
         anc = repo.changelog.ancestors([new.rev()])
         deletefrom = [b.node() for b in divs if b.rev() in anc or b == new]
-        if validdest(repo, repo[marks[cur]], new):
-            marks[cur] = new.node()
+        if validdest(repo, repo[marks[active]], new):
+            marks[active] = new.node()
             update = True
 
-    if deletedivergent(repo, deletefrom, cur):
+    if deletedivergent(repo, deletefrom, active):
         update = True
 
     if update:
@@ -408,6 +396,11 @@
         if scid in repo: # add remote bookmarks for changes we already have
             changed.append((b, bin(scid), status,
                             _("adding remote bookmark %s\n") % (b)))
+        elif b in explicit:
+            explicit.remove(b)
+            ui.warn(_("remote bookmark %s points to locally missing %s\n")
+                    % (b, scid[:12]))
+
     for b, scid, dcid in advsrc:
         changed.append((b, bin(scid), status,
                         _("updating bookmark %s\n") % (b)))
@@ -434,6 +427,11 @@
             explicit.discard(b)
             changed.append((b, bin(scid), status,
                             _("importing bookmark %s\n") % (b)))
+    for b, scid, dcid in differ:
+        if b in explicit:
+            explicit.remove(b)
+            ui.warn(_("remote bookmark %s points to locally missing %s\n")
+                    % (b, scid[:12]))
 
     if changed:
         tr = trfunc()
--- a/mercurial/branchmap.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/branchmap.py	Sat Jul 18 17:32:38 2015 -0500
@@ -55,7 +55,7 @@
                 partial._closednodes.add(node)
     except KeyboardInterrupt:
         raise
-    except Exception, inst:
+    except Exception as inst:
         if repo.ui.debugflag:
             msg = 'invalid branchheads cache'
             if repo.filtername is not None:
@@ -203,7 +203,7 @@
             repo.ui.log('branchcache',
                         'wrote %s branch cache with %d labels and %d nodes\n',
                         repo.filtername, len(self), nodecount)
-        except (IOError, OSError, util.Abort), inst:
+        except (IOError, OSError, util.Abort) as inst:
             repo.ui.debug("couldn't write branch cache: %s\n" % inst)
             # Abort may be raise by read only opener
             pass
@@ -315,7 +315,7 @@
             bndata = repo.vfs.read(_rbcnames)
             self._rbcsnameslen = len(bndata) # for verification before writing
             self._names = [encoding.tolocal(bn) for bn in bndata.split('\0')]
-        except (IOError, OSError), inst:
+        except (IOError, OSError) as inst:
             if readonly:
                 # don't try to use cache - fall back to the slow path
                 self.branchinfo = self._branchinfo
@@ -324,7 +324,7 @@
             try:
                 data = repo.vfs.read(_rbcrevs)
                 self._rbcrevs.fromstring(data)
-            except (IOError, OSError), inst:
+            except (IOError, OSError) as inst:
                 repo.ui.debug("couldn't read revision branch cache: %s\n" %
                               inst)
         # remember number of good records on disk
@@ -418,7 +418,7 @@
                                   for b in self._names[self._rbcnamescount:]))
                 self._rbcsnameslen = f.tell()
                 f.close()
-            except (IOError, OSError, util.Abort), inst:
+            except (IOError, OSError, util.Abort) as inst:
                 repo.ui.debug("couldn't write revision branch cache names: "
                               "%s\n" % inst)
                 return
@@ -436,7 +436,7 @@
                 end = revs * _rbcrecsize
                 f.write(self._rbcrevs[start:end])
                 f.close()
-            except (IOError, OSError, util.Abort), inst:
+            except (IOError, OSError, util.Abort) as inst:
                 repo.ui.debug("couldn't write revision branch cache: %s\n" %
                               inst)
                 return
--- a/mercurial/bundle2.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/bundle2.py	Sat Jul 18 17:32:38 2015 -0500
@@ -66,7 +66,7 @@
 
 :header size: int32
 
-  The total number of Bytes used by the part headers. When the header is empty
+  The total number of Bytes used by the part header. When the header is empty
   (size = 0) this is interpreted as the end of stream marker.
 
 :header:
@@ -156,7 +156,7 @@
 import url
 import re
 
-import changegroup, error
+import changegroup, error, tags
 from i18n import _
 
 _pack = struct.pack
@@ -173,6 +173,16 @@
 
 _parttypeforbidden = re.compile('[^a-zA-Z0-9_:-]')
 
+def outdebug(ui, message):
+    """debug regarding output stream (bundling)"""
+    if ui.configbool('devel', 'bundle2.debug', False):
+        ui.debug('bundle2-output: %s\n' % message)
+
+def indebug(ui, message):
+    """debug on input stream (unbundling)"""
+    if ui.configbool('devel', 'bundle2.debug', False):
+        ui.debug('bundle2-input: %s\n' % message)
+
 def validateparttype(parttype):
     """raise ValueError if a parttype contains invalid character"""
     if _parttypeforbidden.search(parttype):
@@ -310,13 +320,24 @@
     # - replace this is a init function soon.
     # - exception catching
     unbundler.params
-    iterparts = unbundler.iterparts()
+    if repo.ui.debugflag:
+        msg = ['bundle2-input-bundle:']
+        if unbundler.params:
+            msg.append(' %i params')
+        if op.gettransaction is None:
+            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 part in iterparts:
+        for nbpart, part in iterparts:
             _processpart(op, part)
-    except Exception, exc:
-        for part in iterparts:
+    except BaseException as exc:
+        for nbpart, part in iterparts:
             # consume the bundle content
             part.seek(0, 2)
         # Small hack to let caller code distinguish exceptions from bundle2
@@ -326,10 +347,16 @@
         # 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
         raise
+    finally:
+        repo.ui.debug('bundle2-input-bundle: %i parts total\n' % nbpart)
+
     return op
 
 def _processpart(op, part):
@@ -337,23 +364,43 @@
 
     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.UnsupportedPartError(parttype=part.type)
-            op.ui.debug('found a handler for part %r\n' % 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.UnsupportedPartError(parttype=part.type,
                                                params=unknownparams)
-        except error.UnsupportedPartError, exc:
+            status = 'supported'
+        except error.UnsupportedPartError as exc:
             if part.mandatory: # mandatory parts
                 raise
-            op.ui.debug('ignoring unsupported advisory part %s\n' % exc)
+            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
@@ -464,20 +511,26 @@
 
     # methods used to generate the bundle2 stream
     def getchunks(self):
-        self.ui.debug('start emission of %s stream\n' % self._magicstring)
+        if self.ui.debugflag:
+            msg = ['bundle2-output-bundle: "%s",' % self._magicstring]
+            if self._params:
+                msg.append(' (%i params)' % len(self._params))
+            msg.append(' %i parts total\n' % len(self._parts))
+            self.ui.debug(''.join(msg))
+        outdebug(self.ui, 'start emission of %s stream' % self._magicstring)
         yield self._magicstring
         param = self._paramchunk()
-        self.ui.debug('bundle parameter: %s\n' % param)
+        outdebug(self.ui, 'bundle parameter: %s' % param)
         yield _pack(_fstreamparamsize, len(param))
         if param:
             yield param
 
-        self.ui.debug('start of parts\n')
+        outdebug(self.ui, 'start of parts')
         for part in self._parts:
-            self.ui.debug('bundle part: "%s"\n' % part.type)
-            for chunk in part.getchunks():
+            outdebug(self.ui, 'bundle part: "%s"' % part.type)
+            for chunk in part.getchunks(ui=self.ui):
                 yield chunk
-        self.ui.debug('end of bundle\n')
+        outdebug(self.ui, 'end of bundle')
         yield _pack(_fpartheadersize, 0)
 
     def _paramchunk(self):
@@ -532,7 +585,7 @@
         if self._seekable:
             try:
                 return self._fp.tell()
-            except IOError, e:
+            except IOError as e:
                 if e.errno == errno.ESPIPE:
                     self._seekable = False
                 else:
@@ -544,18 +597,18 @@
         if util.safehasattr(self._fp, 'close'):
             return self._fp.close()
 
-def getunbundler(ui, fp, header=None):
-    """return a valid unbundler object for a given header"""
-    if header is None:
-        header = changegroup.readexactly(fp, 4)
-    magic, version = header[0:2], header[2:4]
+def getunbundler(ui, fp, magicstring=None):
+    """return a valid unbundler object for a given magicstring"""
+    if magicstring is None:
+        magicstring = changegroup.readexactly(fp, 4)
+    magic, version = magicstring[0:2], magicstring[2:4]
     if magic != 'HG':
         raise util.Abort(_('not a Mercurial bundle'))
     unbundlerclass = formatmap.get(version)
     if unbundlerclass is None:
         raise util.Abort(_('unknown bundle version %s') % version)
     unbundler = unbundlerclass(ui, fp)
-    ui.debug('start processing of %s stream\n' % header)
+    indebug(ui, 'start processing of %s stream' % magicstring)
     return unbundler
 
 class unbundle20(unpackermixin):
@@ -572,7 +625,7 @@
     @util.propertycache
     def params(self):
         """dictionary of stream level parameters"""
-        self.ui.debug('reading bundle2 stream parameters\n')
+        indebug(self.ui, 'reading bundle2 stream parameters')
         params = {}
         paramssize = self._unpack(_fstreamparamsize)[0]
         if paramssize < 0:
@@ -605,7 +658,7 @@
         # Some logic will be later added here to try to process the option for
         # a dict of known parameter.
         if name[0].islower():
-            self.ui.debug("ignoring unknown parameter %r\n" % name)
+            indebug(self.ui, "ignoring unknown parameter %r" % name)
         else:
             raise error.UnsupportedPartError(params=(name,))
 
@@ -614,14 +667,14 @@
         """yield all parts contained in the stream"""
         # make sure param have been loaded
         self.params
-        self.ui.debug('start extraction of bundle2 parts\n')
+        indebug(self.ui, 'start extraction of bundle2 parts')
         headerblock = self._readpartheader()
         while headerblock is not None:
             part = unbundlepart(self.ui, headerblock, self._fp)
             yield part
             part.seek(0, 2)
             headerblock = self._readpartheader()
-        self.ui.debug('end of bundle2 stream\n')
+        indebug(self.ui, 'end of bundle2 stream')
 
     def _readpartheader(self):
         """reads a part header size and return the bytes blob
@@ -631,7 +684,7 @@
         if headersize < 0:
             raise error.BundleValueError('negative part header size: %i'
                                          % headersize)
-        self.ui.debug('part header size: %i\n' % headersize)
+        indebug(self.ui, 'part header size: %i' % headersize)
         if headersize:
             return self._readexact(headersize)
         return None
@@ -718,15 +771,39 @@
         params.append((name, value))
 
     # methods used to generates the bundle2 stream
-    def getchunks(self):
+    def getchunks(self, ui):
         if self._generated is not None:
             raise RuntimeError('part can only be consumed once')
         self._generated = False
+
+        if ui.debugflag:
+            msg = ['bundle2-output-part: "%s"' % self.type]
+            if not self.mandatory:
+                msg.append(' (advisory)')
+            nbmp = len(self.mandatoryparams)
+            nbap = len(self.advisoryparams)
+            if nbmp or nbap:
+                msg.append(' (params:')
+                if nbmp:
+                    msg.append(' %i mandatory' % nbmp)
+                if nbap:
+                    msg.append(' %i advisory' % nbmp)
+                msg.append(')')
+            if not self.data:
+                msg.append(' empty payload')
+            elif util.safehasattr(self.data, 'next'):
+                msg.append(' streamed payload')
+            else:
+                msg.append(' %i bytes payload' % len(self.data))
+            msg.append('\n')
+            ui.debug(''.join(msg))
+
         #### header
         if self.mandatory:
             parttype = self.type.upper()
         else:
             parttype = self.type.lower()
+        outdebug(ui, 'part %s: "%s"' % (self.id, parttype))
         ## parttype
         header = [_pack(_fparttypesize, len(parttype)),
                   parttype, _pack(_fpartid, self.id),
@@ -755,27 +832,33 @@
             header.append(value)
         ## finalize header
         headerchunk = ''.join(header)
+        outdebug(ui, 'header chunk size: %i' % len(headerchunk))
         yield _pack(_fpartheadersize, len(headerchunk))
         yield headerchunk
         ## payload
         try:
             for chunk in self._payloadchunks():
+                outdebug(ui, 'payload chunk size: %i' % len(chunk))
                 yield _pack(_fpayloadsize, len(chunk))
                 yield chunk
-        except Exception, exc:
+        except BaseException as exc:
             # backup exception data for later
+            ui.debug('bundle2-input-stream-interrupt: encoding exception %s'
+                     % exc)
             exc_info = sys.exc_info()
             msg = 'unexpected error: %s' % exc
             interpart = bundlepart('error:abort', [('message', msg)],
                                    mandatory=False)
             interpart.id = 0
             yield _pack(_fpayloadsize, -1)
-            for chunk in interpart.getchunks():
+            for chunk in interpart.getchunks(ui=ui):
                 yield chunk
+            outdebug(ui, 'closing payload chunk')
             # abort current part payload
             yield _pack(_fpayloadsize, 0)
             raise exc_info[0], exc_info[1], exc_info[2]
         # end of payload
+        outdebug(ui, 'closing payload chunk')
         yield _pack(_fpayloadsize, 0)
         self._generated = True
 
@@ -817,20 +900,25 @@
         if headersize < 0:
             raise error.BundleValueError('negative part header size: %i'
                                          % headersize)
-        self.ui.debug('part header size: %i\n' % headersize)
+        indebug(self.ui, 'part header size: %i\n' % headersize)
         if headersize:
             return self._readexact(headersize)
         return None
 
     def __call__(self):
-        self.ui.debug('bundle2 stream interruption, looking for a part.\n')
+
+        self.ui.debug('bundle2-input-stream-interrupt:'
+                      ' opening out of band context\n')
+        indebug(self.ui, 'bundle2 stream interruption, looking for a part.')
         headerblock = self._readpartheader()
         if headerblock is None:
-            self.ui.debug('no part found during interruption.\n')
+            indebug(self.ui, 'no part found during interruption.')
             return
         part = unbundlepart(self.ui, headerblock, self._fp)
         op = interruptoperation(self.ui)
         _processpart(op, part)
+        self.ui.debug('bundle2-input-stream-interrupt:'
+                      ' closing out of band context\n')
 
 class interruptoperation(object):
     """A limited operation to be use by part handler during interruption
@@ -910,7 +998,7 @@
 
         pos = self._chunkindex[chunknum][0]
         payloadsize = self._unpack(_fpayloadsize)[0]
-        self.ui.debug('payload chunk size: %i\n' % payloadsize)
+        indebug(self.ui, 'payload chunk size: %i' % payloadsize)
         while payloadsize:
             if payloadsize == flaginterrupt:
                 # interruption detection, the handler will now read a
@@ -928,7 +1016,7 @@
                                              super(unbundlepart, self).tell()))
                 yield result
             payloadsize = self._unpack(_fpayloadsize)[0]
-            self.ui.debug('payload chunk size: %i\n' % payloadsize)
+            indebug(self.ui, 'payload chunk size: %i' % payloadsize)
 
     def _findchunk(self, pos):
         '''for a given payload position, return a chunk number and offset'''
@@ -943,16 +1031,16 @@
         """read the header and setup the object"""
         typesize = self._unpackheader(_fparttypesize)[0]
         self.type = self._fromheader(typesize)
-        self.ui.debug('part type: "%s"\n' % self.type)
+        indebug(self.ui, 'part type: "%s"' % self.type)
         self.id = self._unpackheader(_fpartid)[0]
-        self.ui.debug('part id: "%s"\n' % self.id)
+        indebug(self.ui, 'part id: "%s"' % self.id)
         # extract mandatory bit from type
         self.mandatory = (self.type != self.type.lower())
         self.type = self.type.lower()
         ## reading parameters
         # param count
         mancount, advcount = self._unpackheader(_fpartparamcount)
-        self.ui.debug('part parameters: %i\n' % (mancount + advcount))
+        indebug(self.ui, 'part parameters: %i' % (mancount + advcount))
         # param size
         fparamsizes = _makefpartparamsizes(mancount + advcount)
         paramsizes = self._unpackheader(fparamsizes)
@@ -982,9 +1070,12 @@
             data = self._payloadstream.read()
         else:
             data = self._payloadstream.read(size)
+        self._pos += len(data)
         if size is None or len(data) < size:
+            if not self.consumed and self._pos:
+                self.ui.debug('bundle2-input-part: total payload size %i\n'
+                              % self._pos)
             self.consumed = True
-        self._pos += len(data)
         return data
 
     def tell(self):
@@ -1015,11 +1106,16 @@
                 raise util.Abort(_('Seek failed\n'))
             self._pos = newpos
 
+# These are only the static capabilities.
+# Check the 'getrepocaps' function for the rest.
 capabilities = {'HG20': (),
+                'error': ('abort', 'unsupportedcontent', 'pushraced',
+                          'pushkey'),
                 'listkeys': (),
                 'pushkey': (),
                 'digests': tuple(sorted(util.DIGESTS.keys())),
                 'remote-changegroup': ('http', 'https'),
+                'hgtagsfnodes': (),
                }
 
 def getrepocaps(repo, allowpushback=False):
@@ -1050,7 +1146,7 @@
     obscaps = caps.get('obsmarkers', ())
     return [int(c[1:]) for c in obscaps if c.startswith('V')]
 
-@parthandler('changegroup', ('version',))
+@parthandler('changegroup', ('version', 'nbchanges'))
 def handlechangegroup(op, inpart):
     """apply a changegroup part on the repo
 
@@ -1069,7 +1165,11 @@
     cg = unpacker(inpart, 'UN')
     # the source and url passed here are overwritten by the one contained in
     # the transaction.hookargs argument. So 'bundle2' is a placeholder
-    ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2')
+    nbchangesets = None
+    if 'nbchanges' in inpart.params:
+        nbchangesets = int(inpart.params.get('nbchanges'))
+    ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2',
+                                     expectedtotal=nbchangesets)
     op.records.add('changegroup', {'return': ret})
     if op.reply is not None:
         # This is definitely not the final form of this
@@ -1148,7 +1248,7 @@
         part.addparam('return', '%i' % ret, mandatory=False)
     try:
         real_part.validate()
-    except util.Abort, e:
+    except util.Abort as e:
         raise util.Abort(_('bundle at %s is corrupted:\n%s') %
             (util.hidepassword(raw_url), str(e)))
     assert not inpart.read()
@@ -1195,6 +1295,17 @@
     """Used to transmit abort error over the wire"""
     raise util.Abort(inpart.params['message'], hint=inpart.params.get('hint'))
 
+@parthandler('error:pushkey', ('namespace', 'key', 'new', 'old', 'ret',
+                               'in-reply-to'))
+def handleerrorpushkey(op, inpart):
+    """Used to transmit failure of a mandatory pushkey over the wire"""
+    kwargs = {}
+    for name in ('namespace', 'key', 'new', 'old', 'ret'):
+        value = inpart.params.get(name)
+        if value is not None:
+            kwargs[name] = value
+    raise error.PushkeyFailed(inpart.params['in-reply-to'], **kwargs)
+
 @parthandler('error:unsupportedcontent', ('parttype', 'params'))
 def handleerrorunsupportedcontent(op, inpart):
     """Used to transmit unknown content error over the wire"""
@@ -1238,6 +1349,12 @@
         rpart = op.reply.newpart('reply:pushkey')
         rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
         rpart.addparam('return', '%i' % ret, mandatory=False)
+    if inpart.mandatory and not ret:
+        kwargs = {}
+        for key in ('namespace', 'key', 'new', 'old', 'ret'):
+            if key in inpart.params:
+                kwargs[key] = inpart.params[key]
+        raise error.PushkeyFailed(partid=str(inpart.id), **kwargs)
 
 @parthandler('reply:pushkey', ('return', 'in-reply-to'))
 def handlepushkeyreply(op, inpart):
@@ -1265,8 +1382,29 @@
 
 
 @parthandler('reply:obsmarkers', ('new', 'in-reply-to'))
-def handlepushkeyreply(op, inpart):
+def handleobsmarkerreply(op, inpart):
     """retrieve the result of a pushkey request"""
     ret = int(inpart.params['new'])
     partid = int(inpart.params['in-reply-to'])
     op.records.add('obsmarkers', {'new': ret}, partid)
+
+@parthandler('hgtagsfnodes')
+def handlehgtagsfnodes(op, inpart):
+    """Applies .hgtags fnodes cache entries to the local repo.
+
+    Payload is pairs of 20 byte changeset nodes and filenodes.
+    """
+    cache = tags.hgtagsfnodescache(op.repo.unfiltered())
+
+    count = 0
+    while True:
+        node = inpart.read(20)
+        fnode = inpart.read(20)
+        if len(node) < 20 or len(fnode) < 20:
+            op.ui.debug('ignoring incomplete received .hgtags fnodes data\n')
+            break
+        cache.setfnode(node, fnode)
+        count += 1
+
+    cache.write()
+    op.ui.debug('applied %i hgtags fnodes cache entries\n' % count)
--- a/mercurial/bundlerepo.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/bundlerepo.py	Sat Jul 18 17:32:38 2015 -0500
@@ -177,11 +177,10 @@
         return manifest.manifest.revision(self, nodeorrev)
 
 class bundlefilelog(bundlerevlog, filelog.filelog):
-    def __init__(self, opener, path, bundle, linkmapper, repo):
+    def __init__(self, opener, path, bundle, linkmapper):
         filelog.filelog.__init__(self, opener, path)
         bundlerevlog.__init__(self, opener, self.indexfile, bundle,
                               linkmapper)
-        self._repo = repo
 
     def baserevision(self, nodeorrev):
         return filelog.filelog.revision(self, nodeorrev)
@@ -322,8 +321,7 @@
 
         if f in self.bundlefilespos:
             self.bundle.seek(self.bundlefilespos[f])
-            return bundlefilelog(self.svfs, f, self.bundle,
-                                 self.changelog.rev, self)
+            return bundlefilelog(self.svfs, f, self.bundle, self.changelog.rev)
         else:
             return filelog.filelog(self.svfs, f)
 
@@ -348,6 +346,7 @@
 def instance(ui, path, create):
     if create:
         raise util.Abort(_('cannot create new bundle repository'))
+    # internal config: bundle.mainreporoot
     parentpath = ui.config("bundle", "mainreporoot", "")
     if not parentpath:
         # try to find the correct path to the working directory repo
--- a/mercurial/byterange.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/byterange.py	Sat Jul 18 17:32:38 2015 -0500
@@ -264,7 +264,7 @@
 
         try:
             host = socket.gethostbyname(host)
-        except socket.error, msg:
+        except socket.error as msg:
             raise urllib2.URLError(msg)
         path, attrs = splitattr(req.get_selector())
         dirs = path.split('/')
@@ -322,7 +322,7 @@
                 headers += "Content-Length: %d\n" % retrlen
             headers = email.message_from_string(headers)
             return addinfourl(fp, headers, req.get_full_url())
-        except ftplib.all_errors, msg:
+        except ftplib.all_errors as msg:
             raise IOError('ftp error', msg)
 
     def connect_ftp(self, user, passwd, host, port, dirs):
@@ -352,7 +352,7 @@
             # Use nlst to see if the file exists at all
             try:
                 self.ftp.nlst(file)
-            except ftplib.error_perm, reason:
+            except ftplib.error_perm as reason:
                 raise IOError('ftp error', reason)
             # Restore the transfer mode!
             self.ftp.voidcmd(cmd)
@@ -360,7 +360,7 @@
             try:
                 cmd = 'RETR ' + file
                 conn = self.ftp.ntransfercmd(cmd, rest)
-            except ftplib.error_perm, reason:
+            except ftplib.error_perm as reason:
                 if str(reason).startswith('501'):
                     # workaround for REST not supported error
                     fp, retrlen = self.retrfile(file, type)
--- a/mercurial/changegroup.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/changegroup.py	Sat Jul 18 17:32:38 2015 -0500
@@ -283,8 +283,7 @@
         if bundlecaps is None:
             bundlecaps = set()
         self._bundlecaps = bundlecaps
-        self._changelog = repo.changelog
-        self._manifest = repo.manifest
+        # experimental config: bundle.reorder
         reorder = repo.ui.config('bundle', 'reorder', 'auto')
         if reorder == 'auto':
             reorder = None
@@ -304,7 +303,7 @@
     def fileheader(self, fname):
         return chunkheader(len(fname)) + fname
 
-    def group(self, nodelist, revlog, lookup, units=None, reorder=None):
+    def group(self, nodelist, revlog, lookup, units=None):
         """Calculate a delta group, yielding a sequence of changegroup chunks
         (strings).
 
@@ -325,7 +324,7 @@
 
         # for generaldelta revlogs, we linearize the revs; this will both be
         # much quicker and generate a much smaller bundle
-        if (revlog._generaldelta and reorder is not False) or reorder:
+        if (revlog._generaldelta and self._reorder is None) or self._reorder:
             dag = dagutil.revlogdag(revlog)
             revs = set(revlog.rev(n) for n in nodelist)
             revs = dag.linearize(revs)
@@ -347,23 +346,20 @@
             for c in self.revchunk(revlog, curr, prev, linknode):
                 yield c
 
+        if units is not None:
+            self._progress(msgbundling, None)
         yield self.close()
 
     # filter any nodes that claim to be part of the known set
-    def prune(self, revlog, missing, commonrevs, source):
+    def prune(self, revlog, missing, commonrevs):
         rr, rl = revlog.rev, revlog.linkrev
         return [n for n in missing if rl(rr(n)) not in commonrevs]
 
     def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
         '''yield a sequence of changegroup chunks (strings)'''
         repo = self._repo
-        cl = self._changelog
-        mf = self._manifest
-        reorder = self._reorder
-        progress = self._progress
-
-        # for progress output
-        msgbundling = _('bundling')
+        cl = repo.changelog
+        ml = repo.manifest
 
         clrevorder = {}
         mfs = {} # needed manifests
@@ -383,20 +379,34 @@
 
         self._verbosenote(_('uncompressed size of bundle content:\n'))
         size = 0
-        for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets'),
-                                reorder=reorder):
+        for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
             size += len(chunk)
             yield chunk
         self._verbosenote(_('%8.i (changelog)\n') % size)
-        progress(msgbundling, None)
 
+        # We need to make sure that the linkrev in the changegroup refers to
+        # the first changeset that introduced the manifest or file revision.
+        # The fastpath is usually safer than the slowpath, because the filelogs
+        # are walked in revlog order.
+        #
+        # When taking the slowpath with reorder=None and the manifest revlog
+        # uses generaldelta, the manifest may be walked in the "wrong" order.
+        # Without 'clrevorder', we would get an incorrect linkrev (see fix in
+        # cc0ff93d0c0c).
+        #
+        # When taking the fastpath, we are only vulnerable to reordering
+        # of the changelog itself. The changelog never uses generaldelta, so
+        # it is only reordered when reorder=True. To handle this case, we
+        # simply take the slowpath, which already has the 'clrevorder' logic.
+        # This was also fixed in cc0ff93d0c0c.
+        fastpathlinkrev = fastpathlinkrev and not self._reorder
         # Callback for the manifest, used to collect linkrevs for filelog
         # revisions.
         # Returns the linkrev node (collected in lookupcl).
         def lookupmf(x):
             clnode = mfs[x]
-            if not fastpathlinkrev or reorder:
-                mdata = mf.readfast(x)
+            if not fastpathlinkrev:
+                mdata = ml.readfast(x)
                 for f, n in mdata.iteritems():
                     if f in changedfiles:
                         # record the first changeset introducing this filelog
@@ -407,25 +417,23 @@
                             fclnodes[n] = clnode
             return clnode
 
-        mfnodes = self.prune(mf, mfs, commonrevs, source)
+        mfnodes = self.prune(ml, mfs, commonrevs)
         size = 0
-        for chunk in self.group(mfnodes, mf, lookupmf, units=_('manifests'),
-                                reorder=reorder):
+        for chunk in self.group(mfnodes, ml, lookupmf, units=_('manifests')):
             size += len(chunk)
             yield chunk
         self._verbosenote(_('%8.i (manifests)\n') % size)
-        progress(msgbundling, None)
 
         mfs.clear()
-        needed = set(cl.rev(x) for x in clnodes)
+        clrevs = set(cl.rev(x) for x in clnodes)
 
         def linknodes(filerevlog, fname):
-            if fastpathlinkrev and not reorder:
+            if fastpathlinkrev:
                 llr = filerevlog.linkrev
                 def genfilenodes():
                     for r in filerevlog:
                         linkrev = llr(r)
-                        if linkrev in needed:
+                        if linkrev in clrevs:
                             yield filerevlog.node(r), cl.node(linkrev)
                 return dict(genfilenodes())
             return fnodes.get(fname, {})
@@ -435,15 +443,14 @@
             yield chunk
 
         yield self.close()
-        progress(msgbundling, None)
 
         if clnodes:
             repo.hook('outgoing', node=hex(clnodes[0]), source=source)
 
+    # The 'source' parameter is useful for extensions
     def generatefiles(self, changedfiles, linknodes, commonrevs, source):
         repo = self._repo
         progress = self._progress
-        reorder = self._reorder
         msgbundling = _('bundling')
 
         total = len(changedfiles)
@@ -460,18 +467,18 @@
             def lookupfilelog(x):
                 return linkrevnodes[x]
 
-            filenodes = self.prune(filerevlog, linkrevnodes, commonrevs, source)
+            filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
             if filenodes:
                 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
                          total=total)
                 h = self.fileheader(fname)
                 size = len(h)
                 yield h
-                for chunk in self.group(filenodes, filerevlog, lookupfilelog,
-                                        reorder=reorder):
+                for chunk in self.group(filenodes, filerevlog, lookupfilelog):
                     size += len(chunk)
                     yield chunk
                 self._verbosenote(_('%8.i  %s\n') % (size, fname))
+        progress(msgbundling, None)
 
     def deltaparent(self, revlog, rev, p1, p2, prev):
         return prev
@@ -485,7 +492,7 @@
         if revlog.iscensored(base) or revlog.iscensored(rev):
             try:
                 delta = revlog.revision(node)
-            except error.CensoredNodeError, e:
+            except error.CensoredNodeError as e:
                 delta = e.tombstone
             if base == nullrev:
                 prefix = mdiff.trivialdiffheader(len(delta))
@@ -513,11 +520,13 @@
     version = '02'
     deltaheader = _CHANGEGROUPV2_DELTA_HEADER
 
-    def group(self, nodelist, revlog, lookup, units=None, reorder=None):
-        if (revlog._generaldelta and reorder is not True):
-            reorder = False
-        return super(cg2packer, self).group(nodelist, revlog, lookup,
-                                            units=units, reorder=reorder)
+    def __init__(self, repo, bundlecaps=None):
+        super(cg2packer, self).__init__(repo, bundlecaps)
+        if self._reorder is None:
+            # Since generaldelta is directly supported by cg2, reordering
+            # generally doesn't help, so we disable it by default (treating
+            # bundle.reorder=auto just like bundle.reorder=False).
+            self._reorder = False
 
     def deltaparent(self, revlog, rev, p1, p2, prev):
         dp = revlog.deltaparent(rev)
@@ -609,7 +618,7 @@
     bundler = cg1packer(repo, bundlecaps)
     return getsubset(repo, outgoing, bundler, source)
 
-def _computeoutgoing(repo, heads, common):
+def computeoutgoing(repo, heads, common):
     """Computes which revs are outgoing given a set of common
     and a set of heads.
 
@@ -628,22 +637,6 @@
         heads = cl.heads()
     return discovery.outgoing(cl, common, heads)
 
-def getchangegroupraw(repo, source, heads=None, common=None, bundlecaps=None,
-                      version='01'):
-    """Like changegroupsubset, but returns the set difference between the
-    ancestors of heads and the ancestors common.
-
-    If heads is None, use the local heads. If common is None, use [nullid].
-
-    If version is None, use a version '1' changegroup.
-
-    The nodes in common might not all be known locally due to the way the
-    current discovery protocol works. Returns a raw changegroup generator.
-    """
-    outgoing = _computeoutgoing(repo, heads, common)
-    return getlocalchangegroupraw(repo, source, outgoing, bundlecaps=bundlecaps,
-                                  version=version)
-
 def getchangegroup(repo, source, heads=None, common=None, bundlecaps=None):
     """Like changegroupsubset, but returns the set difference between the
     ancestors of heads and the ancestors common.
@@ -653,7 +646,7 @@
     The nodes in common might not all be known locally due to the way the
     current discovery protocol works.
     """
-    outgoing = _computeoutgoing(repo, heads, common)
+    outgoing = computeoutgoing(repo, heads, common)
     return getlocalchangegroup(repo, source, outgoing, bundlecaps=bundlecaps)
 
 def changegroup(repo, basenodes, source):
@@ -675,7 +668,7 @@
         try:
             if not fl.addgroup(source, revmap, trp):
                 raise util.Abort(_("received file revlog group is empty"))
-        except error.CensoredBaseError, e:
+        except error.CensoredBaseError as e:
             raise util.Abort(_("received delta base is censored: %s") % e)
         revisions += len(fl) - o
         files += 1
@@ -705,7 +698,7 @@
     return revisions, files
 
 def addchangegroup(repo, source, srctype, url, emptyok=False,
-                   targetphase=phases.draft):
+                   targetphase=phases.draft, expectedtotal=None):
     """Add the changegroup returned by source.read() to this repo.
     srctype is a string like 'push', 'pull', or 'unbundle'.  url is
     the URL of the repo where this changegroup is coming from.
@@ -728,7 +721,6 @@
         return 0
 
     changesets = files = revisions = 0
-    efiles = set()
 
     tr = repo.transaction("\n".join([srctype, util.hidepassword(url)]))
     # The transaction could have been created before and already carries source
@@ -751,33 +743,35 @@
         repo.ui.status(_("adding changesets\n"))
         clstart = len(cl)
         class prog(object):
-            step = _('changesets')
-            count = 1
-            ui = repo.ui
-            total = None
-            def __call__(repo):
-                repo.ui.progress(repo.step, repo.count, unit=_('chunks'),
-                                 total=repo.total)
-                repo.count += 1
-        pr = prog()
-        source.callback = pr
+            def __init__(self, step, total):
+                self._step = step
+                self._total = total
+                self._count = 1
+            def __call__(self):
+                repo.ui.progress(self._step, self._count, unit=_('chunks'),
+                                 total=self._total)
+                self._count += 1
+        source.callback = prog(_('changesets'), expectedtotal)
+
+        efiles = set()
+        def onchangelog(cl, node):
+            efiles.update(cl.read(node)[3])
 
         source.changelogheader()
-        srccontent = cl.addgroup(source, csmap, trp)
+        srccontent = cl.addgroup(source, csmap, trp,
+                                 addrevisioncb=onchangelog)
+        efiles = len(efiles)
+
         if not (srccontent or emptyok):
             raise util.Abort(_("received changelog group is empty"))
         clend = len(cl)
         changesets = clend - clstart
-        for c in xrange(clstart, clend):
-            efiles.update(repo[c].files())
-        efiles = len(efiles)
         repo.ui.progress(_('changesets'), None)
 
         # pull off the manifest group
         repo.ui.status(_("adding manifests\n"))
-        pr.step = _('manifests')
-        pr.count = 1
-        pr.total = changesets # manifests <= changesets
+        # manifests <= changesets
+        source.callback = prog(_('manifests'), changesets)
         # no need to check for empty manifest group here:
         # if the result of the merge of 1 and 2 is the same in 3 and 4,
         # no new manifest will be created and the manifest group will
@@ -790,19 +784,16 @@
         if repo.ui.configbool('server', 'validate', default=False):
             # validate incoming csets have their manifests
             for cset in xrange(clstart, clend):
-                mfest = repo.changelog.read(repo.changelog.node(cset))[0]
-                mfest = repo.manifest.readdelta(mfest)
+                mfnode = repo.changelog.read(repo.changelog.node(cset))[0]
+                mfest = repo.manifest.readdelta(mfnode)
                 # store file nodes we must see
                 for f, n in mfest.iteritems():
                     needfiles.setdefault(f, set()).add(n)
 
         # process the files
         repo.ui.status(_("adding file changes\n"))
-        pr.step = _('files')
-        pr.count = 1
-        pr.total = efiles
         source.callback = None
-
+        pr = prog(_('files'), efiles)
         newrevs, newfiles = addchangegroupfiles(repo, source, revmap, trp, pr,
                                                 needfiles)
         revisions += newrevs
@@ -835,7 +826,7 @@
             repo.hook('pretxnchangegroup', throw=True, pending=p, **hookargs)
 
         added = [cl.node(r) for r in xrange(clstart, clend)]
-        publishing = repo.ui.configbool('phases', 'publish', True)
+        publishing = repo.publishing()
         if srctype in ('push', 'serve'):
             # Old servers can not push the boundary themselves.
             # New servers won't push the boundary if changeset already
--- a/mercurial/changelog.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/changelog.py	Sat Jul 18 17:32:38 2015 -0500
@@ -172,14 +172,6 @@
         self.rev(self.node(0))
         return self._nodecache
 
-    def hasnode(self, node):
-        """filtered version of revlog.hasnode"""
-        try:
-            i = self.rev(node)
-            return i not in self.filteredrevs
-        except KeyError:
-            return False
-
     def headrevs(self):
         if self.filteredrevs:
             try:
@@ -267,6 +259,18 @@
         self.checkinlinesize(tr)
 
     def readpending(self, file):
+        """read index data from a "pending" file
+
+        During a transaction, the actual changeset data is already stored in the
+        main file, but not yet finalized in the on-disk index. Instead, a
+        "pending" index is written by the transaction logic. If this function
+        is running, we are likely in a subprocess invoked in a hook. The
+        subprocess is informed that it is within a transaction and needs to
+        access its content.
+
+        This function will read all the index data out of the pending file and
+        overwrite the main index."""
+
         if not self.opener.exists(file):
             return # no pending data for changelog
         r = revlog.revlog(self.opener, file)
--- a/mercurial/cmdutil.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/cmdutil.py	Sat Jul 18 17:32:38 2015 -0500
@@ -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 node import hex, nullid, nullrev, short
+from node import hex, bin, nullid, nullrev, short
 from i18n import _
 import os, sys, errno, re, tempfile, cStringIO, shutil
 import util, scmutil, templater, patch, error, templatekw, revlog, copies
@@ -14,9 +14,22 @@
 import changelog
 import bookmarks
 import encoding
+import formatter
 import crecord as crecordmod
 import lock as lockmod
 
+def ishunk(x):
+    hunkclasses = (crecordmod.uihunk, patch.recordhunk)
+    return isinstance(x, hunkclasses)
+
+def newandmodified(chunks, originalchunks):
+    newlyaddedandmodifiedfiles = set()
+    for chunk in chunks:
+        if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
+            originalchunks:
+            newlyaddedandmodifiedfiles.add(chunk.header.filename())
+    return newlyaddedandmodifiedfiles
+
 def parsealiases(cmd):
     return cmd.lstrip("^").split("|")
 
@@ -33,7 +46,7 @@
     setattr(ui, 'write', wrap)
     return oldwrite
 
-def filterchunks(ui, originalhunks, usecurses, testfile):
+def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
     if usecurses:
         if testfile:
             recordfn = crecordmod.testdecorator(testfile,
@@ -41,17 +54,24 @@
         else:
             recordfn = crecordmod.chunkselector
 
-        return crecordmod.filterpatch(ui, originalhunks, recordfn)
+        return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
 
     else:
-        return patch.filterpatch(ui, originalhunks)
-
-def recordfilter(ui, originalhunks):
+        return patch.filterpatch(ui, originalhunks, operation)
+
+def recordfilter(ui, originalhunks, operation=None):
+    """ Prompts the user to filter the originalhunks and return a list of
+    selected hunks.
+    *operation* is used for ui purposes to indicate the user
+    what kind of filtering they are doing: reverting, commiting, shelving, etc.
+    *operation* has to be a translated string.
+    """
     usecurses =  ui.configbool('experimental', 'crecord', False)
     testfile = ui.config('experimental', 'crecordtest', None)
     oldwrite = setupwrapcolorwrite(ui)
     try:
-        newchunks = filterchunks(ui, originalhunks, usecurses, testfile)
+        newchunks = filterchunks(ui, originalhunks, usecurses, testfile,
+                                 operation)
     finally:
         ui.write = oldwrite
     return newchunks
@@ -59,12 +79,13 @@
 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
             filterfn, *pats, **opts):
     import merge as mergemod
-    hunkclasses = (crecordmod.uihunk, patch.recordhunk)
-    ishunk = lambda x: isinstance(x, hunkclasses)
 
     if not ui.interactive():
-        raise util.Abort(_('running non-interactively, use %s instead') %
-                         cmdsuggest)
+        if cmdsuggest:
+            msg = _('running non-interactively, use %s instead') % cmdsuggest
+        else:
+            msg = _('running non-interactively')
+        raise util.Abort(msg)
 
     # make sure username is set before going interactive
     if not opts.get('user'):
@@ -101,17 +122,13 @@
         # 1. filter patch, so we have intending-to apply subset of it
         try:
             chunks = filterfn(ui, originalchunks)
-        except patch.PatchError, err:
+        except patch.PatchError as err:
             raise util.Abort(_('error parsing patch: %s') % err)
 
         # We need to keep a backup of files that have been newly added and
         # modified during the recording process because there is a previous
         # version without the edit in the workdir
-        newlyaddedandmodifiedfiles = set()
-        for chunk in chunks:
-            if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
-                originalchunks:
-                newlyaddedandmodifiedfiles.add(chunk.header.filename())
+        newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
         contenders = set()
         for h in chunks:
             try:
@@ -139,7 +156,7 @@
             backupdir = repo.join('record-backups')
             try:
                 os.mkdir(backupdir)
-            except OSError, err:
+            except OSError as err:
                 if err.errno != errno.EEXIST:
                     raise
         try:
@@ -175,7 +192,7 @@
                     ui.debug('applying patch\n')
                     ui.debug(fp.getvalue())
                     patch.internalpatch(ui, repo, fp, 1, eolmode=None)
-                except patch.PatchError, err:
+                except patch.PatchError as err:
                     raise util.Abort(str(err))
             del fp
 
@@ -189,8 +206,16 @@
         finally:
             # 5. finally restore backed-up files
             try:
+                dirstate = repo.dirstate
                 for realname, tmpname in backups.iteritems():
                     ui.debug('restoring %r to %r\n' % (tmpname, realname))
+
+                    if dirstate[realname] == 'n':
+                        # without normallookup, restoring timestamp
+                        # may cause partially committed files
+                        # to be treated as unmodified
+                        dirstate.normallookup(realname)
+
                     util.copyfile(tmpname, repo.wjoin(realname))
                     # Our calls to copystat() here and above are a
                     # hack to trick any editors that have f open that
@@ -206,7 +231,14 @@
             except OSError:
                 pass
 
-    return commit(ui, repo, recordfunc, pats, opts)
+    def recordinwlock(ui, repo, message, match, opts):
+        wlock = repo.wlock()
+        try:
+            return recordfunc(ui, repo, message, match, opts)
+        finally:
+            wlock.release()
+
+    return commit(ui, repo, recordinwlock, pats, opts)
 
 def findpossible(cmd, table, strict=False):
     """
@@ -295,7 +327,7 @@
                 message = ui.fin.read()
             else:
                 message = '\n'.join(util.readfile(logfile).splitlines())
-        except IOError, inst:
+        except IOError as inst:
             raise util.Abort(_("can't read commit message '%s': %s") %
                              (logfile, inst.strerror))
     return message
@@ -404,7 +436,7 @@
             newname.append(c)
             i += 1
         return ''.join(newname)
-    except KeyError, inst:
+    except KeyError as inst:
         raise util.Abort(_("invalid format spec '%%%s' in output filename") %
                          inst.args[0])
 
@@ -450,14 +482,17 @@
     """opens the changelog, manifest, a filelog or a given revlog"""
     cl = opts['changelog']
     mf = opts['manifest']
+    dir = opts['dir']
     msg = None
     if cl and mf:
         msg = _('cannot specify --changelog and --manifest at the same time')
+    elif cl and dir:
+        msg = _('cannot specify --changelog and --dir at the same time')
     elif cl or mf:
         if file_:
             msg = _('cannot specify filename with --changelog or --manifest')
         elif not repo:
-            msg = _('cannot specify --changelog or --manifest '
+            msg = _('cannot specify --changelog or --manifest or --dir '
                     'without a repository')
     if msg:
         raise util.Abort(msg)
@@ -466,6 +501,13 @@
     if repo:
         if cl:
             r = repo.unfiltered().changelog
+        elif dir:
+            if 'treemanifest' not in repo.requirements:
+                raise util.Abort(_("--dir can only be used on repos with "
+                                   "treemanifest enabled"))
+            dirlog = repo.dirlog(file_)
+            if len(dirlog):
+                r = dirlog
         elif mf:
             r = repo.manifest
         elif file_:
@@ -581,7 +623,7 @@
                 else:
                     util.copyfile(src, target)
                 srcexists = True
-            except IOError, inst:
+            except IOError as inst:
                 if inst.errno == errno.ENOENT:
                     ui.warn(_('%s: deleted in working directory\n') % relsrc)
                     srcexists = False
@@ -749,7 +791,7 @@
         finally:
             try:
                 os.unlink(lockpath)
-            except OSError, e:
+            except OSError as e:
                 if e.errno != errno.ENOENT:
                     raise
         if parentfn:
@@ -818,6 +860,7 @@
     msg = _('applied to working directory')
 
     rejects = False
+    dsguard = None
 
     try:
         cmdline_message = logmessage(ui, opts)
@@ -859,7 +902,7 @@
 
         n = None
         if update:
-            repo.dirstate.beginparentchange()
+            dsguard = dirstateguard(repo, 'tryimportone')
             if p1 != parents[0]:
                 updatefunc(repo, p1.node())
             if p2 != parents[1]:
@@ -873,7 +916,7 @@
             try:
                 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
                             files=files, eolmode=None, similarity=sim / 100.0)
-            except patch.PatchError, e:
+            except patch.PatchError as e:
                 if not partial:
                     raise util.Abort(str(e))
                 if partial:
@@ -896,10 +939,16 @@
                     editor = None
                 else:
                     editor = getcommiteditor(editform=editform, **opts)
-                n = repo.commit(message, opts.get('user') or user,
-                                opts.get('date') or date, match=m,
-                                editor=editor, force=partial)
-            repo.dirstate.endparentchange()
+                allowemptyback = repo.ui.backupconfig('ui', 'allowemptycommit')
+                try:
+                    if partial:
+                        repo.ui.setconfig('ui', 'allowemptycommit', True)
+                    n = repo.commit(message, opts.get('user') or user,
+                                    opts.get('date') or date, match=m,
+                                    editor=editor)
+                finally:
+                    repo.ui.restoreconfig(allowemptyback)
+            dsguard.close()
         else:
             if opts.get('exact') or opts.get('import_branch'):
                 branch = branch or 'default'
@@ -911,7 +960,7 @@
                 try:
                     patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
                                     files, eolmode=None)
-                except patch.PatchError, e:
+                except patch.PatchError as e:
                     raise util.Abort(str(e))
                 if opts.get('exact'):
                     editor = None
@@ -937,6 +986,7 @@
             msg = _('created %s') % short(n)
         return (msg, n, rejects)
     finally:
+        lockmod.release(dsguard)
         os.unlink(tmpname)
 
 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
@@ -1072,7 +1122,8 @@
         self.lastheader = None
         self.footer = None
 
-    def flush(self, rev):
+    def flush(self, ctx):
+        rev = ctx.rev()
         if rev in self.header:
             h = self.header[rev]
             if h != self.lastheader:
@@ -1105,11 +1156,9 @@
             hexfunc = hex
         else:
             hexfunc = short
-        if rev is None:
-            pctx = ctx.p1()
-            revnode = (pctx.rev(), hexfunc(pctx.node()) + '+')
-        else:
-            revnode = (rev, hexfunc(changenode))
+        # 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(rev), hexfunc(bin(ctx.hex())))
 
         if self.ui.quiet:
             self.ui.write("%d:%s\n" % revnode, label='log.node')
@@ -1240,7 +1289,7 @@
             return parents
         if self.ui.debugflag:
             return [parents[0], self.repo['null']]
-        if parents[0].rev() >= scmutil.intrev(self.repo, ctx.rev()) - 1:
+        if parents[0].rev() >= scmutil.intrev(ctx.rev()) - 1:
             return []
         return parents
 
@@ -1427,10 +1476,10 @@
                     self.footer = templater.stringify(self.t(types['footer'],
                                                       **props))
 
-        except KeyError, inst:
+        except KeyError as inst:
             msg = _("%s: no key named '%s'")
             raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
-        except SyntaxError, inst:
+        except SyntaxError as inst:
             raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
 
 def gettemplate(ui, tmpl, style):
@@ -1443,9 +1492,9 @@
         tmpl = ui.config('ui', 'logtemplate')
         if tmpl:
             try:
-                tmpl = templater.parsestring(tmpl)
+                tmpl = templater.unquotestring(tmpl)
             except SyntaxError:
-                tmpl = templater.parsestring(tmpl, quoted=False)
+                pass
             return tmpl, None
         else:
             style = util.expandpath(ui.config('ui', 'style', ''))
@@ -1462,40 +1511,7 @@
     if not tmpl:
         return None, None
 
-    # looks like a literal template?
-    if '{' in tmpl:
-        return tmpl, None
-
-    # perhaps a stock style?
-    if not os.path.split(tmpl)[0]:
-        mapname = (templater.templatepath('map-cmdline.' + tmpl)
-                   or templater.templatepath(tmpl))
-        if mapname and os.path.isfile(mapname):
-            return None, mapname
-
-    # perhaps it's a reference to [templates]
-    t = ui.config('templates', tmpl)
-    if t:
-        try:
-            tmpl = templater.parsestring(t)
-        except SyntaxError:
-            tmpl = templater.parsestring(t, quoted=False)
-        return tmpl, None
-
-    if tmpl == 'list':
-        ui.write(_("available styles: %s\n") % templater.stylelist())
-        raise util.Abort(_("specify a template"))
-
-    # perhaps it's a path to a map or a template
-    if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
-        # is it a mapfile for a style?
-        if os.path.basename(tmpl).startswith("map-"):
-            return None, os.path.realpath(tmpl)
-        tmpl = open(tmpl).read()
-        return tmpl, None
-
-    # constant string?
-    return tmpl, None
+    return formatter.lookuptemplate(ui, 'changeset', tmpl)
 
 def show_changeset(ui, repo, opts, buffered=False):
     """show one changeset using template or regular display.
@@ -1524,7 +1540,7 @@
     try:
         t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
                                 buffered)
-    except SyntaxError, inst:
+    except SyntaxError as inst:
         raise util.Abort(inst.args[0])
     return t
 
@@ -1731,7 +1747,8 @@
     if not revs:
         return []
     wanted = set()
-    slowpath = match.anypats() or (match.files() and opts.get('removed'))
+    slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
+                                   opts.get('removed'))
     fncache = {}
     change = repo.changectx
 
@@ -1743,8 +1760,7 @@
     if match.always():
         # No files, no patterns.  Display all revs.
         wanted = revs
-
-    if not slowpath and match.files():
+    elif not slowpath:
         # We only have to read through the filelog to find wanted revisions
 
         try:
@@ -1812,7 +1828,7 @@
     # Now that wanted is correctly initialized, we can iterate over the
     # revision range, yielding only revisions in wanted.
     def iterate():
-        if follow and not match.files():
+        if follow and match.always():
             ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
             def want(rev):
                 return ff.match(rev) and rev in wanted
@@ -1825,13 +1841,12 @@
         for windowsize in increasingwindows():
             nrevs = []
             for i in xrange(windowsize):
-                try:
-                    rev = it.next()
-                    if want(rev):
-                        nrevs.append(rev)
-                except (StopIteration):
+                rev = next(it, None)
+                if rev is None:
                     stopiteration = True
                     break
+                elif want(rev):
+                    nrevs.append(rev)
             for rev in sorted(nrevs):
                 fns = fncache.get(rev)
                 ctx = change(rev)
@@ -1916,10 +1931,7 @@
     # --follow with FILE behaviour depends on revs...
     it = iter(revs)
     startrev = it.next()
-    try:
-        followdescendants = startrev < it.next()
-    except (StopIteration):
-        followdescendants = False
+    followdescendants = startrev < next(it, startrev)
 
     # branch and only_branch are really aliases and must be handled at
     # the same time
@@ -1931,7 +1943,8 @@
     # platforms without shell expansion (windows).
     wctx = repo[None]
     match, pats = scmutil.matchandpats(wctx, pats, opts)
-    slowpath = match.anypats() or (match.files() and opts.get('removed'))
+    slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
+                                   opts.get('removed'))
     if not slowpath:
         for f in match.files():
             if follow and f not in wctx:
@@ -2113,15 +2126,11 @@
         if not opts.get('rev'):
             revs.sort(reverse=True)
     if limit is not None:
-        count = 0
         limitedrevs = []
-        it = iter(revs)
-        while count < limit:
-            try:
-                limitedrevs.append(it.next())
-            except (StopIteration):
+        for idx, r in enumerate(revs):
+            if limit <= idx:
                 break
-            count += 1
+            limitedrevs.append(r)
         revs = revset.baseset(limitedrevs)
 
     return revs, expr, filematcher
@@ -2151,7 +2160,7 @@
         lines = displayer.hunk.pop(rev).split('\n')
         if not lines[-1]:
             del lines[-1]
-        displayer.flush(rev)
+        displayer.flush(ctx)
         edges = edgefn(type, char, lines, seen, rev, parents)
         for type, char, lines, coldata in edges:
             graphmod.ascii(ui, state, type, char, lines, coldata)
@@ -2189,15 +2198,16 @@
 def add(ui, repo, match, prefix, explicitonly, **opts):
     join = lambda f: os.path.join(prefix, f)
     bad = []
-    oldbad = match.bad
-    match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
+
+    badfn = lambda x, y: bad.append(x) or match.bad(x, y)
     names = []
     wctx = repo[None]
     cca = None
     abort, warn = scmutil.checkportabilityalert(ui)
     if abort or warn:
         cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
-    for f in wctx.walk(match):
+
+    for f in wctx.walk(matchmod.badmatch(match, badfn)):
         exact = match.exact(f)
         if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
             if cca:
@@ -2226,11 +2236,11 @@
 def forget(ui, repo, match, prefix, explicitonly):
     join = lambda f: os.path.join(prefix, f)
     bad = []
-    oldbad = match.bad
-    match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
+    badfn = lambda x, y: bad.append(x) or match.bad(x, y)
     wctx = repo[None]
     forgot = []
-    s = repo.status(match=match, clean=True)
+
+    s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
     forget = sorted(s[0] + s[1] + s[3] + s[6])
     if explicitonly:
         forget = [f for f in forget if match.exact(f)]
@@ -2287,12 +2297,16 @@
         fm.write('path', fmt, m.rel(f))
         ret = 0
 
-    if subrepos:
-        for subpath in sorted(ctx.substate):
+    for subpath in sorted(ctx.substate):
+        def matchessubrepo(subpath):
+            return (m.always() or m.exact(subpath)
+                    or any(f.startswith(subpath + '/') for f in m.files()))
+
+        if subrepos or matchessubrepo(subpath):
             sub = ctx.sub(subpath)
             try:
                 submatch = matchmod.narrowmatcher(subpath, m)
-                if sub.printfiles(ui, submatch, fm, fmt) == 0:
+                if sub.printfiles(ui, submatch, fm, fmt, subrepos) == 0:
                     ret = 0
             except error.LookupError:
                 ui.status(_("skipping missing subrepository: %s\n")
@@ -2336,7 +2350,7 @@
                     return True
             return False
 
-        isdir = f in deleteddirs or f in wctx.dirs()
+        isdir = f in deleteddirs or wctx.hasdir(f)
         if f in repo.dirstate or isdir or f == '.' or insubrepo():
             continue
 
@@ -2408,22 +2422,16 @@
             return 0
 
     # Don't warn about "missing" files that are really in subrepos
-    bad = matcher.bad
-
     def badfn(path, msg):
         for subpath in ctx.substate:
             if path.startswith(subpath):
                 return
-        bad(path, msg)
-
-    matcher.bad = badfn
-
-    for abs in ctx.walk(matcher):
+        matcher.bad(path, msg)
+
+    for abs in ctx.walk(matchmod.badmatch(matcher, badfn)):
         write(abs)
         err = 0
 
-    matcher.bad = bad
-
     for subpath in sorted(ctx.substate):
         sub = ctx.sub(subpath)
         try:
@@ -2463,10 +2471,12 @@
 
     ui.note(_('amending changeset %s\n') % old)
     base = old.p1()
-
-    wlock = lock = newid = None
+    createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
+
+    wlock = dsguard = lock = newid = None
     try:
         wlock = repo.wlock()
+        dsguard = dirstateguard(repo, 'amend')
         lock = repo.lock()
         tr = repo.transaction('amend')
         try:
@@ -2480,13 +2490,13 @@
             # First, do a regular commit to record all changes in the working
             # directory (if there are any)
             ui.callhooks = False
-            currentbookmark = repo._bookmarkcurrent
+            activebookmark = repo._activebookmark
             try:
-                repo._bookmarkcurrent = None
+                repo._activebookmark = None
                 opts['message'] = 'temporary amend commit for %s' % old
                 node = commit(ui, repo, commitfunc, pats, opts)
             finally:
-                repo._bookmarkcurrent = currentbookmark
+                repo._activebookmark = activebookmark
                 ui.callhooks = True
             ctx = repo[node]
 
@@ -2622,21 +2632,23 @@
                 if bms:
                     marks = repo._bookmarks
                     for bm in bms:
+                        ui.debug('moving bookmarks %r from %s to %s\n' %
+                                 (marks, old.hex(), hex(newid)))
                         marks[bm] = newid
-                    marks.write()
-            #commit the whole amend process
-            createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
-            if createmarkers and newid != old.node():
-                # mark the new changeset as successor of the rewritten one
-                new = repo[newid]
-                obs = [(old, (new,))]
-                if node:
-                    obs.append((ctx, ()))
-
-                obsolete.createmarkers(repo, obs)
+                    marks.recordchange(tr)
+                #commit the whole amend process
+                if createmarkers:
+                    # mark the new changeset as successor of the rewritten one
+                    new = repo[newid]
+                    obs = [(old, (new,))]
+                    if node:
+                        obs.append((ctx, ()))
+
+                    obsolete.createmarkers(repo, obs)
             tr.close()
         finally:
             tr.release()
+        dsguard.close()
         if not createmarkers and newid != old.node():
             # Strip the intermediate commit (if there was one) and the amended
             # commit
@@ -2645,9 +2657,7 @@
             ui.note(_('stripping amended changeset %s\n') % old)
             repair.strip(ui, repo, old.node(), topic='amend-backup')
     finally:
-        if newid is None:
-            repo.dirstate.invalidate()
-        lockmod.release(lock, wlock)
+        lockmod.release(lock, dsguard, wlock)
     return newid
 
 def commiteditor(repo, ctx, subs, editform=''):
@@ -2691,7 +2701,7 @@
 
     try:
         t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
-    except SyntaxError, inst:
+    except SyntaxError as inst:
         raise util.Abort(inst.args[0])
 
     for k, v in repo.ui.configitems('committemplate'):
@@ -2721,8 +2731,8 @@
         edittext.append(_("HG: branch merge"))
     if ctx.branch():
         edittext.append(_("HG: branch '%s'") % ctx.branch())
-    if bookmarks.iscurrent(repo):
-        edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
+    if bookmarks.isactivewdirparent(repo):
+        edittext.append(_("HG: bookmark '%s'") % repo._activebookmark)
     edittext.extend([_("HG: subrepo %s") % s for s in subs])
     edittext.extend([_("HG: added %s") % f for f in added])
     edittext.extend([_("HG: changed %s") % f for f in modified])
@@ -2815,8 +2825,7 @@
         targetsubs = sorted(s for s in wctx.substate if m(s))
 
         if not m.always():
-            m.bad = lambda x, y: False
-            for abs in repo.walk(m):
+            for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
                 names[abs] = m.rel(abs), m.exact(abs)
 
             # walk target manifest to fill `names`
@@ -2832,8 +2841,7 @@
                         return
                 ui.warn("%s: %s\n" % (m.rel(path), msg))
 
-            m.bad = badfn
-            for abs in ctx.walk(m):
+            for abs in ctx.walk(matchmod.badmatch(m, badfn)):
                 if abs not in names:
                     names[abs] = m.rel(abs), m.exact(abs)
 
@@ -3077,7 +3085,7 @@
     node = ctx.node()
     def checkout(f):
         fc = ctx[f]
-        return repo.wwrite(f, fc.data(), fc.flags())
+        repo.wwrite(f, fc.data(), fc.flags())
 
     audit_path = pathutil.pathauditor(repo.root)
     for f in actions['forget'][0]:
@@ -3103,17 +3111,33 @@
         else:
             normal = repo.dirstate.normal
 
+    newlyaddedandmodifiedfiles = set()
     if interactive:
         # Prompt the user for changes to revert
         torevert = [repo.wjoin(f) for f in actions['revert'][0]]
         m = scmutil.match(ctx, torevert, {})
-        diff = patch.diff(repo, None, ctx.node(), m)
+        diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
+        diffopts.nodates = True
+        diffopts.git = True
+        reversehunks =  repo.ui.configbool('experimental',
+                                           'revertalternateinteractivemode',
+                                           True)
+        if reversehunks:
+            diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
+        else:
+            diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
         originalchunks = patch.parsepatch(diff)
+
         try:
+
             chunks = recordfilter(repo.ui, originalchunks)
-        except patch.PatchError, err:
+            if reversehunks:
+                chunks = patch.reversehunks(chunks)
+
+        except patch.PatchError as err:
             raise util.Abort(_('error parsing patch: %s') % err)
 
+        newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
         # Apply changes
         fp = cStringIO.StringIO()
         for c in chunks:
@@ -3123,22 +3147,20 @@
         if dopatch:
             try:
                 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
-            except patch.PatchError, err:
+            except patch.PatchError as err:
                 raise util.Abort(str(err))
         del fp
     else:
         for f in actions['revert'][0]:
-            wsize = checkout(f)
+            checkout(f)
             if normal:
                 normal(f)
-            elif wsize == repo.dirstate._map[f][2]:
-                # changes may be overlooked without normallookup,
-                # if size isn't changed at reverting
-                repo.dirstate.normallookup(f)
 
     for f in actions['add'][0]:
-        checkout(f)
-        repo.dirstate.add(f)
+        # Don't checkout modified files, they are already created by the diff
+        if f not in newlyaddedandmodifiedfiles:
+            checkout(f)
+            repo.dirstate.add(f)
 
     normal = repo.dirstate.normallookup
     if node == parent and p2 == nullid:
@@ -3259,3 +3281,59 @@
     for f, clearable, allowcommit, msg, hint in unfinishedstates:
         if clearable and repo.vfs.exists(f):
             util.unlink(repo.join(f))
+
+class dirstateguard(object):
+    '''Restore dirstate at unexpected failure.
+
+    At the construction, this class does:
+
+    - write current ``repo.dirstate`` out, and
+    - save ``.hg/dirstate`` into the backup file
+
+    This restores ``.hg/dirstate`` from backup file, if ``release()``
+    is invoked before ``close()``.
+
+    This just removes the backup file at ``close()`` before ``release()``.
+    '''
+
+    def __init__(self, repo, name):
+        repo.dirstate.write()
+        self._repo = repo
+        self._filename = 'dirstate.backup.%s.%d' % (name, id(self))
+        repo.vfs.write(self._filename, repo.vfs.tryread('dirstate'))
+        self._active = True
+        self._closed = False
+
+    def __del__(self):
+        if self._active: # still active
+            # this may occur, even if this class is used correctly:
+            # for example, releasing other resources like transaction
+            # may raise exception before ``dirstateguard.release`` in
+            # ``release(tr, ....)``.
+            self._abort()
+
+    def close(self):
+        if not self._active: # already inactivated
+            msg = (_("can't close already inactivated backup: %s")
+                   % self._filename)
+            raise util.Abort(msg)
+
+        self._repo.vfs.unlink(self._filename)
+        self._active = False
+        self._closed = True
+
+    def _abort(self):
+        # this "invalidate()" prevents "wlock.release()" from writing
+        # changes of dirstate out after restoring to original status
+        self._repo.dirstate.invalidate()
+
+        self._repo.vfs.rename(self._filename, 'dirstate')
+        self._active = False
+
+    def release(self):
+        if not self._closed:
+            if not self._active: # already inactivated
+                msg = (_("can't release already inactivated backup: %s")
+                       % self._filename)
+                raise util.Abort(msg)
+            self._abort()
--- a/mercurial/commands.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/commands.py	Sat Jul 18 17:32:38 2015 -0500
@@ -21,7 +21,7 @@
 import dagparser, context, simplemerge, graphmod, copies
 import random
 import setdiscovery, treediscovery, dagutil, pvec, localrepo
-import phases, obsolete, exchange, bundle2
+import phases, obsolete, exchange, bundle2, repair, lock as lockmod
 import ui as uimod
 
 table = {}
@@ -40,6 +40,12 @@
 # @command decorator.
 inferrepo = ''
 
+# label constants
+# until 3.5, bookmarks.current was the advertised name, not
+# bookmarks.active, so we must use both to avoid breaking old
+# custom styles
+activebookmarklabel = 'bookmarks.active bookmarks.current'
+
 # common command options
 
 globalopts = [
@@ -344,8 +350,8 @@
     def bad(x, y):
         raise util.Abort("%s: %s" % (x, y))
 
-    m = scmutil.match(ctx, pats, opts)
-    m.bad = bad
+    m = scmutil.match(ctx, pats, opts, badfn=bad)
+
     follow = not opts.get('no_follow')
     diffopts = patch.difffeatureopts(ui, opts, section='annotate',
                                      whitespace=True)
@@ -970,21 +976,24 @@
         raise util.Abort(_("bookmark name required"))
 
     if delete or rename or names or inactive:
-        wlock = repo.wlock()
+        wlock = lock = tr = None
         try:
+            wlock = repo.wlock()
+            lock = repo.lock()
             cur = repo.changectx('.').node()
             marks = repo._bookmarks
             if delete:
+                tr = repo.transaction('bookmark')
                 for mark in names:
                     if mark not in marks:
                         raise util.Abort(_("bookmark '%s' does not exist") %
                                          mark)
-                    if mark == repo._bookmarkcurrent:
-                        bookmarks.unsetcurrent(repo)
+                    if mark == repo._activebookmark:
+                        bookmarks.deactivate(repo)
                     del marks[mark]
-                marks.write()
 
             elif rename:
+                tr = repo.transaction('bookmark')
                 if not names:
                     raise util.Abort(_("new bookmark name required"))
                 elif len(names) > 1:
@@ -994,19 +1003,18 @@
                     raise util.Abort(_("bookmark '%s' does not exist") % rename)
                 checkconflict(repo, mark, cur, force)
                 marks[mark] = marks[rename]
-                if repo._bookmarkcurrent == rename and not inactive:
-                    bookmarks.setcurrent(repo, mark)
+                if repo._activebookmark == rename and not inactive:
+                    bookmarks.activate(repo, mark)
                 del marks[rename]
-                marks.write()
-
             elif names:
+                tr = repo.transaction('bookmark')
                 newact = None
                 for mark in names:
                     mark = checkformat(mark)
                     if newact is None:
                         newact = mark
-                    if inactive and mark == repo._bookmarkcurrent:
-                        bookmarks.unsetcurrent(repo)
+                    if inactive and mark == repo._activebookmark:
+                        bookmarks.deactivate(repo)
                         return
                     tgt = cur
                     if rev:
@@ -1014,20 +1022,21 @@
                     checkconflict(repo, mark, cur, force, tgt)
                     marks[mark] = tgt
                 if not inactive and cur == marks[newact] and not rev:
-                    bookmarks.setcurrent(repo, newact)
-                elif cur != tgt and newact == repo._bookmarkcurrent:
-                    bookmarks.unsetcurrent(repo)
-                marks.write()
-
+                    bookmarks.activate(repo, newact)
+                elif cur != tgt and newact == repo._activebookmark:
+                    bookmarks.deactivate(repo)
             elif inactive:
                 if len(marks) == 0:
                     ui.status(_("no bookmarks set\n"))
-                elif not repo._bookmarkcurrent:
+                elif not repo._activebookmark:
                     ui.status(_("no active bookmark\n"))
                 else:
-                    bookmarks.unsetcurrent(repo)
+                    bookmarks.deactivate(repo)
+            if tr is not None:
+                marks.recordchange(tr)
+                tr.close()
         finally:
-            wlock.release()
+            lockmod.release(tr, lock, wlock)
     else: # show bookmarks
         fm = ui.formatter('bookmarks', opts)
         hexfn = fm.hexfunc
@@ -1035,9 +1044,9 @@
         if len(marks) == 0 and not fm:
             ui.status(_("no bookmarks set\n"))
         for bmark, n in sorted(marks.iteritems()):
-            current = repo._bookmarkcurrent
-            if bmark == current:
-                prefix, label = '*', 'bookmarks.current'
+            active = repo._activebookmark
+            if bmark == active:
+                prefix, label = '*', activebookmarklabel
             else:
                 prefix, label = ' ', ''
 
@@ -1048,7 +1057,7 @@
             pad = " " * (25 - encoding.colwidth(bmark))
             fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
                          repo.changelog.rev(n), hexfn(n), label=label)
-            fm.data(active=(bmark == current))
+            fm.data(active=(bmark == active))
             fm.plain('\n')
         fm.end()
 
@@ -1080,7 +1089,9 @@
     change.
 
     Use the command :hg:`update` to switch to an existing branch. Use
-    :hg:`commit --close-branch` to mark this branch as closed.
+    :hg:`commit --close-branch` to mark this branch head as closed.
+    When all heads of the branch are closed, the branch will be
+    considered closed.
 
     Returns 0 on success.
     """
@@ -1107,8 +1118,13 @@
             scmutil.checknewlabel(repo, label, 'branch')
             repo.dirstate.setbranch(label)
             ui.status(_('marked working directory as branch %s\n') % label)
-            ui.status(_('(branches are permanent and global, '
-                        'did you want a bookmark?)\n'))
+
+            # find any open named branches aside from default
+            others = [n for n, h, t, c in repo.branchmap().iterbranches()
+                      if n != "default" and not c]
+            if not others:
+                ui.status(_('(branches are permanent and global, '
+                            'did you want a bookmark?)\n'))
     finally:
         wlock.release()
 
@@ -1405,7 +1421,8 @@
                  stream=opts.get('uncompressed'),
                  rev=opts.get('rev'),
                  update=opts.get('updaterev') or not opts.get('noupdate'),
-                 branch=opts.get('branch'))
+                 branch=opts.get('branch'),
+                 shareopts=opts.get('shareopts'))
 
     return r is None
 
@@ -1413,7 +1430,7 @@
     [('A', 'addremove', None,
      _('mark new/missing files as added/removed before committing')),
     ('', 'close-branch', None,
-     _('mark a branch as closed, hiding it from the branch list')),
+     _('mark a branch head as closed')),
     ('', 'amend', None, _('amend the parent of the working directory')),
     ('s', 'secret', None, _('use the secret phase for committing')),
     ('e', 'edit', None, _('invoke editor on commit messages')),
@@ -1439,6 +1456,10 @@
     commit fails, you will find a backup of your message in
     ``.hg/last-message.txt``.
 
+    The --close-branch flag can be used to mark the current branch
+    head closed. When all heads of a branch are closed, the branch
+    will be considered closed and no longer listed.
+
     The --amend flag can be used to amend the parent of the
     working directory with a new commit that contains the changes
     in the parent in addition to those currently reported by :hg:`status`,
@@ -1459,7 +1480,7 @@
     """
     if opts.get('interactive'):
         opts.pop('interactive')
-        cmdutil.dorecord(ui, repo, commit, 'commit', False,
+        cmdutil.dorecord(ui, repo, commit, None, False,
                         cmdutil.recordfilter, *pats, **opts)
         return
 
@@ -1506,21 +1527,10 @@
                                match,
                                extra=extra)
 
-        current = repo._bookmarkcurrent
-        marks = old.bookmarks()
         node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
         if node == old.node():
             ui.status(_("nothing changed\n"))
             return 1
-        elif marks:
-            ui.debug('moving bookmarks %r from %s to %s\n' %
-                     (marks, old.hex(), hex(node)))
-            newmarks = repo._bookmarks
-            for bm in marks:
-                newmarks[bm] = node
-                if bm == current:
-                    bookmarks.setcurrent(repo, bm)
-            newmarks.write()
     else:
         def commitfunc(ui, repo, message, match, opts):
             backup = ui.backupconfig('phases', 'new-commit')
@@ -2056,7 +2066,8 @@
 
 @command('debugdata',
     [('c', 'changelog', False, _('open changelog')),
-     ('m', 'manifest', False, _('open manifest'))],
+     ('m', 'manifest', False, _('open manifest')),
+     ('', 'dir', False, _('open directory manifest'))],
     _('-c|-m|FILE REV'))
 def debugdata(ui, repo, file_, rev=None, **opts):
     """dump the contents of a data file revision"""
@@ -2163,8 +2174,8 @@
     '''parse and apply a fileset specification'''
     ctx = scmutil.revsingle(repo, opts.get('rev'), None)
     if ui.verbose:
-        tree = fileset.parse(expr)[0]
-        ui.note(tree, "\n")
+        tree = fileset.parse(expr)
+        ui.note(fileset.prettyformat(tree), "\n")
 
     for f in ctx.getfileset(expr):
         ui.write("%s\n" % f)
@@ -2227,6 +2238,7 @@
 @command('debugindex',
     [('c', 'changelog', False, _('open changelog')),
      ('m', 'manifest', False, _('open manifest')),
+     ('', 'dir', False, _('open directory manifest')),
      ('f', 'format', 0, _('revlog format'), _('FORMAT'))],
     _('[-f FORMAT] -c|-m|FILE'),
     optionalrepo=True)
@@ -2321,7 +2333,7 @@
     ui.status(_("checking encoding (%s)...\n") % encoding.encoding)
     try:
         encoding.fromlocal("test")
-    except util.Abort, inst:
+    except util.Abort as inst:
         ui.write(" %s\n" % inst)
         ui.write(_(" (check that your locale is properly set)\n"))
         problems += 1
@@ -2339,7 +2351,7 @@
     try:
         import bdiff, mpatch, base85, osutil
         dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
-    except Exception, inst:
+    except Exception as inst:
         ui.write(" %s\n" % inst)
         ui.write(_(" One or more extensions could not be found"))
         ui.write(_(" (check that you compiled the extensions)\n"))
@@ -2355,7 +2367,7 @@
             # template found, check if it is working
             try:
                 templater.templater(m)
-            except Exception, inst:
+            except Exception as inst:
                 ui.write(" %s\n" % inst)
                 p = None
         else:
@@ -2387,7 +2399,7 @@
     ui.status(_("checking username...\n"))
     try:
         ui.username()
-    except util.Abort, e:
+    except util.Abort as e:
         ui.write(" %s\n" % e)
         ui.write(_(" (specify a username in your configuration file)\n"))
         problems += 1
@@ -2498,7 +2510,7 @@
                                  % (user, pid, host)
                 ui.write("%-6s %s (%ds)\n" % (name + ":", locker, age))
                 return 1
-            except OSError, e:
+            except OSError as e:
                 if e.errno != errno.ENOENT:
                     raise
 
@@ -2545,26 +2557,25 @@
         try:
             tr = repo.transaction('debugobsolete')
             try:
-                try:
-                    date = opts.get('date')
-                    if date:
-                        date = util.parsedate(date)
-                    else:
-                        date = None
-                    prec = parsenodeid(precursor)
-                    parents = None
-                    if opts['record_parents']:
-                        if prec not in repo.unfiltered():
-                            raise util.Abort('cannot used --record-parents on '
-                                             'unknown changesets')
-                        parents = repo.unfiltered()[prec].parents()
-                        parents = tuple(p.node() for p in parents)
-                    repo.obsstore.create(tr, prec, succs, opts['flags'],
-                                         parents=parents, date=date,
-                                         metadata=metadata)
-                    tr.close()
-                except ValueError, exc:
-                    raise util.Abort(_('bad obsmarker input: %s') % exc)
+                date = opts.get('date')
+                if date:
+                    date = util.parsedate(date)
+                else:
+                    date = None
+                prec = parsenodeid(precursor)
+                parents = None
+                if opts['record_parents']:
+                    if prec not in repo.unfiltered():
+                        raise util.Abort('cannot used --record-parents on '
+                                         'unknown changesets')
+                    parents = repo.unfiltered()[prec].parents()
+                    parents = tuple(p.node() for p in parents)
+                repo.obsstore.create(tr, prec, succs, opts['flags'],
+                                     parents=parents, date=date,
+                                     metadata=metadata)
+                tr.close()
+            except ValueError as exc:
+                raise util.Abort(_('bad obsmarker input: %s') % exc)
             finally:
                 tr.release()
         finally:
@@ -2710,6 +2721,11 @@
     finally:
         wlock.release()
 
+@command('debugrebuildfncache', [], '')
+def debugrebuildfncache(ui, repo):
+    """rebuild the fncache file"""
+    repair.rebuildfncache(ui, repo)
+
 @command('debugrename',
     [('r', 'rev', '', _('revision to debug'), _('REV'))],
     _('[-r REV] FILE'))
@@ -2730,6 +2746,7 @@
 @command('debugrevlog',
     [('c', 'changelog', False, _('open changelog')),
      ('m', 'manifest', False, _('open manifest')),
+     ('', 'dir', False, _('open directory manifest')),
      ('d', 'dump', False, _('dump index data'))],
     _('-c|-m|FILE'),
     optionalrepo=True)
@@ -2916,7 +2933,7 @@
     expansion.
     """
     if ui.verbose:
-        tree = revset.parse(expr)[0]
+        tree = revset.parse(expr)
         ui.note(revset.prettyformat(tree), "\n")
         newtree = revset.findaliases(ui, tree)
         if newtree != tree:
@@ -2977,10 +2994,10 @@
         else:
             timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
                                     time.localtime(ent[3]))
-        if ent[1] & 020000:
+        if ent[1] & 0o20000:
             mode = 'lnk'
         else:
-            mode = '%3o' % (ent[1] & 0777 & ~util.umask)
+            mode = '%3o' % (ent[1] & 0o777 & ~util.umask)
         ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
     for f in repo.dirstate.copies():
         ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
@@ -3448,7 +3465,7 @@
         try:
             nodes = repo.vfs.read('graftstate').splitlines()
             revs = [repo[node].rev() for node in nodes]
-        except IOError, inst:
+        except IOError as inst:
             if inst.errno != errno.ENOENT:
                 raise
             raise util.Abort(_("no graft state found, can't continue"))
@@ -3642,7 +3659,7 @@
         reflags |= re.I
     try:
         regexp = util.re.compile(pattern, reflags)
-    except re.error, inst:
+    except re.error as inst:
         ui.warn(_("grep: invalid match pattern: %s\n") % inst)
         return 1
     sep, eol = ':', '\n'
@@ -4042,13 +4059,19 @@
             if bm:
                 output.append(bm)
     else:
-        if not rev:
+        ctx = scmutil.revsingle(repo, rev, None)
+
+        if ctx.rev() is None:
             ctx = repo[None]
             parents = ctx.parents()
+            taglist = []
+            for p in parents:
+                taglist.extend(p.tags())
+
             changed = ""
             if default or id or num:
-                if (util.any(repo.status())
-                    or util.any(ctx.sub(s).dirty() for s in ctx.substate)):
+                if (any(repo.status())
+                    or any(ctx.sub(s).dirty() for s in ctx.substate)):
                     changed = '+'
             if default or id:
                 output = ["%s%s" %
@@ -4057,11 +4080,11 @@
                 output.append("%s%s" %
                   ('+'.join([str(p.rev()) for p in parents]), changed))
         else:
-            ctx = scmutil.revsingle(repo, rev)
             if default or id:
                 output = [hexfunc(ctx.node())]
             if num:
                 output.append(str(ctx.rev()))
+            taglist = ctx.tags()
 
         if default and not ui.quiet:
             b = ctx.branch()
@@ -4069,7 +4092,7 @@
                 output.append("(%s)" % b)
 
             # multiple tags for a single parent separated by '/'
-            t = '/'.join(ctx.tags())
+            t = '/'.join(taglist)
             if t:
                 output.append(t)
 
@@ -4082,7 +4105,7 @@
                 output.append(ctx.branch())
 
             if tags:
-                output.extend(ctx.tags())
+                output.extend(taglist)
 
             if bookmarks:
                 output.extend(ctx.bookmarks())
@@ -4156,6 +4179,12 @@
     cleanly, :hg:`import --partial` will create an empty changeset,
     importing only the patch metadata.
 
+    It is possible to use external patch programs to perform the patch
+    by setting the ``ui.patch`` configuration option. For the default
+    internal tool, the fuzz can also be configured via ``patch.fuzz``.
+    See :hg:`help config` for more information about configuration
+    files and how to use these options.
+
     To read a patch from standard input, use "-" as the patch name. If
     a URL is specified, the patch will be downloaded from it.
     See :hg:`help dates` for a list of formats valid for -d/--date.
@@ -4181,6 +4210,15 @@
 
           hg import --exact proposed-fix.patch
 
+      - use an external tool to apply a patch which is too fuzzy for
+        the default internal tool.
+
+          hg import --config ui.patch="patch --merge" fuzzy.patch
+
+      - change the default fuzzing from 2 to a less strict 7
+
+          hg import --config ui.fuzz=7 fuzz.patch
+
     Returns 0 on success, 1 on partial success (see --partial).
     """
 
@@ -4215,7 +4253,7 @@
         cmdutil.bailifchanged(repo)
 
     base = opts["base"]
-    wlock = lock = tr = None
+    wlock = dsguard = lock = tr = None
     msgs = []
     ret = 0
 
@@ -4223,7 +4261,7 @@
     try:
         try:
             wlock = repo.wlock()
-            repo.dirstate.beginparentchange()
+            dsguard = cmdutil.dirstateguard(repo, 'import')
             if not opts.get('no_commit'):
                 lock = repo.lock()
                 tr = repo.transaction('import')
@@ -4264,18 +4302,16 @@
                 tr.close()
             if msgs:
                 repo.savecommitmessage('\n* * *\n'.join(msgs))
-            repo.dirstate.endparentchange()
+            dsguard.close()
             return ret
-        except: # re-raises
-            # wlock.release() indirectly calls dirstate.write(): since
-            # we're crashing, we do not want to change the working dir
-            # parent after all, so make sure it writes nothing
-            repo.dirstate.invalidate()
-            raise
+        finally:
+            # TODO: get rid of this meaningless try/finally enclosing.
+            # this is kept only to reduce changes in a patch.
+            pass
     finally:
         if tr:
             tr.release()
-        release(lock, wlock)
+        release(lock, dsguard, wlock)
 
 @command('incoming|in',
     [('f', 'force', None,
@@ -4427,8 +4463,8 @@
 
     ret = 1
     ctx = repo[rev]
-    m = scmutil.match(ctx, pats, opts, default='relglob')
-    m.bad = lambda x, y: False
+    m = scmutil.match(ctx, pats, opts, default='relglob',
+                      badfn=lambda x, y: False)
 
     for abs in ctx.matches(m):
         if opts.get('fullpath'):
@@ -4591,7 +4627,7 @@
         else:
             revmatchfn = None
         displayer.show(ctx, copies=copies, matchfn=revmatchfn)
-        if displayer.flush(rev):
+        if displayer.flush(ctx):
             count += 1
 
     displayer.close()
@@ -4704,9 +4740,9 @@
     if node:
         node = scmutil.revsingle(repo, node).node()
 
-    if not node and repo._bookmarkcurrent:
-        bmheads = repo.bookmarkheads(repo._bookmarkcurrent)
-        curhead = repo[repo._bookmarkcurrent].node()
+    if not node and repo._activebookmark:
+        bmheads = repo.bookmarkheads(repo._activebookmark)
+        curhead = repo[repo._activebookmark].node()
         if len(bmheads) == 2:
             if curhead == bmheads[0]:
                 node = bmheads[1]
@@ -4721,7 +4757,7 @@
                 "please merge with an explicit rev or bookmark"),
                 hint=_("run 'hg heads' to see all heads"))
 
-    if not node and not repo._bookmarkcurrent:
+    if not node and not repo._activebookmark:
         branch = repo[None].branch()
         bheads = repo.branchheads(branch)
         nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
@@ -4954,11 +4990,11 @@
      ('f', 'force', False, _('allow to move boundary backward')),
      ('r', 'rev', [], _('target revision'), _('REV')),
     ],
-    _('[-p|-d|-s] [-f] [-r] REV...'))
+    _('[-p|-d|-s] [-f] [-r] [REV...]'))
 def phase(ui, repo, *revs, **opts):
     """set or show the current phase name
 
-    With no argument, show the phase name of specified revisions.
+    With no argument, show the phase name of the current revision(s).
 
     With one of -p/--public, -d/--draft or -s/--secret, change the
     phase value of the specified revisions.
@@ -4970,6 +5006,8 @@
 
     Returns 0 on success, 1 if no phases were changed or some could not
     be changed.
+
+    (For more information about the phases concept, see :hg:`help phases`.)
     """
     # search for a unique phase argument
     targetphase = None
@@ -4983,7 +5021,9 @@
     revs = list(revs)
     revs.extend(opts['rev'])
     if not revs:
-        raise util.Abort(_('no revisions specified'))
+        # display both parents as the second parent phase can influence
+        # the phase of a merge commit
+        revs = [c.rev() for c in repo[None].parents()]
 
     revs = scmutil.revrange(repo, revs)
 
@@ -5044,14 +5084,14 @@
         checkout, movemarkfrom = bookmarks.calculateupdate(ui, repo, checkout)
         try:
             ret = hg.update(repo, checkout)
-        except util.Abort, inst:
+        except util.Abort as inst:
             ui.warn(_("not updating: %s\n") % str(inst))
             if inst.hint:
                 ui.warn(_("(%s)\n") % inst.hint)
             return 0
         if not ret and not checkout:
             if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
-                ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
+                ui.status(_("updating bookmark %s\n") % repo._activebookmark)
         return ret
     if modheads > 1:
         currentbranchheads = len(repo.branchheads())
@@ -5102,11 +5142,17 @@
         revs, checkout = hg.addbranchrevs(repo, other, branches,
                                           opts.get('rev'))
 
-        remotebookmarks = other.listkeys('bookmarks')
-
+
+        pullopargs = {}
         if opts.get('bookmark'):
             if not revs:
                 revs = []
+            # The list of bookmark used here is not the one used to actually
+            # update the bookmark name. This can result in the revision pulled
+            # not ending up with the name of the bookmark because of a race
+            # condition on the server. (See issue 4689 for details)
+            remotebookmarks = other.listkeys('bookmarks')
+            pullopargs['remotebookmarks'] = remotebookmarks
             for b in opts['bookmark']:
                 if b not in remotebookmarks:
                     raise util.Abort(_('remote bookmark %s not found!') % b)
@@ -5114,6 +5160,9 @@
 
         if revs:
             try:
+                # When 'rev' is a bookmark name, we cannot guarantee that it
+                # will be updated with that name because of a race condition
+                # server side. (See issue 4689 for details)
                 oldrevs = revs
                 revs = [] # actually, nodes
                 for r in oldrevs:
@@ -5128,7 +5177,8 @@
 
         modheads = exchange.pull(repo, other, heads=revs,
                                  force=opts.get('force'),
-                                 bookmarks=opts.get('bookmark', ())).cgresult
+                                 bookmarks=opts.get('bookmark', ()),
+                                 opargs=pullopargs).cgresult
         if checkout:
             checkout = str(repo.changelog.rev(checkout))
         repo._subtoppath = source
@@ -5528,7 +5578,7 @@
             hint = _("uncommitted merge, use --all to discard all changes,"
                      " or 'hg update -C .' to abort the merge")
             raise util.Abort(msg, hint=hint)
-        dirty = util.any(repo.status())
+        dirty = any(repo.status())
         node = ctx.node()
         if node != parent:
             if dirty:
@@ -5880,7 +5930,7 @@
     """summarize working directory state
 
     This generates a brief summary of the working directory state,
-    including parents, branch, commit status, and available updates.
+    including parents, branch, commit status, phase and available updates.
 
     With the --remote option, this will check the default paths for
     incoming and outgoing changes. This can be time-consuming.
@@ -5922,15 +5972,15 @@
         ui.status(m, label='log.branch')
 
     if marks:
-        current = repo._bookmarkcurrent
+        active = repo._activebookmark
         # i18n: column positioning for "hg summary"
         ui.write(_('bookmarks:'), label='log.bookmark')
-        if current is not None:
-            if current in marks:
-                ui.write(' *' + current, label='bookmarks.current')
-                marks.remove(current)
+        if active is not None:
+            if active in marks:
+                ui.write(' *' + active, label=activebookmarklabel)
+                marks.remove(active)
             else:
-                ui.write(' [%s]' % current, label='bookmarks.current')
+                ui.write(' [%s]' % active, label=activebookmarklabel)
         for m in marks:
             ui.write(' ' + m, label='log.bookmark')
         ui.write('\n', label='log.bookmark')
@@ -5986,6 +6036,14 @@
     elif pnode not in bheads:
         t += _(' (new branch head)')
 
+    if parents:
+        pendingphase = max(p.phase() for p in parents)
+    else:
+        pendingphase = phases.public
+
+    if pendingphase > phases.newcommitphase(ui):
+        t += ' (%s)' % phases.phasenames[pendingphase]
+
     if cleanworkdir:
         # i18n: column positioning for "hg summary"
         ui.status(_('commit: %s\n') % t.strip())
@@ -6008,6 +6066,17 @@
         ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
                  (new, len(bheads)))
 
+    t = []
+    draft = len(repo.revs('draft()'))
+    if draft:
+        t.append(_('%d draft') % draft)
+    secret = len(repo.revs('secret()'))
+    if secret:
+        t.append(_('%d secret') % secret)
+
+    if draft or secret:
+        ui.status(_('phases: %s\n') % ', '.join(t))
+
     cmdutil.summaryhooks(ui, repo)
 
     if opts.get('remote'):
@@ -6331,7 +6400,7 @@
 
     Update the repository's working directory to the specified
     changeset. If no changeset is specified, update to the tip of the
-    current named branch and move the current bookmark (see :hg:`help
+    current named branch and move the active bookmark (see :hg:`help
     bookmarks`).
 
     Update sets the working directory's parent revision to the specified
@@ -6384,7 +6453,7 @@
 
     cmdutil.clearunfinished(repo)
 
-    # with no argument, we also move the current bookmark, if any
+    # with no argument, we also move the active bookmark, if any
     rev, movemarkfrom = bookmarks.calculateupdate(ui, repo, rev)
 
     # if we defined a bookmark, we have to remember the original bookmark name
@@ -6413,15 +6482,20 @@
 
     if not ret and movemarkfrom:
         if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
-            ui.status(_("updating bookmark %s\n") % repo._bookmarkcurrent)
+            ui.status(_("updating bookmark %s\n") % repo._activebookmark)
+        else:
+            # this can happen with a non-linear update
+            ui.status(_("(leaving bookmark %s)\n") %
+                      repo._activebookmark)
+            bookmarks.deactivate(repo)
     elif brev in repo._bookmarks:
-        bookmarks.setcurrent(repo, brev)
+        bookmarks.activate(repo, brev)
         ui.status(_("(activating bookmark %s)\n") % brev)
     elif brev:
-        if repo._bookmarkcurrent:
+        if repo._activebookmark:
             ui.status(_("(leaving bookmark %s)\n") %
-                      repo._bookmarkcurrent)
-        bookmarks.unsetcurrent(repo)
+                      repo._activebookmark)
+        bookmarks.deactivate(repo)
 
     return ret
 
--- a/mercurial/commandserver.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/commandserver.py	Sat Jul 18 17:32:38 2015 -0500
@@ -132,6 +132,7 @@
     def __init__(self, ui, repo, fin, fout):
         self.cwd = os.getcwd()
 
+        # developer config: cmdserver.log
         logpath = ui.config("cmdserver", "log", None)
         if logpath:
             global logfile
@@ -300,9 +301,9 @@
                 sv.serve()
             # handle exceptions that may be raised by command server. most of
             # known exceptions are caught by dispatch.
-            except util.Abort, inst:
+            except util.Abort as inst:
                 ui.warn(_('abort: %s\n') % inst)
-            except IOError, inst:
+            except IOError as inst:
                 if inst.errno != errno.EPIPE:
                     raise
             except KeyboardInterrupt:
--- a/mercurial/config.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/config.py	Sat Jul 18 17:32:38 2015 -0500
@@ -10,10 +10,11 @@
 import os, errno
 
 class config(object):
-    def __init__(self, data=None):
+    def __init__(self, data=None, includepaths=[]):
         self._data = {}
         self._source = {}
         self._unset = []
+        self._includepaths = includepaths
         if data:
             for k in data._data:
                 self._data[k] = data[k].copy()
@@ -110,14 +111,18 @@
                 item = None
                 cont = False
             m = includere.match(l)
-            if m:
-                inc = util.expandpath(m.group(1))
-                base = os.path.dirname(src)
-                inc = os.path.normpath(os.path.join(base, inc))
-                if include:
+
+            if m and include:
+                expanded = util.expandpath(m.group(1))
+                includepaths = [os.path.dirname(src)] + self._includepaths
+
+                for base in includepaths:
+                    inc = os.path.normpath(os.path.join(base, expanded))
+
                     try:
                         include(inc, remap=remap, sections=sections)
-                    except IOError, inst:
+                        break
+                    except IOError as inst:
                         if inst.errno != errno.ENOENT:
                             raise error.ParseError(_("cannot include %s (%s)")
                                                    % (inc, inst.strerror),
--- a/mercurial/context.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/context.py	Sat Jul 18 17:32:38 2015 -0500
@@ -5,11 +5,11 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from node import nullid, nullrev, short, hex, bin
+from node import nullid, nullrev, wdirid, short, hex, bin
 from i18n import _
 import mdiff, error, util, scmutil, subrepo, patch, encoding, phases
 import match as matchmod
-import copy, os, errno, stat
+import os, errno, stat
 import obsolete as obsmod
 import repoview
 import fileset
@@ -249,13 +249,25 @@
             return ''
 
     def sub(self, path):
+        '''return a subrepo for the stored revision of path, never wdir()'''
         return subrepo.subrepo(self, path)
 
-    def match(self, pats=[], include=None, exclude=None, default='glob'):
+    def nullsub(self, path, pctx):
+        return subrepo.nullsubrepo(self, path, pctx)
+
+    def workingsub(self, path):
+        '''return a subrepo for the stored revision, or wdir if this is a wdir
+        context.
+        '''
+        return subrepo.subrepo(self, path, allowwdir=True)
+
+    def match(self, pats=[], include=None, exclude=None, default='glob',
+              listsubrepos=False, badfn=None):
         r = self._repo
         return matchmod.match(r.root, r.getcwd(), pats,
                               include, exclude, default,
-                              auditor=r.auditor, ctx=self)
+                              auditor=r.auditor, ctx=self,
+                              listsubrepos=listsubrepos, badfn=badfn)
 
     def diff(self, ctx2=None, match=None, **opts):
         """Returns a diff generator for the given contexts and matcher"""
@@ -338,7 +350,7 @@
 
 
 def makememctx(repo, parents, text, user, date, branch, files, store,
-               editor=None):
+               editor=None, extra=None):
     def getfilectx(repo, memctx, path):
         data, mode, copied = store.getfile(path)
         if data is None:
@@ -346,7 +358,8 @@
         islink, isexec = mode
         return memfilectx(repo, path, data, islink=islink, isexec=isexec,
                                   copied=copied, memctx=memctx)
-    extra = {}
+    if extra is None:
+        extra = {}
     if branch:
         extra['branch'] = encoding.fromlocal(branch)
     ctx =  memctx(repo, parents, text, files, getfilectx, user,
@@ -459,7 +472,7 @@
                 pass
         except (error.FilteredIndexError, error.FilteredLookupError,
                 error.FilteredRepoLookupError):
-            if repo.filtername == 'visible':
+            if repo.filtername.startswith('visible'):
                 msg = _("hidden revision '%s'") % changeid
                 hint = _('use --hidden to access hidden revisions')
                 raise error.FilteredRepoLookupError(msg, hint=hint)
@@ -563,7 +576,8 @@
         elif len(cahs) == 1:
             anc = cahs[0]
         else:
-            for r in self._repo.ui.configlist('merge', 'preferancestor'):
+            # experimental config: merge.preferancestor
+            for r in self._repo.ui.configlist('merge', 'preferancestor', ['*']):
                 try:
                     ctx = changectx(self._repo, r)
                 except error.RepoLookupError:
@@ -589,19 +603,17 @@
     def walk(self, match):
         '''Generates matching file names.'''
 
-        # Override match.bad method to have message with nodeid
-        match = copy.copy(match)
-        oldbad = match.bad
+        # Wrap match.bad method to have message with nodeid
         def bad(fn, msg):
             # The manifest doesn't know about subrepos, so don't complain about
             # paths into valid subrepos.
-            if util.any(fn == s or fn.startswith(s + '/')
-                        for s in self.substate):
+            if any(fn == s or fn.startswith(s + '/')
+                   for s in self.substate):
                 return
-            oldbad(fn, _('no such file in rev %s') % self)
-        match.bad = bad
+            match.bad(fn, _('no such file in rev %s') % self)
 
-        return self._manifest.walk(match)
+        m = matchmod.badmatch(match, bad)
+        return self._manifest.walk(m)
 
     def matches(self, match):
         return self.walk(match)
@@ -1236,10 +1248,7 @@
         return self._extra
 
     def tags(self):
-        t = []
-        for p in self.parents():
-            t.extend(p.tags())
-        return t
+        return []
 
     def bookmarks(self):
         b = []
@@ -1308,6 +1317,11 @@
         self._repo.dirstate.setparents(node)
         self._repo.dirstate.endparentchange()
 
+        # write changes out explicitly, because nesting wlock at
+        # runtime may prevent 'wlock.release()' in 'repo.commit()'
+        # from immediately doing so for subsequent changing files
+        self._repo.dirstate.write()
+
 class workingctx(committablectx):
     """A workingctx object makes access to data related to
     the current working directory convenient.
@@ -1330,6 +1344,9 @@
     def __contains__(self, key):
         return self._repo.dirstate[key] not in "?r"
 
+    def hex(self):
+        return hex(wdirid)
+
     @propertycache
     def _parents(self):
         p = self._repo.dirstate.parents()
@@ -1424,7 +1441,7 @@
     def copy(self, source, dest):
         try:
             st = self._repo.wvfs.lstat(dest)
-        except OSError, err:
+        except OSError as err:
             if err.errno != errno.ENOENT:
                 raise
             self._repo.ui.warn(_("%s does not exist!\n") % dest)
@@ -1443,17 +1460,21 @@
             finally:
                 wlock.release()
 
-    def match(self, pats=[], include=None, exclude=None, default='glob'):
+    def match(self, pats=[], include=None, exclude=None, default='glob',
+              listsubrepos=False, badfn=None):
         r = self._repo
 
         # Only a case insensitive filesystem needs magic to translate user input
         # to actual case in the filesystem.
         if not util.checkcase(r.root):
             return matchmod.icasefsmatcher(r.root, r.getcwd(), pats, include,
-                                           exclude, default, r.auditor, self)
+                                           exclude, default, r.auditor, self,
+                                           listsubrepos=listsubrepos,
+                                           badfn=badfn)
         return matchmod.match(r.root, r.getcwd(), pats,
                               include, exclude, default,
-                              auditor=r.auditor, ctx=self)
+                              auditor=r.auditor, ctx=self,
+                              listsubrepos=listsubrepos, badfn=badfn)
 
     def _filtersuspectsymlink(self, files):
         if not files or self._repo.dirstate._checklink:
@@ -1502,6 +1523,10 @@
                 try:
                     for f in fixup:
                         normal(f)
+                    # write changes out explicitly, because nesting
+                    # wlock at runtime may prevent 'wlock.release()'
+                    # below from doing so for subsequent changing files
+                    self._repo.dirstate.write()
                 finally:
                     wlock.release()
             except error.LockError:
@@ -1666,7 +1691,7 @@
         t, tz = self._changectx.date()
         try:
             return (int(self._repo.wvfs.lstat(self._path).st_mtime), tz)
-        except OSError, err:
+        except OSError as err:
             if err.errno != errno.ENOENT:
                 raise
             return (t, tz)
--- a/mercurial/copies.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/copies.py	Sat Jul 18 17:32:38 2015 -0500
@@ -5,15 +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.
 
-import util
+import util, pathutil
 import heapq
 
-def _dirname(f):
-    s = f.rfind("/")
-    if s == -1:
-        return ""
-    return f[:s]
-
 def _findlimit(repo, a, b):
     """
     Find the last revision that needs to be checked to ensure that a full
@@ -376,6 +370,7 @@
 
     # generate a directory move map
     d1, d2 = c1.dirs(), c2.dirs()
+    # Hack for adding '', which is not otherwise added, to d1 and d2
     d1.addpath('/')
     d2.addpath('/')
     invalid = set()
@@ -384,7 +379,7 @@
     # examine each file copy for a potential directory move, which is
     # when all the files in a directory are moved to a new directory
     for dst, src in fullcopy.iteritems():
-        dsrc, ddst = _dirname(src), _dirname(dst)
+        dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
         if dsrc in invalid:
             # already seen to be uninteresting
             continue
--- a/mercurial/crecord.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/crecord.py	Sat Jul 18 17:32:38 2015 -0500
@@ -20,7 +20,8 @@
 
 # os.name is one of: 'posix', 'nt', 'dos', 'os2', 'mac', or 'ce'
 if os.name == 'posix':
-    import curses, fcntl, termios
+    import curses
+    import fcntl, termios
 else:
     # I have no idea if wcurses works with crecord...
     try:
@@ -424,9 +425,11 @@
     def __repr__(self):
         return '<hunk %r@%d>' % (self.filename(), self.fromline)
 
-def filterpatch(ui, chunks, chunkselector):
+def filterpatch(ui, chunks, chunkselector, operation=None):
     """interactively filter patch chunks into applied-only chunks"""
 
+    if operation is None:
+        operation = _('confirm')
     chunks = list(chunks)
     # convert chunks list into structure suitable for displaying/modifying
     # with curses.  create a list of headers only.
@@ -479,7 +482,12 @@
     """
     ui.write(_('starting interactive selection\n'))
     chunkselector = curseschunkselector(headerlist, ui)
+    f = signal.getsignal(signal.SIGTSTP)
     curses.wrapper(chunkselector.main)
+    if chunkselector.initerr is not None:
+        raise util.Abort(chunkselector.initerr)
+    # ncurses does not restore signal handler for SIGTSTP
+    signal.signal(signal.SIGTSTP, f)
 
 def testdecorator(testfn, f):
     def u(*args, **kwargs):
@@ -508,6 +516,7 @@
 
         self.ui = ui
 
+        self.errorstr = None
         # list of all chunks
         self.chunklist = []
         for h in headerlist:
@@ -973,6 +982,12 @@
 
         # print out the status lines at the top
         try:
+            if self.errorstr is not None:
+                printstring(self.statuswin, self.errorstr, pairname='legend')
+                printstring(self.statuswin, 'Press any key to continue',
+                            pairname='legend')
+                self.statuswin.refresh()
+                return
             printstring(self.statuswin,
                         "SELECT CHUNKS: (j/k/up/dn/pgup/pgdn) move cursor; "
                         "(space/A) toggle hunk/all; (e)dit hunk;",
@@ -1416,6 +1431,13 @@
         """
             edit the currently chelected chunk
         """
+        def updateui(self):
+            self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1
+            self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
+            self.updatescroll()
+            self.stdscr.refresh()
+            self.statuswin.refresh()
+            self.stdscr.keypad(1)
 
         def editpatchwitheditor(self, chunk):
             if chunk is None:
@@ -1451,9 +1473,11 @@
                 f.close()
                 # start the editor and wait for it to complete
                 editor = self.ui.geteditor()
-                self.ui.system("%s \"%s\"" % (editor, patchfn),
-                          environ={'hguser': self.ui.username()},
-                          onerr=util.Abort, errprefix=_("edit failed"))
+                ret = self.ui.system("%s \"%s\"" % (editor, patchfn),
+                          environ={'hguser': self.ui.username()})
+                if ret != 0:
+                    self.errorstr = "Editor exited with status %d" % ret
+                    return None
                 # remove comment lines
                 patchfp = open(patchfn)
                 ncpatchfp = cStringIO.StringIO()
@@ -1478,6 +1502,10 @@
 
         beforeadded, beforeremoved = item.added, item.removed
         newpatches = editpatchwitheditor(self, item)
+        if newpatches is None:
+            if not test:
+                updateui(self)
+            return
         header = item.header
         editedhunkindex = header.hunks.index(item)
         hunksbefore = header.hunks[:editedhunkindex]
@@ -1498,12 +1526,7 @@
         self.currentselecteditem = header
 
         if not test:
-            self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1
-            self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
-            self.updatescroll()
-            self.stdscr.refresh()
-            self.statuswin.refresh()
-            self.stdscr.keypad(1)
+            updateui(self)
 
     def emptypatch(self):
         item = self.headerlist
@@ -1551,6 +1574,8 @@
             self.togglefolded(foldparent=True)
         elif keypressed in ["?"]:
             self.helpwindow()
+            self.stdscr.clear()
+            self.stdscr.refresh()
 
     def main(self, stdscr):
         """
@@ -1559,6 +1584,9 @@
         """
         signal.signal(signal.SIGWINCH, self.sigwinchhandler)
         self.stdscr = stdscr
+        # error during initialization, cannot be printed in the curses
+        # interface, it should be printed by the calling code
+        self.initerr = None
         self.yscreensize, self.xscreensize = self.stdscr.getmaxyx()
 
         curses.start_color()
@@ -1584,8 +1612,12 @@
 
         # add 1 so to account for last line text reaching end of line
         self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1
-        self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
 
+        try:
+            self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
+        except curses.error:
+            self.initerr = _('this diff is too large to be displayed')
+            return
         # initialize selecteitemendline (initial start-line is 0)
         self.selecteditemendline = self.getnumlinesdisplayed(
             self.currentselecteditem, recursechildren=False)
@@ -1594,6 +1626,9 @@
             self.updatescreen()
             try:
                 keypressed = self.statuswin.getkey()
+                if self.errorstr is not None:
+                    self.errorstr = None
+                    continue
             except curses.error:
                 keypressed = "foobar"
             if self.handlekeypressed(keypressed):
--- a/mercurial/dagparser.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/dagparser.py	Sat Jul 18 17:32:38 2015 -0500
@@ -176,10 +176,7 @@
     chiter = (c for c in desc)
 
     def nextch():
-        try:
-            return chiter.next()
-        except StopIteration:
-            return '\0'
+        return next(chiter, '\0')
 
     def nextrun(c, allow):
         s = ''
--- a/mercurial/demandimport.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/demandimport.py	Sat Jul 18 17:32:38 2015 -0500
@@ -24,7 +24,15 @@
   b = __import__(a)
 '''
 
-import __builtin__, os, sys
+import os, sys
+from contextlib import contextmanager
+
+# __builtin__ in Python 2, builtins in Python 3.
+try:
+    import __builtin__ as builtins
+except ImportError:
+    import builtins
+
 _origimport = __import__
 
 nothing = object()
@@ -34,7 +42,7 @@
     level = -1
     if sys.version_info[0] >= 3:
         level = 0
-    _origimport(__builtin__.__name__, {}, {}, None, level)
+    _origimport(builtins.__name__, {}, {}, None, level)
 except TypeError: # no level argument
     def _import(name, globals, locals, fromlist, level):
         "call _origimport with no level argument"
@@ -169,13 +177,26 @@
     ]
 
 def isenabled():
-    return __builtin__.__import__ == _demandimport
+    return builtins.__import__ == _demandimport
 
 def enable():
     "enable global demand-loading of modules"
     if os.environ.get('HGDEMANDIMPORT') != 'disable':
-        __builtin__.__import__ = _demandimport
+        builtins.__import__ = _demandimport
 
 def disable():
     "disable global demand-loading of modules"
-    __builtin__.__import__ = _origimport
+    builtins.__import__ = _origimport
+
+@contextmanager
+def deactivated():
+    "context manager for disabling demandimport in 'with' blocks"
+    demandenabled = isenabled()
+    if demandenabled:
+        disable()
+
+    try:
+        yield
+    finally:
+        if demandenabled:
+            enable()
--- a/mercurial/dirs.c	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/dirs.c	Sat Jul 18 17:32:38 2015 -0500
@@ -9,7 +9,6 @@
 
 #define PY_SSIZE_T_CLEAN
 #include <Python.h>
-#include <string.h>
 #include "util.h"
 
 /*
@@ -29,23 +28,25 @@
 	PyObject *dict;
 } dirsObject;
 
-static inline Py_ssize_t _finddir(PyObject *path, Py_ssize_t pos)
+static inline Py_ssize_t _finddir(const char *path, Py_ssize_t pos)
 {
-	const char *s = PyString_AS_STRING(path);
+	while (pos != -1) {
+		if (path[pos] == '/')
+			break;
+		pos -= 1;
+	}
 
-	const char *ret = strchr(s + pos, '/');
-	return (ret != NULL) ? (ret - s) : -1;
+	return pos;
 }
 
 static int _addpath(PyObject *dirs, PyObject *path)
 {
-	char *cpath = PyString_AS_STRING(path);
-	Py_ssize_t len = PyString_GET_SIZE(path);
-	Py_ssize_t pos = -1;
+	const char *cpath = PyString_AS_STRING(path);
+	Py_ssize_t pos = PyString_GET_SIZE(path);
 	PyObject *key = NULL;
 	int ret = -1;
 
-	while ((pos = _finddir(path, pos + 1)) != -1) {
+	while ((pos = _finddir(cpath, pos - 1)) != -1) {
 		PyObject *val;
 
 		/* It's likely that every prefix already has an entry
@@ -53,18 +54,10 @@
 		   deallocating a string for each prefix we check. */
 		if (key != NULL)
 			((PyStringObject *)key)->ob_shash = -1;
-		else if (pos != 0) {
-			/* pos >= 1, which means that len >= 2. This is
-			   guaranteed to produce a non-interned string. */
-			key = PyString_FromStringAndSize(cpath, len);
-			if (key == NULL)
-				goto bail;
-		} else {
-			/* pos == 0, which means we need to increment the dir
-			   count for the empty string. We need to make sure we
-			   don't muck around with interned strings, so throw it
-			   away later. */
-			key = PyString_FromString("");
+		else {
+			/* Force Python to not reuse a small shared string. */
+			key = PyString_FromStringAndSize(cpath,
+							 pos < 2 ? 2 : pos);
 			if (key == NULL)
 				goto bail;
 		}
@@ -74,11 +67,7 @@
 		val = PyDict_GetItem(dirs, key);
 		if (val != NULL) {
 			PyInt_AS_LONG(val) += 1;
-			if (pos != 0)
-				PyString_AS_STRING(key)[pos] = '/';
-			else
-				key = NULL;
-			continue;
+			break;
 		}
 
 		/* Force Python to not reuse a small shared int. */
@@ -92,9 +81,6 @@
 		Py_DECREF(val);
 		if (ret == -1)
 			goto bail;
-
-		/* Clear the key out since we've already exposed it to Python
-		   and can't mutate it further. */
 		Py_CLEAR(key);
 	}
 	ret = 0;
@@ -107,14 +93,15 @@
 
 static int _delpath(PyObject *dirs, PyObject *path)
 {
-	Py_ssize_t pos = -1;
+	char *cpath = PyString_AS_STRING(path);
+	Py_ssize_t pos = PyString_GET_SIZE(path);
 	PyObject *key = NULL;
 	int ret = -1;
 
-	while ((pos = _finddir(path, pos + 1)) != -1) {
+	while ((pos = _finddir(cpath, pos - 1)) != -1) {
 		PyObject *val;
 
-		key = PyString_FromStringAndSize(PyString_AS_STRING(path), pos);
+		key = PyString_FromStringAndSize(cpath, pos);
 
 		if (key == NULL)
 			goto bail;
@@ -126,9 +113,11 @@
 			goto bail;
 		}
 
-		if (--PyInt_AS_LONG(val) <= 0 &&
-		    PyDict_DelItem(dirs, key) == -1)
-			goto bail;
+		if (--PyInt_AS_LONG(val) <= 0) {
+			if (PyDict_DelItem(dirs, key) == -1)
+				goto bail;
+		} else
+			break;
 		Py_CLEAR(key);
 	}
 	ret = 0;
--- a/mercurial/dirstate.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/dirstate.py	Sat Jul 18 17:32:38 2015 -0500
@@ -7,8 +7,9 @@
 
 from node import nullid
 from i18n import _
-import scmutil, util, ignore, osutil, parsers, encoding, pathutil
+import scmutil, util, osutil, parsers, encoding, pathutil
 import os, stat, errno
+import match as matchmod
 
 propertycache = util.propertycache
 filecache = scmutil.filecache
@@ -47,6 +48,7 @@
         self._ui = ui
         self._filecache = {}
         self._parentwriters = 0
+        self._filename = 'dirstate'
 
     def beginparentchange(self):
         '''Marks the beginning of a set of changes that involve changing
@@ -113,7 +115,7 @@
     def _branch(self):
         try:
             return self._opener.read("branch").strip() or "default"
-        except IOError, inst:
+        except IOError as inst:
             if inst.errno != errno.ENOENT:
                 raise
             return "default"
@@ -121,7 +123,7 @@
     @propertycache
     def _pl(self):
         try:
-            fp = self._opener("dirstate")
+            fp = self._opener(self._filename)
             st = fp.read(40)
             fp.close()
             l = len(st)
@@ -129,7 +131,7 @@
                 return st[:20], st[20:40]
             elif l > 0 and l < 40:
                 raise util.Abort(_('working directory state appears damaged!'))
-        except IOError, err:
+        except IOError as err:
             if err.errno != errno.ENOENT:
                 raise
         return [nullid, nullid]
@@ -143,13 +145,20 @@
 
     @rootcache('.hgignore')
     def _ignore(self):
-        files = [self._join('.hgignore')]
+        files = []
+        if os.path.exists(self._join('.hgignore')):
+            files.append(self._join('.hgignore'))
         for name, path in self._ui.configitems("ui"):
             if name == 'ignore' or name.startswith('ignore.'):
                 # we need to use os.path.join here rather than self._join
                 # because path is arbitrary and user-specified
                 files.append(os.path.join(self._rootdir, util.expandpath(path)))
-        return ignore.ignore(self._root, files, self._ui.warn)
+
+        if not files:
+            return util.never
+
+        pats = ['include:%s' % f for f in files]
+        return matchmod.match(self._root, '', [], pats, warn=self._ui.warn)
 
     @propertycache
     def _slash(self):
@@ -317,14 +326,31 @@
         self._map = {}
         self._copymap = {}
         try:
-            st = self._opener.read("dirstate")
-        except IOError, err:
+            fp = self._opener.open(self._filename)
+            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
@@ -559,7 +585,8 @@
         self._dirty = True
 
     def rebuild(self, parent, allfiles, changedfiles=None):
-        changedfiles = changedfiles or allfiles
+        if changedfiles is None:
+            changedfiles = allfiles
         oldmap = self._map
         self.clear()
         for f in allfiles:
@@ -567,9 +594,9 @@
                 self._map[f] = oldmap[f]
             else:
                 if 'x' in allfiles.flags(f):
-                    self._map[f] = dirstatetuple('n', 0777, -1, 0)
+                    self._map[f] = dirstatetuple('n', 0o777, -1, 0)
                 else:
-                    self._map[f] = dirstatetuple('n', 0666, -1, 0)
+                    self._map[f] = dirstatetuple('n', 0o666, -1, 0)
         self._pl = (parent, nullid)
         self._dirty = True
 
@@ -584,7 +611,7 @@
             import time # to avoid useless import
             time.sleep(delaywrite)
 
-        st = self._opener("dirstate", "w", atomictemp=True)
+        st = self._opener(self._filename, "w", atomictemp=True)
         # use the modification time of the newly created temporary file as the
         # filesystem's notion of 'now'
         now = util.fstat(st).st_mtime
@@ -690,7 +717,7 @@
                     badfn(ff, badtype(kind))
                     if nf in dmap:
                         results[nf] = None
-            except OSError, inst: # nf not found on disk - it is dirstate only
+            except OSError as inst: # nf not found on disk - it is dirstate only
                 if nf in dmap: # does it exactly match a missing file?
                     results[nf] = None
                 else: # does it match a missing directory?
@@ -746,7 +773,7 @@
         if match.isexact(): # match.exact
             exact = True
             dirignore = util.always # skip step 2
-        elif match.files() and not match.anypats(): # match.match, no patterns
+        elif match.prefix(): # match.match, no patterns
             skipstep3 = True
 
         if not exact and self._checkcase:
@@ -775,7 +802,7 @@
                     skip = '.hg'
                 try:
                     entries = listdir(join(nd), stat=True, skip=skip)
-                except OSError, inst:
+                except OSError as inst:
                     if inst.errno in (errno.EACCES, errno.ENOENT):
                         match.bad(self.pathto(nd), inst.strerror)
                         continue
@@ -936,7 +963,7 @@
                 mtime = int(st.st_mtime)
                 if (size >= 0 and
                     ((size != st.st_size and size != st.st_size & _rangemask)
-                     or ((mode ^ st.st_mode) & 0100 and checkexec))
+                     or ((mode ^ st.st_mode) & 0o100 and checkexec))
                     or size == -2 # other parent
                     or fn in copymap):
                     madd(fn)
@@ -972,7 +999,7 @@
             # fast path -- filter the other way around, since typically files is
             # much smaller than dmap
             return [f for f in files if f in dmap]
-        if not match.anypats() and util.all(fn in dmap for fn in files):
+        if match.prefix() and all(fn in dmap for fn in files):
             # fast path -- all the values are known to be files, so just return
             # that
             return list(files)
--- a/mercurial/dispatch.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/dispatch.py	Sat Jul 18 17:32:38 2015 -0500
@@ -11,6 +11,7 @@
 import util, commands, hg, fancyopts, extensions, hook, error
 import cmdutil, encoding
 import ui as uimod
+import demandimport
 
 class request(object):
     def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
@@ -75,12 +76,12 @@
             req.ui.fout = req.fout
         if req.ferr:
             req.ui.ferr = req.ferr
-    except util.Abort, inst:
+    except util.Abort as inst:
         ferr.write(_("abort: %s\n") % inst)
         if inst.hint:
             ferr.write(_("(%s)\n") % inst.hint)
         return -1
-    except error.ParseError, inst:
+    except error.ParseError as inst:
         _formatparse(ferr.write, inst)
         return -1
 
@@ -128,19 +129,21 @@
                 for sec, name, val in cfgs:
                     req.repo.ui.setconfig(sec, name, val, source='--config')
 
-            # if we are in HGPLAIN mode, then disable custom debugging
+            # developer config: ui.debugger
             debugger = ui.config("ui", "debugger")
             debugmod = pdb
             if not debugger or ui.plain():
+                # if we are in HGPLAIN mode, then disable custom debugging
                 debugger = 'pdb'
             elif '--debugger' in req.args:
                 # This import can be slow for fancy debuggers, so only
                 # do it when absolutely necessary, i.e. when actual
                 # debugging has been requested
-                try:
-                    debugmod = __import__(debugger)
-                except ImportError:
-                    pass # Leave debugmod = pdb
+                with demandimport.deactivated():
+                    try:
+                        debugmod = __import__(debugger)
+                    except ImportError:
+                        pass # Leave debugmod = pdb
 
             debugtrace[debugger] = debugmod.set_trace
             debugmortem[debugger] = debugmod.post_mortem
@@ -170,36 +173,43 @@
 
     # Global exception handling, alphabetically
     # Mercurial-specific first, followed by built-in and library exceptions
-    except error.AmbiguousCommand, inst:
+    except error.AmbiguousCommand as inst:
         ui.warn(_("hg: command '%s' is ambiguous:\n    %s\n") %
                 (inst.args[0], " ".join(inst.args[1])))
-    except error.ParseError, inst:
+    except error.ParseError as inst:
         _formatparse(ui.warn, inst)
         return -1
-    except error.LockHeld, inst:
+    except error.LockHeld as inst:
         if inst.errno == errno.ETIMEDOUT:
             reason = _('timed out waiting for lock held by %s') % inst.locker
         else:
             reason = _('lock held by %s') % inst.locker
         ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
-    except error.LockUnavailable, inst:
+    except error.LockUnavailable as inst:
         ui.warn(_("abort: could not lock %s: %s\n") %
                (inst.desc or inst.filename, inst.strerror))
-    except error.CommandError, inst:
+    except error.CommandError as inst:
         if inst.args[0]:
             ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
             commands.help_(ui, inst.args[0], full=False, command=True)
         else:
             ui.warn(_("hg: %s\n") % inst.args[1])
             commands.help_(ui, 'shortlist')
-    except error.OutOfBandError, inst:
-        ui.warn(_("abort: remote error:\n"))
-        ui.warn(''.join(inst.args))
-    except error.RepoError, inst:
+    except error.OutOfBandError as inst:
+        if inst.args:
+            msg = _("abort: remote error:\n")
+        else:
+            msg = _("abort: remote error\n")
+        ui.warn(msg)
+        if inst.args:
+            ui.warn(''.join(inst.args))
+        if inst.hint:
+            ui.warn('(%s)\n' % inst.hint)
+    except error.RepoError as inst:
         ui.warn(_("abort: %s!\n") % inst)
         if inst.hint:
             ui.warn(_("(%s)\n") % inst.hint)
-    except error.ResponseError, inst:
+    except error.ResponseError as inst:
         ui.warn(_("abort: %s") % inst.args[0])
         if not isinstance(inst.args[1], basestring):
             ui.warn(" %r\n" % (inst.args[1],))
@@ -207,13 +217,13 @@
             ui.warn(_(" empty string\n"))
         else:
             ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
-    except error.CensoredNodeError, inst:
+    except error.CensoredNodeError as inst:
         ui.warn(_("abort: file censored %s!\n") % inst)
-    except error.RevlogError, inst:
+    except error.RevlogError as inst:
         ui.warn(_("abort: %s!\n") % inst)
     except error.SignalInterrupt:
         ui.warn(_("killed!\n"))
-    except error.UnknownCommand, inst:
+    except error.UnknownCommand as inst:
         ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
         try:
             # check if the command is in a disabled extension
@@ -229,21 +239,21 @@
                     suggested = True
             if not suggested:
                 commands.help_(ui, 'shortlist')
-    except error.InterventionRequired, inst:
+    except error.InterventionRequired as inst:
         ui.warn("%s\n" % inst)
         return 1
-    except util.Abort, inst:
+    except util.Abort as inst:
         ui.warn(_("abort: %s\n") % inst)
         if inst.hint:
             ui.warn(_("(%s)\n") % inst.hint)
-    except ImportError, inst:
+    except ImportError as inst:
         ui.warn(_("abort: %s!\n") % inst)
         m = str(inst).split()[-1]
         if m in "mpatch bdiff".split():
             ui.warn(_("(did you forget to compile extensions?)\n"))
         elif m in "zlib".split():
             ui.warn(_("(is your Python install correct?)\n"))
-    except IOError, inst:
+    except IOError as inst:
         if util.safehasattr(inst, "code"):
             ui.warn(_("abort: %s\n") % inst)
         elif util.safehasattr(inst, "reason"):
@@ -267,7 +277,7 @@
                 ui.warn(_("abort: %s\n") % inst.strerror)
         else:
             raise
-    except OSError, inst:
+    except OSError as inst:
         if getattr(inst, "filename", None) is not None:
             ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
         else:
@@ -275,7 +285,7 @@
     except KeyboardInterrupt:
         try:
             ui.warn(_("interrupted!\n"))
-        except IOError, inst:
+        except IOError as inst:
             if inst.errno == errno.EPIPE:
                 if ui.debugflag:
                     ui.warn(_("\nbroken pipe\n"))
@@ -283,11 +293,11 @@
                 raise
     except MemoryError:
         ui.warn(_("abort: out of memory\n"))
-    except SystemExit, inst:
+    except SystemExit as inst:
         # Commands shouldn't sys.exit directly, but give a return code.
         # Just in case catch this and and pass exit code to caller.
         return inst.code
-    except socket.error, inst:
+    except socket.error as inst:
         ui.warn(_("abort: %s\n") % inst.args[-1])
     except: # re-raises
         myver = util.version()
@@ -443,7 +453,7 @@
 
         try:
             args = shlex.split(self.definition)
-        except ValueError, inst:
+        except ValueError as inst:
             self.badalias = (_("error in definition for alias '%s': %s")
                              % (self.name, inst))
             return
@@ -534,7 +544,7 @@
 
     try:
         args = fancyopts.fancyopts(args, commands.globalopts, options)
-    except fancyopts.getopt.GetoptError, inst:
+    except fancyopts.getopt.GetoptError as inst:
         raise error.CommandError(None, inst)
 
     if args:
@@ -557,7 +567,7 @@
 
     try:
         args = fancyopts.fancyopts(args, c, cmdoptions, True)
-    except fancyopts.getopt.GetoptError, inst:
+    except fancyopts.getopt.GetoptError as inst:
         raise error.CommandError(cmd, inst)
 
     # separate global options back out
@@ -656,7 +666,7 @@
     """
     try:
         wd = os.getcwd()
-    except OSError, e:
+    except OSError as e:
         raise util.Abort(_("error getting current working directory: %s") %
                          e.strerror)
     path = cmdutil.findrepo(wd) or ""
@@ -891,7 +901,7 @@
     format = ui.config('profiling', 'format', default='text')
     field = ui.config('profiling', 'sort', default='inlinetime')
     limit = ui.configint('profiling', 'limit', default=30)
-    climit = ui.configint('profiling', 'nested', default=5)
+    climit = ui.configint('profiling', 'nested', default=0)
 
     if format not in ['text', 'kcachegrind']:
         ui.warn(_("unrecognized profiling format '%s'"
@@ -921,6 +931,31 @@
             stats.sort(field)
             stats.pprint(limit=limit, file=fp, climit=climit)
 
+def flameprofile(ui, func, fp):
+    try:
+        from flamegraph import flamegraph
+    except ImportError:
+        raise util.Abort(_(
+            'flamegraph not available - install from '
+            'https://github.com/evanhempel/python-flamegraph'))
+    # developer config: profiling.freq
+    freq = ui.configint('profiling', 'freq', default=1000)
+    filter_ = None
+    collapse_recursion = True
+    thread = flamegraph.ProfileThread(fp, 1.0 / freq,
+                                      filter_, collapse_recursion)
+    start_time = time.clock()
+    try:
+        thread.start()
+        func()
+    finally:
+        thread.stop()
+        thread.join()
+        print 'Collected %d stack frames (%d unique) in %2.2f seconds.' % (
+            time.clock() - start_time, thread.num_frames(),
+            thread.num_frames(unique=True))
+
+
 def statprofile(ui, func, fp):
     try:
         import statprof
@@ -952,7 +987,7 @@
         profiler = os.getenv('HGPROF')
         if profiler is None:
             profiler = ui.config('profiling', 'type', default='ls')
-        if profiler not in ('ls', 'stat'):
+        if profiler not in ('ls', 'stat', 'flame'):
             ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
             profiler = 'ls'
 
@@ -967,6 +1002,8 @@
         try:
             if profiler == 'ls':
                 return lsprofile(ui, checkargs, fp)
+            elif profiler == 'flame':
+                return flameprofile(ui, checkargs, fp)
             else:
                 return statprofile(ui, checkargs, fp)
         finally:
--- a/mercurial/encoding.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/encoding.py	Sat Jul 18 17:32:38 2015 -0500
@@ -138,7 +138,7 @@
             except UnicodeDecodeError:
                 u = s.decode("utf-8", "replace") # last ditch
                 return u.encode(encoding, "replace") # can't round-trip
-    except LookupError, k:
+    except LookupError as k:
         raise error.Abort(k, hint="please check your locale settings")
 
 def fromlocal(s):
@@ -158,10 +158,10 @@
 
     try:
         return s.decode(encoding, encodingmode).encode("utf-8")
-    except UnicodeDecodeError, inst:
+    except UnicodeDecodeError as inst:
         sub = s[max(0, inst.start - 10):inst.start + 10]
         raise error.Abort("decoding near '%s': %s!" % (sub, inst))
-    except LookupError, k:
+    except LookupError as k:
         raise error.Abort(k, hint="please check your locale settings")
 
 # How to treat ambiguous-width characters. Set to 'wide' to treat as wide.
@@ -330,7 +330,7 @@
         return lu.encode(encoding)
     except UnicodeError:
         return s.lower() # we don't know how to fold this except in ASCII
-    except LookupError, k:
+    except LookupError as k:
         raise error.Abort(k, hint="please check your locale settings")
 
 def upper(s):
@@ -353,7 +353,7 @@
         return uu.encode(encoding)
     except UnicodeError:
         return s.upper() # we don't know how to fold this except in ASCII
-    except LookupError, k:
+    except LookupError as k:
         raise error.Abort(k, hint="please check your locale settings")
 
 class normcasespecs(object):
--- a/mercurial/error.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/error.py	Sat Jul 18 17:32:38 2015 -0500
@@ -13,7 +13,12 @@
 
 # Do not import anything here, please
 
-class RevlogError(Exception):
+class HintException(Exception):
+    def __init__(self, *args, **kw):
+        Exception.__init__(self, *args)
+        self.hint = kw.get('hint')
+
+class RevlogError(HintException):
     pass
 
 class FilteredIndexError(IndexError):
@@ -46,11 +51,9 @@
 class InterventionRequired(Exception):
     """Exception raised when a command requires human intervention."""
 
-class Abort(Exception):
+class Abort(HintException):
     """Raised if a command needs to print an error and exit."""
-    def __init__(self, *args, **kw):
-        Exception.__init__(self, *args)
-        self.hint = kw.get('hint')
+    pass
 
 class HookAbort(Abort):
     """raised when a validation hook fails, aborting an operation
@@ -64,6 +67,10 @@
 class OutOfBandError(Exception):
     """Exception raised when a remote repo reports failure"""
 
+    def __init__(self, *args, **kw):
+        Exception.__init__(self, *args)
+        self.hint = kw.get('hint')
+
 class ParseError(Exception):
     """Raised when parsing config files and {rev,file}sets (msg[, pos])"""
 
@@ -76,10 +83,8 @@
         self.function = function
         self.symbols = symbols
 
-class RepoError(Exception):
-    def __init__(self, *args, **kw):
-        Exception.__init__(self, *args)
-        self.hint = kw.get('hint')
+class RepoError(HintException):
+    pass
 
 class RepoLookupError(RepoError):
     pass
@@ -146,6 +151,21 @@
     """error raised when code tries to alter a part being generated"""
     pass
 
+class PushkeyFailed(Abort):
+    """error raised when a pushkey part failed to update a value"""
+
+    def __init__(self, partid, namespace=None, key=None, new=None, old=None,
+                 ret=None):
+        self.partid = partid
+        self.namespace = namespace
+        self.key = key
+        self.new = new
+        self.old = old
+        self.ret = ret
+        # no i18n expected to be processed into a better message
+        Abort.__init__(self, 'failed to update value for "%s/%s"'
+                       % (namespace, key))
+
 class CensoredNodeError(RevlogError):
     """error raised when content verification fails on a censored node
 
--- a/mercurial/exchange.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/exchange.py	Sat Jul 18 17:32:38 2015 -0500
@@ -5,12 +5,14 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+import time
 from i18n import _
 from node import hex, nullid
 import errno, urllib
-import util, scmutil, changegroup, base85, error
+import util, scmutil, changegroup, base85, error, store
 import discovery, phases, obsolete, bookmarks as bookmod, bundle2, pushkey
 import lock as lockmod
+import tags
 
 def readbundle(ui, fh, fname, vfs=None):
     header = changegroup.readexactly(fh, 4)
@@ -34,7 +36,7 @@
             alg = changegroup.readexactly(fh, 2)
         return changegroup.cg1unpacker(fh, alg)
     elif version.startswith('2'):
-        return bundle2.getunbundler(ui, fh, header=magic + version)
+        return bundle2.getunbundler(ui, fh, magicstring=magic + version)
     else:
         raise util.Abort(_('%s: unknown bundle version %s') % (fname, version))
 
@@ -57,7 +59,7 @@
     """return true if a pull/push can use bundle2
 
     Feel free to nuke this function when we drop the experimental option"""
-    return (op.repo.ui.configbool('experimental', 'bundle2-exp', False)
+    return (op.repo.ui.configbool('experimental', 'bundle2-exp', True)
             and op.remote.capable('bundle2'))
 
 
@@ -115,6 +117,9 @@
         self.outbookmarks = []
         # transaction manager
         self.trmanager = None
+        # map { pushkey partid -> callback handling failure}
+        # used to handle exception from mandatory pushkey part failure
+        self.pkfailcb = {}
 
     @util.propertycache
     def futureheads(self):
@@ -210,7 +215,7 @@
             localwlock = pushop.repo.wlock()
         locallock = pushop.repo.lock()
         pushop.locallocked = True
-    except IOError, err:
+    except IOError as err:
         pushop.locallocked = False
         if err.errno != errno.EACCES:
             raise
@@ -304,6 +309,20 @@
     unfi = pushop.repo.unfiltered()
     remotephases = pushop.remote.listkeys('phases')
     publishing = remotephases.get('publishing', False)
+    if (pushop.ui.configbool('ui', '_usedassubrepo', False)
+        and remotephases    # server supports phases
+        and not pushop.outgoing.missing # no changesets to be pushed
+        and publishing):
+        # 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 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)
@@ -419,6 +438,8 @@
                     raise util.Abort(mso % ctx)
                 elif ctx.troubled():
                     raise util.Abort(mst[ctx.troubles()[0]] % ctx)
+
+        # internal config: bookmarks.pushing
         newbm = pushop.ui.configlist('bookmarks', 'pushing')
         discovery.checkheads(unfi, pushop.remote, outgoing,
                              pushop.remoteheads,
@@ -505,6 +526,13 @@
         return
     pushop.stepsdone.add('phases')
     part2node = []
+
+    def handlefailure(pushop, exc):
+        targetid = int(exc.partid)
+        for partid, node in part2node:
+            if partid == targetid:
+                raise error.Abort(_('updating %s to public failed') % node)
+
     enc = pushkey.encode
     for newremotehead in pushop.outdatedphases:
         part = bundler.newpart('pushkey')
@@ -513,6 +541,8 @@
         part.addparam('old', enc(str(phases.draft)))
         part.addparam('new', enc(str(phases.public)))
         part2node.append((part.id, newremotehead))
+        pushop.pkfailcb[part.id] = handlefailure
+
     def handlereply(op):
         for partid, node in part2node:
             partrep = op.records.getreplies(partid)
@@ -536,7 +566,8 @@
         return
     pushop.stepsdone.add('obsmarkers')
     if pushop.outobsmarkers:
-        buildobsmarkerspart(bundler, pushop.outobsmarkers)
+        markers = sorted(pushop.outobsmarkers)
+        buildobsmarkerspart(bundler, markers)
 
 @b2partsgenerator('bookmarks')
 def _pushb2bookmarks(pushop, bundler):
@@ -549,6 +580,15 @@
     pushop.stepsdone.add('bookmarks')
     part2book = []
     enc = pushkey.encode
+
+    def handlefailure(pushop, exc):
+        targetid = int(exc.partid)
+        for partid, book, action in part2book:
+            if partid == targetid:
+                raise error.Abort(bookmsgmap[action][1].rstrip() % book)
+        # we should not be called for part we did not generated
+        assert False
+
     for book, old, new in pushop.outbookmarks:
         part = bundler.newpart('pushkey')
         part.addparam('namespace', enc('bookmarks'))
@@ -561,7 +601,7 @@
         elif not new:
             action = 'delete'
         part2book.append((part.id, book, action))
-
+        pushop.pkfailcb[part.id] = handlefailure
 
     def handlereply(op):
         ui = pushop.ui
@@ -606,16 +646,22 @@
         return
     stream = util.chunkbuffer(bundler.getchunks())
     try:
-        reply = pushop.remote.unbundle(stream, ['force'], 'push')
-    except error.BundleValueError, exc:
-        raise util.Abort('missing support for %s' % exc)
-    try:
-        trgetter = None
-        if pushback:
-            trgetter = pushop.trmanager.transaction
-        op = bundle2.processbundle(pushop.repo, reply, trgetter)
-    except error.BundleValueError, exc:
-        raise util.Abort('missing support for %s' % exc)
+        try:
+            reply = pushop.remote.unbundle(stream, ['force'], 'push')
+        except error.BundleValueError as exc:
+            raise util.Abort('missing support for %s' % exc)
+        try:
+            trgetter = None
+            if pushback:
+                trgetter = pushop.trmanager.transaction
+            op = bundle2.processbundle(pushop.repo, reply, trgetter)
+        except error.BundleValueError as exc:
+            raise util.Abort('missing support for %s' % exc)
+    except error.PushkeyFailed as exc:
+        partid = int(exc.partid)
+        if partid not in pushop.pkfailcb:
+            raise
+        pushop.pkfailcb[partid](pushop, exc)
     for rephand in replyhandlers:
         rephand(op)
 
@@ -745,13 +791,13 @@
     """utility function to push obsolete markers to a remote"""
     if 'obsmarkers' in pushop.stepsdone:
         return
-    pushop.ui.debug('try to push obsolete markers to remote\n')
     repo = pushop.repo
     remote = pushop.remote
     pushop.stepsdone.add('obsmarkers')
     if pushop.outobsmarkers:
+        pushop.ui.debug('try to push obsolete markers to remote\n')
         rslts = []
-        remotedata = obsolete._pushkeyescape(pushop.outobsmarkers)
+        remotedata = obsolete._pushkeyescape(sorted(pushop.outobsmarkers))
         for key in sorted(remotedata, reverse=True):
             # reverse sort to ensure we end with dump0
             data = remotedata[key]
@@ -791,7 +837,8 @@
     afterward.
     """
 
-    def __init__(self, repo, remote, heads=None, force=False, bookmarks=()):
+    def __init__(self, repo, remote, heads=None, force=False, bookmarks=(),
+                 remotebookmarks=None):
         # repo we pull into
         self.repo = repo
         # repo we pull from
@@ -811,7 +858,7 @@
         # list of missing changeset to fetch remotely
         self.fetch = None
         # remote bookmarks data
-        self.remotebookmarks = None
+        self.remotebookmarks = remotebookmarks
         # result of changegroup pulling (used as return code by pull)
         self.cgresult = None
         # list of step already done
@@ -869,8 +916,11 @@
         if self._tr is not None:
             self._tr.release()
 
-def pull(repo, remote, heads=None, force=False, bookmarks=()):
-    pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks)
+def pull(repo, remote, heads=None, force=False, bookmarks=(), opargs=None):
+    if opargs is None:
+        opargs = {}
+    pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks,
+                           **opargs)
     if pullop.remote.local():
         missing = set(pullop.remote.requirements) - pullop.repo.supported
         if missing:
@@ -879,7 +929,6 @@
                     " %s") % (', '.join(sorted(missing)))
             raise util.Abort(msg)
 
-    pullop.remotebookmarks = remote.listkeys('bookmarks')
     lock = pullop.repo.lock()
     try:
         pullop.trmanager = transactionmanager(repo, 'pull', remote.url())
@@ -927,6 +976,22 @@
         step = pulldiscoverymapping[stepname]
         step(pullop)
 
+@pulldiscovery('b1:bookmarks')
+def _pullbookmarkbundle1(pullop):
+    """fetch bookmark data in bundle1 case
+
+    If not using bundle2, we have to fetch bookmarks before changeset
+    discovery to reduce the chance and impact of race conditions."""
+    if pullop.remotebookmarks is not None:
+        return
+    if (_canusebundle2(pullop)
+            and 'listkeys' in bundle2.bundle2caps(pullop.remote)):
+        # all known bundle2 servers now support listkeys, but lets be nice with
+        # new implementation.
+        return
+    pullop.remotebookmarks = pullop.remote.listkeys('bookmarks')
+
+
 @pulldiscovery('changegroup')
 def _pulldiscoverychangegroup(pullop):
     """discovery phase for the pull
@@ -978,7 +1043,11 @@
     kwargs['heads'] = pullop.heads or pullop.rheads
     kwargs['cg'] = pullop.fetch
     if 'listkeys' in remotecaps:
-        kwargs['listkeys'] = ['phase', 'bookmarks']
+        kwargs['listkeys'] = ['phase']
+        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')
     if not pullop.fetch:
         pullop.repo.ui.status(_("no changes found\n"))
         pullop.cgresult = 0
@@ -994,7 +1063,7 @@
     bundle = pullop.remote.getbundle('pull', **kwargs)
     try:
         op = bundle2.processbundle(pullop.repo, bundle, pullop.gettransaction)
-    except error.BundleValueError, exc:
+    except error.BundleValueError as exc:
         raise util.Abort('missing support for %s' % exc)
 
     if pullop.fetch:
@@ -1010,7 +1079,10 @@
     for namespace, value in op.records['listkeys']:
         if namespace == 'bookmarks':
             pullop.remotebookmarks = value
-            _pullbookmarks(pullop)
+
+    # bookmark data were either already there or pulled in the bundle
+    if pullop.remotebookmarks is not None:
+        _pullbookmarks(pullop)
 
 def _pullbundle2extraprepare(pullop, kwargs):
     """hook function so that extensions can extend the getbundle call"""
@@ -1180,7 +1252,7 @@
     # bundle10 case
     usebundle2 = False
     if bundlecaps is not None:
-        usebundle2 = util.any((cap.startswith('HG2') for cap in bundlecaps))
+        usebundle2 = any((cap.startswith('HG2') for cap in bundlecaps))
     if not usebundle2:
         if bundlecaps and not kwargs.get('cg', True):
             raise ValueError(_('request for bundle10 must include changegroup'))
@@ -1218,24 +1290,22 @@
         # build changegroup bundle here.
         version = None
         cgversions = b2caps.get('changegroup')
-        if not cgversions:  # 3.1 and 3.2 ship with an empty value
-            cg = changegroup.getchangegroupraw(repo, source, heads=heads,
-                                               common=common,
-                                               bundlecaps=bundlecaps)
-        else:
+        getcgkwargs = {}
+        if cgversions:  # 3.1 and 3.2 ship with an empty value
             cgversions = [v for v in cgversions if v in changegroup.packermap]
             if not cgversions:
                 raise ValueError(_('no common changegroup version'))
-            version = max(cgversions)
-            cg = changegroup.getchangegroupraw(repo, source, heads=heads,
-                                               common=common,
-                                               bundlecaps=bundlecaps,
-                                               version=version)
+            version = getcgkwargs['version'] = max(cgversions)
+        outgoing = changegroup.computeoutgoing(repo, heads, common)
+        cg = changegroup.getlocalchangegroupraw(repo, source, outgoing,
+                                                bundlecaps=bundlecaps,
+                                                **getcgkwargs)
 
     if cg:
         part = bundler.newpart('changegroup', data=cg)
         if version is not None:
             part.addparam('version', version)
+        part.addparam('nbchanges', str(len(outgoing.missing)), mandatory=False)
 
 @getbundle2partsgenerator('listkeys')
 def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None,
@@ -1257,8 +1327,52 @@
             heads = repo.heads()
         subset = [c.node() for c in repo.set('::%ln', heads)]
         markers = repo.obsstore.relevantmarkers(subset)
+        markers = sorted(markers)
         buildobsmarkerspart(bundler, markers)
 
+@getbundle2partsgenerator('hgtagsfnodes')
+def _getbundletagsfnodes(bundler, repo, source, bundlecaps=None,
+                         b2caps=None, heads=None, common=None,
+                         **kwargs):
+    """Transfer the .hgtags filenodes mapping.
+
+    Only values for heads in this bundle will be transferred.
+
+    The part data consists of pairs of 20 byte changeset node and .hgtags
+    filenodes raw values.
+    """
+    # Don't send unless:
+    # - changeset are being exchanged,
+    # - the client supports it.
+    if not (kwargs.get('cg', True) and 'hgtagsfnodes' in b2caps):
+        return
+
+    outgoing = changegroup.computeoutgoing(repo, heads, common)
+
+    if not outgoing.missingheads:
+        return
+
+    cache = tags.hgtagsfnodescache(repo.unfiltered())
+    chunks = []
+
+    # .hgtags fnodes are only relevant for head changesets. While we could
+    # transfer values for all known nodes, there will likely be little to
+    # no benefit.
+    #
+    # We don't bother using a generator to produce output data because
+    # a) we only have 40 bytes per head and even esoteric numbers of heads
+    # consume little memory (1M heads is 40MB) b) we don't want to send the
+    # part if we don't have entries and knowing if we have entries requires
+    # cache lookups.
+    for node in outgoing.missingheads:
+        # Don't compute missing, as this may slow down serving.
+        fnode = cache.getfnode(node, computemissing=False)
+        if fnode is not None:
+            chunks.extend([node, fnode])
+
+    if chunks:
+        bundler.newpart('hgtagsfnodes', data=''.join(chunks))
+
 def check_heads(repo, their_heads, context):
     """check if the heads of a repo have been modified
 
@@ -1288,7 +1402,7 @@
     # quick fix for output mismatch with bundle2 in 3.4
     captureoutput = repo.ui.configbool('experimental', 'bundle2-output-capture',
                                        False)
-    if url.startswith('remote:'):
+    if url.startswith('remote:http:') or url.startswith('remote:https:'):
         captureoutput = True
     try:
         check_heads(repo, heads, 'uploading changes')
@@ -1313,7 +1427,7 @@
                         def recordout(output):
                             r.newpart('output', data=output, mandatory=False)
                 tr.close()
-            except Exception, exc:
+            except BaseException as exc:
                 exc.duringunbundle2 = True
                 if captureoutput and r is not None:
                     parts = exc._bundle2salvagedoutput = r.salvageoutput()
@@ -1330,3 +1444,131 @@
         if recordout is not None:
             recordout(repo.ui.popbuffer())
     return r
+
+# This is it's own function so extensions can override it.
+def _walkstreamfiles(repo):
+    return repo.store.walk()
+
+def generatestreamclone(repo):
+    """Emit content for a streaming clone.
+
+    This is a generator of raw chunks that constitute a streaming clone.
+
+    The stream begins with a line of 2 space-delimited integers containing the
+    number of entries and total bytes size.
+
+    Next, are N entries for each file being transferred. Each file entry starts
+    as a line with the file name and integer size delimited by a null byte.
+    The raw file data follows. Following the raw file data is the next file
+    entry, or EOF.
+
+    When used on the wire protocol, an additional line indicating protocol
+    success will be prepended to the stream. This function is not responsible
+    for adding it.
+
+    This function will obtain a repository lock to ensure a consistent view of
+    the store is captured. It therefore may raise LockError.
+    """
+    entries = []
+    total_bytes = 0
+    # Get consistent snapshot of repo, lock during scan.
+    lock = repo.lock()
+    try:
+        repo.ui.debug('scanning\n')
+        for name, ename, size in _walkstreamfiles(repo):
+            if size:
+                entries.append((name, size))
+                total_bytes += size
+    finally:
+            lock.release()
+
+    repo.ui.debug('%d files, %d bytes to transfer\n' %
+                  (len(entries), total_bytes))
+    yield '%d %d\n' % (len(entries), total_bytes)
+
+    svfs = repo.svfs
+    oldaudit = svfs.mustaudit
+    debugflag = repo.ui.debugflag
+    svfs.mustaudit = False
+
+    try:
+        for name, size in entries:
+            if debugflag:
+                repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
+            # partially encode name over the wire for backwards compat
+            yield '%s\0%d\n' % (store.encodedir(name), size)
+            if size <= 65536:
+                fp = svfs(name)
+                try:
+                    data = fp.read(size)
+                finally:
+                    fp.close()
+                yield data
+            else:
+                for chunk in util.filechunkiter(svfs(name), limit=size):
+                    yield chunk
+    finally:
+        svfs.mustaudit = oldaudit
+
+def consumestreamclone(repo, fp):
+    """Apply the contents from a streaming clone file.
+
+    This takes the output from "streamout" and applies it to the specified
+    repository.
+
+    Like "streamout," the status line added by the wire protocol is not handled
+    by this function.
+    """
+    lock = repo.lock()
+    try:
+        repo.ui.status(_('streaming all changes\n'))
+        l = fp.readline()
+        try:
+            total_files, total_bytes = map(int, l.split(' ', 1))
+        except (ValueError, TypeError):
+            raise error.ResponseError(
+                _('unexpected response from remote server:'), l)
+        repo.ui.status(_('%d files to transfer, %s of data\n') %
+                       (total_files, util.bytecount(total_bytes)))
+        handled_bytes = 0
+        repo.ui.progress(_('clone'), 0, total=total_bytes)
+        start = time.time()
+
+        tr = repo.transaction(_('clone'))
+        try:
+            for i in xrange(total_files):
+                # XXX doesn't support '\n' or '\r' in filenames
+                l = fp.readline()
+                try:
+                    name, size = l.split('\0', 1)
+                    size = int(size)
+                except (ValueError, TypeError):
+                    raise error.ResponseError(
+                        _('unexpected response from remote server:'), l)
+                if repo.ui.debugflag:
+                    repo.ui.debug('adding %s (%s)\n' %
+                                  (name, util.bytecount(size)))
+                # for backwards compat, name was partially encoded
+                ofp = repo.svfs(store.decodedir(name), 'w')
+                for chunk in util.filechunkiter(fp, limit=size):
+                    handled_bytes += len(chunk)
+                    repo.ui.progress(_('clone'), handled_bytes,
+                                     total=total_bytes)
+                    ofp.write(chunk)
+                ofp.close()
+            tr.close()
+        finally:
+            tr.release()
+
+        # Writing straight to files circumvented the inmemory caches
+        repo.invalidate()
+
+        elapsed = time.time() - start
+        if elapsed <= 0:
+            elapsed = 0.001
+        repo.ui.progress(_('clone'), None)
+        repo.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
+                       (util.bytecount(total_bytes), elapsed,
+                        util.bytecount(total_bytes / elapsed)))
+    finally:
+        lock.release()
--- a/mercurial/extensions.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/extensions.py	Sat Jul 18 17:32:38 2015 -0500
@@ -53,7 +53,7 @@
     else:
         try:
             return imp.load_source(module_name, path)
-        except IOError, exc:
+        except IOError as exc:
             if not exc.filename:
                 exc.filename = path # python does not fill this
             raise
@@ -82,9 +82,11 @@
             return mod
         try:
             mod = importh("hgext.%s" % name)
-        except ImportError, err:
+        except ImportError as err:
             ui.debug('could not import hgext.%s (%s): trying %s\n'
                      % (name, err, name))
+            if ui.debugflag:
+                ui.traceback()
             mod = importh(name)
     _extensions[shortname] = mod
     _order.append(shortname)
@@ -103,13 +105,14 @@
             load(ui, name, path)
         except KeyboardInterrupt:
             raise
-        except Exception, inst:
+        except Exception as inst:
             if path:
                 ui.warn(_("*** failed to import extension %s from %s: %s\n")
                         % (name, path, inst))
             else:
                 ui.warn(_("*** failed to import extension %s: %s\n")
                         % (name, inst))
+            ui.traceback()
 
     for name in _order[newindex:]:
         uisetup = getattr(_extensions[name], 'uisetup', None)
--- a/mercurial/fancyopts.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/fancyopts.py	Sat Jul 18 17:32:38 2015 -0500
@@ -103,8 +103,9 @@
     # transfer result to state
     for opt, val in opts:
         name = argmap[opt]
-        t = type(defmap[name])
-        if t is type(fancyopts):
+        obj = defmap[name]
+        t = type(obj)
+        if callable(obj):
             state[name] = defmap[name](val)
         elif t is type(1):
             try:
--- a/mercurial/filemerge.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/filemerge.py	Sat Jul 18 17:32:38 2015 -0500
@@ -75,6 +75,7 @@
             return True
         return False
 
+    # internal config: ui.forcemerge
     # forcemerge comes from command line arguments, highest priority
     force = ui.config('ui', 'forcemerge')
     if force:
@@ -354,7 +355,6 @@
 
     ui = repo.ui
     template = ui.config('ui', 'mergemarkertemplate', _defaultconflictmarker)
-    template = templater.parsestring(template, quoted=False)
     tmpl = templater.templater(None, cache={'conflictmarker': template})
 
     pad = max(len(l) for l in labels)
--- a/mercurial/fileset.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/fileset.py	Sat Jul 18 17:32:38 2015 -0500
@@ -10,20 +10,21 @@
 from i18n import _
 
 elements = {
-    "(": (20, ("group", 1, ")"), ("func", 1, ")")),
-    "-": (5, ("negate", 19), ("minus", 5)),
-    "not": (10, ("not", 10)),
-    "!": (10, ("not", 10)),
-    "and": (5, None, ("and", 5)),
-    "&": (5, None, ("and", 5)),
-    "or": (4, None, ("or", 4)),
-    "|": (4, None, ("or", 4)),
-    "+": (4, None, ("or", 4)),
-    ",": (2, None, ("list", 2)),
-    ")": (0, None, None),
-    "symbol": (0, ("symbol",), None),
-    "string": (0, ("string",), None),
-    "end": (0, None, None),
+    # token-type: binding-strength, primary, prefix, infix, suffix
+    "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
+    "-": (5, None, ("negate", 19), ("minus", 5), None),
+    "not": (10, None, ("not", 10), None, None),
+    "!": (10, None, ("not", 10), None, None),
+    "and": (5, None, None, ("and", 5), None),
+    "&": (5, None, None, ("and", 5), None),
+    "or": (4, None, None, ("or", 4), None),
+    "|": (4, None, None, ("or", 4), None),
+    "+": (4, None, None, ("or", 4), None),
+    ",": (2, None, None, ("list", 2), None),
+    ")": (0, None, None, None, None),
+    "symbol": (0, "symbol", None, None, None),
+    "string": (0, "string", None, None, None),
+    "end": (0, None, None, None, None),
 }
 
 keywords = set(['and', 'or', 'not'])
@@ -80,8 +81,11 @@
     yield ('end', None, pos)
 
 def parse(expr):
-    p = parser.parser(tokenize, elements)
-    return p.parse(expr)
+    p = parser.parser(elements)
+    tree, pos = p.parse(tokenize(expr))
+    if pos != len(expr):
+        raise error.ParseError(_("invalid token"), pos)
+    return tree
 
 def getstring(x, err):
     if x and (x[0] == 'string' or x[0] == 'symbol'):
@@ -186,7 +190,11 @@
 def func(mctx, a, b):
     if a[0] == 'symbol' and a[1] in symbols:
         return symbols[a[1]](mctx, b)
-    raise error.UnknownIdentifier(a[1], symbols.keys())
+
+    keep = lambda fn: getattr(fn, '__doc__', None) is not None
+
+    syms = [s for (s, fn) in symbols.items() if keep(fn)]
+    raise error.UnknownIdentifier(a[1], syms)
 
 def getlist(x):
     if not x:
@@ -273,7 +281,7 @@
     try:
         # i18n: "grep" is a keyword
         r = re.compile(getstring(x, _("grep requires a pattern")))
-    except re.error, e:
+    except re.error as e:
         raise error.ParseError(_('invalid match pattern: %s') % e)
     return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
 
@@ -491,9 +499,7 @@
 ]
 
 def getfileset(ctx, expr):
-    tree, pos = parse(expr)
-    if (pos != len(expr)):
-        raise error.ParseError(_("invalid token"), pos)
+    tree = parse(expr)
 
     # do we need status info?
     if (_intree(['modified', 'added', 'removed', 'deleted',
@@ -516,5 +522,8 @@
 
     return getset(matchctx(ctx, subset, status), tree)
 
+def prettyformat(tree):
+    return parser.prettyformat(tree, ('string', 'symbol'))
+
 # tell hggettext to extract docstrings from these functions:
 i18nfunctions = symbols.values()
--- a/mercurial/formatter.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/formatter.py	Sat Jul 18 17:32:38 2015 -0500
@@ -9,6 +9,8 @@
 from node import hex, short
 from i18n import _
 import encoding, util
+import templater
+import os
 
 class baseformatter(object):
     def __init__(self, ui, topic, opts):
@@ -133,6 +135,58 @@
         baseformatter.end(self)
         self._ui.write("\n]\n")
 
+class templateformatter(baseformatter):
+    def __init__(self, ui, topic, opts):
+        baseformatter.__init__(self, ui, topic, opts)
+        self._topic = topic
+        self._t = gettemplater(ui, topic, opts.get('template', ''))
+    def _showitem(self):
+        g = self._t(self._topic, **self._item)
+        self._ui.write(templater.stringify(g))
+
+def lookuptemplate(ui, topic, tmpl):
+    # looks like a literal template?
+    if '{' in tmpl:
+        return tmpl, None
+
+    # perhaps a stock style?
+    if not os.path.split(tmpl)[0]:
+        mapname = (templater.templatepath('map-cmdline.' + tmpl)
+                   or templater.templatepath(tmpl))
+        if mapname and os.path.isfile(mapname):
+            return None, mapname
+
+    # perhaps it's a reference to [templates]
+    t = ui.config('templates', tmpl)
+    if t:
+        try:
+            tmpl = templater.unquotestring(t)
+        except SyntaxError:
+            tmpl = t
+        return tmpl, None
+
+    if tmpl == 'list':
+        ui.write(_("available styles: %s\n") % templater.stylelist())
+        raise util.Abort(_("specify a template"))
+
+    # perhaps it's a path to a map or a template
+    if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
+        # is it a mapfile for a style?
+        if os.path.basename(tmpl).startswith("map-"):
+            return None, os.path.realpath(tmpl)
+        tmpl = open(tmpl).read()
+        return tmpl, None
+
+    # constant string?
+    return tmpl, None
+
+def gettemplater(ui, topic, spec):
+    tmpl, mapfile = lookuptemplate(ui, topic, spec)
+    t = templater.templater(mapfile, {})
+    if tmpl:
+        t.cache[topic] = tmpl
+    return t
+
 def formatter(ui, topic, opts):
     template = opts.get("template", "")
     if template == "json":
@@ -142,9 +196,11 @@
     elif template == "debug":
         return debugformatter(ui, topic, opts)
     elif template != "":
-        raise util.Abort(_("custom templates not yet supported"))
+        return templateformatter(ui, topic, opts)
+    # developer config: ui.formatdebug
     elif ui.configbool('ui', 'formatdebug'):
         return debugformatter(ui, topic, opts)
+    # deprecated config: ui.formatjson
     elif ui.configbool('ui', 'formatjson'):
         return jsonformatter(ui, topic, opts)
     return plainformatter(ui, topic, opts)
--- a/mercurial/hbisect.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/hbisect.py	Sat Jul 18 17:32:38 2015 -0500
@@ -8,6 +8,7 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+import collections
 import os
 import error
 from i18n import _
@@ -71,7 +72,7 @@
 
     # build children dict
     children = {}
-    visit = util.deque([badrev])
+    visit = collections.deque([badrev])
     candidates = []
     while visit:
         rev = visit.popleft()
--- a/mercurial/help.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/help.py	Sat Jul 18 17:32:38 2015 -0500
@@ -228,7 +228,7 @@
         try:
             aliases, entry = cmdutil.findcmd(name, commands.table,
                                              strict=unknowncmd)
-        except error.AmbiguousCommand, inst:
+        except error.AmbiguousCommand as inst:
             # py3k fix: except vars can't be used outside the scope of the
             # except block, nor can be used inside a lambda. python issue4617
             prefix = inst.args[0]
--- a/mercurial/help/config.txt	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/help/config.txt	Sat Jul 18 17:32:38 2015 -0500
@@ -376,8 +376,8 @@
         HG: --
         HG: user: {author}\n{ifeq(p2rev, "-1", "",
        "HG: branch merge\n")
-       }HG: branch '{branch}'\n{if(currentbookmark,
-       "HG: bookmark '{currentbookmark}'\n")  }{subrepos %
+       }HG: branch '{branch}'\n{if(activebookmark,
+       "HG: bookmark '{activebookmark}'\n")   }{subrepos %
        "HG: subrepo {subrepo}\n"              }{file_adds %
        "HG: added {file}\n"                   }{file_mods %
        "HG: changed {file}\n"                 }{file_dels %
@@ -641,8 +641,8 @@
 Example for ``~/.hgrc``::
 
   [extensions]
-  # (the progress extension will get loaded from Mercurial's path)
-  progress =
+  # (the color extension will get loaded from Mercurial's path)
+  color =
   # (this extension will get loaded from the file specified)
   myfeature = ~/.hgext/myfeature.py
 
@@ -1093,6 +1093,11 @@
     of line, patch line endings are preserved.
     Default: strict.
 
+``fuzz``
+    The number of lines of 'fuzz' to allow when applying patches. This
+    controls how much context the patcher is allowed to ignore when
+    trying to apply a patch.
+    Default: 2
 
 ``paths``
 ---------
@@ -1214,6 +1219,47 @@
     Specific to the ``ls`` instrumenting profiler.
     Default: 5.
 
+``progress``
+------------
+
+Mercurial commands can draw progress bars that are as informative as
+possible. Some progress bars only offer indeterminate information, while others
+have a definite end point.
+
+``delay``
+    Number of seconds (float) before showing the progress bar. (default: 3)
+
+``changedelay``
+    Minimum delay before showing a new topic. When set to less than 3 * refresh,
+    that value will be used instead. (default: 1)
+
+``refresh``
+    Time in seconds between refreshes of the progress bar. (default: 0.1)
+
+``format``
+    Format of the progress bar.
+
+    Valid entries for the format field are ``topic``, ``bar``, ``number``,
+    ``unit``, ``estimate``, speed, and item. item defaults to the last 20
+    characters of the item, but this can be changed by adding either ``-<num>``
+    which would take the last num characters, or ``+<num>`` for the first num
+    characters.
+
+    (default: Topic bar number estimate)
+
+``width``
+    If set, the maximum width of the progress information (that is, min(width,
+    term width) will be used)
+
+``clear-complete``
+    clear the progress bar after it's done (default to True)
+
+``disable``
+    If true, don't show a progress bar
+
+``assume-tty``
+    If true, ALWAYS show a progress bar, unless disable is given
+
 ``revsetalias``
 ---------------
 
@@ -1245,6 +1291,10 @@
     checking that all new file revisions specified in manifests are
     present. Default is False.
 
+``maxhttpheaderlen``
+    Instruct HTTP clients not to send request headers longer than this
+    many bytes. Default is 1024.
+
 ``smtp``
 --------
 
@@ -1405,6 +1455,19 @@
     markers is different from the encoding of the merged files,
     serious problems may occur.
 
+``patch``
+    An optional external tool that ``hg import`` and some extensions
+    will use for applying patches. By default Mercurial uses an
+    internal patch utility. The external tool must work as the common
+    Unix ``patch`` program. In particular, it must accept a ``-p``
+    argument to strip patch headers, a ``-d`` argument to specify the
+    current directory, a file name to patch, and a patch file to take
+    from stdin.
+
+    It is possible to specify a patch tool together with extra
+    arguments. For example, setting this option to ``patch --merge``
+    will use the ``patch`` program with its 2-way merge option.
+
 ``portablefilenames``
     Check for portable filenames. Can be ``warn``, ``ignore`` or ``abort``.
     Default is ``warn``.
@@ -1423,10 +1486,6 @@
 ``remotecmd``
     remote command to use for clone/push/pull operations. Default is ``hg``.
 
-``reportoldssl``
-    Warn if an SSL certificate is unable to be used due to using Python
-    2.5 or earlier. True or False. Default is True.
-
 ``report_untrusted``
     Warn if a ``.hg/hgrc`` file is ignored due to not being owned by a
     trusted user or group. True or False. Default is True.
@@ -1585,6 +1644,9 @@
 ``cache``
     Whether to support caching in hgweb. Defaults to True.
 
+``certificate``
+    Certificate to use when running :hg:`serve`.
+
 ``collapse``
     With ``descend`` enabled, repositories in subdirectories are shown at
     a single level alongside repositories in the current path. With
--- a/mercurial/help/hgignore.txt	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/help/hgignore.txt	Sat Jul 18 17:32:38 2015 -0500
@@ -68,6 +68,10 @@
 and a regexp pattern of the form ``\.c$`` will do the same. To root a
 regexp pattern, start it with ``^``.
 
+Subdirectories can have their own .hgignore settings by adding
+``subinclude:path/to/subdir/.hgignore`` to the root ``.hgignore``. See
+:hg:`help patterns` for details on ``subinclude:`` and ``include:``.
+
 .. note::
 
   Patterns specified in other than ``.hgignore`` are always rooted.
--- a/mercurial/help/patterns.txt	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/help/patterns.txt	Sat Jul 18 17:32:38 2015 -0500
@@ -30,6 +30,12 @@
 feeds. Each string read from the file is itself treated as a file
 pattern.
 
+To read a set of patterns from a file, use ``include:`` or ``subinclude:``.
+``include:`` will use all the patterns from the given file and treat them as if
+they had been passed in manually.  ``subinclude:`` will only apply the patterns
+against files that are under the subinclude file's directory. See :hg:`help
+hgignore` for details on the format of these files.
+
 All patterns, except for ``glob:`` specified in command line (not for
 ``-I`` or ``-X`` options), can match also against directories: files
 under matched directories are treated as matched.
@@ -60,3 +66,9 @@
   listfile0:list.txt read list from list.txt with null byte delimiters
 
 See also :hg:`help filesets`.
+
+Include examples::
+
+  include:path/to/mypatternfile    reads patterns to be applied to all paths
+  subinclude:path/to/subignorefile reads patterns specifically for paths in the
+                                   subdirectory
--- a/mercurial/help/subrepos.txt	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/help/subrepos.txt	Sat Jul 18 17:32:38 2015 -0500
@@ -109,8 +109,10 @@
     elements. Subversion subrepositories are currently silently ignored.
 
 :files: files does not recurse into subrepos unless -S/--subrepos is
-    specified.  Git and Subversion subrepositories are currently
-    silently ignored.
+    specified.  However, if you specify the full path of a file or
+    directory in a subrepo, it will be displayed even without
+    -S/--subrepos being specified.  Git and Subversion subrepositories
+    are currently silently ignored.
 
 :forget: forget currently only handles exact file matches in subrepos.
     Git and Subversion subrepositories are currently silently ignored.
--- a/mercurial/help/templates.txt	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/help/templates.txt	Sat Jul 18 17:32:38 2015 -0500
@@ -47,6 +47,10 @@
 
 - expr % "{template}"
 
+As seen in the above example, "{template}" is interpreted as a template.
+To prevent it from being interpreted, you can use an escape character "\{"
+or a raw string prefix, "r'...'".
+
 Some sample command line templates:
 
 - Format lists, e.g. files::
@@ -67,7 +71,7 @@
 
 - Output the description set to a fill-width of 30::
 
-   $ hg log -r 0 --template "{fill(desc, '30')}"
+   $ hg log -r 0 --template "{fill(desc, 30)}"
 
 - Use a conditional to test for the default branch::
 
@@ -90,9 +94,9 @@
 
    $ hg log -r 0 --template "{join(extras, '\n')}\n"
 
-- Mark the current bookmark with '*'::
+- Mark the active bookmark with '*'::
 
-   $ hg log --template "{bookmarks % '{bookmark}{ifeq(bookmark, current, \"*\")} '}\n"
+   $ hg log --template "{bookmarks % '{bookmark}{ifeq(bookmark, active, '*')} '}\n"
 
 - Mark the working copy parent with '@'::
 
@@ -100,8 +104,8 @@
 
 - Show only commit descriptions that start with "template"::
 
-   $ hg log --template "{startswith(\"template\", firstline(desc))}\n"
+   $ hg log --template "{startswith('template', firstline(desc))}\n"
 
 - Print the first word of each line of a commit message::
 
-   $ hg log --template "{word(\"0\", desc)}\n"
+   $ hg log --template "{word(0, desc)}\n"
--- a/mercurial/hg.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/hg.py	Sat Jul 18 17:32:38 2015 -0500
@@ -92,6 +92,10 @@
     try:
         return thing(path)
     except TypeError:
+        # we can't test callable(thing) because 'thing' can be an unloaded
+        # module that implements __call__
+        if not util.safehasattr(thing, 'instance'):
+            raise
         return thing
 
 def islocal(repo):
@@ -198,7 +202,7 @@
     requirements = ''
     try:
         requirements = srcrepo.vfs.read('requires')
-    except IOError, inst:
+    except IOError as inst:
         if inst.errno != errno.ENOENT:
             raise
 
@@ -249,7 +253,7 @@
                 closetopic[0] = topic
             else:
                 ui.progress(topic, pos + num)
-        srcpublishing = srcrepo.ui.configbool('phases', 'publish', True)
+        srcpublishing = srcrepo.publishing()
         srcvfs = scmutil.vfs(srcrepo.sharedpath)
         dstvfs = scmutil.vfs(destpath)
         for f in srcrepo.store.copylist():
@@ -280,8 +284,50 @@
         release(destlock)
         raise
 
+def clonewithshare(ui, peeropts, sharepath, source, srcpeer, dest, pull=False,
+                   rev=None, update=True, stream=False):
+    """Perform a clone using a shared repo.
+
+    The store for the repository will be located at <sharepath>/.hg. The
+    specified revisions will be cloned or pulled from "source". A shared repo
+    will be created at "dest" and a working copy will be created if "update" is
+    True.
+    """
+    revs = None
+    if rev:
+        if not srcpeer.capable('lookup'):
+            raise util.Abort(_("src repository does not support "
+                               "revision lookup and so doesn't "
+                               "support clone by revision"))
+        revs = [srcpeer.lookup(r) for r in rev]
+
+    basename = os.path.basename(sharepath)
+
+    if os.path.exists(sharepath):
+        ui.status(_('(sharing from existing pooled repository %s)\n') %
+                  basename)
+    else:
+        ui.status(_('(sharing from new pooled repository %s)\n') % basename)
+        # Always use pull mode because hardlinks in share mode don't work well.
+        # Never update because working copies aren't necessary in share mode.
+        clone(ui, peeropts, source, dest=sharepath, pull=True,
+              rev=rev, update=False, stream=stream)
+
+    sharerepo = repository(ui, path=sharepath)
+    share(ui, sharerepo, dest=dest, update=update, bookmarks=False)
+
+    # We need to perform a pull against the dest repo to fetch bookmarks
+    # and other non-store data that isn't shared by default. In the case of
+    # non-existing shared repo, this means we pull from the remote twice. This
+    # is a bit weird. But at the time it was implemented, there wasn't an easy
+    # way to pull just non-changegroup data.
+    destrepo = repository(ui, path=dest)
+    exchange.pull(destrepo, srcpeer, heads=revs)
+
+    return srcpeer, peer(ui, peeropts, dest)
+
 def clone(ui, peeropts, source, dest=None, pull=False, rev=None,
-          update=True, stream=False, branch=None):
+          update=True, stream=False, branch=None, shareopts=None):
     """Make a copy of an existing repository.
 
     Create a copy of an existing repository in a new directory.  The
@@ -316,6 +362,13 @@
     anything else is treated as a revision)
 
     branch: branches to clone
+
+    shareopts: dict of options to control auto sharing behavior. The "pool" key
+    activates auto sharing mode and defines the directory for stores. The
+    "mode" key determines how to construct the directory name of the shared
+    repository. "identity" means the name is derived from the node of the first
+    changeset in the repository. "remote" means the name is derived from the
+    remote's path/URL. Defaults to "identity."
     """
 
     if isinstance(source, str):
@@ -348,6 +401,36 @@
         elif destvfs.listdir():
             raise util.Abort(_("destination '%s' is not empty") % dest)
 
+    shareopts = shareopts or {}
+    sharepool = shareopts.get('pool')
+    sharenamemode = shareopts.get('mode')
+    if sharepool:
+        sharepath = None
+        if sharenamemode == 'identity':
+            # Resolve the name from the initial changeset in the remote
+            # repository. This returns nullid when the remote is empty. It
+            # raises RepoLookupError if revision 0 is filtered or otherwise
+            # not available. If we fail to resolve, sharing is not enabled.
+            try:
+                rootnode = srcpeer.lookup('0')
+                if rootnode != node.nullid:
+                    sharepath = os.path.join(sharepool, node.hex(rootnode))
+                else:
+                    ui.status(_('(not using pooled storage: '
+                                'remote appears to be empty)\n'))
+            except error.RepoLookupError:
+                ui.status(_('(not using pooled storage: '
+                            'unable to resolve identity of remote)\n'))
+        elif sharenamemode == 'remote':
+            sharepath = os.path.join(sharepool, util.sha1(source).hexdigest())
+        else:
+            raise util.Abort('unknown share naming mode: %s' % sharenamemode)
+
+        if sharepath:
+            return clonewithshare(ui, peeropts, sharepath, source, srcpeer,
+                                  dest, pull=pull, rev=rev, update=update,
+                                  stream=stream)
+
     srclock = destlock = cleandir = None
     srcrepo = srcpeer.local()
     try:
@@ -384,7 +467,7 @@
             try:
                 destpath = hgdir
                 util.makedir(destpath, notindexed=True)
-            except OSError, inst:
+            except OSError as inst:
                 if inst.errno == errno.EEXIST:
                     cleandir = None
                     raise util.Abort(_("destination '%s' already exists")
@@ -424,7 +507,7 @@
             try:
                 destpeer = peer(srcrepo or ui, peeropts, dest, create=True)
                                 # only pass ui when no srcrepo
-            except OSError, inst:
+            except OSError as inst:
                 if inst.errno == errno.EEXIST:
                     cleandir = None
                     raise util.Abort(_("destination '%s' already exists")
@@ -497,7 +580,7 @@
                 destrepo.ui.status(status)
                 _update(destrepo, uprev)
                 if update in destrepo._bookmarks:
-                    bookmarks.setcurrent(destrepo, update)
+                    bookmarks.activate(destrepo, update)
     finally:
         release(srclock, destlock)
         if cleandir is not None:
@@ -660,7 +743,28 @@
 
 def verify(repo):
     """verify the consistency of a repository"""
-    return verifymod.verify(repo)
+    ret = verifymod.verify(repo)
+
+    # Broken subrepo references in hidden csets don't seem worth worrying about,
+    # since they can't be pushed/pulled, and --hidden can be used if they are a
+    # concern.
+
+    # pathto() is needed for -R case
+    revs = repo.revs("filelog(%s)",
+                     util.pathto(repo.root, repo.getcwd(), '.hgsubstate'))
+
+    if revs:
+        repo.ui.status(_('checking subrepo links\n'))
+        for rev in revs:
+            ctx = repo[rev]
+            try:
+                for subpath in ctx.substate:
+                    ret = ctx.sub(subpath).verify() or ret
+            except Exception:
+                repo.ui.warn(_('.hgsubstate is corrupt in revision %s\n') %
+                             node.short(ctx.node()))
+
+    return ret
 
 def remoteui(src, opts):
     'build a remote ui from ui or repo and opts'
--- a/mercurial/hgweb/common.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/hgweb/common.py	Sat Jul 18 17:32:38 2015 -0500
@@ -112,8 +112,8 @@
 def statusmessage(code, message=None):
     return '%d %s' % (code, message or _statusmessage(code))
 
-def get_stat(spath, fn="00changelog.i"):
-    """stat fn (00changelog.i by default) if it exists, spath otherwise"""
+def get_stat(spath, fn):
+    """stat fn if it exists, spath otherwise"""
     cl_path = os.path.join(spath, fn)
     if os.path.exists(cl_path):
         return os.stat(cl_path)
@@ -121,7 +121,7 @@
         return os.stat(spath)
 
 def get_mtime(spath):
-    return get_stat(spath).st_mtime
+    return get_stat(spath, "00changelog.i").st_mtime
 
 def staticfile(directory, fname, req):
     """return a file inside directory with guessed Content-Type header
@@ -153,7 +153,7 @@
         req.respond(HTTP_OK, ct, body=data)
     except TypeError:
         raise ErrorResponse(HTTP_SERVER_ERROR, 'illegal filename')
-    except OSError, err:
+    except OSError as err:
         if err.errno == errno.ENOENT:
             raise ErrorResponse(HTTP_NOT_FOUND)
         else:
--- a/mercurial/hgweb/hgweb_mod.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/hgweb/hgweb_mod.py	Sat Jul 18 17:32:38 2015 -0500
@@ -26,6 +26,15 @@
     'pushkey': 'push',
 }
 
+## Files of interest
+# Used to check if the repository has changed looking at mtime and size of
+# theses files. This should probably be relocated a bit higher in core.
+foi = [('spath', '00changelog.i'),
+       ('spath', 'phaseroots'), # ! phase can change content at the same size
+       ('spath', 'obsstore'),
+       ('path', 'bookmarks'), # ! bookmark can change content at the same size
+      ]
+
 def makebreadcrumb(url, prefix=''):
     '''Return a 'URL breadcrumb' list
 
@@ -69,6 +78,10 @@
         r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
         r.ui.setconfig('ui', 'nontty', 'true', 'hgweb')
         r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb')
+        # displaying bundling progress bar while serving feel wrong and may
+        # break some wsgi implementation.
+        r.ui.setconfig('progress', 'disable', 'true', 'hgweb')
+        r.baseui.setconfig('progress', 'disable', 'true', 'hgweb')
         self.repo = r
         hook.redirect(True)
         self.repostate = ((-1, -1), (-1, -1))
@@ -96,6 +109,16 @@
                                        untrusted=untrusted)
 
     def _getview(self, repo):
+        """The 'web.view' config controls changeset filter to hgweb. Possible
+        values are ``served``, ``visible`` and ``all``. Default is ``served``.
+        The ``served`` filter only shows changesets that can be pulled from the
+        hgweb instance.  The``visible`` filter includes secret changesets but
+        still excludes "hidden" one.
+
+        See the repoview module for details.
+
+        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)
         if viewconfig == 'all':
@@ -106,10 +129,13 @@
             return repo.filtered('served')
 
     def refresh(self, request=None):
-        st = get_stat(self.repo.spath)
-        pst = get_stat(self.repo.spath, 'phaseroots')
-        # changelog mtime and size, phaseroots mtime and size
-        repostate = ((st.st_mtime, st.st_size), (pst.st_mtime, pst.st_size))
+        repostate = []
+        # file of interrests mtime and size
+        for meth, fname in foi:
+            prefix = getattr(self.repo, meth)
+            st = get_stat(prefix, fname)
+            repostate.append((st.st_mtime, st.st_size))
+        repostate = tuple(repostate)
         # we need to compare file size in addition to mtime to catch
         # changes made less than a second ago
         if repostate != self.repostate:
@@ -176,7 +202,7 @@
                 if cmd in perms:
                     self.check_perm(req, perms[cmd])
                 return protocol.call(self.repo, req, cmd)
-            except ErrorResponse, inst:
+            except ErrorResponse as inst:
                 # A client that sends unbundle without 100-continue will
                 # break if we respond early.
                 if (cmd == 'unbundle' and
@@ -209,7 +235,7 @@
                 req.form['file'] = ['/'.join(args)]
             else:
                 if args and args[0]:
-                    node = args.pop(0)
+                    node = args.pop(0).replace('%2F', '/')
                     req.form['node'] = [node]
                 if args:
                     req.form['file'] = args
@@ -255,17 +281,17 @@
 
             return content
 
-        except (error.LookupError, error.RepoLookupError), err:
+        except (error.LookupError, error.RepoLookupError) as err:
             req.respond(HTTP_NOT_FOUND, ctype)
             msg = str(err)
             if (util.safehasattr(err, 'name') and
                 not isinstance(err,  error.ManifestLookupError)):
                 msg = 'revision not found: %s' % err.name
             return tmpl('error', error=msg)
-        except (error.RepoError, error.RevlogError), inst:
+        except (error.RepoError, error.RevlogError) as inst:
             req.respond(HTTP_SERVER_ERROR, ctype)
             return tmpl('error', error=str(inst))
-        except ErrorResponse, inst:
+        except ErrorResponse as inst:
             req.respond(inst, ctype)
             if inst.code == HTTP_NOT_MODIFIED:
                 # Not allowed to return a body on a 304
--- a/mercurial/hgweb/hgwebdir_mod.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/hgweb/hgwebdir_mod.py	Sat Jul 18 17:32:38 2015 -0500
@@ -98,6 +98,9 @@
             u = ui.ui()
             u.setconfig('ui', 'report_untrusted', 'off', 'hgwebdir')
             u.setconfig('ui', 'nontty', 'true', 'hgwebdir')
+            # displaying bundling progress bar while serving feels wrong and may
+            # break some wsgi implementations.
+            u.setconfig('progress', 'disable', 'true', 'hgweb')
 
         if not isinstance(self.conf, (dict, list, tuple)):
             map = {'paths': 'hgweb-paths'}
@@ -176,71 +179,70 @@
 
     def run_wsgi(self, req):
         try:
-            try:
-                self.refresh()
+            self.refresh()
 
-                virtual = req.env.get("PATH_INFO", "").strip('/')
-                tmpl = self.templater(req)
-                ctype = tmpl('mimetype', encoding=encoding.encoding)
-                ctype = templater.stringify(ctype)
+            virtual = req.env.get("PATH_INFO", "").strip('/')
+            tmpl = self.templater(req)
+            ctype = tmpl('mimetype', encoding=encoding.encoding)
+            ctype = templater.stringify(ctype)
 
-                # a static file
-                if virtual.startswith('static/') or 'static' in req.form:
-                    if virtual.startswith('static/'):
-                        fname = virtual[7:]
-                    else:
-                        fname = req.form['static'][0]
-                    static = self.ui.config("web", "static", None,
-                                            untrusted=False)
-                    if not static:
-                        tp = self.templatepath or templater.templatepaths()
-                        if isinstance(tp, str):
-                            tp = [tp]
-                        static = [os.path.join(p, 'static') for p in tp]
-                    staticfile(static, fname, req)
-                    return []
+            # a static file
+            if virtual.startswith('static/') or 'static' in req.form:
+                if virtual.startswith('static/'):
+                    fname = virtual[7:]
+                else:
+                    fname = req.form['static'][0]
+                static = self.ui.config("web", "static", None,
+                                        untrusted=False)
+                if not static:
+                    tp = self.templatepath or templater.templatepaths()
+                    if isinstance(tp, str):
+                        tp = [tp]
+                    static = [os.path.join(p, 'static') for p in tp]
+                staticfile(static, fname, req)
+                return []
 
-                # top-level index
-                elif not virtual:
-                    req.respond(HTTP_OK, ctype)
-                    return self.makeindex(req, tmpl)
+            # top-level index
+            elif not virtual:
+                req.respond(HTTP_OK, ctype)
+                return self.makeindex(req, tmpl)
 
-                # nested indexes and hgwebs
+            # nested indexes and hgwebs
 
-                repos = dict(self.repos)
-                virtualrepo = virtual
-                while virtualrepo:
-                    real = repos.get(virtualrepo)
-                    if real:
-                        req.env['REPO_NAME'] = virtualrepo
-                        try:
-                            # ensure caller gets private copy of ui
-                            repo = hg.repository(self.ui.copy(), real)
-                            return hgweb(repo).run_wsgi(req)
-                        except IOError, inst:
-                            msg = inst.strerror
-                            raise ErrorResponse(HTTP_SERVER_ERROR, msg)
-                        except error.RepoError, inst:
-                            raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
+            repos = dict(self.repos)
+            virtualrepo = virtual
+            while virtualrepo:
+                real = repos.get(virtualrepo)
+                if real:
+                    req.env['REPO_NAME'] = virtualrepo
+                    try:
+                        # ensure caller gets private copy of ui
+                        repo = hg.repository(self.ui.copy(), real)
+                        return hgweb(repo).run_wsgi(req)
+                    except IOError as inst:
+                        msg = inst.strerror
+                        raise ErrorResponse(HTTP_SERVER_ERROR, msg)
+                    except error.RepoError as inst:
+                        raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
 
-                    up = virtualrepo.rfind('/')
-                    if up < 0:
-                        break
-                    virtualrepo = virtualrepo[:up]
+                up = virtualrepo.rfind('/')
+                if up < 0:
+                    break
+                virtualrepo = virtualrepo[:up]
 
-                # browse subdirectories
-                subdir = virtual + '/'
-                if [r for r in repos if r.startswith(subdir)]:
-                    req.respond(HTTP_OK, ctype)
-                    return self.makeindex(req, tmpl, subdir)
+            # browse subdirectories
+            subdir = virtual + '/'
+            if [r for r in repos if r.startswith(subdir)]:
+                req.respond(HTTP_OK, ctype)
+                return self.makeindex(req, tmpl, subdir)
 
-                # prefixes not found
-                req.respond(HTTP_NOT_FOUND, ctype)
-                return tmpl("notfound", repo=virtual)
+            # prefixes not found
+            req.respond(HTTP_NOT_FOUND, ctype)
+            return tmpl("notfound", repo=virtual)
 
-            except ErrorResponse, err:
-                req.respond(err, ctype)
-                return tmpl('error', error=err.message or '')
+        except ErrorResponse as err:
+            req.respond(err, ctype)
+            return tmpl('error', error=err.message or '')
         finally:
             tmpl = None
 
@@ -334,7 +336,7 @@
                 u = self.ui.copy()
                 try:
                     u.readconfig(os.path.join(path, '.hg', 'hgrc'))
-                except Exception, e:
+                except Exception as e:
                     u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
                     continue
                 def get(section, name, default=None):
--- a/mercurial/hgweb/request.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/hgweb/request.py	Sat Jul 18 17:32:38 2015 -0500
@@ -111,7 +111,7 @@
         if thing:
             try:
                 self.server_write(thing)
-            except socket.error, inst:
+            except socket.error as inst:
                 if inst[0] != errno.ECONNRESET:
                     raise
 
--- a/mercurial/hgweb/server.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/hgweb/server.py	Sat Jul 18 17:32:38 2015 -0500
@@ -71,7 +71,7 @@
     def do_write(self):
         try:
             self.do_hgweb()
-        except socket.error, inst:
+        except socket.error as inst:
             if inst[0] != errno.EPIPE:
                 raise
 
@@ -226,7 +226,7 @@
         import OpenSSL
         try:
             _httprequesthandler.do_write(self)
-        except OpenSSL.SSL.SysCallError, inst:
+        except OpenSSL.SSL.SysCallError as inst:
             if inst.args[0] != errno.EPIPE:
                 raise
 
@@ -344,6 +344,6 @@
     port = util.getport(ui.config('web', 'port', 8000))
     try:
         return cls(ui, app, (address, port), handler)
-    except socket.error, inst:
+    except socket.error as inst:
         raise util.Abort(_("cannot start server at '%s:%d': %s")
                          % (address, port, inst.args[1]))
--- a/mercurial/hgweb/webcommands.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/hgweb/webcommands.py	Sat Jul 18 17:32:38 2015 -0500
@@ -76,7 +76,7 @@
 
     try:
         fctx = webutil.filectx(web.repo, req)
-    except error.LookupError, inst:
+    except error.LookupError as inst:
         try:
             content = manifest(web, req, tmpl)
             req.respond(HTTP_OK, web.ctype)
@@ -100,7 +100,7 @@
     req.respond(HTTP_OK, mt, path, body=text)
     return []
 
-def _filerevision(web, tmpl, fctx):
+def _filerevision(web, req, tmpl, fctx):
     f = fctx.path()
     text = fctx.data()
     parity = paritygen(web.stripecount)
@@ -121,6 +121,7 @@
                 path=webutil.up(f),
                 text=lines(),
                 rev=fctx.rev(),
+                symrev=webutil.symrevorshortnode(req, fctx),
                 node=fctx.hex(),
                 author=fctx.user(),
                 date=fctx.date(),
@@ -130,6 +131,8 @@
                 parent=webutil.parents(fctx),
                 child=webutil.children(fctx),
                 rename=webutil.renamelink(fctx),
+                tags=webutil.nodetagsdict(web.repo, fctx.node()),
+                bookmarks=webutil.nodebookmarksdict(web.repo, fctx.node()),
                 permissions=fctx.manifest().flags(f))
 
 @webcommand('file')
@@ -156,8 +159,8 @@
     if not path:
         return manifest(web, req, tmpl)
     try:
-        return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
-    except error.LookupError, inst:
+        return _filerevision(web, req, tmpl, webutil.filectx(web.repo, req))
+    except error.LookupError as inst:
         try:
             return manifest(web, req, tmpl)
         except ErrorResponse:
@@ -221,7 +224,7 @@
 
         revdef = 'reverse(%s)' % query
         try:
-            tree, pos = revset.parse(revdef)
+            tree = revset.parse(revdef)
         except ParseError:
             # can't parse to a revset tree
             return MODE_KEYWORD, query
@@ -230,7 +233,7 @@
             # no revset syntax used
             return MODE_KEYWORD, query
 
-        if util.any((token, (value or '')[:3]) == ('string', 're:')
+        if any((token, (value or '')[:3]) == ('string', 're:')
                     for token, value, pos in revset.tokenize(revdef)):
             return MODE_KEYWORD, query
 
@@ -314,7 +317,7 @@
     tip = web.repo['tip']
     parity = paritygen(web.stripecount)
 
-    return tmpl('search', query=query, node=tip.hex(),
+    return tmpl('search', query=query, node=tip.hex(), symrev='tip',
                 entries=changelist, archives=web.archivelist("tip"),
                 morevars=morevars, lessvars=lessvars,
                 modedesc=searchfunc[1],
@@ -349,10 +352,12 @@
     query = ''
     if 'node' in req.form:
         ctx = webutil.changectx(web.repo, req)
+        symrev = webutil.symrevorshortnode(req, ctx)
     elif 'rev' in req.form:
         return _search(web, req, tmpl)
     else:
         ctx = web.repo['tip']
+        symrev = 'tip'
 
     def changelist():
         revs = []
@@ -401,7 +406,7 @@
         nextentry = []
 
     return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
-                node=ctx.hex(), rev=pos, changesets=count,
+                node=ctx.hex(), rev=pos, symrev=symrev, changesets=count,
                 entries=entries,
                 latestentry=latestentry, nextentry=nextentry,
                 archives=web.archivelist("tip"), revcount=revcount,
@@ -468,7 +473,12 @@
 
     The ``manifest`` template will be rendered for this handler.
     """
-    ctx = webutil.changectx(web.repo, req)
+    if 'node' in req.form:
+        ctx = webutil.changectx(web.repo, req)
+        symrev = webutil.symrevorshortnode(req, ctx)
+    else:
+        ctx = web.repo['tip']
+        symrev = 'tip'
     path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
     mf = ctx.manifest()
     node = ctx.node()
@@ -537,6 +547,7 @@
 
     return tmpl("manifest",
                 rev=ctx.rev(),
+                symrev=symrev,
                 node=hex(node),
                 path=abspath,
                 up=webutil.up(abspath),
@@ -546,6 +557,7 @@
                 archives=web.archivelist(hex(node)),
                 tags=webutil.nodetagsdict(web.repo, node),
                 bookmarks=webutil.nodebookmarksdict(web.repo, node),
+                branch=webutil.nodebranchnodefault(ctx),
                 inbranch=webutil.nodeinbranch(web.repo, ctx),
                 branches=webutil.nodebranchdict(web.repo, ctx))
 
@@ -752,6 +764,7 @@
                 branches=branches,
                 shortlog=changelist,
                 node=tip.hex(),
+                symrev='tip',
                 archives=web.archivelist("tip"))
 
 @webcommand('filediff')
@@ -800,6 +813,7 @@
                 file=path,
                 node=hex(n),
                 rev=ctx.rev(),
+                symrev=webutil.symrevorshortnode(req, ctx),
                 date=ctx.date(),
                 desc=ctx.description(),
                 extra=ctx.extra(),
@@ -808,6 +822,8 @@
                 branch=webutil.nodebranchnodefault(ctx),
                 parent=webutil.parents(ctx),
                 child=webutil.children(ctx),
+                tags=webutil.nodetagsdict(web.repo, n),
+                bookmarks=webutil.nodebookmarksdict(web.repo, n),
                 diff=diffs)
 
 diff = webcommand('diff')(filediff)
@@ -872,6 +888,7 @@
                 file=path,
                 node=hex(ctx.node()),
                 rev=ctx.rev(),
+                symrev=webutil.symrevorshortnode(req, ctx),
                 date=ctx.date(),
                 desc=ctx.description(),
                 extra=ctx.extra(),
@@ -880,6 +897,8 @@
                 branch=webutil.nodebranchnodefault(ctx),
                 parent=webutil.parents(fctx),
                 child=webutil.children(fctx),
+                tags=webutil.nodetagsdict(web.repo, ctx.node()),
+                bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()),
                 leftrev=leftrev,
                 leftnode=hex(leftnode),
                 rightrev=rightrev,
@@ -937,6 +956,7 @@
                 annotate=annotate,
                 path=webutil.up(f),
                 rev=fctx.rev(),
+                symrev=webutil.symrevorshortnode(req, fctx),
                 node=fctx.hex(),
                 author=fctx.user(),
                 date=fctx.date(),
@@ -946,6 +966,8 @@
                 branch=webutil.nodebranchnodefault(fctx),
                 parent=webutil.parents(fctx),
                 child=webutil.children(fctx),
+                tags=webutil.nodetagsdict(web.repo, fctx.node()),
+                bookmarks=webutil.nodebookmarksdict(web.repo, fctx.node()),
                 permissions=fctx.manifest().flags(f))
 
 @webcommand('filelog')
@@ -1034,6 +1056,7 @@
     revnav = webutil.filerevnav(web.repo, fctx.path())
     nav = revnav.gen(end - 1, revcount, count)
     return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
+                symrev=webutil.symrevorshortnode(req, fctx),
                 entries=entries,
                 latestentry=latestentry,
                 revcount=revcount, morevars=morevars, lessvars=lessvars)
@@ -1140,7 +1163,12 @@
     This handler will render the ``graph`` template.
     """
 
-    ctx = webutil.changectx(web.repo, req)
+    if 'node' in req.form:
+        ctx = webutil.changectx(web.repo, req)
+        symrev = webutil.symrevorshortnode(req, ctx)
+    else:
+        ctx = web.repo['tip']
+        symrev = 'tip'
     rev = ctx.rev()
 
     bg_height = 39
@@ -1243,7 +1271,8 @@
     rows = len(tree)
     canvasheight = (rows + 1) * bg_height - 27
 
-    return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
+    return tmpl('graph', rev=rev, symrev=symrev, revcount=revcount,
+                uprev=uprev,
                 lessvars=lessvars, morevars=morevars, downrev=downrev,
                 cols=cols, rows=rows,
                 canvaswidth=(cols + 1) * bg_height,
--- a/mercurial/hgweb/webutil.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/hgweb/webutil.py	Sat Jul 18 17:32:38 2015 -0500
@@ -9,7 +9,8 @@
 import os, copy
 from mercurial import match, patch, error, ui, util, pathutil, context
 from mercurial.i18n import _
-from mercurial.node import hex, nullid
+from mercurial.node import hex, nullid, short
+from mercurial.templatefilters import revescape
 from common import ErrorResponse, paritygen
 from common import HTTP_NOT_FOUND
 import difflib
@@ -279,6 +280,12 @@
         "branches": nodebranchdict(repo, ctx)
     }
 
+def symrevorshortnode(req, ctx):
+    if 'node' in req.form:
+        return revescape(req.form['node'][0])
+    else:
+        return short(ctx.node())
+
 def changesetentry(web, req, tmpl, ctx):
     '''Obtain a dictionary to be used to render the "changeset" template.'''
 
@@ -314,6 +321,7 @@
         diff=diff,
         rev=ctx.rev(),
         node=ctx.hex(),
+        symrev=symrevorshortnode(req, ctx),
         parent=tuple(parents(ctx)),
         child=children(ctx),
         basenode=basectx.hex(),
@@ -331,7 +339,7 @@
         archives=web.archivelist(ctx.hex()),
         tags=nodetagsdict(web.repo, ctx.node()),
         bookmarks=nodebookmarksdict(web.repo, ctx.node()),
-        branch=nodebranchnodefault(ctx),
+        branch=showbranch,
         inbranch=nodeinbranch(web.repo, ctx),
         branches=nodebranchdict(web.repo, ctx))
 
--- a/mercurial/hook.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/hook.py	Sat Jul 18 17:32:38 2015 -0500
@@ -35,10 +35,7 @@
             if modpath and modfile:
                 sys.path = sys.path[:] + [modpath]
                 modname = modfile
-        demandimportenabled = demandimport.isenabled()
-        if demandimportenabled:
-            demandimport.disable()
-        try:
+        with demandimport.deactivated():
             try:
                 obj = __import__(modname)
             except ImportError:
@@ -59,9 +56,6 @@
                     raise util.Abort(_('%s hook is invalid '
                                        '(import of "%s" failed)') %
                                      (hname, modname))
-        finally:
-            if demandimportenabled:
-                demandimport.enable()
         sys.path = oldpaths
         try:
             for p in funcname.split('.')[1:]:
@@ -79,27 +73,24 @@
     starttime = time.time()
 
     try:
-        try:
-            # redirect IO descriptors to the ui descriptors so hooks
-            # that write directly to these don't mess up the command
-            # protocol when running through the command server
-            old = sys.stdout, sys.stderr, sys.stdin
-            sys.stdout, sys.stderr, sys.stdin = ui.fout, ui.ferr, ui.fin
+        # redirect IO descriptors to the ui descriptors so hooks
+        # that write directly to these don't mess up the command
+        # protocol when running through the command server
+        old = sys.stdout, sys.stderr, sys.stdin
+        sys.stdout, sys.stderr, sys.stdin = ui.fout, ui.ferr, ui.fin
 
-            r = obj(ui=ui, repo=repo, hooktype=name, **args)
-        except KeyboardInterrupt:
+        r = obj(ui=ui, repo=repo, hooktype=name, **args)
+    except Exception as exc:
+        if isinstance(exc, util.Abort):
+            ui.warn(_('error: %s hook failed: %s\n') %
+                         (hname, exc.args[0]))
+        else:
+            ui.warn(_('error: %s hook raised an exception: '
+                           '%s\n') % (hname, exc))
+        if throw:
             raise
-        except Exception, exc:
-            if isinstance(exc, util.Abort):
-                ui.warn(_('error: %s hook failed: %s\n') %
-                             (hname, exc.args[0]))
-            else:
-                ui.warn(_('error: %s hook raised an exception: '
-                               '%s\n') % (hname, exc))
-            if throw:
-                raise
-            ui.traceback()
-            return True
+        ui.traceback()
+        return True
     finally:
         sys.stdout, sys.stderr, sys.stdin = old
         duration = time.time() - starttime
--- a/mercurial/httpclient/__init__.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/httpclient/__init__.py	Sat Jul 18 17:32:38 2015 -0500
@@ -166,7 +166,7 @@
                 raise HTTPTimeoutException('timeout reading data')
         try:
             data = self.sock.recv(INCOMING_BUFFER_SIZE)
-        except socket.sslerror, e:
+        except socket.sslerror as e:
             if e.args[0] != socket.SSL_ERROR_WANT_READ:
                 raise
             logger.debug('SSL_ERROR_WANT_READ in _select, should retry later')
@@ -555,7 +555,7 @@
                 try:
                     try:
                         data = r[0].recv(INCOMING_BUFFER_SIZE)
-                    except socket.sslerror, e:
+                    except socket.sslerror as e:
                         if e.args[0] != socket.SSL_ERROR_WANT_READ:
                             raise
                         logger.debug('SSL_ERROR_WANT_READ while sending '
@@ -610,7 +610,7 @@
                     # Jump to the next select() call so we load more
                     # data if the server is still sending us content.
                     continue
-                except socket.error, e:
+                except socket.error as e:
                     if e[0] != errno.EPIPE and not was_first:
                         raise
 
@@ -633,7 +633,7 @@
                         else:
                             out = data
                     amt = w[0].send(out)
-                except socket.error, e:
+                except socket.error as e:
                     if e[0] == socket.SSL_ERROR_WANT_WRITE and self.ssl:
                         # This means that SSL hasn't flushed its buffer into
                         # the socket yet.
--- a/mercurial/httpclient/socketutil.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/httpclient/socketutil.py	Sat Jul 18 17:32:38 2015 -0500
@@ -64,7 +64,7 @@
                 sock = socket.socket(af, socktype, proto)
                 logger.info("connect: (%s, %s)", host, port)
                 sock.connect(sa)
-            except socket.error, msg:
+            except socket.error as msg:
                 logger.info('connect fail: %s %s', host, port)
                 if sock:
                     sock.close()
@@ -100,7 +100,7 @@
             while True:
                 try:
                     return self._ssl.read(buflen)
-                except socket.sslerror, x:
+                except socket.sslerror as x:
                     if x.args[0] == socket.SSL_ERROR_WANT_READ:
                         continue
                     else:
--- a/mercurial/httpconnection.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/httpconnection.py	Sat Jul 18 17:32:38 2015 -0500
@@ -70,11 +70,7 @@
         gdict[setting] = val
 
     # Find the best match
-    if '://' in uri:
-        scheme, hostpath = uri.split('://', 1)
-    else:
-        # Python 2.4.1 doesn't provide the full URI
-        scheme, hostpath = 'http', uri
+    scheme, hostpath = uri.split('://', 1)
     bestuser = None
     bestlen = 0
     bestauth = None
@@ -130,6 +126,7 @@
         self.ui = ui
         self.pwmgr = pwmgr
         self._connections = {}
+        # developer config: ui.http2debuglevel
         loglevel = ui.config('ui', 'http2debuglevel', default=None)
         if loglevel and not _configuredlogging:
             _configuredlogging = True
@@ -215,7 +212,7 @@
                 path = '/' + path
             h.request(req.get_method(), path, req.data, headers)
             r = h.getresponse()
-        except socket.error, err: # XXX what error?
+        except socket.error as err: # XXX what error?
             raise urllib2.URLError(err)
 
         # Pick apart the HTTPResponse object to get the addinfourl
@@ -281,7 +278,7 @@
         kwargs.update(sslutil.sslkwargs(self.ui, host))
 
         con = HTTPConnection(host, port, use_ssl=True,
-                             ssl_wrap_socket=sslutil.ssl_wrap_socket,
+                             ssl_wrap_socket=sslutil.wrapsocket,
                              ssl_validator=sslutil.validator(self.ui, host),
                              **kwargs)
         return con
--- a/mercurial/httppeer.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/httppeer.py	Sat Jul 18 17:32:38 2015 -0500
@@ -30,6 +30,7 @@
         self.caps = None
         self.handler = None
         self.urlopener = None
+        self.requestbuilder = None
         u = util.url(path)
         if u.query or u.fragment:
             raise util.Abort(_('unsupported URL component: "%s"') %
@@ -42,6 +43,7 @@
         self.ui.debug('using %s\n' % self._url)
 
         self.urlopener = url.opener(ui, authinfo)
+        self.requestbuilder = urllib2.Request
 
     def __del__(self):
         if self.urlopener:
@@ -111,17 +113,17 @@
             q += sorted(args.items())
         qs = '?%s' % urllib.urlencode(q)
         cu = "%s%s" % (self._url, qs)
-        req = urllib2.Request(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)
-        except urllib2.HTTPError, inst:
+        except urllib2.HTTPError as inst:
             if inst.code == 401:
                 raise util.Abort(_('authorization failed'))
             raise
-        except httplib.HTTPException, inst:
+        except httplib.HTTPException as inst:
             self.ui.debug('http error while sending %s command\n' % cmd)
             self.ui.traceback()
             raise IOError(None, inst)
@@ -198,16 +200,15 @@
         headers = {'Content-Type': 'application/mercurial-0.1'}
 
         try:
-            try:
-                r = self._call(cmd, data=fp, headers=headers, **args)
-                vals = r.split('\n', 1)
-                if len(vals) < 2:
-                    raise error.ResponseError(_("unexpected response:"), r)
-                return vals
-            except socket.error, err:
-                if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
-                    raise util.Abort(_('push failed: %s') % err.args[1])
-                raise util.Abort(err.args[1])
+            r = self._call(cmd, data=fp, headers=headers, **args)
+            vals = r.split('\n', 1)
+            if len(vals) < 2:
+                raise error.ResponseError(_("unexpected response:"), r)
+            return vals
+        except socket.error as err:
+            if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
+                raise util.Abort(_('push failed: %s') % err.args[1])
+            raise util.Abort(err.args[1])
         finally:
             fp.close()
             os.unlink(tempname)
@@ -266,7 +267,7 @@
             # No luck, try older compatibility check.
             inst.between([(nullid, nullid)])
         return inst
-    except error.RepoError, httpexception:
+    except error.RepoError as httpexception:
         try:
             r = statichttprepo.instance(ui, "static-" + path, create)
             ui.note('(falling back to static-http)\n')
--- a/mercurial/ignore.py	Fri Jul 03 18:10:58 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,113 +0,0 @@
-# ignore.py - ignored file handling for mercurial
-#
-# Copyright 2007 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2 or any later version.
-
-from i18n import _
-import util, match
-import re
-
-_commentre = None
-
-def ignorepats(lines):
-    '''parse lines (iterable) of .hgignore text, returning a tuple of
-    (patterns, parse errors). These patterns should be given to compile()
-    to be validated and converted into a match function.'''
-    syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
-    syntax = 'relre:'
-    patterns = []
-    warnings = []
-
-    for line in lines:
-        if "#" in line:
-            global _commentre
-            if not _commentre:
-                _commentre = re.compile(r'((^|[^\\])(\\\\)*)#.*')
-            # remove comments prefixed by an even number of escapes
-            line = _commentre.sub(r'\1', line)
-            # fixup properly escaped comments that survived the above
-            line = line.replace("\\#", "#")
-        line = line.rstrip()
-        if not line:
-            continue
-
-        if line.startswith('syntax:'):
-            s = line[7:].strip()
-            try:
-                syntax = syntaxes[s]
-            except KeyError:
-                warnings.append(_("ignoring invalid syntax '%s'") % s)
-            continue
-        pat = syntax + line
-        for s, rels in syntaxes.iteritems():
-            if line.startswith(rels):
-                pat = line
-                break
-            elif line.startswith(s+':'):
-                pat = rels + line[len(s) + 1:]
-                break
-        patterns.append(pat)
-
-    return patterns, warnings
-
-def readpats(root, files, warn):
-    '''return a dict mapping ignore-file-name to list-of-patterns'''
-
-    pats = {}
-    for f in files:
-        if f in pats:
-            continue
-        try:
-            pats[f] = []
-            fp = open(f)
-            pats[f], warnings = ignorepats(fp)
-            fp.close()
-            for warning in warnings:
-                warn("%s: %s\n" % (f, warning))
-        except IOError, inst:
-            if f != files[0]:
-                warn(_("skipping unreadable ignore file '%s': %s\n") %
-                     (f, inst.strerror))
-    return [(f, pats[f]) for f in files if f in pats]
-
-def ignore(root, files, warn):
-    '''return matcher covering patterns in 'files'.
-
-    the files parsed for patterns include:
-    .hgignore in the repository root
-    any additional files specified in the [ui] section of ~/.hgrc
-
-    trailing white space is dropped.
-    the escape character is backslash.
-    comments start with #.
-    empty lines are skipped.
-
-    lines can be of the following formats:
-
-    syntax: regexp # defaults following lines to non-rooted regexps
-    syntax: glob   # defaults following lines to non-rooted globs
-    re:pattern     # non-rooted regular expression
-    glob:pattern   # non-rooted glob
-    pattern        # pattern of the current default type'''
-
-    pats = readpats(root, files, warn)
-
-    allpats = []
-    for f, patlist in pats:
-        allpats.extend(patlist)
-    if not allpats:
-        return util.never
-
-    try:
-        ignorefunc = match.match(root, '', [], allpats)
-    except util.Abort:
-        # Re-raise an exception where the src is the right file
-        for f, patlist in pats:
-            try:
-                match.match(root, '', [], patlist)
-            except util.Abort, inst:
-                raise util.Abort('%s: %s' % (f, inst[0]))
-
-    return ignorefunc
--- a/mercurial/keepalive.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/keepalive.py	Sat Jul 18 17:32:38 2015 -0500
@@ -251,7 +251,7 @@
                 self._cm.add(host, h, 0)
                 self._start_transaction(h, req)
                 r = h.getresponse()
-        except (socket.error, httplib.HTTPException), err:
+        except (socket.error, httplib.HTTPException) as err:
             raise urllib2.URLError(err)
 
         # if not a persistent connection, don't try to reuse it
@@ -343,7 +343,7 @@
                     h.putheader('Content-length', '%d' % len(data))
             else:
                 h.putrequest('GET', req.get_selector(), **skipheaders)
-        except (socket.error), err:
+        except (socket.error) as err:
             raise urllib2.URLError(err)
         for k, v in headers.items():
             h.putheader(k, v)
@@ -550,7 +550,7 @@
                 data = read(blocksize)
         else:
             self.sock.sendall(str)
-    except socket.error, v:
+    except socket.error as v:
         reraise = True
         if v[0] == errno.EPIPE:      # Broken pipe
             if self._HTTPConnection__state == httplib._CS_REQ_SENT:
@@ -605,7 +605,7 @@
                 status, reason = fo.status, fo.reason
             except AttributeError:
                 status, reason = None, None
-        except IOError, e:
+        except IOError as e:
             print "  EXCEPTION: %s" % e
             raise
         else:
--- a/mercurial/localrepo.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/localrepo.py	Sat Jul 18 17:32:38 2015 -0500
@@ -4,7 +4,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 node import hex, nullid, short
+from node import hex, nullid, wdirrev, short
 from i18n import _
 import urllib
 import peer, changegroup, subrepo, pushkey, obsolete, repoview
@@ -135,7 +135,7 @@
                     stream = util.chunkbuffer(ret.getchunks())
                     ret = bundle2.getunbundler(self.ui, stream)
                 return ret
-            except Exception, exc:
+            except Exception as exc:
                 # If the exception contains output salvaged from a bundle2
                 # reply, we need to make sure it is printed before continuing
                 # to fail. So we build a bundle2 with such output and consume
@@ -152,7 +152,7 @@
                     b = bundle2.getunbundler(self.ui, stream)
                     bundle2.processbundle(self._repo, b)
                 raise
-        except error.PushRaced, exc:
+        except error.PushRaced as exc:
             raise error.ResponseError(_('push failed:'), str(exc))
 
     def lock(self):
@@ -192,11 +192,11 @@
 
 class localrepository(object):
 
-    supportedformats = set(('revlogv1', 'generaldelta', 'manifestv2'))
+    supportedformats = set(('revlogv1', 'generaldelta', 'treemanifest',
+                            'manifestv2'))
     _basesupported = supportedformats | set(('store', 'fncache', 'shared',
                                              'dotencode'))
-    openerreqs = set(('revlogv1', 'generaldelta', 'manifestv2'))
-    requirements = ['revlogv1']
+    openerreqs = set(('revlogv1', 'generaldelta', 'treemanifest', 'manifestv2'))
     filtername = None
 
     # a list of (ui, featureset) functions.
@@ -204,9 +204,10 @@
     featuresetupfuncs = set()
 
     def _baserequirements(self, create):
-        return self.requirements[:]
+        return ['revlogv1']
 
     def __init__(self, baseui, path=None, create=False):
+        self.requirements = set()
         self.wvfs = scmutil.vfs(path, expandpath=True, realpath=True)
         self.wopener = self.wvfs
         self.root = self.wvfs.base
@@ -243,36 +244,38 @@
                 if not self.wvfs.exists():
                     self.wvfs.makedirs()
                 self.vfs.makedir(notindexed=True)
-                requirements = self._baserequirements(create)
+                self.requirements.update(self._baserequirements(create))
                 if self.ui.configbool('format', 'usestore', True):
                     self.vfs.mkdir("store")
-                    requirements.append("store")
+                    self.requirements.add("store")
                     if self.ui.configbool('format', 'usefncache', True):
-                        requirements.append("fncache")
+                        self.requirements.add("fncache")
                         if self.ui.configbool('format', 'dotencode', True):
-                            requirements.append('dotencode')
+                            self.requirements.add('dotencode')
                     # create an invalid changelog
                     self.vfs.append(
                         "00changelog.i",
                         '\0\0\0\2' # represents revlogv2
                         ' dummy changelog to prevent using the old repo layout'
                     )
+                # experimental config: format.generaldelta
                 if self.ui.configbool('format', 'generaldelta', False):
-                    requirements.append("generaldelta")
+                    self.requirements.add("generaldelta")
+                if self.ui.configbool('experimental', 'treemanifest', False):
+                    self.requirements.add("treemanifest")
                 if self.ui.configbool('experimental', 'manifestv2', False):
-                    requirements.append("manifestv2")
-                requirements = set(requirements)
+                    self.requirements.add("manifestv2")
             else:
                 raise error.RepoError(_("repository %s not found") % path)
         elif create:
             raise error.RepoError(_("repository %s already exists") % path)
         else:
             try:
-                requirements = scmutil.readrequires(self.vfs, self.supported)
-            except IOError, inst:
+                self.requirements = scmutil.readrequires(
+                        self.vfs, self.supported)
+            except IOError as inst:
                 if inst.errno != errno.ENOENT:
                     raise
-                requirements = set()
 
         self.sharedpath = self.path
         try:
@@ -283,17 +286,17 @@
                 raise error.RepoError(
                     _('.hg/sharedpath points to nonexistent directory %s') % s)
             self.sharedpath = s
-        except IOError, inst:
+        except IOError as inst:
             if inst.errno != errno.ENOENT:
                 raise
 
-        self.store = store.store(requirements, self.sharedpath, scmutil.vfs)
+        self.store = store.store(
+                self.requirements, self.sharedpath, scmutil.vfs)
         self.spath = self.store.path
         self.svfs = self.store.vfs
-        self.sopener = self.svfs
         self.sjoin = self.store.join
         self.vfs.createmode = self.store.createmode
-        self._applyrequirements(requirements)
+        self._applyopenerreqs()
         if create:
             self._writerequirements()
 
@@ -336,28 +339,24 @@
             caps.add('bundle2=' + urllib.quote(capsblob))
         return caps
 
-    def _applyrequirements(self, requirements):
-        self.requirements = requirements
-        self.svfs.options = dict((r, 1) for r in requirements
+    def _applyopenerreqs(self):
+        self.svfs.options = dict((r, 1) for r in self.requirements
                                            if r in self.openerreqs)
+        # experimental config: format.chunkcachesize
         chunkcachesize = self.ui.configint('format', 'chunkcachesize')
         if chunkcachesize is not None:
             self.svfs.options['chunkcachesize'] = chunkcachesize
+        # experimental config: format.maxchainlen
         maxchainlen = self.ui.configint('format', 'maxchainlen')
         if maxchainlen is not None:
             self.svfs.options['maxchainlen'] = maxchainlen
+        # experimental config: format.manifestcachesize
         manifestcachesize = self.ui.configint('format', 'manifestcachesize')
         if manifestcachesize is not None:
             self.svfs.options['manifestcachesize'] = manifestcachesize
-        usetreemanifest = self.ui.configbool('experimental', 'treemanifest')
-        if usetreemanifest is not None:
-            self.svfs.options['usetreemanifest'] = usetreemanifest
 
     def _writerequirements(self):
-        reqfile = self.vfs("requires", "w")
-        for r in sorted(self.requirements):
-            reqfile.write("%s\n" % r)
-        reqfile.close()
+        scmutil.writerequires(self.vfs, self.requirements)
 
     def _checknested(self, path):
         """Determine if path is a legal nested repository."""
@@ -419,8 +418,8 @@
         return bookmarks.bmstore(self)
 
     @repofilecache('bookmarks.current')
-    def _bookmarkcurrent(self):
-        return bookmarks.readcurrent(self)
+    def _activebookmark(self):
+        return bookmarks.readactive(self)
 
     def bookmarkheads(self, bookmark):
         name = bookmark.split('@', 1)[0]
@@ -437,6 +436,7 @@
     @storecache('obsstore')
     def obsstore(self):
         # read default format for new obsstore.
+        # developer config: format.obsstore-version
         defaultformat = self.ui.configint('format', 'obsstore-version', None)
         # rely on obsstore class default when possible.
         kwargs = {}
@@ -464,6 +464,9 @@
     def manifest(self):
         return manifest.manifest(self.svfs)
 
+    def dirlog(self, dir):
+        return self.manifest.dirlog(dir)
+
     @repofilecache('dirstate')
     def dirstate(self):
         warned = [0]
@@ -481,7 +484,7 @@
         return dirstate.dirstate(self.vfs, self.ui, self.root, validate)
 
     def __getitem__(self, changeid):
-        if changeid is None:
+        if changeid is None or changeid == wdirrev:
             return context.workingctx(self)
         if isinstance(changeid, slice):
             return [context.changectx(self, i)
@@ -579,7 +582,7 @@
 
         try:
             fp = self.wfile('.hgtags', 'rb+')
-        except IOError, e:
+        except IOError as e:
             if e.errno != errno.ENOENT:
                 raise
             fp = self.wfile('.hgtags', 'ab')
@@ -628,7 +631,7 @@
 
         if not local:
             m = matchmod.exact(self.root, '', ['.hgtags'])
-            if util.any(self.status(match=m, unknown=True, ignored=True)):
+            if any(self.status(match=m, unknown=True, ignored=True)):
                 raise util.Abort(_('working copy of .hgtags is changed'),
                                  hint=_('please commit .hgtags manually'))
 
@@ -798,11 +801,16 @@
     def local(self):
         return self
 
+    def publishing(self):
+        # it's safe (and desirable) to trust the publish flag unconditionally
+        # so that we don't finalize changes shared between users via ssh or nfs
+        return self.ui.configbool('phases', 'publish', True, untrusted=True)
+
     def cancopy(self):
         # so statichttprepo's override of local() works
         if not self.local():
             return False
-        if not self.ui.configbool('phases', 'publish', True):
+        if not self.publishing():
             return True
         # if publishing we can't copy if there is filtered content
         return not self.filtered('visible').changelog.filteredrevs
@@ -945,11 +953,11 @@
         return None
 
     def transaction(self, desc, report=None):
-        if (self.ui.configbool('devel', 'all')
+        if (self.ui.configbool('devel', 'all-warnings')
                 or self.ui.configbool('devel', 'check-locks')):
             l = self._lockref and self._lockref()
             if l is None or not l.held:
-                scmutil.develwarn(self.ui, 'transaction with no lock')
+                self.ui.develwarn('transaction with no lock')
         tr = self.currenttransaction()
         if tr is not None:
             return tr.nest()
@@ -979,7 +987,7 @@
             reporef().hook('pretxnclose', throw=True, pending=pending,
                            txnname=desc, **tr.hookargs)
 
-        tr = transaction.transaction(rp, self.sopener, vfsmap,
+        tr = transaction.transaction(rp, self.svfs, vfsmap,
                                      "journal",
                                      "undo",
                                      aftertrans(renames),
@@ -1185,7 +1193,7 @@
     def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc):
         try:
             l = lockmod.lock(vfs, lockname, 0, releasefn, desc=desc)
-        except error.LockHeld, inst:
+        except error.LockHeld as inst:
             if not wait:
                 raise
             self.ui.warn(_("waiting for lock on %s held by %r\n") %
@@ -1250,11 +1258,11 @@
 
         # We do not need to check for non-waiting lock aquisition.  Such
         # acquisition would not cause dead-lock as they would just fail.
-        if wait and (self.ui.configbool('devel', 'all')
+        if wait and (self.ui.configbool('devel', 'all-warnings')
                      or self.ui.configbool('devel', 'check-locks')):
             l = self._lockref and self._lockref()
             if l is not None and l.held:
-                scmutil.develwarn(self.ui, '"wlock" acquired after "lock"')
+                self.ui.develwarn('"wlock" acquired after "lock"')
 
         def unlock():
             if self.dirstate.pendingparentchange():
@@ -1382,7 +1390,7 @@
             wctx = self[None]
             merge = len(wctx.parents()) > 1
 
-            if not force and merge and not match.always():
+            if not force and merge and match.ispartial():
                 raise util.Abort(_('cannot partially commit a merge '
                                    '(do not specify files or patterns)'))
 
@@ -1445,7 +1453,7 @@
                     status.removed.insert(0, '.hgsubstate')
 
             # make sure all explicit patterns are matched
-            if not force and match.files():
+            if not force and (match.isexact() or match.prefix()):
                 matched = set(status.modified + status.added + status.removed)
 
                 for f in match.files():
@@ -1467,9 +1475,11 @@
             cctx = context.workingcommitctx(self, status,
                                             text, user, date, extra)
 
-            if (not force and not extra.get("close") and not merge
-                and not cctx.files()
-                and wctx.branch() == wctx.p1().branch()):
+            # internal config: ui.allowemptycommit
+            allowemptycommit = (wctx.branch() != wctx.p1().branch()
+                                or extra.get('close') or merge or cctx.files()
+                                or self.ui.configbool('ui', 'allowemptycommit'))
+            if not allowemptycommit:
                 return None
 
             if merge and cctx.deleted():
@@ -1522,7 +1532,7 @@
         def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
             # hack for command that use a temporary commit (eg: histedit)
             # temporary commit got stripped before hook release
-            if node in self:
+            if self.changelog.hasnode(ret):
                 self.hook("commit", node=node, parent1=parent1,
                           parent2=parent2)
         self._afterlock(commithook)
@@ -1565,10 +1575,10 @@
                             m[f] = self._filecommit(fctx, m1, m2, linkrev,
                                                     trp, changed)
                             m.setflag(f, fctx.flags())
-                    except OSError, inst:
+                    except OSError as inst:
                         self.ui.warn(_("trouble committing %s!\n") % f)
                         raise
-                    except IOError, inst:
+                    except IOError as inst:
                         errcode = getattr(inst, 'errno', errno.ENOENT)
                         if error or errcode and errcode != errno.ENOENT:
                             self.ui.warn(_("trouble committing %s!\n") % f)
@@ -1755,89 +1765,55 @@
         """
         return util.hooks()
 
-    def stream_in(self, remote, requirements):
+    def stream_in(self, remote, remotereqs):
+        # Save remote branchmap. We will use it later
+        # to speed up branchcache creation
+        rbranchmap = None
+        if remote.capable("branchmap"):
+            rbranchmap = remote.branchmap()
+
+        fp = remote.stream_out()
+        l = fp.readline()
+        try:
+            resp = int(l)
+        except ValueError:
+            raise error.ResponseError(
+                _('unexpected response from remote server:'), l)
+        if resp == 1:
+            raise util.Abort(_('operation forbidden by server'))
+        elif resp == 2:
+            raise util.Abort(_('locking the remote repository failed'))
+        elif resp != 0:
+            raise util.Abort(_('the server sent an unknown error code'))
+
+        self.applystreamclone(remotereqs, rbranchmap, fp)
+        return len(self.heads()) + 1
+
+    def applystreamclone(self, remotereqs, remotebranchmap, fp):
+        """Apply stream clone data to this repository.
+
+        "remotereqs" is a set of requirements to handle the incoming data.
+        "remotebranchmap" is the result of a branchmap lookup on the remote. It
+        can be None.
+        "fp" is a file object containing the raw stream data, suitable for
+        feeding into exchange.consumestreamclone.
+        """
         lock = self.lock()
         try:
-            # Save remote branchmap. We will use it later
-            # to speed up branchcache creation
-            rbranchmap = None
-            if remote.capable("branchmap"):
-                rbranchmap = remote.branchmap()
-
-            fp = remote.stream_out()
-            l = fp.readline()
-            try:
-                resp = int(l)
-            except ValueError:
-                raise error.ResponseError(
-                    _('unexpected response from remote server:'), l)
-            if resp == 1:
-                raise util.Abort(_('operation forbidden by server'))
-            elif resp == 2:
-                raise util.Abort(_('locking the remote repository failed'))
-            elif resp != 0:
-                raise util.Abort(_('the server sent an unknown error code'))
-            self.ui.status(_('streaming all changes\n'))
-            l = fp.readline()
-            try:
-                total_files, total_bytes = map(int, l.split(' ', 1))
-            except (ValueError, TypeError):
-                raise error.ResponseError(
-                    _('unexpected response from remote server:'), l)
-            self.ui.status(_('%d files to transfer, %s of data\n') %
-                           (total_files, util.bytecount(total_bytes)))
-            handled_bytes = 0
-            self.ui.progress(_('clone'), 0, total=total_bytes)
-            start = time.time()
-
-            tr = self.transaction(_('clone'))
-            try:
-                for i in xrange(total_files):
-                    # XXX doesn't support '\n' or '\r' in filenames
-                    l = fp.readline()
-                    try:
-                        name, size = l.split('\0', 1)
-                        size = int(size)
-                    except (ValueError, TypeError):
-                        raise error.ResponseError(
-                            _('unexpected response from remote server:'), l)
-                    if self.ui.debugflag:
-                        self.ui.debug('adding %s (%s)\n' %
-                                      (name, util.bytecount(size)))
-                    # for backwards compat, name was partially encoded
-                    ofp = self.svfs(store.decodedir(name), 'w')
-                    for chunk in util.filechunkiter(fp, limit=size):
-                        handled_bytes += len(chunk)
-                        self.ui.progress(_('clone'), handled_bytes,
-                                         total=total_bytes)
-                        ofp.write(chunk)
-                    ofp.close()
-                tr.close()
-            finally:
-                tr.release()
-
-            # Writing straight to files circumvented the inmemory caches
-            self.invalidate()
-
-            elapsed = time.time() - start
-            if elapsed <= 0:
-                elapsed = 0.001
-            self.ui.progress(_('clone'), None)
-            self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
-                           (util.bytecount(total_bytes), elapsed,
-                            util.bytecount(total_bytes / elapsed)))
+            exchange.consumestreamclone(self, fp)
 
             # new requirements = old non-format requirements +
-            #                    new format-related
+            #                    new format-related remote requirements
             # requirements from the streamed-in repository
-            requirements.update(set(self.requirements) - self.supportedformats)
-            self._applyrequirements(requirements)
+            self.requirements = remotereqs | (
+                    self.requirements - self.supportedformats)
+            self._applyopenerreqs()
             self._writerequirements()
 
-            if rbranchmap:
+            if remotebranchmap:
                 rbheads = []
                 closed = []
-                for bheads in rbranchmap.itervalues():
+                for bheads in remotebranchmap.itervalues():
                     rbheads.extend(bheads)
                     for h in bheads:
                         r = self.changelog.rev(h)
@@ -1848,7 +1824,7 @@
                 if rbheads:
                     rtiprev = max((int(self.changelog.rev(node))
                             for node in rbheads))
-                    cache = branchmap.branchcache(rbranchmap,
+                    cache = branchmap.branchcache(remotebranchmap,
                                                   self[rtiprev].node(),
                                                   rtiprev,
                                                   closednodes=closed)
@@ -1861,7 +1837,6 @@
                             cache.write(rview)
                             break
             self.invalidate()
-            return len(self.heads()) + 1
         finally:
             lock.release()
 
@@ -1897,6 +1872,7 @@
                     if not streamreqs - self.supportedformats:
                         self.stream_in(remote, streamreqs)
 
+        # internal config: ui.quietbookmarkmove
         quiet = self.ui.backupconfig('ui', 'quietbookmarkmove')
         try:
             self.ui.setconfig('ui', 'quietbookmarkmove', True, 'clone')
@@ -1918,7 +1894,7 @@
             hookargs['old'] = old
             hookargs['new'] = new
             self.hook('prepushkey', throw=True, **hookargs)
-        except error.HookAbort, exc:
+        except error.HookAbort as exc:
             self.ui.write_err(_("pushkey-abort: %s\n") % exc)
             if exc.hint:
                 self.ui.write_err(_("(%s)\n") % exc.hint)
--- a/mercurial/lock.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/lock.py	Sat Jul 18 17:32:38 2015 -0500
@@ -58,7 +58,7 @@
             try:
                 self.trylock()
                 return self.timeout - timeout
-            except error.LockHeld, inst:
+            except error.LockHeld as inst:
                 if timeout != 0:
                     time.sleep(1)
                     if timeout > 0:
@@ -78,7 +78,7 @@
             try:
                 self.vfs.makelock(lockname, self.f)
                 self.held = 1
-            except (OSError, IOError), why:
+            except (OSError, IOError) as why:
                 if why.errno == errno.EEXIST:
                     locker = self.testlock()
                     if locker is not None:
@@ -102,7 +102,7 @@
         """
         try:
             locker = self.vfs.readlock(self.f)
-        except (OSError, IOError), why:
+        except (OSError, IOError) as why:
             if why.errno == errno.ENOENT:
                 return None
             raise
--- a/mercurial/mail.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/mail.py	Sat Jul 18 17:32:38 2015 -0500
@@ -9,10 +9,6 @@
 import util, encoding, sslutil
 import os, smtplib, socket, quopri, time, sys
 import email
-# On python2.4 you have to import these by name or they fail to
-# load. This was not a problem on Python 2.7.
-import email.Header
-import email.MIMEText
 
 _oldheaderinit = email.Header.Header.__init__
 def _unifiedheaderinit(self, *args, **kw):
@@ -49,8 +45,8 @@
             raise smtplib.SMTPException(msg)
         (resp, reply) = self.docmd("STARTTLS")
         if resp == 220:
-            self.sock = sslutil.ssl_wrap_socket(self.sock, keyfile, certfile,
-                                                **self._sslkwargs)
+            self.sock = sslutil.wrapsocket(self.sock, keyfile, certfile,
+                                           **self._sslkwargs)
             if not util.safehasattr(self.sock, "read"):
                 # using httplib.FakeSocket with Python 2.5.x or earlier
                 self.sock.read = self.sock.recv
@@ -78,9 +74,9 @@
             if self.debuglevel > 0:
                 print >> sys.stderr, 'connect:', (host, port)
             new_socket = socket.create_connection((host, port), timeout)
-            new_socket = sslutil.ssl_wrap_socket(new_socket,
-                                                 self.keyfile, self.certfile,
-                                                 **self._sslkwargs)
+            new_socket = sslutil.wrapsocket(new_socket,
+                                            self.keyfile, self.certfile,
+                                            **self._sslkwargs)
             self.file = smtplib.SSLFakeFile(new_socket)
             return new_socket
 else:
@@ -108,7 +104,8 @@
     if (starttls or smtps) and verifycert:
         sslkwargs = sslutil.sslkwargs(ui, mailhost)
     else:
-        sslkwargs = {}
+        # 'ui' is required by sslutil.wrapsocket() and set by sslkwargs()
+        sslkwargs = {'ui': ui}
     if smtps:
         ui.note(_('(using smtps)\n'))
         s = SMTPS(sslkwargs, local_hostname=local_hostname)
@@ -141,23 +138,23 @@
                   (username))
         try:
             s.login(username, password)
-        except smtplib.SMTPException, inst:
+        except smtplib.SMTPException as inst:
             raise util.Abort(inst)
 
     def send(sender, recipients, msg):
         try:
             return s.sendmail(sender, recipients, msg)
-        except smtplib.SMTPRecipientsRefused, inst:
+        except smtplib.SMTPRecipientsRefused as inst:
             recipients = [r[1] for r in inst.recipients.values()]
             raise util.Abort('\n' + '\n'.join(recipients))
-        except smtplib.SMTPException, inst:
+        except smtplib.SMTPException as inst:
             raise util.Abort(inst)
 
     return send
 
 def _sendmail(ui, sender, recipients, msg):
     '''send mail using sendmail.'''
-    program = ui.config('email', 'method')
+    program = ui.config('email', 'method', 'smtp')
     cmdline = '%s -f %s %s' % (program, util.email(sender),
                                ' '.join(map(util.email, recipients)))
     ui.note(_('sending mail: %s\n') % cmdline)
--- a/mercurial/manifest.c	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/manifest.c	Sat Jul 18 17:32:38 2015 -0500
@@ -235,7 +235,7 @@
 	PyObject *ret = NULL, *path = NULL, *hash = NULL, *flags = NULL;
 	l = lmiter_nextline((lmIter *)o);
 	if (!l) {
-		goto bail;
+		goto done;
 	}
 	pl = pathlen(l);
 	path = PyString_FromStringAndSize(l->start, pl);
@@ -244,10 +244,10 @@
 	flags = PyString_FromStringAndSize(l->start + consumed,
 									   l->len - consumed - 1);
 	if (!path || !hash || !flags) {
-		goto bail;
+		goto done;
 	}
 	ret = PyTuple_Pack(3, path, hash, flags);
- bail:
+done:
 	Py_XDECREF(path);
 	Py_XDECREF(hash);
 	Py_XDECREF(flags);
@@ -672,7 +672,7 @@
 	copy->pydata = self->pydata;
 	Py_INCREF(copy->pydata);
 	return copy;
- nomem:
+nomem:
 	PyErr_NoMemory();
 	Py_XDECREF(copy);
 	return NULL;
@@ -724,7 +724,7 @@
 	}
 	copy->livelines = copy->numlines;
 	return copy;
- nomem:
+nomem:
 	PyErr_NoMemory();
 	Py_XDECREF(copy);
 	return NULL;
@@ -845,7 +845,7 @@
 	}
 	Py_DECREF(emptyTup);
 	return ret;
- nomem:
+nomem:
 	PyErr_NoMemory();
 	Py_XDECREF(ret);
 	Py_XDECREF(emptyTup);
--- a/mercurial/manifest.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/manifest.py	Sat Jul 18 17:32:38 2015 -0500
@@ -219,7 +219,7 @@
         files instead of over manifest files.'''
         files = match.files()
         return (len(files) < 100 and (match.isexact() or
-            (not match.anypats() and util.all(fn in self for fn in files))))
+            (match.prefix() and all(fn in self for fn in files))))
 
     def walk(self, match):
         '''Generates matching file names.
@@ -441,32 +441,64 @@
     else:
         return '', f
 
+_noop = lambda: None
+
 class treemanifest(object):
     def __init__(self, dir='', text=''):
         self._dir = dir
+        self._node = revlog.nullid
+        self._load = _noop
+        self._dirty = False
         self._dirs = {}
         # Using _lazymanifest here is a little slower than plain old dicts
         self._files = {}
         self._flags = {}
-        self.parse(text)
+        if text:
+            def readsubtree(subdir, subm):
+                raise AssertionError('treemanifest constructor only accepts '
+                                     'flat manifests')
+            self.parse(text, readsubtree)
+            self._dirty = True # Mark flat manifest dirty after parsing
 
     def _subpath(self, path):
         return self._dir + path
 
     def __len__(self):
+        self._load()
         size = len(self._files)
         for m in self._dirs.values():
             size += m.__len__()
         return size
 
     def _isempty(self):
+        self._load() # for consistency; already loaded by all callers
         return (not self._files and (not self._dirs or
-                util.all(m._isempty() for m in self._dirs.values())))
+                all(m._isempty() for m in self._dirs.values())))
 
     def __str__(self):
-        return '<treemanifest dir=%s>' % self._dir
+        return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s>' %
+                (self._dir, revlog.hex(self._node),
+                 bool(self._load is _noop),
+                 self._dirty))
+
+    def dir(self):
+        '''The directory that this tree manifest represents, including a
+        trailing '/'. Empty string for the repo root directory.'''
+        return self._dir
+
+    def node(self):
+        '''This node of this instance. nullid for unsaved instances. Should
+        be updated when the instance is read or written from a revlog.
+        '''
+        assert not self._dirty
+        return self._node
+
+    def setnode(self, node):
+        self._node = node
+        self._dirty = False
 
     def iteritems(self):
+        self._load()
         for p, n in sorted(self._dirs.items() + self._files.items()):
             if p in self._files:
                 yield self._subpath(p), n
@@ -475,6 +507,7 @@
                     yield f, sn
 
     def iterkeys(self):
+        self._load()
         for p in sorted(self._dirs.keys() + self._files.keys()):
             if p in self._files:
                 yield self._subpath(p)
@@ -491,6 +524,7 @@
     def __contains__(self, f):
         if f is None:
             return False
+        self._load()
         dir, subpath = _splittopdir(f)
         if dir:
             if dir not in self._dirs:
@@ -500,6 +534,7 @@
             return f in self._files
 
     def get(self, f, default=None):
+        self._load()
         dir, subpath = _splittopdir(f)
         if dir:
             if dir not in self._dirs:
@@ -509,6 +544,7 @@
             return self._files.get(f, default)
 
     def __getitem__(self, f):
+        self._load()
         dir, subpath = _splittopdir(f)
         if dir:
             return self._dirs[dir].__getitem__(subpath)
@@ -516,6 +552,7 @@
             return self._files[f]
 
     def flags(self, f):
+        self._load()
         dir, subpath = _splittopdir(f)
         if dir:
             if dir not in self._dirs:
@@ -527,6 +564,7 @@
             return self._flags.get(f, '')
 
     def find(self, f):
+        self._load()
         dir, subpath = _splittopdir(f)
         if dir:
             return self._dirs[dir].find(subpath)
@@ -534,6 +572,7 @@
             return self._files[f], self._flags.get(f, '')
 
     def __delitem__(self, f):
+        self._load()
         dir, subpath = _splittopdir(f)
         if dir:
             self._dirs[dir].__delitem__(subpath)
@@ -544,9 +583,11 @@
             del self._files[f]
             if f in self._flags:
                 del self._flags[f]
+        self._dirty = True
 
     def __setitem__(self, f, n):
         assert n is not None
+        self._load()
         dir, subpath = _splittopdir(f)
         if dir:
             if dir not in self._dirs:
@@ -554,9 +595,12 @@
             self._dirs[dir].__setitem__(subpath, n)
         else:
             self._files[f] = n[:21] # to match manifestdict's behavior
+        self._dirty = True
 
     def setflag(self, f, flags):
         """Set the flags (symlink, executable) for path f."""
+        assert 'd' not in flags
+        self._load()
         dir, subpath = _splittopdir(f)
         if dir:
             if dir not in self._dirs:
@@ -564,19 +608,35 @@
             self._dirs[dir].setflag(subpath, flags)
         else:
             self._flags[f] = flags
+        self._dirty = True
 
     def copy(self):
         copy = treemanifest(self._dir)
-        for d in self._dirs:
-            copy._dirs[d] = self._dirs[d].copy()
-        copy._files = dict.copy(self._files)
-        copy._flags = dict.copy(self._flags)
+        copy._node = self._node
+        copy._dirty = self._dirty
+        def _load():
+            self._load()
+            for d in self._dirs:
+                copy._dirs[d] = self._dirs[d].copy()
+            copy._files = dict.copy(self._files)
+            copy._flags = dict.copy(self._flags)
+            copy._load = _noop
+        copy._load = _load
+        if self._load == _noop:
+            # Chaining _load if it's _noop is functionally correct, but the
+            # chain may end up excessively long (stack overflow), and
+            # will prevent garbage collection of 'self'.
+            copy._load()
         return copy
 
     def filesnotin(self, m2):
         '''Set of files in this manifest that are not in the other'''
         files = set()
         def _filesnotin(t1, t2):
+            if t1._node == t2._node and not t1._dirty and not t2._dirty:
+                return
+            t1._load()
+            t2._load()
             for d, m1 in t1._dirs.iteritems():
                 if d in t2._dirs:
                     m2 = t2._dirs[d]
@@ -599,6 +659,7 @@
         return self._alldirs
 
     def hasdir(self, dir):
+        self._load()
         topdir, subdir = _splittopdir(dir)
         if topdir:
             if topdir in self._dirs:
@@ -635,27 +696,20 @@
             if not self.hasdir(fn):
                 match.bad(fn, None)
 
-    def _walk(self, match, alldirs=False):
-        '''Recursively generates matching file names for walk().
-
-        Will visit all subdirectories if alldirs is True, otherwise it will
-        only visit subdirectories for which match.visitdir is True.'''
-
-        if not alldirs:
-            # substring to strip trailing slash
-            visit = match.visitdir(self._dir[:-1] or '.')
-            if not visit:
-                return
-            alldirs = (visit == 'all')
+    def _walk(self, match):
+        '''Recursively generates matching file names for walk().'''
+        if not match.visitdir(self._dir[:-1] or '.'):
+            return
 
         # yield this dir's files and walk its submanifests
+        self._load()
         for p in sorted(self._dirs.keys() + self._files.keys()):
             if p in self._files:
                 fullp = self._subpath(p)
                 if match(fullp):
                     yield fullp
             else:
-                for f in self._dirs[p]._walk(match, alldirs):
+                for f in self._dirs[p]._walk(match):
                     yield f
 
     def matches(self, match):
@@ -665,20 +719,15 @@
 
         return self._matches(match)
 
-    def _matches(self, match, alldirs=False):
+    def _matches(self, match):
         '''recursively generate a new manifest filtered by the match argument.
-
-        Will visit all subdirectories if alldirs is True, otherwise it will
-        only visit subdirectories for which match.visitdir is True.'''
-
+        '''
         ret = treemanifest(self._dir)
-        if not alldirs:
-            # substring to strip trailing slash
-            visit = match.visitdir(self._dir[:-1] or '.')
-            if not visit:
-                return ret
-            alldirs = (visit == 'all')
 
+        if not match.visitdir(self._dir[:-1] or '.'):
+            return ret
+
+        self._load()
         for fn in self._files:
             fullp = self._subpath(fn)
             if not match(fullp):
@@ -688,10 +737,12 @@
                 ret._flags[fn] = self._flags[fn]
 
         for dir, subm in self._dirs.iteritems():
-            m = subm._matches(match, alldirs)
+            m = subm._matches(match)
             if not m._isempty():
                 ret._dirs[dir] = m
 
+        if not ret._isempty():
+            ret._dirty = True
         return ret
 
     def diff(self, m2, clean=False):
@@ -712,6 +763,10 @@
         result = {}
         emptytree = treemanifest()
         def _diff(t1, t2):
+            if t1._node == t2._node and not t1._dirty and not t2._dirty:
+                return
+            t1._load()
+            t2._load()
             for d, m1 in t1._dirs.iteritems():
                 m2 = t2._dirs.get(d, emptytree)
                 _diff(m1, m2)
@@ -737,20 +792,71 @@
         _diff(self, m2)
         return result
 
-    def parse(self, text):
+    def unmodifiedsince(self, m2):
+        return not self._dirty and not m2._dirty and self._node == m2._node
+
+    def parse(self, text, readsubtree):
         for f, n, fl in _parse(text):
-            self[f] = n
-            if fl:
-                self.setflag(f, fl)
+            if fl == 'd':
+                f = f + '/'
+                self._dirs[f] = readsubtree(self._subpath(f), n)
+            elif '/' in f:
+                # This is a flat manifest, so use __setitem__ and setflag rather
+                # than assigning directly to _files and _flags, so we can
+                # assign a path in a subdirectory, and to mark dirty (compared
+                # to nullid).
+                self[f] = n
+                if fl:
+                    self.setflag(f, fl)
+            else:
+                # Assigning to _files and _flags avoids marking as dirty,
+                # and should be a little faster.
+                self._files[f] = n
+                if fl:
+                    self._flags[f] = fl
 
     def text(self, usemanifestv2=False):
         """Get the full data of this manifest as a bytestring."""
+        self._load()
         flags = self.flags
         return _text(((f, self[f], flags(f)) for f in self.keys()),
                      usemanifestv2)
 
+    def dirtext(self, usemanifestv2=False):
+        """Get the full data of this directory as a bytestring. Make sure that
+        any submanifests have been written first, so their nodeids are correct.
+        """
+        self._load()
+        flags = self.flags
+        dirs = [(d[:-1], self._dirs[d]._node, 'd') for d in self._dirs]
+        files = [(f, self._files[f], flags(f)) for f in self._files]
+        return _text(sorted(dirs + files), usemanifestv2)
+
+    def read(self, gettext, readsubtree):
+        def _load():
+            # Mark as loaded already here, so __setitem__ and setflag() don't
+            # cause infinite loops when they try to load.
+            self._load = _noop
+            self.parse(gettext(), readsubtree)
+            self._dirty = False
+        self._load = _load
+
+    def writesubtrees(self, m1, m2, writesubtree):
+        self._load() # for consistency; should never have any effect here
+        emptytree = treemanifest()
+        for d, subm in self._dirs.iteritems():
+            subp1 = m1._dirs.get(d, emptytree)._node
+            subp2 = m2._dirs.get(d, emptytree)._node
+            if subp1 == revlog.nullid:
+                subp1, subp2 = subp2, subp1
+            writesubtree(subm, subp1, subp2)
+
 class manifest(revlog.revlog):
-    def __init__(self, opener):
+    def __init__(self, opener, dir='', dirlogcache=None):
+        '''The 'dir' and 'dirlogcache' arguments are for internal use by
+        manifest.manifest only. External users should create a root manifest
+        log with manifest.manifest(opener) and call dirlog() on it.
+        '''
         # During normal operations, we expect to deal with not more than four
         # revs at a time (such as during commit --amend). When rebasing large
         # stacks of commits, the number can go up, hence the config knob below.
@@ -760,19 +866,38 @@
         opts = getattr(opener, 'options', None)
         if opts is not None:
             cachesize = opts.get('manifestcachesize', cachesize)
-            usetreemanifest = opts.get('usetreemanifest', usetreemanifest)
+            usetreemanifest = opts.get('treemanifest', usetreemanifest)
             usemanifestv2 = opts.get('manifestv2', usemanifestv2)
         self._mancache = util.lrucachedict(cachesize)
-        revlog.revlog.__init__(self, opener, "00manifest.i")
         self._treeinmem = usetreemanifest
         self._treeondisk = usetreemanifest
         self._usemanifestv2 = usemanifestv2
+        indexfile = "00manifest.i"
+        if dir:
+            assert self._treeondisk
+            if not dir.endswith('/'):
+                dir = dir + '/'
+            indexfile = "meta/" + dir + "00manifest.i"
+        revlog.revlog.__init__(self, opener, indexfile)
+        self._dir = dir
+        # The dirlogcache is kept on the root manifest log
+        if dir:
+            self._dirlogcache = dirlogcache
+        else:
+            self._dirlogcache = {'': self}
 
     def _newmanifest(self, data=''):
         if self._treeinmem:
-            return treemanifest('', data)
+            return treemanifest(self._dir, data)
         return manifestdict(data)
 
+    def dirlog(self, dir):
+        assert self._treeondisk
+        if dir not in self._dirlogcache:
+            self._dirlogcache[dir] = manifest(self.opener, dir,
+                                              self._dirlogcache)
+        return self._dirlogcache[dir]
+
     def _slowreaddelta(self, node):
         r0 = self.deltaparent(self.rev(node))
         m0 = self.read(self.node(r0))
@@ -793,7 +918,13 @@
         return self._newmanifest(d)
 
     def readfast(self, node):
-        '''use the faster of readdelta or read'''
+        '''use the faster of readdelta or read
+
+        This will return a manifest which is either only the files
+        added/modified relative to p1, or all files in the
+        manifest. Which one is returned depends on the codepath used
+        to retrieve the data.
+        '''
         r = self.rev(node)
         deltaparent = self.deltaparent(r)
         if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
@@ -805,9 +936,19 @@
             return self._newmanifest() # don't upset local cache
         if node in self._mancache:
             return self._mancache[node][0]
-        text = self.revision(node)
-        arraytext = array.array('c', text)
-        m = self._newmanifest(text)
+        if self._treeondisk:
+            def gettext():
+                return self.revision(node)
+            def readsubtree(dir, subm):
+                return self.dirlog(dir).read(subm)
+            m = self._newmanifest()
+            m.read(gettext, readsubtree)
+            m.setnode(node)
+            arraytext = None
+        else:
+            text = self.revision(node)
+            m = self._newmanifest(text)
+            arraytext = array.array('c', text)
         self._mancache[node] = (m, arraytext)
         return m
 
@@ -845,10 +986,37 @@
             # just encode a fulltext of the manifest and pass that
             # through to the revlog layer, and let it handle the delta
             # process.
-            text = m.text(self._usemanifestv2)
-            arraytext = array.array('c', text)
-            n = self.addrevision(text, transaction, link, p1, p2)
+            if self._treeondisk:
+                m1 = self.read(p1)
+                m2 = self.read(p2)
+                n = self._addtree(m, transaction, link, m1, m2)
+                arraytext = None
+            else:
+                text = m.text(self._usemanifestv2)
+                n = self.addrevision(text, transaction, link, p1, p2)
+                arraytext = array.array('c', text)
 
         self._mancache[n] = (m, arraytext)
 
         return n
+
+    def _addtree(self, m, transaction, link, m1, m2):
+        # If the manifest is unchanged compared to one parent,
+        # don't write a new revision
+        if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
+            return m.node()
+        def writesubtree(subm, subp1, subp2):
+            sublog = self.dirlog(subm.dir())
+            sublog.add(subm, transaction, link, subp1, subp2, None, None)
+        m.writesubtrees(m1, m2, writesubtree)
+        text = m.dirtext(self._usemanifestv2)
+        # Double-check whether contents are unchanged to one parent
+        if text == m1.dirtext(self._usemanifestv2):
+            n = m1.node()
+        elif text == m2.dirtext(self._usemanifestv2):
+            n = m2.node()
+        else:
+            n = self.addrevision(text, transaction, link, m1.node(), m2.node())
+        # Save nodeid so parent manifest can calculate its nodeid
+        m.setnode(n)
+        return n
--- a/mercurial/match.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/match.py	Sat Jul 18 17:32:38 2015 -0500
@@ -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.
 
-import re
+import copy, re
 import util, pathutil
 from i18n import _
 
@@ -21,33 +21,64 @@
     except AttributeError:
         return m.match
 
-def _expandsets(kindpats, ctx):
+def _expandsets(kindpats, ctx, listsubrepos):
     '''Returns the kindpats list with the 'set' patterns expanded.'''
     fset = set()
     other = []
 
-    for kind, pat in kindpats:
+    for kind, pat, source in kindpats:
         if kind == 'set':
             if not ctx:
                 raise util.Abort("fileset expression with no context")
             s = ctx.getfileset(pat)
             fset.update(s)
+
+            if listsubrepos:
+                for subpath in ctx.substate:
+                    s = ctx.sub(subpath).getfileset(pat)
+                    fset.update(subpath + '/' + f for f in s)
+
             continue
-        other.append((kind, pat))
+        other.append((kind, pat, source))
     return fset, other
 
+def _expandsubinclude(kindpats, root):
+    '''Returns the list of subinclude matchers and the kindpats without the
+    subincludes in it.'''
+    relmatchers = []
+    other = []
+
+    for kind, pat, source in kindpats:
+        if kind == 'subinclude':
+            sourceroot = pathutil.dirname(util.normpath(source))
+            pat = util.pconvert(pat)
+            path = pathutil.join(sourceroot, pat)
+
+            newroot = pathutil.dirname(path)
+            relmatcher = match(newroot, '', [], ['include:%s' % path])
+
+            prefix = pathutil.canonpath(root, root, newroot)
+            if prefix:
+                prefix += '/'
+            relmatchers.append((prefix, relmatcher))
+        else:
+            other.append((kind, pat, source))
+
+    return relmatchers, other
+
 def _kindpatsalwaysmatch(kindpats):
     """"Checks whether the kindspats match everything, as e.g.
     'relpath:.' does.
     """
-    for kind, pat in kindpats:
+    for kind, pat, source in kindpats:
         if pat != '' or kind not in ['relpath', 'glob']:
             return False
     return True
 
 class match(object):
     def __init__(self, root, cwd, patterns, include=[], exclude=[],
-                 default='glob', exact=False, auditor=None, ctx=None):
+                 default='glob', exact=False, auditor=None, ctx=None,
+                 listsubrepos=False, warn=None, badfn=None):
         """build an object to match a set of file patterns
 
         arguments:
@@ -58,6 +89,8 @@
         exclude - patterns to exclude (even if they are included)
         default - if a pattern in patterns has no explicit type, assume this one
         exact - patterns are actually filenames (include/exclude still apply)
+        warn - optional function used for printing warnings
+        badfn - optional bad() callback for this matcher instead of the default
 
         a pattern is one of:
         'glob:<glob>' - a glob relative to cwd
@@ -67,6 +100,9 @@
         'relpath:<path>' - a path relative to cwd
         'relre:<regexp>' - a regexp that needn't match the start of a name
         'set:<fileset>' - a fileset expression
+        'include:<path>' - a file of patterns to read and include
+        'subinclude:<path>' - a file of patterns to match against files under
+                              the same directory
         '<something>' - a pattern of the specified default type
         """
 
@@ -76,15 +112,28 @@
         self._anypats = bool(include or exclude)
         self._always = False
         self._pathrestricted = bool(include or exclude or patterns)
+        self._warn = warn
+        self._includeroots = set()
+        self._includedirs = set(['.'])
+        self._excluderoots = set()
+
+        if badfn is not None:
+            self.bad = badfn
 
         matchfns = []
         if include:
             kindpats = self._normalize(include, 'glob', root, cwd, auditor)
-            self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)')
+            self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)',
+                                              listsubrepos, root)
+            self._includeroots.update(_roots(kindpats))
+            self._includedirs.update(util.dirs(self._includeroots))
             matchfns.append(im)
         if exclude:
             kindpats = self._normalize(exclude, 'glob', root, cwd, auditor)
-            self.excludepat, em = _buildmatch(ctx, kindpats, '(?:/|$)')
+            self.excludepat, em = _buildmatch(ctx, kindpats, '(?:/|$)',
+                                              listsubrepos, root)
+            if not _anypats(kindpats):
+                self._excluderoots.update(_roots(kindpats))
             matchfns.append(lambda f: not em(f))
         if exact:
             if isinstance(patterns, list):
@@ -97,7 +146,8 @@
             if not _kindpatsalwaysmatch(kindpats):
                 self._files = _roots(kindpats)
                 self._anypats = self._anypats or _anypats(kindpats)
-                self.patternspat, pm = _buildmatch(ctx, kindpats, '$')
+                self.patternspat, pm = _buildmatch(ctx, kindpats, '$',
+                                                   listsubrepos, root)
                 matchfns.append(pm)
 
         if not matchfns:
@@ -113,7 +163,7 @@
                 return True
 
         self.matchfn = m
-        self._fmap = set(self._files)
+        self._fileroots = set(self._files)
 
     def __call__(self, fn):
         return self.matchfn(fn)
@@ -161,21 +211,35 @@
 
     @propertycache
     def _dirs(self):
-        return set(util.dirs(self._fmap)) | set(['.'])
+        return set(util.dirs(self._fileroots)) | set(['.'])
 
     def visitdir(self, dir):
-        '''Helps while traversing a directory tree. Returns the string 'all' if
-        the given directory and all subdirectories should be visited. Otherwise
-        returns True or False indicating whether the given directory should be
-        visited. If 'all' is returned, calling this method on a subdirectory
-        gives an undefined result.'''
-        if not self._fmap or self.exact(dir):
-            return 'all'
-        return dir in self._dirs
+        '''Decides whether a directory should be visited based on whether it
+        has potential matches in it or one of its subdirectories. This is
+        based on the match's primary, included, and excluded patterns.
+
+        This function's behavior is undefined if it has returned False for
+        one of the dir's parent directories.
+        '''
+        if dir in self._excluderoots:
+            return False
+        if (self._includeroots and
+            '.' not in self._includeroots and
+            dir not in self._includeroots and
+            dir not in self._includedirs and
+            not any(parent in self._includeroots
+                    for parent in util.finddirs(dir))):
+            return False
+        return (not self._fileroots or
+                '.' in self._fileroots or
+                dir in self._fileroots or
+                dir in self._dirs or
+                any(parentdir in self._fileroots
+                    for parentdir in util.finddirs(dir)))
 
     def exact(self, f):
         '''Returns True if f is in .files().'''
-        return f in self._fmap
+        return f in self._fileroots
 
     def anypats(self):
         '''Matcher uses patterns or include/exclude.'''
@@ -186,9 +250,20 @@
         - optimization might be possible and necessary.'''
         return self._always
 
+    def ispartial(self):
+        '''True if the matcher won't always match.
+
+        Although it's just the inverse of _always in this implementation,
+        an extenion such as narrowhg might make it return something
+        slightly different.'''
+        return not self._always
+
     def isexact(self):
         return self.matchfn == self.exact
 
+    def prefix(self):
+        return not self.always() and not self.isexact() and not self.anypats()
+
     def _normalize(self, patterns, default, root, cwd, auditor):
         '''Convert 'kind:pat' from the patterns list to tuples with kind and
         normalized and rooted patterns and with listfiles expanded.'''
@@ -208,18 +283,41 @@
                     files = [f for f in files if f]
                 except EnvironmentError:
                     raise util.Abort(_("unable to read file list (%s)") % pat)
-                kindpats += self._normalize(files, default, root, cwd, auditor)
+                for k, p, source in self._normalize(files, default, root, cwd,
+                                                    auditor):
+                    kindpats.append((k, p, pat))
+                continue
+            elif kind == 'include':
+                try:
+                    includepats = readpatternfile(pat, self._warn)
+                    for k, p, source in self._normalize(includepats, default,
+                                                        root, cwd, auditor):
+                        kindpats.append((k, p, source or pat))
+                except util.Abort as inst:
+                    raise util.Abort('%s: %s' % (pat, inst[0]))
+                except IOError as inst:
+                    if self._warn:
+                        self._warn(_("skipping unreadable pattern file "
+                                     "'%s': %s\n") % (pat, inst.strerror))
                 continue
             # else: re or relre - which cannot be normalized
-            kindpats.append((kind, pat))
+            kindpats.append((kind, pat, ''))
         return kindpats
 
-def exact(root, cwd, files):
-    return match(root, cwd, files, exact=True)
+def exact(root, cwd, files, badfn=None):
+    return match(root, cwd, files, exact=True, badfn=badfn)
 
 def always(root, cwd):
     return match(root, cwd, [])
 
+def badmatch(match, badfn):
+    """Make a copy of the given matcher, replacing its bad method with the given
+    one.
+    """
+    m = copy.copy(match)
+    m.bad = badfn
+    return m
+
 class narrowmatcher(match):
     """Adapt a matcher to work on a subdirectory only.
 
@@ -264,11 +362,11 @@
         # If the parent repo had a path to this subrepo and no patterns are
         # specified, this submatcher always matches.
         if not self._always and not matcher._anypats:
-            self._always = util.any(f == path for f in matcher._files)
+            self._always = any(f == path for f in matcher._files)
 
         self._anypats = matcher._anypats
         self.matchfn = lambda fn: matcher.matchfn(self._path + "/" + fn)
-        self._fmap = set(self._files)
+        self._fileroots = set(self._files)
 
     def abs(self, f):
         return self._matcher.abs(self._path + "/" + f)
@@ -285,26 +383,26 @@
     """
 
     def __init__(self, root, cwd, patterns, include, exclude, default, auditor,
-                 ctx):
+                 ctx, listsubrepos=False, badfn=None):
         init = super(icasefsmatcher, self).__init__
         self._dsnormalize = ctx.repo().dirstate.normalize
 
         init(root, cwd, patterns, include, exclude, default, auditor=auditor,
-             ctx=ctx)
+             ctx=ctx, listsubrepos=listsubrepos, badfn=badfn)
 
         # m.exact(file) must be based off of the actual user input, otherwise
         # inexact case matches are treated as exact, and not noted without -v.
         if self._files:
-            self._fmap = set(_roots(self._kp))
+            self._fileroots = set(_roots(self._kp))
 
     def _normalize(self, patterns, default, root, cwd, auditor):
         self._kp = super(icasefsmatcher, self)._normalize(patterns, default,
                                                           root, cwd, auditor)
         kindpats = []
-        for kind, pats in self._kp:
+        for kind, pats, source in self._kp:
             if kind not in ('re', 'relre'):  # regex can't be normalized
                 pats = self._dsnormalize(pats)
-            kindpats.append((kind, pats))
+            kindpats.append((kind, pats, source))
         return kindpats
 
 def patkind(pattern, default=None):
@@ -317,7 +415,7 @@
     if ':' in pattern:
         kind, pat = pattern.split(':', 1)
         if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
-                    'listfile', 'listfile0', 'set'):
+                    'listfile', 'listfile0', 'set', 'include', 'subinclude'):
             return kind, pat
     return default, pattern
 
@@ -420,24 +518,40 @@
         return '.*' + pat
     return _globre(pat) + globsuffix
 
-def _buildmatch(ctx, kindpats, globsuffix):
+def _buildmatch(ctx, kindpats, globsuffix, listsubrepos, root):
     '''Return regexp string and a matcher function for kindpats.
     globsuffix is appended to the regexp of globs.'''
-    fset, kindpats = _expandsets(kindpats, ctx)
-    if not kindpats:
-        return "", fset.__contains__
+    matchfuncs = []
+
+    subincludes, kindpats = _expandsubinclude(kindpats, root)
+    if subincludes:
+        def matchsubinclude(f):
+            for prefix, mf in subincludes:
+                if f.startswith(prefix) and mf(f[len(prefix):]):
+                    return True
+            return False
+        matchfuncs.append(matchsubinclude)
 
-    regex, mf = _buildregexmatch(kindpats, globsuffix)
+    fset, kindpats = _expandsets(kindpats, ctx, listsubrepos)
     if fset:
-        return regex, lambda f: f in fset or mf(f)
-    return regex, mf
+        matchfuncs.append(fset.__contains__)
+
+    regex = ''
+    if kindpats:
+        regex, mf = _buildregexmatch(kindpats, globsuffix)
+        matchfuncs.append(mf)
+
+    if len(matchfuncs) == 1:
+        return regex, matchfuncs[0]
+    else:
+        return regex, lambda f: any(mf(f) for mf in matchfuncs)
 
 def _buildregexmatch(kindpats, globsuffix):
     """Build a match function from a list of kinds and kindpats,
     return regexp string and a matcher function."""
     try:
         regex = '(?:%s)' % '|'.join([_regex(k, p, globsuffix)
-                                     for (k, p) in kindpats])
+                                     for (k, p, s) in kindpats])
         if len(regex) > 20000:
             raise OverflowError
         return regex, _rematcher(regex)
@@ -452,25 +566,29 @@
         regexb, b = _buildregexmatch(kindpats[l//2:], globsuffix)
         return regex, lambda s: a(s) or b(s)
     except re.error:
-        for k, p in kindpats:
+        for k, p, s in kindpats:
             try:
                 _rematcher('(?:%s)' % _regex(k, p, globsuffix))
             except re.error:
-                raise util.Abort(_("invalid pattern (%s): %s") % (k, p))
+                if s:
+                    raise util.Abort(_("%s: invalid pattern (%s): %s") %
+                                     (s, k, p))
+                else:
+                    raise util.Abort(_("invalid pattern (%s): %s") % (k, p))
         raise util.Abort(_("invalid pattern"))
 
 def _roots(kindpats):
     '''return roots and exact explicitly listed files from patterns
 
-    >>> _roots([('glob', 'g/*'), ('glob', 'g'), ('glob', 'g*')])
+    >>> _roots([('glob', 'g/*', ''), ('glob', 'g', ''), ('glob', 'g*', '')])
     ['g', 'g', '.']
-    >>> _roots([('relpath', 'r'), ('path', 'p/p'), ('path', '')])
+    >>> _roots([('relpath', 'r', ''), ('path', 'p/p', ''), ('path', '', '')])
     ['r', 'p/p', '.']
-    >>> _roots([('relglob', 'rg*'), ('re', 're/'), ('relre', 'rr')])
+    >>> _roots([('relglob', 'rg*', ''), ('re', 're/', ''), ('relre', 'rr', '')])
     ['.', '.', '.']
     '''
     r = []
-    for kind, pat in kindpats:
+    for kind, pat, source in kindpats:
         if kind == 'glob': # find the non-glob prefix
             root = []
             for p in pat.split('/'):
@@ -485,6 +603,69 @@
     return r
 
 def _anypats(kindpats):
-    for kind, pat in kindpats:
+    for kind, pat, source in kindpats:
         if kind in ('glob', 're', 'relglob', 'relre', 'set'):
             return True
+
+_commentre = None
+
+def readpatternfile(filepath, warn):
+    '''parse a pattern file, returning a list of
+    patterns. These patterns should be given to compile()
+    to be validated and converted into a match function.
+
+    trailing white space is dropped.
+    the escape character is backslash.
+    comments start with #.
+    empty lines are skipped.
+
+    lines can be of the following formats:
+
+    syntax: regexp # defaults following lines to non-rooted regexps
+    syntax: glob   # defaults following lines to non-rooted globs
+    re:pattern     # non-rooted regular expression
+    glob:pattern   # non-rooted glob
+    pattern        # pattern of the current default type'''
+
+    syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:',
+                'include': 'include', 'subinclude': 'subinclude'}
+    syntax = 'relre:'
+    patterns = []
+
+    fp = open(filepath)
+    for line in fp:
+        if "#" in line:
+            global _commentre
+            if not _commentre:
+                _commentre = re.compile(r'((^|[^\\])(\\\\)*)#.*')
+            # remove comments prefixed by an even number of escapes
+            line = _commentre.sub(r'\1', line)
+            # fixup properly escaped comments that survived the above
+            line = line.replace("\\#", "#")
+        line = line.rstrip()
+        if not line:
+            continue
+
+        if line.startswith('syntax:'):
+            s = line[7:].strip()
+            try:
+                syntax = syntaxes[s]
+            except KeyError:
+                if warn:
+                    warn(_("%s: ignoring invalid syntax '%s'\n") %
+                         (filepath, s))
+            continue
+
+        linesyntax = syntax
+        for s, rels in syntaxes.iteritems():
+            if line.startswith(rels):
+                linesyntax = rels
+                line = line[len(rels):]
+                break
+            elif line.startswith(s+':'):
+                linesyntax = rels
+                line = line[len(s) + 1:]
+                break
+        patterns.append(linesyntax + line)
+    fp.close()
+    return patterns
--- a/mercurial/merge.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/merge.py	Sat Jul 18 17:32:38 2015 -0500
@@ -145,7 +145,7 @@
                 else:
                     records.append(('F', l[:-1]))
             f.close()
-        except IOError, err:
+        except IOError as err:
             if err.errno != errno.ENOENT:
                 raise
         return records
@@ -170,7 +170,7 @@
                 off += length
                 records.append((rtype, record))
             f.close()
-        except IOError, err:
+        except IOError as err:
             if err.errno != errno.ENOENT:
                 raise
         return records
@@ -605,7 +605,7 @@
             # Consensus?
             if len(bids) == 1: # all bids are the same kind of method
                 m, l = bids.items()[0]
-                if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1
+                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]
                     continue
@@ -617,7 +617,7 @@
             # If there are gets and they all agree [how could they not?], do it.
             if 'g' in bids:
                 ga0 = bids['g'][0]
-                if util.all(a == ga0 for a in bids['g'][1:]):
+                if all(a == ga0 for a in bids['g'][1:]):
                     repo.ui.note(" %s: picking 'get' action\n" % f)
                     actions[f] = ga0
                     continue
@@ -660,7 +660,7 @@
         audit(f)
         try:
             unlink(wjoin(f), ignoremissing=True)
-        except OSError, inst:
+        except OSError as inst:
             repo.ui.warn(_("update failed to remove %s: %s!\n") %
                          (f, inst.strerror))
         if i == 100:
@@ -741,15 +741,7 @@
 
     numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
 
-    def dirtysubstate():
-        # mark '.hgsubstate' as possibly dirty forcibly, because
-        # modified '.hgsubstate' is misunderstood as clean,
-        # when both st_size/st_mtime of '.hgsubstate' aren't changed,
-        # even if "submerge" fails and '.hgsubstate' is inconsistent
-        repo.dirstate.normallookup('.hgsubstate')
-
     if [a for a in actions['r'] if a[0] == '.hgsubstate']:
-        dirtysubstate()
         subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
 
     # remove in parallel (must come first)
@@ -768,7 +760,6 @@
     updated = len(actions['g'])
 
     if [a for a in actions['g'] if a[0] == '.hgsubstate']:
-        dirtysubstate()
         subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
 
     # forget (manifest only, just log it) (must come first)
@@ -794,7 +785,6 @@
         z += 1
         progress(_updating, z, item=f, total=numupdates, unit=_files)
         if f == '.hgsubstate': # subrepo states need updating
-            dirtysubstate()
             subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
                              overwrite)
             continue
@@ -1021,7 +1011,7 @@
 
         p2 = repo[node]
         if pas[0] is None:
-            if repo.ui.config('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:
@@ -1080,6 +1070,7 @@
                     # Allow jumping branches if clean and specific rev given
                     pas = [p1]
 
+        # deprecated config: merge.followcopies
         followcopies = False
         if overwrite:
             pas = [wc]
--- a/mercurial/minirst.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/minirst.py	Sat Jul 18 17:32:38 2015 -0500
@@ -682,7 +682,10 @@
     secs = []
 
     def getname(b):
-        x = b['lines'][0]
+        if b['type'] == 'field':
+            x = b['key']
+        else:
+            x = b['lines'][0]
         x = x.lower().strip('"')
         if '(' in x:
             x = x.split('(')[0]
@@ -696,7 +699,7 @@
             level = nest.index(i) + 1
             nest = nest[:level]
             secs.append((getname(b), level, [b]))
-        elif b['type'] == 'definition':
+        elif b['type'] in ('definition', 'field'):
             i = ' '
             if i not in nest:
                 nest += i
--- a/mercurial/node.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/node.py	Sat Jul 18 17:32:38 2015 -0500
@@ -10,6 +10,11 @@
 nullrev = -1
 nullid = "\0" * 20
 
+# pseudo identifiers for working directory
+# (they are experimental, so don't add too many dependencies on them)
+wdirrev = 0x7fffffff
+wdirid = "\xff" * 20
+
 # This ugly style has a noticeable effect in manifest parsing
 hex = binascii.hexlify
 bin = binascii.unhexlify
--- a/mercurial/obsolete.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/obsolete.py	Sat Jul 18 17:32:38 2015 -0500
@@ -299,7 +299,7 @@
 
     # Loop on markers
     stop = len(data) - _fm1fsize
-    ufixed = util.unpacker(_fm1fixed)
+    ufixed = struct.Struct(_fm1fixed).unpack
 
     while off <= stop:
         # read fixed part
@@ -517,12 +517,12 @@
     # parents: (tuple of nodeid) or None, parents of precursors
     #          None is used when no data has been recorded
 
-    def __init__(self, sopener, defaultformat=_fm1version, readonly=False):
+    def __init__(self, svfs, defaultformat=_fm1version, readonly=False):
         # caches for various obsolescence related cache
         self.caches = {}
         self._all = []
-        self.sopener = sopener
-        data = sopener.tryread('obsstore')
+        self.svfs = svfs
+        data = svfs.tryread('obsstore')
         self._version = defaultformat
         self._readonly = readonly
         if data:
@@ -588,7 +588,7 @@
                 known.add(m)
                 new.append(m)
         if new:
-            f = self.sopener('obsstore', 'ab')
+            f = self.svfs('obsstore', 'ab')
             try:
                 offset = f.tell()
                 transaction.add('obsstore', offset)
@@ -718,7 +718,7 @@
     """List markers over pushkey"""
     if not repo.obsstore:
         return {}
-    return _pushkeyescape(repo.obsstore)
+    return _pushkeyescape(sorted(repo.obsstore))
 
 def pushmarker(repo, key, old, new):
     """Push markers over pushkey"""
@@ -1110,13 +1110,17 @@
 @cachefor('unstable')
 def _computeunstableset(repo):
     """the set of non obsolete revisions with obsolete parents"""
-    # revset is not efficient enough here
-    # we do (obsolete()::) - obsolete() by hand
-    obs = getrevs(repo, 'obsolete')
-    if not obs:
-        return set()
-    cl = repo.changelog
-    return set(r for r in cl.descendants(obs) if r not in obs)
+    revs = [(ctx.rev(), ctx) for ctx in
+            repo.set('(not public()) and (not obsolete())')]
+    revs.sort(key=lambda x:x[0])
+    unstable = set()
+    for rev, ctx in revs:
+        # A rev is unstable if one of its parent is obsolete or unstable
+        # this works since we traverse following growing rev order
+        if any((x.obsolete() or (x.rev() in unstable))
+                for x in ctx.parents()):
+            unstable.add(rev)
+    return unstable
 
 @cachefor('suspended')
 def _computesuspendedset(repo):
@@ -1139,19 +1143,18 @@
     public = phases.public
     cl = repo.changelog
     torev = cl.nodemap.get
-    obs = getrevs(repo, 'obsolete')
-    for rev in repo:
+    for ctx in repo.set('(not public()) and (not obsolete())'):
+        rev = ctx.rev()
         # We only evaluate mutable, non-obsolete revision
-        if (public < phase(repo, rev)) and (rev not in obs):
-            node = cl.node(rev)
-            # (future) A cache of precursors may worth if split is very common
-            for pnode in 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 precursors
-                    bumped.add(rev)
-                    break # Next draft!
+        node = ctx.node()
+        # (future) A cache of precursors may worth if split is very common
+        for pnode in 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 precursors
+                bumped.add(rev)
+                break # Next draft!
     return bumped
 
 @cachefor('divergent')
@@ -1211,8 +1214,9 @@
                 localmetadata.update(rel[2])
 
             if not prec.mutable():
-                raise util.Abort("cannot obsolete immutable changeset: %s"
-                                 % prec)
+                raise util.Abort("cannot obsolete public changeset: %s"
+                                 % prec,
+                                 hint='see "hg help phases" for details')
             nprec = prec.node()
             nsucs = tuple(s.node() for s in sucs)
             npare = None
--- a/mercurial/parser.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/parser.py	Sat Jul 18 17:32:38 2015 -0500
@@ -10,8 +10,9 @@
 # for background
 
 # takes a tokenizer and elements
-# tokenizer is an iterator that returns type, value pairs
-# elements is a mapping of types to binding strength, prefix and infix actions
+# tokenizer is an iterator that returns (type, value, pos) tuples
+# elements is a mapping of types to binding strength, primary, prefix, infix
+# and suffix actions
 # an action is a tree node name, a tree label, and an optional match
 # __call__(program) parses program into a labeled tree
 
@@ -19,68 +20,58 @@
 from i18n import _
 
 class parser(object):
-    def __init__(self, tokenizer, elements, methods=None):
-        self._tokenizer = tokenizer
+    def __init__(self, elements, methods=None):
         self._elements = elements
         self._methods = methods
         self.current = None
     def _advance(self):
         'advance the tokenizer'
         t = self.current
-        try:
-            self.current = self._iter.next()
-        except StopIteration:
-            pass
+        self.current = next(self._iter, None)
         return t
-    def _match(self, m, pos):
+    def _hasnewterm(self):
+        'True if next token may start new term'
+        return any(self._elements[self.current[0]][1:3])
+    def _match(self, m):
         'make sure the tokenizer matches an end condition'
         if self.current[0] != m:
             raise error.ParseError(_("unexpected token: %s") % self.current[0],
                                    self.current[2])
         self._advance()
+    def _parseoperand(self, bind, m=None):
+        'gather right-hand-side operand until an end condition or binding met'
+        if m and self.current[0] == m:
+            expr = None
+        else:
+            expr = self._parse(bind)
+        if m:
+            self._match(m)
+        return expr
     def _parse(self, bind=0):
         token, value, pos = self._advance()
-        # handle prefix rules on current token
-        prefix = self._elements[token][1]
-        if not prefix:
-            raise error.ParseError(_("not a prefix: %s") % token, pos)
-        if len(prefix) == 1:
-            expr = (prefix[0], value)
+        # handle prefix rules on current token, take as primary if unambiguous
+        primary, prefix = self._elements[token][1:3]
+        if primary and not (prefix and self._hasnewterm()):
+            expr = (primary, value)
+        elif prefix:
+            expr = (prefix[0], self._parseoperand(*prefix[1:]))
         else:
-            if len(prefix) > 2 and prefix[2] == self.current[0]:
-                self._match(prefix[2], pos)
-                expr = (prefix[0], None)
-            else:
-                expr = (prefix[0], self._parse(prefix[1]))
-                if len(prefix) > 2:
-                    self._match(prefix[2], pos)
+            raise error.ParseError(_("not a prefix: %s") % token, pos)
         # gather tokens until we meet a lower binding strength
         while bind < self._elements[self.current[0]][0]:
             token, value, pos = self._advance()
-            e = self._elements[token]
-            # check for suffix - next token isn't a valid prefix
-            if len(e) == 4 and not self._elements[self.current[0]][1]:
-                suffix = e[3]
+            # handle infix rules, take as suffix if unambiguous
+            infix, suffix = self._elements[token][3:]
+            if suffix and not (infix and self._hasnewterm()):
                 expr = (suffix[0], expr)
+            elif infix:
+                expr = (infix[0], expr, self._parseoperand(*infix[1:]))
             else:
-                # handle infix rules
-                if len(e) < 3 or not e[2]:
-                    raise error.ParseError(_("not an infix: %s") % token, pos)
-                infix = e[2]
-                if len(infix) == 3 and infix[2] == self.current[0]:
-                    self._match(infix[2], pos)
-                    expr = (infix[0], expr, (None))
-                else:
-                    expr = (infix[0], expr, self._parse(infix[1]))
-                    if len(infix) == 3:
-                        self._match(infix[2], pos)
+                raise error.ParseError(_("not an infix: %s") % token, pos)
         return expr
-    def parse(self, message, lookup=None):
-        'generate a parse tree from a message'
-        if lookup:
-            self._iter = self._tokenizer(message, lookup)
-        else:
-            self._iter = self._tokenizer(message)
+    def parse(self, tokeniter):
+        'generate a parse tree from tokens'
+        self._iter = tokeniter
         self._advance()
         res = self._parse()
         token, value, pos = self.current
@@ -90,9 +81,133 @@
         if not isinstance(tree, tuple):
             return tree
         return self._methods[tree[0]](*[self.eval(t) for t in tree[1:]])
-    def __call__(self, message):
-        'parse a message into a parse tree and evaluate if methods given'
-        t = self.parse(message)
+    def __call__(self, tokeniter):
+        'parse tokens into a parse tree and evaluate if methods given'
+        t = self.parse(tokeniter)
         if self._methods:
             return self.eval(t)
         return t
+
+def buildargsdict(trees, funcname, keys, keyvaluenode, keynode):
+    """Build dict from list containing positional and keyword arguments
+
+    Invalid keywords or too many positional arguments are rejected, but
+    missing arguments are just omitted.
+    """
+    if len(trees) > len(keys):
+        raise error.ParseError(_("%(func)s takes at most %(nargs)d arguments")
+                               % {'func': funcname, 'nargs': len(keys)})
+    args = {}
+    # consume positional arguments
+    for k, x in zip(keys, trees):
+        if x[0] == keyvaluenode:
+            break
+        args[k] = x
+    # remainder should be keyword arguments
+    for x in trees[len(args):]:
+        if x[0] != keyvaluenode or x[1][0] != keynode:
+            raise error.ParseError(_("%(func)s got an invalid argument")
+                                   % {'func': funcname})
+        k = x[1][1]
+        if k not in keys:
+            raise error.ParseError(_("%(func)s got an unexpected keyword "
+                                     "argument '%(key)s'")
+                                   % {'func': funcname, 'key': k})
+        if k in args:
+            raise error.ParseError(_("%(func)s got multiple values for keyword "
+                                     "argument '%(key)s'")
+                                   % {'func': funcname, 'key': k})
+        args[k] = x[2]
+    return args
+
+def _prettyformat(tree, leafnodes, level, lines):
+    if not isinstance(tree, tuple) or tree[0] in leafnodes:
+        lines.append((level, str(tree)))
+    else:
+        lines.append((level, '(%s' % tree[0]))
+        for s in tree[1:]:
+            _prettyformat(s, leafnodes, level + 1, lines)
+        lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
+
+def prettyformat(tree, leafnodes):
+    lines = []
+    _prettyformat(tree, leafnodes, 0, lines)
+    output = '\n'.join(('  ' * l + s) for l, s in lines)
+    return output
+
+def simplifyinfixops(tree, targetnodes):
+    """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')))
+    (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'))))
+    (func
+      ('symbol', 'p1')
+      (or
+        (func
+          ('symbol', 'sort')
+          (list
+            (or
+              ('symbol', '1')
+              ('symbol', '2')
+              ('symbol', '3'))
+            (negate
+              ('symbol', 'rev'))))
+        (and
+          ('symbol', '4')
+          (group
+            (or
+              ('symbol', '5')
+              ('symbol', '6')
+              ('symbol', '7'))))
+        ('symbol', '8')))
+    """
+    if not isinstance(tree, tuple):
+        return tree
+    op = tree[0]
+    if op not in targetnodes:
+        return (op,) + tuple(simplifyinfixops(x, targetnodes) for x in tree[1:])
+
+    # walk down left nodes taking each right node. no recursion to left nodes
+    # because infix operators are left-associative, i.e. left tree is deep.
+    # e.g. '1 + 2 + 3' -> (+ (+ 1 2) 3) -> (+ 1 2 3)
+    simplified = []
+    x = tree
+    while x[0] == op:
+        l, r = x[1:]
+        simplified.append(simplifyinfixops(r, targetnodes))
+        x = l
+    simplified.append(simplifyinfixops(x, targetnodes))
+    simplified.append(op)
+    return tuple(reversed(simplified))
--- a/mercurial/parsers.c	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/parsers.c	Sat Jul 18 17:32:38 2015 -0500
@@ -173,6 +173,28 @@
 	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 *dmap, *spec_obj, *normcase_fallback;
@@ -205,20 +227,9 @@
 		goto quit;
 	}
 
-#if PY_VERSION_HEX >= 0x02060000
-	/* _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 3/2 the size. Also add some
-	   more to deal with additions outside this function. */
-	file_foldmap = _PyDict_NewPresized((PyDict_Size(dmap) / 5) * 8);
-#else
-	file_foldmap = PyDict_New();
-#endif
-
+	/* 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;
 
@@ -741,6 +752,29 @@
 	return PyString_AS_STRING(self->data) + pos * v1_hdrsize;
 }
 
+static inline int index_get_parents(indexObject *self, Py_ssize_t rev,
+				    int *ps, int maxrev)
+{
+	if (rev >= self->length - 1) {
+		PyObject *tuple = PyList_GET_ITEM(self->added,
+						  rev - self->length + 1);
+		ps[0] = (int)PyInt_AS_LONG(PyTuple_GET_ITEM(tuple, 5));
+		ps[1] = (int)PyInt_AS_LONG(PyTuple_GET_ITEM(tuple, 6));
+	} else {
+		const char *data = index_deref(self, rev);
+		ps[0] = getbe32(data + 24);
+		ps[1] = getbe32(data + 28);
+	}
+	/* If index file is corrupted, ps[] may point to invalid revisions. So
+	 * there is a risk of buffer overflow to trust them unconditionally. */
+	if (ps[0] > maxrev || ps[1] > maxrev) {
+		PyErr_SetString(PyExc_ValueError, "parent out of range");
+		return -1;
+	}
+	return 0;
+}
+
+
 /*
  * RevlogNG format (all in big endian, data may be inlined):
  *    6 bytes: offset
@@ -1071,23 +1105,21 @@
 		phases[i] = phases[parent_2];
 }
 
-static PyObject *compute_phases(indexObject *self, PyObject *args)
+static PyObject *compute_phases_map_sets(indexObject *self, PyObject *args)
 {
 	PyObject *roots = Py_None;
+	PyObject *ret = NULL;
 	PyObject *phaseslist = NULL;
 	PyObject *phaseroots = NULL;
-	PyObject *rev = NULL;
-	PyObject *p1 = NULL;
-	PyObject *p2 = NULL;
-	Py_ssize_t addlen = self->added ? PyList_GET_SIZE(self->added) : 0;
+	PyObject *phaseset = NULL;
+	PyObject *phasessetlist = NULL;
 	Py_ssize_t len = index_length(self) - 1;
 	Py_ssize_t numphase = 0;
 	Py_ssize_t minrevallphases = 0;
 	Py_ssize_t minrevphase = 0;
 	Py_ssize_t i = 0;
-	int parent_1, parent_2;
 	char *phases = NULL;
-	const char *data;
+	long phase;
 
 	if (!PyArg_ParseTuple(args, "O", &roots))
 		goto release_none;
@@ -1100,50 +1132,72 @@
 	/* Put the phase information of all the roots in phases */
 	numphase = PyList_GET_SIZE(roots)+1;
 	minrevallphases = len + 1;
+	phasessetlist = PyList_New(numphase);
+	if (phasessetlist == NULL)
+		goto release_none;
+
+	PyList_SET_ITEM(phasessetlist, 0, Py_None);
+	Py_INCREF(Py_None);
+
 	for (i = 0; i < numphase-1; i++) {
 		phaseroots = PyList_GET_ITEM(roots, i);
+		phaseset = PySet_New(NULL);
+		if (phaseset == NULL)
+			goto release_phasesetlist;
+		PyList_SET_ITEM(phasessetlist, i+1, phaseset);
 		if (!PyList_Check(phaseroots))
-			goto release_phases;
+			goto release_phasesetlist;
 		minrevphase = add_roots_get_min(self, phaseroots, i+1, phases);
 		if (minrevphase == -2) /* Error from add_roots_get_min */
-			goto release_phases;
+			goto release_phasesetlist;
 		minrevallphases = MIN(minrevallphases, minrevphase);
 	}
 	/* Propagate the phase information from the roots to the revs */
 	if (minrevallphases != -1) {
-		for (i = minrevallphases; i < self->raw_length; i++) {
-			data = index_deref(self, i);
-			set_phase_from_parents(phases, getbe32(data+24), getbe32(data+28), i);
-		}
-		for (i = 0; i < addlen; i++) {
-			rev = PyList_GET_ITEM(self->added, i);
-			p1 = PyTuple_GET_ITEM(rev, 5);
-			p2 = PyTuple_GET_ITEM(rev, 6);
-			if (!PyInt_Check(p1) || !PyInt_Check(p2)) {
-				PyErr_SetString(PyExc_TypeError, "revlog parents are invalid");
-				goto release_phases;
-			}
-			parent_1 = (int)PyInt_AS_LONG(p1);
-			parent_2 = (int)PyInt_AS_LONG(p2);
-			set_phase_from_parents(phases, parent_1, parent_2, i+self->raw_length);
+		int parents[2];
+		for (i = minrevallphases; i < len; i++) {
+			if (index_get_parents(self, i, parents, len - 1) < 0)
+				goto release_phasesetlist;
+			set_phase_from_parents(phases, parents[0], parents[1], i);
 		}
 	}
 	/* Transform phase list to a python list */
 	phaseslist = PyList_New(len);
 	if (phaseslist == NULL)
-		goto release_phases;
-	for (i = 0; i < len; i++)
-		PyList_SET_ITEM(phaseslist, i, PyInt_FromLong(phases[i]));
+		goto release_phasesetlist;
+	for (i = 0; i < len; i++) {
+		phase = phases[i];
+		/* We only store the sets of phase for non public phase, the public phase
+		 * is computed as a difference */
+		if (phase != 0) {
+			phaseset = PyList_GET_ITEM(phasessetlist, phase);
+			PySet_Add(phaseset, PyInt_FromLong(i));
+		}
+		PyList_SET_ITEM(phaseslist, i, PyInt_FromLong(phase));
+	}
+	ret = PyList_New(2);
+	if (ret == NULL)
+		goto release_phaseslist;
 
+	PyList_SET_ITEM(ret, 0, phaseslist);
+	PyList_SET_ITEM(ret, 1, phasessetlist);
+	/* We don't release phaseslist and phasessetlist as we return them to
+	 * python */
+	goto release_phases;
+
+release_phaseslist:
+	Py_XDECREF(phaseslist);
+release_phasesetlist:
+	Py_XDECREF(phasessetlist);
 release_phases:
 	free(phases);
 release_none:
-	return phaseslist;
+	return ret;
 }
 
 static PyObject *index_headrevs(indexObject *self, PyObject *args)
 {
-	Py_ssize_t i, len, addlen;
+	Py_ssize_t i, j, len;
 	char *nothead = NULL;
 	PyObject *heads = NULL;
 	PyObject *filter = NULL;
@@ -1186,9 +1240,9 @@
 	if (nothead == NULL)
 		goto bail;
 
-	for (i = 0; i < self->raw_length; i++) {
-		const char *data;
-		int parent_1, parent_2, isfiltered;
+	for (i = 0; i < len; i++) {
+		int isfiltered;
+		int parents[2];
 
 		isfiltered = check_filter(filter, i);
 		if (isfiltered == -1) {
@@ -1202,49 +1256,12 @@
 			continue;
 		}
 
-		data = index_deref(self, i);
-		parent_1 = getbe32(data + 24);
-		parent_2 = getbe32(data + 28);
-
-		if (parent_1 >= 0)
-			nothead[parent_1] = 1;
-		if (parent_2 >= 0)
-			nothead[parent_2] = 1;
-	}
-
-	addlen = self->added ? PyList_GET_SIZE(self->added) : 0;
-
-	for (i = 0; i < addlen; i++) {
-		PyObject *rev = PyList_GET_ITEM(self->added, i);
-		PyObject *p1 = PyTuple_GET_ITEM(rev, 5);
-		PyObject *p2 = PyTuple_GET_ITEM(rev, 6);
-		long parent_1, parent_2;
-		int isfiltered;
-
-		if (!PyInt_Check(p1) || !PyInt_Check(p2)) {
-			PyErr_SetString(PyExc_TypeError,
-					"revlog parents are invalid");
+		if (index_get_parents(self, i, parents, len - 1) < 0)
 			goto bail;
-		}
-
-		isfiltered = check_filter(filter, i);
-		if (isfiltered == -1) {
-			PyErr_SetString(PyExc_TypeError,
-				"unable to check filter");
-			goto bail;
+		for (j = 0; j < 2; j++) {
+			if (parents[j] >= 0)
+				nothead[parents[j]] = 1;
 		}
-
-		if (isfiltered) {
-			nothead[i] = 1;
-			continue;
-		}
-
-		parent_1 = PyInt_AS_LONG(p1);
-		parent_2 = PyInt_AS_LONG(p2);
-		if (parent_1 >= 0)
-			nothead[parent_1] = 1;
-		if (parent_2 >= 0)
-			nothead[parent_2] = 1;
 	}
 
 	for (i = 0; i < len; i++) {
@@ -1647,20 +1664,6 @@
 	}
 }
 
-static inline void index_get_parents(indexObject *self, int rev, int *ps)
-{
-	if (rev >= self->length - 1) {
-		PyObject *tuple = PyList_GET_ITEM(self->added,
-						  rev - self->length + 1);
-		ps[0] = (int)PyInt_AS_LONG(PyTuple_GET_ITEM(tuple, 5));
-		ps[1] = (int)PyInt_AS_LONG(PyTuple_GET_ITEM(tuple, 6));
-	} else {
-		const char *data = index_deref(self, rev);
-		ps[0] = getbe32(data + 24);
-		ps[1] = getbe32(data + 28);
-	}
-}
-
 typedef uint64_t bitmask;
 
 /*
@@ -1722,7 +1725,8 @@
 				}
 			}
 		}
-		index_get_parents(self, v, parents);
+		if (index_get_parents(self, v, parents, maxrev) < 0)
+			goto bail;
 
 		for (i = 0; i < 2; i++) {
 			int p = parents[i];
@@ -1819,7 +1823,8 @@
 			continue;
 
 		sv = seen[v];
-		index_get_parents(self, v, parents);
+		if (index_get_parents(self, v, parents, maxrev) < 0)
+			goto bail;
 
 		for (i = 0; i < 2; i++) {
 			int p = parents[i];
@@ -2271,8 +2276,8 @@
 	 "clear the index caches"},
 	{"get", (PyCFunction)index_m_get, METH_VARARGS,
 	 "get an index entry"},
-	{"computephases", (PyCFunction)compute_phases, METH_VARARGS,
-		"compute phases"},
+	{"computephasesmapsets", (PyCFunction)compute_phases_map_sets,
+			METH_VARARGS, "compute phases"},
 	{"headrevs", (PyCFunction)index_headrevs, METH_VARARGS,
 	 "get head revisions"}, /* Can do filtering since 3.2 */
 	{"headrevsfiltered", (PyCFunction)index_headrevs, METH_VARARGS,
@@ -2540,6 +2545,8 @@
 	{"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"},
--- a/mercurial/patch.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/patch.py	Sat Jul 18 17:32:38 2015 -0500
@@ -6,16 +6,12 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+import collections
 import cStringIO, email, os, errno, re, posixpath, copy
 import tempfile, zlib, shutil
-# On python2.4 you have to import these by name or they fail to
-# load. This was not a problem on Python 2.7.
-import email.Generator
-import email.Parser
 
 from i18n import _
 from node import hex, short
-import cStringIO
 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
 import pathutil
 
@@ -292,8 +288,8 @@
         self.binary = False
 
     def setmode(self, mode):
-        islink = mode & 020000
-        isexec = mode & 0100
+        islink = mode & 0o20000
+        isexec = mode & 0o100
         self.mode = (islink, isexec)
 
     def copy(self):
@@ -434,13 +430,13 @@
 
         isexec = False
         try:
-            isexec = self.opener.lstat(fname).st_mode & 0100 != 0
-        except OSError, e:
+            isexec = self.opener.lstat(fname).st_mode & 0o100 != 0
+        except OSError as e:
             if e.errno != errno.ENOENT:
                 raise
         try:
             return (self.opener.read(fname), (False, isexec))
-        except IOError, e:
+        except IOError as e:
             if e.errno != errno.ENOENT:
                 raise
             return None, None
@@ -773,7 +769,7 @@
         for x, s in enumerate(self.lines):
             self.hash.setdefault(s, []).append(x)
 
-        for fuzzlen in xrange(3):
+        for fuzzlen in xrange(self.ui.configint("patch", "fuzz", 2) + 1):
             for toponly in [True, False]:
                 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
                 oldstart = oldstart + self.offset + self.skew
@@ -830,7 +826,7 @@
         self.hunks = []
 
     def binary(self):
-        return util.any(h.startswith('index ') for h in self.header)
+        return any(h.startswith('index ') for h in self.header)
 
     def pretty(self, fp):
         for h in self.header:
@@ -853,7 +849,7 @@
         fp.write(''.join(self.header))
 
     def allhunks(self):
-        return util.any(self.allhunks_re.match(h) for h in self.header)
+        return any(self.allhunks_re.match(h) for h in self.header)
 
     def files(self):
         match = self.diffgit_re.match(self.header[0])
@@ -872,7 +868,7 @@
         return '<header %s>' % (' '.join(map(repr, self.files())))
 
     def isnewfile(self):
-        return util.any(self.newfile_re.match(h) for h in self.header)
+        return any(self.newfile_re.match(h) for h in self.header)
 
     def special(self):
         # Special files are shown only at the header level and not at the hunk
@@ -885,7 +881,7 @@
         nocontent = len(self.header) == 2
         emptynewfile = self.isnewfile() and nocontent
         return emptynewfile or \
-                util.any(self.special_re.match(h) for h in self.header)
+                any(self.special_re.match(h) for h in self.header)
 
 class recordhunk(object):
     """patch hunk
@@ -948,8 +944,10 @@
     def __repr__(self):
         return '<hunk %r@%d>' % (self.filename(), self.fromline)
 
-def filterpatch(ui, headers):
+def filterpatch(ui, headers, operation=None):
     """Interactively filter patch chunks into applied-only chunks"""
+    if operation is None:
+        operation = _('record')
 
     def prompt(skipfile, skipall, query, chunk):
         """prompt query, and process base inputs
@@ -1021,9 +1019,11 @@
                     f.close()
                     # Start the editor and wait for it to complete
                     editor = ui.geteditor()
-                    ui.system("%s \"%s\"" % (editor, patchfn),
-                              environ={'HGUSER': ui.username()},
-                              onerr=util.Abort, errprefix=_("edit failed"))
+                    ret = ui.system("%s \"%s\"" % (editor, patchfn),
+                                    environ={'HGUSER': ui.username()})
+                    if ret != 0:
+                        ui.warn(_("editor exited with exit code %d\n") % ret)
+                        continue
                     # Remove comment lines
                     patchfp = open(patchfn)
                     ncpatchfp = cStringIO.StringIO()
@@ -1363,7 +1363,7 @@
                 l = ord(l) - ord('a') + 27
             try:
                 dec.append(base85.b85decode(line[1:])[:l])
-            except ValueError, e:
+            except ValueError as e:
                 raise PatchError(_('could not decode "%s" binary patch: %s')
                                  % (self._fname, str(e)))
             line = getline(lr, self.hunk)
@@ -1383,6 +1383,77 @@
             return s
     return s[:i]
 
+def reversehunks(hunks):
+    '''reverse the signs in the hunks given as argument
+
+    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
+    ... --- a/folder1/g
+    ... +++ b/folder1/g
+    ... @@ -1,7 +1,7 @@
+    ... +firstline
+    ...  c
+    ...  1
+    ...  2
+    ... + 3
+    ... -4
+    ...  5
+    ...  d
+    ... +lastline"""
+    >>> hunks = parsepatch(rawpatch)
+    >>> hunkscomingfromfilterpatch = []
+    >>> for h in hunks:
+    ...     hunkscomingfromfilterpatch.append(h)
+    ...     hunkscomingfromfilterpatch.extend(h.hunks)
+
+    >>> reversedhunks = reversehunks(hunkscomingfromfilterpatch)
+    >>> fp = cStringIO.StringIO()
+    >>> for c in reversedhunks:
+    ...      c.write(fp)
+    >>> fp.seek(0)
+    >>> reversedpatch = fp.read()
+    >>> print reversedpatch
+    diff --git a/folder1/g b/folder1/g
+    --- a/folder1/g
+    +++ b/folder1/g
+    @@ -1,4 +1,3 @@
+    -firstline
+     c
+     1
+     2
+    @@ -1,6 +2,6 @@
+     c
+     1
+     2
+    - 3
+    +4
+     5
+     d
+    @@ -5,3 +6,2 @@
+     5
+     d
+    -lastline
+
+    '''
+
+    import crecord as crecordmod
+    newhunks = []
+    for c in hunks:
+        if isinstance(c, crecordmod.uihunk):
+            # curses hunks encapsulate the record hunk in _hunk
+            c = c._hunk
+        if isinstance(c, recordhunk):
+            for j, line in enumerate(c.hunk):
+                if line.startswith("-"):
+                    c.hunk[j] = "+" + c.hunk[j][1:]
+                elif line.startswith("+"):
+                    c.hunk[j] = "-" + c.hunk[j][1:]
+            c.added, c.removed = c.removed, c.added
+        newhunks.append(c)
+    return newhunks
+
 def parsepatch(originalchunks):
     """patch -> [] of headers -> [] of hunks """
     class parser(object):
@@ -1867,7 +1938,7 @@
             try:
                 current_file = patcher(ui, gp, backend, store,
                                        eolmode=eolmode)
-            except PatchError, inst:
+            except PatchError as inst:
                 ui.warn(str(inst) + '\n')
                 current_file = None
                 rejects += 1
@@ -2102,7 +2173,7 @@
 
     def lrugetfilectx():
         cache = {}
-        order = util.deque()
+        order = collections.deque()
         def getfilectx(f, ctx):
             fctx = ctx.filectx(f, filelog=cache.get(f))
             if f not in cache:
--- a/mercurial/pathutil.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/pathutil.py	Sat Jul 18 17:32:38 2015 -0500
@@ -1,4 +1,4 @@
-import os, errno, stat
+import os, errno, stat, posixpath
 
 import encoding
 import util
@@ -76,7 +76,7 @@
             curpath = os.path.join(self.root, prefix)
             try:
                 st = os.lstat(curpath)
-            except OSError, err:
+            except OSError as err:
                 # EINVAL can be raised as invalid path syntax under win32.
                 # They must be ignored for patterns can be checked too.
                 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
@@ -152,7 +152,19 @@
                 break
             name = dirname
 
-        raise util.Abort(_("%s not under root '%s'") % (myname, root))
+        # A common mistake is to use -R, but specify a file relative to the repo
+        # instead of cwd.  Detect that case, and provide a hint to the user.
+        hint = None
+        try:
+            if cwd != root:
+                canonpath(root, root, myname, auditor)
+                hint = (_("consider using '--cwd %s'")
+                        % os.path.relpath(root, cwd))
+        except util.Abort:
+            pass
+
+        raise util.Abort(_("%s not under root '%s'") % (myname, root),
+                         hint=hint)
 
 def normasprefix(path):
     '''normalize the specified path as path prefix
@@ -175,3 +187,9 @@
         return path + os.sep
     else:
         return path
+
+# forward two methods from posixpath that do what we need, but we'd
+# rather not let our internals know that we're thinking in posix terms
+# - instead we'll let them be oblivious.
+join = posixpath.join
+dirname = posixpath.dirname
--- a/mercurial/phases.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/phases.py	Sat Jul 18 17:32:38 2015 -0500
@@ -129,7 +129,7 @@
         if 'HG_PENDING' in os.environ:
             try:
                 f = repo.svfs('phaseroots.pending')
-            except IOError, inst:
+            except IOError as inst:
                 if inst.errno != errno.ENOENT:
                     raise
         if f is None:
@@ -140,7 +140,7 @@
                 roots[int(phase)].add(bin(nh))
         finally:
             f.close()
-    except IOError, inst:
+    except IOError as inst:
         if inst.errno != errno.ENOENT:
             raise
         if phasedefaults:
@@ -155,6 +155,7 @@
             # Cheap trick to allow shallow-copy without copy module
             self.phaseroots, self.dirty = _readroots(repo, phasedefaults)
             self._phaserevs = None
+            self._phasesets = None
             self.filterunknown(repo)
             self.opener = repo.svfs
 
@@ -166,10 +167,12 @@
         ph.dirty = self.dirty
         ph.opener = self.opener
         ph._phaserevs = self._phaserevs
+        ph._phasesets = self._phasesets
         return ph
 
     def replace(self, phcache):
-        for a in 'phaseroots dirty opener _phaserevs'.split():
+        """replace all values in 'self' with content of phcache"""
+        for a in ('phaseroots', 'dirty', 'opener', '_phaserevs', '_phasesets'):
             setattr(self, a, getattr(phcache, a))
 
     def _getphaserevsnative(self, repo):
@@ -192,20 +195,22 @@
                 for rev in repo.changelog.descendants(roots):
                     revs[rev] = phase
 
-    def getphaserevs(self, repo):
+    def loadphaserevs(self, repo):
+        """ensure phase information is loaded in the object"""
         if self._phaserevs is None:
             try:
                 if repo.ui.configbool('experimental',
                                       'nativephaseskillswitch'):
                     self._computephaserevspure(repo)
                 else:
-                    self._phaserevs = self._getphaserevsnative(repo)
+                    res = self._getphaserevsnative(repo)
+                    self._phaserevs, self._phasesets = res
             except AttributeError:
                 self._computephaserevspure(repo)
-        return self._phaserevs
 
     def invalidate(self):
         self._phaserevs = None
+        self._phasesets = None
 
     def _populatephaseroots(self, repo):
         """Fills the _phaserevs cache with phases for the roots.
@@ -229,7 +234,7 @@
             raise ValueError(_('cannot lookup negative revision'))
         if self._phaserevs is None or rev >= len(self._phaserevs):
             self.invalidate()
-            self._phaserevs = self.getphaserevs(repo)
+            self.loadphaserevs(repo)
         return self._phaserevs[rev]
 
     def write(self):
@@ -355,7 +360,7 @@
     for root in repo._phasecache.phaseroots[draft]:
         keys[hex(root)] = value
 
-    if repo.ui.configbool('phases', 'publish', True):
+    if repo.publishing():
         # Add an extra data to let remote know we are a publishing
         # repo. Publishing repo can't just pretend they are old repo.
         # When pushing to a publishing repo, the client still need to
--- a/mercurial/posix.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/posix.py	Sat Jul 18 17:32:38 2015 -0500
@@ -8,6 +8,7 @@
 from i18n import _
 import encoding
 import os, sys, errno, stat, getpass, pwd, grp, socket, tempfile, unicodedata
+import select
 import fcntl, re
 
 posixfile = open
@@ -70,7 +71,7 @@
 
 def isexec(f):
     """check whether a file is executable"""
-    return (os.lstat(f).st_mode & 0100 != 0)
+    return (os.lstat(f).st_mode & 0o100 != 0)
 
 def setflags(f, l, x):
     s = os.lstat(f).st_mode
@@ -97,30 +98,30 @@
         fp = open(f, "w")
         fp.write(data)
         fp.close()
-        s = 0666 & ~umask # avoid restatting for chmod
+        s = 0o666 & ~umask # avoid restatting for chmod
 
-    sx = s & 0100
+    sx = s & 0o100
     if x and not sx:
         # Turn on +x for every +r bit when making a file executable
         # and obey umask.
-        os.chmod(f, s | (s & 0444) >> 2 & ~umask)
+        os.chmod(f, s | (s & 0o444) >> 2 & ~umask)
     elif not x and sx:
         # Turn off all +x bits
-        os.chmod(f, s & 0666)
+        os.chmod(f, s & 0o666)
 
 def copymode(src, dst, mode=None):
     '''Copy the file mode from the file at path src to dst.
     If src doesn't exist, we're using mode instead. If mode is None, we're
     using umask.'''
     try:
-        st_mode = os.lstat(src).st_mode & 0777
-    except OSError, inst:
+        st_mode = os.lstat(src).st_mode & 0o777
+    except OSError as inst:
         if inst.errno != errno.ENOENT:
             raise
         st_mode = mode
         if st_mode is None:
             st_mode = ~umask
-        st_mode &= 0666
+        st_mode &= 0o666
     os.chmod(dst, st_mode)
 
 def checkexec(path):
@@ -139,10 +140,10 @@
         fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
         try:
             os.close(fh)
-            m = os.stat(fn).st_mode & 0777
+            m = os.stat(fn).st_mode & 0o777
             new_file_has_exec = m & EXECFLAGS
             os.chmod(fn, m ^ EXECFLAGS)
-            exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
+            exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0o777) == m)
         finally:
             os.unlink(fn)
     except (IOError, OSError):
@@ -165,7 +166,7 @@
             fd.close()
     except AttributeError:
         return False
-    except OSError, inst:
+    except OSError as inst:
         # sshfs might report failure while successfully creating the link
         if inst[0] == errno.EIO and os.path.exists(name):
             os.unlink(name)
@@ -354,7 +355,7 @@
     try:
         os.kill(pid, 0)
         return True
-    except OSError, inst:
+    except OSError as inst:
         return inst.errno != errno.ESRCH
 
 def explainexit(code):
@@ -409,7 +410,7 @@
             st = lstat(nf)
             if getkind(st.st_mode) not in _wantedkinds:
                 st = None
-        except OSError, err:
+        except OSError as err:
             if err.errno not in (errno.ENOENT, errno.ENOTDIR):
                 raise
             st = None
@@ -476,7 +477,7 @@
                     pass
             except ValueError:
                 pass
-            except IOError, e:
+            except IOError as e:
                 if e[0] == errno.EINVAL:
                     pass
                 else:
@@ -492,7 +493,7 @@
     """unlink and remove the directory if it is empty"""
     try:
         os.unlink(f)
-    except OSError, e:
+    except OSError as e:
         if not (ignoremissing and e.errno == errno.ENOENT):
             raise
     # try removing directories that might now be empty
@@ -559,7 +560,7 @@
                 os.unlink(self.path)
         try:
             self.bind(self.realpath)
-        except socket.error, err:
+        except socket.error as err:
             if err.args[0] == 'AF_UNIX path too long':
                 tmpdir = tempfile.mkdtemp(prefix='hg-%s-' % subsystem)
                 self.realpath = os.path.join(tmpdir, sockname)
@@ -577,7 +578,7 @@
         def okayifmissing(f, path):
             try:
                 f(path)
-            except OSError, err:
+            except OSError as err:
                 if err.errno != errno.ENOENT:
                     raise
 
@@ -592,7 +593,20 @@
 
 def statisexec(st):
     '''check whether a stat result is an executable file'''
-    return st and (st.st_mode & 0100 != 0)
+    return st and (st.st_mode & 0o100 != 0)
+
+def poll(fds):
+    """block until something happens on any file descriptor
+
+    This is a generic helper that will check for any activity
+    (read, write.  exception) and return the list of touched files.
+
+    In unsupported cases, it will raise a NotImplementedError"""
+    try:
+        res = select.select(fds, fds, fds)
+    except ValueError: # out of range file descriptor
+        raise NotImplementedError()
+    return sorted(list(set(sum(res, []))))
 
 def readpipe(pipe):
     """Read all available data from a pipe."""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/progress.py	Sat Jul 18 17:32:38 2015 -0500
@@ -0,0 +1,252 @@
+# progress.py progress bars related code
+#
+# Copyright (C) 2010 Augie Fackler <durin42@gmail.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+import sys
+import time
+import threading
+from mercurial import encoding
+
+from mercurial.i18n import _
+
+
+def spacejoin(*args):
+    return ' '.join(s for s in args if s)
+
+def shouldprint(ui):
+    return not (ui.quiet or ui.plain()) and (
+        ui._isatty(sys.stderr) or ui.configbool('progress', 'assume-tty'))
+
+def fmtremaining(seconds):
+    """format a number of remaining seconds in humain readable way
+
+    This will properly display seconds, minutes, hours, days if needed"""
+    if seconds < 60:
+        # i18n: format XX seconds as "XXs"
+        return _("%02ds") % (seconds)
+    minutes = seconds // 60
+    if minutes < 60:
+        seconds -= minutes * 60
+        # i18n: format X minutes and YY seconds as "XmYYs"
+        return _("%dm%02ds") % (minutes, seconds)
+    # we're going to ignore seconds in this case
+    minutes += 1
+    hours = minutes // 60
+    minutes -= hours * 60
+    if hours < 30:
+        # i18n: format X hours and YY minutes as "XhYYm"
+        return _("%dh%02dm") % (hours, minutes)
+    # we're going to ignore minutes in this case
+    hours += 1
+    days = hours // 24
+    hours -= days * 24
+    if days < 15:
+        # i18n: format X days and YY hours as "XdYYh"
+        return _("%dd%02dh") % (days, hours)
+    # we're going to ignore hours in this case
+    days += 1
+    weeks = days // 7
+    days -= weeks * 7
+    if weeks < 55:
+        # i18n: format X weeks and YY days as "XwYYd"
+        return _("%dw%02dd") % (weeks, days)
+    # we're going to ignore days and treat a year as 52 weeks
+    weeks += 1
+    years = weeks // 52
+    weeks -= years * 52
+    # i18n: format X years and YY weeks as "XyYYw"
+    return _("%dy%02dw") % (years, weeks)
+
+class progbar(object):
+    def __init__(self, ui):
+        self.ui = ui
+        self._refreshlock = threading.Lock()
+        self.resetstate()
+
+    def resetstate(self):
+        self.topics = []
+        self.topicstates = {}
+        self.starttimes = {}
+        self.startvals = {}
+        self.printed = False
+        self.lastprint = time.time() + float(self.ui.config(
+            'progress', 'delay', default=3))
+        self.curtopic = None
+        self.lasttopic = None
+        self.indetcount = 0
+        self.refresh = float(self.ui.config(
+            'progress', 'refresh', default=0.1))
+        self.changedelay = max(3 * self.refresh,
+                               float(self.ui.config(
+                                   'progress', 'changedelay', default=1)))
+        self.order = self.ui.configlist(
+            'progress', 'format',
+            default=['topic', 'bar', 'number', 'estimate'])
+
+    def show(self, now, topic, pos, item, unit, total):
+        if not shouldprint(self.ui):
+            return
+        termwidth = self.width()
+        self.printed = True
+        head = ''
+        needprogress = False
+        tail = ''
+        for indicator in self.order:
+            add = ''
+            if indicator == 'topic':
+                add = topic
+            elif indicator == 'number':
+                if total:
+                    add = ('% ' + str(len(str(total))) +
+                           's/%s') % (pos, total)
+                else:
+                    add = str(pos)
+            elif indicator.startswith('item') and item:
+                slice = 'end'
+                if '-' in indicator:
+                    wid = int(indicator.split('-')[1])
+                elif '+' in indicator:
+                    slice = 'beginning'
+                    wid = int(indicator.split('+')[1])
+                else:
+                    wid = 20
+                if slice == 'end':
+                    add = encoding.trim(item, wid, leftside=True)
+                else:
+                    add = encoding.trim(item, wid)
+                add += (wid - encoding.colwidth(add)) * ' '
+            elif indicator == 'bar':
+                add = ''
+                needprogress = True
+            elif indicator == 'unit' and unit:
+                add = unit
+            elif indicator == 'estimate':
+                add = self.estimate(topic, pos, total, now)
+            elif indicator == 'speed':
+                add = self.speed(topic, pos, unit, now)
+            if not needprogress:
+                head = spacejoin(head, add)
+            else:
+                tail = spacejoin(tail, add)
+        if needprogress:
+            used = 0
+            if head:
+                used += encoding.colwidth(head) + 1
+            if tail:
+                used += encoding.colwidth(tail) + 1
+            progwidth = termwidth - used - 3
+            if total and pos <= total:
+                amt = pos * progwidth // total
+                bar = '=' * (amt - 1)
+                if amt > 0:
+                    bar += '>'
+                bar += ' ' * (progwidth - amt)
+            else:
+                progwidth -= 3
+                self.indetcount += 1
+                # mod the count by twice the width so we can make the
+                # cursor bounce between the right and left sides
+                amt = self.indetcount % (2 * progwidth)
+                amt -= progwidth
+                bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
+                       ' ' * int(abs(amt)))
+            prog = ''.join(('[', bar , ']'))
+            out = spacejoin(head, prog, tail)
+        else:
+            out = spacejoin(head, tail)
+        sys.stderr.write('\r' + encoding.trim(out, termwidth))
+        self.lasttopic = topic
+        sys.stderr.flush()
+
+    def clear(self):
+        if not shouldprint(self.ui):
+            return
+        sys.stderr.write('\r%s\r' % (' ' * self.width()))
+
+    def complete(self):
+        if not shouldprint(self.ui):
+            return
+        if self.ui.configbool('progress', 'clear-complete', default=True):
+            self.clear()
+        else:
+            sys.stderr.write('\n')
+        sys.stderr.flush()
+
+    def width(self):
+        tw = self.ui.termwidth()
+        return min(int(self.ui.config('progress', 'width', default=tw)), tw)
+
+    def estimate(self, topic, pos, total, now):
+        if total is None:
+            return ''
+        initialpos = self.startvals[topic]
+        target = total - initialpos
+        delta = pos - initialpos
+        if delta > 0:
+            elapsed = now - self.starttimes[topic]
+            # experimental config: progress.estimate
+            if elapsed > float(
+                self.ui.config('progress', 'estimate', default=2)):
+                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', default=2)):
+            return _('%d %s/sec') % (delta / elapsed, unit)
+        return ''
+
+    def _oktoprint(self, now):
+        '''Check if conditions are met to print - e.g. changedelay elapsed'''
+        if (self.lasttopic is None # first time we printed
+            # not a topic change
+            or self.curtopic == self.lasttopic
+            # it's been long enough we should print anyway
+            or now - self.lastprint >= self.changedelay):
+            return True
+        else:
+            return False
+
+    def progress(self, topic, pos, item='', unit='', total=None):
+        now = time.time()
+        self._refreshlock.acquire()
+        try:
+            if pos is None:
+                self.starttimes.pop(topic, None)
+                self.startvals.pop(topic, None)
+                self.topicstates.pop(topic, None)
+                # reset the progress bar if this is the outermost topic
+                if self.topics and self.topics[0] == topic and self.printed:
+                    self.complete()
+                    self.resetstate()
+                # truncate the list of topics assuming all topics within
+                # this one are also closed
+                if topic in self.topics:
+                    self.topics = self.topics[:self.topics.index(topic)]
+                    # reset the last topic to the one we just unwound to,
+                    # so that higher-level topics will be stickier than
+                    # lower-level topics
+                    if self.topics:
+                        self.lasttopic = self.topics[-1]
+                    else:
+                        self.lasttopic = None
+            else:
+                if topic not in self.topics:
+                    self.starttimes[topic] = now
+                    self.startvals[topic] = pos
+                    self.topics.append(topic)
+                self.topicstates[topic] = pos, item, unit, total
+                self.curtopic = topic
+                if now - self.lastprint >= self.refresh and self.topics:
+                    if self._oktoprint(now):
+                        self.lastprint = now
+                        self.show(now, topic, *self.topicstates[topic])
+        finally:
+            self._refreshlock.release()
--- a/mercurial/pure/osutil.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/pure/osutil.py	Sat Jul 18 17:32:38 2015 -0500
@@ -59,7 +59,6 @@
     posixfile = open
 else:
     import ctypes, msvcrt
-    from errno import ESRCH, ENOENT
 
     _kernel32 = ctypes.windll.kernel32
 
@@ -99,14 +98,7 @@
 
     def _raiseioerror(name):
         err = ctypes.WinError()
-        # For python 2.4, treat ESRCH as ENOENT like WindowsError does
-        # in python 2.5 or later.
-        # py24:           WindowsError(3, '').errno => 3
-        # py25 or later:  WindowsError(3, '').errno => 2
-        errno = err.errno
-        if errno == ESRCH:
-            errno = ENOENT
-        raise IOError(errno, '%s: %s' % (name, err.strerror))
+        raise IOError(err.errno, '%s: %s' % (name, err.strerror))
 
     class posixfile(object):
         '''a file object aiming for POSIX-like semantics
--- a/mercurial/repair.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/repair.py	Sat Jul 18 17:32:38 2015 -0500
@@ -7,13 +7,13 @@
 # GNU General Public License version 2 or any later version.
 
 from mercurial import changegroup, exchange, util, bundle2
-from mercurial.node import short, hex
+from mercurial.node import short
 from mercurial.i18n import _
 import errno
 
 def _bundle(repo, bases, heads, node, suffix, compress=True):
     """create a bundle with the specified revisions as a backup"""
-    usebundle2 = (repo.ui.config('experimental', 'bundle2-exp') and
+    usebundle2 = (repo.ui.configbool('experimental', 'bundle2-exp', True) and
                   repo.ui.config('experimental', 'strip-bundle2-version'))
     if usebundle2:
         cgversion = repo.ui.config('experimental', 'strip-bundle2-version')
@@ -34,9 +34,7 @@
         vfs.mkdir(backupdir)
 
     # Include a hash of all the nodes in the filename for uniqueness
-    hexbases = (hex(n) for n in bases)
-    hexheads = (hex(n) for n in heads)
-    allcommits = repo.set('%ls::%ls', hexbases, hexheads)
+    allcommits = repo.set('%ln::%ln', bases, heads)
     allhashes = sorted(c.hex() for c in allcommits)
     totalhash = util.sha1(''.join(allhashes)).hexdigest()
     name = "%s/%s-%s-%s.hg" % (backupdir, short(node), totalhash[:8], suffix)
@@ -151,6 +149,12 @@
 
     mfst = repo.manifest
 
+    curtr = repo.currenttransaction()
+    if curtr is not None:
+        del curtr  # avoid carrying reference to transaction for nothing
+        msg = _('programming error: cannot strip from inside a transaction')
+        raise util.Abort(msg, hint=_('contact your extension maintainer'))
+
     tr = repo.transaction("strip")
     offset = len(tr.entries)
 
@@ -201,7 +205,7 @@
         for undovfs, undofile in repo.undofiles():
             try:
                 undovfs.unlink(undofile)
-            except OSError, e:
+            except OSError as e:
                 if e.errno != errno.ENOENT:
                     ui.warn(_('error removing %s: %s\n') %
                             (undovfs.join(undofile), str(e)))
@@ -223,3 +227,72 @@
             vfs.unlink(chgrpfile)
 
     repo.destroyed()
+
+def rebuildfncache(ui, repo):
+    """Rebuilds the fncache file from repo history.
+
+    Missing entries will be added. Extra entries will be removed.
+    """
+    repo = repo.unfiltered()
+
+    if 'fncache' not in repo.requirements:
+        ui.warn(_('(not rebuilding fncache because repository does not '
+                  'support fncache\n'))
+        return
+
+    lock = repo.lock()
+    try:
+        fnc = repo.store.fncache
+        # Trigger load of fncache.
+        if 'irrelevant' in fnc:
+            pass
+
+        oldentries = set(fnc.entries)
+        newentries = set()
+        seenfiles = set()
+
+        repolen = len(repo)
+        for rev in repo:
+            ui.progress(_('changeset'), rev, total=repolen)
+
+            ctx = repo[rev]
+            for f in ctx.files():
+                # This is to minimize I/O.
+                if f in seenfiles:
+                    continue
+                seenfiles.add(f)
+
+                i = 'data/%s.i' % f
+                d = 'data/%s.d' % f
+
+                if repo.store._exists(i):
+                    newentries.add(i)
+                if repo.store._exists(d):
+                    newentries.add(d)
+
+        ui.progress(_('changeset'), None)
+
+        addcount = len(newentries - oldentries)
+        removecount = len(oldentries - newentries)
+        for p in sorted(oldentries - newentries):
+            ui.write(_('removing %s\n') % p)
+        for p in sorted(newentries - oldentries):
+            ui.write(_('adding %s\n') % p)
+
+        if addcount or removecount:
+            ui.write(_('%d items added, %d removed from fncache\n') %
+                     (addcount, removecount))
+            fnc.entries = newentries
+            fnc._dirty = True
+
+            tr = repo.transaction('fncache')
+            try:
+                fnc.write(tr)
+                tr.close()
+            finally:
+                tr.release()
+        else:
+            ui.write(_('fncache already up to date\n'))
+    finally:
+        lock.release()
+
--- a/mercurial/repoview.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/repoview.py	Sat Jul 18 17:32:38 2015 -0500
@@ -115,16 +115,15 @@
     """
     wlock = fh = None
     try:
-        try:
-            wlock = repo.wlock(wait=False)
-            # write cache to file
-            newhash = cachehash(repo, hideable)
-            fh = repo.vfs.open(cachefile, 'w+b', atomictemp=True)
-            _writehiddencache(fh, newhash, hidden)
-        except (IOError, OSError):
-            repo.ui.debug('error writing hidden changesets cache')
-        except error.LockHeld:
-            repo.ui.debug('cannot obtain lock to write hidden changesets cache')
+        wlock = repo.wlock(wait=False)
+        # write cache to file
+        newhash = cachehash(repo, hideable)
+        fh = repo.vfs.open(cachefile, 'w+b', atomictemp=True)
+        _writehiddencache(fh, newhash, hidden)
+    except (IOError, OSError):
+        repo.ui.debug('error writing hidden changesets cache')
+    except error.LockHeld:
+        repo.ui.debug('cannot obtain lock to write hidden changesets cache')
     finally:
         if fh:
             fh.close()
@@ -197,7 +196,7 @@
     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 util.any(repo._phasecache.phaseroots[1:]):
+    if any(repo._phasecache.phaseroots[1:]):
         getphase = repo._phasecache.phase
         maymutable = filterrevs(repo, 'base')
         return frozenset(r for r in maymutable if getphase(repo, r))
--- a/mercurial/revlog.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/revlog.py	Sat Jul 18 17:32:38 2015 -0500
@@ -12,6 +12,7 @@
 """
 
 # import stuff from node for others to import from revlog
+import collections
 from node import bin, hex, nullid, nullrev
 from i18n import _
 import ancestor, mdiff, parsers, error, util, templatefilters
@@ -88,7 +89,7 @@
     if t == 'x':
         try:
             return _decompress(bin)
-        except zlib.error, e:
+        except zlib.error as e:
             raise RevlogError(_("revlog decompress error: %s") % str(e))
     if t == 'u':
         return bin[1:]
@@ -152,6 +153,10 @@
 ngshaoffset = 32
 versionformat = ">I"
 
+# corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
+# signed integer)
+_maxentrysize = 0x7fffffff
+
 class revlogio(object):
     def __init__(self):
         self.size = struct.calcsize(indexformatng)
@@ -241,7 +246,7 @@
             if len(i) > 0:
                 v = struct.unpack(versionformat, i[:4])[0]
                 self._initempty = False
-        except IOError, inst:
+        except IOError as inst:
             if inst.errno != errno.ENOENT:
                 raise
 
@@ -485,7 +490,7 @@
 
         # take all ancestors from heads that aren't in has
         missing = set()
-        visit = util.deque(r for r in heads if r not in has)
+        visit = collections.deque(r for r in heads if r not in has)
         while visit:
             r = visit.popleft()
             if r in missing:
@@ -725,7 +730,7 @@
             return self._headrevs()
 
     def computephases(self, roots):
-        return self.index.computephases(roots)
+        return self.index.computephasesmapsets(roots)
 
     def _headrevs(self):
         count = len(self)
@@ -1179,6 +1184,12 @@
         if link == nullrev:
             raise RevlogError(_("attempted to add linkrev -1 to %s")
                               % self.indexfile)
+
+        if len(text) > _maxentrysize:
+            raise RevlogError(
+                _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
+                % (self.indexfile, len(text)))
+
         node = node or self.hash(text, p1, p2)
         if node in self.nodemap:
             return node
@@ -1368,13 +1379,16 @@
             ifh.write(data[1])
             self.checkinlinesize(transaction, ifh)
 
-    def addgroup(self, bundle, linkmapper, transaction):
+    def addgroup(self, bundle, linkmapper, transaction, addrevisioncb=None):
         """
         add a delta group
 
         given a set of deltas, add them to the revision log. the
         first delta is against its parent, which should be in our
         log, the rest are against the previous delta.
+
+        If ``addrevisioncb`` is defined, it will be called with arguments of
+        this revlog and the node that was added.
         """
 
         # track the base of the current delta log
@@ -1448,6 +1462,14 @@
                 chain = self._addrevision(node, None, transaction, link,
                                           p1, p2, flags, (baserev, delta),
                                           ifh, dfh)
+
+                if addrevisioncb:
+                    # Data for added revision can't be read unless flushed
+                    # because _loadchunk always opensa new file handle and
+                    # there is no guarantee data was actually written yet.
+                    flush()
+                    addrevisioncb(self, chain)
+
                 if not dfh and not self._inline:
                     # addrevision switched from inline to conventional
                     # reopen the index
@@ -1560,7 +1582,7 @@
             actual = f.tell()
             f.close()
             dd = actual - expected
-        except IOError, inst:
+        except IOError as inst:
             if inst.errno != errno.ENOENT:
                 raise
             dd = 0
@@ -1579,7 +1601,7 @@
                     databytes += max(0, self.length(r))
                 dd = 0
                 di = actual - len(self) * s - databytes
-        except IOError, inst:
+        except IOError as inst:
             if inst.errno != errno.ENOENT:
                 raise
             di = 0
--- a/mercurial/revset.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/revset.py	Sat Jul 18 17:32:38 2015 -0500
@@ -25,23 +25,22 @@
     cl = repo.changelog
 
     def iterate():
-        revqueue, revsnode = None, None
+        revs.sort(reverse=True)
+        irevs = iter(revs)
         h = []
 
-        revs.sort(reverse=True)
-        revqueue = util.deque(revs)
-        if revqueue:
-            revsnode = revqueue.popleft()
-            heapq.heappush(h, -revsnode)
+        inputrev = next(irevs, None)
+        if inputrev is not None:
+            heapq.heappush(h, -inputrev)
 
         seen = set()
         while h:
             current = -heapq.heappop(h)
+            if current == inputrev:
+                inputrev = next(irevs, None)
+                if inputrev is not None:
+                    heapq.heappush(h, -inputrev)
             if current not in seen:
-                if revsnode and current == revsnode:
-                    if revqueue:
-                        revsnode = revqueue.popleft()
-                        heapq.heappush(h, -revsnode)
                 seen.add(current)
                 yield current
                 for parent in cl.parentrevs(current)[:cut]:
@@ -59,6 +58,8 @@
 
     def iterate():
         cl = repo.changelog
+        # XXX this should be 'parentset.min()' assuming 'parentset' is a
+        # smartset (and if it is not, it should.)
         first = min(revs)
         nullrev = node.nullrev
         if first == nullrev:
@@ -86,51 +87,59 @@
     visit = list(heads)
     reachable = set()
     seen = {}
+    # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
+    # (and if it is not, it should.)
     minroot = min(roots)
     roots = set(roots)
+    # prefetch all the things! (because python is slow)
+    reached = reachable.add
+    dovisit = visit.append
+    nextvisit = visit.pop
     # open-code the post-order traversal due to the tiny size of
     # sys.getrecursionlimit()
     while visit:
-        rev = visit.pop()
+        rev = nextvisit()
         if rev in roots:
-            reachable.add(rev)
+            reached(rev)
         parents = parentrevs(rev)
         seen[rev] = parents
         for parent in parents:
             if parent >= minroot and parent not in seen:
-                visit.append(parent)
+                dovisit(parent)
     if not reachable:
         return baseset()
     for rev in sorted(seen):
         for parent in seen[rev]:
             if parent in reachable:
-                reachable.add(rev)
+                reached(rev)
     return baseset(sorted(reachable))
 
 elements = {
-    "(": (21, ("group", 1, ")"), ("func", 1, ")")),
-    "##": (20, None, ("_concat", 20)),
-    "~": (18, None, ("ancestor", 18)),
-    "^": (18, None, ("parent", 18), ("parentpost", 18)),
-    "-": (5, ("negate", 19), ("minus", 5)),
-    "::": (17, ("dagrangepre", 17), ("dagrange", 17),
+    # token-type: binding-strength, primary, prefix, infix, suffix
+    "(": (21, None, ("group", 1, ")"), ("func", 1, ")"), None),
+    "##": (20, None, None, ("_concat", 20), None),
+    "~": (18, None, None, ("ancestor", 18), None),
+    "^": (18, None, None, ("parent", 18), ("parentpost", 18)),
+    "-": (5, None, ("negate", 19), ("minus", 5), None),
+    "::": (17, None, ("dagrangepre", 17), ("dagrange", 17),
            ("dagrangepost", 17)),
-    "..": (17, ("dagrangepre", 17), ("dagrange", 17),
+    "..": (17, None, ("dagrangepre", 17), ("dagrange", 17),
            ("dagrangepost", 17)),
-    ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
-    "not": (10, ("not", 10)),
-    "!": (10, ("not", 10)),
-    "and": (5, None, ("and", 5)),
-    "&": (5, None, ("and", 5)),
-    "%": (5, None, ("only", 5), ("onlypost", 5)),
-    "or": (4, None, ("or", 4)),
-    "|": (4, None, ("or", 4)),
-    "+": (4, None, ("or", 4)),
-    ",": (2, None, ("list", 2)),
-    ")": (0, None, None),
-    "symbol": (0, ("symbol",), None),
-    "string": (0, ("string",), None),
-    "end": (0, None, None),
+    ":": (15, "rangeall", ("rangepre", 15), ("range", 15), ("rangepost", 15)),
+    "not": (10, None, ("not", 10), None, None),
+    "!": (10, None, ("not", 10), None, None),
+    "and": (5, None, None, ("and", 5), None),
+    "&": (5, None, None, ("and", 5), None),
+    "%": (5, None, None, ("only", 5), ("onlypost", 5)),
+    "or": (4, None, None, ("or", 4), None),
+    "|": (4, None, None, ("or", 4), None),
+    "+": (4, None, None, ("or", 4), None),
+    "=": (3, None, None, ("keyvalue", 3), None),
+    ",": (2, None, None, ("list", 2), None),
+    ")": (0, None, None, None, None),
+    "symbol": (0, "symbol", None, None, None),
+    "string": (0, "string", None, None, None),
+    "end": (0, None, None, None, None),
 }
 
 keywords = set(['and', 'or', 'not'])
@@ -183,7 +192,7 @@
         elif c == '#' and program[pos:pos + 2] == '##': # look ahead carefully
             yield ('##', None, pos)
             pos += 1 # skip ahead
-        elif c in "():,-|&+!~^%": # handle simple operators
+        elif c in "():=,-|&+!~^%": # handle simple operators
             yield (c, None, pos)
         elif (c in '"\'' or c == 'r' and
               program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
@@ -274,6 +283,10 @@
         raise error.ParseError(err)
     return l
 
+def getargsdict(x, funcname, keys):
+    return parser.buildargsdict(getlist(x), funcname, keys.split(),
+                                keyvaluenode='keyvalue', keynode='symbol')
+
 def isvalidsymbol(tree):
     """Examine whether specified ``tree`` is valid ``symbol`` or not
     """
@@ -314,6 +327,13 @@
     s = methods[x[0]](repo, subset, *x[1:])
     if util.safehasattr(s, 'isascending'):
         return s
+    if (repo.ui.configbool('devel', 'all-warnings')
+            or repo.ui.configbool('devel', 'old-revset')):
+        # else case should not happen, because all non-func are internal,
+        # ignoring for now.
+        if x[0] == 'func' and x[1][0] == 'symbol' and x[1][1] in symbols:
+            repo.ui.develwarn('revset "%s" use list instead of smartset, '
+                              '(upgrade your code)' % x[1][1])
     return baseset(s)
 
 def _getrevsource(repo, r):
@@ -335,11 +355,6 @@
         return baseset([x])
     return baseset()
 
-def symbolset(repo, subset, x):
-    if x in symbols:
-        raise error.ParseError(_("can't use %s here") % x)
-    return stringset(repo, subset, x)
-
 def rangeset(repo, subset, x, y):
     m = getset(repo, fullreposet(repo), x)
     n = getset(repo, fullreposet(repo), y)
@@ -348,24 +363,36 @@
         return baseset()
     m, n = m.first(), n.last()
 
-    if m < n:
+    if m == n:
+        r = baseset([m])
+    elif n == node.wdirrev:
+        r = spanset(repo, m, len(repo)) + baseset([n])
+    elif m == node.wdirrev:
+        r = baseset([m]) + spanset(repo, len(repo) - 1, n - 1)
+    elif m < n:
         r = spanset(repo, m, n + 1)
     else:
         r = spanset(repo, m, n - 1)
+    # XXX We should combine with subset first: 'subset & baseset(...)'. This is
+    # necessary to ensure we preserve the order in subset.
+    #
+    # This has performance implication, carrying the sorting over when possible
+    # would be more efficient.
     return r & subset
 
 def dagrange(repo, subset, x, y):
     r = fullreposet(repo)
     xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
+    # XXX We should combine with subset first: 'subset & baseset(...)'. This is
+    # necessary to ensure we preserve the order in subset.
     return xs & subset
 
 def andset(repo, subset, x, y):
     return getset(repo, getset(repo, subset, x), y)
 
-def orset(repo, subset, x, y):
-    xl = getset(repo, subset, x)
-    yl = getset(repo, subset - xl, y)
-    return xl + yl
+def orset(repo, subset, *xs):
+    rs = [getset(repo, subset, x) for x in xs]
+    return _combinesets(rs)
 
 def notset(repo, subset, x):
     return subset - getset(repo, subset, x)
@@ -373,10 +400,17 @@
 def listset(repo, subset, a, b):
     raise error.ParseError(_("can't use a list in this context"))
 
+def keyvaluepair(repo, subset, k, v):
+    raise error.ParseError(_("can't use a key-value pair in this context"))
+
 def func(repo, subset, a, b):
     if a[0] == 'symbol' and a[1] in symbols:
         return symbols[a[1]](repo, subset, b)
-    raise error.UnknownIdentifier(a[1], symbols.keys())
+
+    keep = lambda fn: getattr(fn, '__doc__', None) is not None
+
+    syms = [s for (s, fn) in symbols.items() if keep(fn)]
+    raise error.UnknownIdentifier(a[1], syms)
 
 # functions
 
@@ -610,17 +644,19 @@
     return subset.filter(matches)
 
 def _children(repo, narrow, parentset):
+    if not parentset:
+        return baseset()
     cs = set()
-    if not parentset:
-        return baseset(cs)
     pr = repo.changelog.parentrevs
-    minrev = min(parentset)
+    minrev = parentset.min()
     for r in narrow:
         if r <= minrev:
             continue
         for p in pr(r):
             if p in parentset:
                 cs.add(r)
+    # XXX using a set to feed the baseset is wrong. Sets are not ordered.
+    # This does not break because of other fullreposet misbehavior.
     return baseset(cs)
 
 def children(repo, subset, x):
@@ -793,16 +829,6 @@
     divergent = obsmod.getrevs(repo, 'divergent')
     return subset & divergent
 
-def draft(repo, subset, x):
-    """``draft()``
-    Changeset in draft phase."""
-    # i18n: "draft" is a keyword
-    getargs(x, 0, 0, _("draft takes no arguments"))
-    phase = repo._phasecache.phase
-    target = phases.draft
-    condition = lambda r: phase(repo, r) == target
-    return subset.filter(condition, cache=False)
-
 def extinct(repo, subset, x):
     """``extinct()``
     Obsolete changesets with obsolete descendants only.
@@ -821,16 +847,19 @@
     a regular expression. To match a value that actually starts with `re:`,
     use the prefix `literal:`.
     """
-
-    # i18n: "extra" is a keyword
-    l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
+    args = getargsdict(x, 'extra', 'label value')
+    if 'label' not in args:
+        # i18n: "extra" is a keyword
+        raise error.ParseError(_('extra takes at least 1 argument'))
     # i18n: "extra" is a keyword
-    label = getstring(l[0], _('first argument to extra must be a string'))
+    label = getstring(args['label'], _('first argument to extra must be '
+                                       'a string'))
     value = None
 
-    if len(l) > 1:
+    if 'value' in args:
         # i18n: "extra" is a keyword
-        value = getstring(l[1], _('second argument to extra must be a string'))
+        value = getstring(args['value'], _('second argument to extra must be '
+                                           'a string'))
         kind, value, matcher = _stringmatcher(value)
 
     def _matchvalue(r):
@@ -1008,7 +1037,7 @@
     try:
         # i18n: "grep" is a keyword
         gr = re.compile(getstring(x, _("grep requires a string")))
-    except re.error, e:
+    except re.error as e:
         raise error.ParseError(_('invalid match pattern: %s') % e)
 
     def matches(x):
@@ -1097,9 +1126,14 @@
     # i18n: "head" is a keyword
     getargs(x, 0, 0, _("head takes no arguments"))
     hs = set()
+    cl = repo.changelog
     for b, ls in repo.branchmap().iteritems():
-        hs.update(repo[h].rev() for h in ls)
-    return baseset(hs).filter(subset.__contains__)
+        hs.update(cl.rev(h) for h in ls)
+    # XXX using a set to feed the baseset is wrong. Sets are not ordered.
+    # This does not break because of other fullreposet misbehavior.
+    # XXX We should combine with subset first: 'subset & baseset(...)'. This is
+    # necessary to ensure we preserve the order in subset.
+    return baseset(hs) & subset
 
 def heads(repo, subset, x):
     """``heads(set)``
@@ -1128,8 +1162,8 @@
 
     def matches(r):
         c = repo[r]
-        return util.any(kw in encoding.lower(t) for t in c.files() + [c.user(),
-            c.description()])
+        return any(kw in encoding.lower(t)
+                   for t in c.files() + [c.user(), c.description()])
 
     return subset.filter(matches)
 
@@ -1152,12 +1186,11 @@
     result = []
     it = iter(os)
     for x in xrange(lim):
-        try:
-            y = it.next()
-            if y in ss:
-                result.append(y)
-        except (StopIteration):
+        y = next(it, None)
+        if y is None:
             break
+        elif y in ss:
+            result.append(y)
     return baseset(result)
 
 def last(repo, subset, x):
@@ -1180,12 +1213,11 @@
     result = []
     it = iter(os)
     for x in xrange(lim):
-        try:
-            y = it.next()
-            if y in ss:
-                result.append(y)
-        except (StopIteration):
+        y = next(it, None)
+        if y is None:
             break
+        elif y in ss:
+            result.append(y)
     return baseset(result)
 
 def maxrev(repo, subset, x):
@@ -1217,6 +1249,8 @@
     cl = repo.changelog
     if not subset:
         return baseset()
+    # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
+    # (and if it is not, it should.)
     baserev = min(subset)
     parentscount = [0]*(len(repo) - baserev)
     for r in cl.revs(start=baserev + 1):
@@ -1340,6 +1374,8 @@
         exclude = getset(repo, fullreposet(repo), args[1])
 
     results = set(cl.findmissingrevs(common=exclude, heads=include))
+    # XXX we should turn this into a baseset instead of a set, smartset may do
+    # some optimisations from the fact this is a baseset.
     return subset & results
 
 def origin(repo, subset, x):
@@ -1369,6 +1405,8 @@
 
     o = set([_firstsrc(r) for r in dests])
     o -= set([None])
+    # XXX we should turn this into a baseset instead of a set, smartset may do
+    # some optimisations from the fact this is a baseset.
     return subset & o
 
 def outgoing(repo, subset, x):
@@ -1411,6 +1449,8 @@
     for r in getset(repo, fullreposet(repo), x):
         ps.add(cl.parentrevs(r)[0])
     ps -= set([node.nullrev])
+    # XXX we should turn this into a baseset instead of a set, smartset may do
+    # some optimisations from the fact this is a baseset.
     return subset & ps
 
 def p2(repo, subset, x):
@@ -1432,6 +1472,8 @@
     for r in getset(repo, fullreposet(repo), x):
         ps.add(cl.parentrevs(r)[1])
     ps -= set([node.nullrev])
+    # XXX we should turn this into a baseset instead of a set, smartset may do
+    # some optimisations from the fact this is a baseset.
     return subset & ps
 
 def parents(repo, subset, x):
@@ -1443,11 +1485,45 @@
     else:
         ps = set()
         cl = repo.changelog
+        up = ps.update
+        parentrevs = cl.parentrevs
         for r in getset(repo, fullreposet(repo), x):
-            ps.update(cl.parentrevs(r))
+            if r == node.wdirrev:
+                up(p.rev() for p in repo[r].parents())
+            else:
+                up(parentrevs(r))
     ps -= set([node.nullrev])
     return subset & ps
 
+def _phase(repo, subset, target):
+    """helper to select all rev in phase <target>"""
+    repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
+    if repo._phasecache._phasesets:
+        s = repo._phasecache._phasesets[target] - repo.changelog.filteredrevs
+        s = baseset(s)
+        s.sort() # set are non ordered, so we enforce ascending
+        return subset & s
+    else:
+        phase = repo._phasecache.phase
+        condition = lambda r: phase(repo, r) == target
+        return subset.filter(condition, cache=False)
+
+def draft(repo, subset, x):
+    """``draft()``
+    Changeset in draft phase."""
+    # i18n: "draft" is a keyword
+    getargs(x, 0, 0, _("draft takes no arguments"))
+    target = phases.draft
+    return _phase(repo, subset, target)
+
+def secret(repo, subset, x):
+    """``secret()``
+    Changeset in secret phase."""
+    # i18n: "secret" is a keyword
+    getargs(x, 0, 0, _("secret takes no arguments"))
+    target = phases.secret
+    return _phase(repo, subset, target)
+
 def parentspec(repo, subset, x, n):
     """``set^0``
     The set.
@@ -1487,6 +1563,23 @@
     except error.RepoLookupError:
         return baseset()
 
+# for internal use
+def _notpublic(repo, subset, x):
+    getargs(x, 0, 0, "_notpublic takes no arguments")
+    repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
+    if repo._phasecache._phasesets:
+        s = set()
+        for u in repo._phasecache._phasesets[1:]:
+            s.update(u)
+        s = baseset(s - repo.changelog.filteredrevs)
+        s.sort()
+        return subset & s
+    else:
+        phase = repo._phasecache.phase
+        target = phases.public
+        condition = lambda r: phase(repo, r) != target
+        return subset.filter(condition, cache=False)
+
 def public(repo, subset, x):
     """``public()``
     Changeset in public phase."""
@@ -1685,19 +1778,13 @@
     Changesets in set with no parent changeset in set.
     """
     s = getset(repo, fullreposet(repo), x)
-    subset = baseset([r for r in s if r in subset])
-    cs = _children(repo, subset, s)
-    return subset - cs
-
-def secret(repo, subset, x):
-    """``secret()``
-    Changeset in secret phase."""
-    # i18n: "secret" is a keyword
-    getargs(x, 0, 0, _("secret takes no arguments"))
-    phase = repo._phasecache.phase
-    target = phases.secret
-    condition = lambda r: phase(repo, r) == target
-    return subset.filter(condition, cache=False)
+    parents = repo.changelog.parentrevs
+    def filter(r):
+        for p in parents(r):
+            if 0 <= p and p in s:
+                return False
+        return True
+    return subset & s.filter(filter)
 
 def sort(repo, subset, x):
     """``sort(set[, [-]key...])``
@@ -1788,7 +1875,7 @@
             return s.added or s.modified or s.removed
 
         if s.added:
-            return util.any(submatches(c.substate.keys()))
+            return any(submatches(c.substate.keys()))
 
         if s.modified:
             subs = set(c.p1().substate.keys())
@@ -1799,7 +1886,7 @@
                     return True
 
         if s.removed:
-            return util.any(submatches(c.p1().substate.keys()))
+            return any(submatches(c.p1().substate.keys()))
 
         return False
 
@@ -1836,7 +1923,7 @@
         pattern = pattern[3:]
         try:
             regex = re.compile(pattern)
-        except re.error, e:
+        except re.error as e:
             raise error.ParseError(_('invalid regular expression: %s')
                                    % e)
         return 're', pattern, regex.search
@@ -1906,8 +1993,8 @@
 def wdir(repo, subset, x):
     # i18n: "wdir" is a keyword
     getargs(x, 0, 0, _("wdir takes no arguments"))
-    if None in subset or isinstance(subset, fullreposet):
-        return baseset([None])
+    if node.wdirrev in subset or isinstance(subset, fullreposet):
+        return baseset([node.wdirrev])
     return baseset()
 
 # for internal use
@@ -1915,9 +2002,26 @@
     s = getstring(x, "internal error")
     if not s:
         return baseset()
-    ls = [repo[r].rev() for r in s.split('\0')]
-    s = subset
-    return baseset([r for r in ls if r in s])
+    # remove duplicates here. it's difficult for caller to deduplicate sets
+    # because different symbols can point to the same rev.
+    cl = repo.changelog
+    ls = []
+    seen = set()
+    for t in s.split('\0'):
+        try:
+            # fast path for integer revision
+            r = int(t)
+            if str(r) != t or r not in cl:
+                raise ValueError
+        except ValueError:
+            r = repo[t].rev()
+        if r in seen:
+            continue
+        if (r in subset
+            or r == node.nullrev and isinstance(subset, fullreposet)):
+            ls.append(r)
+        seen.add(r)
+    return baseset(ls)
 
 # for internal use
 def _intlist(repo, subset, x):
@@ -1993,6 +2097,7 @@
     "parents": parents,
     "present": present,
     "public": public,
+    "_notpublic": _notpublic,
     "remote": remote,
     "removes": removes,
     "rev": rev,
@@ -2067,6 +2172,7 @@
     "parents",
     "present",
     "public",
+    "_notpublic",
     "remote",
     "removes",
     "rev",
@@ -2089,16 +2195,16 @@
     "range": rangeset,
     "dagrange": dagrange,
     "string": stringset,
-    "symbol": symbolset,
+    "symbol": stringset,
     "and": andset,
     "or": orset,
     "not": notset,
     "list": listset,
+    "keyvalue": keyvaluepair,
     "func": func,
     "ancestor": ancestorspec,
     "parent": parentspec,
     "parentpost": p1,
-    "only": only,
 }
 
 def optimize(x, small):
@@ -2121,6 +2227,8 @@
         return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
     elif op == 'dagrangepost':
         return optimize(('func', ('symbol', 'descendants'), x[1]), small)
+    elif op == 'rangeall':
+        return optimize(('range', ('string', '0'), ('string', 'tip')), small)
     elif op == 'rangepre':
         return optimize(('range', ('string', '0'), x[1]), small)
     elif op == 'rangepost':
@@ -2153,14 +2261,45 @@
             return w, (op, tb, ta)
         return w, (op, ta, tb)
     elif op == 'or':
-        wa, ta = optimize(x[1], False)
-        wb, tb = optimize(x[2], False)
-        if wb < wa:
-            wb, wa = wa, wb
-        return max(wa, wb), (op, ta, tb)
+        # fast path for machine-generated expression, that is likely to have
+        # lots of trivial revisions: 'a + b + c()' to '_list(a b) + c()'
+        ws, ts, ss = [], [], []
+        def flushss():
+            if not ss:
+                return
+            if len(ss) == 1:
+                w, t = ss[0]
+            else:
+                s = '\0'.join(t[1] for w, t in ss)
+                y = ('func', ('symbol', '_list'), ('string', s))
+                w, t = optimize(y, False)
+            ws.append(w)
+            ts.append(t)
+            del ss[:]
+        for y in x[1:]:
+            w, t = optimize(y, False)
+            if t[0] == 'string' or t[0] == 'symbol':
+                ss.append((w, t))
+                continue
+            flushss()
+            ws.append(w)
+            ts.append(t)
+        flushss()
+        if len(ts) == 1:
+            return ws[0], ts[0] # 'or' operation is fully optimized out
+        # we can't reorder trees by weight because it would change the order.
+        # ("sort(a + b)" == "sort(b + a)", but "a + b" != "b + a")
+        #   ts = tuple(t for w, t in sorted(zip(ws, ts), key=lambda wt: wt[0]))
+        return max(ws), (op,) + tuple(ts)
     elif op == 'not':
-        o = optimize(x[1], not small)
-        return o[0], (op, o[1])
+        # Optimize not public() to _notpublic() because we have a fast version
+        if x[1] == ('func', ('symbol', 'public'), None):
+            newsym =  ('func', ('symbol', '_notpublic'), None)
+            o = optimize(newsym, not small)
+            return o[0], o[1]
+        else:
+            o = optimize(x[1], not small)
+            return o[0], (op, o[1])
     elif op == 'parentpost':
         o = optimize(x[1], small)
         return o[0], (op, o[1])
@@ -2274,9 +2413,9 @@
     >>> _parsealiasdecl('foo($1, $2, $1)')
     ('foo', None, None, 'argument names collide with each other')
     """
-    p = parser.parser(_tokenizealias, elements)
+    p = parser.parser(elements)
     try:
-        tree, pos = p.parse(decl)
+        tree, pos = p.parse(_tokenizealias(decl))
         if (pos != len(decl)):
             raise error.ParseError(_('invalid token'), pos)
 
@@ -2303,7 +2442,7 @@
             return (name, ('func', ('symbol', name)), args, None)
 
         return (decl, None, None, _("invalid format"))
-    except error.ParseError, inst:
+    except error.ParseError as inst:
         return (decl, None, None, parseerrordetail(inst))
 
 def _parsealiasdefn(defn, args):
@@ -2365,11 +2504,11 @@
                                            pos)
             yield (t, value, pos)
 
-    p = parser.parser(tokenizedefn, elements)
-    tree, pos = p.parse(defn)
+    p = parser.parser(elements)
+    tree, pos = p.parse(tokenizedefn(defn))
     if pos != len(defn):
         raise error.ParseError(_('invalid token'), pos)
-    return tree
+    return parser.simplifyinfixops(tree, ('or',))
 
 class revsetalias(object):
     # whether own `error` information is already shown or not.
@@ -2392,7 +2531,7 @@
             self.replacement = _parsealiasdefn(value, self.args)
             # Check for placeholder injection
             _checkaliasarg(self.replacement, self.args)
-        except error.ParseError, inst:
+        except error.ParseError as inst:
             self.error = _('failed to parse the definition of revset alias'
                            ' "%s": %s') % (self.name, parseerrordetail(inst))
 
@@ -2496,8 +2635,11 @@
         return tuple(foldconcat(t) for t in tree)
 
 def parse(spec, lookup=None):
-    p = parser.parser(tokenize, elements)
-    return p.parse(spec, lookup=lookup)
+    p = parser.parser(elements)
+    tree, pos = p.parse(tokenize(spec, lookup=lookup))
+    if pos != len(spec):
+        raise error.ParseError(_("invalid token"), pos)
+    return parser.simplifyinfixops(tree, ('or',))
 
 def posttreebuilthook(tree, repo):
     # hook for extensions to execute code on the optimized tree
@@ -2509,9 +2651,7 @@
     lookup = None
     if repo:
         lookup = repo.__contains__
-    tree, pos = parse(spec, lookup)
-    if (pos != len(spec)):
-        raise error.ParseError(_("invalid token"), pos)
+    tree = parse(spec, lookup)
     if ui:
         tree = findaliases(ui, tree, showwarning=ui.warn)
     tree = foldconcat(tree)
@@ -2622,19 +2762,7 @@
     return ret
 
 def prettyformat(tree):
-    def _prettyformat(tree, level, lines):
-        if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
-            lines.append((level, str(tree)))
-        else:
-            lines.append((level, '(%s' % tree[0]))
-            for s in tree[1:]:
-                _prettyformat(s, level + 1, lines)
-            lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
-
-    lines = []
-    _prettyformat(tree, 0, lines)
-    output = '\n'.join(('  '*l + s) for l, s in lines)
-    return output
+    return parser.prettyformat(tree, ('string', 'symbol'))
 
 def depth(tree):
     if isinstance(tree, tuple):
@@ -2926,20 +3054,73 @@
 
     def last(self):
         it = None
-        if self._subset.isascending:
-            it = self.fastdesc
-        elif self._subset.isdescending:
+        if self.isascending():
             it = self.fastdesc
-        if it is None:
-            # slowly consume everything. This needs improvement
-            it = lambda: reversed(list(self))
-        for x in it():
+        elif self.isdescending():
+            it = self.fastasc
+        if it is not None:
+            for x in it():
+                return x
+            return None #empty case
+        else:
+            x = None
+            for x in self:
+                pass
             return x
-        return None
 
     def __repr__(self):
         return '<%s %r>' % (type(self).__name__, self._subset)
 
+# this function will be removed, or merged to addset or orset, when
+# - scmutil.revrange() can be rewritten to not combine calculated smartsets
+# - or addset can handle more than two sets without balanced tree
+def _combinesets(subsets):
+    """Create balanced tree of addsets representing union of given sets"""
+    if not subsets:
+        return baseset()
+    if len(subsets) == 1:
+        return subsets[0]
+    p = len(subsets) // 2
+    xs = _combinesets(subsets[:p])
+    ys = _combinesets(subsets[p:])
+    return addset(xs, ys)
+
+def _iterordered(ascending, iter1, iter2):
+    """produce an ordered iteration from two iterators with the same order
+
+    The ascending is used to indicated the iteration direction.
+    """
+    choice = max
+    if ascending:
+        choice = min
+
+    val1 = None
+    val2 = None
+    try:
+        # Consume both iterators in an ordered way until one is empty
+        while True:
+            if val1 is None:
+                val1 = iter1.next()
+            if val2 is None:
+                val2 = iter2.next()
+            next = choice(val1, val2)
+            yield next
+            if val1 == next:
+                val1 = None
+            if val2 == next:
+                val2 = None
+    except StopIteration:
+        # Flush any remaining values and consume the other one
+        it = iter2
+        if val1 is not None:
+            yield val1
+            it = iter1
+        elif val2 is not None:
+            # might have been equality and both are empty
+            yield val2
+        for val in it:
+            yield val
+
 class addset(abstractsmartset):
     """Represent the addition of two sets
 
@@ -2949,6 +3130,64 @@
     If the ascending attribute is set, that means the two structures are
     ordered in either an ascending or descending way. Therefore, we can add
     them maintaining the order by iterating over both at the same time
+
+    >>> xs = baseset([0, 3, 2])
+    >>> ys = baseset([5, 2, 4])
+
+    >>> rs = addset(xs, ys)
+    >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()
+    (True, True, False, True, 0, 4)
+    >>> rs = addset(xs, baseset([]))
+    >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()
+    (True, True, False, 0, 2)
+    >>> rs = addset(baseset([]), baseset([]))
+    >>> bool(rs), 0 in rs, rs.first(), rs.last()
+    (False, False, None, None)
+
+    iterate unsorted:
+    >>> rs = addset(xs, ys)
+    >>> [x for x in rs]  # without _genlist
+    [0, 3, 2, 5, 4]
+    >>> assert not rs._genlist
+    >>> len(rs)
+    5
+    >>> [x for x in rs]  # with _genlist
+    [0, 3, 2, 5, 4]
+    >>> assert rs._genlist
+
+    iterate ascending:
+    >>> rs = addset(xs, ys, ascending=True)
+    >>> [x for x in rs], [x for x in rs.fastasc()]  # without _asclist
+    ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
+    >>> assert not rs._asclist
+    >>> len(rs)
+    5
+    >>> [x for x in rs], [x for x in rs.fastasc()]
+    ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
+    >>> assert rs._asclist
+
+    iterate descending:
+    >>> rs = addset(xs, ys, ascending=False)
+    >>> [x for x in rs], [x for x in rs.fastdesc()]  # without _asclist
+    ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
+    >>> assert not rs._asclist
+    >>> len(rs)
+    5
+    >>> [x for x in rs], [x for x in rs.fastdesc()]
+    ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
+    >>> assert rs._asclist
+
+    iterate ascending without fastasc:
+    >>> rs = addset(xs, generatorset(ys), ascending=True)
+    >>> assert rs.fastasc is None
+    >>> [x for x in rs]
+    [0, 2, 3, 4, 5]
+
+    iterate descending without fastdesc:
+    >>> rs = addset(generatorset(xs), ys, ascending=False)
+    >>> assert rs.fastdesc is None
+    >>> [x for x in rs]
+    [5, 4, 3, 2, 0]
     """
     def __init__(self, revs1, revs2, ascending=None):
         self._r1 = revs1
@@ -2967,10 +3206,10 @@
     @util.propertycache
     def _list(self):
         if not self._genlist:
-            self._genlist = baseset(self._iterator())
+            self._genlist = baseset(iter(self))
         return self._genlist
 
-    def _iterator(self):
+    def __iter__(self):
         """Iterate over both collections without repeating elements
 
         If the ascending attribute is not set, iterate over the first one and
@@ -2981,35 +3220,41 @@
         same time, yielding only one value at a time in the given order.
         """
         if self._ascending is None:
-            def gen():
+            if self._genlist:
+                return iter(self._genlist)
+            def arbitraryordergen():
                 for r in self._r1:
                     yield r
                 inr1 = self._r1.__contains__
                 for r in self._r2:
                     if not inr1(r):
                         yield r
-            gen = gen()
-        else:
-            iter1 = iter(self._r1)
-            iter2 = iter(self._r2)
-            gen = self._iterordered(self._ascending, iter1, iter2)
-        return gen
-
-    def __iter__(self):
-        if self._ascending is None:
-            if self._genlist:
-                return iter(self._genlist)
-            return iter(self._iterator())
+            return arbitraryordergen()
+        # try to use our own fast iterator if it exists
         self._trysetasclist()
         if self._ascending:
-            it = self.fastasc
+            attr = 'fastasc'
         else:
-            it = self.fastdesc
-        if it is None:
-            # consume the gen and try again
-            self._list
-            return iter(self)
-        return it()
+            attr = 'fastdesc'
+        it = getattr(self, attr)
+        if it is not None:
+            return it()
+        # maybe half of the component supports fast
+        # get iterator for _r1
+        iter1 = getattr(self._r1, attr)
+        if iter1 is None:
+            # let's avoid side effect (not sure it matters)
+            iter1 = iter(sorted(self._r1, reverse=not self._ascending))
+        else:
+            iter1 = iter1()
+        # get iterator for _r2
+        iter2 = getattr(self._r2, attr)
+        if iter2 is None:
+            # let's avoid side effect (not sure it matters)
+            iter2 = iter(sorted(self._r2, reverse=not self._ascending))
+        else:
+            iter2 = iter2()
+        return _iterordered(self._ascending, iter1, iter2)
 
     def _trysetasclist(self):
         """populate the _asclist attribute if possible and necessary"""
@@ -3025,7 +3270,7 @@
         iter2 = self._r2.fastasc
         if None in (iter1, iter2):
             return None
-        return lambda: self._iterordered(True, iter1(), iter2())
+        return lambda: _iterordered(True, iter1(), iter2())
 
     @property
     def fastdesc(self):
@@ -3036,48 +3281,7 @@
         iter2 = self._r2.fastdesc
         if None in (iter1, iter2):
             return None
-        return lambda: self._iterordered(False, iter1(), iter2())
-
-    def _iterordered(self, ascending, iter1, iter2):
-        """produce an ordered iteration from two iterators with the same order
-
-        The ascending is used to indicated the iteration direction.
-        """
-        choice = max
-        if ascending:
-            choice = min
-
-        val1 = None
-        val2 = None
-
-        choice = max
-        if ascending:
-            choice = min
-        try:
-            # Consume both iterators in an ordered way until one is
-            # empty
-            while True:
-                if val1 is None:
-                    val1 = iter1.next()
-                if val2 is None:
-                    val2 = iter2.next()
-                next = choice(val1, val2)
-                yield next
-                if val1 == next:
-                    val1 = None
-                if val2 == next:
-                    val2 = None
-        except StopIteration:
-            # Flush any remaining values and consume the other one
-            it = iter2
-            if val1 is not None:
-                yield val1
-                it = iter1
-            elif val2 is not None:
-                # might have been equality and both are empty
-                yield val2
-            for val in it:
-                yield val
+        return lambda: _iterordered(False, iter1(), iter2())
 
     def __contains__(self, x):
         return x in self._r1 or x in self._r2
@@ -3144,7 +3348,12 @@
                 self.__contains__ = self._desccontains
 
     def __nonzero__(self):
-        for r in self:
+        # Do not use 'for r in self' because it will enforce the iteration
+        # order (default ascending), possibly unrolling a whole descending
+        # iterator.
+        if self._genlist:
+            return True
+        for r in self._consumegen():
             return True
         return False
 
@@ -3268,9 +3477,7 @@
             for x in self._consumegen():
                 pass
             return self.first()
-        if self:
-            return it().next()
-        return None
+        return next(it(), None)
 
     def last(self):
         if self._ascending:
@@ -3282,9 +3489,7 @@
             for x in self._consumegen():
                 pass
             return self.first()
-        if self:
-            return it().next()
-        return None
+        return next(it(), None)
 
     def __repr__(self):
         d = {False: '-', True: '+'}[self._ascending]
@@ -3424,6 +3629,17 @@
             # object.
             other = baseset(other - self._hiddenrevs)
 
+        # XXX As fullreposet is also used as bootstrap, this is wrong.
+        #
+        # With a giveme312() revset returning [3,1,2], this makes
+        #   'hg log -r "giveme312()"' -> 1, 2, 3 (wrong)
+        # We cannot just drop it because other usage still need to sort it:
+        #   'hg log -r "all() and giveme312()"' -> 1, 2, 3 (right)
+        #
+        # There is also some faulty revset implementations that rely on it
+        # (eg: children as of its state in e8075329c5fb)
+        #
+        # When we fix the two points above we can move this into the if clause
         other.sort(reverse=self.isdescending())
         return other
 
--- a/mercurial/scmutil.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/scmutil.py	Sat Jul 18 17:32:38 2015 -0500
@@ -6,11 +6,11 @@
 # GNU General Public License version 2 or any later version.
 
 from i18n import _
-from mercurial.node import nullrev
+from mercurial.node import nullrev, wdirrev
 import util, error, osutil, revset, similar, encoding, phases
 import pathutil
 import match as matchmod
-import os, errno, re, glob, tempfile, shutil, stat, inspect
+import os, errno, re, glob, tempfile, shutil, stat
 
 if os.name == 'nt':
     import scmwindows as scmplatform
@@ -80,9 +80,24 @@
     # has been modified (in ctx2) but not yet committed (in ctx1).
     subpaths = dict.fromkeys(ctx2.substate, ctx2)
     subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
+
+    missing = set()
+
+    for subpath in ctx2.substate:
+        if subpath not in ctx1.substate:
+            del subpaths[subpath]
+            missing.add(subpath)
+
     for subpath, ctx in sorted(subpaths.iteritems()):
         yield subpath, ctx.sub(subpath)
 
+    # Yield an empty subrepo based on ctx1 for anything only in ctx2.  That way,
+    # status and diff will have an accurate result when it does
+    # 'sub.{status|diff}(rev2)'.  Otherwise, the ctx2 subrepo is compared
+    # against itself.
+    for subpath in missing:
+        yield subpath, ctx2.nullsub(subpath, ctx1)
+
 def nochangesfound(ui, repo, excluded=None):
     '''Report no changes for push/pull, excluded is None or a list of
     nodes excluded from the push/pull.
@@ -172,16 +187,6 @@
         self._loweredfiles.add(fl)
         self._newfiles.add(f)
 
-def develwarn(tui, msg):
-    """issue a developer warning message"""
-    msg = 'devel-warn: ' + msg
-    if tui.tracebackflag:
-        util.debugstacktrace(msg, 2)
-    else:
-        curframe = inspect.currentframe()
-        calframe = inspect.getouterframes(curframe, 2)
-        tui.write_err('%s at: %s:%s (%s)\n' % ((msg,) + calframe[2][1:4]))
-
 def filteredhash(repo, maxrev):
     """build hash of filtered revisions in the current repoview.
 
@@ -217,7 +222,7 @@
         '''gracefully return an empty string for missing files'''
         try:
             return self.read(path)
-        except IOError, inst:
+        except IOError as inst:
             if inst.errno != errno.ENOENT:
                 raise
         return ""
@@ -226,7 +231,7 @@
         '''gracefully return an empty array for missing files'''
         try:
             return self.readlines(path, mode=mode)
-        except IOError, inst:
+        except IOError as inst:
             if inst.errno != errno.ENOENT:
                 raise
         return []
@@ -277,9 +282,21 @@
         finally:
             fp.close()
 
+    def basename(self, path):
+        """return base element of a path (as os.path.basename would do)
+
+        This exists to allow handling of strange encoding if needed."""
+        return os.path.basename(path)
+
     def chmod(self, path, mode):
         return os.chmod(self.join(path), mode)
 
+    def dirname(self, path):
+        """return dirname element of a path (as os.path.dirname would do)
+
+        This exists to allow handling of strange encoding if needed."""
+        return os.path.dirname(path)
+
     def exists(self, path=None):
         return os.path.exists(self.join(path))
 
@@ -445,7 +462,7 @@
     def _fixfilemode(self, name):
         if self.createmode is None or not self._chmod:
             return
-        os.chmod(name, self.createmode & 0666)
+        os.chmod(name, self.createmode & 0o666)
 
     def __call__(self, path, mode="r", text=False, atomictemp=False,
                  notindexed=False):
@@ -486,7 +503,7 @@
                         if nlink < 1:
                             nlink = 2 # force mktempcopy (issue1922)
                         fd.close()
-                except (OSError, IOError), e:
+                except (OSError, IOError) as e:
                     if e.errno != errno.ENOENT:
                         raise
                     nlink = 0
@@ -514,7 +531,7 @@
         if self._cansymlink:
             try:
                 os.symlink(src, linkname)
-            except OSError, err:
+            except OSError as err:
                 raise OSError(err.errno, _('could not symlink to %r: %s') %
                               (src, err.strerror), linkname)
         else:
@@ -657,11 +674,11 @@
             _rcpath = osrcpath()
     return _rcpath
 
-def intrev(repo, rev):
+def intrev(rev):
     """Return integer for a given revision that can be used in comparison or
     arithmetic operation"""
     if rev is None:
-        return len(repo)
+        return wdirrev
     return rev
 
 def revsingle(repo, revspec, default='.'):
@@ -709,14 +726,12 @@
             return defval
         return repo[val].rev()
 
-    seen, l = set(), revset.baseset([])
+    subsets = []
 
     revsetaliases = [alias for (alias, _) in
                      repo.ui.configitems("revsetalias")]
 
     for spec in revs:
-        if l and not seen:
-            seen = set(l)
         # attempt to parse old-style ranges first to deal with
         # things like old-tag which contain query metacharacters
         try:
@@ -727,8 +742,7 @@
                 raise error.RepoLookupError
 
             if isinstance(spec, int):
-                seen.add(spec)
-                l = l + revset.baseset([spec])
+                subsets.append(revset.baseset([spec]))
                 continue
 
             if _revrangesep in spec:
@@ -740,40 +754,24 @@
                 end = revfix(repo, end, len(repo) - 1)
                 if end == nullrev and start < 0:
                     start = nullrev
-                rangeiter = repo.changelog.revs(start, end)
-                if not seen and not l:
-                    # by far the most common case: revs = ["-1:0"]
-                    l = revset.baseset(rangeiter)
-                    # defer syncing seen until next iteration
-                    continue
-                newrevs = set(rangeiter)
-                if seen:
-                    newrevs.difference_update(seen)
-                    seen.update(newrevs)
+                if start < end:
+                    l = revset.spanset(repo, start, end + 1)
                 else:
-                    seen = newrevs
-                l = l + revset.baseset(sorted(newrevs, reverse=start > end))
+                    l = revset.spanset(repo, start, end - 1)
+                subsets.append(l)
                 continue
             elif spec and spec in repo: # single unquoted rev
                 rev = revfix(repo, spec, None)
-                if rev in seen:
-                    continue
-                seen.add(rev)
-                l = l + revset.baseset([rev])
+                subsets.append(revset.baseset([rev]))
                 continue
         except error.RepoLookupError:
             pass
 
         # fall through to new-style queries if old-style fails
         m = revset.match(repo.ui, spec, repo)
-        if seen or l:
-            dl = [r for r in m(repo) if r not in seen]
-            l = l + revset.baseset(dl)
-            seen.update(dl)
-        else:
-            l = m(repo)
+        subsets.append(m(repo))
 
-    return l
+    return revset._combinesets(subsets)
 
 def expandpats(pats):
     '''Expand bare globs when running on windows.
@@ -794,34 +792,40 @@
         ret.append(kindpat)
     return ret
 
-def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
+def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath',
+                 badfn=None):
     '''Return a matcher and the patterns that were used.
-    The matcher will warn about bad matches.'''
+    The matcher will warn about bad matches, unless an alternate badfn callback
+    is provided.'''
     if pats == ("",):
         pats = []
     if not globbed and default == 'relpath':
         pats = expandpats(pats or [])
 
+    def bad(f, msg):
+        ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
+
+    if badfn is None:
+        badfn = bad
+
     m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
-                         default)
-    def badfn(f, msg):
-        ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
-    m.bad = badfn
+                  default, listsubrepos=opts.get('subrepos'), badfn=badfn)
+
     if m.always():
         pats = []
     return m, pats
 
-def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
+def match(ctx, pats=[], opts={}, globbed=False, default='relpath', badfn=None):
     '''Return a matcher that will warn about bad matches.'''
-    return matchandpats(ctx, pats, opts, globbed, default)[0]
+    return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
 
 def matchall(repo):
     '''Return a matcher that will efficiently match everything.'''
     return matchmod.always(repo.root, repo.getcwd())
 
-def matchfiles(repo, files):
+def matchfiles(repo, files, badfn=None):
     '''Return a matcher that will efficiently match exactly these files.'''
-    return matchmod.exact(repo.root, repo.getcwd(), files)
+    return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
 
 def addremove(repo, matcher, prefix, opts={}, dry_run=None, similarity=None):
     m = matcher
@@ -854,15 +858,14 @@
                                  % join(subpath))
 
     rejected = []
-    origbad = m.bad
     def badfn(f, msg):
         if f in m.files():
-            origbad(f, msg)
+            m.bad(f, msg)
         rejected.append(f)
 
-    m.bad = badfn
-    added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
-    m.bad = origbad
+    badmatch = matchmod.badmatch(m, badfn)
+    added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
+                                                                    badmatch)
 
     unknownset = set(unknown + forgotten)
     toprint = unknownset.copy()
@@ -889,9 +892,8 @@
 def marktouched(repo, files, similarity=0.0):
     '''Assert that files have somehow been operated upon. files are relative to
     the repo root.'''
-    m = matchfiles(repo, files)
+    m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
     rejected = []
-    m.bad = lambda x, y: rejected.append(x)
 
     added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
 
@@ -1011,6 +1013,12 @@
                    " for more information"))
     return requirements
 
+def writerequires(opener, requirements):
+    reqfile = opener("requires", "w")
+    for r in sorted(requirements):
+        reqfile.write("%s\n" % r)
+    reqfile.close()
+
 class filecachesubentry(object):
     def __init__(self, path, stat):
         self.path = path
@@ -1062,7 +1070,7 @@
     def stat(path):
         try:
             return util.cachestat(path)
-        except OSError, e:
+        except OSError as e:
             if e.errno != errno.ENOENT:
                 raise
 
--- a/mercurial/setdiscovery.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/setdiscovery.py	Sat Jul 18 17:32:38 2015 -0500
@@ -40,6 +40,7 @@
 classified with it (since all ancestors or descendants will be marked as well).
 """
 
+import collections
 from node import nullid, nullrev
 from i18n import _
 import random
@@ -65,7 +66,7 @@
     else:
         heads = dag.heads()
     dist = {}
-    visit = util.deque(heads)
+    visit = collections.deque(heads)
     seen = set()
     factor = 1
     while visit:
@@ -168,7 +169,7 @@
         ui.debug("all remote heads known locally\n")
         return (srvheadhashes, False, srvheadhashes,)
 
-    if sample and len(ownheads) <= initialsamplesize and util.all(yesno):
+    if sample and len(ownheads) <= initialsamplesize and all(yesno):
         ui.note(_("all local heads known remotely\n"))
         ownheadhashes = dag.externalizeall(ownheads)
         return (ownheadhashes, True, srvheadhashes,)
--- a/mercurial/sshpeer.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/sshpeer.py	Sat Jul 18 17:32:38 2015 -0500
@@ -27,6 +27,87 @@
         return s
     return "'%s'" % s.replace("'", "'\\''")
 
+def _forwardoutput(ui, pipe):
+    """display all data currently available on pipe as remote output.
+
+    This is non blocking."""
+    s = util.readpipe(pipe)
+    if s:
+        for l in s.splitlines():
+            ui.status(_("remote: "), l, '\n')
+
+class doublepipe(object):
+    """Operate a side-channel pipe in addition of a main one
+
+    The side-channel pipe contains server output to be forwarded to the user
+    input. The double pipe will behave as the "main" pipe, but will ensure the
+    content of the "side" pipe is properly processed while we wait for blocking
+    call on the "main" pipe.
+
+    If large amounts of data are read from "main", the forward will cease after
+    the first bytes start to appear. This simplifies the implementation
+    without affecting actual output of sshpeer too much as we rarely issue
+    large read for data not yet emitted by the server.
+
+    The main pipe is expected to be a 'bufferedinputpipe' from the util module
+    that handle all the os specific bites. This class lives in this module
+    because it focus on behavior specifig to the ssh protocol."""
+
+    def __init__(self, ui, main, side):
+        self._ui = ui
+        self._main = main
+        self._side = side
+
+    def _wait(self):
+        """wait until some data are available on main or side
+
+        return a pair of boolean (ismainready, issideready)
+
+        (This will only wait for data if the setup is supported by `util.poll`)
+        """
+        if getattr(self._main, 'hasbuffer', False): # getattr for classic pipe
+            return (True, True) # main has data, assume side is worth poking at.
+        fds = [self._main.fileno(), self._side.fileno()]
+        try:
+            act = util.poll(fds)
+        except NotImplementedError:
+            # non supported yet case, assume all have data.
+            act = fds
+        return (self._main.fileno() in act, self._side.fileno() in act)
+
+    def write(self, data):
+        return self._call('write', data)
+
+    def read(self, size):
+        return self._call('read', size)
+
+    def readline(self):
+        return self._call('readline')
+
+    def _call(self, methname, data=None):
+        """call <methname> on "main", forward output of "side" while blocking
+        """
+        # data can be '' or 0
+        if (data is not None and not data) or self._main.closed:
+            _forwardoutput(self._ui, self._side)
+            return ''
+        while True:
+            mainready, sideready = self._wait()
+            if sideready:
+                _forwardoutput(self._ui, self._side)
+            if mainready:
+                meth = getattr(self._main, methname)
+                if data is None:
+                    return meth()
+                else:
+                    return meth(data)
+
+    def close(self):
+        return self._main.close()
+
+    def flush(self):
+        return self._main.flush()
+
 class sshpeer(wireproto.wirepeer):
     def __init__(self, ui, path, create=False):
         self._url = path
@@ -78,7 +159,16 @@
 
         # while self.subprocess isn't used, having it allows the subprocess to
         # to clean up correctly later
-        self.pipeo, self.pipei, self.pipee, self.subprocess = util.popen4(cmd)
+        #
+        # 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.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")
@@ -108,10 +198,7 @@
         return self._caps
 
     def readerr(self):
-        s = util.readpipe(self.pipee)
-        if s:
-            for l in s.splitlines():
-                self.ui.status(_("remote: "), l, '\n')
+        _forwardoutput(self.ui, self.pipee)
 
     def _abort(self, exception):
         self.cleanup()
@@ -195,16 +282,9 @@
     def _recv(self):
         l = self.pipei.readline()
         if l == '\n':
-            err = []
-            while True:
-                line = self.pipee.readline()
-                if line == '-\n':
-                    break
-                err.extend([line])
-            if len(err) > 0:
-                # strip the trailing newline added to the last line server-side
-                err[-1] = err[-1][:-1]
-            self._abort(error.OutOfBandError(*err))
+            self.readerr()
+            msg = _('check previous remote output')
+            self._abort(error.OutOfBandError(hint=msg))
         self.readerr()
         try:
             l = int(l)
--- a/mercurial/sshserver.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/sshserver.py	Sat Jul 18 17:32:38 2015 -0500
@@ -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.
 
-import util, hook, wireproto, changegroup
+import util, hook, wireproto
 import os, sys
 
 class sshserver(wireproto.abstractserverproto):
@@ -120,33 +120,6 @@
             else: self.sendresponse("")
         return cmd != ''
 
-    def do_lock(self):
-        '''DEPRECATED - allowing remote client to lock repo is not safe'''
-
-        self.lock = self.repo.lock()
-        return ""
-
-    def do_unlock(self):
-        '''DEPRECATED'''
-
-        if self.lock:
-            self.lock.release()
-        self.lock = None
-        return ""
-
-    def do_addchangegroup(self):
-        '''DEPRECATED'''
-
-        if not self.lock:
-            self.sendresponse("not locked")
-            return
-
-        self.sendresponse("")
-        cg = changegroup.cg1unpacker(self.fin, "UN")
-        r = changegroup.addchangegroup(self.repo, cg, 'serve', self._client())
-        self.lock.release()
-        return str(r)
-
     def _client(self):
         client = os.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
         return 'remote:ssh:' + client
--- a/mercurial/sslutil.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/sslutil.py	Sat Jul 18 17:32:38 2015 -0500
@@ -6,77 +6,59 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-import os, sys
+import os, sys, ssl
 
 from mercurial import util
 from mercurial.i18n import _
 
 _canloaddefaultcerts = False
 try:
-    # avoid using deprecated/broken FakeSocket in python 2.6
-    import ssl
-    CERT_REQUIRED = ssl.CERT_REQUIRED
-    try:
-        ssl_context = ssl.SSLContext
-        _canloaddefaultcerts = util.safehasattr(ssl_context,
-                                                'load_default_certs')
-
-        def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=ssl.CERT_NONE,
-                            ca_certs=None, serverhostname=None):
-            # Allow any version of SSL starting with TLSv1 and
-            # up. Note that specifying TLSv1 here prohibits use of
-            # newer standards (like TLSv1_2), so this is the right way
-            # to do this. Note that in the future it'd be better to
-            # support using ssl.create_default_context(), which sets
-            # up a bunch of things in smart ways (strong ciphers,
-            # protocol versions, etc) and is upgraded by Python
-            # maintainers for us, but that breaks too many things to
-            # do it in a hurry.
-            sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
-            sslcontext.options &= ssl.OP_NO_SSLv2 & ssl.OP_NO_SSLv3
-            if certfile is not None:
-                sslcontext.load_cert_chain(certfile, keyfile)
-            sslcontext.verify_mode = cert_reqs
-            if ca_certs is not None:
-                sslcontext.load_verify_locations(cafile=ca_certs)
-            elif _canloaddefaultcerts:
-                sslcontext.load_default_certs()
+    ssl_context = ssl.SSLContext
+    _canloaddefaultcerts = util.safehasattr(ssl_context, 'load_default_certs')
 
-            sslsocket = sslcontext.wrap_socket(sock,
-                                               server_hostname=serverhostname)
-            # check if wrap_socket failed silently because socket had been
-            # closed
-            # - see http://bugs.python.org/issue13721
-            if not sslsocket.cipher():
-                raise util.Abort(_('ssl connection failed'))
-            return sslsocket
-    except AttributeError:
-        def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=ssl.CERT_NONE,
-                            ca_certs=None, serverhostname=None):
-            sslsocket = ssl.wrap_socket(sock, keyfile, certfile,
-                                        cert_reqs=cert_reqs, ca_certs=ca_certs,
-                                        ssl_version=ssl.PROTOCOL_TLSv1)
-            # check if wrap_socket failed silently because socket had been
-            # closed
-            # - see http://bugs.python.org/issue13721
-            if not sslsocket.cipher():
-                raise util.Abort(_('ssl connection failed'))
-            return sslsocket
-except ImportError:
-    CERT_REQUIRED = 2
+    def wrapsocket(sock, keyfile, certfile, ui, cert_reqs=ssl.CERT_NONE,
+                   ca_certs=None, serverhostname=None):
+        # Allow any version of SSL starting with TLSv1 and
+        # up. Note that specifying TLSv1 here prohibits use of
+        # newer standards (like TLSv1_2), so this is the right way
+        # to do this. Note that in the future it'd be better to
+        # support using ssl.create_default_context(), which sets
+        # up a bunch of things in smart ways (strong ciphers,
+        # protocol versions, etc) and is upgraded by Python
+        # maintainers for us, but that breaks too many things to
+        # do it in a hurry.
+        sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+        sslcontext.options &= ssl.OP_NO_SSLv2 & ssl.OP_NO_SSLv3
+        if certfile is not None:
+            def password():
+                f = keyfile or certfile
+                return ui.getpass(_('passphrase for %s: ') % f, '')
+            sslcontext.load_cert_chain(certfile, keyfile, password)
+        sslcontext.verify_mode = cert_reqs
+        if ca_certs is not None:
+            sslcontext.load_verify_locations(cafile=ca_certs)
+        elif _canloaddefaultcerts:
+            sslcontext.load_default_certs()
 
-    import socket, httplib
-
-    def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=CERT_REQUIRED,
-                        ca_certs=None, serverhostname=None):
-        if not util.safehasattr(socket, 'ssl'):
-            raise util.Abort(_('Python SSL support not found'))
-        if ca_certs:
-            raise util.Abort(_(
-                'certificate checking requires Python 2.6'))
-
-        ssl = socket.ssl(sock, keyfile, certfile)
-        return httplib.FakeSocket(sock, ssl)
+        sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
+        # check if wrap_socket failed silently because socket had been
+        # closed
+        # - see http://bugs.python.org/issue13721
+        if not sslsocket.cipher():
+            raise util.Abort(_('ssl connection failed'))
+        return sslsocket
+except AttributeError:
+    def wrapsocket(sock, keyfile, certfile, ui, cert_reqs=ssl.CERT_NONE,
+                   ca_certs=None, serverhostname=None):
+        sslsocket = ssl.wrap_socket(sock, keyfile, certfile,
+                                    cert_reqs=cert_reqs, ca_certs=ca_certs,
+                                    ssl_version=ssl.PROTOCOL_TLSv1)
+        # check if wrap_socket failed silently because socket had been
+        # closed
+        # - see http://bugs.python.org/issue13721
+        if not sslsocket.cipher():
+            raise util.Abort(_('ssl connection failed'))
+        return sslsocket
 
 def _verifycert(cert, hostname):
     '''Verify that cert (in socket.getpeercert() format) matches hostname.
@@ -117,9 +99,6 @@
 
 # CERT_REQUIRED means fetch the cert from the server all the time AND
 # validate it against the CA store provided in web.cacerts.
-#
-# We COMPLETELY ignore CERT_REQUIRED on Python <= 2.5, as it's totally
-# busted on those versions.
 
 def _plainapplepython():
     """return true if this seems to be a pure Apple Python that
@@ -146,7 +125,7 @@
     return '!'
 
 def sslkwargs(ui, host):
-    kws = {}
+    kws = {'ui': ui}
     hostfingerprint = ui.config('hostfingerprints', host)
     if hostfingerprint:
         return kws
@@ -164,7 +143,7 @@
         ui.setconfig('web', 'cacerts', cacerts, 'defaultcacerts')
     if cacerts != '!':
         kws.update({'ca_certs': cacerts,
-                    'cert_reqs': CERT_REQUIRED,
+                    'cert_reqs': ssl.CERT_REQUIRED,
                     })
     return kws
 
@@ -177,17 +156,6 @@
         host = self.host
         cacerts = self.ui.config('web', 'cacerts')
         hostfingerprint = self.ui.config('hostfingerprints', host)
-        if not getattr(sock, 'getpeercert', False): # python 2.5 ?
-            if hostfingerprint:
-                raise util.Abort(_("host fingerprint for %s can't be "
-                                   "verified (Python too old)") % host)
-            if strict:
-                raise util.Abort(_("certificate for %s can't be verified "
-                                   "(Python too old)") % host)
-            if self.ui.configbool('ui', 'reportoldssl', True):
-                self.ui.warn(_("warning: certificate for %s can't be verified "
-                               "(Python too old)\n") % host)
-            return
 
         if not sock.cipher(): # work around http://bugs.python.org/issue13721
             raise util.Abort(_('%s ssl connection error') % host)
--- a/mercurial/statichttprepo.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/statichttprepo.py	Sat Jul 18 17:32:38 2015 -0500
@@ -32,15 +32,11 @@
         try:
             f = self.opener.open(req)
             data = f.read()
-            # Python 2.6+ defines a getcode() function, and 2.4 and
-            # 2.5 appear to always have an undocumented code attribute
-            # set. If we can't read either of those, fall back to 206
-            # and hope for the best.
-            code = getattr(f, 'getcode', lambda : getattr(f, 'code', 206))()
-        except urllib2.HTTPError, inst:
+            code = f.code
+        except urllib2.HTTPError as inst:
             num = inst.code == 404 and errno.ENOENT or None
             raise IOError(num, inst)
-        except urllib2.URLError, inst:
+        except urllib2.URLError as inst:
             raise IOError(None, inst.reason[1])
 
         if code == 200:
@@ -110,7 +106,7 @@
 
         try:
             requirements = scmutil.readrequires(self.vfs, self.supported)
-        except IOError, inst:
+        except IOError as inst:
             if inst.errno != errno.ENOENT:
                 raise
             requirements = set()
@@ -120,7 +116,7 @@
                 fp = self.vfs("00changelog.i")
                 fp.read(1)
                 fp.close()
-            except IOError, inst:
+            except IOError as inst:
                 if inst.errno != errno.ENOENT:
                     raise
                 # we do not care about empty old-style repositories here
@@ -131,7 +127,6 @@
         self.store = store.store(requirements, self.path, opener)
         self.spath = self.store.path
         self.svfs = self.store.opener
-        self.sopener = self.svfs
         self.sjoin = self.store.join
         self._filecache = {}
         self.requirements = requirements
--- a/mercurial/store.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/store.py	Sat Jul 18 17:32:38 2015 -0500
@@ -187,7 +187,7 @@
 
 def _hashencode(path, dotencode):
     digest = _sha(path).hexdigest()
-    le = lowerencode(path).split('/')[1:]
+    le = lowerencode(path[5:]).split('/') # skips prefix 'data/' or 'meta/'
     parts = _auxencode(le, dotencode)
     basename = parts[-1]
     _root, ext = os.path.splitext(basename)
@@ -274,7 +274,7 @@
         # files in .hg/ will be created using this mode
         mode = vfs.stat().st_mode
             # avoid some useless chmods
-        if (0777 & ~util.umask) == (0777 & mode):
+        if (0o777 & ~util.umask) == (0o777 & mode):
             mode = None
     except OSError:
         mode = None
@@ -489,7 +489,7 @@
             ef = self.encode(f)
             try:
                 yield f, ef, self.getsize(ef)
-            except OSError, err:
+            except OSError as err:
                 if err.errno != errno.ENOENT:
                     raise
 
@@ -513,7 +513,7 @@
         try:
             self.getsize(ef)
             return True
-        except OSError, err:
+        except OSError as err:
             if err.errno != errno.ENOENT:
                 raise
             # nonexistent entry
--- a/mercurial/subrepo.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/subrepo.py	Sat Jul 18 17:32:38 2015 -0500
@@ -44,10 +44,10 @@
     def decoratedmethod(self, *args, **kargs):
         try:
             res = func(self, *args, **kargs)
-        except SubrepoAbort, ex:
+        except SubrepoAbort as ex:
             # This exception has already been handled
             raise ex
-        except error.Abort, ex:
+        except error.Abort as ex:
             subrepo = subrelpath(self)
             errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo
             # avoid handling this exception by raising a SubrepoAbort exception
@@ -62,23 +62,22 @@
     (key in types dict))
     """
     p = config.config()
+    repo = ctx.repo()
     def read(f, sections=None, remap=None):
         if f in ctx:
             try:
                 data = ctx[f].data()
-            except IOError, err:
+            except IOError as err:
                 if err.errno != errno.ENOENT:
                     raise
                 # handle missing subrepo spec files as removed
                 ui.warn(_("warning: subrepo spec file \'%s\' not found\n") %
-                        util.pathto(ctx.repo().root, ctx.repo().getcwd(), f))
+                        repo.pathto(f))
                 return
             p.parse(f, data, sections, remap, read)
         else:
-            repo = ctx.repo()
             raise util.Abort(_("subrepo spec file \'%s\' not found") %
-                             util.pathto(repo.root, repo.getcwd(), f))
-
+                             repo.pathto(f))
     if '.hgsub' in ctx:
         read('.hgsub')
 
@@ -95,13 +94,11 @@
                 try:
                     revision, path = l.split(" ", 1)
                 except ValueError:
-                    repo = ctx.repo()
                     raise util.Abort(_("invalid subrepository revision "
                                        "specifier in \'%s\' line %d")
-                                     % (util.pathto(repo.root, repo.getcwd(),
-                                        '.hgsubstate'), (i + 1)))
+                                     % (repo.pathto('.hgsubstate'), (i + 1)))
                 rev[path] = revision
-        except IOError, err:
+        except IOError as err:
             if err.errno != errno.ENOENT:
                 raise
 
@@ -116,7 +113,7 @@
             repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
             try:
                 src = re.sub(pattern, repl, src, 1)
-            except re.error, e:
+            except re.error as e:
                 raise util.Abort(_("bad subrepository pattern in %s: %s")
                                  % (p.source('subpaths', pattern), e))
         return src
@@ -132,7 +129,7 @@
             src = src.lstrip() # strip any extra whitespace after ']'
 
         if not util.url(src).isabs():
-            parent = _abssource(ctx.repo(), abort=False)
+            parent = _abssource(repo, abort=False)
             if parent:
                 parent = util.url(parent)
                 parent.path = posixpath.join(parent.path or '', src)
@@ -316,7 +313,7 @@
             if d.lower() == ignore:
                 del dirs[i]
                 break
-        if os.path.basename(dirname).lower() != '.hg':
+        if vfs.basename(dirname).lower() != '.hg':
             continue
         for f in names:
             if f.lower() == 'hgrc':
@@ -324,7 +321,7 @@
                           "in '%s'\n") % vfs.join(dirname))
                 vfs.unlink(vfs.reljoin(dirname, f))
 
-def subrepo(ctx, path):
+def subrepo(ctx, path, allowwdir=False):
     """return instance of the right subrepo class for subrepo in path"""
     # subrepo inherently violates our import layering rules
     # because it wants to make repo objects from deep inside the stack
@@ -338,8 +335,29 @@
     state = ctx.substate[path]
     if state[2] not in types:
         raise util.Abort(_('unknown subrepo type %s') % state[2])
+    if allowwdir:
+        state = (state[0], ctx.subrev(path), state[2])
     return types[state[2]](ctx, path, state[:2])
 
+def nullsubrepo(ctx, path, pctx):
+    """return an empty subrepo in pctx for the extant subrepo in ctx"""
+    # 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
+    import hg as h
+    hg = h
+
+    pathutil.pathauditor(ctx.repo().root)(path)
+    state = ctx.substate[path]
+    if state[2] not in types:
+        raise util.Abort(_('unknown subrepo type %s') % state[2])
+    subrev = ''
+    if state[2] == 'hg':
+        subrev = "0" * 40
+    return types[state[2]](pctx, path, (state[0], subrev))
+
 def newcommitphase(ui, ctx):
     commitphase = phases.newcommitphase(ui)
     substate = getattr(ctx, "substate", None)
@@ -500,7 +518,11 @@
         """return file flags"""
         return ''
 
-    def printfiles(self, ui, m, fm, fmt):
+    def getfileset(self, expr):
+        """Resolve the fileset expression for this repo"""
+        return set()
+
+    def printfiles(self, ui, m, fm, fmt, subrepos):
         """handle the files command for this subrepo"""
         return 1
 
@@ -515,7 +537,7 @@
                          unit=_('files'), total=total)
         for i, name in enumerate(files):
             flags = self.fileflags(name)
-            mode = 'x' in flags and 0755 or 0644
+            mode = 'x' in flags and 0o755 or 0o644
             symlink = 'l' in flags
             archiver.addfile(prefix + self._path + '/' + name,
                              mode, symlink, self.filedata(name))
@@ -549,6 +571,12 @@
     def shortid(self, revid):
         return revid
 
+    def verify(self):
+        '''verify the integrity of the repository.  Return 0 on success or
+        warning, 1 on any error.
+        '''
+        return 0
+
     @propertycache
     def wvfs(self):
         """return vfs to access the working directory of this subrepository
@@ -579,6 +607,7 @@
             v = r.ui.config(s, k)
             if v:
                 self.ui.setconfig(s, k, v, 'subrepo')
+        # internal config: ui._usedassubrepo
         self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
         self._initrepo(r, state[0], create)
 
@@ -592,21 +621,14 @@
     def _storeclean(self, path):
         clean = True
         itercache = self._calcstorehash(path)
-        try:
-            for filehash in self._readstorehashcache(path):
-                if filehash != itercache.next():
-                    clean = False
-                    break
-        except StopIteration:
+        for filehash in self._readstorehashcache(path):
+            if filehash != next(itercache, None):
+                clean = False
+                break
+        if clean:
+            # if not empty:
             # the cached and current pull states have a different size
-            clean = False
-        if clean:
-            try:
-                itercache.next()
-                # the cached and current pull states have a different size
-                clean = False
-            except StopIteration:
-                pass
+            clean = next(itercache, None) is None
         return clean
 
     def _calcstorehash(self, remotepath):
@@ -646,6 +668,15 @@
         finally:
             lock.release()
 
+    def _getctx(self):
+        '''fetch the context for this subrepo revision, possibly a workingctx
+        '''
+        if self._ctx.rev() is None:
+            return self._repo[None]  # workingctx if parent is workingctx
+        else:
+            rev = self._state[1]
+            return self._repo[rev]
+
     @annotatesubrepoerror
     def _initrepo(self, parentrepo, source, create):
         self._repo._subparent = parentrepo
@@ -701,7 +732,7 @@
             ctx1 = self._repo[rev1]
             ctx2 = self._repo[rev2]
             return self._repo.status(ctx1, ctx2, **opts)
-        except error.RepoLookupError, inst:
+        except error.RepoLookupError as inst:
             self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
                          % (inst, subrelpath(self)))
             return scmutil.status([], [], [], [], [], [], [])
@@ -718,7 +749,7 @@
                                    node1, node2, match,
                                    prefix=posixpath.join(prefix, self._path),
                                    listsubrepos=True, **opts)
-        except error.RepoLookupError, inst:
+        except error.RepoLookupError as inst:
             self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
                           % (inst, subrelpath(self)))
 
@@ -729,7 +760,7 @@
         rev = self._state[1]
         ctx = self._repo[rev]
         for subpath in ctx.substate:
-            s = subrepo(ctx, subpath)
+            s = subrepo(ctx, subpath, True)
             submatch = matchmod.narrowmatcher(subpath, match)
             total += s.archive(archiver, prefix + self._path + '/', submatch)
         return total
@@ -907,7 +938,7 @@
         return ctx.flags(name)
 
     @annotatesubrepoerror
-    def printfiles(self, ui, m, fm, fmt):
+    def printfiles(self, ui, m, fm, fmt, subrepos):
         # If the parent context is a workingctx, use the workingctx here for
         # consistency.
         if self._ctx.rev() is None:
@@ -915,7 +946,27 @@
         else:
             rev = self._state[1]
             ctx = self._repo[rev]
-        return cmdutil.files(ui, ctx, m, fm, fmt, True)
+        return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
+
+    @annotatesubrepoerror
+    def getfileset(self, expr):
+        if self._ctx.rev() is None:
+            ctx = self._repo[None]
+        else:
+            rev = self._state[1]
+            ctx = self._repo[rev]
+
+        files = ctx.getfileset(expr)
+
+        for subpath in ctx.substate:
+            sub = ctx.sub(subpath)
+
+            try:
+                files.extend(subpath + '/' + f for f in sub.getfileset(expr))
+            except error.LookupError:
+                self.ui.status(_("skipping missing subrepository: %s\n")
+                               % self.wvfs.reljoin(reporelpath(self), subpath))
+        return files
 
     def walk(self, match):
         ctx = self._repo[None]
@@ -966,6 +1017,24 @@
     def shortid(self, revid):
         return revid[:12]
 
+    def verify(self):
+        try:
+            rev = self._state[1]
+            ctx = self._repo.unfiltered()[rev]
+            if ctx.hidden():
+                # Since hidden revisions aren't pushed/pulled, it seems worth an
+                # explicit warning.
+                ui = self._repo.ui
+                ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
+                        (self._relpath, node.short(self._ctx.node())))
+            return 0
+        except error.RepoLookupError:
+            # A missing subrepo revision may be a case of needing to pull it, so
+            # don't treat this as an error.
+            self._repo.ui.warn(_("subrepo '%s' not found in revision %s\n") %
+                               (self._relpath, node.short(self._ctx.node())))
+            return 0
+
     @propertycache
     def wvfs(self):
         """return own wvfs for efficiency and consitency
@@ -1138,7 +1207,8 @@
 
         self.wvfs.rmtree(forcibly=True)
         try:
-            self._ctx.repo().wvfs.removedirs(os.path.dirname(self._path))
+            pwvfs = self._ctx.repo().wvfs
+            pwvfs.removedirs(pwvfs.dirname(self._path))
         except OSError:
             pass
 
@@ -1209,7 +1279,7 @@
         try:
             self._gitexecutable = 'git'
             out, err = self._gitnodir(['--version'])
-        except OSError, e:
+        except OSError as e:
             if e.errno != 2 or os.name != 'nt':
                 raise
             self._gitexecutable = 'git.cmd'
@@ -1711,7 +1781,7 @@
         modified, added, removed = [], [], []
         self._gitupdatestat()
         if rev2:
-            command = ['diff-tree', rev1, rev2]
+            command = ['diff-tree', '-r', rev1, rev2]
         else:
             command = ['diff-index', rev1]
         out = self._gitcommand(command)
--- a/mercurial/tags.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/tags.py	Sat Jul 18 17:32:38 2015 -0500
@@ -120,7 +120,7 @@
     '''Read local tags in repo. Update alltags and tagtypes.'''
     try:
         data = repo.vfs.read("localtags")
-    except IOError, inst:
+    except IOError as inst:
         if inst.errno != errno.ENOENT:
             raise
         return
@@ -442,11 +442,13 @@
                 self._raw.pop()
             self._dirtyoffset = len(self._raw)
 
-    def getfnode(self, node):
+    def getfnode(self, node, computemissing=True):
         """Obtain the filenode of the .hgtags file at a specified revision.
 
         If the value is in the cache, the entry will be validated and returned.
-        Otherwise, the filenode will be computed and returned.
+        Otherwise, the filenode will be computed and returned unless
+        "computemissing" is False, in which case None will be returned without
+        any potentially expensive computation being performed.
 
         If an .hgtags does not exist at the specified revision, nullid is
         returned.
@@ -470,21 +472,39 @@
 
             # Fall through.
 
-        # If we get here, the entry is either missing or invalid. Populate it.
+        # If we get here, the entry is either missing or invalid.
+
+        if not computemissing:
+            return None
+
+        # Populate missing entry.
         try:
             fnode = ctx.filenode('.hgtags')
         except error.LookupError:
             # No .hgtags file on this revision.
             fnode = nullid
 
+        self._writeentry(offset, properprefix, fnode)
+        return fnode
+
+    def setfnode(self, node, fnode):
+        """Set the .hgtags filenode for a given changeset."""
+        assert len(fnode) == 20
+        ctx = self._repo[node]
+
+        # Do a lookup first to avoid writing if nothing has changed.
+        if self.getfnode(ctx.node(), computemissing=False) == fnode:
+            return
+
+        self._writeentry(ctx.rev() * _fnodesrecsize, node[0:4], fnode)
+
+    def _writeentry(self, offset, prefix, fnode):
         # Slices on array instances only accept other array.
-        entry = array('c', properprefix + fnode)
+        entry = array('c', prefix + fnode)
         self._raw[offset:offset + _fnodesrecsize] = entry
         # self._dirtyoffset could be None.
         self._dirtyoffset = min(self._dirtyoffset, offset) or 0
 
-        return fnode
-
     def write(self):
         """Perform all necessary writes to cache file.
 
@@ -509,26 +529,25 @@
             return
 
         try:
+            f = repo.vfs.open(_fnodescachefile, 'ab')
             try:
-                f = repo.vfs.open(_fnodescachefile, 'ab')
-                try:
-                    # if the file has been truncated
-                    actualoffset = f.tell()
-                    if actualoffset < self._dirtyoffset:
-                        self._dirtyoffset = actualoffset
-                        data = self._raw[self._dirtyoffset:]
-                    f.seek(self._dirtyoffset)
-                    f.truncate()
-                    repo.ui.log('tagscache',
-                                'writing %d bytes to %s\n' % (
-                                len(data), _fnodescachefile))
-                    f.write(data)
-                    self._dirtyoffset = None
-                finally:
-                    f.close()
-            except (IOError, OSError), inst:
+                # if the file has been truncated
+                actualoffset = f.tell()
+                if actualoffset < self._dirtyoffset:
+                    self._dirtyoffset = actualoffset
+                    data = self._raw[self._dirtyoffset:]
+                f.seek(self._dirtyoffset)
+                f.truncate()
                 repo.ui.log('tagscache',
-                            "couldn't write %s: %s\n" % (
-                            _fnodescachefile, inst))
+                            'writing %d bytes to %s\n' % (
+                            len(data), _fnodescachefile))
+                f.write(data)
+                self._dirtyoffset = None
+            finally:
+                f.close()
+        except (IOError, OSError) as inst:
+            repo.ui.log('tagscache',
+                        "couldn't write %s: %s\n" % (
+                        _fnodescachefile, inst))
         finally:
             lock.release()
--- a/mercurial/templatefilters.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templatefilters.py	Sat Jul 18 17:32:38 2015 -0500
@@ -283,6 +283,13 @@
     f = author.find('@')
     return author[:f].replace('.', ' ')
 
+def revescape(text):
+    """:revescape: Any text. Escapes all "special" characters, except @.
+    Forward slashes are escaped twice to prevent web servers from prematurely
+    unescaping them. For example, "@foo bar/baz" becomes "@foo%20bar%252Fbaz".
+    """
+    return urllib.quote(text, safe='/@').replace('/', '%252F')
+
 def rfc3339date(text):
     """:rfc3339date: Date. Returns a date using the Internet date format
     specified in RFC 3339: "2009-08-18T13:00:13+02:00".
@@ -326,6 +333,8 @@
     """
     if util.safehasattr(thing, '__iter__') and not isinstance(thing, str):
         return "".join([stringify(t) for t in thing if t is not None])
+    if thing is None:
+        return ""
     return str(thing)
 
 def strip(text):
@@ -400,6 +409,7 @@
     "obfuscate": obfuscate,
     "permissions": permissions,
     "person": person,
+    "revescape": revescape,
     "rfc3339date": rfc3339date,
     "rfc822date": rfc822date,
     "short": short,
--- a/mercurial/templatekw.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templatekw.py	Sat Jul 18 17:32:38 2015 -0500
@@ -6,7 +6,7 @@
 # GNU General Public License version 2 or any later version.
 
 from node import hex
-import patch, util, error
+import patch, scmutil, util, error
 import hbisect
 
 # This helper class allows us to handle both:
@@ -40,24 +40,25 @@
             raise AttributeError(name)
         return getattr(self.values, name)
 
-def showlist(name, values, plural=None, element=None, **args):
+def showlist(name, values, plural=None, element=None, separator=' ', **args):
     if not element:
         element = name
-    f = _showlist(name, values, plural, **args)
+    f = _showlist(name, values, plural, separator, **args)
     return _hybrid(f, values, lambda x: {element: x})
 
-def _showlist(name, values, plural=None, **args):
+def _showlist(name, values, plural=None, separator=' ', **args):
     '''expand set of values.
     name is name of key in template map.
     values is list of strings or dicts.
     plural is plural of name, if not simply name + 's'.
+    separator is used to join values as a string
 
     expansion works like this, given name 'foo'.
 
     if values is empty, expand 'no_foos'.
 
     if 'foo' not in template map, return values as a string,
-    joined by space.
+    joined by 'separator'.
 
     expand 'start_foos'.
 
@@ -77,7 +78,7 @@
         return
     if name not in templ:
         if isinstance(values[0], str):
-            yield ' '.join(values)
+            yield separator.join(values)
         else:
             for v in values:
                 yield dict(v, **args)
@@ -120,7 +121,7 @@
     if 'latesttags' not in cache:
         # Cache mapping from rev to a tuple with tag date, tag
         # distance and tag name
-        cache['latesttags'] = {-1: (0, 0, 'null')}
+        cache['latesttags'] = {-1: (0, 0, ['null'])}
     latesttags = cache['latesttags']
 
     rev = ctx.rev()
@@ -133,7 +134,7 @@
         tags = [t for t in ctx.tags()
                 if (repo.tagtype(t) and repo.tagtype(t) != 'local')]
         if tags:
-            latesttags[rev] = ctx.date()[0], 0, ':'.join(sorted(tags))
+            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
@@ -206,12 +207,12 @@
 
 def showbookmarks(**args):
     """:bookmarks: List of strings. Any bookmarks associated with the
-    changeset.
+    changeset. Also sets 'active', the name of the active bookmark.
     """
     repo = args['ctx']._repo
     bookmarks = args['ctx'].bookmarks()
-    current = repo._bookmarkcurrent
-    makemap = lambda v: {'bookmark': v, 'current': current}
+    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'])
 
@@ -221,15 +222,18 @@
     childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
     return showlist('children', childrevs, element='child', **args)
 
+# Deprecated, but kept alive for help generation a purpose.
 def showcurrentbookmark(**args):
     """:currentbookmark: String. The active bookmark, if it is
+    associated with the changeset (DEPRECATED)"""
+    return showactivebookmark(**args)
+
+def showactivebookmark(**args):
+    """:activebookmark: String. The active bookmark, if it is
     associated with the changeset"""
-    import bookmarks as bookmarks # to avoid circular import issues
-    repo = args['repo']
-    if bookmarks.iscurrent(repo):
-        current = repo._bookmarkcurrent
-        if current in args['ctx'].bookmarks():
-            return current
+    active = args['repo']._activebookmark
+    if active and active in args['ctx'].bookmarks():
+        return active
     return ''
 
 def showdate(repo, ctx, templ, **args):
@@ -321,19 +325,39 @@
     """
     return showlist('file', args['ctx'].files(), **args)
 
-def showlatesttag(repo, ctx, templ, cache, **args):
-    """:latesttag: String. Most recent global tag in the ancestors of this
-    changeset.
+def showlatesttag(**args):
+    """:latesttag: List of strings. The global tags on the most recent globally
+    tagged ancestor of this changeset.
     """
-    return getlatesttags(repo, ctx, cache)[2]
+    repo, ctx = args['repo'], args['ctx']
+    cache = args['cache']
+    latesttags = getlatesttags(repo, ctx, cache)[2]
+
+    return showlist('latesttag', latesttags, separator=':', **args)
 
 def showlatesttagdistance(repo, ctx, templ, cache, **args):
     """:latesttagdistance: Integer. Longest path to the latest tag."""
     return getlatesttags(repo, ctx, cache)[1]
 
+def showchangessincelatesttag(repo, ctx, templ, cache, **args):
+    """:changessincelatesttag: Integer. All ancestors not in the latest tag."""
+    latesttag = getlatesttags(repo, ctx, cache)[2][0]
+    offset = 0
+    revs = [ctx.rev()]
+
+    # The only() revset doesn't currently support wdir()
+    if ctx.rev() is None:
+        offset = 1
+        revs = [p.rev() for p in ctx.parents()]
+
+    return len(repo.revs('only(%ld, %s)', revs, latesttag)) + offset
+
 def showmanifest(**args):
     repo, ctx, templ = args['repo'], args['ctx'], args['templ']
     mnode = ctx.manifestnode()
+    if mnode is None:
+        # just avoid crash, we might want to use the 'ff...' hash in future
+        return
     args = args.copy()
     args.update({'rev': repo.manifest.rev(mnode), 'node': hex(mnode)})
     return templ('manifest', **args)
@@ -376,7 +400,7 @@
 
 def showrev(repo, ctx, templ, **args):
     """:rev: Integer. The repository-local changeset revision number."""
-    return ctx.rev()
+    return scmutil.intrev(ctx.rev())
 
 def showsubrepos(**args):
     """:subrepos: List of strings. Updated subrepositories in the changeset."""
@@ -418,12 +442,15 @@
 # cache - a cache dictionary for the whole templater run
 # revcache - a cache dictionary for the current revision
 keywords = {
+    'activebookmark': showactivebookmark,
     'author': showauthor,
     'bisect': showbisect,
     'branch': showbranch,
     'branches': showbranches,
     'bookmarks': showbookmarks,
+    'changessincelatesttag': showchangessincelatesttag,
     'children': showchildren,
+    # currentbookmark is deprecated
     'currentbookmark': showcurrentbookmark,
     'date': showdate,
     'desc': showdescription,
--- a/mercurial/templater.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templater.py	Sat Jul 18 17:32:38 2015 -0500
@@ -15,19 +15,20 @@
 # template parsing
 
 elements = {
-    "(": (20, ("group", 1, ")"), ("func", 1, ")")),
-    ",": (2, None, ("list", 2)),
-    "|": (5, None, ("|", 5)),
-    "%": (6, None, ("%", 6)),
-    ")": (0, None, None),
-    "symbol": (0, ("symbol",), None),
-    "string": (0, ("string",), None),
-    "rawstring": (0, ("rawstring",), None),
-    "end": (0, None, None),
+    # token-type: binding-strength, primary, prefix, infix, suffix
+    "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
+    ",": (2, None, None, ("list", 2), None),
+    "|": (5, None, None, ("|", 5), None),
+    "%": (6, None, None, ("%", 6), None),
+    ")": (0, None, None, None, None),
+    "integer": (0, "integer", None, None, None),
+    "symbol": (0, "symbol", None, None, None),
+    "string": (0, "string", None, None, None),
+    "template": (0, "template", None, None, None),
+    "end": (0, None, None, None, None),
 }
 
-def tokenizer(data):
-    program, start, end = data
+def tokenize(program, start, end):
     pos = start
     while pos < end:
         c = program[pos]
@@ -35,30 +36,40 @@
             pass
         elif c in "(,)%|": # handle simple operators
             yield (c, None, pos)
-        elif (c in '"\'' or c == 'r' and
-              program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
-            if c == 'r':
-                pos += 1
-                c = program[pos]
-                decode = False
-            else:
-                decode = True
-            pos += 1
-            s = pos
+        elif c in '"\'': # handle quoted templates
+            s = pos + 1
+            data, pos = _parsetemplate(program, s, end, c)
+            yield ('template', data, s)
+            pos -= 1
+        elif c == 'r' and program[pos:pos + 2] in ("r'", 'r"'):
+            # handle quoted strings
+            c = program[pos + 1]
+            s = pos = pos + 2
             while pos < end: # find closing quote
                 d = program[pos]
                 if d == '\\': # skip over escaped characters
                     pos += 2
                     continue
                 if d == c:
-                    if not decode:
-                        yield ('rawstring', program[s:pos], s)
-                        break
                     yield ('string', program[s:pos], s)
                     break
                 pos += 1
             else:
                 raise error.ParseError(_("unterminated string"), s)
+        elif c.isdigit() or c == '-':
+            s = pos
+            if c == '-': # simply take negate operator as part of integer
+                pos += 1
+            if pos >= end or not program[pos].isdigit():
+                raise error.ParseError(_("integer literal without digits"), s)
+            pos += 1
+            while pos < end:
+                d = program[pos]
+                if not d.isdigit():
+                    break
+                pos += 1
+            yield ('integer', program[s:pos], s)
+            pos -= 1
         elif (c == '\\' and program[pos:pos + 2] in (r"\'", r'\"')
               or c == 'r' and program[pos:pos + 3] in (r"r\'", r'r\"')):
             # handle escaped quoted strings for compatibility with 2.9.2-3.4,
@@ -73,9 +84,9 @@
             #             escaped quoted string
             if c == 'r':
                 pos += 1
-                token = 'rawstring'
+                token = 'string'
             else:
-                token = 'string'
+                token = 'template'
             quote = program[pos:pos + 2]
             s = pos = pos + 2
             while pos < end: # find closing escaped quote
@@ -88,6 +99,8 @@
                         data = program[s:pos].decode('string-escape')
                     except ValueError: # unbalanced escapes
                         raise error.ParseError(_("syntax error"), s)
+                    if token == 'template':
+                        data = _parsetemplate(data, 0, len(data))[0]
                     yield (token, data, s)
                     pos += 1
                     break
@@ -106,41 +119,65 @@
             yield ('symbol', sym, s)
             pos -= 1
         elif c == '}':
-            pos += 1
-            break
+            yield ('end', None, pos + 1)
+            return
         else:
             raise error.ParseError(_("syntax error"), pos)
         pos += 1
-    yield ('end', None, pos)
+    raise error.ParseError(_("unterminated template expansion"), start)
 
-def compiletemplate(tmpl, context, strtoken="string"):
+def _parsetemplate(tmpl, start, stop, quote=''):
+    r"""
+    >>> _parsetemplate('foo{bar}"baz', 0, 12)
+    ([('string', 'foo'), ('symbol', 'bar'), ('string', '"baz')], 12)
+    >>> _parsetemplate('foo{bar}"baz', 0, 12, quote='"')
+    ([('string', 'foo'), ('symbol', 'bar')], 9)
+    >>> _parsetemplate('foo"{bar}', 0, 9, quote='"')
+    ([('string', 'foo')], 4)
+    >>> _parsetemplate(r'foo\"bar"baz', 0, 12, quote='"')
+    ([('string', 'foo"'), ('string', 'bar')], 9)
+    >>> _parsetemplate(r'foo\\"bar', 0, 10, quote='"')
+    ([('string', 'foo\\')], 6)
+    """
     parsed = []
-    pos, stop = 0, len(tmpl)
-    p = parser.parser(tokenizer, elements)
+    sepchars = '{' + quote
+    pos = start
+    p = parser.parser(elements)
     while pos < stop:
-        n = tmpl.find('{', pos)
+        n = min((tmpl.find(c, pos, stop) for c in sepchars),
+                key=lambda n: (n < 0, n))
         if n < 0:
-            parsed.append((strtoken, tmpl[pos:]))
+            parsed.append(('string', tmpl[pos:stop].decode('string-escape')))
+            pos = stop
             break
+        c = tmpl[n]
         bs = (n - pos) - len(tmpl[pos:n].rstrip('\\'))
-        if strtoken == 'string' and bs % 2 == 1:
-            # escaped (e.g. '\{', '\\\{', but not '\\{' nor r'\{')
-            parsed.append((strtoken, (tmpl[pos:n - 1] + "{")))
+        if bs % 2 == 1:
+            # escaped (e.g. '\{', '\\\{', but not '\\{')
+            parsed.append(('string',
+                           tmpl[pos:n - 1].decode('string-escape') + c))
             pos = n + 1
             continue
         if n > pos:
-            parsed.append((strtoken, tmpl[pos:n]))
+            parsed.append(('string', tmpl[pos:n].decode('string-escape')))
+        if c == quote:
+            return parsed, n + 1
 
-        pd = [tmpl, n + 1, stop]
-        parseres, pos = p.parse(pd)
+        parseres, pos = p.parse(tokenize(tmpl, n + 1, stop))
         parsed.append(parseres)
 
-    return [compileexp(e, context) for e in parsed]
+    if quote:
+        raise error.ParseError(_("unterminated string"), start)
+    return parsed, pos
 
-def compileexp(exp, context):
+def compiletemplate(tmpl, context):
+    parsed, pos = _parsetemplate(tmpl, 0, len(tmpl))
+    return [compileexp(e, context, methods) for e in parsed]
+
+def compileexp(exp, context, curmethods):
     t = exp[0]
-    if t in methods:
-        return methods[t](exp, context)
+    if t in curmethods:
+        return curmethods[t](exp, context)
     raise error.ParseError(_("unknown method '%s'") % t)
 
 # template evaluation
@@ -164,16 +201,19 @@
     return context._filters[f]
 
 def gettemplate(exp, context):
-    if exp[0] == 'string' or exp[0] == 'rawstring':
-        return compiletemplate(exp[1], context, strtoken=exp[0])
+    if exp[0] == 'template':
+        return [compileexp(e, context, methods) for e in exp[1]]
     if exp[0] == 'symbol':
+        # unlike runsymbol(), here 'symbol' is always taken as template name
+        # even if it exists in mapping. this allows us to override mapping
+        # by web templates, e.g. 'changelogtag' is redefined in map file.
         return context._load(exp[1])
     raise error.ParseError(_("expected template specifier"))
 
+def runinteger(context, mapping, data):
+    return int(data)
+
 def runstring(context, mapping, data):
-    return data.decode("string-escape")
-
-def runrawstring(context, mapping, data):
     return data
 
 def runsymbol(context, mapping, key):
@@ -191,8 +231,18 @@
         v = list(v)
     return v
 
+def buildtemplate(exp, context):
+    ctmpl = [compileexp(e, context, methods) for e in exp[1]]
+    if len(ctmpl) == 1:
+        return ctmpl[0]  # fast path for string with no template fragment
+    return (runtemplate, ctmpl)
+
+def runtemplate(context, mapping, template):
+    for func, data in template:
+        yield func(context, mapping, data)
+
 def buildfilter(exp, context):
-    func, data = compileexp(exp[1], context)
+    func, data = compileexp(exp[1], context, methods)
     filt = getfilter(exp[2], context)
     return (runfilter, (func, data, filt))
 
@@ -214,14 +264,10 @@
                            "keyword '%s'") % (filt.func_name, dt))
 
 def buildmap(exp, context):
-    func, data = compileexp(exp[1], context)
+    func, data = compileexp(exp[1], context, methods)
     ctmpl = gettemplate(exp[2], context)
     return (runmap, (func, data, ctmpl))
 
-def runtemplate(context, mapping, template):
-    for func, data in template:
-        yield func(context, mapping, data)
-
 def runmap(context, mapping, data):
     func, data, ctmpl = data
     d = func(context, mapping, data)
@@ -243,7 +289,7 @@
 
 def buildfunc(exp, context):
     n = getsymbol(exp[1])
-    args = [compileexp(x, context) for x in getlist(exp[2])]
+    args = [compileexp(x, context, exprmethods) for x in getlist(exp[2])]
     if n in funcs:
         f = funcs[n]
         return (f, args)
@@ -311,8 +357,8 @@
             # i18n: "fill" is a keyword
             raise error.ParseError(_("fill expects an integer width"))
         try:
-            initindent = stringify(_evalifliteral(args[2], context, mapping))
-            hangindent = stringify(_evalifliteral(args[3], context, mapping))
+            initindent = stringify(args[2][0](context, mapping, args[2][1]))
+            hangindent = stringify(args[3][0](context, mapping, args[3][1]))
         except IndexError:
             pass
 
@@ -328,9 +374,6 @@
     width = int(args[1][1])
 
     text = stringify(args[0][0](context, mapping, args[0][1]))
-    if args[0][0] == runstring:
-        text = stringify(runtemplate(context, mapping,
-            compiletemplate(text, context)))
 
     right = False
     fillchar = ' '
@@ -344,6 +387,26 @@
     else:
         return text.ljust(width, fillchar)
 
+def indent(context, mapping, args):
+    """:indent(text, indentchars[, firstline]): Indents all non-empty lines
+    with the characters given in the indentchars string. An optional
+    third parameter will override the indent for the first line only
+    if present."""
+    if not (2 <= len(args) <= 3):
+        # i18n: "indent" is a keyword
+        raise error.ParseError(_("indent() expects two or three arguments"))
+
+    text = stringify(args[0][0](context, mapping, args[0][1]))
+    indent = stringify(args[1][0](context, mapping, args[1][1]))
+
+    if len(args) == 3:
+        firstline = stringify(args[2][0](context, mapping, args[2][1]))
+    else:
+        firstline = indent
+
+    # the indent function doesn't indent the first line, so we do it here
+    return templatefilters.indent(firstline + text, indent)
+
 def get(context, mapping, args):
     """:get(dict, key): Get an attribute/key from an object. Some keywords
     are complex types. This function allows you to obtain the value of an
@@ -360,15 +423,6 @@
     key = args[1][0](context, mapping, args[1][1])
     yield dictarg.get(key)
 
-def _evalifliteral(arg, context, mapping):
-    # get back to token tag to reinterpret string as template
-    strtoken = {runstring: 'string', runrawstring: 'rawstring'}.get(arg[0])
-    if strtoken:
-        yield runtemplate(context, mapping,
-                          compiletemplate(arg[1], context, strtoken))
-    else:
-        yield stringify(arg[0](context, mapping, arg[1]))
-
 def if_(context, mapping, args):
     """:if(expr, then[, else]): Conditionally execute based on the result of
     an expression."""
@@ -378,9 +432,9 @@
 
     test = stringify(args[0][0](context, mapping, args[0][1]))
     if test:
-        yield _evalifliteral(args[1], context, mapping)
+        yield args[1][0](context, mapping, args[1][1])
     elif len(args) == 3:
-        yield _evalifliteral(args[2], context, mapping)
+        yield args[2][0](context, mapping, args[2][1])
 
 def ifcontains(context, mapping, args):
     """:ifcontains(search, thing, then[, else]): Conditionally execute based
@@ -393,9 +447,9 @@
     items = args[1][0](context, mapping, args[1][1])
 
     if item in items:
-        yield _evalifliteral(args[2], context, mapping)
+        yield args[2][0](context, mapping, args[2][1])
     elif len(args) == 4:
-        yield _evalifliteral(args[3], context, mapping)
+        yield args[3][0](context, mapping, args[3][1])
 
 def ifeq(context, mapping, args):
     """:ifeq(expr1, expr2, then[, else]): Conditionally execute based on
@@ -407,9 +461,9 @@
     test = stringify(args[0][0](context, mapping, args[0][1]))
     match = stringify(args[1][0](context, mapping, args[1][1]))
     if test == match:
-        yield _evalifliteral(args[2], context, mapping)
+        yield args[2][0](context, mapping, args[2][1])
     elif len(args) == 4:
-        yield _evalifliteral(args[3], context, mapping)
+        yield args[3][0](context, mapping, args[3][1])
 
 def join(context, mapping, args):
     """:join(list, sep): Join items in a list with a delimiter."""
@@ -443,7 +497,7 @@
         raise error.ParseError(_("label expects two arguments"))
 
     # ignore args[0] (the label string) since this is supposed to be a a no-op
-    yield _evalifliteral(args[1], context, mapping)
+    yield args[1][0](context, mapping, args[1][1])
 
 def revset(context, mapping, args):
     """:revset(query[, formatargs...]): Execute a revision set query. See
@@ -559,7 +613,7 @@
 
     pat = stringify(args[0][0](context, mapping, args[0][1]))
     rpl = stringify(args[1][0](context, mapping, args[1][1]))
-    src = stringify(_evalifliteral(args[2], context, mapping))
+    src = stringify(args[2][0](context, mapping, args[2][1]))
     yield re.sub(pat, rpl, src)
 
 def startswith(context, mapping, args):
@@ -587,8 +641,7 @@
         num = int(stringify(args[0][0](context, mapping, args[0][1])))
     except ValueError:
         # i18n: "word" is a keyword
-        raise error.ParseError(
-                _("Use strings like '3' for numbers passed to word function"))
+        raise error.ParseError(_("word expects an integer index"))
     text = stringify(args[1][0](context, mapping, args[1][1]))
     if len(args) == 3:
         splitter = stringify(args[2][0](context, mapping, args[2][1]))
@@ -601,17 +654,23 @@
     else:
         return tokens[num]
 
-methods = {
+# methods to interpret function arguments or inner expressions (e.g. {_(x)})
+exprmethods = {
+    "integer": lambda e, c: (runinteger, e[1]),
     "string": lambda e, c: (runstring, e[1]),
-    "rawstring": lambda e, c: (runrawstring, e[1]),
     "symbol": lambda e, c: (runsymbol, e[1]),
-    "group": lambda e, c: compileexp(e[1], c),
+    "template": buildtemplate,
+    "group": lambda e, c: compileexp(e[1], c, exprmethods),
 #    ".": buildmember,
     "|": buildfilter,
     "%": buildmap,
     "func": buildfunc,
     }
 
+# methods to interpret top-level template (e.g. {x}, {x|_}, {x % "y"})
+methods = exprmethods.copy()
+methods["integer"] = exprmethods["symbol"]  # '{1}' as variable
+
 funcs = {
     "date": date,
     "diff": diff,
@@ -620,6 +679,7 @@
     "if": if_,
     "ifcontains": ifcontains,
     "ifeq": ifeq,
+    "indent": indent,
     "join": join,
     "label": label,
     "pad": pad,
@@ -654,14 +714,11 @@
                 for j in _flatten(i):
                     yield j
 
-def parsestring(s, quoted=True):
-    '''unwrap quotes if quoted is True'''
-    if quoted:
-        if len(s) < 2 or s[0] != s[-1]:
-            raise SyntaxError(_('unmatched quotes'))
-        return s[1:-1]
-
-    return s
+def unquotestring(s):
+    '''unwrap quotes'''
+    if len(s) < 2 or s[0] != s[-1]:
+        raise SyntaxError(_('unmatched quotes'))
+    return s[1:-1]
 
 class engine(object):
     '''template expansion engine.
@@ -745,7 +802,7 @@
             raise util.Abort(_("style '%s' not found") % mapfile,
                              hint=_("available styles: %s") % stylelist())
 
-        conf = config.config()
+        conf = config.config(includepaths=templatepaths())
         conf.read(mapfile)
 
         for key, val in conf[''].items():
@@ -753,8 +810,8 @@
                 raise SyntaxError(_('%s: missing value') % conf.source('', key))
             if val[0] in "'\"":
                 try:
-                    self.cache[key] = parsestring(val)
-                except SyntaxError, inst:
+                    self.cache[key] = unquotestring(val)
+                except SyntaxError as inst:
                     raise SyntaxError('%s: %s' %
                                       (conf.source('', key), inst.args[0]))
             else:
@@ -771,10 +828,10 @@
         if t not in self.cache:
             try:
                 self.cache[t] = util.readfile(self.map[t][1])
-            except KeyError, inst:
+            except KeyError as inst:
                 raise TemplateNotFound(_('"%s" not in template map') %
                                        inst.args[0])
-            except IOError, inst:
+            except IOError as inst:
                 raise IOError(inst.args[0], _('template file %s: %s') %
                               (self.map[t][1], inst.args[1]))
         return self.cache[t]
--- a/mercurial/templates/coal/map	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/coal/map	Sat Jul 18 17:32:38 2015 -0500
@@ -43,12 +43,12 @@
 filenav = '{before%filenaventry}{after%filenaventry}'
 
 direntry = '
-  <tr class="fileline parity{parity}">
+  <tr class="fileline">
     <td class="name">
-      <a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">
+      <a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">
         <img src="{staticurl|urlescape}coal-folder.png" alt="dir."/> {basename|escape}/
       </a>
-      <a href="{url|urlescape}file/{node|short}{path|urlescape}/{emptydirs|urlescape}{sessionvars%urlparameter}">
+      <a href="{url|urlescape}file/{symrev}{path|urlescape}/{emptydirs|urlescape}{sessionvars%urlparameter}">
         {emptydirs|escape}
       </a>
     </td>
@@ -57,9 +57,9 @@
   </tr>'
 
 fileentry = '
-  <tr class="fileline parity{parity}">
+  <tr class="fileline">
     <td class="filename">
-      <a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">
+      <a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">
         <img src="{staticurl|urlescape}coal-file.png" alt="file"/> {basename|escape}
       </a>
     </td>
@@ -73,11 +73,11 @@
 filecomparison = ../paper/filecomparison.tmpl
 filelog = ../paper/filelog.tmpl
 fileline = '
-  <div class="parity{parity} source"><a href="#{lineid}" id="{lineid}">{linenumber}</a> {line|escape}</div>'
+  <div class="source"><a href="#{lineid}" id="{lineid}">{linenumber}</a> {line|escape}</div>'
 filelogentry = ../paper/filelogentry.tmpl
 
 annotateline = '
-  <tr class="parity{parity}">
+  <tr>
     <td class="annotate">
       <a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}#{targetline}"
          title="{node|short}: {desc|escape|firstline}">{author|user}@{rev}</a>
@@ -85,7 +85,7 @@
     <td class="source"><a href="#{lineid}" id="{lineid}">{linenumber}</a> {line|escape}</td>
   </tr>'
 
-diffblock = '<div class="source bottomline parity{parity}"><pre>{lines}</pre></div>'
+diffblock = '<div class="source bottomline"><pre>{lines}</pre></div>'
 difflineplus = '<a href="#{lineid}" id="{lineid}">{linenumber}</a> <span class="plusline">{line|escape}</span>'
 difflineminus = '<a href="#{lineid}" id="{lineid}">{linenumber}</a> <span class="minusline">{line|escape}</span>'
 difflineat = '<a href="#{lineid}" id="{lineid}">{linenumber}</a> <span class="atline">{line|escape}</span>'
@@ -156,38 +156,44 @@
   </tr>'
 tags = ../paper/tags.tmpl
 tagentry = '
-  <tr class="tagEntry parity{parity}">
+  <tr class="tagEntry">
     <td>
-      <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">
+      <a href="{url|urlescape}rev/{tag|revescape}{sessionvars%urlparameter}">
         {tag|escape}
       </a>
     </td>
     <td class="node">
-      {node|short}
+      <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">
+        {node|short}
+      </a>
     </td>
   </tr>'
 bookmarks = ../paper/bookmarks.tmpl
 bookmarkentry = '
-  <tr class="tagEntry parity{parity}">
+  <tr class="tagEntry">
     <td>
-      <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">
+      <a href="{url|urlescape}rev/{bookmark|revescape}{sessionvars%urlparameter}">
         {bookmark|escape}
       </a>
     </td>
     <td class="node">
-      {node|short}
+      <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">
+        {node|short}
+      </a>
     </td>
   </tr>'
 branches = ../paper/branches.tmpl
 branchentry = '
-  <tr class="tagEntry parity{parity}">
+  <tr class="tagEntry">
     <td>
-      <a href="{url|urlescape}shortlog/{node|short}{sessionvars%urlparameter}" class="{status}">
+      <a href="{url|urlescape}shortlog/{branch|revescape}{sessionvars%urlparameter}" class="{status}">
         {branch|escape}
       </a>
     </td>
     <td class="node">
-      {node|short}
+      <a href="{url|urlescape}shortlog/{node|short}{sessionvars%urlparameter}" class="{status}">
+        {node|short}
+      </a>
     </td>
   </tr>'
 changelogtag = '<span class="tag">{name|escape}</span> '
@@ -219,7 +225,7 @@
   </tr>'
 
 indexentry = '
-  <tr class="parity{parity}">
+  <tr>
     <td><a href="{url|urlescape}{sessionvars%urlparameter}">{name|escape}</a></td>
     <td>{description}</td>
     <td>{contact|obfuscate}</td>
@@ -230,7 +236,7 @@
 index = ../paper/index.tmpl
 archiveentry = '
   <li>
-    <a href="{url|urlescape}archive/{node|short}{extension|urlescape}{ifeq(path,'/','',path|urlescape)}">{type|escape}</a>
+    <a href="{url|urlescape}archive/{symrev}{extension|urlescape}{ifeq(path,'/','',path|urlescape)}">{type|escape}</a>
   </li>'
 notfound = ../paper/notfound.tmpl
 error = ../paper/error.tmpl
--- a/mercurial/templates/gitweb/bookmarks.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/gitweb/bookmarks.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -20,7 +20,7 @@
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a> |
 bookmarks |
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a> |
-<a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a> |
+<a href="{url|urlescape}file{sessionvars%urlparameter}">files</a> |
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 <br/>
 </div>
--- a/mercurial/templates/gitweb/branches.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/gitweb/branches.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -20,7 +20,7 @@
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a> |
 <a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a> |
 branches |
-<a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a> |
+<a href="{url|urlescape}file{sessionvars%urlparameter}">files</a> |
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 <br/>
 </div>
--- a/mercurial/templates/gitweb/changelog.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/gitweb/changelog.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -21,13 +21,13 @@
 
 <div class="page_nav">
 <a href="{url|urlescape}summary{sessionvars%urlparameter}">summary</a> |
-<a href="{url|urlescape}shortlog/{rev}{sessionvars%urlparameter}">shortlog</a> |
+<a href="{url|urlescape}shortlog/{symrev}{sessionvars%urlparameter}">shortlog</a> |
 changelog |
 <a href="{url|urlescape}graph{sessionvars%urlparameter}">graph</a> |
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a> |
 <a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a> |
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a> |
-<a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a>{archives%archiveentry} |
+<a href="{url|urlescape}file/{symrev}{sessionvars%urlparameter}">files</a>{archives%archiveentry} |
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 <br/>
 {changenav%nav}<br/>
--- a/mercurial/templates/gitweb/changeset.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/gitweb/changeset.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -14,15 +14,15 @@
 
 <div class="page_nav">
 <a href="{url|urlescape}summary{sessionvars%urlparameter}">summary</a> |
-<a href="{url|urlescape}shortlog/{rev}{sessionvars%urlparameter}">shortlog</a> |
-<a href="{url|urlescape}log/{rev}{sessionvars%urlparameter}">changelog</a> |
+<a href="{url|urlescape}shortlog/{symrev}{sessionvars%urlparameter}">shortlog</a> |
+<a href="{url|urlescape}log/{symrev}{sessionvars%urlparameter}">changelog</a> |
 <a href="{url|urlescape}graph{sessionvars%urlparameter}">graph</a> |
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a> |
 <a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a> |
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a> |
-<a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a> |
+<a href="{url|urlescape}file/{symrev}{sessionvars%urlparameter}">files</a> |
 changeset |
-<a href="{url|urlescape}raw-rev/{node|short}">raw</a> {archives%archiveentry} |
+<a href="{url|urlescape}raw-rev/{symrev}">raw</a> {archives%archiveentry} |
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 <br/>
 </div>
@@ -35,7 +35,10 @@
 <tr><td>author</td><td>{author|obfuscate}</td></tr>
 <tr><td></td><td class="date age">{date|rfc822date}</td></tr>
 {branch%changesetbranch}
-<tr><td>changeset {rev}</td><td style="font-family:monospace">{node|short}</td></tr>
+<tr>
+ <td>changeset {rev}</td>
+ <td style="font-family:monospace"><a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td>
+</tr>
 {ifeq(count(parent), '2', parent%changesetparentdiff, parent%changesetparent)}
 {child%changesetchild}
 </table></div>
--- a/mercurial/templates/gitweb/error.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/gitweb/error.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -19,7 +19,7 @@
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a> |
 <a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a> |
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a> |
-<a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a> |
+<a href="{url|urlescape}file{sessionvars%urlparameter}">files</a> |
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 <br/>
 </div>
--- a/mercurial/templates/gitweb/fileannotate.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/gitweb/fileannotate.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -20,15 +20,15 @@
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a> |
 <a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a> |
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a> |
-<a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">files</a> |
-<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a> |
-<a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a> |
+<a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">files</a> |
+<a href="{url|urlescape}rev/{symrev}{sessionvars%urlparameter}">changeset</a> |
+<a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file</a> |
 <a href="{url|urlescape}file/tip/{file|urlescape}{sessionvars%urlparameter}">latest</a> |
-<a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">revisions</a> |
+<a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">revisions</a> |
 annotate |
-<a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a> |
-<a href="{url|urlescape}comparison/{node|short}/{file|urlescape}{sessionvars%urlparameter}">comparison</a> |
-<a href="{url|urlescape}raw-annotate/{node|short}/{file|urlescape}">raw</a> |
+<a href="{url|urlescape}diff/{symrev}/{file|urlescape}{sessionvars%urlparameter}">diff</a> |
+<a href="{url|urlescape}comparison/{symrev}/{file|urlescape}{sessionvars%urlparameter}">comparison</a> |
+<a href="{url|urlescape}raw-annotate/{symrev}/{file|urlescape}">raw</a> |
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 <br/>
 </div>
@@ -39,19 +39,23 @@
 <table cellspacing="0">
 <tr>
  <td>author</td>
- <td>{author|obfuscate}</td></tr>
+ <td>{author|obfuscate}</td>
+</tr>
 <tr>
  <td></td>
- <td class="date age">{date|rfc822date}</td></tr>
+ <td class="date age">{date|rfc822date}</td>
+</tr>
 {branch%filerevbranch}
 <tr>
  <td>changeset {rev}</td>
- <td style="font-family:monospace"><a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr>
+ <td style="font-family:monospace"><a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td>
+</tr>
 {parent%fileannotateparent}
 {child%fileannotatechild}
 <tr>
  <td>permissions</td>
- <td style="font-family:monospace">{permissions|permissions}</td></tr>
+ <td style="font-family:monospace">{permissions|permissions}</td>
+</tr>
 </table>
 </div>
 
--- a/mercurial/templates/gitweb/filecomparison.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/gitweb/filecomparison.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -20,15 +20,15 @@
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a> |
 <a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a> |
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a> |
-<a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">files</a> |
-<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a> |
-<a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a> |
+<a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">files</a> |
+<a href="{url|urlescape}rev/{symrev}{sessionvars%urlparameter}">changeset</a> |
+<a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file</a> |
 <a href="{url|urlescape}file/tip/{file|urlescape}{sessionvars%urlparameter}">latest</a> |
-<a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">revisions</a> |
-<a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a> |
-<a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a> |
+<a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">revisions</a> |
+<a href="{url|urlescape}annotate/{symrev}/{file|urlescape}{sessionvars%urlparameter}">annotate</a> |
+<a href="{url|urlescape}diff/{symrev}/{file|urlescape}{sessionvars%urlparameter}">diff</a> |
 comparison |
-<a href="{url|urlescape}raw-diff/{node|short}/{file|urlescape}">raw</a> |
+<a href="{url|urlescape}raw-diff/{symrev}/{file|urlescape}">raw</a> |
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 <br/>
 </div>
@@ -39,7 +39,8 @@
 {branch%filerevbranch}
 <tr>
  <td>changeset {rev}</td>
- <td style="font-family:monospace"><a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr>
+ <td style="font-family:monospace"><a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td>
+</tr>
 {parent%filecompparent}
 {child%filecompchild}
 </table>
--- a/mercurial/templates/gitweb/filediff.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/gitweb/filediff.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -20,15 +20,15 @@
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a> |
 <a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a> |
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a> |
-<a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">files</a> |
-<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a> |
-<a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a> |
+<a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">files</a> |
+<a href="{url|urlescape}rev/{symrev}{sessionvars%urlparameter}">changeset</a> |
+<a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file</a> |
 <a href="{url|urlescape}file/tip/{file|urlescape}{sessionvars%urlparameter}">latest</a> |
-<a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">revisions</a> |
-<a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a> |
+<a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">revisions</a> |
+<a href="{url|urlescape}annotate/{symrev}/{file|urlescape}{sessionvars%urlparameter}">annotate</a> |
 diff |
-<a href="{url|urlescape}comparison/{node|short}/{file|urlescape}{sessionvars%urlparameter}">comparison</a> |
-<a href="{url|urlescape}raw-diff/{node|short}/{file|urlescape}">raw</a> |
+<a href="{url|urlescape}comparison/{symrev}/{file|urlescape}{sessionvars%urlparameter}">comparison</a> |
+<a href="{url|urlescape}raw-diff/{symrev}/{file|urlescape}">raw</a> |
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 <br/>
 </div>
@@ -39,7 +39,8 @@
 {branch%filerevbranch}
 <tr>
  <td>changeset {rev}</td>
- <td style="font-family:monospace"><a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr>
+ <td style="font-family:monospace"><a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td>
+</tr>
 {parent%filediffparent}
 {child%filediffchild}
 </table>
--- a/mercurial/templates/gitweb/filelog.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/gitweb/filelog.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -20,11 +20,11 @@
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a> |
 <a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a> |
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a> |
-<a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a> |
+<a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file</a> |
 revisions |
-<a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a> |
-<a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a> |
-<a href="{url|urlescape}comparison/{node|short}/{file|urlescape}{sessionvars%urlparameter}">comparison</a> |
+<a href="{url|urlescape}annotate/{symrev}/{file|urlescape}{sessionvars%urlparameter}">annotate</a> |
+<a href="{url|urlescape}diff/{symrev}/{file|urlescape}{sessionvars%urlparameter}">diff</a> |
+<a href="{url|urlescape}comparison/{symrev}/{file|urlescape}{sessionvars%urlparameter}">comparison</a> |
 <a href="{url|urlescape}rss-log/tip/{file|urlescape}">rss</a> |
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 <br/>
--- a/mercurial/templates/gitweb/filerevision.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/gitweb/filerevision.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -20,15 +20,15 @@
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a> |
 <a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a> |
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a> |
-<a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">files</a> |
-<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a> |
+<a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">files</a> |
+<a href="{url|urlescape}rev/{symrev}{sessionvars%urlparameter}">changeset</a> |
 file |
 <a href="{url|urlescape}file/tip/{file|urlescape}{sessionvars%urlparameter}">latest</a> |
-<a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">revisions</a> |
-<a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a> |
-<a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a> |
-<a href="{url|urlescape}comparison/{node|short}/{file|urlescape}{sessionvars%urlparameter}">comparison</a> |
-<a href="{url|urlescape}raw-file/{node|short}/{file|urlescape}">raw</a> |
+<a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">revisions</a> |
+<a href="{url|urlescape}annotate/{symrev}/{file|urlescape}{sessionvars%urlparameter}">annotate</a> |
+<a href="{url|urlescape}diff/{symrev}/{file|urlescape}{sessionvars%urlparameter}">diff</a> |
+<a href="{url|urlescape}comparison/{symrev}/{file|urlescape}{sessionvars%urlparameter}">comparison</a> |
+<a href="{url|urlescape}raw-file/{symrev}/{file|urlescape}">raw</a> |
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 <br/>
 </div>
@@ -39,19 +39,23 @@
 <table cellspacing="0">
 <tr>
  <td>author</td>
- <td>{author|obfuscate}</td></tr>
+ <td>{author|obfuscate}</td>
+</tr>
 <tr>
  <td></td>
- <td class="date age">{date|rfc822date}</td></tr>
+ <td class="date age">{date|rfc822date}</td>
+</tr>
 {branch%filerevbranch}
 <tr>
  <td>changeset {rev}</td>
- <td style="font-family:monospace"><a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr>
+ <td style="font-family:monospace"><a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td>
+</tr>
 {parent%filerevparent}
 {child%filerevchild}
 <tr>
  <td>permissions</td>
- <td style="font-family:monospace">{permissions|permissions}</td></tr>
+ <td style="font-family:monospace">{permissions|permissions}</td>
+</tr>
 </table>
 </div>
 
--- a/mercurial/templates/gitweb/graph.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/gitweb/graph.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -22,16 +22,16 @@
 <div class="page_nav">
 <a href="{url|urlescape}summary{sessionvars%urlparameter}">summary</a> |
 <a href="{url|urlescape}shortlog{sessionvars%urlparameter}">shortlog</a> |
-<a href="{url|urlescape}log/{rev}{sessionvars%urlparameter}">changelog</a> |
+<a href="{url|urlescape}log/{symrev}{sessionvars%urlparameter}">changelog</a> |
 graph |
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a> |
 <a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a> |
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a> |
-<a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a> |
+<a href="{url|urlescape}file/{symrev}{sessionvars%urlparameter}">files</a> |
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 <br/>
-<a href="{url|urlescape}graph/{rev}{lessvars%urlparameter}">less</a>
-<a href="{url|urlescape}graph/{rev}{morevars%urlparameter}">more</a>
+<a href="{url|urlescape}graph/{symrev}{lessvars%urlparameter}">less</a>
+<a href="{url|urlescape}graph/{symrev}{morevars%urlparameter}">more</a>
 | {changenav%navgraph}<br/>
 </div>
 
@@ -103,8 +103,8 @@
 </script>
 
 <div class="page_nav">
-<a href="{url|urlescape}graph/{rev}{lessvars%urlparameter}">less</a>
-<a href="{url|urlescape}graph/{rev}{morevars%urlparameter}">more</a>
+<a href="{url|urlescape}graph/{symrev}{lessvars%urlparameter}">less</a>
+<a href="{url|urlescape}graph/{symrev}{morevars%urlparameter}">more</a>
 | {changenav%navgraph}
 </div>
 
--- a/mercurial/templates/gitweb/help.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/gitweb/help.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -1,5 +1,5 @@
 {header}
-<title>{repo|escape}: Branches</title>
+<title>Help: {topic}</title>
 <link rel="alternate" type="application/atom+xml"
    href="{url|urlescape}atom-tags" title="Atom feed for {repo|escape}"/>
 <link rel="alternate" type="application/rss+xml"
@@ -20,7 +20,7 @@
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a> |
 <a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a> |
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a> |
-<a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a> |
+<a href="{url|urlescape}file{sessionvars%urlparameter}">files</a> |
 help
 <br/>
 </div>
--- a/mercurial/templates/gitweb/helptopics.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/gitweb/helptopics.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -1,5 +1,5 @@
 {header}
-<title>{repo|escape}: Branches</title>
+<title>Help: {title}</title>
 <link rel="alternate" type="application/atom+xml"
    href="{url|urlescape}atom-tags" title="Atom feed for {repo|escape}"/>
 <link rel="alternate" type="application/rss+xml"
@@ -20,7 +20,7 @@
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a> |
 <a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a> |
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a> |
-<a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a> |
+<a href="{url|urlescape}file{sessionvars%urlparameter}">files</a> |
 help
 <br/>
 </div>
--- a/mercurial/templates/gitweb/manifest.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/gitweb/manifest.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -21,7 +21,7 @@
 <a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a> |
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a> |
 files |
-<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a> {archives%archiveentry} |
+<a href="{url|urlescape}rev/{symrev}{sessionvars%urlparameter}">changeset</a> {archives%archiveentry} |
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 <br/>
 </div>
@@ -32,7 +32,7 @@
 <td style="font-family:monospace">drwxr-xr-x</td>
 <td style="font-family:monospace"></td>
 <td style="font-family:monospace"></td>
-<td><a href="{url|urlescape}file/{node|short}{up|urlescape}{sessionvars%urlparameter}">[up]</a></td>
+<td><a href="{url|urlescape}file/{symrev}{up|urlescape}{sessionvars%urlparameter}">[up]</a></td>
 <td class="link">&nbsp;</td>
 </tr>
 {dentries%direntry}
--- a/mercurial/templates/gitweb/map	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/gitweb/map	Sat Jul 18 17:32:38 2015 -0500
@@ -66,11 +66,11 @@
     <td style="font-family:monospace"></td>
     <td style="font-family:monospace"></td>
     <td>
-      <a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">{basename|escape}</a>
-      <a href="{url|urlescape}file/{node|short}{path|urlescape}/{emptydirs|urlescape}{sessionvars%urlparameter}">{emptydirs|escape}</a>
+      <a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">{basename|escape}</a>
+      <a href="{url|urlescape}file/{symrev}{path|urlescape}/{emptydirs|urlescape}{sessionvars%urlparameter}">{emptydirs|escape}</a>
     </td>
     <td class="link">
-      <a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">files</a>
+      <a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">files</a>
     </td>
   </tr>'
 fileentry = '
@@ -79,12 +79,12 @@
     <td style="font-family:monospace" align=right>{date|isodate}</td>
     <td style="font-family:monospace" align=right>{size}</td>
     <td class="list">
-      <a class="list" href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{basename|escape}</a>
+      <a class="list" href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">{basename|escape}</a>
     </td>
     <td class="link">
-      <a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a> |
-      <a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">revisions</a> |
-      <a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a>
+      <a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file</a> |
+      <a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">revisions</a> |
+      <a href="{url|urlescape}annotate/{symrev}/{file|urlescape}{sessionvars%urlparameter}">annotate</a>
     </td>
   </tr>'
 filerevision = filerevision.tmpl
@@ -293,12 +293,17 @@
     <td>
       <a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">
         <b>{desc|strip|firstline|escape|nonempty}</b>
+        <span class="logtags">{inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag}</span>
       </a>
     </td>
     <td class="link">
-      <a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a>&nbsp;|&nbsp;<a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a>&nbsp;|&nbsp;<a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a> {rename%filelogrename}</td>
-    </tr>'
-archiveentry = ' | <a href="{url|urlescape}archive/{node|short}{extension}{ifeq(path,'/','',path|urlescape)}">{type|escape}</a> '
+      <a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a> |
+      <a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a> |
+      <a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a>
+      {rename%filelogrename}
+    </td>
+  </tr>'
+archiveentry = ' | <a href="{url|urlescape}archive/{symrev}{extension}{ifeq(path,'/','',path|urlescape)}">{type|escape}</a> '
 indexentry = '
   <tr class="parity{parity}">
     <td>
--- a/mercurial/templates/gitweb/search.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/gitweb/search.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -27,7 +27,7 @@
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a> |
 <a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a> |
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a> |
-<a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a>{archives%archiveentry}
+<a href="{url|urlescape}file{sessionvars%urlparameter}">files</a>{archives%archiveentry}
  |
  <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 <br/>
--- a/mercurial/templates/gitweb/shortlog.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/gitweb/shortlog.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -21,12 +21,12 @@
 <div class="page_nav">
 <a href="{url|urlescape}summary{sessionvars%urlparameter}">summary</a> |
 shortlog |
-<a href="{url|urlescape}log/{rev}{sessionvars%urlparameter}">changelog</a> |
+<a href="{url|urlescape}log/{symrev}{sessionvars%urlparameter}">changelog</a> |
 <a href="{url|urlescape}graph{sessionvars%urlparameter}">graph</a> |
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a> |
 <a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a> |
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a> |
-<a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a>{archives%archiveentry} |
+<a href="{url|urlescape}file/{symrev}{sessionvars%urlparameter}">files</a>{archives%archiveentry} |
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 <br/>{changenav%navshort}<br/>
 </div>
--- a/mercurial/templates/gitweb/summary.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/gitweb/summary.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -26,7 +26,7 @@
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a> |
 <a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a> |
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a> |
-<a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a>{archives%archiveentry} |
+<a href="{url|urlescape}file{sessionvars%urlparameter}">files</a>{archives%archiveentry} |
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 <br/>
 </div>
--- a/mercurial/templates/gitweb/tags.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/gitweb/tags.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -20,7 +20,7 @@
 tags |
 <a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a> |
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a> |
-<a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a> |
+<a href="{url|urlescape}file{sessionvars%urlparameter}">files</a> |
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 <br/>
 </div>
--- a/mercurial/templates/map-cmdline.bisect	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/map-cmdline.bisect	Sat Jul 18 17:32:38 2015 -0500
@@ -1,25 +1,14 @@
-changeset = 'changeset:   {rev}:{node|short}\nbisect:      {bisect}\n{branches}{bookmarks}{tags}{parents}user:        {author}\ndate:        {date|date}\nsummary:     {desc|firstline}\n\n'
-changeset_quiet = '{bisect|shortbisect} {rev}:{node|short}\n'
-changeset_verbose = 'changeset:   {rev}:{node|short}\nbisect:      {bisect}\n{branches}{bookmarks}{tags}{parents}user:        {author}\ndate:        {date|date}\n{files}{file_copies_switch}description:\n{desc|strip}\n\n\n'
-changeset_debug = 'changeset:   {rev}:{node}\nbisect:      {bisect}\n{branches}{bookmarks}{tags}{parents}{manifest}user:        {author}\ndate:        {date|date}\n{file_mods}{file_adds}{file_dels}{file_copies_switch}{extras}description:\n{desc|strip}\n\n\n'
-start_files = 'files:      '
-file = ' {file}'
-end_files = '\n'
-start_file_mods = 'files:      '
-file_mod = ' {file_mod}'
-end_file_mods = '\n'
-start_file_adds = 'files+:     '
-file_add = ' {file_add}'
-end_file_adds = '\n'
-start_file_dels = 'files-:     '
-file_del = ' {file_del}'
-end_file_dels = '\n'
-start_file_copies = 'copies:     '
-file_copy = ' {name} ({source})'
-end_file_copies = '\n'
-parent = 'parent:      {rev}:{node|formatnode}\n'
-manifest = 'manifest:    {rev}:{node}\n'
-branch = 'branch:      {branch}\n'
-tag = 'tag:         {tag}\n'
-bookmark = 'bookmark:    {bookmark}\n'
-extra = 'extra:       {key}={value|stringescape}\n'
+%include map-cmdline.default
+
+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'
+changeset_debug = '{fullcset}{lbisect}{branches}{bookmarks}{tags}{lphase}{parents}{manifest}{user}{ldate}{lfile_mods}{lfile_adds}{lfile_dels}{lfile_copies_switch}{extras}{description}\n'
+
+# We take the zeroth word in order to omit "(implicit)" in the label
+bisectlabel = ' bisect.{word('0', bisect)}'
+
+lbisect ='{label("log.bisect{if(bisect, bisectlabel)}",
+                                "bisect:      {bisect}\n")}'
+lshortbisect ='{label("log.bisect{if(bisect, bisectlabel)}",
+                                    "{bisect|shortbisect}")}'
--- a/mercurial/templates/map-cmdline.default	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/map-cmdline.default	Sat Jul 18 17:32:38 2015 -0500
@@ -71,3 +71,5 @@
                                        'description:')}
                                 {label('ui.note log.description',
                                        '{desc|strip}')}\n\n")}'
+
+status = '{status} {path}\n{if(copy, "  {copy}\n")}'
--- a/mercurial/templates/map-cmdline.phases	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/map-cmdline.phases	Sat Jul 18 17:32:38 2015 -0500
@@ -1,73 +1,3 @@
-# Base templates. Due to name clashes with existing keywords, we have
-# to replace some keywords with 'lkeyword', for 'labelled keyword'
+%include map-cmdline.default
 changeset = '{cset}{branches}{bookmarks}{tags}{lphase}{parents}{user}{ldate}{summary}\n'
-changeset_quiet = '{lnode}'
 changeset_verbose = '{cset}{branches}{bookmarks}{tags}{lphase}{parents}{user}{ldate}{lfiles}{lfile_copies_switch}{description}\n'
-changeset_debug = '{fullcset}{branches}{bookmarks}{tags}{lphase}{parents}{manifest}{user}{ldate}{lfile_mods}{lfile_adds}{lfile_dels}{lfile_copies_switch}{extras}{description}\n'
-
-# File templates
-lfiles = '{if(files,
-               label("ui.note log.files",
-                     "files:       {files}\n"))}'
-
-lfile_mods = '{if(file_mods,
-                  label("ui.debug log.files",
-                        "files:       {file_mods}\n"))}'
-
-lfile_adds = '{if(file_adds,
-                  label("ui.debug log.files",
-                        "files+:      {file_adds}\n"))}'
-
-lfile_dels = '{if(file_dels,
-                  label("ui.debug log.files",
-                        "files-:      {file_dels}\n"))}'
-
-lfile_copies_switch = '{if(file_copies_switch,
-                           label("ui.note log.copies",
-                                 "copies:     {file_copies_switch
-                                               % ' {name} ({source})'}\n"))}'
-
-# General templates
-cset = '{label("log.changeset changeset.{phase}",
-               "changeset:   {rev}:{node|short}")}\n'
-
-lphase = '{label("log.phase",
-                 "phase:       {phase}")}\n'
-
-fullcset = '{label("log.changeset changeset.{phase}",
-                   "changeset:   {rev}:{node}")}\n'
-
-parent = '{label("log.parent changeset.{phase}",
-                  "parent:      {rev}:{node|formatnode}")}\n'
-
-lnode = '{label("log.node",
-                "{rev}:{node|short}")}\n'
-
-manifest = '{label("ui.debug log.manifest",
-                   "manifest:    {rev}:{node}")}\n'
-
-branch = '{label("log.branch",
-                 "branch:      {branch}")}\n'
-
-tag = '{label("log.tag",
-              "tag:         {tag}")}\n'
-
-bookmark = '{label("log.bookmark",
-                   "bookmark:    {bookmark}")}\n'
-
-user = '{label("log.user",
-               "user:        {author}")}\n'
-
-summary = '{if(desc|strip, "{label('log.summary',
-                                   'summary:     {desc|firstline}')}\n")}'
-
-ldate = '{label("log.date",
-                "date:        {date|date}")}\n'
-
-extra = '{label("ui.debug log.extra",
-                "extra:       {key}={value|stringescape}")}\n'
-
-description = '{if(desc|strip, "{label('ui.note log.description',
-                                       'description:')}
-                                {label('ui.note log.description',
-                                       '{desc|strip}')}\n\n")}'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/templates/map-cmdline.status	Sat Jul 18 17:32:38 2015 -0500
@@ -0,0 +1,25 @@
+%include map-cmdline.default
+
+# Override base templates
+changeset = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{summary}{lfiles}\n'
+changeset_verbose = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{description}{lfiles}\n'
+changeset_debug = '{fullcset}{branches}{bookmarks}{tags}{lphase}{parents}{manifest}{user}{ldate}{extras}{description}{lfiles}\n'
+
+# Override the file templates
+lfiles = '{if(files,
+              label('ui.note log.files',
+                    'files:\n'))}{lfile_mods}{lfile_adds}{lfile_copies_switch}{lfile_dels}'
+
+# Exclude copied files, will display those in lfile_copies_switch
+lfile_adds  = '{file_adds % "{ifcontains(file, file_copies_switch,
+                                         '',
+                                         '{lfile_add}')}"}'
+lfile_add = '{label("status.added", "A {file}\n")}'
+
+lfile_copies_switch = '{file_copies_switch % "{lfile_copy_orig}{lfile_copy_dest}"}'
+lfile_copy_orig = '{label("status.added", "A {name}\n")}'
+lfile_copy_dest = '{label("status.copied", "  {source}\n")}'
+
+lfile_mods = '{file_mods % "{label('status.modified', 'M {file}\n')}"}'
+
+lfile_dels = '{file_dels % "{label('status.removed', 'R {file}\n')}"}'
--- a/mercurial/templates/monoblue/bookmarks.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/monoblue/bookmarks.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -21,11 +21,11 @@
             <li><a href="{url|urlescape}summary{sessionvars%urlparameter}">summary</a></li>
             <li><a href="{url|urlescape}shortlog{sessionvars%urlparameter}">shortlog</a></li>
             <li><a href="{url|urlescape}changelog{sessionvars%urlparameter}">changelog</a></li>
-            <li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+            <li><a href="{url|urlescape}graph{sessionvars%urlparameter}">graph</a></li>
             <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
             <li class="current">bookmarks</li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
-            <li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
+            <li><a href="{url|urlescape}file{sessionvars%urlparameter}">files</a></li>
             <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
     </div>
--- a/mercurial/templates/monoblue/branches.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/monoblue/branches.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -21,11 +21,11 @@
             <li><a href="{url|urlescape}summary{sessionvars%urlparameter}">summary</a></li>
             <li><a href="{url|urlescape}shortlog{sessionvars%urlparameter}">shortlog</a></li>
             <li><a href="{url|urlescape}changelog{sessionvars%urlparameter}">changelog</a></li>
-            <li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+            <li><a href="{url|urlescape}graph{sessionvars%urlparameter}">graph</a></li>
             <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li class="current">branches</li>
-            <li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
+            <li><a href="{url|urlescape}file{sessionvars%urlparameter}">files</a></li>
             <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
     </div>
--- a/mercurial/templates/monoblue/changelog.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/monoblue/changelog.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -21,11 +21,11 @@
             <li><a href="{url|urlescape}summary{sessionvars%urlparameter}">summary</a></li>
             <li><a href="{url|urlescape}shortlog{sessionvars%urlparameter}">shortlog</a></li>
             <li class="current">changelog</li>
-            <li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+            <li><a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a></li>
             <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
-            <li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
+            <li><a href="{url|urlescape}file/{symrev}{sessionvars%urlparameter}">files</a></li>
             {archives%archiveentry}
             <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
--- a/mercurial/templates/monoblue/changeset.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/monoblue/changeset.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -21,18 +21,18 @@
             <li><a href="{url|urlescape}summary{sessionvars%urlparameter}">summary</a></li>
             <li><a href="{url|urlescape}shortlog{sessionvars%urlparameter}">shortlog</a></li>
             <li><a href="{url|urlescape}changelog{sessionvars%urlparameter}">changelog</a></li>
-            <li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+            <li><a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a></li>
             <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
-            <li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
+            <li><a href="{url|urlescape}file/{symrev}{sessionvars%urlparameter}">files</a></li>
             <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
     </div>
 
     <ul class="submenu">
         <li class="current">changeset</li>
-        <li><a href="{url|urlescape}raw-rev/{node|short}">raw</a></li>
+        <li><a href="{url|urlescape}raw-rev/{symrev}">raw</a></li>
         {archives%archiveentry}
     </ul>
 
@@ -48,7 +48,7 @@
         <dd>{date|rfc822date}</dd>
         {branch%changesetbranch}
         <dt>changeset {rev}</dt>
-        <dd>{node|short}</dd>
+        <dd><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></dd>
         {ifeq(count(parent), '2', parent%changesetparentdiff, parent%changesetparent)}
         {child%changesetchild}
     </dl>
--- a/mercurial/templates/monoblue/error.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/monoblue/error.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -18,14 +18,14 @@
         </form>
 
         <ul class="page-nav">
-            <li class="current">summary</li>
+            <li><a href="{url|urlescape}summary{sessionvars%urlparameter}">summary</a></li>
             <li><a href="{url|urlescape}shortlog{sessionvars%urlparameter}">shortlog</a></li>
             <li><a href="{url|urlescape}log{sessionvars%urlparameter}">changelog</a></li>
-            <li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+            <li><a href="{url|urlescape}graph{sessionvars%urlparameter}">graph</a></li>
             <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
-            <li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
+            <li><a href="{url|urlescape}file{sessionvars%urlparameter}">files</a></li>
             <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
     </div>
--- a/mercurial/templates/monoblue/fileannotate.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/monoblue/fileannotate.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -21,22 +21,22 @@
             <li><a href="{url|urlescape}summary{sessionvars%urlparameter}">summary</a></li>
             <li><a href="{url|urlescape}shortlog{sessionvars%urlparameter}">shortlog</a></li>
             <li><a href="{url|urlescape}log{sessionvars%urlparameter}">changelog</a></li>
-            <li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+            <li><a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a></li>
             <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
-            <li><a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">files</a></li>
+            <li><a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">files</a></li>
             <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
     </div>
 
     <ul class="submenu">
-        <li><a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
-        <li><a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">revisions</a></li>
+        <li><a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
+        <li><a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">revisions</a></li>
         <li class="current">annotate</li>
-        <li><a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
-        <li><a href="{url|urlescape}comparison/{node|short}/{file|urlescape}{sessionvars%urlparameter}">comparison</a></li>
-        <li><a href="{url|urlescape}raw-annotate/{node|short}/{file|urlescape}">raw</a></li>
+        <li><a href="{url|urlescape}diff/{symrev}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
+        <li><a href="{url|urlescape}comparison/{symrev}/{file|urlescape}{sessionvars%urlparameter}">comparison</a></li>
+        <li><a href="{url|urlescape}raw-annotate/{symrev}/{file|urlescape}">raw</a></li>
     </ul>
 
     <h2 class="no-link no-border">{file|escape}@{node|short} (annotated)</h2>
--- a/mercurial/templates/monoblue/filecomparison.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/monoblue/filecomparison.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -21,22 +21,22 @@
             <li><a href="{url|urlescape}summary{sessionvars%urlparameter}">summary</a></li>
             <li><a href="{url|urlescape}shortlog{sessionvars%urlparameter}">shortlog</a></li>
             <li><a href="{url|urlescape}log{sessionvars%urlparameter}">changelog</a></li>
-            <li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+            <li><a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a></li>
             <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
-            <li><a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">files</a></li>
+            <li><a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">files</a></li>
             <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
     </div>
 
     <ul class="submenu">
-        <li><a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
-        <li><a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">revisions</a></li>
-        <li><a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
-        <li><a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
+        <li><a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
+        <li><a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">revisions</a></li>
+        <li><a href="{url|urlescape}annotate/{symrev}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
+        <li><a href="{url|urlescape}diff/{symrev}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
         <li class="current">comparison</li>
-        <li><a href="{url|urlescape}raw-diff/{node|short}/{file|urlescape}">raw</a></li>
+        <li><a href="{url|urlescape}raw-diff/{symrev}/{file|urlescape}">raw</a></li>
     </ul>
 
     <h2 class="no-link no-border">comparison: {file|escape}</h2>
--- a/mercurial/templates/monoblue/filediff.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/monoblue/filediff.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -21,22 +21,22 @@
             <li><a href="{url|urlescape}summary{sessionvars%urlparameter}">summary</a></li>
             <li><a href="{url|urlescape}shortlog{sessionvars%urlparameter}">shortlog</a></li>
             <li><a href="{url|urlescape}log{sessionvars%urlparameter}">changelog</a></li>
-            <li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+            <li><a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a></li>
             <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
-            <li><a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">files</a></li>
+            <li><a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">files</a></li>
             <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
     </div>
 
     <ul class="submenu">
-        <li><a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
-        <li><a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">revisions</a></li>
-        <li><a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
+        <li><a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
+        <li><a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">revisions</a></li>
+        <li><a href="{url|urlescape}annotate/{symrev}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
         <li class="current">diff</li>
-        <li><a href="{url|urlescape}comparison/{node|short}/{file|urlescape}{sessionvars%urlparameter}">comparison</a></li>
-        <li><a href="{url|urlescape}raw-diff/{node|short}/{file|urlescape}">raw</a></li>
+        <li><a href="{url|urlescape}comparison/{symrev}/{file|urlescape}{sessionvars%urlparameter}">comparison</a></li>
+        <li><a href="{url|urlescape}raw-diff/{symrev}/{file|urlescape}">raw</a></li>
     </ul>
 
     <h2 class="no-link no-border">diff: {file|escape}</h2>
--- a/mercurial/templates/monoblue/filelog.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/monoblue/filelog.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -21,21 +21,21 @@
             <li><a href="{url|urlescape}summary{sessionvars%urlparameter}">summary</a></li>
             <li><a href="{url|urlescape}shortlog{sessionvars%urlparameter}">shortlog</a></li>
             <li><a href="{url|urlescape}log{sessionvars%urlparameter}">changelog</a></li>
-            <li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+            <li><a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a></li>
             <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
-            <li><a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">files</a></li>
+            <li><a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">files</a></li>
             <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
     </div>
 
     <ul class="submenu">
-        <li><a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
+        <li><a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
         <li class="current">revisions</li>
-        <li><a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
-        <li><a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
-        <li><a href="{url|urlescape}comparison/{node|short}/{file|urlescape}{sessionvars%urlparameter}">comparison</a></li>
+        <li><a href="{url|urlescape}annotate/{symrev}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
+        <li><a href="{url|urlescape}diff/{symrev}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
+        <li><a href="{url|urlescape}comparison/{symrev}/{file|urlescape}{sessionvars%urlparameter}">comparison</a></li>
         <li><a href="{url|urlescape}rss-log/tip/{file|urlescape}">rss</a></li>
     </ul>
 
--- a/mercurial/templates/monoblue/filerevision.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/monoblue/filerevision.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -21,22 +21,22 @@
             <li><a href="{url|urlescape}summary{sessionvars%urlparameter}">summary</a></li>
             <li><a href="{url|urlescape}shortlog{sessionvars%urlparameter}">shortlog</a></li>
             <li><a href="{url|urlescape}changelog{sessionvars%urlparameter}">changelog</a></li>
-            <li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+            <li><a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a></li>
             <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
-            <li><a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">files</a></li>
+            <li><a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">files</a></li>
             <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
     </div>
 
     <ul class="submenu">
         <li class="current">file</li>
-        <li><a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">revisions</a></li>
-        <li><a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
-        <li><a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
-        <li><a href="{url|urlescape}comparison/{node|short}/{file|urlescape}{sessionvars%urlparameter}">comparison</a></li>
-        <li><a href="{url|urlescape}raw-file/{node|short}/{file|urlescape}">raw</a></li>
+        <li><a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">revisions</a></li>
+        <li><a href="{url|urlescape}annotate/{symrev}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
+        <li><a href="{url|urlescape}diff/{symrev}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
+        <li><a href="{url|urlescape}comparison/{symrev}/{file|urlescape}{sessionvars%urlparameter}">comparison</a></li>
+        <li><a href="{url|urlescape}raw-file/{symrev}/{file|urlescape}">raw</a></li>
     </ul>
 
     <h2 class="no-link no-border">{file|escape}@{node|short}</h2>
--- a/mercurial/templates/monoblue/graph.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/monoblue/graph.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -26,7 +26,7 @@
             <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
-            <li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
+            <li><a href="{url|urlescape}file/{symrev}{sessionvars%urlparameter}">files</a></li>
             <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
     </div>
@@ -99,8 +99,8 @@
     </script>
 
     <div class="page-path">
-        <a href="{url|urlescape}graph/{rev}{lessvars%urlparameter}">less</a>
-        <a href="{url|urlescape}graph/{rev}{morevars%urlparameter}">more</a>
+        <a href="{url|urlescape}graph/{symrev}{lessvars%urlparameter}">less</a>
+        <a href="{url|urlescape}graph/{symrev}{morevars%urlparameter}">more</a>
         | {changenav%navgraph}
     </div>
 
--- a/mercurial/templates/monoblue/help.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/monoblue/help.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -1,5 +1,5 @@
 {header}
-    <title>{repo|escape}: Branches</title>
+    <title>Help: {topic}</title>
     <link rel="alternate" type="application/atom+xml" href="{url|urlescape}atom-log" title="Atom feed for {repo|escape}"/>
     <link rel="alternate" type="application/rss+xml" href="{url|urlescape}rss-log" title="RSS feed for {repo|escape}"/>
 </head>
@@ -21,11 +21,11 @@
             <li><a href="{url|urlescape}summary{sessionvars%urlparameter}">summary</a></li>
             <li><a href="{url|urlescape}shortlog{sessionvars%urlparameter}">shortlog</a></li>
             <li><a href="{url|urlescape}changelog{sessionvars%urlparameter}">changelog</a></li>
-            <li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+            <li><a href="{url|urlescape}graph{sessionvars%urlparameter}">graph</a></li>
             <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
-            <li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
+            <li><a href="{url|urlescape}file{sessionvars%urlparameter}">files</a></li>
             <li class="current">help</li>
         </ul>
     </div>
--- a/mercurial/templates/monoblue/helptopics.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/monoblue/helptopics.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -1,5 +1,5 @@
 {header}
-    <title>{repo|escape}: Branches</title>
+    <title>Help: {title}</title>
     <link rel="alternate" type="application/atom+xml" href="{url|urlescape}atom-log" title="Atom feed for {repo|escape}"/>
     <link rel="alternate" type="application/rss+xml" href="{url|urlescape}rss-log" title="RSS feed for {repo|escape}"/>
 </head>
@@ -21,11 +21,11 @@
             <li><a href="{url|urlescape}summary{sessionvars%urlparameter}">summary</a></li>
             <li><a href="{url|urlescape}shortlog{sessionvars%urlparameter}">shortlog</a></li>
             <li><a href="{url|urlescape}changelog{sessionvars%urlparameter}">changelog</a></li>
-            <li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+            <li><a href="{url|urlescape}graph{sessionvars%urlparameter}">graph</a></li>
             <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}help{sessionvars%urlparameter}">branches</a></li>
-            <li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
+            <li><a href="{url|urlescape}file{sessionvars%urlparameter}">files</a></li>
             <li class="current">help</li>
         </ul>
     </div>
--- a/mercurial/templates/monoblue/index.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/monoblue/index.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -1,5 +1,5 @@
 {header}
-    <title>{repo|escape}: Mercurial repositories index</title>
+    <title>Mercurial repositories index</title>
 </head>
 
 <body>
--- a/mercurial/templates/monoblue/manifest.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/monoblue/manifest.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -21,7 +21,7 @@
             <li><a href="{url|urlescape}summary{sessionvars%urlparameter}">summary</a></li>
             <li><a href="{url|urlescape}shortlog{sessionvars%urlparameter}">shortlog</a></li>
             <li><a href="{url|urlescape}changelog{sessionvars%urlparameter}">changelog</a></li>
-            <li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+            <li><a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a></li>
             <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
@@ -31,7 +31,7 @@
     </div>
 
     <ul class="submenu">
-        <li><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a></li>
+        <li><a href="{url|urlescape}rev/{symrev}{sessionvars%urlparameter}">changeset</a></li>
         {archives%archiveentry}
     </ul>
 
@@ -43,7 +43,7 @@
             <td>drwxr-xr-x</td>
             <td></td>
             <td></td>
-            <td><a href="{url|urlescape}file/{node|short}{up|urlescape}{sessionvars%urlparameter}">[up]</a></td>
+            <td><a href="{url|urlescape}file/{symrev}{up|urlescape}{sessionvars%urlparameter}">[up]</a></td>
             <td class="link">&nbsp;</td>
         </tr>
         {dentries%direntry}
--- a/mercurial/templates/monoblue/map	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/monoblue/map	Sat Jul 18 17:32:38 2015 -0500
@@ -65,19 +65,22 @@
     <td>drwxr-xr-x</td>
     <td></td>
     <td></td>
-    <td><a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">{basename|escape}</a></td>
-    <td><a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">files</a></td>
+    <td>
+      <a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">{basename|escape}</a>
+      <a href="{url|urlescape}file/{symrev}{path|urlescape}/{emptydirs|urlescape}{sessionvars%urlparameter}">{emptydirs|escape}</a>
+    </td>
+    <td><a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">files</a></td>
   </tr>'
 fileentry = '
   <tr class="parity{parity}">
     <td>{permissions|permissions}</td>
     <td>{date|isodate}</td>
     <td>{size}</td>
-    <td><a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{basename|escape}</a></td>
+    <td><a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">{basename|escape}</a></td>
     <td>
-      <a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a> |
-      <a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">revisions</a> |
-      <a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a>
+      <a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file</a> |
+      <a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">revisions</a> |
+      <a href="{url|urlescape}annotate/{symrev}/{file|urlescape}{sessionvars%urlparameter}">annotate</a>
     </td>
   </tr>'
 filerevision = filerevision.tmpl
@@ -244,13 +247,18 @@
 filelogentry = '
   <tr class="parity{parity}">
     <td class="nowrap age">{date|rfc822date}</td>
-    <td><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{desc|strip|firstline|escape|nonempty}</a></td>
+    <td>
+      <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">
+        {desc|strip|firstline|escape|nonempty}
+        <span class="logtags">{inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag}</span>
+      </a>
+    </td>
     <td class="nowrap">
       <a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a>&nbsp;|&nbsp;<a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a>&nbsp;|&nbsp;<a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a>
       {rename%filelogrename}
     </td>
   </tr>'
-archiveentry = '<li><a href="{url|urlescape}archive/{node|short}{extension}{ifeq(path,'/','',path|urlescape)}">{type|escape}</a></li>'
+archiveentry = '<li><a href="{url|urlescape}archive/{symrev}{extension}{ifeq(path,'/','',path|urlescape)}">{type|escape}</a></li>'
 indexentry = '
   <tr class="parity{parity}">
     <td><a href="{url|urlescape}{sessionvars%urlparameter}">{name|escape}</a></td>
--- a/mercurial/templates/monoblue/notfound.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/monoblue/notfound.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -1,5 +1,5 @@
 {header}
-    <title>{repo|escape}: Mercurial repository not found</title>
+    <title>Mercurial repository not found</title>
     <link rel="alternate" type="application/atom+xml" href="{url|urlescape}atom-log" title="Atom feed for {repo|escape}"/>
     <link rel="alternate" type="application/rss+xml" href="{url|urlescape}rss-log" title="RSS feed for {repo|escape}"/>
 </head>
@@ -18,14 +18,14 @@
         </form>
 
         <ul class="page-nav">
-            <li class="current">summary</li>
+            <li><a href="{url|urlescape}summary{sessionvars%urlparameter}">summary</a></li>
             <li><a href="{url|urlescape}shortlog{sessionvars%urlparameter}">shortlog</a></li>
             <li><a href="{url|urlescape}log{sessionvars%urlparameter}">changelog</a></li>
-            <li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+            <li><a href="{url|urlescape}graph{sessionvars%urlparameter}">graph</a></li>
             <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
-            <li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
+            <li><a href="{url|urlescape}file{sessionvars%urlparameter}">files</a></li>
             {archives%archiveentry}
             <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
--- a/mercurial/templates/monoblue/search.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/monoblue/search.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -21,11 +21,11 @@
             <li><a href="{url|urlescape}summary{sessionvars%urlparameter}">summary</a></li>
             <li><a href="{url|urlescape}shortlog{sessionvars%urlparameter}">shortlog</a></li>
             <li><a href="{url|urlescape}log{sessionvars%urlparameter}">changelog</a></li>
-            <li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+            <li><a href="{url|urlescape}graph{sessionvars%urlparameter}">graph</a></li>
             <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
-            <li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
+            <li><a href="{url|urlescape}file{sessionvars%urlparameter}">files</a></li>
             {archives%archiveentry}
             <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
--- a/mercurial/templates/monoblue/shortlog.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/monoblue/shortlog.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -21,11 +21,11 @@
             <li><a href="{url|urlescape}summary{sessionvars%urlparameter}">summary</a></li>
             <li class="current">shortlog</li>
             <li><a href="{url|urlescape}log{sessionvars%urlparameter}">changelog</a></li>
-            <li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+            <li><a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a></li>
             <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
-            <li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
+            <li><a href="{url|urlescape}file/{symrev}{sessionvars%urlparameter}">files</a></li>
             {archives%archiveentry}
             <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
--- a/mercurial/templates/monoblue/summary.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/monoblue/summary.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -21,11 +21,11 @@
             <li class="current">summary</li>
             <li><a href="{url|urlescape}shortlog{sessionvars%urlparameter}">shortlog</a></li>
             <li><a href="{url|urlescape}log{sessionvars%urlparameter}">changelog</a></li>
-            <li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+            <li><a href="{url|urlescape}graph{sessionvars%urlparameter}">graph</a></li>
             <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
-            <li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
+            <li><a href="{url|urlescape}file{sessionvars%urlparameter}">files</a></li>
             <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
     </div>
--- a/mercurial/templates/monoblue/tags.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/monoblue/tags.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -21,11 +21,11 @@
             <li><a href="{url|urlescape}summary{sessionvars%urlparameter}">summary</a></li>
             <li><a href="{url|urlescape}shortlog{sessionvars%urlparameter}">shortlog</a></li>
             <li><a href="{url|urlescape}changelog{sessionvars%urlparameter}">changelog</a></li>
-            <li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+            <li><a href="{url|urlescape}graph{sessionvars%urlparameter}">graph</a></li>
             <li class="current">tags</li>
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
-            <li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a></li>
+            <li><a href="{url|urlescape}file{sessionvars%urlparameter}">files</a></li>
             <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
     </div>
--- a/mercurial/templates/paper/changeset.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/paper/changeset.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -9,16 +9,16 @@
 <img src="{staticurl|urlescape}{logoimg}" alt="mercurial" /></a>
 </div>
 <ul>
- <li><a href="{url|urlescape}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
- <li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+ <li><a href="{url|urlescape}shortlog/{symrev}{sessionvars%urlparameter}">log</a></li>
+ <li><a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a></li>
  <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
  <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
  <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
 </ul>
 <ul>
  <li class="active">changeset</li>
- <li><a href="{url|urlescape}raw-rev/{node|short}{sessionvars%urlparameter}">raw</a></li>
- <li><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">browse</a></li>
+ <li><a href="{url|urlescape}raw-rev/{symrev}{sessionvars%urlparameter}">raw</a></li>
+ <li><a href="{url|urlescape}file/{symrev}{sessionvars%urlparameter}">browse</a></li>
 </ul>
 <ul>
  {archives%archiveentry}
@@ -31,7 +31,10 @@
 <div class="main">
 
 <h2 class="breadcrumb"><a href="/">Mercurial</a> {pathdef%breadcrumb}</h2>
-<h3>changeset {rev}:{node|short} {changesetbranch%changelogbranchname} {changesettag} {changesetbookmark}</h3>
+<h3>
+ changeset {rev}:<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a>
+ {changesetbranch%changelogbranchname}{changesettag}{changesetbookmark}
+</h3>
 
 <form class="search" action="{url|urlescape}log">
 {sessionvars%hiddenformentry}
--- a/mercurial/templates/paper/fileannotate.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/paper/fileannotate.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -10,25 +10,25 @@
 <img src="{staticurl|urlescape}{logoimg}" alt="mercurial" /></a>
 </div>
 <ul>
-<li><a href="{url|urlescape}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
-<li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+<li><a href="{url|urlescape}shortlog/{symrev}{sessionvars%urlparameter}">log</a></li>
+<li><a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a></li>
 <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
 <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
 <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
 </ul>
 
 <ul>
-<li><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a></li>
-<li><a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
+<li><a href="{url|urlescape}rev/{symrev}{sessionvars%urlparameter}">changeset</a></li>
+<li><a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
 </ul>
 <ul>
-<li><a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
+<li><a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
 <li><a href="{url|urlescape}file/tip/{file|urlescape}{sessionvars%urlparameter}">latest</a></li>
-<li><a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
-<li><a href="{url|urlescape}comparison/{node|short}/{file|urlescape}{sessionvars%urlparameter}">comparison</a></li>
+<li><a href="{url|urlescape}diff/{symrev}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
+<li><a href="{url|urlescape}comparison/{symrev}/{file|urlescape}{sessionvars%urlparameter}">comparison</a></li>
 <li class="active">annotate</li>
-<li><a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file log</a></li>
-<li><a href="{url|urlescape}raw-annotate/{node|short}/{file|urlescape}">raw</a></li>
+<li><a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file log</a></li>
+<li><a href="{url|urlescape}raw-annotate/{symrev}/{file|urlescape}">raw</a></li>
 </ul>
 <ul>
 <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
@@ -37,7 +37,10 @@
 
 <div class="main">
 <h2 class="breadcrumb"><a href="/">Mercurial</a> {pathdef%breadcrumb}</h2>
-<h3>annotate {file|escape} @ {rev}:{node|short}</h3>
+<h3>
+ annotate {file|escape} @ {rev}:<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a>
+ {branch%changelogbranchname}{tags%changelogtag}{bookmarks%changelogtag}
+</h3>
 
 <form class="search" action="{url|urlescape}log">
 {sessionvars%hiddenformentry}
--- a/mercurial/templates/paper/filecomparison.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/paper/filecomparison.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -10,24 +10,24 @@
 <img src="{staticurl|urlescape}{logoimg}" alt="mercurial" /></a>
 </div>
 <ul>
-<li><a href="{url|urlescape}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
-<li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+<li><a href="{url|urlescape}shortlog/{symrev}{sessionvars%urlparameter}">log</a></li>
+<li><a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a></li>
 <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
 <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
 <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
 </ul>
 <ul>
-<li><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a></li>
-<li><a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
+<li><a href="{url|urlescape}rev/{symrev}{sessionvars%urlparameter}">changeset</a></li>
+<li><a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
 </ul>
 <ul>
-<li><a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
+<li><a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
 <li><a href="{url|urlescape}file/tip/{file|urlescape}{sessionvars%urlparameter}">latest</a></li>
-<li><a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
+<li><a href="{url|urlescape}diff/{symrev}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
 <li class="active">comparison</li>
-<li><a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
-<li><a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file log</a></li>
-<li><a href="{url|urlescape}raw-file/{node|short}/{file|urlescape}">raw</a></li>
+<li><a href="{url|urlescape}annotate/{symrev}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
+<li><a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file log</a></li>
+<li><a href="{url|urlescape}raw-file/{symrev}/{file|urlescape}">raw</a></li>
 </ul>
 <ul>
 <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
@@ -36,7 +36,10 @@
 
 <div class="main">
 <h2 class="breadcrumb"><a href="/">Mercurial</a> {pathdef%breadcrumb}</h2>
-<h3>comparison {file|escape} @ {rev}:{node|short}</h3>
+<h3>
+ comparison {file|escape} @ {rev}:<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a>
+ {branch%changelogbranchname}{tags%changelogtag}{bookmarks%changelogtag}
+</h3>
 
 <form class="search" action="{url|urlescape}log">
 <p>{sessionvars%hiddenformentry}</p>
--- a/mercurial/templates/paper/filediff.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/paper/filediff.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -10,24 +10,24 @@
 <img src="{staticurl|urlescape}{logoimg}" alt="mercurial" /></a>
 </div>
 <ul>
-<li><a href="{url|urlescape}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
-<li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+<li><a href="{url|urlescape}shortlog/{symrev}{sessionvars%urlparameter}">log</a></li>
+<li><a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a></li>
 <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
 <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
 <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
 </ul>
 <ul>
-<li><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a></li>
-<li><a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
+<li><a href="{url|urlescape}rev/{symrev}{sessionvars%urlparameter}">changeset</a></li>
+<li><a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
 </ul>
 <ul>
-<li><a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
+<li><a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
 <li><a href="{url|urlescape}file/tip/{file|urlescape}{sessionvars%urlparameter}">latest</a></li>
 <li class="active">diff</li>
-<li><a href="{url|urlescape}comparison/{node|short}/{file|urlescape}{sessionvars%urlparameter}">comparison</a></li>
-<li><a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
-<li><a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file log</a></li>
-<li><a href="{url|urlescape}raw-file/{node|short}/{file|urlescape}">raw</a></li>
+<li><a href="{url|urlescape}comparison/{symrev}/{file|urlescape}{sessionvars%urlparameter}">comparison</a></li>
+<li><a href="{url|urlescape}annotate/{symrev}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
+<li><a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file log</a></li>
+<li><a href="{url|urlescape}raw-file/{symrev}/{file|urlescape}">raw</a></li>
 </ul>
 <ul>
 <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
@@ -36,7 +36,10 @@
 
 <div class="main">
 <h2 class="breadcrumb"><a href="/">Mercurial</a> {pathdef%breadcrumb}</h2>
-<h3>diff {file|escape} @ {rev}:{node|short}</h3>
+<h3>
+ diff {file|escape} @ {rev}:<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a>
+ {branch%changelogbranchname}{tags%changelogtag}{bookmarks%changelogtag}
+</h3>
 
 <form class="search" action="{url|urlescape}log">
 <p>{sessionvars%hiddenformentry}</p>
--- a/mercurial/templates/paper/filelog.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/paper/filelog.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -14,23 +14,23 @@
 <img src="{staticurl|urlescape}{logoimg}" alt="mercurial" /></a>
 </div>
 <ul>
-<li><a href="{url|urlescape}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
-<li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+<li><a href="{url|urlescape}shortlog/{symrev}{sessionvars%urlparameter}">log</a></li>
+<li><a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a></li>
 <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
 <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
 <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
 </ul>
 <ul>
-<li><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a></li>
-<li><a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
+<li><a href="{url|urlescape}rev/{symrev}{sessionvars%urlparameter}">changeset</a></li>
+<li><a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
 </ul>
 <ul>
-<li><a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
-<li><a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
-<li><a href="{url|urlescape}comparison/{node|short}/{file|urlescape}{sessionvars%urlparameter}">comparison</a></li>
-<li><a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
+<li><a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
+<li><a href="{url|urlescape}diff/{symrev}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
+<li><a href="{url|urlescape}comparison/{symrev}/{file|urlescape}{sessionvars%urlparameter}">comparison</a></li>
+<li><a href="{url|urlescape}annotate/{symrev}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
 <li class="active">file log</li>
-<li><a href="{url|urlescape}raw-file/{node|short}/{file|urlescape}">raw</a></li>
+<li><a href="{url|urlescape}raw-file/{symrev}/{file|urlescape}">raw</a></li>
 </ul>
 <ul>
 <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
@@ -53,8 +53,8 @@
 </form>
 
 <div class="navigate">
-<a href="{url|urlescape}log/{node|short}/{file|urlescape}{lessvars%urlparameter}">less</a>
-<a href="{url|urlescape}log/{node|short}/{file|urlescape}{morevars%urlparameter}">more</a>
+<a href="{url|urlescape}log/{symrev}/{file|urlescape}{lessvars%urlparameter}">less</a>
+<a href="{url|urlescape}log/{symrev}/{file|urlescape}{morevars%urlparameter}">more</a>
 | {nav%filenav}</div>
 
 <table class="bigtable">
@@ -71,8 +71,8 @@
 </table>
 
 <div class="navigate">
-<a href="{url|urlescape}log/{node|short}/{file|urlescape}{lessvars%urlparameter}">less</a>
-<a href="{url|urlescape}log/{node|short}/{file|urlescape}{morevars%urlparameter}">more</a>
+<a href="{url|urlescape}log/{symrev}/{file|urlescape}{lessvars%urlparameter}">less</a>
+<a href="{url|urlescape}log/{symrev}/{file|urlescape}{morevars%urlparameter}">more</a>
 | {nav%filenav}
 </div>
 
--- a/mercurial/templates/paper/filelogentry.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/paper/filelogentry.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -1,5 +1,8 @@
  <tr>
   <td class="age">{date|rfc822date}</td>
   <td class="author">{author|person}</td>
-  <td class="description"><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{desc|strip|firstline|escape|nonempty}</a>{inbranch%changelogbranchname}{branches%changelogbranchhead}{tags%changelogtag}{rename%filelogrename}</td>
+  <td class="description">
+   <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{desc|strip|firstline|escape|nonempty}</a>
+   {inbranch%changelogbranchname}{branches%changelogbranchhead}{tags%changelogtag}{bookmarks%changelogtag}{rename%filelogrename}
+  </td>
  </tr>
--- a/mercurial/templates/paper/filerevision.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/paper/filerevision.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -10,24 +10,24 @@
 <img src="{staticurl|urlescape}{logoimg}" alt="mercurial" /></a>
 </div>
 <ul>
-<li><a href="{url|urlescape}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
-<li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+<li><a href="{url|urlescape}shortlog/{symrev}{sessionvars%urlparameter}">log</a></li>
+<li><a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a></li>
 <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
 <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
 <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
 </ul>
 <ul>
-<li><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a></li>
-<li><a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
+<li><a href="{url|urlescape}rev/{symrev}{sessionvars%urlparameter}">changeset</a></li>
+<li><a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
 </ul>
 <ul>
 <li class="active">file</li>
 <li><a href="{url|urlescape}file/tip/{file|urlescape}{sessionvars%urlparameter}">latest</a></li>
-<li><a href="{url|urlescape}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
-<li><a href="{url|urlescape}comparison/{node|short}/{file|urlescape}{sessionvars%urlparameter}">comparison</a></li>
-<li><a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
-<li><a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file log</a></li>
-<li><a href="{url|urlescape}raw-file/{node|short}/{file|urlescape}">raw</a></li>
+<li><a href="{url|urlescape}diff/{symrev}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
+<li><a href="{url|urlescape}comparison/{symrev}/{file|urlescape}{sessionvars%urlparameter}">comparison</a></li>
+<li><a href="{url|urlescape}annotate/{symrev}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
+<li><a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file log</a></li>
+<li><a href="{url|urlescape}raw-file/{symrev}/{file|urlescape}">raw</a></li>
 </ul>
 <ul>
 <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
@@ -36,7 +36,10 @@
 
 <div class="main">
 <h2 class="breadcrumb"><a href="/">Mercurial</a> {pathdef%breadcrumb}</h2>
-<h3>view {file|escape} @ {rev}:{node|short}</h3>
+<h3>
+ view {file|escape} @ {rev}:<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a>
+ {branch%changelogbranchname}{tags%changelogtag}{bookmarks%changelogtag}
+</h3>
 
 <form class="search" action="{url|urlescape}log">
 {sessionvars%hiddenformentry}
--- a/mercurial/templates/paper/graph.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/paper/graph.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -15,15 +15,15 @@
 <img src="{staticurl|urlescape}{logoimg}" alt="mercurial" /></a>
 </div>
 <ul>
-<li><a href="{url|urlescape}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
+<li><a href="{url|urlescape}shortlog/{symrev}{sessionvars%urlparameter}">log</a></li>
 <li class="active">graph</li>
 <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
 <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
 <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
 </ul>
 <ul>
-<li><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a></li>
-<li><a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
+<li><a href="{url|urlescape}rev/{symrev}{sessionvars%urlparameter}">changeset</a></li>
+<li><a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
 </ul>
 <ul>
  <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
@@ -46,8 +46,8 @@
 </form>
 
 <div class="navigate">
-<a href="{url|urlescape}graph/{rev}{lessvars%urlparameter}">less</a>
-<a href="{url|urlescape}graph/{rev}{morevars%urlparameter}">more</a>
+<a href="{url|urlescape}graph/{symrev}{lessvars%urlparameter}">less</a>
+<a href="{url|urlescape}graph/{symrev}{morevars%urlparameter}">more</a>
 | rev {rev}: {changenav%navgraph}
 </div>
 
@@ -116,8 +116,8 @@
 </script>
 
 <div class="navigate">
-<a href="{url|urlescape}graph/{rev}{lessvars%urlparameter}">less</a>
-<a href="{url|urlescape}graph/{rev}{morevars%urlparameter}">more</a>
+<a href="{url|urlescape}graph/{symrev}{lessvars%urlparameter}">less</a>
+<a href="{url|urlescape}graph/{symrev}{morevars%urlparameter}">more</a>
 | rev {rev}: {changenav%navgraph}
 </div>
 
--- a/mercurial/templates/paper/manifest.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/paper/manifest.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -10,14 +10,14 @@
 <img src="{staticurl|urlescape}{logoimg}" alt="mercurial" /></a>
 </div>
 <ul>
-<li><a href="{url|urlescape}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
-<li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+<li><a href="{url|urlescape}shortlog/{symrev}{sessionvars%urlparameter}">log</a></li>
+<li><a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a></li>
 <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
 <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
 <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
 </ul>
 <ul>
-<li><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a></li>
+<li><a href="{url|urlescape}rev/{symrev}{sessionvars%urlparameter}">changeset</a></li>
 <li class="active">browse</li>
 </ul>
 <ul>
@@ -30,7 +30,10 @@
 
 <div class="main">
 <h2 class="breadcrumb"><a href="/">Mercurial</a> {pathdef%breadcrumb}</h2>
-<h3>directory {path|escape} @ {rev}:{node|short} {tags%changelogtag}</h3>
+<h3>
+ directory {path|escape} @ {rev}:<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a>
+ {branch%changelogbranchname}{tags%changelogtag}{bookmarks%changelogtag}
+</h3>
 
 <form class="search" action="{url|urlescape}log">
 {sessionvars%hiddenformentry}
@@ -48,7 +51,7 @@
 </thead>
 <tbody class="stripes2">
 <tr class="fileline">
-  <td class="name"><a href="{url|urlescape}file/{node|short}{up|urlescape}{sessionvars%urlparameter}">[up]</a></td>
+  <td class="name"><a href="{url|urlescape}file/{symrev}{up|urlescape}{sessionvars%urlparameter}">[up]</a></td>
   <td class="size"></td>
   <td class="permissions">drwxr-xr-x</td>
 </tr>
--- a/mercurial/templates/paper/map	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/paper/map	Sat Jul 18 17:32:38 2015 -0500
@@ -44,10 +44,10 @@
 direntry = '
   <tr class="fileline">
     <td class="name">
-      <a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">
+      <a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">
         <img src="{staticurl|urlescape}coal-folder.png" alt="dir."/> {basename|escape}/
       </a>
-      <a href="{url|urlescape}file/{node|short}{path|urlescape}/{emptydirs|urlescape}{sessionvars%urlparameter}">
+      <a href="{url|urlescape}file/{symrev}{path|urlescape}/{emptydirs|urlescape}{sessionvars%urlparameter}">
         {emptydirs|escape}
       </a>
     </td>
@@ -58,7 +58,7 @@
 fileentry = '
   <tr class="fileline">
     <td class="filename">
-      <a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">
+      <a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">
         <img src="{staticurl|urlescape}coal-file.png" alt="file"/> {basename|escape}
       </a>
     </td>
@@ -161,36 +161,42 @@
 tagentry = '
   <tr class="tagEntry">
     <td>
-      <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">
+      <a href="{url|urlescape}rev/{tag|revescape}{sessionvars%urlparameter}">
         {tag|escape}
       </a>
     </td>
     <td class="node">
-      {node|short}
+      <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">
+        {node|short}
+      </a>
     </td>
   </tr>'
 bookmarks = bookmarks.tmpl
 bookmarkentry = '
   <tr class="tagEntry">
     <td>
-      <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">
+      <a href="{url|urlescape}rev/{bookmark|revescape}{sessionvars%urlparameter}">
         {bookmark|escape}
       </a>
     </td>
     <td class="node">
-      {node|short}
+      <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">
+        {node|short}
+      </a>
     </td>
   </tr>'
 branches = branches.tmpl
 branchentry = '
   <tr class="tagEntry">
     <td>
-      <a href="{url|urlescape}shortlog/{node|short}{sessionvars%urlparameter}" class="{status}">
+      <a href="{url|urlescape}shortlog/{branch|revescape}{sessionvars%urlparameter}" class="{status}">
         {branch|escape}
       </a>
     </td>
     <td class="node">
-      {node|short}
+      <a href="{url|urlescape}shortlog/{node|short}{sessionvars%urlparameter}" class="{status}">
+        {node|short}
+      </a>
     </td>
   </tr>'
 changelogtag = '<span class="tag">{name|escape}</span> '
@@ -240,7 +246,7 @@
 index = index.tmpl
 archiveentry = '
   <li>
-    <a href="{url|urlescape}archive/{node|short}{extension|urlescape}{ifeq(path,'/','',path|urlescape)}">{type|escape}</a>
+    <a href="{url|urlescape}archive/{symrev}{extension|urlescape}{ifeq(path,'/','',path|urlescape)}">{type|escape}</a>
   </li>'
 notfound = notfound.tmpl
 error = error.tmpl
--- a/mercurial/templates/paper/search.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/paper/search.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -15,6 +15,8 @@
 <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
 <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
 <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
+</ul>
+<ul>
 <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
 </ul>
 </div>
--- a/mercurial/templates/paper/shortlog.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/paper/shortlog.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -15,14 +15,14 @@
 </div>
 <ul>
 <li class="active">log</li>
-<li><a href="{url|urlescape}graph/{node|short}{sessionvars%urlparameter}">graph</a></li>
+<li><a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a></li>
 <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
 <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
 <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
 </ul>
 <ul>
-<li><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a></li>
-<li><a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
+<li><a href="{url|urlescape}rev/{symrev}{sessionvars%urlparameter}">changeset</a></li>
+<li><a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
 </ul>
 <ul>
 {archives%archiveentry}
@@ -48,8 +48,8 @@
 </form>
 
 <div class="navigate">
-<a href="{url|urlescape}shortlog/{rev}{lessvars%urlparameter}">less</a>
-<a href="{url|urlescape}shortlog/{rev}{morevars%urlparameter}">more</a>
+<a href="{url|urlescape}shortlog/{symrev}{lessvars%urlparameter}">less</a>
+<a href="{url|urlescape}shortlog/{symrev}{morevars%urlparameter}">more</a>
 | rev {rev}: {changenav%navshort}
 </div>
 
@@ -67,8 +67,8 @@
 </table>
 
 <div class="navigate">
-<a href="{url|urlescape}shortlog/{rev}{lessvars%urlparameter}">less</a>
-<a href="{url|urlescape}shortlog/{rev}{morevars%urlparameter}">more</a>
+<a href="{url|urlescape}shortlog/{symrev}{lessvars%urlparameter}">less</a>
+<a href="{url|urlescape}shortlog/{symrev}{morevars%urlparameter}">more</a>
 | rev {rev}: {changenav%navshort}
 </div>
 
--- a/mercurial/templates/paper/shortlogentry.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/paper/shortlogentry.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -1,5 +1,8 @@
  <tr>
   <td class="age">{date|rfc822date}</td>
   <td class="author">{author|person}</td>
-  <td class="description"><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{desc|strip|firstline|escape|nonempty}</a>{inbranch%changelogbranchname}{branches%changelogbranchhead}{tags%changelogtag}{bookmarks%changelogtag}</td>
+  <td class="description">
+   <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{desc|strip|firstline|escape|nonempty}</a>
+   {inbranch%changelogbranchname}{branches%changelogbranchhead}{tags%changelogtag}{bookmarks%changelogtag}
+  </td>
  </tr>
--- a/mercurial/templates/spartan/branches.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/spartan/branches.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -12,7 +12,7 @@
 <a href="{url|urlescape}shortlog{sessionvars%urlparameter}">shortlog</a>
 <a href="{url|urlescape}graph{sessionvars%urlparameter}">graph</a>
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a>
-<a href="{url|urlescape}file/{node|short}/{sessionvars%urlparameter}">files</a>
+<a href="{url|urlescape}file{sessionvars%urlparameter}">files</a>
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 <a type="application/rss+xml" href="{url|urlescape}rss-branches">rss</a>
 <a type="application/atom+xml" href="{url|urlescape}atom-branches">atom</a>
--- a/mercurial/templates/spartan/changelog.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/spartan/changelog.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -8,11 +8,11 @@
 <body>
 
 <div class="buttons">
-<a href="{url|urlescape}shortlog/{rev}{sessionvars%urlparameter}">shortlog</a>
-<a href="{url|urlescape}graph{sessionvars%urlparameter}">graph</a>
+<a href="{url|urlescape}shortlog/{symrev}{sessionvars%urlparameter}">shortlog</a>
+<a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a>
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a>
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a>
-<a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a>
+<a href="{url|urlescape}file/{symrev}{sessionvars%urlparameter}">files</a>
 {archives%archiveentry}
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 <a type="application/rss+xml" href="{url|urlescape}rss-log">rss</a>
--- a/mercurial/templates/spartan/changeset.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/spartan/changeset.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -4,13 +4,13 @@
 <body>
 
 <div class="buttons">
-<a href="{url|urlescape}log/{rev}{sessionvars%urlparameter}">changelog</a>
-<a href="{url|urlescape}shortlog/{rev}{sessionvars%urlparameter}">shortlog</a>
-<a href="{url|urlescape}graph{sessionvars%urlparameter}">graph</a>
+<a href="{url|urlescape}log/{symrev}{sessionvars%urlparameter}">changelog</a>
+<a href="{url|urlescape}shortlog/{symrev}{sessionvars%urlparameter}">shortlog</a>
+<a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a>
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a>
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a>
-<a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a>
-<a href="{url|urlescape}raw-rev/{node|short}">raw</a>
+<a href="{url|urlescape}file/{symrev}{sessionvars%urlparameter}">files</a>
+<a href="{url|urlescape}raw-rev/{symrev}">raw</a>
 {archives%archiveentry}
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 </div>
--- a/mercurial/templates/spartan/fileannotate.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/spartan/fileannotate.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -4,16 +4,16 @@
 <body>
 
 <div class="buttons">
-<a href="{url|urlescape}log/{rev}{sessionvars%urlparameter}">changelog</a>
-<a href="{url|urlescape}shortlog/{rev}{sessionvars%urlparameter}">shortlog</a>
-<a href="{url|urlescape}graph{sessionvars%urlparameter}">graph</a>
+<a href="{url|urlescape}log/{symrev}{sessionvars%urlparameter}">changelog</a>
+<a href="{url|urlescape}shortlog/{symrev}{sessionvars%urlparameter}">shortlog</a>
+<a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a>
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a>
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a>
-<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a>
-<a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">files</a>
-<a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a>
-<a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">revisions</a>
-<a href="{url|urlescape}raw-annotate/{node|short}/{file|urlescape}">raw</a>
+<a href="{url|urlescape}rev/{symrev}{sessionvars%urlparameter}">changeset</a>
+<a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">files</a>
+<a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file</a>
+<a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">revisions</a>
+<a href="{url|urlescape}raw-annotate/{symrev}/{file|urlescape}">raw</a>
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 </div>
 
@@ -22,12 +22,14 @@
 <table>
 <tr>
  <td class="metatag">changeset {rev}:</td>
- <td><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr>
+ <td><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td>
+</tr>
 {parent%fileannotateparent}
 {child%fileannotatechild}
 <tr>
  <td class="metatag">author:</td>
- <td>{author|obfuscate}</td></tr>
+ <td>{author|obfuscate}</td>
+</tr>
 <tr>
  <td class="metatag">date:</td>
  <td class="date age">{date|rfc822date}</td>
--- a/mercurial/templates/spartan/filediff.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/spartan/filediff.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -4,16 +4,16 @@
 <body>
 
 <div class="buttons">
-<a href="{url|urlescape}log/{rev}{sessionvars%urlparameter}">changelog</a>
-<a href="{url|urlescape}shortlog/{rev}{sessionvars%urlparameter}">shortlog</a>
-<a href="{url|urlescape}graph{sessionvars%urlparameter}">graph</a>
+<a href="{url|urlescape}log/{symrev}{sessionvars%urlparameter}">changelog</a>
+<a href="{url|urlescape}shortlog/{symrev}{sessionvars%urlparameter}">shortlog</a>
+<a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a>
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a>
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a>
-<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a>
-<a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a>
-<a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">revisions</a>
-<a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a>
-<a href="{url|urlescape}raw-diff/{node|short}/{file|urlescape}">raw</a>
+<a href="{url|urlescape}rev/{symrev}{sessionvars%urlparameter}">changeset</a>
+<a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file</a>
+<a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">revisions</a>
+<a href="{url|urlescape}annotate/{symrev}/{file|urlescape}{sessionvars%urlparameter}">annotate</a>
+<a href="{url|urlescape}raw-diff/{symrev}/{file|urlescape}">raw</a>
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 </div>
 
--- a/mercurial/templates/spartan/filelog.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/spartan/filelog.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -13,8 +13,8 @@
 <a href="{url|urlescape}graph{sessionvars%urlparameter}">graph</a>
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a>
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a>
-<a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a>
-<a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a>
+<a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file</a>
+<a href="{url|urlescape}annotate/{symrev}/{file|urlescape}{sessionvars%urlparameter}">annotate</a>
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 <a type="application/rss+xml" href="{url|urlescape}rss-log/tip/{file|urlescape}">rss</a>
 <a type="application/atom+xml" href="{url|urlescape}atom-log/tip/{file|urlescape}" title="Atom feed for {repo|escape}:{file}">atom</a>
--- a/mercurial/templates/spartan/filerevision.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/spartan/filerevision.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -4,16 +4,16 @@
 <body>
 
 <div class="buttons">
-<a href="{url|urlescape}log/{rev}{sessionvars%urlparameter}">changelog</a>
-<a href="{url|urlescape}shortlog/{rev}{sessionvars%urlparameter}">shortlog</a>
-<a href="{url|urlescape}graph{sessionvars%urlparameter}">graph</a>
+<a href="{url|urlescape}log/{symrev}{sessionvars%urlparameter}">changelog</a>
+<a href="{url|urlescape}shortlog/{symrev}{sessionvars%urlparameter}">shortlog</a>
+<a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a>
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a>
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a>
-<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a>
-<a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">files</a>
-<a href="{url|urlescape}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">revisions</a>
-<a href="{url|urlescape}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a>
-<a href="{url|urlescape}raw-file/{node|short}/{file|urlescape}">raw</a>
+<a href="{url|urlescape}rev/{symrev}{sessionvars%urlparameter}">changeset</a>
+<a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">files</a>
+<a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">revisions</a>
+<a href="{url|urlescape}annotate/{symrev}/{file|urlescape}{sessionvars%urlparameter}">annotate</a>
+<a href="{url|urlescape}raw-file/{symrev}/{file|urlescape}">raw</a>
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 </div>
 
@@ -22,18 +22,22 @@
 <table>
 <tr>
  <td class="metatag">changeset {rev}:</td>
- <td><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr>
+ <td><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td>
+</tr>
 {parent%filerevparent}
 {child%filerevchild}
 <tr>
  <td class="metatag">author:</td>
- <td>{author|obfuscate}</td></tr>
+ <td>{author|obfuscate}</td>
+</tr>
 <tr>
  <td class="metatag">date:</td>
- <td class="date age">{date|rfc822date}</td></tr>
+ <td class="date age">{date|rfc822date}</td>
+</tr>
 <tr>
  <td class="metatag">permissions:</td>
- <td>{permissions|permissions}</td></tr>
+ <td>{permissions|permissions}</td>
+</tr>
 <tr>
   <td class="metatag">description:</td>
   <td>{desc|strip|escape|websub|addbreaks|nonempty}</td>
--- a/mercurial/templates/spartan/graph.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/spartan/graph.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -9,11 +9,11 @@
 <body>
 
 <div class="buttons">
-<a href="{url|urlescape}log{sessionvars%urlparameter}">changelog</a>
-<a href="{url|urlescape}shortlog{sessionvars%urlparameter}">shortlog</a>
+<a href="{url|urlescape}log/{symrev}{sessionvars%urlparameter}">changelog</a>
+<a href="{url|urlescape}shortlog/{symrev}{sessionvars%urlparameter}">shortlog</a>
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a>
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a>
-<a href="{url|urlescape}file/{node|short}/{sessionvars%urlparameter}">files</a>
+<a href="{url|urlescape}file/{symrev}/{sessionvars%urlparameter}">files</a>
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 </div>
 
--- a/mercurial/templates/spartan/manifest.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/spartan/manifest.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -4,12 +4,12 @@
 <body>
 
 <div class="buttons">
-<a href="{url|urlescape}log/{rev}{sessionvars%urlparameter}">changelog</a>
-<a href="{url|urlescape}shortlog/{rev}{sessionvars%urlparameter}">shortlog</a>
-<a href="{url|urlescape}graph{sessionvars%urlparameter}">graph</a>
+<a href="{url|urlescape}log/{symrev}{sessionvars%urlparameter}">changelog</a>
+<a href="{url|urlescape}shortlog/{symrev}{sessionvars%urlparameter}">shortlog</a>
+<a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a>
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a>
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a>
-<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">changeset</a>
+<a href="{url|urlescape}rev/{symrev}{sessionvars%urlparameter}">changeset</a>
 {archives%archiveentry}
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 </div>
@@ -21,7 +21,7 @@
   <td><tt>drwxr-xr-x</tt>&nbsp;
   <td>&nbsp;
   <td>&nbsp;
-  <td><a href="{url|urlescape}file/{node|short}{up|urlescape}{sessionvars%urlparameter}">[up]</a>
+  <td><a href="{url|urlescape}file/{symrev}{up|urlescape}{sessionvars%urlparameter}">[up]</a>
 </tr>
 {dentries%direntry}
 {fentries%fileentry}
--- a/mercurial/templates/spartan/map	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/spartan/map	Sat Jul 18 17:32:38 2015 -0500
@@ -31,8 +31,8 @@
     <td>&nbsp;
     <td>&nbsp;
     <td>
-      <a href="{url|urlescape}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">{basename|escape}/</a>
-      <a href="{url|urlescape}file/{node|short}{path|urlescape}/{emptydirs|urlescape}{sessionvars%urlparameter}">
+      <a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">{basename|escape}/</a>
+      <a href="{url|urlescape}file/{symrev}{path|urlescape}/{emptydirs|urlescape}{sessionvars%urlparameter}">
         {emptydirs|urlescape}
       </a>'
 
@@ -41,7 +41,7 @@
     <td><tt>{permissions|permissions}</tt>&nbsp;
     <td align=right><tt class="date">{date|isodate}</tt>&nbsp;
     <td align=right><tt>{size}</tt>&nbsp;
-    <td><a href="{url|urlescape}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{basename|escape}</a>'
+    <td><a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">{basename|escape}</a>'
 
 filerevision = filerevision.tmpl
 fileannotate = fileannotate.tmpl
@@ -183,7 +183,7 @@
     </td>
   </tr>'
 index = index.tmpl
-archiveentry = '<a href="{url|urlescape}archive/{node|short}{extension|urlescape}">{type|escape}</a> '
+archiveentry = '<a href="{url|urlescape}archive/{symrev}{extension|urlescape}">{type|escape}</a> '
 notfound = notfound.tmpl
 error = error.tmpl
 urlparameter = '{separator}{name}={value|urlescape}'
--- a/mercurial/templates/spartan/search.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/spartan/search.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -9,7 +9,7 @@
 <a href="{url|urlescape}graph{sessionvars%urlparameter}">graph</a>
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a>
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a>
-<a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a>
+<a href="{url|urlescape}file{sessionvars%urlparameter}">files</a>
 {archives%archiveentry}
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 </div>
--- a/mercurial/templates/spartan/shortlog.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/spartan/shortlog.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -8,11 +8,11 @@
 <body>
 
 <div class="buttons">
-<a href="{url|urlescape}log/{rev}{sessionvars%urlparameter}">changelog</a>
-<a href="{url|urlescape}graph{sessionvars%urlparameter}">graph</a>
+<a href="{url|urlescape}log/{symrev}{sessionvars%urlparameter}">changelog</a>
+<a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a>
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a>
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a>
-<a href="{url|urlescape}file/{node|short}/{sessionvars%urlparameter}">files</a>
+<a href="{url|urlescape}file/{symrev}/{sessionvars%urlparameter}">files</a>
 {archives%archiveentry}
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 <a type="application/rss+xml" href="{url|urlescape}rss-log">rss</a>
--- a/mercurial/templates/spartan/tags.tmpl	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/spartan/tags.tmpl	Sat Jul 18 17:32:38 2015 -0500
@@ -12,7 +12,7 @@
 <a href="{url|urlescape}shortlog{sessionvars%urlparameter}">shortlog</a>
 <a href="{url|urlescape}graph{sessionvars%urlparameter}">graph</a>
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a>
-<a href="{url|urlescape}file/{node|short}/{sessionvars%urlparameter}">files</a>
+<a href="{url|urlescape}file{sessionvars%urlparameter}">files</a>
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 <a type="application/rss+xml" href="{url|urlescape}rss-tags">rss</a>
 <a type="application/atom+xml" href="{url|urlescape}atom-tags">atom</a>
--- a/mercurial/templates/static/style-coal.css	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/static/style-coal.css	Sat Jul 18 17:32:38 2015 -0500
@@ -97,8 +97,12 @@
 .age { white-space:nowrap; }
 .date { white-space:nowrap; }
 .indexlinks { white-space:nowrap; }
-.parity0 { background-color: #f0f0f0; }
-.parity1 { background-color: white; }
+.parity0,
+.stripes4 > :nth-child(4n+1),
+.stripes2 > :nth-child(2n+1) { background-color: #f0f0f0; }
+.parity1,
+.stripes4 > :nth-child(4n+3),
+.stripes2 > :nth-child(2n+2) { background-color: white; }
 .plusline { color: green; }
 .minusline { color: #dc143c; } /* crimson */
 .atline { color: purple; }
--- a/mercurial/templates/static/style-monoblue.css	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/templates/static/style-monoblue.css	Sat Jul 18 17:32:38 2015 -0500
@@ -53,10 +53,8 @@
   }
 
   div.page-header form {
-    position: absolute;
-    margin-bottom: 2px;
-    bottom: 0;
-    right: 20px;
+    float: right;
+    margin-top: -2px;
   }
   div.page-header form label {
     color: #DDD;
@@ -83,7 +81,6 @@
     margin: 10px 0 0 0;
     list-style-type: none;
     overflow: hidden;
-    width: 900px;
   }
     ul.page-nav li {
       margin: 0 2px 0 0;
@@ -156,7 +153,7 @@
 }
   div.page-footer p {
     position: relative;
-    left: 20px;
+    padding-left: 20px;
     bottom: 5px;
     font-size: 1.2em;
   }
--- a/mercurial/transaction.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/transaction.py	Sat Jul 18 17:32:38 2015 -0500
@@ -39,7 +39,7 @@
         else:
             try:
                 opener.unlink(f)
-            except (IOError, OSError), inst:
+            except (IOError, OSError) as inst:
                 if inst.errno != errno.ENOENT:
                     raise
 
@@ -62,10 +62,10 @@
                 target = f or b
                 try:
                     vfs.unlink(target)
-                except (IOError, OSError), inst:
+                except (IOError, OSError) as inst:
                     if inst.errno != errno.ENOENT:
                         raise
-        except (IOError, OSError, util.Abort), inst:
+        except (IOError, OSError, util.Abort) as inst:
             if not c:
                 raise
 
@@ -77,7 +77,7 @@
         for f in backupfiles:
             if opener.exists(f):
                 opener.unlink(f)
-    except (IOError, OSError, util.Abort), inst:
+    except (IOError, OSError, util.Abort) as inst:
         # only pure backup file remains, it is sage to ignore any error
         pass
 
@@ -130,8 +130,8 @@
         self._backupsfile.write('%d\n' % version)
 
         if createmode is not None:
-            opener.chmod(self.journal, createmode & 0666)
-            opener.chmod(self._backupjournal, createmode & 0666)
+            opener.chmod(self.journal, createmode & 0o666)
+            opener.chmod(self._backupjournal, createmode & 0o666)
 
         # hold file generations to be performed on commit
         self._filegenerators = {}
@@ -405,7 +405,7 @@
             if not f and b and vfs.exists(b):
                 try:
                     vfs.unlink(b)
-                except (IOError, OSError, util.Abort), inst:
+                except (IOError, OSError, util.Abort) as inst:
                     if not c:
                         raise
                     # Abort may be raise by read only opener
@@ -428,7 +428,7 @@
                 if b and vfs.exists(b):
                     try:
                         vfs.unlink(b)
-                    except (IOError, OSError, util.Abort), inst:
+                    except (IOError, OSError, util.Abort) as inst:
                         if not c:
                             raise
                         # Abort may be raise by read only opener
@@ -496,7 +496,7 @@
                 _playback(self.journal, self.report, self.opener, self._vfsmap,
                           self.entries, self._backupentries, False)
                 self.report(_("rollback completed\n"))
-            except Exception:
+            except BaseException:
                 self.report(_("rollback failed - please run hg recover\n"))
         finally:
             self.journal = None
--- a/mercurial/treediscovery.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/treediscovery.py	Sat Jul 18 17:32:38 2015 -0500
@@ -5,6 +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.
 
+import collections
 from node import nullid, short
 from i18n import _
 import util, error
@@ -56,7 +57,7 @@
     # a 'branch' here is a linear segment of history, with four parts:
     # head, root, first parent, second parent
     # (a branch always has two parents (or none) by definition)
-    unknown = util.deque(remote.branches(unknown))
+    unknown = collections.deque(remote.branches(unknown))
     while unknown:
         r = []
         while unknown:
--- a/mercurial/ui.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/ui.py	Sat Jul 18 17:32:38 2015 -0500
@@ -5,9 +5,10 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
+import inspect
 from i18n import _
 import errno, getpass, os, socket, sys, tempfile, traceback
-import config, scmutil, util, error, formatter
+import config, scmutil, util, error, formatter, progress
 from node import hex
 
 samplehgrcs = {
@@ -152,7 +153,7 @@
         try:
             cfg.read(filename, fp, sections=sections, remap=remap)
             fp.close()
-        except error.ConfigError, inst:
+        except error.ConfigError as inst:
             if trusted:
                 raise
             self.warn(_("ignored: %s\n") % str(inst))
@@ -584,6 +585,7 @@
         "cmdname.type" is recommended. For example, status issues
         a label of "status.modified" for modified files.
         '''
+        self._progclear()
         if self._buffers:
             self._buffers[-1].extend([str(a) for a in args])
         else:
@@ -591,6 +593,7 @@
                 self.fout.write(str(a))
 
     def write_err(self, *args, **opts):
+        self._progclear()
         try:
             if self._bufferstates and self._bufferstates[-1][0]:
                 return self.write(*args, **opts)
@@ -602,7 +605,7 @@
             # including stdout.
             if not getattr(self.ferr, 'closed', False):
                 self.ferr.flush()
-        except IOError, inst:
+        except IOError as inst:
             if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
                 raise
 
@@ -842,7 +845,7 @@
         output will be redirected if fout is not stdout.
         '''
         out = self.fout
-        if util.any(s[1] for s in self._bufferstates):
+        if any(s[1] for s in self._bufferstates):
             out = self
         return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
                            errprefix=errprefix, out=out)
@@ -867,8 +870,8 @@
                                ''.join(causetb),
                                ''.join(exconly))
             else:
-                traceback.print_exception(exc[0], exc[1], exc[2],
-                                          file=self.ferr)
+                output = traceback.format_exception(exc[0], exc[1], exc[2])
+                self.write_err(''.join(output))
         return self.tracebackflag or force
 
     def geteditor(self):
@@ -885,6 +888,22 @@
                 os.environ.get("VISUAL") or
                 os.environ.get("EDITOR", editor))
 
+    @util.propertycache
+    def _progbar(self):
+        """setup the progbar singleton to the ui object"""
+        if (self.quiet or self.debugflag
+                or self.configbool('progress', 'disable', False)
+                or not progress.shouldprint(self)):
+            return None
+        return getprogbar(self)
+
+    def _progclear(self):
+        """clear progress bar output if any. use it before any output"""
+        if '_progbar' not in vars(self): # nothing loadef yet
+            return
+        if self._progbar is not None and self._progbar.printed:
+            self._progbar.clear()
+
     def progress(self, topic, pos, item="", unit="", total=None):
         '''show a progress message
 
@@ -901,8 +920,10 @@
         All topics should be marked closed by setting pos to None at
         termination.
         '''
-
-        if pos is None or not self.debugflag:
+        if self._progbar is not None:
+            self._progbar.progress(topic, pos, item=item, unit=unit,
+                                   total=total)
+        if pos is None or not self.configbool('progress', 'debug'):
             return
 
         if unit:
@@ -938,6 +959,16 @@
         '''
         return msg
 
+    def develwarn(self, msg):
+        """issue a developer warning message"""
+        msg = 'devel-warn: ' + msg
+        if self.tracebackflag:
+            util.debugstacktrace(msg, 2)
+        else:
+            curframe = inspect.currentframe()
+            calframe = inspect.getouterframes(curframe, 2)
+            self.write_err('%s at: %s:%s (%s)\n' % ((msg,) + calframe[2][1:4]))
+
 class paths(dict):
     """Represents a collection of paths and their configs.
 
@@ -982,3 +1013,15 @@
         self.name = name
         # We'll do more intelligent things with rawloc in the future.
         self.loc = rawloc
+
+# we instantiate one globally shared progress bar to avoid
+# competing progress bars when multiple UI objects get created
+_progresssingleton = None
+
+def getprogbar(ui):
+    global _progresssingleton
+    if _progresssingleton is None:
+        # passing 'ui' object to the singleton is fishy,
+        # this is how the extension used to work but feel free to rework it.
+        _progresssingleton = progress.progbar(ui)
+    return _progresssingleton
--- a/mercurial/url.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/url.py	Sat Jul 18 17:32:38 2015 -0500
@@ -120,16 +120,6 @@
             if e.startswith('.') and host.endswith(e[1:]):
                 return None
 
-        # work around a bug in Python < 2.4.2
-        # (it leaves a "\n" at the end of Proxy-authorization headers)
-        baseclass = req.__class__
-        class _request(baseclass):
-            def add_header(self, key, val):
-                if key.lower() == 'proxy-authorization':
-                    val = val.strip()
-                return baseclass.add_header(self, key, val)
-        req.__class__ = _request
-
         return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
 
 def _gen_sendfile(orgsend):
@@ -169,7 +159,7 @@
                     sock.connect(sa)
                     return sock
 
-                except socket.error, msg:
+                except socket.error as msg:
                     if sock is not None:
                         sock.close()
 
@@ -185,8 +175,8 @@
             self.sock.connect((self.host, self.port))
             if _generic_proxytunnel(self):
                 # we do not support client X.509 certificates
-                self.sock = sslutil.ssl_wrap_socket(self.sock, None, None,
-                                                    serverhostname=self.host)
+                self.sock = sslutil.wrapsocket(self.sock, None, None, None,
+                                               serverhostname=self.host)
         else:
             keepalive.HTTPConnection.connect(self)
 
@@ -328,11 +318,18 @@
         return keepalive.HTTPHandler._start_transaction(self, h, req)
 
 if has_https:
-    class httpsconnection(httplib.HTTPSConnection):
+    class httpsconnection(httplib.HTTPConnection):
         response_class = keepalive.HTTPResponse
+        default_port = httplib.HTTPS_PORT
         # must be able to send big bundle as stream.
         send = _gen_sendfile(keepalive.safesend)
-        getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
+        getresponse = keepalive.wrapgetresponse(httplib.HTTPConnection)
+
+        def __init__(self, host, port=None, key_file=None, cert_file=None,
+                     *args, **kwargs):
+            httplib.HTTPConnection.__init__(self, host, port, *args, **kwargs)
+            self.key_file = key_file
+            self.cert_file = cert_file
 
         def connect(self):
             self.sock = _create_connection((self.host, self.port))
@@ -341,7 +338,7 @@
             if self.realhostport: # use CONNECT proxy
                 _generic_proxytunnel(self)
                 host = self.realhostport.rsplit(':', 1)[0]
-            self.sock = sslutil.ssl_wrap_socket(
+            self.sock = sslutil.wrapsocket(
                 self.sock, self.key_file, self.cert_file, serverhostname=host,
                 **sslutil.sslkwargs(self.ui, host))
             sslutil.validator(self.ui, host)(self.sock)
@@ -414,7 +411,7 @@
         try:
             return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
                         self, auth_header, host, req, headers)
-        except ValueError, inst:
+        except ValueError as inst:
             arg = inst.args[0]
             if arg.startswith("AbstractDigestAuthHandler doesn't know "):
                 return
@@ -472,6 +469,7 @@
     construct an opener suitable for urllib2
     authinfo will be added to the password manager
     '''
+    # experimental config: ui.usehttp2
     if ui.configbool('ui', 'usehttp2', False):
         handlers = [httpconnectionmod.http2handler(ui, passwordmgr(ui))]
     else:
--- a/mercurial/util.h	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/util.h	Sat Jul 18 17:32:38 2015 -0500
@@ -57,66 +57,6 @@
 
 #endif /* PY_MAJOR_VERSION */
 
-/* Backports from 2.6 */
-#if PY_VERSION_HEX < 0x02060000
-
-#define Py_TYPE(ob) (ob)->ob_type
-#define Py_SIZE(ob) (ob)->ob_size
-#define PyVarObject_HEAD_INIT(type, size) PyObject_HEAD_INIT(type) size,
-
-/* Shamelessly stolen from bytesobject.h */
-#define PyBytesObject PyStringObject
-#define PyBytes_Type PyString_Type
-
-#define PyBytes_Check PyString_Check
-#define PyBytes_CheckExact PyString_CheckExact
-#define PyBytes_CHECK_INTERNED PyString_CHECK_INTERNED
-#define PyBytes_AS_STRING PyString_AS_STRING
-#define PyBytes_GET_SIZE PyString_GET_SIZE
-#define Py_TPFLAGS_BYTES_SUBCLASS Py_TPFLAGS_STRING_SUBCLASS
-
-#define PyBytes_FromStringAndSize PyString_FromStringAndSize
-#define PyBytes_FromString PyString_FromString
-#define PyBytes_FromFormatV PyString_FromFormatV
-#define PyBytes_FromFormat PyString_FromFormat
-#define PyBytes_Size PyString_Size
-#define PyBytes_AsString PyString_AsString
-#define PyBytes_Repr PyString_Repr
-#define PyBytes_Concat PyString_Concat
-#define PyBytes_ConcatAndDel PyString_ConcatAndDel
-#define _PyBytes_Resize _PyString_Resize
-#define _PyBytes_Eq _PyString_Eq
-#define PyBytes_Format PyString_Format
-#define _PyBytes_FormatLong _PyString_FormatLong
-#define PyBytes_DecodeEscape PyString_DecodeEscape
-#define _PyBytes_Join _PyString_Join
-#define PyBytes_Decode PyString_Decode
-#define PyBytes_Encode PyString_Encode
-#define PyBytes_AsEncodedObject PyString_AsEncodedObject
-#define PyBytes_AsEncodedString PyString_AsEncodedString
-#define PyBytes_AsDecodedObject PyString_AsDecodedObject
-#define PyBytes_AsDecodedString PyString_AsDecodedString
-#define PyBytes_AsStringAndSize PyString_AsStringAndSize
-#define _PyBytes_InsertThousandsGrouping _PyString_InsertThousandsGrouping
-
-#endif /* PY_VERSION_HEX */
-
-#if (PY_VERSION_HEX < 0x02050000)
-/* Definitions to get compatibility with python 2.4 and earlier which
-   does not have Py_ssize_t. See also PEP 353.
-   Note: msvc (8 or earlier) does not have ssize_t, so we use Py_ssize_t.
-*/
-typedef int Py_ssize_t;
-typedef Py_ssize_t (*lenfunc)(PyObject *);
-typedef PyObject *(*ssizeargfunc)(PyObject *, Py_ssize_t);
-#define PyInt_FromSsize_t PyInt_FromLong
-
-#if !defined(PY_SSIZE_T_MIN)
-#define PY_SSIZE_T_MAX INT_MAX
-#define PY_SSIZE_T_MIN INT_MIN
-#endif
-#endif
-
 #ifdef _WIN32
 #ifdef _MSC_VER
 /* msvc 6.0 has problems */
--- a/mercurial/util.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/util.py	Sat Jul 18 17:32:38 2015 -0500
@@ -19,7 +19,7 @@
 import errno, shutil, sys, tempfile, traceback
 import re as remod
 import os, time, datetime, calendar, textwrap, signal, collections
-import imp, socket, urllib, struct
+import imp, socket, urllib
 import gc
 
 if os.name == 'nt':
@@ -54,6 +54,7 @@
 oslink = platform.oslink
 parsepatchoutput = platform.parsepatchoutput
 pconvert = platform.pconvert
+poll = platform.poll
 popen = platform.popen
 posixfile = platform.posixfile
 quotecommand = platform.quotecommand
@@ -232,14 +233,101 @@
 import subprocess
 closefds = os.name == 'posix'
 
-def unpacker(fmt):
-    """create a struct unpacker for the specified format"""
-    try:
-        # 2.5+
-        return struct.Struct(fmt).unpack
-    except AttributeError:
-        # 2.4
-        return lambda buf: struct.unpack(fmt, buf)
+_chunksize = 4096
+
+class bufferedinputpipe(object):
+    """a manually buffered input pipe
+
+    Python will not let us use buffered IO and lazy reading with 'polling' at
+    the same time. We cannot probe the buffer state and select will not detect
+    that data are ready to read if they are already buffered.
+
+    This class let us work around that by implementing its own buffering
+    (allowing efficient readline) while offering a way to know if the buffer is
+    empty from the output (allowing collaboration of the buffer with polling).
+
+    This class lives in the 'util' module because it makes use of the 'os'
+    module from the python stdlib.
+    """
+
+    def __init__(self, input):
+        self._input = input
+        self._buffer = []
+        self._eof = False
+        self._lenbuf = 0
+
+    @property
+    def hasbuffer(self):
+        """True is any data is currently buffered
+
+        This will be used externally a pre-step for polling IO. If there is
+        already data then no polling should be set in place."""
+        return bool(self._buffer)
+
+    @property
+    def closed(self):
+        return self._input.closed
+
+    def fileno(self):
+        return self._input.fileno()
+
+    def close(self):
+        return self._input.close()
+
+    def read(self, size):
+        while (not self._eof) and (self._lenbuf < size):
+            self._fillbuffer()
+        return self._frombuffer(size)
+
+    def readline(self, *args, **kwargs):
+        if 1 < len(self._buffer):
+            # this should not happen because both read and readline end with a
+            # _frombuffer call that collapse it.
+            self._buffer = [''.join(self._buffer)]
+            self._lenbuf = len(self._buffer[0])
+        lfi = -1
+        if self._buffer:
+            lfi = self._buffer[-1].find('\n')
+        while (not self._eof) and lfi < 0:
+            self._fillbuffer()
+            if self._buffer:
+                lfi = self._buffer[-1].find('\n')
+        size = lfi + 1
+        if lfi < 0: # end of file
+            size = self._lenbuf
+        elif 1 < len(self._buffer):
+            # we need to take previous chunks into account
+            size += self._lenbuf - len(self._buffer[-1])
+        return self._frombuffer(size)
+
+    def _frombuffer(self, size):
+        """return at most 'size' data from the buffer
+
+        The data are removed from the buffer."""
+        if size == 0 or not self._buffer:
+            return ''
+        buf = self._buffer[0]
+        if 1 < len(self._buffer):
+            buf = ''.join(self._buffer)
+
+        data = buf[:size]
+        buf = buf[len(data):]
+        if buf:
+            self._buffer = [buf]
+            self._lenbuf = len(buf)
+        else:
+            self._buffer = []
+            self._lenbuf = 0
+        return data
+
+    def _fillbuffer(self):
+        """read data to the buffer"""
+        data = os.read(self._input.fileno(), _chunksize)
+        if not data:
+            self._eof = True
+        else:
+            self._lenbuf += len(data)
+            self._buffer.append(data)
 
 def popen2(cmd, env=None, newlines=False):
     # Setting bufsize to -1 lets the system decide the buffer size.
@@ -256,8 +344,8 @@
     stdin, stdout, stderr, p = popen4(cmd, env, newlines)
     return stdin, stdout, stderr
 
-def popen4(cmd, env=None, newlines=False):
-    p = subprocess.Popen(cmd, shell=True, bufsize=-1,
+def popen4(cmd, env=None, newlines=False, bufsize=-1):
+    p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
                          close_fds=closefds,
                          stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                          stderr=subprocess.PIPE,
@@ -334,18 +422,6 @@
 
     return f
 
-try:
-    collections.deque.remove
-    deque = collections.deque
-except AttributeError:
-    # python 2.4 lacks deque.remove
-    class deque(collections.deque):
-        def remove(self, val):
-            for i, v in enumerate(self):
-                if v == val:
-                    del self[i]
-                    break
-
 class sortdict(dict):
     '''a simple sorted dictionary'''
     def __init__(self, data=None):
@@ -396,7 +472,7 @@
     def __init__(self, maxsize):
         self._cache = {}
         self._maxsize = maxsize
-        self._order = deque()
+        self._order = collections.deque()
 
     def __getitem__(self, key):
         value = self._cache[key]
@@ -418,12 +494,12 @@
 
     def clear(self):
         self._cache.clear()
-        self._order = deque()
+        self._order = collections.deque()
 
 def lrucachefunc(func):
     '''cache most recent results of function calls'''
     cache = {}
-    order = deque()
+    order = collections.deque()
     if func.func_code.co_argcount == 1:
         def f(arg):
             if arg not in cache:
@@ -739,7 +815,7 @@
         try:
             shutil.copyfile(src, dest)
             shutil.copymode(src, dest)
-        except shutil.Error, inst:
+        except shutil.Error as inst:
             raise Abort(str(inst))
 
 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
@@ -838,7 +914,7 @@
 def makelock(info, pathname):
     try:
         return os.symlink(info, pathname)
-    except OSError, why:
+    except OSError as why:
         if why.errno == errno.EEXIST:
             raise
     except AttributeError: # no symlink in os
@@ -851,7 +927,7 @@
 def readlock(pathname):
     try:
         return os.readlink(pathname)
-    except OSError, why:
+    except OSError as why:
         if why.errno not in (errno.EINVAL, errno.ENOSYS):
             raise
     except AttributeError: # no symlink in os
@@ -1003,15 +1079,13 @@
     f2 = testfile + ".hgtmp2"
     fd = None
     try:
-        try:
-            oslink(f1, f2)
-        except OSError:
-            return False
-
+        oslink(f1, f2)
         # nlinks() may behave differently for files on Windows shares if
         # the file is open.
         fd = posixfile(f2)
         return nlinks(f2) > 1
+    except OSError:
+        return False
     finally:
         if fd is not None:
             fd.close()
@@ -1070,7 +1144,7 @@
     try:
         try:
             ifp = posixfile(name, "rb")
-        except IOError, inst:
+        except IOError as inst:
             if inst.errno == errno.ENOENT:
                 return temp
             if not getattr(inst, 'filename', None):
@@ -1129,7 +1203,7 @@
     """recursive directory creation with parent mode inheritance"""
     try:
         makedir(name, notindexed)
-    except OSError, err:
+    except OSError as err:
         if err.errno == errno.EEXIST:
             return
         if err.errno != errno.ENOENT or not name:
@@ -1156,7 +1230,7 @@
         ensuredirs(parent, mode, notindexed)
     try:
         makedir(name, notindexed)
-    except OSError, err:
+    except OSError as err:
         if err.errno == errno.EEXIST and os.path.isdir(name):
             # someone else seems to have won a directory creation race
             return
@@ -1203,7 +1277,7 @@
                 else:
                     yield chunk
         self.iter = splitbig(in_iter)
-        self._queue = deque()
+        self._queue = collections.deque()
 
     def read(self, l=None):
         """Read L bytes of data from the iterator of chunks of data.
@@ -1573,13 +1647,6 @@
 
         This requires use decision to determine width of such characters.
         """
-        def __init__(self, **kwargs):
-            textwrap.TextWrapper.__init__(self, **kwargs)
-
-            # for compatibility between 2.4 and 2.6
-            if getattr(self, 'drop_whitespace', None) is None:
-                self.drop_whitespace = kwargs.get('drop_whitespace', True)
-
         def _cutdown(self, ucstr, space_left):
             l = 0
             colwidth = encoding.ucolwidth
@@ -1734,21 +1801,6 @@
         if prevhandler is not None:
             signal.signal(signal.SIGCHLD, prevhandler)
 
-try:
-    any, all = any, all
-except NameError:
-    def any(iterable):
-        for i in iterable:
-            if i:
-                return True
-        return False
-
-    def all(iterable):
-        for i in iterable:
-            if not i:
-                return False
-        return True
-
 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
     """Return the result of interpolating items in the mapping into string s.
 
--- a/mercurial/verify.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/verify.py	Sat Jul 18 17:32:38 2015 -0500
@@ -108,7 +108,7 @@
             if p2 not in seen and p2 != nullid:
                 err(lr, _("unknown parent 2 %s of %s") %
                     (short(p2), short(node)), f)
-        except Exception, inst:
+        except Exception as inst:
             exc(lr, _("checking parents of %s") % short(node), inst, f)
 
         if node in seen:
@@ -144,7 +144,7 @@
                 refersmf = True
             for f in changes[3]:
                 filelinkrevs.setdefault(_normpath(f), []).append(i)
-        except Exception, inst:
+        except Exception as inst:
             refersmf = True
             exc(i, _("unpacking changeset %s") % short(n), inst)
     ui.progress(_('checking'), None)
@@ -171,7 +171,7 @@
                     err(lr, _("file without name in manifest"))
                 elif f != "/dev/null": # ignore this in very old repos
                     filenodes.setdefault(_normpath(f), {}).setdefault(fn, lr)
-        except Exception, inst:
+        except Exception as inst:
             exc(lr, _("reading manifest delta %s") % short(n), inst)
     ui.progress(_('checking'), None)
 
@@ -219,6 +219,7 @@
         elif size > 0 or not revlogv1:
             storefiles.add(_normpath(f))
 
+    fncachewarned = False
     files = sorted(set(filenodes) | set(filelinkrevs))
     total = len(files)
     for i, f in enumerate(files):
@@ -236,7 +237,7 @@
 
         try:
             fl = repo.file(f)
-        except error.RevlogError, e:
+        except error.RevlogError as e:
             err(lr, _("broken revlog! (%s)") % e, f)
             continue
 
@@ -244,7 +245,8 @@
             try:
                 storefiles.remove(ff)
             except KeyError:
-                err(lr, _("missing revlog!"), ff)
+                warn(_(" warning: revlog '%s' not in fncache!") % ff)
+                fncachewarned = True
 
         checklog(fl, f, lr)
         seen = {}
@@ -268,9 +270,10 @@
                         err(lr, _("unpacked size is %s, %s expected") %
                             (l, fl.size(i)), f)
             except error.CensoredNodeError:
+                # experimental config: censor.policy
                 if ui.config("censor", "policy", "abort") == "abort":
                     err(lr, _("censored file data"), f)
-            except Exception, inst:
+            except Exception as inst:
                 exc(lr, _("unpacking %s") % short(n), inst, f)
 
             # check renames
@@ -296,7 +299,7 @@
                             % (f, lr, rp[0], short(rp[1])))
                     else:
                         fl2.rev(rp[1])
-            except Exception, inst:
+            except Exception as inst:
                 exc(lr, _("checking rename of %s") % short(n), inst, f)
 
         # cross-check
@@ -313,6 +316,9 @@
                    (len(files), len(cl), revisions))
     if warnings[0]:
         ui.warn(_("%d warnings encountered!\n") % warnings[0])
+    if fncachewarned:
+        ui.warn(_('hint: run "hg debugrebuildfncache" to recover from '
+                  'corrupt fncache\n'))
     if errors[0]:
         ui.warn(_("%d integrity errors encountered!\n") % errors[0])
         if badrevs:
--- a/mercurial/win32.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/win32.py	Sat Jul 18 17:32:38 2015 -0500
@@ -467,7 +467,7 @@
         try:
             os.rename(f, temp)  # raises OSError EEXIST if temp exists
             break
-        except OSError, e:
+        except OSError as e:
             if e.errno != errno.EEXIST:
                 raise
     else:
--- a/mercurial/windows.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/windows.py	Sat Jul 18 17:32:38 2015 -0500
@@ -25,8 +25,7 @@
 testpid = win32.testpid
 unlink = win32.unlink
 
-umask = 0022
-_SEEK_END = 2 # os.SEEK_END was introduced in Python 2.5
+umask = 0o022
 
 def posixfile(name, mode='r', buffering=-1):
     '''Open a file with even more POSIX-like semantics'''
@@ -36,10 +35,10 @@
         # The position when opening in append mode is implementation defined, so
         # make it consistent with other platforms, which position at EOF.
         if 'a' in mode:
-            fp.seek(0, _SEEK_END)
+            fp.seek(0, os.SEEK_END)
 
         return fp
-    except WindowsError, err:
+    except WindowsError as err:
         # convert to a friendlier exception
         raise IOError(err.errno, '%s: %s' % (name, err.strerror))
 
@@ -70,7 +69,7 @@
                 end = start + limit
                 self.fp.write(s[start:end])
                 start = end
-        except IOError, inst:
+        except IOError as inst:
             if inst.errno != 0:
                 raise
             self.close()
@@ -79,7 +78,7 @@
     def flush(self):
         try:
             return self.fp.flush()
-        except IOError, inst:
+        except IOError as inst:
             if inst.errno != errno.EINVAL:
                 raise
             self.close()
@@ -139,7 +138,7 @@
     return pconvert(os.path.normpath(path))
 
 def normcase(path):
-    return encoding.upper(path)
+    return encoding.upper(path) # NTFS compares via upper()
 
 # see posix.py for definitions
 normcasespec = encoding.normcasespecs.upper
@@ -162,6 +161,18 @@
 _quotere = None
 _needsshellquote = None
 def shellquote(s):
+    r"""
+    >>> shellquote(r'C:\Users\xyz')
+    '"C:\\Users\\xyz"'
+    >>> shellquote(r'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')
+    '"C:\\\\Users\\\\xyz"'
+    >>> # But this must be quoted
+    >>> shellquote(r'C:\\Users\\xyz/abc')
+    '"C:\\\\Users\\\\xyz/abc"'
+    """
     global _quotere
     if _quotere is None:
         _quotere = re.compile(r'(\\*)("|\\$)')
@@ -248,12 +259,10 @@
                 dmap = dict([(normcase(n), s)
                              for n, k, s in osutil.listdir(dir, True)
                              if getkind(s.st_mode) in _wantedkinds])
-            except OSError, err:
-                # handle directory not found in Python version prior to 2.5
-                # Python <= 2.4 returns native Windows code 3 in errno
+            except OSError as err:
                 # Python >= 2.5 returns ENOENT and adds winerror field
                 # EINVAL is raised if dir is not a directory.
-                if err.errno not in (3, errno.ENOENT, errno.EINVAL,
+                if err.errno not in (errno.ENOENT, errno.EINVAL,
                                      errno.ENOTDIR):
                     raise
                 dmap = {}
@@ -294,7 +303,7 @@
     """unlink and remove the directory if it is empty"""
     try:
         unlink(f)
-    except OSError, e:
+    except OSError as e:
         if not (ignoremissing and e.errno == errno.ENOENT):
             raise
     # try removing directories that might now be empty
@@ -307,7 +316,7 @@
     '''atomically rename file src to dst, replacing dst if it exists'''
     try:
         os.rename(src, dst)
-    except OSError, e:
+    except OSError as e:
         if e.errno != errno.EEXIST:
             raise
         unlink(dst)
@@ -361,6 +370,10 @@
     '''check whether a stat result is an executable file'''
     return False
 
+def poll(fds):
+    # see posix.py for description
+    raise NotImplementedError()
+
 def readpipe(pipe):
     """Read all available data from a pipe."""
     chunks = []
--- a/mercurial/wireproto.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/wireproto.py	Sat Jul 18 17:32:38 2015 -0500
@@ -9,7 +9,7 @@
 from i18n import _
 from node import bin, hex
 import changegroup as changegroupmod, bundle2, pushkey as pushkeymod
-import peer, error, encoding, util, store, exchange
+import peer, error, encoding, util, exchange
 
 
 class abstractserverproto(object):
@@ -175,24 +175,23 @@
     try:
         return sep.join(map(hex, l))
     except TypeError:
-        print l
         raise
 
 # batched call argument encoding
 
 def escapearg(plain):
     return (plain
-            .replace(':', '::')
-            .replace(',', ':,')
-            .replace(';', ':;')
-            .replace('=', ':='))
+            .replace(':', ':c')
+            .replace(',', ':o')
+            .replace(';', ':s')
+            .replace('=', ':e'))
 
 def unescapearg(escaped):
     return (escaped
-            .replace(':=', '=')
-            .replace(':;', ';')
-            .replace(':,', ',')
-            .replace('::', ':'))
+            .replace(':e', '=')
+            .replace(':s', ';')
+            .replace(':o', ',')
+            .replace(':c', ':'))
 
 # mapping of options accepted by getbundle and their types
 #
@@ -203,11 +202,12 @@
 #
 # :nodes: list of binary nodes
 # :csv:   list of comma-separated values
+# :scsv:  list of comma-separated values return as set
 # :plain: string with no transformation needed.
 gboptsmap = {'heads':  'nodes',
              'common': 'nodes',
              'obsmarkers': 'boolean',
-             'bundlecaps': 'csv',
+             'bundlecaps': 'scsv',
              'listkeys': 'csv',
              'cg': 'boolean'}
 
@@ -220,10 +220,11 @@
     def _submitbatch(self, req):
         cmds = []
         for op, argsdict in req:
-            args = ','.join('%s=%s' % p for p in argsdict.iteritems())
+            args = ','.join('%s=%s' % (escapearg(k), escapearg(v))
+                            for k, v in argsdict.iteritems())
             cmds.append('%s %s' % (op, args))
         rsp = self._call("batch", cmds=';'.join(cmds))
-        return rsp.split(';')
+        return [unescapearg(r) for r in rsp.split(';')]
     def _submitone(self, op, args):
         return self._call(op, **args)
 
@@ -324,6 +325,8 @@
         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):
@@ -345,6 +348,11 @@
     def getbundle(self, source, **kwargs):
         self.requirecap('getbundle', _('look up remote changes'))
         opts = {}
+        bundlecaps = kwargs.get('bundlecaps')
+        if bundlecaps is not None:
+            kwargs['bundlecaps'] = sorted(bundlecaps)
+        else:
+            bundlecaps = () # kwargs could have it to None
         for key, value in kwargs.iteritems():
             if value is None:
                 continue
@@ -353,7 +361,7 @@
                 assert False, 'unexpected'
             elif keytype == 'nodes':
                 value = encodelist(value)
-            elif keytype == 'csv':
+            elif keytype in ('csv', 'scsv'):
                 value = ','.join(value)
             elif keytype == 'boolean':
                 value = '%i' % bool(value)
@@ -362,10 +370,7 @@
                                % keytype)
             opts[key] = value
         f = self._callcompressable("getbundle", **opts)
-        bundlecaps = kwargs.get('bundlecaps')
-        if bundlecaps is None:
-            bundlecaps = () # kwargs could have it to None
-        if util.any((cap.startswith('HG2') for cap in bundlecaps)):
+        if any((cap.startswith('HG2') for cap in bundlecaps)):
             return bundle2.getunbundler(self.ui, f)
         else:
             return changegroupmod.cg1unpacker(f, 'UN')
@@ -619,7 +624,8 @@
         capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
         caps.append('bundle2=' + urllib.quote(capsblob))
     caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
-    caps.append('httpheader=1024')
+    caps.append(
+        'httpheader=%d' % repo.ui.configint('server', 'maxhttpheaderlen', 1024))
     return caps
 
 # If you are writing an extension and consider wrapping this function. Wrap
@@ -661,6 +667,8 @@
         if keytype == 'nodes':
             opts[k] = decodelist(v)
         elif keytype == 'csv':
+            opts[k] = list(v.split(','))
+        elif keytype == 'scsv':
             opts[k] = set(v.split(','))
         elif keytype == 'boolean':
             opts[k] = bool(v)
@@ -698,7 +706,7 @@
         c = repo[k]
         r = c.hex()
         success = 1
-    except Exception, inst:
+    except Exception as inst:
         r = str(inst)
         success = 0
     return "%s %s\n" % (success, r)
@@ -742,76 +750,27 @@
 def _allowstream(ui):
     return ui.configbool('server', 'uncompressed', True, untrusted=True)
 
-def _walkstreamfiles(repo):
-    # this is it's own function so extensions can override it
-    return repo.store.walk()
-
 @wireprotocommand('stream_out')
 def stream(repo, proto):
     '''If the server supports streaming clone, it advertises the "stream"
     capability with a value representing the version and flags of the repo
     it is serving. Client checks to see if it understands the format.
-
-    The format is simple: the server writes out a line with the amount
-    of files, then the total amount of bytes to be transferred (separated
-    by a space). Then, for each file, the server first writes the filename
-    and file size (separated by the null character), then the file contents.
     '''
-
     if not _allowstream(repo.ui):
         return '1\n'
 
-    entries = []
-    total_bytes = 0
-    try:
-        # get consistent snapshot of repo, lock during scan
-        lock = repo.lock()
-        try:
-            repo.ui.debug('scanning\n')
-            for name, ename, size in _walkstreamfiles(repo):
-                if size:
-                    entries.append((name, size))
-                    total_bytes += size
-        finally:
-            lock.release()
-    except error.LockError:
-        return '2\n' # error: 2
-
-    def streamer(repo, entries, total):
-        '''stream out all metadata files in repository.'''
-        yield '0\n' # success
-        repo.ui.debug('%d files, %d bytes to transfer\n' %
-                      (len(entries), total_bytes))
-        yield '%d %d\n' % (len(entries), total_bytes)
+    def getstream(it):
+        yield '0\n'
+        for chunk in it:
+            yield chunk
 
-        sopener = repo.svfs
-        oldaudit = sopener.mustaudit
-        debugflag = repo.ui.debugflag
-        sopener.mustaudit = False
-
-        try:
-            for name, size in entries:
-                if debugflag:
-                    repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
-                # partially encode name over the wire for backwards compat
-                yield '%s\0%d\n' % (store.encodedir(name), size)
-                if size <= 65536:
-                    fp = sopener(name)
-                    try:
-                        data = fp.read(size)
-                    finally:
-                        fp.close()
-                    yield data
-                else:
-                    for chunk in util.filechunkiter(sopener(name), limit=size):
-                        yield chunk
-        # replace with "finally:" when support for python 2.4 has been dropped
-        except Exception:
-            sopener.mustaudit = oldaudit
-            raise
-        sopener.mustaudit = oldaudit
-
-    return streamres(streamer(repo, entries, total_bytes))
+    try:
+        # LockError may be raised before the first result is yielded. Don't
+        # emit output until we're sure we got the lock successfully.
+        it = exchange.generatestreamclone(repo)
+        return streamres(getstream(it))
+    except error.LockError:
+        return '2\n'
 
 @wireprotocommand('unbundle', 'heads')
 def unbundle(repo, proto, heads):
@@ -842,7 +801,7 @@
             fp.close()
             os.unlink(tempname)
 
-    except (error.BundleValueError, util.Abort, error.PushRaced), exc:
+    except (error.BundleValueError, util.Abort, error.PushRaced) as exc:
         # handle non-bundle2 case first
         if not getattr(exc, 'duringunbundle2', False):
             try:
@@ -861,20 +820,40 @@
         for out in getattr(exc, '_bundle2salvagedoutput', ()):
             bundler.addpart(out)
         try:
-            raise
-        except error.BundleValueError, exc:
+            try:
+                raise
+            except error.PushkeyFailed as exc:
+                # check client caps
+                remotecaps = getattr(exc, '_replycaps', None)
+                if (remotecaps is not None
+                        and 'pushkey' not in remotecaps.get('error', ())):
+                    # no support remote side, fallback to Abort handler.
+                    raise
+                part = bundler.newpart('error:pushkey')
+                part.addparam('in-reply-to', exc.partid)
+                if exc.namespace is not None:
+                    part.addparam('namespace', exc.namespace, mandatory=False)
+                if exc.key is not None:
+                    part.addparam('key', exc.key, mandatory=False)
+                if exc.new is not None:
+                    part.addparam('new', exc.new, mandatory=False)
+                if exc.old is not None:
+                    part.addparam('old', exc.old, mandatory=False)
+                if exc.ret is not None:
+                    part.addparam('ret', exc.ret, mandatory=False)
+        except error.BundleValueError as exc:
             errpart = bundler.newpart('error:unsupportedcontent')
             if exc.parttype is not None:
                 errpart.addparam('parttype', exc.parttype)
             if exc.params:
                 errpart.addparam('params', '\0'.join(exc.params))
-        except util.Abort, exc:
+        except util.Abort as exc:
             manargs = [('message', str(exc))]
             advargs = []
             if exc.hint is not None:
                 advargs.append(('hint', exc.hint))
             bundler.addpart(bundle2.bundlepart('error:abort',
                                                manargs, advargs))
-        except error.PushRaced, exc:
+        except error.PushRaced as exc:
             bundler.newpart('error:pushraced', [('message', str(exc))])
         return streamres(bundler.getchunks())
--- a/mercurial/worker.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/mercurial/worker.py	Sat Jul 18 17:32:38 2015 -0500
@@ -101,7 +101,7 @@
         for p in pids:
             try:
                 os.kill(p, signal.SIGTERM)
-            except OSError, err:
+            except OSError as err:
                 if err.errno != errno.ESRCH:
                     raise
     def waitforworkers():
--- a/setup.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/setup.py	Sat Jul 18 17:32:38 2015 -0500
@@ -5,8 +5,8 @@
 # 'python setup.py --help' for more options
 
 import sys, platform
-if getattr(sys, 'version_info', (0, 0, 0)) < (2, 4, 0, 'final'):
-    raise SystemExit("Mercurial requires Python 2.4 or later.")
+if getattr(sys, 'version_info', (0, 0, 0)) < (2, 6, 0, 'final'):
+    raise SystemExit("Mercurial requires Python 2.6 or later.")
 
 if sys.version_info[0] >= 3:
     def b(s):
@@ -106,25 +106,24 @@
     tmpdir = tempfile.mkdtemp(prefix='hg-install-')
     devnull = oldstderr = None
     try:
-        try:
-            fname = os.path.join(tmpdir, 'funcname.c')
-            f = open(fname, 'w')
-            f.write('int main(void) {\n')
-            f.write('    %s();\n' % funcname)
-            f.write('}\n')
-            f.close()
-            # Redirect stderr to /dev/null to hide any error messages
-            # from the compiler.
-            # This will have to be changed if we ever have to check
-            # for a function on Windows.
-            devnull = open('/dev/null', 'w')
-            oldstderr = os.dup(sys.stderr.fileno())
-            os.dup2(devnull.fileno(), sys.stderr.fileno())
-            objects = cc.compile([fname], output_dir=tmpdir)
-            cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
-        except Exception:
-            return False
+        fname = os.path.join(tmpdir, 'funcname.c')
+        f = open(fname, 'w')
+        f.write('int main(void) {\n')
+        f.write('    %s();\n' % funcname)
+        f.write('}\n')
+        f.close()
+        # Redirect stderr to /dev/null to hide any error messages
+        # from the compiler.
+        # This will have to be changed if we ever have to check
+        # for a function on Windows.
+        devnull = open('/dev/null', 'w')
+        oldstderr = os.dup(sys.stderr.fileno())
+        os.dup2(devnull.fileno(), sys.stderr.fileno())
+        objects = cc.compile([fname], output_dir=tmpdir)
+        cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
         return True
+    except Exception:
+        return False
     finally:
         if oldstderr is not None:
             os.dup2(oldstderr, sys.stderr.fileno())
@@ -408,11 +407,12 @@
                 # Persist executable bit (apply it to group and other if user
                 # has it)
                 if st[stat.ST_MODE] & stat.S_IXUSR:
-                    setmode = 0755
+                    setmode = int('0755', 8)
                 else:
-                    setmode = 0644
-                os.chmod(dst, (stat.S_IMODE(st[stat.ST_MODE]) & ~0777) |
-                         setmode)
+                    setmode = int('0644', 8)
+                m = stat.S_IMODE(st[stat.ST_MODE])
+                m = (m & ~int('0777', 8)) | setmode
+                os.chmod(dst, m)
         file_util.copy_file = copyfileandsetmode
         try:
             install_lib.run(self)
@@ -483,6 +483,11 @@
 
 common_depends = ['mercurial/util.h']
 
+osutil_ldflags = []
+
+if sys.platform == 'darwin':
+    osutil_ldflags += ['-framework', 'ApplicationServices']
+
 extmodules = [
     Extension('mercurial.base85', ['mercurial/base85.c'],
               depends=common_depends),
@@ -497,21 +502,11 @@
                                     'mercurial/parsers.c',
                                     'mercurial/pathencode.c'],
               depends=common_depends),
+    Extension('mercurial.osutil', ['mercurial/osutil.c'],
+              extra_link_args=osutil_ldflags,
+              depends=common_depends),
     ]
 
-osutil_ldflags = []
-
-if sys.platform == 'darwin':
-    osutil_ldflags += ['-framework', 'ApplicationServices']
-
-# disable osutil.c under windows + python 2.4 (issue1364)
-if sys.platform == 'win32' and sys.version_info < (2, 5, 0, 'final'):
-    pymodules.append('mercurial.pure.osutil')
-else:
-    extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c'],
-                                extra_link_args=osutil_ldflags,
-                                depends=common_depends))
-
 try:
     from distutils import cygwinccompiler
 
@@ -562,6 +557,8 @@
          'product_version':version}]
     # sub command of 'build' because 'py2exe' does not handle sub_commands
     build.sub_commands.insert(0, ('build_hgextindex', None))
+    # put dlls in sub directory so that they won't pollute PATH
+    extra['zipfile'] = 'lib/library.zip'
 
 if os.name == 'nt':
     # Windows binary file versions for exe/dll files must have the
@@ -572,6 +569,8 @@
     version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[0].splitlines()
     if version:
         version = version[0]
+        if sys.version_info[0] == 3:
+            version = version.decode('utf-8')
         xcode4 = (version.startswith('Xcode') and
                   StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
         xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/fakedirstatewritetime.py	Sat Jul 18 17:32:38 2015 -0500
@@ -0,0 +1,58 @@
+# extension to emulate invoking 'dirstate.write()' at the time
+# specified by '[fakedirstatewritetime] fakenow', only when
+# 'dirstate.write()' is invoked via functions below:
+#
+#   - 'workingctx._checklookup()' (= 'repo.status()')
+#   - 'committablectx.markcommitted()'
+
+from mercurial import context, extensions, parsers, util
+
+def pack_dirstate(fakenow, orig, dmap, copymap, pl, now):
+    # execute what original parsers.pack_dirstate should do actually
+    # for consistency
+    actualnow = int(now)
+    for f, e in dmap.iteritems():
+        if e[0] == 'n' and e[3] == actualnow:
+            e = parsers.dirstatetuple(e[0], e[1], e[2], -1)
+            dmap[f] = e
+
+    return orig(dmap, copymap, pl, fakenow)
+
+def fakewrite(ui, func):
+    # fake "now" of 'pack_dirstate' only if it is invoked while 'func'
+
+    fakenow = ui.config('fakedirstatewritetime', 'fakenow')
+    if not fakenow:
+        # Execute original one, if fakenow isn't configured. This is
+        # useful to prevent subrepos from executing replaced one,
+        # because replacing 'parsers.pack_dirstate' is also effective
+        # in subrepos.
+        return func()
+
+    # parsing 'fakenow' in YYYYmmddHHMM format makes comparison between
+    # 'fakenow' value and 'touch -t YYYYmmddHHMM' argument easy
+    timestamp = util.parsedate(fakenow, ['%Y%m%d%H%M'])[0]
+    fakenow = float(timestamp)
+
+    orig_pack_dirstate = parsers.pack_dirstate
+    wrapper = lambda *args: pack_dirstate(fakenow, orig_pack_dirstate, *args)
+
+    parsers.pack_dirstate = wrapper
+    try:
+        return func()
+    finally:
+        parsers.pack_dirstate = orig_pack_dirstate
+
+def _checklookup(orig, workingctx, files):
+    ui = workingctx.repo().ui
+    return fakewrite(ui, lambda : orig(workingctx, files))
+
+def markcommitted(orig, committablectx, node):
+    ui = committablectx.repo().ui
+    return fakewrite(ui, lambda : orig(committablectx, node))
+
+def extsetup(ui):
+    extensions.wrapfunction(context.workingctx, '_checklookup',
+                            _checklookup)
+    extensions.wrapfunction(context.committablectx, 'markcommitted',
+                            markcommitted)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/fakepatchtime.py	Sat Jul 18 17:32:38 2015 -0500
@@ -0,0 +1,26 @@
+# extension to emulate invoking 'patch.internalpatch()' at the time
+# specified by '[fakepatchtime] fakenow'
+
+from mercurial import extensions, patch as patchmod, util
+
+def internalpatch(orig, ui, repo, patchobj, strip,
+                  prefix='', files=None,
+                  eolmode='strict', similarity=0):
+    if files is None:
+        files = set()
+    r = orig(ui, repo, patchobj, strip,
+             prefix=prefix, files=files,
+             eolmode=eolmode, similarity=similarity)
+
+    fakenow = ui.config('fakepatchtime', 'fakenow')
+    if fakenow:
+        # parsing 'fakenow' in YYYYmmddHHMM format makes comparison between
+        # 'fakenow' value and 'touch -t YYYYmmddHHMM' argument easy
+        fakenow = util.parsedate(fakenow, ['%Y%m%d%H%M'])[0]
+        for f in files:
+            repo.wvfs.utime(f, (fakenow, fakenow))
+
+    return r
+
+def extsetup(ui):
+    extensions.wrapfunction(patchmod, 'internalpatch', internalpatch)
--- a/tests/get-with-headers.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/get-with-headers.py	Sat Jul 18 17:32:38 2015 -0500
@@ -33,8 +33,6 @@
     sys.argv.remove('--json')
     formatjson = True
 
-reasons = {'Not modified': 'Not Modified'} # python 2.4
-
 tag = None
 def request(host, path, show):
     assert not path.startswith('/'), path
@@ -46,7 +44,7 @@
     conn = httplib.HTTPConnection(host)
     conn.request("GET", '/' + path, None, headers)
     response = conn.getresponse()
-    print response.status, reasons.get(response.reason, response.reason)
+    print response.status, response.reason
     if show[:1] == ['-']:
         show = sorted(h for h, v in response.getheaders()
                       if h.lower() not in show)
--- a/tests/heredoctest.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/heredoctest.py	Sat Jul 18 17:32:38 2015 -0500
@@ -5,7 +5,7 @@
 while lines:
     l = lines.pop(0)
     if l.startswith('SALT'):
-        print l[:-1]
+        print(l[:-1])
     elif l.startswith('>>> '):
         snippet = l[4:]
         while lines and lines[0].startswith('... '):
@@ -13,6 +13,6 @@
             snippet += l[4:]
         c = compile(snippet, '<heredoc>', 'single')
         try:
-            exec c in globalvars
-        except Exception, inst:
-            print repr(inst)
+            exec(c, globalvars)
+        except Exception as inst:
+            print(repr(inst))
--- a/tests/hghave	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/hghave	Sat Jul 18 17:32:38 2015 -0500
@@ -4,7 +4,7 @@
 prefixed with "no-", the absence of feature is tested.
 """
 import optparse
-import sys
+import os, sys
 import hghave
 
 checks = hghave.checks
@@ -33,8 +33,30 @@
 parser.add_option("-q", "--quiet", action="store_true",
                   help="check features silently")
 
+def _loadaddon(quiet):
+    if 'TESTDIR' in os.environ:
+        # loading from '.' isn't needed, because `hghave` should be
+        # running at TESTTMP in this case
+        path = os.environ['TESTDIR']
+    else:
+        path = '.'
+
+    if not os.path.exists(os.path.join(path, 'hghaveaddon.py')):
+        return
+
+    sys.path.insert(0, path)
+    try:
+        import hghaveaddon
+    except BaseException, inst:
+        if not quiet:
+            sys.stderr.write('failed to import hghaveaddon.py from %r: %s\n'
+                             % (path, inst))
+        sys.exit(2)
+    sys.path.pop(0)
+
 if __name__ == '__main__':
     options, args = parser.parse_args()
+    _loadaddon(options.quiet)
     if options.list_features:
         list_features()
         sys.exit(0)
--- a/tests/hghave.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/hghave.py	Sat Jul 18 17:32:38 2015 -0500
@@ -88,10 +88,10 @@
         fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
         try:
             os.close(fh)
-            m = os.stat(fn).st_mode & 0777
+            m = os.stat(fn).st_mode & 0o777
             new_file_has_exec = m & EXECFLAGS
             os.chmod(fn, m ^ EXECFLAGS)
-            exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
+            exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0o777) == m)
         finally:
             os.unlink(fn)
     except (IOError, OSError):
@@ -225,12 +225,11 @@
     os.close(fh)
     name = tempfile.mktemp(dir='.', prefix=tempprefix)
     try:
-        try:
-            util.oslink(fn, name)
-            os.unlink(name)
-            return True
-        except OSError:
-            return False
+        util.oslink(fn, name)
+        os.unlink(name)
+        return True
+    except OSError:
+        return False
     finally:
         os.unlink(fn)
 
@@ -247,13 +246,13 @@
     d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
     try:
         fname = os.path.join(d, 'foo')
-        for umask in (077, 007, 022):
+        for umask in (0o77, 0o07, 0o22):
             os.umask(umask)
             f = open(fname, 'w')
             f.close()
             mode = os.stat(fname).st_mode
             os.unlink(fname)
-            if mode & 0777 != ~umask & 0666:
+            if mode & 0o777 != ~umask & 0o666:
                 return False
         return True
     finally:
@@ -282,10 +281,6 @@
     except ImportError:
         return False
 
-@check("python243", "python >= 2.4.3")
-def has_python243():
-    return sys.version_info >= (2, 4, 3)
-
 @check("json", "some json module available")
 def has_json():
     try:
@@ -320,6 +315,15 @@
     except ImportError:
         return False
 
+@check("sslcontext", "python >= 2.7.9 ssl")
+def has_sslcontext():
+    try:
+        import ssl
+        ssl.SSLContext
+        return True
+    except (ImportError, AttributeError):
+        return False
+
 @check("defaultcacerts", "can verify SSL certs by system's CA certs store")
 def has_defaultcacerts():
     from mercurial import sslutil
--- a/tests/killdaemons.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/killdaemons.py	Sat Jul 18 17:32:38 2015 -0500
@@ -64,7 +64,7 @@
                 os.kill(pid, 0)
             logfn('# Daemon process %d is stuck - really killing it' % pid)
             os.kill(pid, signal.SIGKILL)
-        except OSError, err:
+        except OSError as err:
             if err.errno != errno.ESRCH:
                 raise
 
@@ -87,5 +87,9 @@
         pass
 
 if __name__ == '__main__':
-    path, = sys.argv[1:]
+    if len(sys.argv) > 1:
+        path, = sys.argv[1:]
+    else:
+        path = os.environ["DAEMON_PIDS"]
+
     killdaemons(path)
--- a/tests/md5sum.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/md5sum.py	Sat Jul 18 17:32:38 2015 -0500
@@ -23,7 +23,7 @@
 for filename in sys.argv[1:]:
     try:
         fp = open(filename, 'rb')
-    except IOError, msg:
+    except IOError as msg:
         sys.stderr.write('%s: Can\'t open: %s\n' % (filename, msg))
         sys.exit(1)
 
@@ -34,7 +34,7 @@
             if not data:
                 break
             m.update(data)
-    except IOError, msg:
+    except IOError as msg:
         sys.stderr.write('%s: I/O error: %s\n' % (filename, msg))
         sys.exit(1)
     sys.stdout.write('%s  %s\n' % (m.hexdigest(), filename))
--- a/tests/printenv.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/printenv.py	Sat Jul 18 17:32:38 2015 -0500
@@ -1,3 +1,5 @@
+#!/usr/bin/env python
+#
 # simple script to be used in hooks
 #
 # put something like this in the repo .hg/hgrc:
--- a/tests/readlink.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/readlink.py	Sat Jul 18 17:32:38 2015 -0500
@@ -5,7 +5,7 @@
 for f in sys.argv[1:]:
     try:
         print f, '->', os.readlink(f)
-    except OSError, err:
+    except OSError as err:
         if err.errno != errno.EINVAL:
             raise
         print f, 'not a symlink'
--- a/tests/run-tests.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/run-tests.py	Sat Jul 18 17:32:38 2015 -0500
@@ -41,6 +41,8 @@
 # completes fairly quickly, includes both shell and Python scripts, and
 # includes some scripts that run daemon processes.)
 
+from __future__ import print_function
+
 from distutils import version
 import difflib
 import errno
@@ -49,6 +51,7 @@
 import shutil
 import subprocess
 import signal
+import socket
 import sys
 import tempfile
 import time
@@ -56,10 +59,15 @@
 import re
 import threading
 import killdaemons as killmod
-import Queue as queue
+try:
+    import Queue as queue
+except ImportError:
+    import queue
 from xml.dom import minidom
 import unittest
 
+osenvironb = getattr(os, 'environb', os.environ)
+
 try:
     import json
 except ImportError:
@@ -70,14 +78,46 @@
 
 processlock = threading.Lock()
 
-# subprocess._cleanup can race with any Popen.wait or Popen.poll on py24
-# http://bugs.python.org/issue1731717 for details. We shouldn't be producing
-# zombies but it's pretty harmless even if we do.
-if sys.version_info < (2, 5):
-    subprocess._cleanup = lambda: None
+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):
+        return p.encode('utf-8')
+
+    def _strpath(p):
+        return p.decode('utf-8')
+
+elif sys.version_info >= (3, 0, 0):
+    print('%s is only supported on Python 3.5+ and 2.6-2.7, not %s' %
+          (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3])))
+    sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
+else:
+    PYTHON3 = False
 
+    # In python 2.x, path operations are generally done using
+    # bytestrings by default, so we don't have to do any extra
+    # fiddling there. We define the wrapper functions anyway just to
+    # help keep code consistent between platforms.
+    def _bytespath(p):
+        return p
+
+    _strpath = _bytespath
+
+# For Windows support
 wifexited = getattr(os, "WIFEXITED", lambda x: False)
 
+def checkportisavailable(port):
+    """return true if a port seems free to bind on localhost"""
+    try:
+        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        s.bind(('localhost', port))
+        s.close()
+        return True
+    except socket.error as exc:
+        if not exc.errno == errno.EADDRINUSE:
+            raise
+        return False
+
 closefds = os.name == 'posix'
 def Popen4(cmd, wd, timeout, env=None):
     processlock.acquire()
@@ -104,10 +144,10 @@
 
     return p
 
-PYTHON = sys.executable.replace('\\', '/')
-IMPL_PATH = 'PYTHONPATH'
+PYTHON = _bytespath(sys.executable.replace('\\', '/'))
+IMPL_PATH = b'PYTHONPATH'
 if 'java' in sys.platform:
-    IMPL_PATH = 'JYTHONPATH'
+    IMPL_PATH = b'JYTHONPATH'
 
 defaults = {
     'jobs': ('HGTEST_JOBS', 1),
@@ -122,15 +162,15 @@
         try:
             path = os.path.expanduser(os.path.expandvars(filename))
             f = open(path, "rb")
-        except IOError, err:
+        except IOError as err:
             if err.errno != errno.ENOENT:
                 raise
             if warn:
-                print "warning: no such %s file: %s" % (listtype, filename)
+                print("warning: no such %s file: %s" % (listtype, filename))
             continue
 
         for line in f.readlines():
-            line = line.split('#', 1)[0].strip()
+            line = line.split(b'#', 1)[0].strip()
             if line:
                 entries[line] = filename
 
@@ -217,6 +257,8 @@
                       help='set the given config opt in the test hgrc')
     parser.add_option('--random', action="store_true",
                       help='run tests in random order')
+    parser.add_option('--profile-runner', action='store_true',
+                      help='run statprof on run-tests')
 
     for option, (envvar, default) in defaults.items():
         defaults[option] = type(default)(os.environ.get(envvar, default))
@@ -240,8 +282,8 @@
         if not os.path.basename(options.with_hg) == 'hg':
             sys.stderr.write('warning: --with-hg should specify an hg script\n')
     if options.local:
-        testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
-        hgbin = os.path.join(os.path.dirname(testdir), 'hg')
+        testdir = os.path.dirname(_bytespath(os.path.realpath(sys.argv[0])))
+        hgbin = os.path.join(os.path.dirname(testdir), b'hg')
         if os.name != 'nt' and not os.access(hgbin, os.X_OK):
             parser.error('--local specified, but %r not found or not executable'
                          % hgbin)
@@ -283,8 +325,9 @@
                 'warning: --timeout option ignored with --debug\n')
         options.timeout = 0
     if options.py3k_warnings:
-        if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
-            parser.error('--py3k-warnings can only be used on Python 2.6+')
+        if PYTHON3:
+            parser.error(
+                '--py3k-warnings can only be used on Python 2.6 and 2.7')
     if options.blacklist:
         options.blacklist = parselistfiles(options.blacklist, 'blacklist')
     if options.whitelist:
@@ -301,17 +344,22 @@
     shutil.copy(src, dst)
     os.remove(src)
 
+_unified_diff = difflib.unified_diff
+if PYTHON3:
+    import functools
+    _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
+
 def getdiff(expected, output, ref, err):
     servefail = False
     lines = []
-    for line in difflib.unified_diff(expected, output, ref, err):
-        if line.startswith('+++') or line.startswith('---'):
-            line = line.replace('\\', '/')
-            if line.endswith(' \n'):
-                line = line[:-2] + '\n'
+    for line in _unified_diff(expected, output, ref, err):
+        if line.startswith(b'+++') or line.startswith(b'---'):
+            line = line.replace(b'\\', b'/')
+            if line.endswith(b' \n'):
+                line = line[:-2] + b'\n'
         lines.append(line)
         if not servefail and line.startswith(
-                             '+  abort: child process failed to start'):
+                             b'+  abort: child process failed to start'):
             servefail = True
 
     return servefail, lines
@@ -326,7 +374,7 @@
 
 # Bytes that break XML even in a CDATA block: control characters 0-31
 # sans \t, \n and \r
-CDATA_EVIL = re.compile(r"[\000-\010\013\014\016-\037]")
+CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]")
 
 def cdatasafe(data):
     """Make a string safe to include in a CDATA block.
@@ -336,21 +384,20 @@
     replaces illegal bytes with ? and adds a space between the ]] so
     that it won't break the CDATA block.
     """
-    return CDATA_EVIL.sub('?', data).replace(']]>', '] ]>')
+    return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>')
 
 def log(*msg):
     """Log something to stdout.
 
     Arguments are strings to print.
     """
-    iolock.acquire()
-    if verbose:
-        print verbose,
-    for m in msg:
-        print m,
-    print
-    sys.stdout.flush()
-    iolock.release()
+    with iolock:
+        if verbose:
+            print(verbose, end=' ')
+        for m in msg:
+            print(m, end=' ')
+        print()
+        sys.stdout.flush()
 
 def terminate(proc):
     """Terminate subprocess (with fallback for Python versions < 2.6)"""
@@ -408,11 +455,11 @@
 
         shell is the shell to execute tests in.
         """
-
         self.path = path
-        self.name = os.path.basename(path)
+        self.bname = os.path.basename(path)
+        self.name = _strpath(self.bname)
         self._testdir = os.path.dirname(path)
-        self.errpath = os.path.join(self._testdir, '%s.err' % self.name)
+        self.errpath = os.path.join(self._testdir, b'%s.err' % self.bname)
 
         self._threadtmp = tmpdir
         self._keeptmpdir = keeptmpdir
@@ -421,7 +468,7 @@
         self._startport = startport
         self._extraconfigopts = extraconfigopts or []
         self._py3kwarnings = py3kwarnings
-        self._shell = shell
+        self._shell = _bytespath(shell)
 
         self._aborted = False
         self._daemonpids = []
@@ -442,6 +489,11 @@
         else:
             self._refout = []
 
+    # needed to get base class __repr__ running
+    @property
+    def _testMethodName(self):
+        return self.name
+
     def __str__(self):
         return self.name
 
@@ -457,7 +509,7 @@
 
         try:
             os.mkdir(self._threadtmp)
-        except OSError, e:
+        except OSError as e:
             if e.errno != errno.EEXIST:
                 raise
 
@@ -469,7 +521,7 @@
         if os.path.exists(self.errpath):
             try:
                 os.remove(self.errpath)
-            except OSError, e:
+            except OSError as e:
                 # We might have raced another test to clean up a .err
                 # file, so ignore ENOENT when removing a previous .err
                 # file.
@@ -499,20 +551,20 @@
             except KeyboardInterrupt:
                 self._aborted = True
                 raise
-            except SkipTest, e:
+            except SkipTest as e:
                 result.addSkip(self, str(e))
                 # The base class will have already counted this as a
                 # test we "ran", but we want to exclude skipped tests
                 # from those we count towards those run.
                 result.testsRun -= 1
-            except IgnoreTest, e:
+            except IgnoreTest as e:
                 result.addIgnore(self, str(e))
                 # As with skips, ignores also should be excluded from
                 # the number of tests executed.
                 result.testsRun -= 1
-            except WarnTest, e:
+            except WarnTest as e:
                 result.addWarn(self, str(e))
-            except self.failureException, e:
+            except self.failureException as e:
                 # This differs from unittest in that we don't capture
                 # the stack trace. This is for historical reasons and
                 # this decision could be revisited in the future,
@@ -620,7 +672,7 @@
                 f.write(line)
             f.close()
 
-        vlog("# Ret was:", self._ret)
+        vlog("# Ret was:", self._ret, '(%s)' % self.name)
 
     def _run(self, env):
         # This should be implemented in child classes to run tests.
@@ -638,20 +690,20 @@
         occur.
         """
         r = [
-            (r':%s\b' % self._startport, ':$HGPORT'),
-            (r':%s\b' % (self._startport + 1), ':$HGPORT1'),
-            (r':%s\b' % (self._startport + 2), ':$HGPORT2'),
-            (r'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$',
-             r'\1 (glob)'),
+            (br':%d\b' % self._startport, b':$HGPORT'),
+            (br':%d\b' % (self._startport + 1), b':$HGPORT1'),
+            (br':%d\b' % (self._startport + 2), b':$HGPORT2'),
+            (br'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$',
+             br'\1 (glob)'),
             ]
 
         if os.name == 'nt':
             r.append(
-                (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
-                    c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
-                    for c in self._testtmp), '$TESTTMP'))
+                (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
+                    c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
+                    for c in self._testtmp), b'$TESTTMP'))
         else:
-            r.append((re.escape(self._testtmp), '$TESTTMP'))
+            r.append((re.escape(self._testtmp), b'$TESTTMP'))
 
         return r
 
@@ -663,8 +715,8 @@
         env["HGPORT"] = str(self._startport)
         env["HGPORT1"] = str(self._startport + 1)
         env["HGPORT2"] = str(self._startport + 2)
-        env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
-        env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
+        env["HGRCPATH"] = os.path.join(self._threadtmp, b'.hgrc')
+        env["DAEMON_PIDS"] = os.path.join(self._threadtmp, b'daemon.pids')
         env["HGEDITOR"] = ('"' + sys.executable + '"'
                            + ' -c "import sys; sys.exit(0)"')
         env["HGMERGE"] = "internal:merge"
@@ -695,27 +747,27 @@
     def _createhgrc(self, path):
         """Create an hgrc file for this test."""
         hgrc = open(path, 'wb')
-        hgrc.write('[ui]\n')
-        hgrc.write('slash = True\n')
-        hgrc.write('interactive = False\n')
-        hgrc.write('mergemarkers = detailed\n')
-        hgrc.write('promptecho = True\n')
-        hgrc.write('[defaults]\n')
-        hgrc.write('backout = -d "0 0"\n')
-        hgrc.write('commit = -d "0 0"\n')
-        hgrc.write('shelve = --date "0 0"\n')
-        hgrc.write('tag = -d "0 0"\n')
-        hgrc.write('[devel]\n')
-        hgrc.write('all = true\n')
-        hgrc.write('[largefiles]\n')
-        hgrc.write('usercache = %s\n' %
-                   (os.path.join(self._testtmp, '.cache/largefiles')))
+        hgrc.write(b'[ui]\n')
+        hgrc.write(b'slash = True\n')
+        hgrc.write(b'interactive = False\n')
+        hgrc.write(b'mergemarkers = detailed\n')
+        hgrc.write(b'promptecho = True\n')
+        hgrc.write(b'[defaults]\n')
+        hgrc.write(b'backout = -d "0 0"\n')
+        hgrc.write(b'commit = -d "0 0"\n')
+        hgrc.write(b'shelve = --date "0 0"\n')
+        hgrc.write(b'tag = -d "0 0"\n')
+        hgrc.write(b'[devel]\n')
+        hgrc.write(b'all-warnings = true\n')
+        hgrc.write(b'[largefiles]\n')
+        hgrc.write(b'usercache = %s\n' %
+                   (os.path.join(self._testtmp, b'.cache/largefiles')))
 
         for opt in self._extraconfigopts:
             section, key = opt.split('.', 1)
             assert '=' in key, ('extra config opt %s must '
                                 'have an = for assignment' % opt)
-            hgrc.write('[%s]\n%s\n' % (section, key))
+            hgrc.write(b'[%s]\n%s\n' % (section, key))
         hgrc.close()
 
     def fail(self, msg):
@@ -777,11 +829,11 @@
 
     @property
     def refpath(self):
-        return os.path.join(self._testdir, '%s.out' % self.name)
+        return os.path.join(self._testdir, b'%s.out' % self.bname)
 
     def _run(self, env):
-        py3kswitch = self._py3kwarnings and ' -3' or ''
-        cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self.path)
+        py3kswitch = self._py3kwarnings and b' -3' or b''
+        cmd = b'%s%s "%s"' % (PYTHON, py3kswitch, self.path)
         vlog("# Running", cmd)
         normalizenewlines = os.name == 'nt'
         result = self._runcommand(cmd, env,
@@ -795,25 +847,29 @@
 # Windows, but check-code.py wants a glob on these lines unconditionally.  Don't
 # warn if that is the case for anything matching these lines.
 checkcodeglobpats = [
-    re.compile(r'^pushing to \$TESTTMP/.*[^)]$'),
-    re.compile(r'^moving \S+/.*[^)]$'),
-    re.compile(r'^pulling from \$TESTTMP/.*[^)]$')
+    re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
+    re.compile(br'^moving \S+/.*[^)]$'),
+    re.compile(br'^pulling from \$TESTTMP/.*[^)]$')
 ]
 
+bchr = chr
+if PYTHON3:
+    bchr = lambda x: bytes([x])
+
 class TTest(Test):
     """A "t test" is a test backed by a .t file."""
 
     SKIPPED_PREFIX = 'skipped: '
     FAILED_PREFIX = 'hghave check failed: '
-    NEEDESCAPE = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
+    NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
 
-    ESCAPESUB = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
-    ESCAPEMAP = dict((chr(i), r'\x%02x' % i) for i in range(256))
-    ESCAPEMAP.update({'\\': '\\\\', '\r': r'\r'})
+    ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
+    ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256))
+    ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
 
     @property
     def refpath(self):
-        return os.path.join(self._testdir, self.name)
+        return os.path.join(self._testdir, self.bname)
 
     def _run(self, env):
         f = open(self.path, 'rb')
@@ -823,13 +879,13 @@
         salt, script, after, expected = self._parsetest(lines)
 
         # Write out the generated script.
-        fname = '%s.sh' % self._testtmp
+        fname = b'%s.sh' % self._testtmp
         f = open(fname, 'wb')
         for l in script:
             f.write(l)
         f.close()
 
-        cmd = '%s "%s"' % (self._shell, fname)
+        cmd = b'%s "%s"' % (self._shell, fname)
         vlog("# Running", cmd)
 
         exitcode, output = self._runcommand(cmd, env)
@@ -846,16 +902,17 @@
 
     def _hghave(self, reqs):
         # TODO do something smarter when all other uses of hghave are gone.
-        tdir = self._testdir.replace('\\', '/')
-        proc = Popen4('%s -c "%s/hghave %s"' %
-                      (self._shell, tdir, ' '.join(reqs)),
+        runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
+        tdir = runtestdir.replace(b'\\', b'/')
+        proc = Popen4(b'%s -c "%s/hghave %s"' %
+                      (self._shell, tdir, b' '.join(reqs)),
                       self._testtmp, 0, self._getenv())
         stdout, stderr = proc.communicate()
         ret = proc.wait()
         if wifexited(ret):
             ret = os.WEXITSTATUS(ret)
         if ret == 2:
-            print stdout
+            print(stdout)
             sys.exit(1)
 
         return ret == 0
@@ -864,12 +921,12 @@
         # We generate a shell script which outputs unique markers to line
         # up script results with our source. These markers include input
         # line number and the last return code.
-        salt = "SALT" + str(time.time())
+        salt = b"SALT%d" % time.time()
         def addsalt(line, inpython):
             if inpython:
-                script.append('%s %d 0\n' % (salt, line))
+                script.append(b'%s %d 0\n' % (salt, line))
             else:
-                script.append('echo %s %s $?\n' % (salt, line))
+                script.append(b'echo %s %d $?\n' % (salt, line))
 
         script = []
 
@@ -892,42 +949,42 @@
         inpython = False
 
         if self._debug:
-            script.append('set -x\n')
+            script.append(b'set -x\n')
         if os.getenv('MSYSTEM'):
-            script.append('alias pwd="pwd -W"\n')
+            script.append(b'alias pwd="pwd -W"\n')
 
         for n, l in enumerate(lines):
-            if not l.endswith('\n'):
-                l += '\n'
-            if l.startswith('#require'):
+            if not l.endswith(b'\n'):
+                l += b'\n'
+            if l.startswith(b'#require'):
                 lsplit = l.split()
-                if len(lsplit) < 2 or lsplit[0] != '#require':
+                if len(lsplit) < 2 or lsplit[0] != b'#require':
                     after.setdefault(pos, []).append('  !!! invalid #require\n')
                 if not self._hghave(lsplit[1:]):
-                    script = ["exit 80\n"]
+                    script = [b"exit 80\n"]
                     break
                 after.setdefault(pos, []).append(l)
-            elif l.startswith('#if'):
+            elif l.startswith(b'#if'):
                 lsplit = l.split()
-                if len(lsplit) < 2 or lsplit[0] != '#if':
+                if len(lsplit) < 2 or lsplit[0] != b'#if':
                     after.setdefault(pos, []).append('  !!! invalid #if\n')
                 if skipping is not None:
                     after.setdefault(pos, []).append('  !!! nested #if\n')
                 skipping = not self._hghave(lsplit[1:])
                 after.setdefault(pos, []).append(l)
-            elif l.startswith('#else'):
+            elif l.startswith(b'#else'):
                 if skipping is None:
                     after.setdefault(pos, []).append('  !!! missing #if\n')
                 skipping = not skipping
                 after.setdefault(pos, []).append(l)
-            elif l.startswith('#endif'):
+            elif l.startswith(b'#endif'):
                 if skipping is None:
                     after.setdefault(pos, []).append('  !!! missing #if\n')
                 skipping = None
                 after.setdefault(pos, []).append(l)
             elif skipping:
                 after.setdefault(pos, []).append(l)
-            elif l.startswith('  >>> '): # python inlines
+            elif l.startswith(b'  >>> '): # python inlines
                 after.setdefault(pos, []).append(l)
                 prepos = pos
                 pos = n
@@ -935,39 +992,39 @@
                     # We've just entered a Python block. Add the header.
                     inpython = True
                     addsalt(prepos, False) # Make sure we report the exit code.
-                    script.append('%s -m heredoctest <<EOF\n' % PYTHON)
+                    script.append(b'%s -m heredoctest <<EOF\n' % PYTHON)
                 addsalt(n, True)
                 script.append(l[2:])
-            elif l.startswith('  ... '): # python inlines
+            elif l.startswith(b'  ... '): # python inlines
                 after.setdefault(prepos, []).append(l)
                 script.append(l[2:])
-            elif l.startswith('  $ '): # commands
+            elif l.startswith(b'  $ '): # commands
                 if inpython:
-                    script.append('EOF\n')
+                    script.append(b'EOF\n')
                     inpython = False
                 after.setdefault(pos, []).append(l)
                 prepos = pos
                 pos = n
                 addsalt(n, False)
                 cmd = l[4:].split()
-                if len(cmd) == 2 and cmd[0] == 'cd':
-                    l = '  $ cd %s || exit 1\n' % cmd[1]
+                if len(cmd) == 2 and cmd[0] == b'cd':
+                    l = b'  $ cd %s || exit 1\n' % cmd[1]
                 script.append(l[4:])
-            elif l.startswith('  > '): # continuations
+            elif l.startswith(b'  > '): # continuations
                 after.setdefault(prepos, []).append(l)
                 script.append(l[4:])
-            elif l.startswith('  '): # results
+            elif l.startswith(b'  '): # results
                 # Queue up a list of expected results.
                 expected.setdefault(pos, []).append(l[2:])
             else:
                 if inpython:
-                    script.append('EOF\n')
+                    script.append(b'EOF\n')
                     inpython = False
                 # Non-command/result. Queue up for merged output.
                 after.setdefault(pos, []).append(l)
 
         if inpython:
-            script.append('EOF\n')
+            script.append(b'EOF\n')
         if skipping is not None:
             after.setdefault(pos, []).append('  !!! missing #endif\n')
         addsalt(n + 1, False)
@@ -987,9 +1044,9 @@
             if salt in l:
                 lout, lcmd = l.split(salt, 1)
 
-            if lout:
-                if not lout.endswith('\n'):
-                    lout += ' (no-eol)\n'
+            while lout:
+                if not lout.endswith(b'\n'):
+                    lout += b' (no-eol)\n'
 
                 # Find the expected output at the current position.
                 el = None
@@ -1004,26 +1061,38 @@
                     elif r == '-glob':
                         lout = ''.join(el.rsplit(' (glob)', 1))
                         r = '' # Warn only this line.
+                    elif r == "retry":
+                        postout.append(b'  ' + el)
+                        continue
                     else:
                         log('\ninfo, unknown linematch result: %r\n' % r)
                         r = False
                 if r:
-                    postout.append('  ' + el)
+                    postout.append(b'  ' + el)
                 else:
                     if self.NEEDESCAPE(lout):
-                        lout = TTest._stringescape('%s (esc)\n' %
-                                                   lout.rstrip('\n'))
-                    postout.append('  ' + lout) # Let diff deal with it.
+                        lout = TTest._stringescape(b'%s (esc)\n' %
+                                                   lout.rstrip(b'\n'))
+                    postout.append(b'  ' + lout) # Let diff deal with it.
                     if r != '': # If line failed.
                         warnonly = 3 # for sure not
                     elif warnonly == 1: # Is "not yet" and line is warn only.
                         warnonly = 2 # Yes do warn.
+                break
+
+            # clean up any optional leftovers
+            while expected.get(pos, None):
+                el = expected[pos].pop(0)
+                if not el.endswith(" (?)\n"):
+                    expected[pos].insert(0, el)
+                    break
+                postout.append(b'  ' + el)
 
             if lcmd:
                 # Add on last return code.
                 ret = int(lcmd.split()[1])
                 if ret != 0:
-                    postout.append('  [%s]\n' % ret)
+                    postout.append(b'  [%d]\n' % ret)
                 if pos in after:
                     # Merge in non-active test bits.
                     postout += after.pop(pos)
@@ -1042,8 +1111,8 @@
         try:
             # use \Z to ensure that the regex matches to the end of the string
             if os.name == 'nt':
-                return re.match(el + r'\r?\n\Z', l)
-            return re.match(el + r'\n\Z', l)
+                return re.match(el + br'\r?\n\Z', l)
+            return re.match(el + br'\n\Z', l)
         except re.error:
             # el is an invalid regex
             return False
@@ -1052,51 +1121,59 @@
     def globmatch(el, l):
         # The only supported special characters are * and ? plus / which also
         # matches \ on windows. Escaping of these characters is supported.
-        if el + '\n' == l:
+        if el + b'\n' == l:
             if os.altsep:
                 # matching on "/" is not needed for this line
                 for pat in checkcodeglobpats:
                     if pat.match(el):
                         return True
-                return '-glob'
+                return b'-glob'
             return True
         i, n = 0, len(el)
-        res = ''
+        res = b''
         while i < n:
-            c = el[i]
+            c = el[i:i + 1]
             i += 1
-            if c == '\\' and i < n and el[i] in '*?\\/':
+            if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
                 res += el[i - 1:i + 1]
                 i += 1
-            elif c == '*':
-                res += '.*'
-            elif c == '?':
-                res += '.'
-            elif c == '/' and os.altsep:
-                res += '[/\\\\]'
+            elif c == b'*':
+                res += b'.*'
+            elif c == b'?':
+                res += b'.'
+            elif c == b'/' and os.altsep:
+                res += b'[/\\\\]'
             else:
                 res += re.escape(c)
         return TTest.rematch(res, l)
 
     @staticmethod
     def linematch(el, l):
+        retry = False
         if el == l: # perfect match (fast)
             return True
         if el:
-            if el.endswith(" (esc)\n"):
-                el = el[:-7].decode('string-escape') + '\n'
-            if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
+            if el.endswith(" (?)\n"):
+                retry = "retry"
+                el = el[:-5] + "\n"
+            if el.endswith(b" (esc)\n"):
+                if PYTHON3:
+                    el = el[:-7].decode('unicode_escape') + '\n'
+                    el = el.encode('utf-8')
+                else:
+                    el = el[:-7].decode('string-escape') + '\n'
+            if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
                 return True
-            if el.endswith(" (re)\n"):
-                return TTest.rematch(el[:-6], l)
-            if el.endswith(" (glob)\n"):
+            if el.endswith(b" (re)\n"):
+                return TTest.rematch(el[:-6], l) or retry
+            if el.endswith(b" (glob)\n"):
                 # ignore '(glob)' added to l by 'replacements'
-                if l.endswith(" (glob)\n"):
-                    l = l[:-8] + "\n"
+                if l.endswith(b" (glob)\n"):
+                    l = l[:-8] + b"\n"
                 return TTest.globmatch(el[:-8], l)
-            if os.altsep and l.replace('\\', '/') == el:
-                return '+glob'
-        return False
+            if os.altsep and l.replace(b'\\', b'/') == el:
+                return b'+glob'
+        return retry
 
     @staticmethod
     def parsehghaveoutput(lines):
@@ -1160,6 +1237,7 @@
         self.warned = []
 
         self.times = []
+        self._firststarttime =  None
         # Data stored for the benefit of generating xunit reports.
         self.successes = []
         self.faildata = {}
@@ -1170,18 +1248,16 @@
         if self._options.first:
             self.stop()
         else:
-            iolock.acquire()
-            if not self._options.nodiff:
-                self.stream.write('\nERROR: %s output changed\n' % test)
+            with iolock:
+                if not self._options.nodiff:
+                    self.stream.write('\nERROR: %s output changed\n' % test)
 
-            self.stream.write('!')
-            self.stream.flush()
-            iolock.release()
+                self.stream.write('!')
+                self.stream.flush()
 
     def addSuccess(self, test):
-        iolock.acquire()
-        super(TestResult, self).addSuccess(test)
-        iolock.release()
+        with iolock:
+            super(TestResult, self).addSuccess(test)
         self.successes.append(test)
 
     def addError(self, test, err):
@@ -1192,26 +1268,24 @@
     # Polyfill.
     def addSkip(self, test, reason):
         self.skipped.append((test, reason))
-        iolock.acquire()
-        if self.showAll:
-            self.stream.writeln('skipped %s' % reason)
-        else:
-            self.stream.write('s')
-            self.stream.flush()
-        iolock.release()
+        with iolock:
+            if self.showAll:
+                self.stream.writeln('skipped %s' % reason)
+            else:
+                self.stream.write('s')
+                self.stream.flush()
 
     def addIgnore(self, test, reason):
         self.ignored.append((test, reason))
-        iolock.acquire()
-        if self.showAll:
-            self.stream.writeln('ignored %s' % reason)
-        else:
-            if reason != 'not retesting' and reason != "doesn't match keyword":
-                self.stream.write('i')
+        with iolock:
+            if self.showAll:
+                self.stream.writeln('ignored %s' % reason)
             else:
-                self.testsRun += 1
-            self.stream.flush()
-        iolock.release()
+                if reason not in ('not retesting', "doesn't match keyword"):
+                    self.stream.write('i')
+                else:
+                    self.testsRun += 1
+                self.stream.flush()
 
     def addWarn(self, test, reason):
         self.warned.append((test, reason))
@@ -1219,13 +1293,12 @@
         if self._options.first:
             self.stop()
 
-        iolock.acquire()
-        if self.showAll:
-            self.stream.writeln('warned %s' % reason)
-        else:
-            self.stream.write('~')
-            self.stream.flush()
-        iolock.release()
+        with iolock:
+            if self.showAll:
+                self.stream.writeln('warned %s' % reason)
+            else:
+                self.stream.write('~')
+                self.stream.flush()
 
     def addOutputMismatch(self, test, ret, got, expected):
         """Record a mismatch in test output for a particular test."""
@@ -1239,38 +1312,45 @@
         failed = False
         lines = []
 
-        iolock.acquire()
-        if self._options.nodiff:
-            pass
-        elif self._options.view:
-            os.system("%s %s %s" %
-                      (self._options.view, test.refpath, test.errpath))
-        else:
-            servefail, lines = getdiff(expected, got,
-                                       test.refpath, test.errpath)
-            if servefail:
-                self.addFailure(
-                    test,
-                    'server failed to start (HGPORT=%s)' % test._startport)
+        with iolock:
+            if self._options.nodiff:
+                pass
+            elif self._options.view:
+                v = self._options.view
+                if PYTHON3:
+                    v = _bytespath(v)
+                os.system(b"%s %s %s" %
+                          (v, test.refpath, test.errpath))
             else:
-                self.stream.write('\n')
-                for line in lines:
-                    self.stream.write(line)
-                self.stream.flush()
+                servefail, lines = getdiff(expected, got,
+                                           test.refpath, test.errpath)
+                if servefail:
+                    self.addFailure(
+                        test,
+                        'server failed to start (HGPORT=%s)' % test._startport)
+                else:
+                    self.stream.write('\n')
+                    for line in lines:
+                        if PYTHON3:
+                            self.stream.flush()
+                            self.stream.buffer.write(line)
+                            self.stream.buffer.flush()
+                        else:
+                            self.stream.write(line)
+                            self.stream.flush()
 
-        # handle interactive prompt without releasing iolock
-        if self._options.interactive:
-            self.stream.write('Accept this change? [n] ')
-            answer = sys.stdin.readline().strip()
-            if answer.lower() in ('y', 'yes'):
-                if test.name.endswith('.t'):
-                    rename(test.errpath, test.path)
-                else:
-                    rename(test.errpath, '%s.out' % test.path)
-                accepted = True
-        if not accepted and not failed:
-            self.faildata[test.name] = ''.join(lines)
-        iolock.release()
+            # handle interactive prompt without releasing iolock
+            if self._options.interactive:
+                self.stream.write('Accept this change? [n] ')
+                answer = sys.stdin.readline().strip()
+                if answer.lower() in ('y', 'yes'):
+                    if test.name.endswith('.t'):
+                        rename(test.errpath, test.path)
+                    else:
+                        rename(test.errpath, '%s.out' % test.path)
+                    accepted = True
+            if not accepted and not failed:
+                self.faildata[test.name] = b''.join(lines)
 
         return accepted
 
@@ -1282,6 +1362,8 @@
         # This module has one limitation. It can only work for Linux user
         # and not for Windows.
         test.started = os.times()
+        if self._firststarttime is None: # thread racy but irrelevant
+            self._firststarttime = test.started[4]
 
     def stopTest(self, test, interrupted=False):
         super(TestResult, self).stopTest(test)
@@ -1290,14 +1372,19 @@
 
         starttime = test.started
         endtime = test.stopped
-        self.times.append((test.name, endtime[2] - starttime[2],
-                    endtime[3] - starttime[3], endtime[4] - starttime[4]))
+        origin = self._firststarttime
+        self.times.append((test.name,
+                           endtime[2] - starttime[2], # user space CPU time
+                           endtime[3] - starttime[3], # sys  space CPU time
+                           endtime[4] - starttime[4], # real time
+                           starttime[4] - origin, # start date in run context
+                           endtime[4] - origin, # end date in run context
+                           ))
 
         if interrupted:
-            iolock.acquire()
-            self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
-                test.name, self.times[-1][3]))
-            iolock.release()
+            with iolock:
+                self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
+                    test.name, self.times[-1][3]))
 
 class TestSuite(unittest.TestSuite):
     """Custom unittest TestSuite that knows how to execute Mercurial tests."""
@@ -1352,14 +1439,14 @@
             def get():
                 num_tests[0] += 1
                 if getattr(test, 'should_reload', False):
-                    return self._loadtest(test.name, num_tests[0])
+                    return self._loadtest(test.bname, num_tests[0])
                 return test
             if not os.path.exists(test.path):
                 result.addSkip(test, "Doesn't exist")
                 continue
 
             if not (self._whitelist and test.name in self._whitelist):
-                if self._blacklist and test.name in self._blacklist:
+                if self._blacklist and test.bname in self._blacklist:
                     result.addSkip(test, 'blacklisted')
                     continue
 
@@ -1369,7 +1456,7 @@
 
                 if self._keywords:
                     f = open(test.path, 'rb')
-                    t = f.read().lower() + test.name.lower()
+                    t = f.read().lower() + test.bname.lower()
                     f.close()
                     ignored = False
                     for k in self._keywords.lower().split():
@@ -1460,101 +1547,92 @@
         skipped = len(result.skipped)
         ignored = len(result.ignored)
 
-        iolock.acquire()
-        self.stream.writeln('')
-
-        if not self._runner.options.noskips:
-            for test, msg in result.skipped:
-                self.stream.writeln('Skipped %s: %s' % (test.name, msg))
-        for test, msg in result.warned:
-            self.stream.writeln('Warned %s: %s' % (test.name, msg))
-        for test, msg in result.failures:
-            self.stream.writeln('Failed %s: %s' % (test.name, msg))
-        for test, msg in result.errors:
-            self.stream.writeln('Errored %s: %s' % (test.name, msg))
+        with iolock:
+            self.stream.writeln('')
 
-        if self._runner.options.xunit:
-            xuf = open(self._runner.options.xunit, 'wb')
-            try:
-                timesd = dict(
-                    (test, real) for test, cuser, csys, real in result.times)
-                doc = minidom.Document()
-                s = doc.createElement('testsuite')
-                s.setAttribute('name', 'run-tests')
-                s.setAttribute('tests', str(result.testsRun))
-                s.setAttribute('errors', "0") # TODO
-                s.setAttribute('failures', str(failed))
-                s.setAttribute('skipped', str(skipped + ignored))
-                doc.appendChild(s)
-                for tc in result.successes:
-                    t = doc.createElement('testcase')
-                    t.setAttribute('name', tc.name)
-                    t.setAttribute('time', '%.3f' % timesd[tc.name])
-                    s.appendChild(t)
-                for tc, err in sorted(result.faildata.iteritems()):
-                    t = doc.createElement('testcase')
-                    t.setAttribute('name', tc)
-                    t.setAttribute('time', '%.3f' % timesd[tc])
-                    # createCDATASection expects a unicode or it will convert
-                    # using default conversion rules, which will fail if
-                    # string isn't ASCII.
-                    err = cdatasafe(err).decode('utf-8', 'replace')
-                    cd = doc.createCDATASection(err)
-                    t.appendChild(cd)
-                    s.appendChild(t)
-                xuf.write(doc.toprettyxml(indent='  ', encoding='utf-8'))
-            finally:
-                xuf.close()
+            if not self._runner.options.noskips:
+                for test, msg in result.skipped:
+                    self.stream.writeln('Skipped %s: %s' % (test.name, msg))
+            for test, msg in result.warned:
+                self.stream.writeln('Warned %s: %s' % (test.name, msg))
+            for test, msg in result.failures:
+                self.stream.writeln('Failed %s: %s' % (test.name, msg))
+            for test, msg in result.errors:
+                self.stream.writeln('Errored %s: %s' % (test.name, msg))
 
-        if self._runner.options.json:
-            if json is None:
-                raise ImportError("json module not installed")
-            jsonpath = os.path.join(self._runner._testdir, 'report.json')
-            fp = open(jsonpath, 'w')
-            try:
-                timesd = {}
-                for test, cuser, csys, real in result.times:
-                    timesd[test] = (real, cuser, csys)
-
-                outcome = {}
-                for tc in result.successes:
-                    testresult = {'result': 'success',
-                                  'time': ('%0.3f' % timesd[tc.name][0]),
-                                  'cuser': ('%0.3f' % timesd[tc.name][1]),
-                                  'csys': ('%0.3f' % timesd[tc.name][2])}
-                    outcome[tc.name] = testresult
-
-                for tc, err in sorted(result.faildata.iteritems()):
-                    testresult = {'result': 'failure',
-                                  'time': ('%0.3f' % timesd[tc][0]),
-                                  'cuser': ('%0.3f' % timesd[tc][1]),
-                                  'csys': ('%0.3f' % timesd[tc][2])}
-                    outcome[tc] = testresult
+            if self._runner.options.xunit:
+                xuf = open(self._runner.options.xunit, 'wb')
+                try:
+                    timesd = dict((t[0], t[3]) for t in result.times)
+                    doc = minidom.Document()
+                    s = doc.createElement('testsuite')
+                    s.setAttribute('name', 'run-tests')
+                    s.setAttribute('tests', str(result.testsRun))
+                    s.setAttribute('errors', "0") # TODO
+                    s.setAttribute('failures', str(failed))
+                    s.setAttribute('skipped', str(skipped + ignored))
+                    doc.appendChild(s)
+                    for tc in result.successes:
+                        t = doc.createElement('testcase')
+                        t.setAttribute('name', tc.name)
+                        t.setAttribute('time', '%.3f' % timesd[tc.name])
+                        s.appendChild(t)
+                    for tc, err in sorted(result.faildata.items()):
+                        t = doc.createElement('testcase')
+                        t.setAttribute('name', tc)
+                        t.setAttribute('time', '%.3f' % timesd[tc])
+                        # createCDATASection expects a unicode or it will
+                        # convert using default conversion rules, which will
+                        # fail if string isn't ASCII.
+                        err = cdatasafe(err).decode('utf-8', 'replace')
+                        cd = doc.createCDATASection(err)
+                        t.appendChild(cd)
+                        s.appendChild(t)
+                    xuf.write(doc.toprettyxml(indent='  ', encoding='utf-8'))
+                finally:
+                    xuf.close()
 
-                for tc, reason in result.skipped:
-                    testresult = {'result': 'skip',
-                                  'time': ('%0.3f' % timesd[tc.name][0]),
-                                  'cuser': ('%0.3f' % timesd[tc.name][1]),
-                                  'csys': ('%0.3f' % timesd[tc.name][2])}
-                    outcome[tc.name] = testresult
-
-                jsonout = json.dumps(outcome, sort_keys=True, indent=4)
-                fp.writelines(("testreport =", jsonout))
-            finally:
-                fp.close()
+            if self._runner.options.json:
+                if json is None:
+                    raise ImportError("json module not installed")
+                jsonpath = os.path.join(self._runner._testdir, 'report.json')
+                fp = open(jsonpath, 'w')
+                try:
+                    timesd = {}
+                    for tdata in result.times:
+                        test = tdata[0]
+                        timesd[test] = tdata[1:]
 
-        self._runner._checkhglib('Tested')
+                    outcome = {}
+                    groups = [('success', ((tc, None)
+                               for tc in result.successes)),
+                              ('failure', result.failures),
+                              ('skip', result.skipped)]
+                    for res, testcases in groups:
+                        for tc, __ in testcases:
+                            tres = {'result': res,
+                                    'time': ('%0.3f' % timesd[tc.name][2]),
+                                    'cuser': ('%0.3f' % timesd[tc.name][0]),
+                                    'csys': ('%0.3f' % timesd[tc.name][1]),
+                                    'start': ('%0.3f' % timesd[tc.name][3]),
+                                    'end': ('%0.3f' % timesd[tc.name][4])}
+                            outcome[tc.name] = tres
+                    jsonout = json.dumps(outcome, sort_keys=True, indent=4)
+                    fp.writelines(("testreport =", jsonout))
+                finally:
+                    fp.close()
 
-        self.stream.writeln('# Ran %d tests, %d skipped, %d warned, %d failed.'
-            % (result.testsRun,
-               skipped + ignored, warned, failed))
-        if failed:
-            self.stream.writeln('python hash seed: %s' %
-                os.environ['PYTHONHASHSEED'])
-        if self._runner.options.time:
-            self.printtimes(result.times)
+            self._runner._checkhglib('Tested')
 
-        iolock.release()
+            self.stream.writeln(
+                '# Ran %d tests, %d skipped, %d warned, %d failed.'
+                % (result.testsRun,
+                   skipped + ignored, warned, failed))
+            if failed:
+                self.stream.writeln('python hash seed: %s' %
+                    os.environ['PYTHONHASHSEED'])
+            if self._runner.options.time:
+                self.printtimes(result.times)
 
         return result
 
@@ -1562,11 +1640,13 @@
         # iolock held by run
         self.stream.writeln('# Producing time report')
         times.sort(key=lambda t: (t[3]))
-        cols = '%7.3f %7.3f %7.3f   %s'
-        self.stream.writeln('%-7s %-7s %-7s   %s' % ('cuser', 'csys', 'real',
-                    'Test'))
-        for test, cuser, csys, real in times:
-            self.stream.writeln(cols % (cuser, csys, real, test))
+        cols = '%7.3f %7.3f %7.3f %7.3f %7.3f   %s'
+        self.stream.writeln('%-7s %-7s %-7s %-7s %-7s   %s' %
+                            ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
+        for tdata in times:
+            test = tdata[0]
+            cuser, csys, real, start, end = tdata[1:6]
+            self.stream.writeln(cols % (start, end, cuser, csys, real, test))
 
 class TestRunner(object):
     """Holds context for executing tests.
@@ -1576,19 +1656,19 @@
 
     # Programs required to run tests.
     REQUIREDTOOLS = [
-        os.path.basename(sys.executable),
-        'diff',
-        'grep',
-        'unzip',
-        'gunzip',
-        'bunzip2',
-        'sed',
+        os.path.basename(_bytespath(sys.executable)),
+        b'diff',
+        b'grep',
+        b'unzip',
+        b'gunzip',
+        b'bunzip2',
+        b'sed',
     ]
 
     # Maps file extensions to test class.
     TESTTYPES = [
-        ('.py', PythonTest),
-        ('.t', TTest),
+        (b'.py', PythonTest),
+        (b'.t', TTest),
     ]
 
     def __init__(self):
@@ -1603,18 +1683,31 @@
         self._coveragefile = None
         self._createdfiles = []
         self._hgpath = None
+        self._portoffset = 0
+        self._ports = {}
 
     def run(self, args, parser=None):
         """Run the test suite."""
-        oldmask = os.umask(022)
+        oldmask = os.umask(0o22)
         try:
             parser = parser or getparser()
             options, args = parseargs(args, parser)
+            # positional arguments are paths to test files to run, so
+            # we make sure they're all bytestrings
+            args = [_bytespath(a) for a in args]
             self.options = options
 
             self._checktools()
             tests = self.findtests(args)
-            return self._run(tests)
+            if options.profile_runner:
+                import statprof
+                statprof.start()
+            result = self._run(tests)
+            if options.profile_runner:
+                statprof.stop()
+                statprof.display()
+            return result
+
         finally:
             os.umask(oldmask)
 
@@ -1623,22 +1716,26 @@
             random.shuffle(tests)
         else:
             # keywords for slow tests
-            slow = 'svn gendoc check-code-hg'.split()
+            slow = {b'svn': 10,
+                    b'gendoc': 10,
+                    b'check-code-hg': 100,
+                   }
             def sortkey(f):
                 # run largest tests first, as they tend to take the longest
                 try:
                     val = -os.stat(f).st_size
-                except OSError, e:
+                except OSError as e:
                     if e.errno != errno.ENOENT:
                         raise
                     return -1e9 # file does not exist, tell early
-                for kw in slow:
+                for kw, mul in slow.items():
                     if kw in f:
-                        val *= 10
+                        val *= mul
                 return val
             tests.sort(key=sortkey)
 
-        self._testdir = os.environ['TESTDIR'] = os.getcwd()
+        self._testdir = osenvironb[b'TESTDIR'] = getattr(
+            os, 'getcwdb', os.getcwd)()
 
         if 'PYTHONHASHSEED' not in os.environ:
             # use a random python hash seed all the time
@@ -1647,12 +1744,12 @@
 
         if self.options.tmpdir:
             self.options.keep_tmpdir = True
-            tmpdir = self.options.tmpdir
+            tmpdir = _bytespath(self.options.tmpdir)
             if os.path.exists(tmpdir):
                 # Meaning of tmpdir has changed since 1.3: we used to create
                 # HGTMP inside tmpdir; now HGTMP is tmpdir.  So fail if
                 # tmpdir already exists.
-                print "error: temp dir %r already exists" % tmpdir
+                print("error: temp dir %r already exists" % tmpdir)
                 return 1
 
                 # Automatically removing tmpdir sounds convenient, but could
@@ -1666,15 +1763,24 @@
             if os.name == 'nt':
                 # without this, we get the default temp dir location, but
                 # in all lowercase, which causes troubles with paths (issue3490)
-                d = os.getenv('TMP')
-            tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
-        self._hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
+                d = osenvironb.get(b'TMP', None)
+            tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
+
+        self._hgtmp = osenvironb[b'HGTMP'] = (
+            os.path.realpath(tmpdir))
 
         if self.options.with_hg:
             self._installdir = None
-            self._bindir = os.path.dirname(os.path.realpath(
-                                           self.options.with_hg))
-            self._tmpbindir = os.path.join(self._hgtmp, 'install', 'bin')
+            whg = self.options.with_hg
+            # If --with-hg is not specified, we have bytes already,
+            # but if it was specified in python3 we get a str, so we
+            # have to encode it back into a bytes.
+            if PYTHON3:
+                if not isinstance(whg, bytes):
+                    whg = _bytespath(whg)
+            self._bindir = os.path.dirname(os.path.realpath(whg))
+            assert isinstance(self._bindir, bytes)
+            self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
             os.makedirs(self._tmpbindir)
 
             # This looks redundant with how Python initializes sys.path from
@@ -1684,25 +1790,33 @@
             # ... which means it's not really redundant at all.
             self._pythondir = self._bindir
         else:
-            self._installdir = os.path.join(self._hgtmp, "install")
-            self._bindir = os.environ["BINDIR"] = \
-                os.path.join(self._installdir, "bin")
+            self._installdir = os.path.join(self._hgtmp, b"install")
+            self._bindir = osenvironb[b"BINDIR"] = \
+                os.path.join(self._installdir, b"bin")
             self._tmpbindir = self._bindir
-            self._pythondir = os.path.join(self._installdir, "lib", "python")
+            self._pythondir = os.path.join(self._installdir, b"lib", b"python")
+
+        osenvironb[b"BINDIR"] = self._bindir
+        osenvironb[b"PYTHON"] = PYTHON
 
-        os.environ["BINDIR"] = self._bindir
-        os.environ["PYTHON"] = PYTHON
-
-        runtestdir = os.path.abspath(os.path.dirname(__file__))
-        path = [self._bindir, runtestdir] + os.environ["PATH"].split(os.pathsep)
+        fileb = _bytespath(__file__)
+        runtestdir = os.path.abspath(os.path.dirname(fileb))
+        osenvironb[b'RUNTESTDIR'] = runtestdir
+        if PYTHON3:
+            sepb = _bytespath(os.pathsep)
+        else:
+            sepb = os.pathsep
+        path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
         if os.path.islink(__file__):
             # test helper will likely be at the end of the symlink
-            realfile = os.path.realpath(__file__)
+            realfile = os.path.realpath(fileb)
             realdir = os.path.abspath(os.path.dirname(realfile))
             path.insert(2, realdir)
+        if self._testdir != runtestdir:
+            path = [self._testdir] + path
         if self._tmpbindir != self._bindir:
             path = [self._tmpbindir] + path
-        os.environ["PATH"] = os.pathsep.join(path)
+        osenvironb[b"PATH"] = sepb.join(path)
 
         # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
         # can run .../tests/run-tests.py test-foo where test-foo
@@ -1713,20 +1827,21 @@
         # it, in case external libraries are only available via current
         # PYTHONPATH.  (In particular, the Subversion bindings on OS X
         # are in /opt/subversion.)
-        oldpypath = os.environ.get(IMPL_PATH)
+        oldpypath = osenvironb.get(IMPL_PATH)
         if oldpypath:
             pypath.append(oldpypath)
-        os.environ[IMPL_PATH] = os.pathsep.join(pypath)
+        osenvironb[IMPL_PATH] = sepb.join(pypath)
 
         if self.options.pure:
             os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
 
-        self._coveragefile = os.path.join(self._testdir, '.coverage')
+        self._coveragefile = os.path.join(self._testdir, b'.coverage')
 
         vlog("# Using TESTDIR", self._testdir)
+        vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
         vlog("# Using HGTMP", self._hgtmp)
         vlog("# Using PATH", os.environ["PATH"])
-        vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
+        vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
 
         try:
             return self._runtests(tests) or 0
@@ -1745,13 +1860,13 @@
                 proc = Popen4('hg st --rev "%s" -man0 .' %
                               self.options.changed, None, 0)
                 stdout, stderr = proc.communicate()
-                args = stdout.strip('\0').split('\0')
+                args = stdout.strip(b'\0').split(b'\0')
             else:
-                args = os.listdir('.')
+                args = os.listdir(b'.')
 
         return [t for t in args
-                if os.path.basename(t).startswith('test-')
-                    and (t.endswith('.py') or t.endswith('.t'))]
+                if os.path.basename(t).startswith(b'test-')
+                    and (t.endswith(b'.py') or t.endswith(b'.t'))]
 
     def _runtests(self, tests):
         try:
@@ -1768,20 +1883,23 @@
                         break
                     tests.pop(0)
                 if not tests:
-                    print "running all tests"
+                    print("running all tests")
                     tests = orig
 
             tests = [self._gettest(t, i) for i, t in enumerate(tests)]
 
             failed = False
             warned = False
+            kws = self.options.keywords
+            if kws is not None and PYTHON3:
+                kws = kws.encode('utf-8')
 
             suite = TestSuite(self._testdir,
                               jobs=self.options.jobs,
                               whitelist=self.options.whitelisted,
                               blacklist=self.options.blacklist,
                               retest=self.options.retest,
-                              keywords=self.options.keywords,
+                              keywords=kws,
                               loop=self.options.loop,
                               runs_per_test=self.options.runs_per_test,
                               tests=tests, loadtest=self._gettest)
@@ -1800,13 +1918,31 @@
                 self._outputcoverage()
         except KeyboardInterrupt:
             failed = True
-            print "\ninterrupted!"
+            print("\ninterrupted!")
 
         if failed:
             return 1
         if warned:
             return 80
 
+    def _getport(self, count):
+        port = self._ports.get(count) # do we have a cached entry?
+        if port is None:
+            port = self.options.port + self._portoffset
+            portneeded = 3
+            # above 100 tries we just give up and let test reports failure
+            for tries in xrange(100):
+                allfree = True
+                for idx in xrange(portneeded):
+                    if not checkportisavailable(port + idx):
+                        allfree = False
+                        break
+                self._portoffset += portneeded
+                if allfree:
+                    break
+            self._ports[count] = port
+        return port
+
     def _gettest(self, test, count):
         """Obtain a Test by looking at its filename.
 
@@ -1822,13 +1958,13 @@
                 break
 
         refpath = os.path.join(self._testdir, test)
-        tmpdir = os.path.join(self._hgtmp, 'child%d' % count)
+        tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
 
         t = testcls(refpath, tmpdir,
                     keeptmpdir=self.options.keep_tmpdir,
                     debug=self.options.debug,
                     timeout=self.options.timeout,
-                    startport=self.options.port + count * 3,
+                    startport=self._getport(count),
                     extraconfigopts=self.options.extra_config_opt,
                     py3kwarnings=self.options.py3k_warnings,
                     shell=self.options.shell)
@@ -1852,7 +1988,7 @@
     def _usecorrectpython(self):
         """Configure the environment to use the appropriate Python in tests."""
         # Tests must use the same interpreter as us or bad things will happen.
-        pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
+        pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
         if getattr(os, 'symlink', None):
             vlog("# Making python executable in test path a symlink to '%s'" %
                  sys.executable)
@@ -1861,14 +1997,14 @@
                 if os.readlink(mypython) == sys.executable:
                     return
                 os.unlink(mypython)
-            except OSError, err:
+            except OSError as err:
                 if err.errno != errno.ENOENT:
                     raise
             if self._findprogram(pyexename) != sys.executable:
                 try:
                     os.symlink(sys.executable, mypython)
                     self._createdfiles.append(mypython)
-                except OSError, err:
+                except OSError as err:
                     # child processes may race, which is harmless
                     if err.errno != errno.EEXIST:
                         raise
@@ -1881,7 +2017,7 @@
                 path.remove(exedir)
             os.environ['PATH'] = os.pathsep.join([exedir] + path)
             if not self._findprogram(pyexename):
-                print "WARNING: Cannot find %s in search path" % pyexename
+                print("WARNING: Cannot find %s in search path" % pyexename)
 
     def _installhg(self):
         """Install hg into the test environment.
@@ -1889,47 +2025,51 @@
         This will also configure hg with the appropriate testing settings.
         """
         vlog("# Performing temporary installation of HG")
-        installerrs = os.path.join("tests", "install.err")
+        installerrs = os.path.join(b"tests", b"install.err")
         compiler = ''
         if self.options.compiler:
             compiler = '--compiler ' + self.options.compiler
         if self.options.pure:
-            pure = "--pure"
+            pure = b"--pure"
         else:
-            pure = ""
+            pure = b""
         py3 = ''
-        if sys.version_info[0] == 3:
-            py3 = '--c2to3'
 
         # Run installer in hg root
         script = os.path.realpath(sys.argv[0])
+        exe = sys.executable
+        if PYTHON3:
+            py3 = b'--c2to3'
+            compiler = _bytespath(compiler)
+            script = _bytespath(script)
+            exe = _bytespath(exe)
         hgroot = os.path.dirname(os.path.dirname(script))
         self._hgroot = hgroot
         os.chdir(hgroot)
-        nohome = '--home=""'
+        nohome = b'--home=""'
         if os.name == 'nt':
             # The --home="" trick works only on OS where os.sep == '/'
             # because of a distutils convert_path() fast-path. Avoid it at
             # least on Windows for now, deal with .pydistutils.cfg bugs
             # when they happen.
-            nohome = ''
-        cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
-               ' build %(compiler)s --build-base="%(base)s"'
-               ' install --force --prefix="%(prefix)s"'
-               ' --install-lib="%(libdir)s"'
-               ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
-               % {'exe': sys.executable, 'py3': py3, 'pure': pure,
-                  'compiler': compiler,
-                  'base': os.path.join(self._hgtmp, "build"),
-                  'prefix': self._installdir, 'libdir': self._pythondir,
-                  'bindir': self._bindir,
-                  'nohome': nohome, 'logfile': installerrs})
+            nohome = b''
+        cmd = (b'%(exe)s setup.py %(py3)s %(pure)s clean --all'
+               b' build %(compiler)s --build-base="%(base)s"'
+               b' install --force --prefix="%(prefix)s"'
+               b' --install-lib="%(libdir)s"'
+               b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
+               % {b'exe': exe, b'py3': py3, b'pure': pure,
+                  b'compiler': compiler,
+                  b'base': os.path.join(self._hgtmp, b"build"),
+                  b'prefix': self._installdir, b'libdir': self._pythondir,
+                  b'bindir': self._bindir,
+                  b'nohome': nohome, b'logfile': installerrs})
 
         # setuptools requires install directories to exist.
         def makedirs(p):
             try:
                 os.makedirs(p)
-            except OSError, e:
+            except OSError as e:
                 if e.errno != errno.EEXIST:
                     raise
         makedirs(self._pythondir)
@@ -1942,7 +2082,10 @@
         else:
             f = open(installerrs, 'rb')
             for line in f:
-                sys.stdout.write(line)
+                if PYTHON3:
+                    sys.stdout.buffer.write(line)
+                else:
+                    sys.stdout.write(line)
             f.close()
             sys.exit(1)
         os.chdir(self._testdir)
@@ -1960,21 +2103,21 @@
                 f.write(line + '\n')
             f.close()
 
-        hgbat = os.path.join(self._bindir, 'hg.bat')
+        hgbat = os.path.join(self._bindir, b'hg.bat')
         if os.path.isfile(hgbat):
             # hg.bat expects to be put in bin/scripts while run-tests.py
             # installation layout put it in bin/ directly. Fix it
             f = open(hgbat, 'rb')
             data = f.read()
             f.close()
-            if '"%~dp0..\python" "%~dp0hg" %*' in data:
-                data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
-                                    '"%~dp0python" "%~dp0hg" %*')
+            if b'"%~dp0..\python" "%~dp0hg" %*' in data:
+                data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*',
+                                    b'"%~dp0python" "%~dp0hg" %*')
                 f = open(hgbat, 'wb')
                 f.write(data)
                 f.close()
             else:
-                print 'WARNING: cannot fix hg.bat reference to python.exe'
+                print('WARNING: cannot fix hg.bat reference to python.exe')
 
         if self.options.anycoverage:
             custom = os.path.join(self._testdir, 'sitecustomize.py')
@@ -1987,7 +2130,7 @@
             covdir = os.path.join(self._installdir, '..', 'coverage')
             try:
                 os.mkdir(covdir)
-            except OSError, e:
+            except OSError as e:
                 if e.errno != errno.EEXIST:
                     raise
 
@@ -2001,7 +2144,7 @@
             # The pythondir has been inferred from --with-hg flag.
             # We cannot expect anything sensible here.
             return
-        expecthg = os.path.join(self._pythondir, 'mercurial')
+        expecthg = os.path.join(self._pythondir, b'mercurial')
         actualhg = self._gethgpath()
         if os.path.abspath(actualhg) != os.path.abspath(expecthg):
             sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
@@ -2013,10 +2156,13 @@
         if self._hgpath is not None:
             return self._hgpath
 
-        cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
-        pipe = os.popen(cmd % PYTHON)
+        cmd = b'%s -c "import mercurial; print (mercurial.__path__[0])"'
+        cmd = cmd % PYTHON
+        if PYTHON3:
+            cmd = _strpath(cmd)
+        pipe = os.popen(cmd)
         try:
-            self._hgpath = pipe.read().strip()
+            self._hgpath = _bytespath(pipe.read().strip())
         finally:
             pipe.close()
 
@@ -2052,7 +2198,9 @@
 
     def _findprogram(self, program):
         """Search PATH for a executable program"""
-        for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
+        dpb = _bytespath(os.defpath)
+        sepb = _bytespath(os.pathsep)
+        for p in osenvironb.get(b'PATH', dpb).split(sepb):
             name = os.path.join(p, program)
             if os.name == 'nt' or os.access(name, os.X_OK):
                 return name
@@ -2067,7 +2215,7 @@
             if found:
                 vlog("# Found prerequisite", p, "at", found)
             else:
-                print "WARNING: Did not find prerequisite tool: %s " % p
+                print("WARNING: Did not find prerequisite tool: %s " % p)
 
 if __name__ == '__main__':
     runner = TestRunner()
--- a/tests/test-acl.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-acl.t	Sat Jul 18 17:32:38 2015 -0500
@@ -44,6 +44,13 @@
   > EOF
   > }
 
+  $ cat << EOF >> $HGRCPATH
+  > [experimental]
+  > # drop me once bundle2 is the default,
+  > # added to get test change early.
+  > bundle2-exp = True
+  > EOF
+
   $ hg init a
   $ cd a
   $ mkdir foo foo/Bar quux
@@ -91,37 +98,40 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundling: 1/3 changesets (33.33%)
-  bundling: 2/3 changesets (66.67%)
-  bundling: 3/3 changesets (100.00%)
-  bundling: 1/3 manifests (33.33%)
-  bundling: 2/3 manifests (66.67%)
-  bundling: 3/3 manifests (100.00%)
-  bundling: foo/Bar/file.txt 1/3 files (33.33%)
-  bundling: foo/file.txt 2/3 files (66.67%)
-  bundling: quux/file.py 3/3 files (100.00%)
+  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
   adding manifests
-  manifests: 1/3 chunks (33.33%)
-  manifests: 2/3 chunks (66.67%)
-  manifests: 3/3 chunks (100.00%)
   adding file changes
   adding foo/Bar/file.txt revisions
-  files: 1/3 chunks (33.33%)
   adding foo/file.txt revisions
-  files: 2/3 chunks (66.67%)
   adding quux/file.py revisions
-  files: 3/3 chunks (100.00%)
   added 3 changesets with 3 changes to 3 files
+  bundle2-input-part: total payload size 1606
+  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
+  bundle2-input-bundle: 3 parts total
   updating the branch cache
+  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-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
+  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
+  bundle2-input-bundle: 1 parts total
   listing keys for "phases"
-  try to push obsolete markers to remote
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
   
@@ -151,39 +161,42 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundling: 1/3 changesets (33.33%)
-  bundling: 2/3 changesets (66.67%)
-  bundling: 3/3 changesets (100.00%)
-  bundling: 1/3 manifests (33.33%)
-  bundling: 2/3 manifests (66.67%)
-  bundling: 3/3 manifests (100.00%)
-  bundling: foo/Bar/file.txt 1/3 files (33.33%)
-  bundling: foo/file.txt 2/3 files (66.67%)
-  bundling: quux/file.py 3/3 files (100.00%)
+  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
   adding manifests
-  manifests: 1/3 chunks (33.33%)
-  manifests: 2/3 chunks (66.67%)
-  manifests: 3/3 chunks (100.00%)
   adding file changes
   adding foo/Bar/file.txt revisions
-  files: 1/3 chunks (33.33%)
   adding foo/file.txt revisions
-  files: 2/3 chunks (66.67%)
   adding quux/file.py revisions
-  files: 3/3 chunks (100.00%)
   added 3 changesets with 3 changes to 3 files
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: changes have source "push" - skipping
+  bundle2-input-part: total payload size 1606
+  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
+  bundle2-input-bundle: 3 parts total
   updating the branch cache
+  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-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
+  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
+  bundle2-input-bundle: 1 parts total
   listing keys for "phases"
-  try to push obsolete markers to remote
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
   
@@ -214,33 +227,26 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundling: 1/3 changesets (33.33%)
-  bundling: 2/3 changesets (66.67%)
-  bundling: 3/3 changesets (100.00%)
-  bundling: 1/3 manifests (33.33%)
-  bundling: 2/3 manifests (66.67%)
-  bundling: 3/3 manifests (100.00%)
-  bundling: foo/Bar/file.txt 1/3 files (33.33%)
-  bundling: foo/file.txt 2/3 files (66.67%)
-  bundling: quux/file.py 3/3 files (100.00%)
+  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
   adding manifests
-  manifests: 1/3 chunks (33.33%)
-  manifests: 2/3 chunks (66.67%)
-  manifests: 3/3 chunks (100.00%)
   adding file changes
   adding foo/Bar/file.txt revisions
-  files: 1/3 chunks (33.33%)
   adding foo/file.txt revisions
-  files: 2/3 chunks (66.67%)
   adding quux/file.py revisions
-  files: 3/3 chunks (100.00%)
   added 3 changesets with 3 changes to 3 files
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "fred"
@@ -254,9 +260,19 @@
   acl: path access granted: "f9cafe1212c8"
   acl: branch access granted: "911600dab2ae" on branch "default"
   acl: path access granted: "911600dab2ae"
+  bundle2-input-part: total payload size 1606
+  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
+  bundle2-input-bundle: 3 parts total
   updating the branch cache
+  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-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
+  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
+  bundle2-input-bundle: 1 parts total
   listing keys for "phases"
-  try to push obsolete markers to remote
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
   
@@ -287,33 +303,26 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundling: 1/3 changesets (33.33%)
-  bundling: 2/3 changesets (66.67%)
-  bundling: 3/3 changesets (100.00%)
-  bundling: 1/3 manifests (33.33%)
-  bundling: 2/3 manifests (66.67%)
-  bundling: 3/3 manifests (100.00%)
-  bundling: foo/Bar/file.txt 1/3 files (33.33%)
-  bundling: foo/file.txt 2/3 files (66.67%)
-  bundling: quux/file.py 3/3 files (100.00%)
+  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
   adding manifests
-  manifests: 1/3 chunks (33.33%)
-  manifests: 2/3 chunks (66.67%)
-  manifests: 3/3 chunks (100.00%)
   adding file changes
   adding foo/Bar/file.txt revisions
-  files: 1/3 chunks (33.33%)
   adding foo/file.txt revisions
-  files: 2/3 chunks (66.67%)
   adding quux/file.py revisions
-  files: 3/3 chunks (100.00%)
   added 3 changesets with 3 changes to 3 files
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "fred"
@@ -323,6 +332,8 @@
   acl: acl.deny not enabled
   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 1606
+  bundle2-input-bundle: 3 parts total
   transaction abort!
   rollback completed
   abort: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
@@ -357,33 +368,26 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundling: 1/3 changesets (33.33%)
-  bundling: 2/3 changesets (66.67%)
-  bundling: 3/3 changesets (100.00%)
-  bundling: 1/3 manifests (33.33%)
-  bundling: 2/3 manifests (66.67%)
-  bundling: 3/3 manifests (100.00%)
-  bundling: foo/Bar/file.txt 1/3 files (33.33%)
-  bundling: foo/file.txt 2/3 files (66.67%)
-  bundling: quux/file.py 3/3 files (100.00%)
+  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
   adding manifests
-  manifests: 1/3 chunks (33.33%)
-  manifests: 2/3 chunks (66.67%)
-  manifests: 3/3 chunks (100.00%)
   adding file changes
   adding foo/Bar/file.txt revisions
-  files: 1/3 chunks (33.33%)
   adding foo/file.txt revisions
-  files: 2/3 chunks (66.67%)
   adding quux/file.py revisions
-  files: 3/3 chunks (100.00%)
   added 3 changesets with 3 changes to 3 files
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "fred"
@@ -397,6 +401,8 @@
   acl: path access granted: "f9cafe1212c8"
   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 1606
+  bundle2-input-bundle: 3 parts total
   transaction abort!
   rollback completed
   abort: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
@@ -432,33 +438,26 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundling: 1/3 changesets (33.33%)
-  bundling: 2/3 changesets (66.67%)
-  bundling: 3/3 changesets (100.00%)
-  bundling: 1/3 manifests (33.33%)
-  bundling: 2/3 manifests (66.67%)
-  bundling: 3/3 manifests (100.00%)
-  bundling: foo/Bar/file.txt 1/3 files (33.33%)
-  bundling: foo/file.txt 2/3 files (66.67%)
-  bundling: quux/file.py 3/3 files (100.00%)
+  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
   adding manifests
-  manifests: 1/3 chunks (33.33%)
-  manifests: 2/3 chunks (66.67%)
-  manifests: 3/3 chunks (100.00%)
   adding file changes
   adding foo/Bar/file.txt revisions
-  files: 1/3 chunks (33.33%)
   adding foo/file.txt revisions
-  files: 2/3 chunks (66.67%)
   adding quux/file.py revisions
-  files: 3/3 chunks (100.00%)
   added 3 changesets with 3 changes to 3 files
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "barney"
@@ -468,6 +467,8 @@
   acl: acl.deny enabled, 0 entries for user barney
   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 1606
+  bundle2-input-bundle: 3 parts total
   transaction abort!
   rollback completed
   abort: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
@@ -504,33 +505,26 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundling: 1/3 changesets (33.33%)
-  bundling: 2/3 changesets (66.67%)
-  bundling: 3/3 changesets (100.00%)
-  bundling: 1/3 manifests (33.33%)
-  bundling: 2/3 manifests (66.67%)
-  bundling: 3/3 manifests (100.00%)
-  bundling: foo/Bar/file.txt 1/3 files (33.33%)
-  bundling: foo/file.txt 2/3 files (66.67%)
-  bundling: quux/file.py 3/3 files (100.00%)
+  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
   adding manifests
-  manifests: 1/3 chunks (33.33%)
-  manifests: 2/3 chunks (66.67%)
-  manifests: 3/3 chunks (100.00%)
   adding file changes
   adding foo/Bar/file.txt revisions
-  files: 1/3 chunks (33.33%)
   adding foo/file.txt revisions
-  files: 2/3 chunks (66.67%)
   adding quux/file.py revisions
-  files: 3/3 chunks (100.00%)
   added 3 changesets with 3 changes to 3 files
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "fred"
@@ -544,6 +538,8 @@
   acl: path access granted: "f9cafe1212c8"
   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 1606
+  bundle2-input-bundle: 3 parts total
   transaction abort!
   rollback completed
   abort: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
@@ -581,33 +577,26 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundling: 1/3 changesets (33.33%)
-  bundling: 2/3 changesets (66.67%)
-  bundling: 3/3 changesets (100.00%)
-  bundling: 1/3 manifests (33.33%)
-  bundling: 2/3 manifests (66.67%)
-  bundling: 3/3 manifests (100.00%)
-  bundling: foo/Bar/file.txt 1/3 files (33.33%)
-  bundling: foo/file.txt 2/3 files (66.67%)
-  bundling: quux/file.py 3/3 files (100.00%)
+  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
   adding manifests
-  manifests: 1/3 chunks (33.33%)
-  manifests: 2/3 chunks (66.67%)
-  manifests: 3/3 chunks (100.00%)
   adding file changes
   adding foo/Bar/file.txt revisions
-  files: 1/3 chunks (33.33%)
   adding foo/file.txt revisions
-  files: 2/3 chunks (66.67%)
   adding quux/file.py revisions
-  files: 3/3 chunks (100.00%)
   added 3 changesets with 3 changes to 3 files
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "fred"
@@ -619,6 +608,8 @@
   acl: path access granted: "ef1ea85a6374"
   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 1606
+  bundle2-input-bundle: 3 parts total
   transaction abort!
   rollback completed
   abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
@@ -655,33 +646,26 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundling: 1/3 changesets (33.33%)
-  bundling: 2/3 changesets (66.67%)
-  bundling: 3/3 changesets (100.00%)
-  bundling: 1/3 manifests (33.33%)
-  bundling: 2/3 manifests (66.67%)
-  bundling: 3/3 manifests (100.00%)
-  bundling: foo/Bar/file.txt 1/3 files (33.33%)
-  bundling: foo/file.txt 2/3 files (66.67%)
-  bundling: quux/file.py 3/3 files (100.00%)
+  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
   adding manifests
-  manifests: 1/3 chunks (33.33%)
-  manifests: 2/3 chunks (66.67%)
-  manifests: 3/3 chunks (100.00%)
   adding file changes
   adding foo/Bar/file.txt revisions
-  files: 1/3 chunks (33.33%)
   adding foo/file.txt revisions
-  files: 2/3 chunks (66.67%)
   adding quux/file.py revisions
-  files: 3/3 chunks (100.00%)
   added 3 changesets with 3 changes to 3 files
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "barney"
@@ -691,6 +675,8 @@
   acl: acl.deny enabled, 0 entries for user barney
   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 1606
+  bundle2-input-bundle: 3 parts total
   transaction abort!
   rollback completed
   abort: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
@@ -731,33 +717,26 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundling: 1/3 changesets (33.33%)
-  bundling: 2/3 changesets (66.67%)
-  bundling: 3/3 changesets (100.00%)
-  bundling: 1/3 manifests (33.33%)
-  bundling: 2/3 manifests (66.67%)
-  bundling: 3/3 manifests (100.00%)
-  bundling: foo/Bar/file.txt 1/3 files (33.33%)
-  bundling: foo/file.txt 2/3 files (66.67%)
-  bundling: quux/file.py 3/3 files (100.00%)
+  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
   adding manifests
-  manifests: 1/3 chunks (33.33%)
-  manifests: 2/3 chunks (66.67%)
-  manifests: 3/3 chunks (100.00%)
   adding file changes
   adding foo/Bar/file.txt revisions
-  files: 1/3 chunks (33.33%)
   adding foo/file.txt revisions
-  files: 2/3 chunks (66.67%)
   adding quux/file.py revisions
-  files: 3/3 chunks (100.00%)
   added 3 changesets with 3 changes to 3 files
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "barney"
@@ -771,9 +750,19 @@
   acl: path access granted: "f9cafe1212c8"
   acl: branch access granted: "911600dab2ae" on branch "default"
   acl: path access granted: "911600dab2ae"
+  bundle2-input-part: total payload size 1606
+  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
+  bundle2-input-bundle: 3 parts total
   updating the branch cache
+  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-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
+  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
+  bundle2-input-bundle: 1 parts total
   listing keys for "phases"
-  try to push obsolete markers to remote
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
   
@@ -811,33 +800,26 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundling: 1/3 changesets (33.33%)
-  bundling: 2/3 changesets (66.67%)
-  bundling: 3/3 changesets (100.00%)
-  bundling: 1/3 manifests (33.33%)
-  bundling: 2/3 manifests (66.67%)
-  bundling: 3/3 manifests (100.00%)
-  bundling: foo/Bar/file.txt 1/3 files (33.33%)
-  bundling: foo/file.txt 2/3 files (66.67%)
-  bundling: quux/file.py 3/3 files (100.00%)
+  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
   adding manifests
-  manifests: 1/3 chunks (33.33%)
-  manifests: 2/3 chunks (66.67%)
-  manifests: 3/3 chunks (100.00%)
   adding file changes
   adding foo/Bar/file.txt revisions
-  files: 1/3 chunks (33.33%)
   adding foo/file.txt revisions
-  files: 2/3 chunks (66.67%)
   adding quux/file.py revisions
-  files: 3/3 chunks (100.00%)
   added 3 changesets with 3 changes to 3 files
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "wilma"
@@ -851,6 +833,8 @@
   acl: path access granted: "f9cafe1212c8"
   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 1606
+  bundle2-input-bundle: 3 parts total
   transaction abort!
   rollback completed
   abort: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae")
@@ -894,37 +878,32 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundling: 1/3 changesets (33.33%)
-  bundling: 2/3 changesets (66.67%)
-  bundling: 3/3 changesets (100.00%)
-  bundling: 1/3 manifests (33.33%)
-  bundling: 2/3 manifests (66.67%)
-  bundling: 3/3 manifests (100.00%)
-  bundling: foo/Bar/file.txt 1/3 files (33.33%)
-  bundling: foo/file.txt 2/3 files (66.67%)
-  bundling: quux/file.py 3/3 files (100.00%)
+  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
   adding manifests
-  manifests: 1/3 chunks (33.33%)
-  manifests: 2/3 chunks (66.67%)
-  manifests: 3/3 chunks (100.00%)
   adding file changes
   adding foo/Bar/file.txt revisions
-  files: 1/3 chunks (33.33%)
   adding foo/file.txt revisions
-  files: 2/3 chunks (66.67%)
   adding quux/file.py revisions
-  files: 3/3 chunks (100.00%)
   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'
+  bundle2-input-part: total payload size 1606
+  bundle2-input-bundle: 3 parts total
   transaction abort!
   rollback completed
   abort: No such file or directory: ../acl.config
@@ -972,33 +951,26 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundling: 1/3 changesets (33.33%)
-  bundling: 2/3 changesets (66.67%)
-  bundling: 3/3 changesets (100.00%)
-  bundling: 1/3 manifests (33.33%)
-  bundling: 2/3 manifests (66.67%)
-  bundling: 3/3 manifests (100.00%)
-  bundling: foo/Bar/file.txt 1/3 files (33.33%)
-  bundling: foo/file.txt 2/3 files (66.67%)
-  bundling: quux/file.py 3/3 files (100.00%)
+  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
   adding manifests
-  manifests: 1/3 chunks (33.33%)
-  manifests: 2/3 chunks (66.67%)
-  manifests: 3/3 chunks (100.00%)
   adding file changes
   adding foo/Bar/file.txt revisions
-  files: 1/3 chunks (33.33%)
   adding foo/file.txt revisions
-  files: 2/3 chunks (66.67%)
   adding quux/file.py revisions
-  files: 3/3 chunks (100.00%)
   added 3 changesets with 3 changes to 3 files
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "betty"
@@ -1012,6 +984,8 @@
   acl: path access granted: "f9cafe1212c8"
   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 1606
+  bundle2-input-bundle: 3 parts total
   transaction abort!
   rollback completed
   abort: acl: user "betty" not allowed on "quux/file.py" (changeset "911600dab2ae")
@@ -1061,33 +1035,26 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundling: 1/3 changesets (33.33%)
-  bundling: 2/3 changesets (66.67%)
-  bundling: 3/3 changesets (100.00%)
-  bundling: 1/3 manifests (33.33%)
-  bundling: 2/3 manifests (66.67%)
-  bundling: 3/3 manifests (100.00%)
-  bundling: foo/Bar/file.txt 1/3 files (33.33%)
-  bundling: foo/file.txt 2/3 files (66.67%)
-  bundling: quux/file.py 3/3 files (100.00%)
+  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
   adding manifests
-  manifests: 1/3 chunks (33.33%)
-  manifests: 2/3 chunks (66.67%)
-  manifests: 3/3 chunks (100.00%)
   adding file changes
   adding foo/Bar/file.txt revisions
-  files: 1/3 chunks (33.33%)
   adding foo/file.txt revisions
-  files: 2/3 chunks (66.67%)
   adding quux/file.py revisions
-  files: 3/3 chunks (100.00%)
   added 3 changesets with 3 changes to 3 files
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "barney"
@@ -1101,9 +1068,19 @@
   acl: path access granted: "f9cafe1212c8"
   acl: branch access granted: "911600dab2ae" on branch "default"
   acl: path access granted: "911600dab2ae"
+  bundle2-input-part: total payload size 1606
+  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
+  bundle2-input-bundle: 3 parts total
   updating the branch cache
+  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-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
+  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
+  bundle2-input-bundle: 1 parts total
   listing keys for "phases"
-  try to push obsolete markers to remote
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
   
@@ -1144,33 +1121,26 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundling: 1/3 changesets (33.33%)
-  bundling: 2/3 changesets (66.67%)
-  bundling: 3/3 changesets (100.00%)
-  bundling: 1/3 manifests (33.33%)
-  bundling: 2/3 manifests (66.67%)
-  bundling: 3/3 manifests (100.00%)
-  bundling: foo/Bar/file.txt 1/3 files (33.33%)
-  bundling: foo/file.txt 2/3 files (66.67%)
-  bundling: quux/file.py 3/3 files (100.00%)
+  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
   adding manifests
-  manifests: 1/3 chunks (33.33%)
-  manifests: 2/3 chunks (66.67%)
-  manifests: 3/3 chunks (100.00%)
   adding file changes
   adding foo/Bar/file.txt revisions
-  files: 1/3 chunks (33.33%)
   adding foo/file.txt revisions
-  files: 2/3 chunks (66.67%)
   adding quux/file.py revisions
-  files: 3/3 chunks (100.00%)
   added 3 changesets with 3 changes to 3 files
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "fred"
@@ -1184,9 +1154,19 @@
   acl: path access granted: "f9cafe1212c8"
   acl: branch access granted: "911600dab2ae" on branch "default"
   acl: path access granted: "911600dab2ae"
+  bundle2-input-part: total payload size 1606
+  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
+  bundle2-input-bundle: 3 parts total
   updating the branch cache
+  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-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
+  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
+  bundle2-input-bundle: 1 parts total
   listing keys for "phases"
-  try to push obsolete markers to remote
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
   
@@ -1223,33 +1203,26 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundling: 1/3 changesets (33.33%)
-  bundling: 2/3 changesets (66.67%)
-  bundling: 3/3 changesets (100.00%)
-  bundling: 1/3 manifests (33.33%)
-  bundling: 2/3 manifests (66.67%)
-  bundling: 3/3 manifests (100.00%)
-  bundling: foo/Bar/file.txt 1/3 files (33.33%)
-  bundling: foo/file.txt 2/3 files (66.67%)
-  bundling: quux/file.py 3/3 files (100.00%)
+  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
   adding manifests
-  manifests: 1/3 chunks (33.33%)
-  manifests: 2/3 chunks (66.67%)
-  manifests: 3/3 chunks (100.00%)
   adding file changes
   adding foo/Bar/file.txt revisions
-  files: 1/3 chunks (33.33%)
   adding foo/file.txt revisions
-  files: 2/3 chunks (66.67%)
   adding quux/file.py revisions
-  files: 3/3 chunks (100.00%)
   added 3 changesets with 3 changes to 3 files
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "fred"
@@ -1261,6 +1234,8 @@
   acl: path access granted: "ef1ea85a6374"
   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 1606
+  bundle2-input-bundle: 3 parts total
   transaction abort!
   rollback completed
   abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
@@ -1304,33 +1279,26 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundling: 1/3 changesets (33.33%)
-  bundling: 2/3 changesets (66.67%)
-  bundling: 3/3 changesets (100.00%)
-  bundling: 1/3 manifests (33.33%)
-  bundling: 2/3 manifests (66.67%)
-  bundling: 3/3 manifests (100.00%)
-  bundling: foo/Bar/file.txt 1/3 files (33.33%)
-  bundling: foo/file.txt 2/3 files (66.67%)
-  bundling: quux/file.py 3/3 files (100.00%)
+  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
   adding manifests
-  manifests: 1/3 chunks (33.33%)
-  manifests: 2/3 chunks (66.67%)
-  manifests: 3/3 chunks (100.00%)
   adding file changes
   adding foo/Bar/file.txt revisions
-  files: 1/3 chunks (33.33%)
   adding foo/file.txt revisions
-  files: 2/3 chunks (66.67%)
   adding quux/file.py revisions
-  files: 3/3 chunks (100.00%)
   added 3 changesets with 3 changes to 3 files
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "fred"
@@ -1345,9 +1313,19 @@
   acl: path access granted: "f9cafe1212c8"
   acl: branch access granted: "911600dab2ae" on branch "default"
   acl: path access granted: "911600dab2ae"
+  bundle2-input-part: total payload size 1606
+  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
+  bundle2-input-bundle: 3 parts total
   updating the branch cache
+  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-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
+  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
+  bundle2-input-bundle: 1 parts total
   listing keys for "phases"
-  try to push obsolete markers to remote
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
   
@@ -1384,33 +1362,26 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundling: 1/3 changesets (33.33%)
-  bundling: 2/3 changesets (66.67%)
-  bundling: 3/3 changesets (100.00%)
-  bundling: 1/3 manifests (33.33%)
-  bundling: 2/3 manifests (66.67%)
-  bundling: 3/3 manifests (100.00%)
-  bundling: foo/Bar/file.txt 1/3 files (33.33%)
-  bundling: foo/file.txt 2/3 files (66.67%)
-  bundling: quux/file.py 3/3 files (100.00%)
+  bundle2-output-bundle: "HG20", 4 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
   adding manifests
-  manifests: 1/3 chunks (33.33%)
-  manifests: 2/3 chunks (66.67%)
-  manifests: 3/3 chunks (100.00%)
   adding file changes
   adding foo/Bar/file.txt revisions
-  files: 1/3 chunks (33.33%)
   adding foo/file.txt revisions
-  files: 2/3 chunks (66.67%)
   adding quux/file.py revisions
-  files: 3/3 chunks (100.00%)
   added 3 changesets with 3 changes to 3 files
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "fred"
@@ -1424,6 +1395,8 @@
   acl: path access granted: "ef1ea85a6374"
   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 1606
+  bundle2-input-bundle: 3 parts total
   transaction abort!
   rollback completed
   abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
@@ -1504,41 +1477,29 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundling: 1/4 changesets (25.00%)
-  bundling: 2/4 changesets (50.00%)
-  bundling: 3/4 changesets (75.00%)
-  bundling: 4/4 changesets (100.00%)
-  bundling: 1/4 manifests (25.00%)
-  bundling: 2/4 manifests (50.00%)
-  bundling: 3/4 manifests (75.00%)
-  bundling: 4/4 manifests (100.00%)
-  bundling: abc.txt 1/4 files (25.00%)
-  bundling: foo/Bar/file.txt 2/4 files (50.00%)
-  bundling: foo/file.txt 3/4 files (75.00%)
-  bundling: quux/file.py 4/4 files (100.00%)
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
-  changesets: 4 chunks
   add changeset e8fc755d4d82
   adding manifests
-  manifests: 1/4 chunks (25.00%)
-  manifests: 2/4 chunks (50.00%)
-  manifests: 3/4 chunks (75.00%)
-  manifests: 4/4 chunks (100.00%)
   adding file changes
   adding abc.txt revisions
-  files: 1/4 chunks (25.00%)
   adding foo/Bar/file.txt revisions
-  files: 2/4 chunks (50.00%)
   adding foo/file.txt revisions
-  files: 3/4 chunks (75.00%)
   adding quux/file.py revisions
-  files: 4/4 chunks (100.00%)
   added 4 changesets with 4 changes to 4 files (+1 heads)
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "astro"
@@ -1554,9 +1515,23 @@
   acl: path access granted: "911600dab2ae"
   acl: branch access granted: "e8fc755d4d82" on branch "foobar"
   acl: path access granted: "e8fc755d4d82"
+  bundle2-input-part: total payload size 2101
+  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: 4 parts total
   updating the branch cache
+  bundle2-output-bundle: "HG20", 3 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: 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
+  bundle2-input-bundle: 2 parts total
   listing keys for "phases"
-  try to push obsolete markers to remote
   repository tip rolled back to revision 2 (undo push)
   2:fb35475503ef
   
@@ -1590,41 +1565,29 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundling: 1/4 changesets (25.00%)
-  bundling: 2/4 changesets (50.00%)
-  bundling: 3/4 changesets (75.00%)
-  bundling: 4/4 changesets (100.00%)
-  bundling: 1/4 manifests (25.00%)
-  bundling: 2/4 manifests (50.00%)
-  bundling: 3/4 manifests (75.00%)
-  bundling: 4/4 manifests (100.00%)
-  bundling: abc.txt 1/4 files (25.00%)
-  bundling: foo/Bar/file.txt 2/4 files (50.00%)
-  bundling: foo/file.txt 3/4 files (75.00%)
-  bundling: quux/file.py 4/4 files (100.00%)
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
-  changesets: 4 chunks
   add changeset e8fc755d4d82
   adding manifests
-  manifests: 1/4 chunks (25.00%)
-  manifests: 2/4 chunks (50.00%)
-  manifests: 3/4 chunks (75.00%)
-  manifests: 4/4 chunks (100.00%)
   adding file changes
   adding abc.txt revisions
-  files: 1/4 chunks (25.00%)
   adding foo/Bar/file.txt revisions
-  files: 2/4 chunks (50.00%)
   adding foo/file.txt revisions
-  files: 3/4 chunks (75.00%)
   adding quux/file.py revisions
-  files: 4/4 chunks (100.00%)
   added 4 changesets with 4 changes to 4 files (+1 heads)
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "astro"
@@ -1639,6 +1602,8 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   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 2101
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82")
@@ -1674,41 +1639,29 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundling: 1/4 changesets (25.00%)
-  bundling: 2/4 changesets (50.00%)
-  bundling: 3/4 changesets (75.00%)
-  bundling: 4/4 changesets (100.00%)
-  bundling: 1/4 manifests (25.00%)
-  bundling: 2/4 manifests (50.00%)
-  bundling: 3/4 manifests (75.00%)
-  bundling: 4/4 manifests (100.00%)
-  bundling: abc.txt 1/4 files (25.00%)
-  bundling: foo/Bar/file.txt 2/4 files (50.00%)
-  bundling: foo/file.txt 3/4 files (75.00%)
-  bundling: quux/file.py 4/4 files (100.00%)
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
-  changesets: 4 chunks
   add changeset e8fc755d4d82
   adding manifests
-  manifests: 1/4 chunks (25.00%)
-  manifests: 2/4 chunks (50.00%)
-  manifests: 3/4 chunks (75.00%)
-  manifests: 4/4 chunks (100.00%)
   adding file changes
   adding abc.txt revisions
-  files: 1/4 chunks (25.00%)
   adding foo/Bar/file.txt revisions
-  files: 2/4 chunks (50.00%)
   adding foo/file.txt revisions
-  files: 3/4 chunks (75.00%)
   adding quux/file.py revisions
-  files: 4/4 chunks (100.00%)
   added 4 changesets with 4 changes to 4 files (+1 heads)
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "astro"
@@ -1717,6 +1670,8 @@
   acl: acl.allow not enabled
   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 2101
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
@@ -1754,41 +1709,29 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundling: 1/4 changesets (25.00%)
-  bundling: 2/4 changesets (50.00%)
-  bundling: 3/4 changesets (75.00%)
-  bundling: 4/4 changesets (100.00%)
-  bundling: 1/4 manifests (25.00%)
-  bundling: 2/4 manifests (50.00%)
-  bundling: 3/4 manifests (75.00%)
-  bundling: 4/4 manifests (100.00%)
-  bundling: abc.txt 1/4 files (25.00%)
-  bundling: foo/Bar/file.txt 2/4 files (50.00%)
-  bundling: foo/file.txt 3/4 files (75.00%)
-  bundling: quux/file.py 4/4 files (100.00%)
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
-  changesets: 4 chunks
   add changeset e8fc755d4d82
   adding manifests
-  manifests: 1/4 chunks (25.00%)
-  manifests: 2/4 chunks (50.00%)
-  manifests: 3/4 chunks (75.00%)
-  manifests: 4/4 chunks (100.00%)
   adding file changes
   adding abc.txt revisions
-  files: 1/4 chunks (25.00%)
   adding foo/Bar/file.txt revisions
-  files: 2/4 chunks (50.00%)
   adding foo/file.txt revisions
-  files: 3/4 chunks (75.00%)
   adding quux/file.py revisions
-  files: 4/4 chunks (100.00%)
   added 4 changesets with 4 changes to 4 files (+1 heads)
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "astro"
@@ -1797,6 +1740,8 @@
   acl: acl.allow not enabled
   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 2101
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
@@ -1828,41 +1773,29 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundling: 1/4 changesets (25.00%)
-  bundling: 2/4 changesets (50.00%)
-  bundling: 3/4 changesets (75.00%)
-  bundling: 4/4 changesets (100.00%)
-  bundling: 1/4 manifests (25.00%)
-  bundling: 2/4 manifests (50.00%)
-  bundling: 3/4 manifests (75.00%)
-  bundling: 4/4 manifests (100.00%)
-  bundling: abc.txt 1/4 files (25.00%)
-  bundling: foo/Bar/file.txt 2/4 files (50.00%)
-  bundling: foo/file.txt 3/4 files (75.00%)
-  bundling: quux/file.py 4/4 files (100.00%)
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
-  changesets: 4 chunks
   add changeset e8fc755d4d82
   adding manifests
-  manifests: 1/4 chunks (25.00%)
-  manifests: 2/4 chunks (50.00%)
-  manifests: 3/4 chunks (75.00%)
-  manifests: 4/4 chunks (100.00%)
   adding file changes
   adding abc.txt revisions
-  files: 1/4 chunks (25.00%)
   adding foo/Bar/file.txt revisions
-  files: 2/4 chunks (50.00%)
   adding foo/file.txt revisions
-  files: 3/4 chunks (75.00%)
   adding quux/file.py revisions
-  files: 4/4 chunks (100.00%)
   added 4 changesets with 4 changes to 4 files (+1 heads)
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "george"
@@ -1878,9 +1811,23 @@
   acl: path access granted: "911600dab2ae"
   acl: branch access granted: "e8fc755d4d82" on branch "foobar"
   acl: path access granted: "e8fc755d4d82"
+  bundle2-input-part: total payload size 2101
+  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: 4 parts total
   updating the branch cache
+  bundle2-output-bundle: "HG20", 3 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: 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
+  bundle2-input-bundle: 2 parts total
   listing keys for "phases"
-  try to push obsolete markers to remote
   repository tip rolled back to revision 2 (undo push)
   2:fb35475503ef
   
@@ -1919,41 +1866,29 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundling: 1/4 changesets (25.00%)
-  bundling: 2/4 changesets (50.00%)
-  bundling: 3/4 changesets (75.00%)
-  bundling: 4/4 changesets (100.00%)
-  bundling: 1/4 manifests (25.00%)
-  bundling: 2/4 manifests (50.00%)
-  bundling: 3/4 manifests (75.00%)
-  bundling: 4/4 manifests (100.00%)
-  bundling: abc.txt 1/4 files (25.00%)
-  bundling: foo/Bar/file.txt 2/4 files (50.00%)
-  bundling: foo/file.txt 3/4 files (75.00%)
-  bundling: quux/file.py 4/4 files (100.00%)
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
-  changesets: 4 chunks
   add changeset e8fc755d4d82
   adding manifests
-  manifests: 1/4 chunks (25.00%)
-  manifests: 2/4 chunks (50.00%)
-  manifests: 3/4 chunks (75.00%)
-  manifests: 4/4 chunks (100.00%)
   adding file changes
   adding abc.txt revisions
-  files: 1/4 chunks (25.00%)
   adding foo/Bar/file.txt revisions
-  files: 2/4 chunks (50.00%)
   adding foo/file.txt revisions
-  files: 3/4 chunks (75.00%)
   adding quux/file.py revisions
-  files: 4/4 chunks (100.00%)
   added 4 changesets with 4 changes to 4 files (+1 heads)
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "george"
@@ -1969,9 +1904,23 @@
   acl: path access granted: "911600dab2ae"
   acl: branch access granted: "e8fc755d4d82" on branch "foobar"
   acl: path access granted: "e8fc755d4d82"
+  bundle2-input-part: total payload size 2101
+  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: 4 parts total
   updating the branch cache
+  bundle2-output-bundle: "HG20", 3 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: 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
+  bundle2-input-bundle: 2 parts total
   listing keys for "phases"
-  try to push obsolete markers to remote
   repository tip rolled back to revision 2 (undo push)
   2:fb35475503ef
   
@@ -2009,41 +1958,29 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundling: 1/4 changesets (25.00%)
-  bundling: 2/4 changesets (50.00%)
-  bundling: 3/4 changesets (75.00%)
-  bundling: 4/4 changesets (100.00%)
-  bundling: 1/4 manifests (25.00%)
-  bundling: 2/4 manifests (50.00%)
-  bundling: 3/4 manifests (75.00%)
-  bundling: 4/4 manifests (100.00%)
-  bundling: abc.txt 1/4 files (25.00%)
-  bundling: foo/Bar/file.txt 2/4 files (50.00%)
-  bundling: foo/file.txt 3/4 files (75.00%)
-  bundling: quux/file.py 4/4 files (100.00%)
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
-  changesets: 4 chunks
   add changeset e8fc755d4d82
   adding manifests
-  manifests: 1/4 chunks (25.00%)
-  manifests: 2/4 chunks (50.00%)
-  manifests: 3/4 chunks (75.00%)
-  manifests: 4/4 chunks (100.00%)
   adding file changes
   adding abc.txt revisions
-  files: 1/4 chunks (25.00%)
   adding foo/Bar/file.txt revisions
-  files: 2/4 chunks (50.00%)
   adding foo/file.txt revisions
-  files: 3/4 chunks (75.00%)
   adding quux/file.py revisions
-  files: 4/4 chunks (100.00%)
   added 4 changesets with 4 changes to 4 files (+1 heads)
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "george"
@@ -2052,6 +1989,8 @@
   acl: acl.allow not enabled
   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 2101
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
@@ -2088,41 +2027,29 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundling: 1/4 changesets (25.00%)
-  bundling: 2/4 changesets (50.00%)
-  bundling: 3/4 changesets (75.00%)
-  bundling: 4/4 changesets (100.00%)
-  bundling: 1/4 manifests (25.00%)
-  bundling: 2/4 manifests (50.00%)
-  bundling: 3/4 manifests (75.00%)
-  bundling: 4/4 manifests (100.00%)
-  bundling: abc.txt 1/4 files (25.00%)
-  bundling: foo/Bar/file.txt 2/4 files (50.00%)
-  bundling: foo/file.txt 3/4 files (75.00%)
-  bundling: quux/file.py 4/4 files (100.00%)
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
-  changesets: 4 chunks
   add changeset e8fc755d4d82
   adding manifests
-  manifests: 1/4 chunks (25.00%)
-  manifests: 2/4 chunks (50.00%)
-  manifests: 3/4 chunks (75.00%)
-  manifests: 4/4 chunks (100.00%)
   adding file changes
   adding abc.txt revisions
-  files: 1/4 chunks (25.00%)
   adding foo/Bar/file.txt revisions
-  files: 2/4 chunks (50.00%)
   adding foo/file.txt revisions
-  files: 3/4 chunks (75.00%)
   adding quux/file.py revisions
-  files: 4/4 chunks (100.00%)
   added 4 changesets with 4 changes to 4 files (+1 heads)
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "astro"
@@ -2138,9 +2065,23 @@
   acl: path access granted: "911600dab2ae"
   acl: branch access granted: "e8fc755d4d82" on branch "foobar"
   acl: path access granted: "e8fc755d4d82"
+  bundle2-input-part: total payload size 2101
+  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: 4 parts total
   updating the branch cache
+  bundle2-output-bundle: "HG20", 3 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: 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
+  bundle2-input-bundle: 2 parts total
   listing keys for "phases"
-  try to push obsolete markers to remote
   repository tip rolled back to revision 2 (undo push)
   2:fb35475503ef
   
@@ -2172,41 +2113,29 @@
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
-  bundling: 1/4 changesets (25.00%)
-  bundling: 2/4 changesets (50.00%)
-  bundling: 3/4 changesets (75.00%)
-  bundling: 4/4 changesets (100.00%)
-  bundling: 1/4 manifests (25.00%)
-  bundling: 2/4 manifests (50.00%)
-  bundling: 3/4 manifests (75.00%)
-  bundling: 4/4 manifests (100.00%)
-  bundling: abc.txt 1/4 files (25.00%)
-  bundling: foo/Bar/file.txt 2/4 files (50.00%)
-  bundling: foo/file.txt 3/4 files (75.00%)
-  bundling: quux/file.py 4/4 files (100.00%)
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 155 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: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
   adding changesets
-  changesets: 1 chunks
   add changeset ef1ea85a6374
-  changesets: 2 chunks
   add changeset f9cafe1212c8
-  changesets: 3 chunks
   add changeset 911600dab2ae
-  changesets: 4 chunks
   add changeset e8fc755d4d82
   adding manifests
-  manifests: 1/4 chunks (25.00%)
-  manifests: 2/4 chunks (50.00%)
-  manifests: 3/4 chunks (75.00%)
-  manifests: 4/4 chunks (100.00%)
   adding file changes
   adding abc.txt revisions
-  files: 1/4 chunks (25.00%)
   adding foo/Bar/file.txt revisions
-  files: 2/4 chunks (50.00%)
   adding foo/file.txt revisions
-  files: 3/4 chunks (75.00%)
   adding quux/file.py revisions
-  files: 4/4 chunks (100.00%)
   added 4 changesets with 4 changes to 4 files (+1 heads)
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: checking access for user "george"
@@ -2215,6 +2144,8 @@
   acl: acl.allow not enabled
   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 2101
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
--- a/tests/test-add.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-add.t	Sat Jul 18 17:32:38 2015 -0500
@@ -75,6 +75,13 @@
 
   $ hg ci -m 0 --traceback
 
+  $ hg log -r "heads(. or wdir() & file('**'))"
+  changeset:   0:* (glob)
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     0
+  
 should fail
 
   $ hg add a
@@ -99,6 +106,15 @@
   M a
   ? a.orig
 
+wdir doesn't cause a crash, and can be dynamically selected if dirty
+
+  $ hg log -r "heads(. or wdir() & file('**'))"
+  changeset:   2147483647:ffffffffffff
+  parent:      2:* (glob)
+  parent:      1:* (glob)
+  user:        test
+  date:        * (glob)
+  
 should fail
 
   $ hg add a
--- a/tests/test-archive-symlinks.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-archive-symlinks.t	Sat Jul 18 17:32:38 2015 -0500
@@ -18,7 +18,7 @@
 
   $ cd "$origdir"
   $ cd archive
-  $ "$TESTDIR/readlink.py" dangling
+  $ readlink.py dangling
   dangling -> nothing
 
 tar
@@ -26,7 +26,7 @@
   $ cd "$origdir"
   $ tar xf archive.tar
   $ cd tar
-  $ "$TESTDIR/readlink.py" dangling
+  $ readlink.py dangling
   dangling -> nothing
 
 zip
@@ -34,7 +34,7 @@
   $ cd "$origdir"
   $ unzip archive.zip > /dev/null 2>&1
   $ cd zip
-  $ "$TESTDIR/readlink.py" dangling
+  $ readlink.py dangling
   dangling -> nothing
 
   $ cd ..
--- a/tests/test-archive.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-archive.t	Sat Jul 18 17:32:38 2015 -0500
@@ -27,11 +27,11 @@
   >     hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
   >     cat hg.pid >> $DAEMON_PIDS
   >     echo % $1 allowed should give 200
-  >     "$TESTDIR/get-with-headers.py" localhost:$HGPORT "archive/tip.$2" | head -n 1
+  >     get-with-headers.py localhost:$HGPORT "archive/tip.$2" | head -n 1
   >     echo % $3 and $4 disallowed should both give 403
-  >     "$TESTDIR/get-with-headers.py" localhost:$HGPORT "archive/tip.$3" | head -n 1
-  >     "$TESTDIR/get-with-headers.py" localhost:$HGPORT "archive/tip.$4" | head -n 1
-  >     "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  >     get-with-headers.py localhost:$HGPORT "archive/tip.$3" | head -n 1
+  >     get-with-headers.py localhost:$HGPORT "archive/tip.$4" | head -n 1
+  >     killdaemons.py
   >     cat errors.log
   >     cp .hg/hgrc-base .hg/hgrc
   > }
@@ -63,7 +63,7 @@
 
 invalid arch type should give 404
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT "archive/tip.invalid" | head -n 1
+  $ get-with-headers.py localhost:$HGPORT "archive/tip.invalid" | head -n 1
   404 Unsupported archive type: None
 
   $ TIP=`hg id -v | cut -f1 -d' '`
@@ -134,7 +134,7 @@
   $ python getarchive.py "$TIP" gz relre:baz
   HTTP Error 404: file(s) not found: relre:baz
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
 
   $ hg archive -t tar test.tar
   $ tar tf test.tar
@@ -145,7 +145,7 @@
   test/baz/bletch
   test/foo
 
-  $ hg archive --debug -t tbz2 -X baz test.tar.bz2
+  $ hg archive --debug -t tbz2 -X baz test.tar.bz2 --config progress.debug=true
   archiving: 0/4 files (0.00%)
   archiving: .hgsub 1/4 files (25.00%)
   archiving: .hgsubstate 2/4 files (50.00%)
@@ -282,18 +282,11 @@
   $ hg archive ../with-progress
   \r (no-eol) (esc)
   archiving [                                           ] 0/6\r (no-eol) (esc)
-  archiving [                                           ] 0/6\r (no-eol) (esc)
   archiving [======>                                    ] 1/6\r (no-eol) (esc)
-  archiving [======>                                    ] 1/6\r (no-eol) (esc)
-  archiving [=============>                             ] 2/6\r (no-eol) (esc)
   archiving [=============>                             ] 2/6\r (no-eol) (esc)
   archiving [====================>                      ] 3/6\r (no-eol) (esc)
-  archiving [====================>                      ] 3/6\r (no-eol) (esc)
-  archiving [===========================>               ] 4/6\r (no-eol) (esc)
   archiving [===========================>               ] 4/6\r (no-eol) (esc)
   archiving [==================================>        ] 5/6\r (no-eol) (esc)
-  archiving [==================================>        ] 5/6\r (no-eol) (esc)
-  archiving [==========================================>] 6/6\r (no-eol) (esc)
   archiving [==========================================>] 6/6\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
 
--- a/tests/test-backout.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-backout.t	Sat Jul 18 17:32:38 2015 -0500
@@ -42,6 +42,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 3 draft
 
 commit option
 
@@ -69,6 +70,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 4 draft
 
   $ echo ypples > a
   $ hg commit -d '5 0' -m ypples
@@ -83,6 +85,7 @@
   branch: default
   commit: 1 unresolved (clean)
   update: (current)
+  phases: 5 draft
 
 file that was removed is recreated
 (this also tests that editor is not invoked if the commit message is
@@ -110,6 +113,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 3 draft
 
 backout of backout is as if nothing happened
 
@@ -124,6 +128,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 4 draft
 
 across branch
 
@@ -144,6 +149,7 @@
   branch: default
   commit: (clean)
   update: 1 new changesets (update)
+  phases: 2 draft
 
 should fail
 
@@ -160,6 +166,7 @@
   branch: default
   commit: (clean)
   update: 1 new changesets, 2 branch heads (merge)
+  phases: 3 draft
 
 should fail
 
@@ -172,6 +179,7 @@
   branch: default
   commit: (clean)
   update: 1 new changesets, 2 branch heads (merge)
+  phases: 3 draft
 
 backout with merge
 
@@ -189,6 +197,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 1 draft
 
 remove line 1
 
@@ -213,6 +222,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 5 draft
 
 check line 1 is back
 
@@ -241,6 +251,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 3 draft
 
 without --merge
   $ hg backout -d '3 0' 1 --tool=true
@@ -258,6 +269,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 3 draft
 
 with --merge
   $ hg backout --merge -d '3 0' 1 --tool=true
@@ -302,6 +314,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 5 draft
 
 backout of merge should fail
 
@@ -332,6 +345,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 6 draft
 
   $ hg rollback
   repository tip rolled back to revision 4 (undo commit)
@@ -344,6 +358,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 5 draft
 
   $ hg backout -d '6 0' --parent 3 4 --tool=true
   removing c
@@ -354,6 +369,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 6 draft
 
   $ cd ..
 
@@ -373,7 +389,6 @@
   adding file1
   $ hg branch branch2
   marked working directory as branch branch2
-  (branches are permanent and global, did you want a bookmark?)
   $ echo branch2 > file2
   $ hg ci -d '2 0' -Am file2
   adding file2
@@ -394,6 +409,7 @@
   branch: branch2
   commit: 1 removed
   update: (current)
+  phases: 3 draft
 
 with --merge
 (this also tests that editor is invoked if '--edit' is specified
@@ -424,6 +440,7 @@
   branch: branch2
   commit: 1 removed (merge)
   update: (current)
+  phases: 4 draft
   $ hg update -q -C 2
 
 on branch2 with branch1 not merged, so file1 should still exist:
@@ -440,6 +457,7 @@
   branch: branch2
   commit: (clean)
   update: 1 new changesets, 2 branch heads (merge)
+  phases: 4 draft
 
 on branch2 with branch1 merged, so file1 should be gone:
 
@@ -458,6 +476,7 @@
   branch: branch2
   commit: (clean)
   update: (current)
+  phases: 5 draft
 
 on branch1, so no file1 and file2:
 
@@ -474,6 +493,7 @@
   branch: branch1
   commit: (clean)
   update: (current)
+  phases: 5 draft
 
   $ cd ..
 
@@ -553,6 +573,7 @@
   branch: default
   commit: 1 unresolved (clean)
   update: (current)
+  phases: 3 draft
   $ hg resolve --all --debug
   picked tool 'internal:merge' for foo (binary False symlink False)
   merging foo
@@ -570,6 +591,7 @@
   branch: default
   commit: 1 modified, 1 unknown
   update: (current)
+  phases: 3 draft
   $ cat foo
   one
   two
--- a/tests/test-bad-extension.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-bad-extension.t	Sat Jul 18 17:32:38 2015 -0500
@@ -15,3 +15,32 @@
   hg help [-ec] [TOPIC]
   
   show help for a given topic or a help overview
+
+show traceback
+
+  $ hg -q help help --traceback 2>&1 | grep -v '^  '
+  *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
+  Traceback (most recent call last):
+  Exception: bit bucket overflow
+  *** failed to import extension badext2: No module named badext2
+  Traceback (most recent call last):
+  ImportError: No module named badext2
+  hg help [-ec] [TOPIC]
+  
+  show help for a given topic or a help overview
+
+show traceback for ImportError of hgext.name if debug is set
+(note that --debug option isn't applied yet when loading extensions)
+
+  $ hg help help --traceback --config ui.debug=True 2>&1 \
+  > | grep -v '^  ' | head -n10
+  *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
+  Traceback (most recent call last):
+  Exception: bit bucket overflow
+  could not import hgext.badext2 (No module named badext2): trying badext2
+  Traceback (most recent call last):
+  ImportError: No module named badext2
+  *** failed to import extension badext2: No module named badext2
+  Traceback (most recent call last):
+  ImportError: No module named badext2
+  hg help [-ec] [TOPIC]
--- a/tests/test-bad-pull.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-bad-pull.t	Sat Jul 18 17:32:38 2015 -0500
@@ -18,4 +18,4 @@
   $ hg clone http://localhost:$HGPORT/foo copy2
   abort: HTTP Error 404: * (glob)
   [255]
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
--- a/tests/test-basic.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-basic.t	Sat Jul 18 17:32:38 2015 -0500
@@ -5,7 +5,7 @@
   defaults.commit=-d "0 0"
   defaults.shelve=--date "0 0"
   defaults.tag=-d "0 0"
-  devel.all=true
+  devel.all-warnings=true
   largefiles.usercache=$TESTTMP/.cache/largefiles (glob)
   ui.slash=True
   ui.interactive=False
--- a/tests/test-bheads.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-bheads.t	Sat Jul 18 17:32:38 2015 -0500
@@ -37,7 +37,6 @@
   $ hg add b
   $ hg branch b
   marked working directory as branch b
-  (branches are permanent and global, did you want a bookmark?)
   $ hg commit -m "Adding b branch"
   $ heads
   2: Adding b branch (b)
@@ -119,7 +118,6 @@
   $ hg add c
   $ hg branch c
   marked working directory as branch c
-  (branches are permanent and global, did you want a bookmark?)
   $ hg commit -m "Adding c branch"
   $ heads
   7: Adding c branch (c)
@@ -302,7 +300,6 @@
   $ hg up -q null
   $ hg branch -f b
   marked working directory as branch b
-  (branches are permanent and global, did you want a bookmark?)
   $ echo 1 > bb
   $ hg ci -Am "b4 (NN): new topo root for branch b"
   adding bb
@@ -317,7 +314,6 @@
 
   $ hg branch -f default
   marked working directory as branch default
-  (branches are permanent and global, did you want a bookmark?)
   $ echo 1 > aa
   $ hg ci -Am "a6 (BN): new branch root"
   adding aa
@@ -337,7 +333,6 @@
   $ hg merge -q 3
   $ hg branch -f default
   marked working directory as branch default
-  (branches are permanent and global, did you want a bookmark?)
   $ hg ci -m "a8 (BB): weird new branch root"
   created new head
 
--- a/tests/test-bisect.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-bisect.t	Sat Jul 18 17:32:38 2015 -0500
@@ -190,6 +190,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 32 draft
   $ hg bisect -g 1
   Testing changeset 16:a2e6ea4973e9 (30 changesets remaining, ~4 tests)
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-bisect3.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-bisect3.t	Sat Jul 18 17:32:38 2015 -0500
@@ -230,3 +230,20 @@
   I 2:e1355ee1f23e
   G 1:ce7c85e06a9f
   G 0:b4e73ffab476
+
+  $ hg --config extensions.color= --color=debug log --quiet --style bisect
+  [log.bisect| ] 14:cbf2f3105bbf
+  [log.bisect| ] 13:e07efca37c43
+  [log.bisect bisect.bad|B] 12:98c6b56349c0
+  [log.bisect bisect.bad|B] 11:03f491376e63
+  [log.bisect bisect.bad|B] 10:c012b15e2409
+  [log.bisect bisect.untested|U] 9:2197c557e14c
+  [log.bisect bisect.untested|U] 8:e74a86251f58
+  [log.bisect bisect.skipped|S] 7:a5f87041c899
+  [log.bisect bisect.good|G] 6:7d997bedcd8d
+  [log.bisect bisect.good|G] 5:2dd1875f1028
+  [log.bisect bisect.good|G] 4:2a1daef14cd4
+  [log.bisect bisect.ignored|I] 3:8417d459b90c
+  [log.bisect bisect.ignored|I] 2:e1355ee1f23e
+  [log.bisect bisect.good|G] 1:ce7c85e06a9f
+  [log.bisect bisect.good|G] 0:b4e73ffab476
--- a/tests/test-bookmarks-current.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-bookmarks-current.t	Sat Jul 18 17:32:38 2015 -0500
@@ -69,7 +69,7 @@
    * Y                         0:719295282060
      Z                         -1:000000000000
 
-Verify that switching to Z updates the current bookmark:
+Verify that switching to Z updates the active bookmark:
   $ hg update Z
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   (activating bookmark Z)
@@ -118,7 +118,7 @@
    * Y                         0:719295282060
      Z                         0:719295282060
 
-deactivate current bookmark using -i
+deactivate active bookmark using -i
 
   $ hg bookmark -i Y
   $ hg bookmarks
@@ -137,7 +137,7 @@
    * Y                         0:719295282060
      Z                         0:719295282060
 
-deactivate current bookmark while renaming
+deactivate active bookmark while renaming
 
   $ hg bookmark -i -m Y X
   $ hg bookmarks
@@ -193,3 +193,12 @@
   $ hg up -q .
   $ test -f .hg/bookmarks.current
   [1]
+
+issue 4552 -- simulate a pull moving the active bookmark
+
+  $ hg up -q X
+  $ printf "Z" > .hg/bookmarks.current
+  $ hg log -T '{activebookmark}\n' -r Z
+  Z
+  $ hg log -T '{bookmarks % "{active}\n"}' -r Z
+  Z
--- a/tests/test-bookmarks-pushpull.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-bookmarks-pushpull.t	Sat Jul 18 17:32:38 2015 -0500
@@ -7,6 +7,9 @@
   > publish=False
   > [experimental]
   > evolution=createmarkers,exchange
+  > # drop me once bundle2 is the default,
+  > # added to get test change early.
+  > bundle2-exp = True
   > EOF
 
 initialize
@@ -260,7 +263,14 @@
 
   $ cd ..
   $ hg clone -q a pull-race
-  $ hg clone -q pull-race pull-race2
+
+We want to use http because it is stateless and therefore more susceptible to
+race conditions
+
+  $ hg -R pull-race serve -p $HGPORT -d --pid-file=pull-race.pid -E main-error.log
+  $ cat pull-race.pid >> $DAEMON_PIDS
+
+  $ hg clone -q http://localhost:$HGPORT/ pull-race2
   $ cd pull-race
   $ hg up -q Y
   $ echo c4 > f2
@@ -270,13 +280,23 @@
   > [hooks]
   > outgoing.makecommit = hg ci -Am5; echo committed in pull-race
   > EOF
-  $ cd ../pull-race2
+
+(new config needs a server restart)
+
+  $ cd ..
+  $ killdaemons.py
+  $ hg -R pull-race serve -p $HGPORT -d --pid-file=pull-race.pid -E main-error.log
+  $ cat pull-race.pid >> $DAEMON_PIDS
+  $ cd pull-race2
+  $ hg -R $TESTTMP/pull-race book
+     @                         1:0d2164f0ce0d
+     X                         1:0d2164f0ce0d
+   * Y                         4:b0a5eff05604
+     Z                         1:0d2164f0ce0d
   $ hg pull
-  pulling from $TESTTMP/pull-race (glob)
+  pulling from http://localhost:$HGPORT/
   searching for changes
   adding changesets
-  adding f3
-  committed in pull-race
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
@@ -287,6 +307,47 @@
      X                         1:0d2164f0ce0d
      Y                         4:b0a5eff05604
      Z                         1:0d2164f0ce0d
+
+Update a bookmark right after the initial lookup -B (issue4689)
+
+  $ echo c6 > ../pull-race/f3 # to be committed during the race
+  $ cat <<EOF > ../pull-race/.hg/hgrc
+  > [hooks]
+  > # If anything to commit, commit it right after the first key listing used
+  > # during lookup. This makes the commit appear before the actual getbundle
+  > # call.
+  > listkeys.makecommit= ((hg st | grep -q M) && (hg commit -m race; echo commited in pull-race)) || exit 0
+  > EOF
+
+(new config need server restart)
+
+  $ killdaemons.py
+  $ hg -R ../pull-race serve -p $HGPORT -d --pid-file=../pull-race.pid -E main-error.log
+  $ cat ../pull-race.pid >> $DAEMON_PIDS
+
+  $ hg -R $TESTTMP/pull-race book
+     @                         1:0d2164f0ce0d
+     X                         1:0d2164f0ce0d
+   * Y                         5:35d1ef0a8d1b
+     Z                         1:0d2164f0ce0d
+  $ hg pull -B Y
+  pulling from http://localhost:$HGPORT/
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  updating bookmark Y
+  (run 'hg update' to get a working copy)
+  $ hg book
+   * @                         1:0d2164f0ce0d
+     X                         1:0d2164f0ce0d
+     Y                         5:35d1ef0a8d1b
+     Z                         1:0d2164f0ce0d
+
+(done with this section of the test)
+
+  $ killdaemons.py
   $ cd ../b
 
 diverging a remote bookmark fails
@@ -368,6 +429,7 @@
   remote: adding manifests
   remote: adding file changes
   remote: added 2 changesets with 2 changes to 1 files (+1 heads)
+  remote: 2 new obsolescence markers
   updating bookmark Y
   $ hg -R ../a book
      @                         1:0d2164f0ce0d
@@ -436,6 +498,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 3 files (+2 heads)
+  2 new obsolescence markers
   updating to bookmark @
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R cloned-bookmarks bookmarks
@@ -571,6 +634,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 3 files (+2 heads)
+  2 new obsolescence markers
   updating to bookmark @
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd addmarks
@@ -679,7 +743,7 @@
   > push_ssl = false
   > allow_push = *
   > EOF
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
   $ hg -R ../issue4455-dest serve -p $HGPORT -d --pid-file=../issue4455.pid -E ../issue4455-error.log
   $ cat ../issue4455.pid >> $DAEMON_PIDS
 
@@ -691,15 +755,25 @@
   searching for changes
   no changes found
   pushkey-abort: prepushkey hook exited with status 1
-  exporting bookmark @ failed!
-  [1]
+  abort: exporting bookmark @ failed!
+  [255]
   $ hg -R ../issue4455-dest/ bookmarks
   no bookmarks set
 
 Using ssh
 ---------
 
-  $ hg push -B @ ssh
+  $ hg push -B @ ssh --config experimental.bundle2-exp=True
+  pushing to ssh://user@dummy/issue4455-dest
+  searching for changes
+  no changes found
+  remote: pushkey-abort: prepushkey hook exited with status 1
+  abort: exporting bookmark @ failed!
+  [255]
+  $ hg -R ../issue4455-dest/ bookmarks
+  no bookmarks set
+
+  $ hg push -B @ ssh --config experimental.bundle2-exp=False
   pushing to ssh://user@dummy/issue4455-dest
   searching for changes
   no changes found
@@ -712,7 +786,17 @@
 Using http
 ----------
 
-  $ hg push -B @ http
+  $ hg push -B @ http --config experimental.bundle2-exp=True
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  no changes found
+  remote: pushkey-abort: prepushkey hook exited with status 1
+  abort: exporting bookmark @ failed!
+  [255]
+  $ hg -R ../issue4455-dest/ bookmarks
+  no bookmarks set
+
+  $ hg push -B @ http --config experimental.bundle2-exp=False
   pushing to http://localhost:$HGPORT/
   searching for changes
   no changes found
--- a/tests/test-bookmarks-strip.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-bookmarks-strip.t	Sat Jul 18 17:32:38 2015 -0500
@@ -63,55 +63,3 @@
   $ hg book
      test                      0:5c9ad3787638
      test2                     0:5c9ad3787638
-
-immediate rollback and reentrancy issue
-
-  $ echo "mq=!" >> $HGRCPATH
-  $ hg init repo
-  $ cd repo
-  $ echo a > a
-  $ hg ci -Am adda
-  adding a
-  $ echo b > b
-  $ hg ci -Am addb
-  adding b
-  $ hg bookmarks markb
-  $ hg rollback
-  repository tip rolled back to revision 0 (undo commit)
-  working directory now based on revision 0
-
-are you there?
-
-  $ hg bookmarks
-  no bookmarks set
-
-can we commit? (issue2692)
-
-  $ echo c > c
-  $ hg ci -Am rockon
-  adding c
-
-can you be added again?
-
-  $ hg bookmarks markb
-  $ hg bookmarks
-   * markb                     1:fdb34407462c
-
-rollback dry run with rollback information
-
-  $ hg rollback -n
-  repository tip rolled back to revision 0 (undo commit)
-  $ hg bookmarks
-   * markb                     1:fdb34407462c
-
-rollback dry run with rollback information and no commit undo
-
-  $ rm .hg/store/undo
-  $ hg rollback -n
-  no rollback information available
-  [1]
-  $ hg bookmarks
-   * markb                     1:fdb34407462c
-
-  $ cd ..
-
--- a/tests/test-bookmarks.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-bookmarks.t	Sat Jul 18 17:32:38 2015 -0500
@@ -393,6 +393,7 @@
   bookmarks: *Z Y x  y
   commit: (clean)
   update: 1 new changesets, 2 branch heads (merge)
+  phases: 3 draft
 
 test id
 
@@ -402,10 +403,15 @@
 test rollback
 
   $ echo foo > f1
+  $ hg bookmark tmp-rollback
   $ hg ci -Amr
   adding f1
-  $ hg bookmark -f Y -r 1
-  $ hg bookmark -f Z -r 1
+  $ hg bookmarks
+     X2                        1:925d80f479bb
+     Y                         2:db815d6d32e6
+     Z                         2:db815d6d32e6
+   * tmp-rollback              3:2bf5cfec5864
+     x  y                      2:db815d6d32e6
   $ hg rollback
   repository tip rolled back to revision 2 (undo commit)
   working directory now based on revision 2
@@ -413,7 +419,18 @@
      X2                        1:925d80f479bb
      Y                         2:db815d6d32e6
      Z                         2:db815d6d32e6
+   * tmp-rollback              2:db815d6d32e6
      x  y                      2:db815d6d32e6
+  $ hg bookmark -f Z -r 1
+  $ hg rollback
+  repository tip rolled back to revision 2 (undo bookmark)
+  $ hg bookmarks
+     X2                        1:925d80f479bb
+     Y                         2:db815d6d32e6
+     Z                         2:db815d6d32e6
+   * tmp-rollback              2:db815d6d32e6
+     x  y                      2:db815d6d32e6
+  $ hg bookmark -d tmp-rollback
 
 activate bookmark on working dir parent without --force
 
@@ -529,7 +546,7 @@
   added 2 changesets with 2 changes to 2 files (+1 heads)
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
-update to current bookmark if it's not the parent
+update to active bookmark if it's not the parent
 
   $ hg summary
   parent: 2:db815d6d32e6 
@@ -538,6 +555,7 @@
   bookmarks: *Z Y x  y
   commit: 1 added, 1 unknown (new branch head)
   update: 2 new changesets (update)
+  phases: 5 draft
   $ hg update
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updating bookmark Z
@@ -655,6 +673,31 @@
      date:        Thu Jan 01 00:00:00 1970 +0000
      summary:     0
   
+test non-linear update not clearing active bookmark
+
+  $ hg up 1
+  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  (leaving bookmark four)
+  $ hg book drop
+  $ hg up -C
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (leaving bookmark drop)
+  $ hg sum
+  parent: 2:db815d6d32e6 
+   2
+  branch: default
+  bookmarks: should-end-on-two
+  commit: 2 unknown (clean)
+  update: 1 new changesets, 2 branch heads (merge)
+  phases: 4 draft
+  $ hg book
+     drop                      1:925d80f479bb
+     four                      3:9ba5f110a0b3
+     should-end-on-two         2:db815d6d32e6
+  $ hg book -d drop
+  $ hg up four
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark four)
 
 test clearing divergent bookmarks of linear ancestors
 
--- a/tests/test-branch-option.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-branch-option.t	Sat Jul 18 17:32:38 2015 -0500
@@ -14,7 +14,6 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg branch c
   marked working directory as branch c
-  (branches are permanent and global, did you want a bookmark?)
   $ echo c > foo
   $ hg ci -d '0 0' -mc
   $ hg tag -l z
@@ -31,21 +30,18 @@
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg branch b
   marked working directory as branch b
-  (branches are permanent and global, did you want a bookmark?)
   $ echo b > foo
   $ hg ci -d '0 0' -mb
   $ hg up 0
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg --encoding utf-8 branch æ
   marked working directory as branch \xc3\xa6 (esc)
-  (branches are permanent and global, did you want a bookmark?)
   $ echo ae1 > foo
   $ hg ci -d '0 0' -mae1
   $ hg up 0
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg --encoding utf-8 branch -f æ
   marked working directory as branch \xc3\xa6 (esc)
-  (branches are permanent and global, did you want a bookmark?)
   $ echo ae2 > foo
   $ hg ci -d '0 0' -mae2
   created new head
@@ -53,7 +49,6 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg branch -f b
   marked working directory as branch b
-  (branches are permanent and global, did you want a bookmark?)
   $ echo b2 > foo
   $ hg ci -d '0 0' -mb2
   created new head
--- a/tests/test-branches.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-branches.t	Sat Jul 18 17:32:38 2015 -0500
@@ -13,7 +13,6 @@
 
   $ hg branch q
   marked working directory as branch q
-  (branches are permanent and global, did you want a bookmark?)
   $ echo 'aa' >a
   $ hg branch -C
   reset working directory to branch a
@@ -25,7 +24,6 @@
   $ hg add b
   $ hg branch b
   marked working directory as branch b
-  (branches are permanent and global, did you want a bookmark?)
   $ hg commit -d '2 0' -m "Adding b branch"
 
   $ echo 'bh1' >bh1
@@ -42,7 +40,6 @@
   $ hg add c
   $ hg branch c
   marked working directory as branch c
-  (branches are permanent and global, did you want a bookmark?)
   $ hg commit -d '5 0' -m "Adding c branch"
 
 reserved names
@@ -101,7 +98,6 @@
   $ hg add d
   $ hg branch 'a branch name much longer than the default justification used by branches'
   marked working directory as branch a branch name much longer than the default justification used by branches
-  (branches are permanent and global, did you want a bookmark?)
   $ hg commit -d '6 0' -m "Adding d branch"
 
   $ hg branches
@@ -601,7 +597,6 @@
 cache is updated when committing
   $ hg branch i-will-regret-this
   marked working directory as branch i-will-regret-this
-  (branches are permanent and global, did you want a bookmark?)
   $ hg ci -m regrets
   $ f --size .hg/cache/rbc-*
   .hg/cache/rbc-names-v1: size=106
--- a/tests/test-bundle.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-bundle.t	Sat Jul 18 17:32:38 2015 -0500
@@ -211,7 +211,7 @@
 Pull ../full.hg into empty (with hook)
 
   $ echo "[hooks]" >> .hg/hgrc
-  $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup" >> .hg/hgrc
+  $ echo "changegroup = printenv.py changegroup" >> .hg/hgrc
 
 doesn't work (yet ?)
 
@@ -629,7 +629,7 @@
 
 == bundling
 
-  $ hg bundle bundle.hg part --debug
+  $ hg bundle bundle.hg part --debug --config progress.debug=true
   query 1; heads
   searching for changes
   all remote heads known locally
--- a/tests/test-bundle2-exchange.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-bundle2-exchange.t	Sat Jul 18 17:32:38 2015 -0500
@@ -28,7 +28,7 @@
   > [hooks]
   > pretxnclose.tip = hg log -r tip -T "pre-close-tip:{node|short} {phase} {bookmarks}\n"
   > txnclose.tip = hg log -r tip -T "postclose-tip:{node|short} {phase} {bookmarks}\n"
-  > txnclose.env = sh -c  "HG_LOCAL= python \"$TESTDIR/printenv.py\" txnclose"
+  > txnclose.env = sh -c  "HG_LOCAL= printenv.py txnclose"
   > pushkey= sh "$TESTTMP/bundle2-pushkey-hook.sh"
   > EOF
 
@@ -173,36 +173,66 @@
 add extra data to test their exchange during push
 
   $ hg -R main bookmark --rev eea13746799a book_eea1
+  pre-close-tip:02de42196ebe draft 
+  postclose-tip:02de42196ebe draft 
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
   $ hg -R main debugobsolete -d '0 0' 3333333333333333333333333333333333333333 `getmainid eea13746799a`
   pre-close-tip:02de42196ebe draft 
   postclose-tip:02de42196ebe draft 
   txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
   $ hg -R main bookmark --rev 02de42196ebe book_02de
+  pre-close-tip:02de42196ebe draft book_02de
+  postclose-tip:02de42196ebe draft book_02de
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
   $ hg -R main debugobsolete -d '0 0' 4444444444444444444444444444444444444444 `getmainid 02de42196ebe`
   pre-close-tip:02de42196ebe draft book_02de
   postclose-tip:02de42196ebe draft book_02de
   txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
   $ hg -R main bookmark --rev 42ccdea3bb16 book_42cc
+  pre-close-tip:02de42196ebe draft book_02de
+  postclose-tip:02de42196ebe draft book_02de
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
   $ hg -R main debugobsolete -d '0 0' 5555555555555555555555555555555555555555 `getmainid 42ccdea3bb16`
   pre-close-tip:02de42196ebe draft book_02de
   postclose-tip:02de42196ebe draft book_02de
   txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
   $ hg -R main bookmark --rev 5fddd98957c8 book_5fdd
+  pre-close-tip:02de42196ebe draft book_02de
+  postclose-tip:02de42196ebe draft book_02de
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
   $ hg -R main debugobsolete -d '0 0' 6666666666666666666666666666666666666666 `getmainid 5fddd98957c8`
   pre-close-tip:02de42196ebe draft book_02de
   postclose-tip:02de42196ebe draft book_02de
   txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
   $ hg -R main bookmark --rev 32af7686d403 book_32af
+  pre-close-tip:02de42196ebe draft book_02de
+  postclose-tip:02de42196ebe draft book_02de
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
   $ hg -R main debugobsolete -d '0 0' 7777777777777777777777777777777777777777 `getmainid 32af7686d403`
   pre-close-tip:02de42196ebe draft book_02de
   postclose-tip:02de42196ebe draft book_02de
   txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
 
   $ hg -R other bookmark --rev cd010b8cd998 book_eea1
+  pre-close-tip:24b6387c8c8c public 
+  postclose-tip:24b6387c8c8c public 
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
   $ hg -R other bookmark --rev cd010b8cd998 book_02de
+  pre-close-tip:24b6387c8c8c public 
+  postclose-tip:24b6387c8c8c public 
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
   $ hg -R other bookmark --rev cd010b8cd998 book_42cc
+  pre-close-tip:24b6387c8c8c public 
+  postclose-tip:24b6387c8c8c public 
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
   $ hg -R other bookmark --rev cd010b8cd998 book_5fdd
+  pre-close-tip:24b6387c8c8c public 
+  postclose-tip:24b6387c8c8c public 
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
   $ hg -R other bookmark --rev cd010b8cd998 book_32af
+  pre-close-tip:24b6387c8c8c public 
+  postclose-tip:24b6387c8c8c public 
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
 
   $ hg -R main phase --public eea13746799a
   pre-close-tip:02de42196ebe draft book_02de
@@ -466,7 +496,7 @@
   > failpush=$TESTTMP/failpush.py
   > EOF
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
   $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
   $ cat other.pid >> $DAEMON_PIDS
 
@@ -562,7 +592,7 @@
   > txnabort.failpush = sh -c "echo 'Cleaning up the mess...'"
   > EOF
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
   $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
   $ cat other.pid >> $DAEMON_PIDS
 
@@ -625,7 +655,7 @@
   $ cat << EOF >> $HGRCPATH
   > pretxnchangegroup = sh -c "echo 'Fail early!'; false"
   > EOF
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS # reload http config
+  $ killdaemons.py # reload http config
   $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
   $ cat other.pid >> $DAEMON_PIDS
 
@@ -717,3 +747,153 @@
   remote: rollback completed
   abort: pretxnchangegroup hook exited with status 1
   [255]
+
+Check abort from mandatory pushkey
+
+  $ cat > mandatorypart.py << EOF
+  > from mercurial import exchange
+  > from mercurial import pushkey
+  > from mercurial import node
+  > from mercurial import error
+  > @exchange.b2partsgenerator('failingpuskey')
+  > def addfailingpushey(pushop, bundler):
+  >     enc = pushkey.encode
+  >     part = bundler.newpart('pushkey')
+  >     part.addparam('namespace', enc('phases'))
+  >     part.addparam('key', enc(pushop.repo['cd010b8cd998'].hex()))
+  >     part.addparam('old', enc(str(0))) # successful update
+  >     part.addparam('new', enc(str(0)))
+  >     def fail(pushop, exc):
+  >         raise error.Abort('Correct phase push failed (because hooks)')
+  >     pushop.pkfailcb[part.id] = fail
+  > EOF
+  $ cat >> $HGRCPATH << EOF
+  > [hooks]
+  > pretxnchangegroup=
+  > pretxnclose.failpush=
+  > prepushkey.failpush = sh -c "echo 'do not push the key !'; false"
+  > [extensions]
+  > mandatorypart=$TESTTMP/mandatorypart.py
+  > EOF
+  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS # reload http config
+  $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
+  $ cat other.pid >> $DAEMON_PIDS
+
+(Failure from a hook)
+
+  $ hg -R main push other -r e7ec4e813ba6
+  pushing to other
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  do not push the key !
+  pushkey-abort: prepushkey.failpush hook exited with status 1
+  transaction abort!
+  Cleaning up the mess...
+  rollback completed
+  abort: Correct phase push failed (because hooks)
+  [255]
+  $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
+  pushing to ssh://user@dummy/other
+  searching for changes
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 1 changesets with 1 changes to 1 files
+  remote: do not push the key !
+  remote: pushkey-abort: prepushkey.failpush hook exited with status 1
+  remote: transaction abort!
+  remote: Cleaning up the mess...
+  remote: rollback completed
+  abort: Correct phase push failed (because hooks)
+  [255]
+  $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
+  pushing to http://localhost:$HGPORT2/
+  searching for changes
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 1 changesets with 1 changes to 1 files
+  remote: do not push the key !
+  remote: pushkey-abort: prepushkey.failpush hook exited with status 1
+  remote: transaction abort!
+  remote: Cleaning up the mess...
+  remote: rollback completed
+  abort: Correct phase push failed (because hooks)
+  [255]
+
+(Failure from a the pushkey)
+
+  $ cat > mandatorypart.py << EOF
+  > from mercurial import exchange
+  > from mercurial import pushkey
+  > from mercurial import node
+  > from mercurial import error
+  > @exchange.b2partsgenerator('failingpuskey')
+  > def addfailingpushey(pushop, bundler):
+  >     enc = pushkey.encode
+  >     part = bundler.newpart('pushkey')
+  >     part.addparam('namespace', enc('phases'))
+  >     part.addparam('key', enc(pushop.repo['cd010b8cd998'].hex()))
+  >     part.addparam('old', enc(str(4))) # will fail
+  >     part.addparam('new', enc(str(3)))
+  >     def fail(pushop, exc):
+  >         raise error.Abort('Clown phase push failed')
+  >     pushop.pkfailcb[part.id] = fail
+  > EOF
+  $ cat >> $HGRCPATH << EOF
+  > [hooks]
+  > prepushkey.failpush =
+  > EOF
+  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS # reload http config
+  $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
+  $ cat other.pid >> $DAEMON_PIDS
+
+  $ hg -R main push other -r e7ec4e813ba6
+  pushing to other
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  transaction abort!
+  Cleaning up the mess...
+  rollback completed
+  pushkey: lock state after "phases"
+  lock:  free
+  wlock: free
+  abort: Clown phase push failed
+  [255]
+  $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
+  pushing to ssh://user@dummy/other
+  searching for changes
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 1 changesets with 1 changes to 1 files
+  remote: transaction abort!
+  remote: Cleaning up the mess...
+  remote: rollback completed
+  remote: pushkey: lock state after "phases"
+  remote: lock:  free
+  remote: wlock: free
+  abort: Clown phase push failed
+  [255]
+  $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
+  pushing to http://localhost:$HGPORT2/
+  searching for changes
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 1 changesets with 1 changes to 1 files
+  remote: transaction abort!
+  remote: Cleaning up the mess...
+  remote: rollback completed
+  remote: pushkey: lock state after "phases"
+  remote: lock:  free
+  remote: wlock: free
+  abort: Clown phase push failed
+  [255]
+
--- a/tests/test-bundle2-format.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-bundle2-format.t	Sat Jul 18 17:32:38 2015 -0500
@@ -336,11 +336,12 @@
 
 bundling debug
 
-  $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2
-  start emission of HG20 stream
-  bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple
-  start of parts
-  end of bundle
+  $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2 --config progress.debug=true --config devel.bundle2.debug=true
+  bundle2-output-bundle: "HG20", (2 params) 0 parts total
+  bundle2-output: start emission of HG20 stream
+  bundle2-output: bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple
+  bundle2-output: start of parts
+  bundle2-output: end of bundle
 
 file content is ok
 
@@ -349,18 +350,18 @@
 
 unbundling debug
 
-  $ hg statbundle2 --debug < ../out.hg2
-  start processing of HG20 stream
-  reading bundle2 stream parameters
-  ignoring unknown parameter 'e|! 7/'
-  ignoring unknown parameter 'simple'
+  $ 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'
   options count: 2
   - e|! 7/
       babar%#==tutu
   - simple
-  start extraction of bundle2 parts
-  part header size: 0
-  end of bundle2 stream
+  bundle2-input: start extraction of bundle2 parts
+  bundle2-input: part header size: 0
+  bundle2-input: end of bundle2 stream
   parts count:   0
 
 
@@ -383,18 +384,49 @@
 Test part
 =================
 
-  $ hg bundle2 --parts ../parts.hg2 --debug
-  start emission of HG20 stream
-  bundle parameter: 
-  start of parts
-  bundle part: "test:empty"
-  bundle part: "test:empty"
-  bundle part: "test:song"
-  bundle part: "test:debugreply"
-  bundle part: "test:math"
-  bundle part: "test:song"
-  bundle part: "test:ping"
-  end of bundle
+  $ hg bundle2 --parts ../parts.hg2 --debug --config progress.debug=true --config devel.bundle2.debug=true
+  bundle2-output-bundle: "HG20", 7 parts total
+  bundle2-output: start emission of HG20 stream
+  bundle2-output: bundle parameter: 
+  bundle2-output: start of parts
+  bundle2-output: bundle part: "test:empty"
+  bundle2-output-part: "test:empty" (advisory) empty payload
+  bundle2-output: part 0: "test:empty"
+  bundle2-output: header chunk size: 17
+  bundle2-output: closing payload chunk
+  bundle2-output: bundle part: "test:empty"
+  bundle2-output-part: "test:empty" (advisory) empty payload
+  bundle2-output: part 1: "test:empty"
+  bundle2-output: header chunk size: 17
+  bundle2-output: closing payload chunk
+  bundle2-output: bundle part: "test:song"
+  bundle2-output-part: "test:song" (advisory) 178 bytes payload
+  bundle2-output: part 2: "test:song"
+  bundle2-output: header chunk size: 16
+  bundle2-output: payload chunk size: 178
+  bundle2-output: closing payload chunk
+  bundle2-output: bundle part: "test:debugreply"
+  bundle2-output-part: "test:debugreply" (advisory) empty payload
+  bundle2-output: part 3: "test:debugreply"
+  bundle2-output: header chunk size: 22
+  bundle2-output: closing payload chunk
+  bundle2-output: bundle part: "test:math"
+  bundle2-output-part: "test:math" (advisory) (params: 2 mandatory 2 advisory) 2 bytes payload
+  bundle2-output: part 4: "test:math"
+  bundle2-output: header chunk size: 43
+  bundle2-output: payload chunk size: 2
+  bundle2-output: closing payload chunk
+  bundle2-output: bundle part: "test:song"
+  bundle2-output-part: "test:song" (advisory) (params: 1 mandatory) empty payload
+  bundle2-output: part 5: "test:song"
+  bundle2-output: header chunk size: 29
+  bundle2-output: closing payload chunk
+  bundle2-output: bundle part: "test:ping"
+  bundle2-output-part: "test:ping" (advisory) empty payload
+  bundle2-output: part 6: "test:ping"
+  bundle2-output: header chunk size: 16
+  bundle2-output: closing payload chunk
+  bundle2-output: end of bundle
 
   $ cat ../parts.hg2
   HG20\x00\x00\x00\x00\x00\x00\x00\x11 (esc)
@@ -436,78 +468,80 @@
       payload: 0 bytes
   parts count:   7
 
-  $ hg statbundle2 --debug < ../parts.hg2
-  start processing of HG20 stream
-  reading bundle2 stream parameters
+  $ hg statbundle2 --debug --config progress.debug=true --config devel.bundle2.debug=true < ../parts.hg2
+  bundle2-input: start processing of HG20 stream
+  bundle2-input: reading bundle2 stream parameters
   options count: 0
-  start extraction of bundle2 parts
-  part header size: 17
-  part type: "test:empty"
-  part id: "0"
-  part parameters: 0
+  bundle2-input: start extraction of bundle2 parts
+  bundle2-input: part header size: 17
+  bundle2-input: part type: "test:empty"
+  bundle2-input: part id: "0"
+  bundle2-input: part parameters: 0
     :test:empty:
       mandatory: 0
       advisory: 0
-  payload chunk size: 0
+  bundle2-input: payload chunk size: 0
       payload: 0 bytes
-  part header size: 17
-  part type: "test:empty"
-  part id: "1"
-  part parameters: 0
+  bundle2-input: part header size: 17
+  bundle2-input: part type: "test:empty"
+  bundle2-input: part id: "1"
+  bundle2-input: part parameters: 0
     :test:empty:
       mandatory: 0
       advisory: 0
-  payload chunk size: 0
+  bundle2-input: payload chunk size: 0
       payload: 0 bytes
-  part header size: 16
-  part type: "test:song"
-  part id: "2"
-  part parameters: 0
+  bundle2-input: part header size: 16
+  bundle2-input: part type: "test:song"
+  bundle2-input: part id: "2"
+  bundle2-input: part parameters: 0
     :test:song:
       mandatory: 0
       advisory: 0
-  payload chunk size: 178
-  payload chunk size: 0
+  bundle2-input: payload chunk size: 178
+  bundle2-input: payload chunk size: 0
+  bundle2-input-part: total payload size 178
       payload: 178 bytes
-  part header size: 22
-  part type: "test:debugreply"
-  part id: "3"
-  part parameters: 0
+  bundle2-input: part header size: 22
+  bundle2-input: part type: "test:debugreply"
+  bundle2-input: part id: "3"
+  bundle2-input: part parameters: 0
     :test:debugreply:
       mandatory: 0
       advisory: 0
-  payload chunk size: 0
+  bundle2-input: payload chunk size: 0
       payload: 0 bytes
-  part header size: 43
-  part type: "test:math"
-  part id: "4"
-  part parameters: 3
+  bundle2-input: part header size: 43
+  bundle2-input: part type: "test:math"
+  bundle2-input: part id: "4"
+  bundle2-input: part parameters: 3
     :test:math:
       mandatory: 2
       advisory: 1
-  payload chunk size: 2
-  payload chunk size: 0
+  bundle2-input: payload chunk size: 2
+  bundle2-input: payload chunk size: 0
+  bundle2-input-part: total payload size 2
       payload: 2 bytes
-  part header size: 29
-  part type: "test:song"
-  part id: "5"
-  part parameters: 1
+  bundle2-input: part header size: 29
+  bundle2-input: part type: "test:song"
+  bundle2-input: part id: "5"
+  bundle2-input: part parameters: 1
     :test:song:
       mandatory: 1
       advisory: 0
-  payload chunk size: 0
+  bundle2-input: payload chunk size: 0
       payload: 0 bytes
-  part header size: 16
-  part type: "test:ping"
-  part id: "6"
-  part parameters: 0
+  bundle2-input: part header size: 16
+  bundle2-input: part type: "test:ping"
+  bundle2-input: part id: "6"
+  bundle2-input: part parameters: 0
     :test:ping:
       mandatory: 0
       advisory: 0
-  payload chunk size: 0
+  bundle2-input: payload chunk size: 0
       payload: 0 bytes
-  part header size: 0
-  end of bundle2 stream
+  bundle2-input: part header size: 0
+  bundle2-input: end of bundle2 stream
   parts count:   7
 
 Test actual unbundling of test part
@@ -515,63 +549,74 @@
 
 Process the bundle
 
-  $ hg unbundle2 --debug < ../parts.hg2
-  start processing of HG20 stream
-  reading bundle2 stream parameters
-  start extraction of bundle2 parts
-  part header size: 17
-  part type: "test:empty"
-  part id: "0"
-  part parameters: 0
-  ignoring unsupported advisory part test:empty
-  payload chunk size: 0
-  part header size: 17
-  part type: "test:empty"
-  part id: "1"
-  part parameters: 0
-  ignoring unsupported advisory part test:empty
-  payload chunk size: 0
-  part header size: 16
-  part type: "test:song"
-  part id: "2"
-  part parameters: 0
-  found a handler for part 'test:song'
+  $ hg unbundle2 --debug --config progress.debug=true --config devel.bundle2.debug=true < ../parts.hg2
+  bundle2-input: start processing of HG20 stream
+  bundle2-input: reading bundle2 stream parameters
+  bundle2-input-bundle: with-transaction
+  bundle2-input: start extraction of bundle2 parts
+  bundle2-input: part header size: 17
+  bundle2-input: part type: "test:empty"
+  bundle2-input: part id: "0"
+  bundle2-input: part parameters: 0
+  bundle2-input: ignoring unsupported advisory part test:empty
+  bundle2-input-part: "test:empty" (advisory) unsupported-type
+  bundle2-input: payload chunk size: 0
+  bundle2-input: part header size: 17
+  bundle2-input: part type: "test:empty"
+  bundle2-input: part id: "1"
+  bundle2-input: part parameters: 0
+  bundle2-input: ignoring unsupported advisory part test:empty
+  bundle2-input-part: "test:empty" (advisory) unsupported-type
+  bundle2-input: payload chunk size: 0
+  bundle2-input: part header size: 16
+  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-part: "test:song" (advisory) supported
   The choir starts singing:
-  payload chunk size: 178
-  payload chunk size: 0
+  bundle2-input: payload chunk size: 178
+  bundle2-input: payload chunk size: 0
+  bundle2-input-part: total payload size 178
       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.
-  part header size: 22
-  part type: "test:debugreply"
-  part id: "3"
-  part parameters: 0
-  found a handler for part 'test:debugreply'
+  bundle2-input: part header size: 22
+  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-part: "test:debugreply" (advisory) supported
   debugreply: no reply
-  payload chunk size: 0
-  part header size: 43
-  part type: "test:math"
-  part id: "4"
-  part parameters: 3
-  ignoring unsupported advisory part test:math
-  payload chunk size: 2
-  payload chunk size: 0
-  part header size: 29
-  part type: "test:song"
-  part id: "5"
-  part parameters: 1
-  found a handler for part 'test:song'
-  ignoring unsupported advisory part test:song - randomparam
-  payload chunk size: 0
-  part header size: 16
-  part type: "test:ping"
-  part id: "6"
-  part parameters: 0
-  found a handler for part 'test:ping'
+  bundle2-input: payload chunk size: 0
+  bundle2-input: part header size: 43
+  bundle2-input: part type: "test:math"
+  bundle2-input: part id: "4"
+  bundle2-input: part parameters: 3
+  bundle2-input: ignoring unsupported advisory part test:math
+  bundle2-input-part: "test:math" (advisory) (params: 2 mandatory 2 advisory) unsupported-type
+  bundle2-input: payload chunk size: 2
+  bundle2-input: payload chunk size: 0
+  bundle2-input-part: total payload size 2
+  bundle2-input: part header size: 29
+  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: ignoring unsupported advisory part test:song - 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-part: "test:ping" (advisory) supported
   received ping request (id 6)
-  payload chunk size: 0
-  part header size: 0
-  end of bundle2 stream
+  bundle2-input: payload chunk size: 0
+  bundle2-input: part header size: 0
+  bundle2-input: end of bundle2 stream
+  bundle2-input-bundle: 6 parts total
   0 unread bytes
   3 total verses sung
 
@@ -704,17 +749,21 @@
   @  0:3903775176ed draft test  a
   
 
-  $ hg bundle2 --debug --rev '8+7+5+4' ../rev.hg2
+  $ hg bundle2 --debug --config progress.debug=true --config devel.bundle2.debug=true --rev '8+7+5+4' ../rev.hg2
   4 changesets found
   list of changesets:
   32af7686d403cf45b5d95f2d70cebea587ac806a
   9520eea781bcca16c1e15acc0ba14335a0e8e5ba
   eea13746799a9e0bfd88f29d3c2e9dc9389f524f
   02de42196ebee42ef284b6780a87cdc96e8eaab6
-  start emission of HG20 stream
-  bundle parameter: 
-  start of parts
-  bundle part: "changegroup"
+  bundle2-output-bundle: "HG20", 1 parts total
+  bundle2-output: start emission of HG20 stream
+  bundle2-output: bundle parameter: 
+  bundle2-output: start of parts
+  bundle2-output: bundle part: "changegroup"
+  bundle2-output-part: "changegroup" (advisory) streamed payload
+  bundle2-output: part 0: "changegroup"
+  bundle2-output: header chunk size: 18
   bundling: 1/4 changesets (25.00%)
   bundling: 2/4 changesets (50.00%)
   bundling: 3/4 changesets (75.00%)
@@ -726,7 +775,9 @@
   bundling: D 1/3 files (33.33%)
   bundling: E 2/3 files (66.67%)
   bundling: H 3/3 files (100.00%)
-  end of bundle
+  bundle2-output: payload chunk size: 1555
+  bundle2-output: closing payload chunk
+  bundle2-output: end of bundle
 
   $ cat ../rev.hg2
   HG20\x00\x00\x00\x00\x00\x00\x00\x12\x0bchangegroup\x00\x00\x00\x00\x00\x00\x00\x00\x06\x13\x00\x00\x00\xa42\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j_\xdd\xd9\x89W\xc8\xa5JMCm\xfe\x1d\xa9\xd8\x7f!\xa1\xb9{\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)6e1f4c47ecb533ffd0c8e52cdc88afb6cd39e20c (esc)
--- a/tests/test-bundle2-multiple-changegroups.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-bundle2-multiple-changegroups.t	Sat Jul 18 17:32:38 2015 -0500
@@ -67,9 +67,9 @@
   $ cd ../clone
   $ cat >> .hg/hgrc <<EOF
   > [hooks]
-  > pretxnchangegroup = sh -c "python \"$TESTDIR/printenv.py\" pretxnchangegroup"
-  > changegroup = sh -c "python \"$TESTDIR/printenv.py\" changegroup"
-  > incoming = sh -c "python \"$TESTDIR/printenv.py\" incoming"
+  > pretxnchangegroup = sh -c "printenv.py pretxnchangegroup"
+  > changegroup = sh -c "printenv.py changegroup"
+  > incoming = sh -c "printenv.py incoming"
   > EOF
 
 Pull the new commits in the clone
--- a/tests/test-bundle2-pushback.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-bundle2-pushback.t	Sat Jul 18 17:32:38 2015 -0500
@@ -63,11 +63,11 @@
   $ hg push
   pushing to ssh://user@dummy/server
   searching for changes
-  remote: pushback not enabled
   remote: adding changesets
   remote: adding manifests
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
+  remote: pushback not enabled
   $ hg bookmark
   no bookmarks set
 
--- a/tests/test-bundle2-remote-changegroup.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-bundle2-remote-changegroup.t	Sat Jul 18 17:32:38 2015 -0500
@@ -589,4 +589,4 @@
   
   $ rm -rf clone
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
--- a/tests/test-censor.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-censor.t	Sat Jul 18 17:32:38 2015 -0500
@@ -72,7 +72,10 @@
 
 Censor revision with 2 offenses
 
-  $ hg censor -r $C2 -t "remove password" target
+(this also tests file pattern matching: path relative to cwd case)
+
+  $ mkdir -p foo/bar/baz
+  $ hg --cwd foo/bar/baz censor -r $C2 -t "remove password" ../../../target
   $ hg cat -r $H1 target
   Tainted file is now sanitized
   $ hg cat -r $H2 target
@@ -89,7 +92,9 @@
 
 Censor revision with 1 offense
 
-  $ hg censor -r $C1 target
+(this also tests file pattern matching: with 'path:' scheme)
+
+  $ hg --cwd foo/bar/baz censor -r $C1 path:target
   $ hg cat -r $H1 target
   Tainted file is now sanitized
   $ hg cat -r $H2 target
--- a/tests/test-check-code.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-check-code.t	Sat Jul 18 17:32:38 2015 -0500
@@ -17,53 +17,6 @@
   > ( 4-1 ) """, "( 1+1 )\" and ")
   > a, '\\\\\\\\', "\\\\\\" x-2", "c-1"
   > EOF
-  $ cat > non-py24.py <<EOF
-  > # Using builtins that does not exist in Python 2.4
-  > if any():
-  >     x = all()
-  >     y = format(x)
-  >     # next(generator) is new in 2.6
-  >     z = next(x)
-  >     # but generator.next() is okay
-  >     x.next()
-  >     # and we can make our own next
-  >     def next(stuff):
-  >         pass
-  > 
-  > # Do not complain about our own definition
-  > def any(x):
-  >     pass
-  > 
-  > # try/except/finally block does not exist in Python 2.4
-  >     try:
-  >         pass
-  >     except StandardError, inst:
-  >         pass
-  >     finally:
-  >         pass
-  > 
-  > # nested try/finally+try/except is allowed
-  >     try:
-  >         try:
-  >             pass
-  >         except StandardError, inst:
-  >             pass
-  >     finally:
-  >         pass
-  > 
-  > # yield inside a try/finally block is not allowed in Python 2.4
-  >     try:
-  >         pass
-  >         yield 1
-  >     finally:
-  >         pass
-  >     try:
-  >         yield
-  >         pass
-  >     finally:
-  >         pass
-  > 
-  > EOF
   $ cat > classstyle.py <<EOF
   > class newstyle_class(object):
   >     pass
@@ -78,7 +31,7 @@
   >     pass
   > EOF
   $ check_code="$TESTDIR"/../contrib/check-code.py
-  $ "$check_code" ./wrong.py ./correct.py ./quote.py ./non-py24.py ./classstyle.py
+  $ "$check_code" ./wrong.py ./correct.py ./quote.py ./classstyle.py
   ./wrong.py:1:
    > def toto( arg1, arg2):
    gratuitous whitespace in () or []
@@ -92,33 +45,12 @@
   ./quote.py:5:
    > '"""', 42+1, """and
    missing whitespace in expression
-  ./non-py24.py:2:
-   > if any():
-   any/all/format not available in Python 2.4
-  ./non-py24.py:3:
-   >     x = all()
-   any/all/format not available in Python 2.4
-  ./non-py24.py:4:
-   >     y = format(x)
-   any/all/format not available in Python 2.4
-  ./non-py24.py:6:
-   >     z = next(x)
-   no next(foo) in Python 2.4 and 2.5, use foo.next() instead
-  ./non-py24.py:18:
-   >     try:
-   no try/except/finally in Python 2.4
-  ./non-py24.py:35:
-   >     try:
-   no yield inside try/finally in Python 2.4
-  ./non-py24.py:40:
-   >     try:
-   no yield inside try/finally in Python 2.4
   ./classstyle.py:4:
    > class oldstyle_class:
    old-style class, use class foo(object)
   ./classstyle.py:7:
    > class empty():
-   class foo() not available in Python 2.4, use class foo(object)
+   class foo() creates old style object, use class foo(object)
   [1]
   $ cat > python3-compat.py << EOF
   > foo <> bar
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-check-config-hg.t	Sat Jul 18 17:32:38 2015 -0500
@@ -0,0 +1,27 @@
+#require test-repo
+
+  $ cd "$TESTDIR"/..
+
+New errors are not allowed. Warnings are strongly discouraged.
+
+  $ hg files "set:(**.py or **.txt) - tests/**" |
+  >   xargs python contrib/check-config.py
+  undocumented: convert.cvsps.cache (bool) [True]
+  undocumented: convert.cvsps.fuzz (str) [60]
+  undocumented: convert.cvsps.mergefrom (str)
+  undocumented: convert.cvsps.mergeto (str)
+  undocumented: convert.git.remoteprefix (str) ['remote']
+  undocumented: convert.git.similarity (int) [50]
+  undocumented: convert.hg.clonebranches (bool)
+  undocumented: convert.hg.ignoreerrors (bool)
+  undocumented: convert.hg.revs (str)
+  undocumented: convert.hg.saverev (bool)
+  undocumented: convert.hg.sourcename (str)
+  undocumented: convert.hg.startrev (str)
+  undocumented: convert.hg.tagsbranch (str) ['default']
+  undocumented: convert.hg.usebranchnames (bool) [True]
+  undocumented: convert.localtimezone (bool)
+  undocumented: convert.p4.startrev (str)
+  undocumented: convert.skiptags (bool)
+  undocumented: convert.svn.debugsvnlog (bool) [True]
+  undocumented: convert.svn.startrev (str)
--- a/tests/test-clone-update-order.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-clone-update-order.t	Sat Jul 18 17:32:38 2015 -0500
@@ -14,7 +14,6 @@
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg branch other
   marked working directory as branch other
-  (branches are permanent and global, did you want a bookmark?)
   $ echo good > bye
   $ hg commit -Am other
   adding bye
--- a/tests/test-clone.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-clone.t	Sat Jul 18 17:32:38 2015 -0500
@@ -64,7 +64,7 @@
 No update, with debug option:
 
 #if hardlink
-  $ hg --debug clone -U . ../c
+  $ hg --debug clone -U . ../c --config progress.debug=true
   linking: 1
   linking: 2
   linking: 3
@@ -75,7 +75,7 @@
   linking: 8
   linked 8 files
 #else
-  $ hg --debug clone -U . ../c
+  $ hg --debug clone -U . ../c --config progress.debug=true
   linking: 1
   copying: 2
   copying: 3
@@ -357,7 +357,6 @@
 
   $ hg -R ua branch @
   marked working directory as branch @
-  (branches are permanent and global, did you want a bookmark?)
   $ hg -R ua commit -m 'created branch @'
   $ hg clone ua atbranch
   updating to branch default
@@ -675,4 +674,342 @@
   $ hg clone -U -q src dst
   $ hg -R dst log -q
   0:e1bab28bca43
+
+Create repositories to test auto sharing functionality
+
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > share=
+  > EOF
+
+  $ hg init empty
+  $ hg init source1a
+  $ cd source1a
+  $ echo initial1 > foo
+  $ hg -q commit -A -m initial
+  $ echo second > foo
+  $ hg commit -m second
   $ cd ..
+
+  $ hg init filteredrev0
+  $ cd filteredrev0
+  $ cat >> .hg/hgrc << EOF
+  > [experimental]
+  > evolution=createmarkers
+  > EOF
+  $ echo initial1 > foo
+  $ hg -q commit -A -m initial0
+  $ hg -q up -r null
+  $ echo initial2 > foo
+  $ hg -q commit -A -m initial1
+  $ hg debugobsolete c05d5c47a5cf81401869999f3d05f7d699d2b29a e082c1832e09a7d1e78b7fd49a592d372de854c8
+  $ cd ..
+
+  $ hg -q clone --pull source1a source1b
+  $ cd source1a
+  $ hg bookmark bookA
+  $ echo 1a > foo
+  $ hg commit -m 1a
+  $ cd ../source1b
+  $ hg -q up -r 0
+  $ echo head1 > foo
+  $ hg commit -m head1
+  created new head
+  $ hg bookmark head1
+  $ hg -q up -r 0
+  $ echo head2 > foo
+  $ hg commit -m head2
+  created new head
+  $ hg bookmark head2
+  $ hg -q up -r 0
+  $ hg branch branch1
+  marked working directory as branch branch1
+  (branches are permanent and global, did you want a bookmark?)
+  $ echo branch1 > foo
+  $ hg commit -m branch1
+  $ hg -q up -r 0
+  $ hg branch branch2
+  marked working directory as branch branch2
+  $ echo branch2 > foo
+  $ hg commit -m branch2
+  $ cd ..
+  $ hg init source2
+  $ cd source2
+  $ echo initial2 > foo
+  $ hg -q commit -A -m initial2
+  $ echo second > foo
+  $ hg commit -m second
+  $ cd ..
+
+Clone with auto share from an empty repo should not result in share
+
+  $ mkdir share
+  $ hg --config share.pool=share clone empty share-empty
+  (not using pooled storage: remote appears to be empty)
+  updating to branch default
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ ls share
+  $ test -d share-empty/.hg/store
+  $ test -f share-empty/.hg/sharedpath
+  [1]
+
+Clone with auto share from a repo with filtered revision 0 should not result in share
+
+  $ hg --config share.pool=share clone filteredrev0 share-filtered
+  (not using pooled storage: unable to resolve identity of remote)
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Clone from repo with content should result in shared store being created
+
+  $ hg --config share.pool=share clone source1a share-dest1a
+  (sharing from new pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 3 changes to 1 files
+  updating working directory
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  searching for changes
+  no changes found
+  adding remote bookmark bookA
+
+The shared repo should have been created
+
+  $ ls share
+  b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1
+
+The destination should point to it
+
+  $ cat share-dest1a/.hg/sharedpath; echo
+  $TESTTMP/share/b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1/.hg
+
+The destination should have bookmarks
+
+  $ hg -R share-dest1a bookmarks
+     bookA                     2:e5bfe23c0b47
+
+The default path should be the remote, not the share
+
+  $ hg -R share-dest1a config paths.default
+  $TESTTMP/source1a
+
+Clone with existing share dir should result in pull + share
+
+  $ hg --config share.pool=share clone source1b share-dest1b
+  (sharing from existing pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
+  updating working directory
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 4 changesets with 4 changes to 1 files (+4 heads)
+  adding remote bookmark head1
+  adding remote bookmark head2
+
+  $ ls share
+  b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1
+
+  $ cat share-dest1b/.hg/sharedpath; echo
+  $TESTTMP/share/b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1/.hg
+
+We only get bookmarks from the remote, not everything in the share
+
+  $ hg -R share-dest1b bookmarks
+     head1                     3:4a8dc1ab4c13
+     head2                     4:99f71071f117
+
+Default path should be source, not share.
+
+  $ hg -R share-dest1b config paths.default
+  $TESTTMP/source1a
+
+Clone from unrelated repo should result in new share
+
+  $ hg --config share.pool=share clone source2 share-dest2
+  (sharing from new pooled repository 22aeff664783fd44c6d9b435618173c118c3448e)
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 1 files
+  updating working directory
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  searching for changes
+  no changes found
+
+  $ ls share
+  22aeff664783fd44c6d9b435618173c118c3448e
+  b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1
+
+remote naming mode works as advertised
+
+  $ hg --config share.pool=shareremote --config share.poolnaming=remote clone source1a share-remote1a
+  (sharing from new pooled repository 195bb1fcdb595c14a6c13e0269129ed78f6debde)
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 3 changes to 1 files
+  updating working directory
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  searching for changes
+  no changes found
+  adding remote bookmark bookA
+
+  $ ls shareremote
+  195bb1fcdb595c14a6c13e0269129ed78f6debde
+
+  $ hg --config share.pool=shareremote --config share.poolnaming=remote clone source1b share-remote1b
+  (sharing from new pooled repository c0d4f83847ca2a873741feb7048a45085fd47c46)
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 6 changesets with 6 changes to 1 files (+4 heads)
+  updating working directory
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  searching for changes
+  no changes found
+  adding remote bookmark head1
+  adding remote bookmark head2
+
+  $ ls shareremote
+  195bb1fcdb595c14a6c13e0269129ed78f6debde
+  c0d4f83847ca2a873741feb7048a45085fd47c46
+
+request to clone a single revision is respected in sharing mode
+
+  $ hg --config share.pool=sharerevs clone -r 4a8dc1ab4c13 source1b share-1arev
+  (sharing from new pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 1 files
+  updating working directory
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  no changes found
+  adding remote bookmark head1
+
+  $ hg -R share-1arev log -G
+  @  changeset:   1:4a8dc1ab4c13
+  |  bookmark:    head1
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     head1
+  |
+  o  changeset:   0:b5f04eac9d8f
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     initial
+  
+
+making another clone should only pull down requested rev
+
+  $ hg --config share.pool=sharerevs clone -r 99f71071f117 source1b share-1brev
+  (sharing from existing pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
+  updating working directory
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  adding remote bookmark head1
+  adding remote bookmark head2
+
+  $ hg -R share-1brev log -G
+  o  changeset:   2:99f71071f117
+  |  bookmark:    head2
+  |  tag:         tip
+  |  parent:      0:b5f04eac9d8f
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     head2
+  |
+  | @  changeset:   1:4a8dc1ab4c13
+  |/   bookmark:    head1
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     head1
+  |
+  o  changeset:   0:b5f04eac9d8f
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     initial
+  
+
+Request to clone a single branch is respected in sharing mode
+
+  $ hg --config share.pool=sharebranch clone -b branch1 source1b share-1bbranch1
+  (sharing from new pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 1 files
+  updating working directory
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  no changes found
+
+  $ hg -R share-1bbranch1 log -G
+  o  changeset:   1:5f92a6c1a1b1
+  |  branch:      branch1
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     branch1
+  |
+  @  changeset:   0:b5f04eac9d8f
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     initial
+  
+
+  $ hg --config share.pool=sharebranch clone -b branch2 source1b share-1bbranch2
+  (sharing from existing pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
+  updating working directory
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+
+  $ hg -R share-1bbranch2 log -G
+  o  changeset:   2:6bacf4683960
+  |  branch:      branch2
+  |  tag:         tip
+  |  parent:      0:b5f04eac9d8f
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     branch2
+  |
+  | o  changeset:   1:5f92a6c1a1b1
+  |/   branch:      branch1
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     branch1
+  |
+  @  changeset:   0:b5f04eac9d8f
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     initial
+  
+
+-U is respected in share clone mode
+
+  $ hg --config share.pool=share clone -U source1a share-1anowc
+  (sharing from existing pooled repository b5f04eac9d8f7a6a9fcb070243cccea7dc5ea0c1)
+  searching for changes
+  no changes found
+  adding remote bookmark bookA
+
+  $ ls share-1anowc
--- a/tests/test-command-template.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-command-template.t	Sat Jul 18 17:32:38 2015 -0500
@@ -51,6 +51,18 @@
   8 t
   7 f
 
+Working-directory revision has special identifiers, though they are still
+experimental:
+
+  $ hg log -r 'wdir()' -T '{rev}:{node}\n'
+  2147483647:ffffffffffffffffffffffffffffffffffffffff
+
+Some keywords are invalid for working-directory revision, but they should
+never cause crash:
+
+  $ hg log -r 'wdir()' -T '{manifest}\n'
+  
+
 Quoting for ui.logtemplate
 
   $ hg tip --config "ui.logtemplate={rev}\n"
@@ -95,7 +107,7 @@
   8
 
 Add a commit with empty description, to ensure that the templates
-following below omit it properly.
+below will omit the description line.
 
   $ echo c >> c
   $ hg add c
@@ -108,32 +120,52 @@
   $ hg log --style default > style.out
   $ cmp log.out style.out || diff -u log.out style.out
   $ hg log -T phases > phases.out
-  $ diff -u log.out phases.out | grep "phase:"
+  $ diff -U 0 log.out phases.out | grep -v '^---\|^+++'
+  @@ -2,0 +3 @@
   +phase:       draft
+  @@ -6,0 +8 @@
   +phase:       draft
+  @@ -11,0 +14 @@
   +phase:       draft
+  @@ -17,0 +21 @@
   +phase:       draft
+  @@ -24,0 +29 @@
   +phase:       draft
+  @@ -31,0 +37 @@
   +phase:       draft
+  @@ -36,0 +43 @@
   +phase:       draft
+  @@ -41,0 +49 @@
   +phase:       draft
+  @@ -46,0 +55 @@
   +phase:       draft
+  @@ -51,0 +61 @@
   +phase:       draft
 
   $ hg log -v > log.out
   $ hg log -v --style default > style.out
   $ cmp log.out style.out || diff -u log.out style.out
   $ hg log -v -T phases > phases.out
-  $ diff -u log.out phases.out | grep phase:
+  $ diff -U 0 log.out phases.out | grep -v '^---\|^+++'
+  @@ -2,0 +3 @@
   +phase:       draft
+  @@ -7,0 +9 @@
   +phase:       draft
+  @@ -15,0 +18 @@
   +phase:       draft
+  @@ -24,0 +28 @@
   +phase:       draft
+  @@ -33,0 +38 @@
   +phase:       draft
+  @@ -43,0 +49 @@
   +phase:       draft
+  @@ -50,0 +57 @@
   +phase:       draft
+  @@ -58,0 +66 @@
   +phase:       draft
+  @@ -66,0 +75 @@
   +phase:       draft
+  @@ -77,0 +87 @@
   +phase:       draft
 
   $ hg log -q > log.out
@@ -148,6 +180,26 @@
   $ hg log --debug -T phases > phases.out
   $ cmp log.out phases.out || diff -u log.out phases.out
 
+Default style of working-directory revision should also be the same (but
+date may change while running tests):
+
+  $ hg log -r 'wdir()' | sed 's|^date:.*|date:|' > log.out
+  $ hg log -r 'wdir()' --style default | sed 's|^date:.*|date:|' > style.out
+  $ cmp log.out style.out || diff -u log.out style.out
+
+  $ hg log -r 'wdir()' -v | sed 's|^date:.*|date:|' > log.out
+  $ hg log -r 'wdir()' -v --style default | sed 's|^date:.*|date:|' > style.out
+  $ cmp log.out style.out || diff -u log.out style.out
+
+  $ hg log -r 'wdir()' -q > log.out
+  $ hg log -r 'wdir()' -q --style default > style.out
+  $ cmp log.out style.out || diff -u log.out style.out
+
+  $ hg log -r 'wdir()' --debug | sed 's|^date:.*|date:|' > log.out
+  $ hg log -r 'wdir()' --debug --style default \
+  > | sed 's|^date:.*|date:|' > style.out
+  $ cmp log.out style.out || diff -u log.out style.out
+
 Default style should also preserve color information (issue2866):
 
   $ cp $HGRCPATH $HGRCPATH-bak
@@ -160,32 +212,52 @@
   $ hg --color=debug log --style default > style.out
   $ cmp log.out style.out || diff -u log.out style.out
   $ hg --color=debug log -T phases > phases.out
-  $ diff -u log.out phases.out | grep phase:
+  $ diff -U 0 log.out phases.out | grep -v '^---\|^+++'
+  @@ -2,0 +3 @@
   +[log.phase|phase:       draft]
+  @@ -6,0 +8 @@
   +[log.phase|phase:       draft]
+  @@ -11,0 +14 @@
   +[log.phase|phase:       draft]
+  @@ -17,0 +21 @@
   +[log.phase|phase:       draft]
+  @@ -24,0 +29 @@
   +[log.phase|phase:       draft]
+  @@ -31,0 +37 @@
   +[log.phase|phase:       draft]
+  @@ -36,0 +43 @@
   +[log.phase|phase:       draft]
+  @@ -41,0 +49 @@
   +[log.phase|phase:       draft]
+  @@ -46,0 +55 @@
   +[log.phase|phase:       draft]
+  @@ -51,0 +61 @@
   +[log.phase|phase:       draft]
 
   $ hg --color=debug -v log > log.out
   $ hg --color=debug -v log --style default > style.out
   $ cmp log.out style.out || diff -u log.out style.out
   $ hg --color=debug -v log -T phases > phases.out
-  $ diff -u log.out phases.out | grep phase:
+  $ diff -U 0 log.out phases.out | grep -v '^---\|^+++'
+  @@ -2,0 +3 @@
   +[log.phase|phase:       draft]
+  @@ -7,0 +9 @@
   +[log.phase|phase:       draft]
+  @@ -15,0 +18 @@
   +[log.phase|phase:       draft]
+  @@ -24,0 +28 @@
   +[log.phase|phase:       draft]
+  @@ -33,0 +38 @@
   +[log.phase|phase:       draft]
+  @@ -43,0 +49 @@
   +[log.phase|phase:       draft]
+  @@ -50,0 +57 @@
   +[log.phase|phase:       draft]
+  @@ -58,0 +66 @@
   +[log.phase|phase:       draft]
+  @@ -66,0 +75 @@
   +[log.phase|phase:       draft]
+  @@ -77,0 +87 @@
   +[log.phase|phase:       draft]
 
   $ hg --color=debug -q log > log.out
@@ -952,11 +1024,11 @@
 
   $ hg log --style notexist
   abort: style 'notexist' not found
-  (available styles: bisect, changelog, compact, default, phases, xml)
+  (available styles: bisect, changelog, compact, default, phases, status, xml)
   [255]
 
   $ hg log -T list
-  available styles: bisect, changelog, compact, default, phases, xml
+  available styles: bisect, changelog, compact, default, phases, status, xml
   abort: specify a template
   [255]
 
@@ -1898,6 +1970,8 @@
 
 Age filter:
 
+  $ hg init unstable-hash
+  $ cd unstable-hash
   $ hg log --template '{date|age}\n' > /dev/null || exit 1
 
   >>> from datetime import datetime, timedelta
@@ -1911,6 +1985,15 @@
   $ hg log -l1 --template '{date|age}\n'
   7 years from now
 
+  $ cd ..
+  $ rm -rf unstable-hash
+
+Add a dummy commit to make up for the instability of the above:
+
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m future
+
 Count filter:
 
   $ hg log -l1 --template '{node|count} {node|short|count}\n'
@@ -1953,6 +2036,476 @@
   abort: template filter 'upper' is not compatible with keyword 'date'
   [255]
 
+Add a commit that does all possible modifications at once
+
+  $ echo modify >> third
+  $ touch b
+  $ hg add b
+  $ hg mv fourth fifth
+  $ hg rm a
+  $ hg ci -m "Modify, add, remove, rename"
+
+Check the status template
+
+  $ cat <<EOF >> $HGRCPATH
+  > [extensions]
+  > color=
+  > EOF
+
+  $ hg log -T status -r 10
+  changeset:   10:0f9759ec227a
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     Modify, add, remove, rename
+  files:
+  M third
+  A b
+  A fifth
+  R a
+  R fourth
+  
+  $ hg log -T status -C -r 10
+  changeset:   10:0f9759ec227a
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     Modify, add, remove, rename
+  files:
+  M third
+  A b
+  A fifth
+    fourth
+  R a
+  R fourth
+  
+  $ hg log -T status -C -r 10 -v
+  changeset:   10:0f9759ec227a
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  description:
+  Modify, add, remove, rename
+  
+  files:
+  M third
+  A b
+  A fifth
+    fourth
+  R a
+  R fourth
+  
+  $ hg log -T status -C -r 10 --debug
+  changeset:   10:0f9759ec227a4859c2014a345cd8a859022b7c6c
+  tag:         tip
+  phase:       secret
+  parent:      9:bf9dfba36635106d6a73ccc01e28b762da60e066
+  parent:      -1:0000000000000000000000000000000000000000
+  manifest:    8:89dd546f2de0a9d6d664f58d86097eb97baba567
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  extra:       branch=default
+  description:
+  Modify, add, remove, rename
+  
+  files:
+  M third
+  A b
+  A fifth
+    fourth
+  R a
+  R fourth
+  
+  $ hg log -T status -C -r 10 --quiet
+  10:0f9759ec227a
+  $ hg --color=debug log -T status -r 10
+  [log.changeset changeset.secret|changeset:   10:0f9759ec227a]
+  [log.tag|tag:         tip]
+  [log.user|user:        test]
+  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
+  [log.summary|summary:     Modify, add, remove, rename]
+  [ui.note log.files|files:]
+  [status.modified|M third]
+  [status.added|A b]
+  [status.added|A fifth]
+  [status.removed|R a]
+  [status.removed|R fourth]
+  
+  $ hg --color=debug log -T status -C -r 10
+  [log.changeset changeset.secret|changeset:   10:0f9759ec227a]
+  [log.tag|tag:         tip]
+  [log.user|user:        test]
+  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
+  [log.summary|summary:     Modify, add, remove, rename]
+  [ui.note log.files|files:]
+  [status.modified|M third]
+  [status.added|A b]
+  [status.added|A fifth]
+  [status.copied|  fourth]
+  [status.removed|R a]
+  [status.removed|R fourth]
+  
+  $ hg --color=debug log -T status -C -r 10 -v
+  [log.changeset changeset.secret|changeset:   10:0f9759ec227a]
+  [log.tag|tag:         tip]
+  [log.user|user:        test]
+  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
+  [ui.note log.description|description:]
+  [ui.note log.description|Modify, add, remove, rename]
+  
+  [ui.note log.files|files:]
+  [status.modified|M third]
+  [status.added|A b]
+  [status.added|A fifth]
+  [status.copied|  fourth]
+  [status.removed|R a]
+  [status.removed|R fourth]
+  
+  $ hg --color=debug log -T status -C -r 10 --debug
+  [log.changeset changeset.secret|changeset:   10:0f9759ec227a4859c2014a345cd8a859022b7c6c]
+  [log.tag|tag:         tip]
+  [log.phase|phase:       secret]
+  [log.parent changeset.secret|parent:      9:bf9dfba36635106d6a73ccc01e28b762da60e066]
+  [log.parent changeset.public|parent:      -1:0000000000000000000000000000000000000000]
+  [ui.debug log.manifest|manifest:    8:89dd546f2de0a9d6d664f58d86097eb97baba567]
+  [log.user|user:        test]
+  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
+  [ui.debug log.extra|extra:       branch=default]
+  [ui.note log.description|description:]
+  [ui.note log.description|Modify, add, remove, rename]
+  
+  [ui.note log.files|files:]
+  [status.modified|M third]
+  [status.added|A b]
+  [status.added|A fifth]
+  [status.copied|  fourth]
+  [status.removed|R a]
+  [status.removed|R fourth]
+  
+  $ hg --color=debug log -T status -C -r 10 --quiet
+  [log.node|10:0f9759ec227a]
+
+Check the bisect template
+
+  $ hg bisect -g 1
+  $ hg bisect -b 3 --noupdate
+  Testing changeset 2:97054abb4ab8 (2 changesets remaining, ~1 tests)
+  $ hg log -T bisect -r 0:4
+  changeset:   0:1e4e1b8f71e0
+  bisect:      good (implicit)
+  user:        User Name <user@hostname>
+  date:        Mon Jan 12 13:46:40 1970 +0000
+  summary:     line 1
+  
+  changeset:   1:b608e9d1a3f0
+  bisect:      good
+  user:        A. N. Other <other@place>
+  date:        Tue Jan 13 17:33:20 1970 +0000
+  summary:     other 1
+  
+  changeset:   2:97054abb4ab8
+  bisect:      untested
+  user:        other@place
+  date:        Wed Jan 14 21:20:00 1970 +0000
+  summary:     no person
+  
+  changeset:   3:10e46f2dcbf4
+  bisect:      bad
+  user:        person
+  date:        Fri Jan 16 01:06:40 1970 +0000
+  summary:     no user, no domain
+  
+  changeset:   4:bbe44766e73d
+  bisect:      bad (implicit)
+  branch:      foo
+  user:        person
+  date:        Sat Jan 17 04:53:20 1970 +0000
+  summary:     new branch
+  
+  $ hg log --debug -T bisect -r 0:4
+  changeset:   0:1e4e1b8f71e05681d422154f5421e385fec3454f
+  bisect:      good (implicit)
+  phase:       public
+  parent:      -1:0000000000000000000000000000000000000000
+  parent:      -1:0000000000000000000000000000000000000000
+  manifest:    0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
+  user:        User Name <user@hostname>
+  date:        Mon Jan 12 13:46:40 1970 +0000
+  files+:      a
+  extra:       branch=default
+  description:
+  line 1
+  line 2
+  
+  
+  changeset:   1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965
+  bisect:      good
+  phase:       public
+  parent:      0:1e4e1b8f71e05681d422154f5421e385fec3454f
+  parent:      -1:0000000000000000000000000000000000000000
+  manifest:    1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
+  user:        A. N. Other <other@place>
+  date:        Tue Jan 13 17:33:20 1970 +0000
+  files+:      b
+  extra:       branch=default
+  description:
+  other 1
+  other 2
+  
+  other 3
+  
+  
+  changeset:   2:97054abb4ab824450e9164180baf491ae0078465
+  bisect:      untested
+  phase:       public
+  parent:      1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965
+  parent:      -1:0000000000000000000000000000000000000000
+  manifest:    2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
+  user:        other@place
+  date:        Wed Jan 14 21:20:00 1970 +0000
+  files+:      c
+  extra:       branch=default
+  description:
+  no person
+  
+  
+  changeset:   3:10e46f2dcbf4823578cf180f33ecf0b957964c47
+  bisect:      bad
+  phase:       public
+  parent:      2:97054abb4ab824450e9164180baf491ae0078465
+  parent:      -1:0000000000000000000000000000000000000000
+  manifest:    3:cb5a1327723bada42f117e4c55a303246eaf9ccc
+  user:        person
+  date:        Fri Jan 16 01:06:40 1970 +0000
+  files:       c
+  extra:       branch=default
+  description:
+  no user, no domain
+  
+  
+  changeset:   4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
+  bisect:      bad (implicit)
+  branch:      foo
+  phase:       draft
+  parent:      3:10e46f2dcbf4823578cf180f33ecf0b957964c47
+  parent:      -1:0000000000000000000000000000000000000000
+  manifest:    3:cb5a1327723bada42f117e4c55a303246eaf9ccc
+  user:        person
+  date:        Sat Jan 17 04:53:20 1970 +0000
+  extra:       branch=foo
+  description:
+  new branch
+  
+  
+  $ hg log -v -T bisect -r 0:4
+  changeset:   0:1e4e1b8f71e0
+  bisect:      good (implicit)
+  user:        User Name <user@hostname>
+  date:        Mon Jan 12 13:46:40 1970 +0000
+  files:       a
+  description:
+  line 1
+  line 2
+  
+  
+  changeset:   1:b608e9d1a3f0
+  bisect:      good
+  user:        A. N. Other <other@place>
+  date:        Tue Jan 13 17:33:20 1970 +0000
+  files:       b
+  description:
+  other 1
+  other 2
+  
+  other 3
+  
+  
+  changeset:   2:97054abb4ab8
+  bisect:      untested
+  user:        other@place
+  date:        Wed Jan 14 21:20:00 1970 +0000
+  files:       c
+  description:
+  no person
+  
+  
+  changeset:   3:10e46f2dcbf4
+  bisect:      bad
+  user:        person
+  date:        Fri Jan 16 01:06:40 1970 +0000
+  files:       c
+  description:
+  no user, no domain
+  
+  
+  changeset:   4:bbe44766e73d
+  bisect:      bad (implicit)
+  branch:      foo
+  user:        person
+  date:        Sat Jan 17 04:53:20 1970 +0000
+  description:
+  new branch
+  
+  
+  $ hg --color=debug log -T bisect -r 0:4
+  [log.changeset changeset.public|changeset:   0:1e4e1b8f71e0]
+  [log.bisect bisect.good|bisect:      good (implicit)]
+  [log.user|user:        User Name <user@hostname>]
+  [log.date|date:        Mon Jan 12 13:46:40 1970 +0000]
+  [log.summary|summary:     line 1]
+  
+  [log.changeset changeset.public|changeset:   1:b608e9d1a3f0]
+  [log.bisect bisect.good|bisect:      good]
+  [log.user|user:        A. N. Other <other@place>]
+  [log.date|date:        Tue Jan 13 17:33:20 1970 +0000]
+  [log.summary|summary:     other 1]
+  
+  [log.changeset changeset.public|changeset:   2:97054abb4ab8]
+  [log.bisect bisect.untested|bisect:      untested]
+  [log.user|user:        other@place]
+  [log.date|date:        Wed Jan 14 21:20:00 1970 +0000]
+  [log.summary|summary:     no person]
+  
+  [log.changeset changeset.public|changeset:   3:10e46f2dcbf4]
+  [log.bisect bisect.bad|bisect:      bad]
+  [log.user|user:        person]
+  [log.date|date:        Fri Jan 16 01:06:40 1970 +0000]
+  [log.summary|summary:     no user, no domain]
+  
+  [log.changeset changeset.draft|changeset:   4:bbe44766e73d]
+  [log.bisect bisect.bad|bisect:      bad (implicit)]
+  [log.branch|branch:      foo]
+  [log.user|user:        person]
+  [log.date|date:        Sat Jan 17 04:53:20 1970 +0000]
+  [log.summary|summary:     new branch]
+  
+  $ hg --color=debug log --debug -T bisect -r 0:4
+  [log.changeset changeset.public|changeset:   0:1e4e1b8f71e05681d422154f5421e385fec3454f]
+  [log.bisect bisect.good|bisect:      good (implicit)]
+  [log.phase|phase:       public]
+  [log.parent changeset.public|parent:      -1:0000000000000000000000000000000000000000]
+  [log.parent changeset.public|parent:      -1:0000000000000000000000000000000000000000]
+  [ui.debug log.manifest|manifest:    0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0]
+  [log.user|user:        User Name <user@hostname>]
+  [log.date|date:        Mon Jan 12 13:46:40 1970 +0000]
+  [ui.debug log.files|files+:      a]
+  [ui.debug log.extra|extra:       branch=default]
+  [ui.note log.description|description:]
+  [ui.note log.description|line 1
+  line 2]
+  
+  
+  [log.changeset changeset.public|changeset:   1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965]
+  [log.bisect bisect.good|bisect:      good]
+  [log.phase|phase:       public]
+  [log.parent changeset.public|parent:      0:1e4e1b8f71e05681d422154f5421e385fec3454f]
+  [log.parent changeset.public|parent:      -1:0000000000000000000000000000000000000000]
+  [ui.debug log.manifest|manifest:    1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55]
+  [log.user|user:        A. N. Other <other@place>]
+  [log.date|date:        Tue Jan 13 17:33:20 1970 +0000]
+  [ui.debug log.files|files+:      b]
+  [ui.debug log.extra|extra:       branch=default]
+  [ui.note log.description|description:]
+  [ui.note log.description|other 1
+  other 2
+  
+  other 3]
+  
+  
+  [log.changeset changeset.public|changeset:   2:97054abb4ab824450e9164180baf491ae0078465]
+  [log.bisect bisect.untested|bisect:      untested]
+  [log.phase|phase:       public]
+  [log.parent changeset.public|parent:      1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965]
+  [log.parent changeset.public|parent:      -1:0000000000000000000000000000000000000000]
+  [ui.debug log.manifest|manifest:    2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1]
+  [log.user|user:        other@place]
+  [log.date|date:        Wed Jan 14 21:20:00 1970 +0000]
+  [ui.debug log.files|files+:      c]
+  [ui.debug log.extra|extra:       branch=default]
+  [ui.note log.description|description:]
+  [ui.note log.description|no person]
+  
+  
+  [log.changeset changeset.public|changeset:   3:10e46f2dcbf4823578cf180f33ecf0b957964c47]
+  [log.bisect bisect.bad|bisect:      bad]
+  [log.phase|phase:       public]
+  [log.parent changeset.public|parent:      2:97054abb4ab824450e9164180baf491ae0078465]
+  [log.parent changeset.public|parent:      -1:0000000000000000000000000000000000000000]
+  [ui.debug log.manifest|manifest:    3:cb5a1327723bada42f117e4c55a303246eaf9ccc]
+  [log.user|user:        person]
+  [log.date|date:        Fri Jan 16 01:06:40 1970 +0000]
+  [ui.debug log.files|files:       c]
+  [ui.debug log.extra|extra:       branch=default]
+  [ui.note log.description|description:]
+  [ui.note log.description|no user, no domain]
+  
+  
+  [log.changeset changeset.draft|changeset:   4:bbe44766e73d5f11ed2177f1838de10c53ef3e74]
+  [log.bisect bisect.bad|bisect:      bad (implicit)]
+  [log.branch|branch:      foo]
+  [log.phase|phase:       draft]
+  [log.parent changeset.public|parent:      3:10e46f2dcbf4823578cf180f33ecf0b957964c47]
+  [log.parent changeset.public|parent:      -1:0000000000000000000000000000000000000000]
+  [ui.debug log.manifest|manifest:    3:cb5a1327723bada42f117e4c55a303246eaf9ccc]
+  [log.user|user:        person]
+  [log.date|date:        Sat Jan 17 04:53:20 1970 +0000]
+  [ui.debug log.extra|extra:       branch=foo]
+  [ui.note log.description|description:]
+  [ui.note log.description|new branch]
+  
+  
+  $ hg --color=debug log -v -T bisect -r 0:4
+  [log.changeset changeset.public|changeset:   0:1e4e1b8f71e0]
+  [log.bisect bisect.good|bisect:      good (implicit)]
+  [log.user|user:        User Name <user@hostname>]
+  [log.date|date:        Mon Jan 12 13:46:40 1970 +0000]
+  [ui.note log.files|files:       a]
+  [ui.note log.description|description:]
+  [ui.note log.description|line 1
+  line 2]
+  
+  
+  [log.changeset changeset.public|changeset:   1:b608e9d1a3f0]
+  [log.bisect bisect.good|bisect:      good]
+  [log.user|user:        A. N. Other <other@place>]
+  [log.date|date:        Tue Jan 13 17:33:20 1970 +0000]
+  [ui.note log.files|files:       b]
+  [ui.note log.description|description:]
+  [ui.note log.description|other 1
+  other 2
+  
+  other 3]
+  
+  
+  [log.changeset changeset.public|changeset:   2:97054abb4ab8]
+  [log.bisect bisect.untested|bisect:      untested]
+  [log.user|user:        other@place]
+  [log.date|date:        Wed Jan 14 21:20:00 1970 +0000]
+  [ui.note log.files|files:       c]
+  [ui.note log.description|description:]
+  [ui.note log.description|no person]
+  
+  
+  [log.changeset changeset.public|changeset:   3:10e46f2dcbf4]
+  [log.bisect bisect.bad|bisect:      bad]
+  [log.user|user:        person]
+  [log.date|date:        Fri Jan 16 01:06:40 1970 +0000]
+  [ui.note log.files|files:       c]
+  [ui.note log.description|description:]
+  [ui.note log.description|no user, no domain]
+  
+  
+  [log.changeset changeset.draft|changeset:   4:bbe44766e73d]
+  [log.bisect bisect.bad|bisect:      bad (implicit)]
+  [log.branch|branch:      foo]
+  [log.user|user:        person]
+  [log.date|date:        Sat Jan 17 04:53:20 1970 +0000]
+  [ui.note log.description|description:]
+  [ui.note log.description|new branch]
+  
+  
+  $ hg bisect --reset
+
 Error on syntax:
 
   $ echo 'x = "f' >> t
@@ -1960,6 +2513,10 @@
   abort: t:3: unmatched quotes
   [255]
 
+  $ hg log -T '{date'
+  hg: parse error at 1: unterminated template expansion
+  [255]
+
 Behind the scenes, this will throw TypeError
 
   $ hg log -l 3 --template '{date|obfuscate}\n'
@@ -1984,6 +2541,16 @@
   abort: template filter 'datefilter' is not compatible with keyword 'author'
   [255]
 
+Error in nested template:
+
+  $ hg log -T '{"date'
+  hg: parse error at 2: unterminated string
+  [255]
+
+  $ hg log -T '{"foo{date|=}"}'
+  hg: parse error at 11: syntax error
+  [255]
+
 Thrown an error if a template function doesn't exist
 
   $ hg tip --template '{foo()}\n'
@@ -2249,6 +2816,54 @@
   hg: parse error: date expects a date information
   [255]
 
+Test integer literal:
+
+  $ hg log -Ra -r0 -T '{(0)}\n'
+  0
+  $ hg log -Ra -r0 -T '{(123)}\n'
+  123
+  $ hg log -Ra -r0 -T '{(-4)}\n'
+  -4
+  $ hg log -Ra -r0 -T '{(-)}\n'
+  hg: parse error at 2: integer literal without digits
+  [255]
+  $ hg log -Ra -r0 -T '{(-a)}\n'
+  hg: parse error at 2: integer literal without digits
+  [255]
+
+top-level integer literal is interpreted as symbol (i.e. variable name):
+
+  $ hg log -Ra -r0 -T '{1}\n'
+  
+  $ hg log -Ra -r0 -T '{if("t", "{1}")}\n'
+  
+  $ hg log -Ra -r0 -T '{1|stringify}\n'
+  
+
+unless explicit symbol is expected:
+
+  $ hg log -Ra -r0 -T '{desc|1}\n'
+  hg: parse error: expected a symbol, got 'integer'
+  [255]
+  $ hg log -Ra -r0 -T '{1()}\n'
+  hg: parse error: expected a symbol, got 'integer'
+  [255]
+
+Test string literal:
+
+  $ hg log -Ra -r0 -T '{"string with no template fragment"}\n'
+  string with no template fragment
+  $ hg log -Ra -r0 -T '{"template: {rev}"}\n'
+  template: 0
+  $ hg log -Ra -r0 -T '{r"rawstring: {rev}"}\n'
+  rawstring: {rev}
+
+because map operation requires template, raw string can't be used
+
+  $ hg log -Ra -r0 -T '{files % r"rawstring"}\n'
+  hg: parse error: expected template specifier
+  [255]
+
 Test string escaping:
 
   $ hg log -R latesttag -r 0 --template '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
@@ -2291,11 +2906,34 @@
   $ hg log -Ra -r0 -T '{r"\\\""}\n'
   \\\"
 
+
+  $ hg log -Ra -r0 -T '{"\""}\n'
+  "
+  $ hg log -Ra -r0 -T '{"\\\""}\n'
+  \"
+  $ hg log -Ra -r0 -T '{r"\""}\n'
+  \"
+  $ hg log -Ra -r0 -T '{r"\\\""}\n'
+  \\\"
+
+Test exception in quoted template. single backslash before quotation mark is
+stripped before parsing:
+
+  $ cat <<'EOF' > escquotetmpl
+  > changeset = "\" \\" \\\" \\\\" {files % \"{file}\"}\n"
+  > EOF
+  $ cd latesttag
+  $ hg log -r 2 --style ../escquotetmpl
+  " \" \" \\" head1
+
+  $ hg log -r 2 -T esc --config templates.esc='"{\"valid\"}\n"'
+  valid
+  $ hg log -r 2 -T esc --config templates.esc="'"'{\'"'"'valid\'"'"'}\n'"'"
+  valid
+
 Test compatibility with 2.9.2-3.4 of escaped quoted strings in nested
 _evalifliteral() templates (issue4733):
 
-  $ cd latesttag
-
   $ hg log -r 2 -T '{if(rev, "\"{rev}")}\n'
   "2
   $ hg log -r 2 -T '{if(rev, "{if(rev, \"\\\"{rev}\")}")}\n'
@@ -2324,7 +2962,7 @@
   $ hg log -r 2 -T "{if(rev, '{if(rev, r\'foo\')}')}"'\n'
   foo
   $ hg log -r 2 -T '{if(rev, "{if(rev, \")}")}\n'
-  hg: parse error at 11: unterminated string
+  hg: parse error at 21: unterminated string
   [255]
   $ hg log -r 2 -T '{if(rev, \"\\"")}\n'
   hg: parse error at 11: syntax error
@@ -2338,23 +2976,23 @@
 Test leading backslashes:
 
   $ cd latesttag
-  $ hg log -r 2 -T '\{rev} {files % "\{file}"} {files % r"\{file}"}\n'
-  {rev} {file} \head1
-  $ hg log -r 2 -T '\\{rev} {files % "\\{file}"} {files % r"\\{file}"}\n'
-  \2 \head1 \\head1
-  $ hg log -r 2 -T '\\\{rev} {files % "\\\{file}"} {files % r"\\\{file}"}\n'
-  \{rev} \{file} \\\head1
+  $ hg log -r 2 -T '\{rev} {files % "\{file}"}\n'
+  {rev} {file}
+  $ hg log -r 2 -T '\\{rev} {files % "\\{file}"}\n'
+  \2 \head1
+  $ hg log -r 2 -T '\\\{rev} {files % "\\\{file}"}\n'
+  \{rev} \{file}
   $ cd ..
 
 Test leading backslashes in "if" expression (issue4714):
 
   $ cd latesttag
   $ hg log -r 2 -T '{if("1", "\{rev}")} {if("1", r"\{rev}")}\n'
-  {rev} \2
+  {rev} \{rev}
   $ hg log -r 2 -T '{if("1", "\\{rev}")} {if("1", r"\\{rev}")}\n'
-  \2 \\2
+  \2 \\{rev}
   $ hg log -r 2 -T '{if("1", "\\\{rev}")} {if("1", r"\\\{rev}")}\n'
-  \{rev} \\\2
+  \{rev} \\\{rev}
   $ cd ..
 
 "string-escape"-ed "\x5c\x786e" becomes r"\x6e" (once) or r"n" (twice)
@@ -2424,8 +3062,6 @@
   fourth
   second
   third
-  $ hg log -R a -r 8 --template '{files % r"{file}\n"}\n'
-  fourth\nsecond\nthird\n
 
 Test string escaping in nested expression:
 
@@ -2443,6 +3079,14 @@
   3:\x6eo user, \x6eo domai\x6e
   4:\x5c\x786eew bra\x5c\x786ech
 
+Test quotes in nested expression are evaluated just like a $(command)
+substitution in POSIX shells:
+
+  $ hg log -R a -r 8 -T '{"{"{rev}:{node|short}"}"}\n'
+  8:95c24699272e
+  $ hg log -R a -r 8 -T '{"{"\{{rev}} \"{node|short}\""}"}\n'
+  {8} "95c24699272e"
+
 Test recursive evaluation:
 
   $ hg init r
@@ -2531,6 +3175,14 @@
   1------------------- {node|short}
   0------------------- test
 
+Test template string in pad function
+
+  $ hg log -r 0 -T '{pad("\{{rev}}", 10)} {author|user}\n'
+  {0}        test
+
+  $ hg log -r 0 -T '{pad(r"\{rev}", 10)} {author|user}\n'
+  \{rev}     test
+
 Test ifcontains function
 
   $ hg log --template '{rev} {ifcontains(rev, "2 two 0", "is in the string", "is not")}\n'
@@ -2585,20 +3237,20 @@
   $ hg log --template '{revset("TIP"|lower)}\n' -l1
   2
 
-Test current bookmark templating
+Test active bookmark templating
 
   $ hg book foo
   $ hg book bar
-  $ hg log --template "{rev} {bookmarks % '{bookmark}{ifeq(bookmark, current, \"*\")} '}\n"
+  $ hg log --template "{rev} {bookmarks % '{bookmark}{ifeq(bookmark, active, \"*\")} '}\n"
   2 bar* foo 
   1 
   0 
-  $ hg log --template "{rev} {currentbookmark}\n"
+  $ hg log --template "{rev} {activebookmark}\n"
   2 bar
   1 
   0 
   $ hg bookmarks --inactive bar
-  $ hg log --template "{rev} {currentbookmark}\n"
+  $ hg log --template "{rev} {activebookmark}\n"
   2 
   1 
   0 
@@ -2623,7 +3275,9 @@
 Test splitlines
 
   $ hg log -Gv -R a --template "{splitlines(desc) % 'foo {line}\n'}"
-  @  foo future
+  @  foo Modify, add, remove, rename
+  |
+  o  foo future
   |
   o  foo third
   |
@@ -2657,6 +3311,8 @@
   o
   |
   o
+  |
+  o
   
   o
   |\
@@ -2682,7 +3338,9 @@
 Test word function (including index out of bounds graceful failure)
 
   $ hg log -Gv -R a --template "{word('1', desc)}"
-  @
+  @  add,
+  |
+  o
   |
   o
   |
@@ -2706,7 +3364,9 @@
 Test word third parameter used as splitter
 
   $ hg log -Gv -R a --template "{word('0', desc, 'o')}"
-  @  future
+  @  M
+  |
+  o  future
   |
   o  third
   |
@@ -2737,8 +3397,31 @@
   hg: parse error: word expects two or three arguments, got 7
   [255]
 
+Test word for integer literal
+
+  $ hg log -R a --template "{word(2, desc)}\n" -r0
+  line
+
 Test word for invalid numbers
 
-  $ hg log -Gv -R a --template "{word(2, desc)}"
-  hg: parse error: Use strings like '3' for numbers passed to word function
+  $ hg log -Gv -R a --template "{word('a', desc)}"
+  hg: parse error: word expects an integer index
   [255]
+
+Test indent and not adding to empty lines
+
+  $ hg log -T "-----\n{indent(desc, '>> ', ' > ')}\n" -r 0:1 -R a
+  -----
+   > line 1
+  >> line 2
+  -----
+   > other 1
+  >> other 2
+  
+  >> other 3
+
+Test with non-strings like dates
+
+  $ hg log -T "{indent(date, '   ')}\n" -r 2:3 -R a
+     1200000.00
+     1300000.00
--- a/tests/test-commandserver.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-commandserver.t	Sat Jul 18 17:32:38 2015 -0500
@@ -178,7 +178,7 @@
   defaults.commit=-d "0 0"
   defaults.shelve=--date "0 0"
   defaults.tag=-d "0 0"
-  devel.all=true
+  devel.all-warnings=true
   largefiles.usercache=$TESTTMP/.cache/largefiles
   ui.slash=True
   ui.interactive=False
--- a/tests/test-commit-amend.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-commit-amend.t	Sat Jul 18 17:32:38 2015 -0500
@@ -72,6 +72,7 @@
   branch: default
   commit: 1 added, 1 unknown
   update: (current)
+  phases: 2 draft
   $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend
   transaction abort!
   rollback completed
@@ -83,6 +84,7 @@
   branch: default
   commit: 1 added, 1 unknown
   update: (current)
+  phases: 2 draft
 
 Add new file:
   $ hg ci --amend -m 'amend base1 new file'
@@ -371,7 +373,6 @@
   $ hg ci -m 'branch foo'
   $ hg branch default -f
   marked working directory as branch default
-  (branches are permanent and global, did you want a bookmark?)
   $ hg ci --amend -m 'back to default'
   saved backup bundle to $TESTTMP/.hg/strip-backup/8ac881fbf49d-fd962fef-amend-backup.hg (glob)
   $ hg branches
@@ -846,7 +847,6 @@
   $ hg up -q default
   $ hg branch closewithamend
   marked working directory as branch closewithamend
-  (branches are permanent and global, did you want a bookmark?)
   $ echo foo > foo
   $ hg add foo
   $ hg ci -m..
@@ -858,7 +858,6 @@
 
   $ hg branch silliness
   marked working directory as branch silliness
-  (branches are permanent and global, did you want a bookmark?)
   $ echo b >> b
   $ hg ci --close-branch -m'open and close'
   abort: can only close branch heads
--- a/tests/test-commit-interactive-curses.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-commit-interactive-curses.t	Sat Jul 18 17:32:38 2015 -0500
@@ -65,14 +65,16 @@
   a
   a
 
-Committing only one hunk
+Committing only one hunk while aborting edition of hunk
 
 - Untoggle all the hunks, go down to the second file
 - unfold it
 - go down to second hunk (1 for the first hunk, 1 for the first hunkline, 1 for the second hunk, 1 for the second hunklike)
 - toggle the second hunk
+- edit the hunk and quit the editor imediately with non-zero status
 - commit
 
+  $ printf "printf 'editor ran\n'; exit 1" > editor.sh
   $ echo "x" > c
   $ cat b >> c
   $ echo "y" >> c
@@ -86,9 +88,12 @@
   > KEY_DOWN
   > KEY_DOWN
   > TOGGLE
+  > e
   > X
   > EOF
-  $ hg commit -i  -m "one hunk" -d "0 0"
+  $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i  -m "one hunk" -d "0 0"
+  editor ran
+  $ rm editor.sh
   $ hg tip
   changeset:   2:7d10dfe755a8
   tag:         tip
--- a/tests/test-commit-interactive.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-commit-interactive.t	Sat Jul 18 17:32:38 2015 -0500
@@ -15,6 +15,12 @@
   $ touch empty-rw
   $ hg add empty-rw
 
+  $ hg record --config ui.interactive=false
+  abort: running non-interactively, use commit instead
+  [255]
+  $ hg commit -i --config ui.interactive=false
+  abort: running non-interactively
+  [255]
   $ hg commit -i empty-rw<<EOF
   > n
   > EOF
@@ -81,6 +87,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 1 draft
 
 Rename empty file
 
@@ -1290,6 +1297,33 @@
   abort: error parsing patch: unhandled transition: range -> range
   [255]
 
+Exiting editor with status 1, ignores the edit but does not stop the recording
+session
+
+  $ HGEDITOR=false hg commit -i <<EOF
+  > y
+  > e
+  > n
+  > EOF
+  diff --git a/editedfile b/editedfile
+  1 hunks, 3 lines changed
+  examine changes to 'editedfile'? [Ynesfdaq?] y
+  
+  @@ -1,3 +1,3 @@
+  -This is the first line
+  -This change will be committed
+  -This is the third line
+  +This change will not be committed
+  +This is the second line
+  +This line has been added
+  record this change to 'editedfile'? [Ynesfdaq?] e
+  
+  editor exited with exit code 1
+  record this change to 'editedfile'? [Ynesfdaq?] n
+  
+  no changes to record
+
+
 random text in random positions is still an error
 
   $ cat > editor.sh << '__EOF__'
@@ -1353,6 +1387,8 @@
   record this change to 'subdir/f1'? [Ynesfdaq?] y
   
 
+  $ hg status -A subdir/f1
+  C subdir/f1
   $ hg tip -p
   changeset:   28:* (glob)
   tag:         tip
@@ -1389,6 +1425,8 @@
   +e
   record this change to 'subdir/f1'? [Ynesfdaq?] y
   
+  $ hg status -A subdir/f1
+  C subdir/f1
   $ hg log --template '{author}\n' -l 1
   xyz
   $ HGUSER="test"
@@ -1398,7 +1436,7 @@
 Moving files
 
   $ hg update -C .
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg mv plain plain3
   $ echo somechange >> plain3
   $ hg commit -i -d '23 0' -mmoving_files << EOF
@@ -1419,6 +1457,8 @@
   record this change to 'plain3'? [Ynesfdaq?] y
   
 The #if execbit block above changes the hash here on some systems
+  $ hg status -A plain3
+  C plain3
   $ hg tip
   changeset:   30:* (glob)
   tag:         tip
@@ -1498,3 +1538,98 @@
   +foo
   
   $ cd ..
+
+  $ hg status -A folder/bar
+  C folder/bar
+
+Clear win32text configuration before size/timestamp sensitive test
+
+  $ cat >> .hg/hgrc <<EOF
+  > [extensions]
+  > win32text = !
+  > [decode]
+  > ** = !
+  > [encode]
+  > ** = !
+  > [patch]
+  > eol = strict
+  > EOF
+  $ hg update -q -C null
+  $ hg update -q -C tip
+
+Test that partially committed file is still treated as "modified",
+even if none of mode, size and timestamp is changed on the filesystem
+(see also issue4583).
+
+  $ cat > subdir/f1 <<EOF
+  > A
+  > a
+  > a
+  > b
+  > c
+  > d
+  > E
+  > EOF
+  $ hg diff --git subdir/f1
+  diff --git a/subdir/f1 b/subdir/f1
+  --- a/subdir/f1
+  +++ b/subdir/f1
+  @@ -1,7 +1,7 @@
+  -a
+  +A
+   a
+   a
+   b
+   c
+   d
+  -e
+  +E
+
+  $ touch -t 200001010000 subdir/f1
+
+  $ cat >> .hg/hgrc <<EOF
+  > # emulate invoking patch.internalpatch() at 2000-01-01 00:00
+  > [fakepatchtime]
+  > fakenow = 200001010000
+  > 
+  > [extensions]
+  > fakepatchtime = $TESTDIR/fakepatchtime.py
+  > EOF
+  $ hg commit -i -m 'commit subdir/f1 partially' <<EOF
+  > y
+  > y
+  > n
+  > EOF
+  diff --git a/subdir/f1 b/subdir/f1
+  2 hunks, 2 lines changed
+  examine changes to 'subdir/f1'? [Ynesfdaq?] y
+  
+  @@ -1,6 +1,6 @@
+  -a
+  +A
+   a
+   a
+   b
+   c
+   d
+  record change 1/2 to 'subdir/f1'? [Ynesfdaq?] y
+  
+  @@ -2,6 +2,6 @@
+   a
+   a
+   b
+   c
+   d
+  -e
+  +E
+  record change 2/2 to 'subdir/f1'? [Ynesfdaq?] n
+  
+  $ cat >> .hg/hgrc <<EOF
+  > [extensions]
+  > fakepatchtime = !
+  > EOF
+
+  $ hg debugstate | grep ' subdir/f1$'
+  n   0         -1 unset               subdir/f1
+  $ hg status -A subdir/f1
+  M subdir/f1
--- a/tests/test-commit-multiple.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-commit-multiple.t	Sat Jul 18 17:32:38 2015 -0500
@@ -52,7 +52,6 @@
   1 files updated, 0 files merged, 2 files removed, 0 files unresolved
   $ hg branch release
   marked working directory as branch release
-  (branches are permanent and global, did you want a bookmark?)
   $ hg transplant 2 3
   applying [0-9a-f]{12} (re)
   [0-9a-f]{12} transplanted to [0-9a-f]{12} (re)
--- a/tests/test-commit.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-commit.t	Sat Jul 18 17:32:38 2015 -0500
@@ -302,7 +302,7 @@
   $ cd commitmsg
   $ echo changed > changed
   $ echo removed > removed
-  $ hg book currentbookmark
+  $ hg book activebookmark
   $ hg ci -qAm init
 
   $ hg rm removed
@@ -317,7 +317,7 @@
   HG: --
   HG: user: test
   HG: branch 'default'
-  HG: bookmark 'currentbookmark'
+  HG: bookmark 'activebookmark'
   HG: added added
   HG: changed changed
   HG: removed removed
@@ -354,7 +354,7 @@
   HG: --
   HG: user: test
   HG: branch 'default'
-  HG: bookmark 'currentbookmark'
+  HG: bookmark 'activebookmark'
   HG: subrepo sub
   HG: added .hgsub
   HG: added added
@@ -376,22 +376,22 @@
   > [committemplate]
   > changeset.commit.normal = HG: this is "commit.normal" template
   >     HG: {extramsg}
-  >     {if(currentbookmark,
-  >    "HG: bookmark '{currentbookmark}' is activated\n",
+  >     {if(activebookmark,
+  >    "HG: bookmark '{activebookmark}' is activated\n",
   >    "HG: no bookmark is activated\n")}{subrepos %
   >    "HG: subrepo '{subrepo}' is changed\n"}
   > 
   > changeset.commit = HG: this is "commit" template
   >     HG: {extramsg}
-  >     {if(currentbookmark,
-  >    "HG: bookmark '{currentbookmark}' is activated\n",
+  >     {if(activebookmark,
+  >    "HG: bookmark '{activebookmark}' is activated\n",
   >    "HG: no bookmark is activated\n")}{subrepos %
   >    "HG: subrepo '{subrepo}' is changed\n"}
   > 
   > changeset = HG: this is customized commit template
   >     HG: {extramsg}
-  >     {if(currentbookmark,
-  >    "HG: bookmark '{currentbookmark}' is activated\n",
+  >     {if(activebookmark,
+  >    "HG: bookmark '{activebookmark}' is activated\n",
   >    "HG: no bookmark is activated\n")}{subrepos %
   >    "HG: subrepo '{subrepo}' is changed\n"}
   > EOF
@@ -404,7 +404,7 @@
   $ HGEDITOR=cat hg commit -S -q
   HG: this is "commit.normal" template
   HG: Leave message empty to abort commit.
-  HG: bookmark 'currentbookmark' is activated
+  HG: bookmark 'activebookmark' is activated
   HG: subrepo 'sub' is changed
   HG: subrepo 'sub2' is changed
   abort: empty commit message
@@ -416,7 +416,7 @@
   > # now, "changeset.commit" should be chosen for "hg commit"
   > EOF
 
-  $ hg bookmark --inactive currentbookmark
+  $ hg bookmark --inactive activebookmark
   $ hg forget .hgsub
   $ HGEDITOR=cat hg commit -q
   HG: this is "commit" template
@@ -580,6 +580,18 @@
        0         0       6  .....       0 26d3ca0dfd18 000000000000 000000000000 (re)
        1         6       7  .....       1 d267bddd54f7 26d3ca0dfd18 000000000000 (re)
 
+Test making empty commits
+  $ hg commit --config ui.allowemptycommit=True -m "empty commit"
+  $ hg log -r . -v --stat
+  changeset:   2:d809f3644287
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  description:
+  empty commit
+  
+  
+  
 verify pathauditor blocks evil filepaths
   $ cat > evil-commit.py <<EOF
   > from mercurial import ui, hg, context, node
@@ -604,7 +616,7 @@
 #endif
 
   $ hg rollback -f
-  repository tip rolled back to revision 1 (undo commit)
+  repository tip rolled back to revision 2 (undo commit)
   $ cat > evil-commit.py <<EOF
   > from mercurial import ui, hg, context, node
   > notrc = "HG~1/hgrc"
@@ -622,7 +634,7 @@
   [255]
 
   $ hg rollback -f
-  repository tip rolled back to revision 1 (undo commit)
+  repository tip rolled back to revision 2 (undo commit)
   $ cat > evil-commit.py <<EOF
   > from mercurial import ui, hg, context, node
   > notrc = "HG8B6C~2/hgrc"
--- a/tests/test-completion.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-completion.t	Sat Jul 18 17:32:38 2015 -0500
@@ -96,6 +96,7 @@
   debugpushkey
   debugpvec
   debugrebuilddirstate
+  debugrebuildfncache
   debugrename
   debugrevlog
   debugrevspec
@@ -234,7 +235,7 @@
   debugcommands: 
   debugcomplete: options
   debugdag: tags, branches, dots, spaces
-  debugdata: changelog, manifest
+  debugdata: changelog, manifest, dir
   debugdate: extended
   debugdirstate: nodates, datesort
   debugdiscovery: old, nonheads, ssh, remotecmd, insecure
@@ -242,7 +243,7 @@
   debugfsinfo: 
   debuggetbundle: head, common, type
   debugignore: 
-  debugindex: changelog, manifest, format
+  debugindex: changelog, manifest, dir, format
   debugindexdot: 
   debuginstall: 
   debugknown: 
@@ -254,8 +255,9 @@
   debugpushkey: 
   debugpvec: 
   debugrebuilddirstate: rev
+  debugrebuildfncache: 
   debugrename: rev
-  debugrevlog: changelog, manifest, dump
+  debugrevlog: changelog, manifest, dir, dump
   debugrevspec: optimize
   debugsetparents: 
   debugsub: rev
--- a/tests/test-convert-clonebranches.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-convert-clonebranches.t	Sat Jul 18 17:32:38 2015 -0500
@@ -55,13 +55,11 @@
   $ cd source
   $ hg branch branch1
   marked working directory as branch branch1
-  (branches are permanent and global, did you want a bookmark?)
   $ echo a > file1
   $ hg ci -qAm c1
   $ hg up -qC mergeab
   $ hg branch branch2
   marked working directory as branch branch2
-  (branches are permanent and global, did you want a bookmark?)
   $ echo a > file2
   $ hg ci -qAm c2
   $ hg merge branch1
@@ -69,7 +67,6 @@
   (branch merge, don't forget to commit)
   $ hg branch branch3
   marked working directory as branch branch3
-  (branches are permanent and global, did you want a bookmark?)
   $ hg ci -qAm c3
   $ cd ..
 
--- a/tests/test-convert-cvs.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-convert-cvs.t	Sat Jul 18 17:32:38 2015 -0500
@@ -121,7 +121,7 @@
   1 ci0
   0 import
   filtering out empty revision
-  repository tip rolled back to revision 1 (undo commit)
+  repository tip rolled back to revision 1 (undo convert)
   updating tags
   $ hgcat b/c
   c
--- a/tests/test-convert-datesort.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-convert-datesort.t	Sat Jul 18 17:32:38 2015 -0500
@@ -21,7 +21,6 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg branch branchb
   marked working directory as branch branchb
-  (branches are permanent and global, did you want a bookmark?)
   $ echo b >> b
   $ hg ci -Am b0 -d '6 0'
   adding b
@@ -42,7 +41,6 @@
   $ echo c >> c
   $ hg branch branchc
   marked working directory as branch branchc
-  (branches are permanent and global, did you want a bookmark?)
   $ hg ci -Am c0 -d '10 0'
   adding c
   $ hg up -C brancha
--- a/tests/test-convert-filemap.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-convert-filemap.t	Sat Jul 18 17:32:38 2015 -0500
@@ -438,7 +438,7 @@
   $ hg ci -m 'merging something'
   $ cd ..
   $ echo "53792d18237d2b64971fa571936869156655338d 6d955580116e82c4b029bd30f321323bae71a7f0" >> branchpruning-hg2/.hg/shamap
-  $ hg convert --filemap branchpruning/filemap branchpruning branchpruning-hg2 --debug
+  $ hg convert --filemap branchpruning/filemap branchpruning branchpruning-hg2 --debug --config progress.debug=true
   run hg source pre-conversion action
   run hg sink pre-conversion action
   scanning source...
@@ -477,7 +477,7 @@
   2 add
   1 rename
   filtering out empty revision
-  repository tip rolled back to revision 0 (undo commit)
+  repository tip rolled back to revision 0 (undo convert)
   0 modify
   $ glog -R renameundo2
   o  1 "modify" files: a c
--- a/tests/test-convert-git.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-convert-git.t	Sat Jul 18 17:32:38 2015 -0500
@@ -442,6 +442,40 @@
   abort: --sourcesort is not supported by this data source
   [255]
 
+test converting certain branches
+
+  $ mkdir git-testrevs
+  $ cd git-testrevs
+  $ git init
+  Initialized empty Git repository in $TESTTMP/git-testrevs/.git/
+  $ echo a >> a ; git add a > /dev/null; git commit -m 'first' > /dev/null
+  $ echo a >> a ; git add a > /dev/null; git commit -m 'master commit' > /dev/null
+  $ git checkout -b goodbranch 'HEAD^'
+  Switched to a new branch 'goodbranch'
+  $ echo a >> b ; git add b > /dev/null; git commit -m 'good branch commit' > /dev/null
+  $ git checkout -b badbranch 'HEAD^'
+  Switched to a new branch 'badbranch'
+  $ echo a >> c ; git add c > /dev/null; git commit -m 'bad branch commit' > /dev/null
+  $ cd ..
+  $ hg convert git-testrevs hg-testrevs --rev master --rev goodbranch
+  initializing destination hg-testrevs repository
+  scanning source...
+  sorting...
+  converting...
+  2 first
+  1 good branch commit
+  0 master commit
+  updating bookmarks
+  $ cd hg-testrevs
+  $ hg log -G -T '{rev} {bookmarks}'
+  o  2 master
+  |
+  | o  1 goodbranch
+  |/
+  o  0
+  
+  $ cd ..
+
 test sub modules
 
   $ mkdir git-repo5
@@ -457,6 +491,53 @@
   $ git init-db >/dev/null 2>/dev/null
   $ git submodule add ${BASE} >/dev/null 2>/dev/null
   $ commit -a -m 'addsubmodule' >/dev/null 2>/dev/null
+
+test non-tab whitespace .gitmodules
+
+  $ cat >> .gitmodules <<EOF
+  > [submodule "git-repo5"]
+  >   path = git-repo5
+  >   url = git-repo5
+  > EOF
+  $ git commit -q -a -m "weird white space submodule"
+  $ cd ..
+  $ hg convert git-repo6 hg-repo6
+  initializing destination hg-repo6 repository
+  scanning source...
+  sorting...
+  converting...
+  1 addsubmodule
+  0 weird white space submodule
+  updating bookmarks
+
+  $ rm -rf hg-repo6
+  $ cd git-repo6
+  $ git reset --hard 'HEAD^' > /dev/null
+
+test missing .gitmodules
+
+  $ git submodule add ../git-repo4 >/dev/null 2>/dev/null
+  $ git checkout HEAD .gitmodules
+  $ git rm .gitmodules
+  rm '.gitmodules'
+  $ git commit -q -m "remove .gitmodules" .gitmodules
+  $ git commit -q -m "missing .gitmodules"
+  $ cd ..
+  $ hg convert git-repo6 hg-repo6 --traceback
+  fatal: Path '.gitmodules' does not exist in '*' (glob)
+  initializing destination hg-repo6 repository
+  scanning source...
+  sorting...
+  converting...
+  2 addsubmodule
+  1 remove .gitmodules
+  0 missing .gitmodules
+  warning: cannot read submodules config file in * (glob)
+  updating bookmarks
+  $ rm -rf hg-repo6
+  $ cd git-repo6
+  $ rm -rf git-repo4
+  $ git reset --hard 'HEAD^^' > /dev/null
   $ cd ..
 
 test invalid splicemap1
@@ -561,6 +642,30 @@
   $ hg -R git-repo6-hg tip -T "{file_dels}\n"
   .hgsub .hgsubstate
 
+convert using a different remote prefix
+  $ git init git-repo7
+  Initialized empty Git repository in $TESTTMP/git-repo7/.git/
+  $ cd git-repo7
+  $ touch a && git add a && git commit -am "commit a"
+  [master (root-commit) 8ae5f69] commit a
+   Author: nottest <test@example.org>
+   1 file changed, 0 insertions(+), 0 deletions(-)
+   create mode 100644 a
+  $ cd ..
+  $ git clone git-repo7 git-repo7-client
+  Cloning into 'git-repo7-client'...
+  done.
+  $ hg convert --config convert.git.remoteprefix=origin git-repo7-client hg-repo7
+  initializing destination hg-repo7 repository
+  scanning source...
+  sorting...
+  converting...
+  0 commit a
+  updating bookmarks
+  $ hg -R hg-repo7 bookmarks
+     master                    0:03bf38caa4c6
+     origin/master             0:03bf38caa4c6
+
 damaged git repository tests:
 In case the hard-coded hashes change, the following commands can be used to
 list the hashes and their corresponding types in the repository:
--- a/tests/test-convert-hg-sink.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-convert-hg-sink.t	Sat Jul 18 17:32:38 2015 -0500
@@ -41,6 +41,7 @@
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     add foo and bar
   
+  $ hg phase --public -r tip
   $ cd ..
   $ hg convert orig new 2>&1 | grep -v 'subversion python bindings could not be loaded'
   initializing destination new repository
@@ -52,6 +53,16 @@
   1 add foo/file
   0 Added tag some-tag for changeset ad681a868e44
   $ cd new
+  $ hg log -G --template '{rev} {node|short} ({phase}) "{desc}"\n'
+  o  3 593cbf6fb2b4 (public) "Added tag some-tag for changeset ad681a868e44"
+  |
+  o  2 ad681a868e44 (public) "add foo/file"
+  |
+  o  1 cbba8ecc03b7 (public) "remove foo"
+  |
+  o  0 327daa9251fa (public) "add foo and bar"
+  
+
   $ hg out ../orig
   comparing with ../orig
   searching for changes
@@ -132,7 +143,7 @@
 
   $ glog()
   > {
-  >     hg log -G --template '{rev} {node|short} "{desc}" files: {files}\n' $*
+  >     hg log -G --template '{rev} {node|short} ({phase}) "{desc}" files: {files}\n' $*
   > }
 
 Create a tricky source repo
@@ -171,20 +182,21 @@
   dir/c
   dir/d
   e
+  $ hg phase --public -r tip
   $ glog
-  @  6 0613c8e59a3d "6: change a" files: a
+  @  6 0613c8e59a3d (public) "6: change a" files: a
   |
-  o    5 717e9b37cdb7 "5: merge 2 and 3, copy b to dir/d" files: dir/d e
+  o    5 717e9b37cdb7 (public) "5: merge 2 and 3, copy b to dir/d" files: dir/d e
   |\
-  | o  4 86a55cb968d5 "4: change a" files: a
+  | o  4 86a55cb968d5 (public) "4: change a" files: a
   | |
-  o |  3 0e6e235919dd "3: copy a to e, change b" files: b e
+  o |  3 0e6e235919dd (public) "3: copy a to e, change b" files: b e
   | |
-  o |  2 0394b0d5e4f7 "2: add dir/c" files: dir/c
+  o |  2 0394b0d5e4f7 (public) "2: add dir/c" files: dir/c
   |/
-  o  1 333546584845 "1: add a and dir/b" files: a dir/b
+  o  1 333546584845 (public) "1: add a and dir/b" files: a dir/b
   |
-  o  0 d1a24e2ebd23 "0: add 0" files: 0
+  o  0 d1a24e2ebd23 (public) "0: add 0" files: 0
   
   $ cd ..
 
@@ -209,15 +221,15 @@
 Verify that conversion skipped rev 2:
 
   $ glog -R dest
-  o  4 78814e84a217 "6: change a" files: a
+  o  4 78814e84a217 (draft) "6: change a" files: a
   |
-  o    3 f7cff662c5e5 "5: merge 2 and 3, copy b to dir/d" files: e
+  o    3 f7cff662c5e5 (draft) "5: merge 2 and 3, copy b to dir/d" files: e
   |\
-  | o  2 ab40a95b0072 "4: change a" files: a
+  | o  2 ab40a95b0072 (draft) "4: change a" files: a
   | |
-  o |  1 bd51f17597bf "3: copy a to e, change b" files: b e
+  o |  1 bd51f17597bf (draft) "3: copy a to e, change b" files: b e
   |/
-  o  0 a4a1dae0fe35 "1: add a and dir/b" files: 0 a
+  o  0 a4a1dae0fe35 (draft) "1: add a and dir/b" files: 0 a
   
 
 Verify mapping correct in both directions:
@@ -347,17 +359,17 @@
   e
 
   $ glog -r 6:
-  @  11 0c8927d1f7f4 "11: source change" files: a
+  @  11 0c8927d1f7f4 (draft) "11: source change" files: a
   |
-  o    10 9ccb7ee8d261 "10: source merge" files: a
+  o    10 9ccb7ee8d261 (draft) "10: source merge" files: a
   |\
-  | o  9 f131b1518dba "9: source second branch" files: a
+  | o  9 f131b1518dba (draft) "9: source second branch" files: a
   | |
-  o |  8 669cf0e74b50 "8: source first branch" files: a
+  o |  8 669cf0e74b50 (draft) "8: source first branch" files: a
   | |
-  | o  7 e6d364a69ff1 "change in dest" files: dest
+  | o  7 e6d364a69ff1 (draft) "change in dest" files: dest
   |/
-  o  6 0613c8e59a3d "6: change a" files: a
+  o  6 0613c8e59a3d (public) "6: change a" files: a
   |
   $ cd ..
 
@@ -371,25 +383,25 @@
   0 11: source change
 
   $ glog -R dest
-  o  9 8432d597b263 "11: source change" files: a
+  o  9 8432d597b263 (draft) "11: source change" files: a
   |
-  o    8 632ffacdcd6f "10: source merge" files: a
+  o    8 632ffacdcd6f (draft) "10: source merge" files: a
   |\
-  | o  7 049cfee90ee6 "9: source second branch" files: a
+  | o  7 049cfee90ee6 (draft) "9: source second branch" files: a
   | |
-  o |  6 9b6845e036e5 "8: source first branch" files: a
+  o |  6 9b6845e036e5 (draft) "8: source first branch" files: a
   | |
-  | @  5 a2e0e3cc6d1d "change in dest" files: dest
+  | @  5 a2e0e3cc6d1d (draft) "change in dest" files: dest
   |/
-  o  4 78814e84a217 "6: change a" files: a
+  o  4 78814e84a217 (draft) "6: change a" files: a
   |
-  o    3 f7cff662c5e5 "5: merge 2 and 3, copy b to dir/d" files: e
+  o    3 f7cff662c5e5 (draft) "5: merge 2 and 3, copy b to dir/d" files: e
   |\
-  | o  2 ab40a95b0072 "4: change a" files: a
+  | o  2 ab40a95b0072 (draft) "4: change a" files: a
   | |
-  o |  1 bd51f17597bf "3: copy a to e, change b" files: b e
+  o |  1 bd51f17597bf (draft) "3: copy a to e, change b" files: b e
   |/
-  o  0 a4a1dae0fe35 "1: add a and dir/b" files: 0 a
+  o  0 a4a1dae0fe35 (draft) "1: add a and dir/b" files: 0 a
   
   $ cd ..
 
@@ -520,7 +532,7 @@
 Conversion after rollback
 
   $ hg -R a rollback -f
-  repository tip rolled back to revision 2 (undo commit)
+  repository tip rolled back to revision 2 (undo convert)
 
   $ hg convert --filemap filemap-b 0 a --config convert.hg.revs=1::
   scanning source...
--- a/tests/test-convert-hg-source.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-convert-hg-source.t	Sat Jul 18 17:32:38 2015 -0500
@@ -83,7 +83,43 @@
      premerge1                 3:973ef48a98a4
      premerge2                 8:3537b15eaaca
 #endif
-  $ cd ..
+
+Test that redoing a convert results in an identical graph
+  $ cd ../
+  $ rm new/.hg/shamap
+  $ hg convert --datesort orig new 2>&1 | grep -v 'subversion python bindings could not be loaded'
+  scanning source...
+  sorting...
+  converting...
+  8 add foo bar
+  7 change foo
+  6 make bar and baz copies of foo
+  5 merge local copy
+  4 merge remote copy
+  3 Added tag that for changeset 88586c4e9f02
+  2 Removed tag that
+  1 Added tag this for changeset c56a7f387039
+  0 mark baz executable
+  updating bookmarks
+  $ hg -R new log -G -T '{rev} {desc}'
+  o  8 mark baz executable
+  |
+  o  7 Added tag this for changeset c56a7f387039
+  |
+  o  6 Removed tag that
+  |
+  o  5 Added tag that for changeset 88586c4e9f02
+  |
+  o    4 merge remote copy
+  |\
+  +---o  3 merge local copy
+  | |/
+  | o  2 make bar and baz copies of foo
+  | |
+  o |  1 change foo
+  |/
+  o  0 add foo bar
+  
 
 check shamap LF and CRLF handling
 
--- a/tests/test-convert-mtn.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-convert-mtn.t	Sat Jul 18 17:32:38 2015 -0500
@@ -220,7 +220,7 @@
   >>> fp = file('large-file', 'wb')
   >>> for x in xrange(10000): fp.write('%d\n' % x)
   >>> fp.close()
-  $ $TESTDIR/md5sum.py large-file
+  $ md5sum.py large-file
   5d6de8a95c3b6bf9e0ffb808ba5299c1  large-file
   $ mtn add large-file
   mtn: adding 'large-file' to workspace manifest
@@ -386,7 +386,7 @@
 
 test large file support (> 32kB)
 
-  $ $TESTDIR/md5sum.py large-file
+  $ md5sum.py large-file
   5d6de8a95c3b6bf9e0ffb808ba5299c1  large-file
 
 check branch closing
--- a/tests/test-convert-svn-branches.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-convert-svn-branches.t	Sat Jul 18 17:32:38 2015 -0500
@@ -99,7 +99,7 @@
 Convert 'trunk' to branch other than 'default'
 
   $ cat > branchmap <<EOF
-  > None hgtrunk
+  > default hgtrunk
   > 
   > 
   > EOF
@@ -121,9 +121,8 @@
   0 last change to a
 
   $ cd C-hg
-  $ hg branches
-  hgtrunk                       10:745f063703b4
-  old                            9:aa50d7b8d922
-  old2                           8:c85a22267b6e (inactive)
+  $ hg branches --template '{branch}\n'
+  hgtrunk
+  old
+  old2
   $ cd ..
-
--- a/tests/test-convert-svn-encoding.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-convert-svn-encoding.t	Sat Jul 18 17:32:38 2015 -0500
@@ -10,7 +10,7 @@
 
 Convert while testing all possible outputs
 
-  $ hg --debug convert svn-repo A-hg
+  $ hg --debug convert svn-repo A-hg --config progress.debug=1
   initializing destination A-hg repository
   reparent to file://*/svn-repo (glob)
   run hg sink pre-conversion action
--- a/tests/test-convert-svn-sink.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-convert-svn-sink.t	Sat Jul 18 17:32:38 2015 -0500
@@ -32,7 +32,7 @@
 
 Modify
 
-  $ "$TESTDIR/svn-safe-append.py" a a/a
+  $ svn-safe-append.py a a/a
   $ hg --cwd a ci -d '1 0' -m 'modify a file'
   $ hg --cwd a tip -q
   1:e0e2b8a9156b
@@ -354,12 +354,12 @@
   $ hg --cwd b ci -d '0 0' -Ambase
   adding b
 
-  $ "$TESTDIR/svn-safe-append.py" left-1 b/b
+  $ svn-safe-append.py left-1 b/b
   $ echo left-1 > b/left-1
   $ hg --cwd b ci -d '1 0' -Amleft-1
   adding left-1
 
-  $ "$TESTDIR/svn-safe-append.py" left-2 b/b
+  $ svn-safe-append.py left-2 b/b
   $ echo left-2 > b/left-2
   $ hg --cwd b ci -d '2 0' -Amleft-2
   adding left-2
@@ -367,13 +367,13 @@
   $ hg --cwd b up 0
   1 files updated, 0 files merged, 2 files removed, 0 files unresolved
 
-  $ "$TESTDIR/svn-safe-append.py" right-1 b/b
+  $ svn-safe-append.py right-1 b/b
   $ echo right-1 > b/right-1
   $ hg --cwd b ci -d '3 0' -Amright-1
   adding right-1
   created new head
 
-  $ "$TESTDIR/svn-safe-append.py" right-2 b/b
+  $ svn-safe-append.py right-2 b/b
   $ echo right-2 > b/right-2
   $ hg --cwd b ci -d '4 0' -Amright-2
   adding right-2
--- a/tests/test-convert-svn-source.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-convert-svn-source.t	Sat Jul 18 17:32:38 2015 -0500
@@ -46,7 +46,7 @@
   Transmitting file data .
   Committed revision 2.
 
-  $ "$TESTDIR/svn-safe-append.py" world 'letter .txt'
+  $ svn-safe-append.py world 'letter .txt'
   $ svn ci -m world
   Sending        letter .txt
   Transmitting file data .
@@ -56,7 +56,7 @@
   
   Committed revision 4.
 
-  $ "$TESTDIR/svn-safe-append.py" 'nice day today!' 'letter .txt'
+  $ svn-safe-append.py 'nice day today!' 'letter .txt'
   $ svn ci -m "nice day"
   Sending        letter .txt
   Transmitting file data .
@@ -86,7 +86,7 @@
 Update svn repository again
 
   $ cd B
-  $ "$TESTDIR/svn-safe-append.py" "see second letter" 'letter .txt'
+  $ svn-safe-append.py "see second letter" 'letter .txt'
   $ echo "nice to meet you" > letter2.txt
   $ svn add letter2.txt
   A         letter2.txt
@@ -100,7 +100,7 @@
   
   Committed revision 7.
 
-  $ "$TESTDIR/svn-safe-append.py" "blah-blah-blah" letter2.txt
+  $ svn-safe-append.py "blah-blah-blah" letter2.txt
   $ svn ci -m "work in progress"
   Sending        letter2.txt
   Transmitting file data .
--- a/tests/test-convert-tagsbranch-topology.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-convert-tagsbranch-topology.t	Sat Jul 18 17:32:38 2015 -0500
@@ -45,6 +45,19 @@
   $ action tag -m "tag1" tag1
   $ cd ..
 
+Convert without tags
+
+  $ hg convert git-repo hg-repo --config convert.skiptags=True
+  initializing destination hg-repo repository
+  scanning source...
+  sorting...
+  converting...
+  0 rev1
+  updating bookmarks
+  $ hg -R hg-repo tags
+  tip                                0:d98c8ad3a4cf
+  $ rm -rf hg-repo
+
 Do a first conversion
 
   $ convertrepo
--- a/tests/test-convert.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-convert.t	Sat Jul 18 17:32:38 2015 -0500
@@ -259,6 +259,10 @@
                     for large projects, and is only effective when
                     "convert.git.similarity" is greater than 0. The default is
                     False.
+      convert.git.remoteprefix
+                    remote refs are converted as bookmarks with
+                    "convert.git.remoteprefix" as a prefix followed by a /. The
+                    default is 'remote'.
   
       Perforce Source
       ###############
@@ -279,6 +283,12 @@
       Mercurial Destination
       #####################
   
+      The Mercurial destination will recognize Mercurial subrepositories in the
+      destination directory, and update the .hgsubstate file automatically if
+      the destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
+      Converting a repository with subrepositories requires converting a single
+      repository at a time, from the bottom up.
+  
       The following options are supported:
   
       convert.hg.clonebranches
@@ -288,12 +298,25 @@
                     branch name for tag revisions, defaults to "default".
       convert.hg.usebranchnames
                     preserve branch names. The default is True.
+      convert.hg.sourcename
+                    records the given string as a 'convert_source' extra value
+                    on each commit made in the target repository. The default is
+                    None.
   
-  options:
+      All Destinations
+      ################
+  
+      All destination types accept the following options:
+  
+      convert.skiptags
+                    does not convert tags from the source repo to the target
+                    repo. The default is False.
+  
+  options ([+] can be repeated):
   
    -s --source-type TYPE source repository type
    -d --dest-type TYPE   destination repository type
-   -r --rev REV          import up to source revision REV
+   -r --rev REV [+]      import up to source revision REV
    -A --authormap FILE   remap usernames using this file
       --filemap FILE     remap file names using contents of file
       --full             apply filemap changes by converting all files again
@@ -488,3 +511,17 @@
   date:        Thu Jan 01 00:00:04 1970 +0000
   summary:     e
   
+
+test specifying a sourcename
+  $ echo g > a/g
+  $ hg -R a ci -d'0 0' -Amg
+  adding g
+  $ hg --config convert.hg.sourcename=mysource --config convert.hg.saverev=True convert a c
+  scanning source...
+  sorting...
+  converting...
+  0 g
+  $ hg -R c log -r tip --template '{extras % "{extra}\n"}'
+  branch=default
+  convert_revision=a3bc6100aa8ec03e00aaf271f1f50046fb432072
+  convert_source=mysource
--- a/tests/test-copy-move-merge.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-copy-move-merge.t	Sat Jul 18 17:32:38 2015 -0500
@@ -35,13 +35,11 @@
    preserving a for resolve of c
   removing a
    b: remote moved from a -> m
-  updating: b 1/2 files (50.00%)
   picked tool 'internal:merge' for b (binary False symlink False)
   merging a and b to b
   my b@add3f11052fa+ other b@17c05bb7fcb6 ancestor a@b8bf91eeebbc
    premerge successful
    c: remote moved from a -> m
-  updating: c 2/2 files (100.00%)
   picked tool 'internal:merge' for c (binary False symlink False)
   merging a and c to c
   my c@add3f11052fa+ other c@17c05bb7fcb6 ancestor a@b8bf91eeebbc
--- a/tests/test-copy.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-copy.t	Sat Jul 18 17:32:38 2015 -0500
@@ -19,6 +19,7 @@
   branch: default
   commit: 1 copied
   update: (current)
+  phases: 1 draft
   $ hg --debug commit -m "2"
   committing files:
   b
@@ -85,13 +86,13 @@
   copy: a
   copyrev: b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3
 
-  $ "$TESTDIR/md5sum.py" .hg/store/data/b.i
+  $ md5sum.py .hg/store/data/b.i
   4999f120a3b88713bbefddd195cf5133  .hg/store/data/b.i
   $ hg cat b > bsum
-  $ "$TESTDIR/md5sum.py" bsum
+  $ md5sum.py bsum
   60b725f10c9c85c70d97880dfe8191b3  bsum
   $ hg cat a > asum
-  $ "$TESTDIR/md5sum.py" asum
+  $ md5sum.py asum
   60b725f10c9c85c70d97880dfe8191b3  asum
   $ hg verify
   checking changesets
--- a/tests/test-debugbuilddag.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-debugbuilddag.t	Sat Jul 18 17:32:38 2015 -0500
@@ -10,37 +10,20 @@
   \r (no-eol) (esc)
   building [                                          ]  0/12\r (no-eol) (esc)
   building [                                          ]  0/12\r (no-eol) (esc)
-  building [                                          ]  0/12\r (no-eol) (esc)
-  building [                                          ]  0/12\r (no-eol) (esc)
-  building [==>                                       ]  1/12\r (no-eol) (esc)
-  building [==>                                       ]  1/12\r (no-eol) (esc)
   building [==>                                       ]  1/12\r (no-eol) (esc)
   building [==>                                       ]  1/12\r (no-eol) (esc)
   building [======>                                   ]  2/12\r (no-eol) (esc)
-  building [======>                                   ]  2/12\r (no-eol) (esc)
-  building [=========>                                ]  3/12\r (no-eol) (esc)
   building [=========>                                ]  3/12\r (no-eol) (esc)
   building [=============>                            ]  4/12\r (no-eol) (esc)
   building [=============>                            ]  4/12\r (no-eol) (esc)
   building [=============>                            ]  4/12\r (no-eol) (esc)
-  building [=============>                            ]  4/12\r (no-eol) (esc)
-  building [=============>                            ]  4/12\r (no-eol) (esc)
-  building [=============>                            ]  4/12\r (no-eol) (esc)
-  building [================>                         ]  5/12\r (no-eol) (esc)
   building [================>                         ]  5/12\r (no-eol) (esc)
   building [====================>                     ]  6/12\r (no-eol) (esc)
-  building [====================>                     ]  6/12\r (no-eol) (esc)
-  building [=======================>                  ]  7/12\r (no-eol) (esc)
   building [=======================>                  ]  7/12\r (no-eol) (esc)
   building [===========================>              ]  8/12\r (no-eol) (esc)
   building [===========================>              ]  8/12\r (no-eol) (esc)
-  building [===========================>              ]  8/12\r (no-eol) (esc)
-  building [===========================>              ]  8/12\r (no-eol) (esc)
-  building [==============================>           ]  9/12\r (no-eol) (esc)
   building [==============================>           ]  9/12\r (no-eol) (esc)
   building [==================================>       ] 10/12\r (no-eol) (esc)
-  building [==================================>       ] 10/12\r (no-eol) (esc)
-  building [=====================================>    ] 11/12\r (no-eol) (esc)
   building [=====================================>    ] 11/12\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
 
--- a/tests/test-devel-warnings.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-devel-warnings.t	Sat Jul 18 17:32:38 2015 -0500
@@ -3,7 +3,7 @@
   > """A small extension that acquire locks in the wrong order
   > """
   > 
-  > from mercurial import cmdutil
+  > from mercurial import cmdutil, repair, revset
   > 
   > cmdtable = {}
   > command = cmdutil.command(cmdtable)
@@ -38,13 +38,27 @@
   >     wl = repo.wlock(wait=False)
   >     wl.release()
   >     lo.release()
+  > 
+  > @command('stripintr', [], '')
+  > def stripintr(ui, repo):
+  >     lo = repo.lock()
+  >     tr = repo.transaction('foobar')
+  >     try:
+  >         repair.strip(repo.ui, repo, [repo['.'].node()])
+  >     finally:
+  >         lo.release()
+  > 
+  > def oldstylerevset(repo, subset, x):
+  >     return list(subset)
+  > 
+  > revset.symbols['oldstyle'] = oldstylerevset
   > EOF
 
   $ cat << EOF >> $HGRCPATH
   > [extensions]
   > buggylocking=$TESTTMP/buggylocking.py
   > [devel]
-  > all=1
+  > all-warnings=1
   > EOF
 
   $ hg init lock-checker
@@ -87,4 +101,18 @@
    $TESTTMP/buggylocking.py:* in buggylocking (glob)
   $ hg properlocking
   $ hg nowaitlocking
+
+  $ echo a > a
+  $ hg add a
+  $ hg commit -m a
+  $ hg stripintr
+  saved backup bundle to $TESTTMP/lock-checker/.hg/strip-backup/cb9a9f314b8b-cc5ccb0b-backup.hg (glob)
+  abort: programming error: cannot strip from inside a transaction
+  (contact your extension maintainer)
+  [255]
+
+  $ hg log -r "oldstyle()" -T '{rev}\n'
+  devel-warn: revset "oldstyle" use list instead of smartset, (upgrade your code) at: */mercurial/revset.py:* (mfunc) (glob)
+  0
+
   $ cd ..
--- a/tests/test-doctest.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-doctest.py	Sat Jul 18 17:32:38 2015 -0500
@@ -21,15 +21,19 @@
 testmod('mercurial.minirst')
 testmod('mercurial.patch')
 testmod('mercurial.pathutil')
+testmod('mercurial.parser')
 testmod('mercurial.revset')
 testmod('mercurial.store')
 testmod('mercurial.subrepo')
 testmod('mercurial.templatefilters')
+testmod('mercurial.templater')
 testmod('mercurial.ui')
 testmod('mercurial.url')
 testmod('mercurial.util')
 testmod('mercurial.util', testtarget='platform')
+testmod('hgext.convert.convcmd')
 testmod('hgext.convert.cvsps')
 testmod('hgext.convert.filemap')
+testmod('hgext.convert.p4')
 testmod('hgext.convert.subversion')
 testmod('hgext.mq')
--- a/tests/test-double-merge.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-double-merge.t	Sat Jul 18 17:32:38 2015 -0500
@@ -38,13 +38,11 @@
    preserving foo for resolve of bar
    preserving foo for resolve of foo
    bar: remote copied from foo -> m
-  updating: bar 1/2 files (50.00%)
   picked tool 'internal:merge' for bar (binary False symlink False)
   merging foo and bar to bar
   my bar@6a0df1dad128+ other bar@484bf6903104 ancestor foo@e6dc8efe11cc
    premerge successful
    foo: versions differ -> m
-  updating: foo 2/2 files (100.00%)
   picked tool 'internal:merge' for foo (binary False symlink False)
   merging foo
   my foo@6a0df1dad128+ other foo@484bf6903104 ancestor foo@e6dc8efe11cc
--- a/tests/test-encoding-align.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-encoding-align.t	Sat Jul 18 17:32:38 2015 -0500
@@ -119,12 +119,10 @@
   $ hg book -f $S
   $ hg branch $M
   marked working directory as branch MIDDLE_
-  (branches are permanent and global, did you want a bookmark?)
   $ hg tag $M
   $ hg book -f $M
   $ hg branch $L
   marked working directory as branch \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d (esc)
-  (branches are permanent and global, did you want a bookmark?)
   $ hg tag $L
   $ hg book -f $L
 
--- a/tests/test-extdiff.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-extdiff.t	Sat Jul 18 17:32:38 2015 -0500
@@ -48,6 +48,7 @@
    -c --change REV          change made by revision
    -I --include PATTERN [+] include names matching the given patterns
    -X --exclude PATTERN [+] exclude names matching the given patterns
+   -S --subrepos            recurse into subrepositories
   
   (some details hidden, use --verbose to show complete help)
 
--- a/tests/test-extension.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-extension.t	Sat Jul 18 17:32:38 2015 -0500
@@ -394,6 +394,7 @@
    -c --change REV          change made by revision
    -I --include PATTERN [+] include names matching the given patterns
    -X --exclude PATTERN [+] exclude names matching the given patterns
+   -S --subrepos            recurse into subrepositories
   
   (some details hidden, use --verbose to show complete help)
 
--- a/tests/test-fetch.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-fetch.t	Sat Jul 18 17:32:38 2015 -0500
@@ -168,7 +168,6 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R nbase branch b
   marked working directory as branch b
-  (branches are permanent and global, did you want a bookmark?)
   $ echo b > nbase/b
   $ hg -R nbase ci -Am b
   adding b
@@ -363,7 +362,6 @@
   adding b
   $ hg --cwd ib1 branch -f default
   marked working directory as branch default
-  (branches are permanent and global, did you want a bookmark?)
   $ echo c > ib1/c
   $ hg --cwd ib1 ci -Am newdefault
   adding c
--- a/tests/test-fileset.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-fileset.t	Sat Jul 18 17:32:38 2015 -0500
@@ -16,15 +16,21 @@
 
 Test operators and basic patterns
 
-  $ fileset a1
+  $ fileset -v a1
+  ('symbol', 'a1')
   a1
-  $ fileset 'a*'
+  $ fileset -v 'a*'
+  ('symbol', 'a*')
   a1
   a2
-  $ fileset '"re:a\d"'
+  $ fileset -v '"re:a\d"'
+  ('string', 're:a\\d')
   a1
   a2
-  $ fileset 'a1 or a2'
+  $ fileset -v 'a1 or a2'
+  (or
+    ('symbol', 'a1')
+    ('symbol', 'a2'))
   a1
   a2
   $ fileset 'a1 | a2'
@@ -174,16 +180,54 @@
   $ hg -R sub add sub/suba
   $ hg -R sub ci -m sub
   $ echo 'sub = sub' > .hgsub
+  $ hg init sub2
+  $ echo b > sub2/b
+  $ hg -R sub2 ci -Am sub2
+  adding b
+  $ echo 'sub2 = sub2' >> .hgsub
   $ fileset 'subrepo()'
   $ hg add .hgsub
   $ fileset 'subrepo()'
   sub
+  sub2
   $ fileset 'subrepo("sub")'
   sub
   $ fileset 'subrepo("glob:*")'
   sub
+  sub2
   $ hg ci -m subrepo
 
+Test that .hgsubstate is updated as appropriate during a conversion.  The
+saverev property is enough to alter the hashes of the subrepo.
+
+  $ hg init ../converted
+  $ hg --config extensions.convert= convert --config convert.hg.saverev=True  \
+  >      sub ../converted/sub
+  initializing destination ../converted/sub repository
+  scanning source...
+  sorting...
+  converting...
+  0 sub
+  $ hg clone -U sub2 ../converted/sub2
+  $ hg --config extensions.convert= convert --config convert.hg.saverev=True  \
+  >      . ../converted
+  scanning source...
+  sorting...
+  converting...
+  4 addfiles
+  3 manychanges
+  2 diverging
+  1 merge
+  0 subrepo
+  no ".hgsubstate" updates will be made for "sub2"
+  $ hg up -q -R ../converted -r tip
+  $ hg --cwd ../converted cat sub/suba sub2/b -r tip
+  a
+  b
+  $ oldnode=`hg log -r tip -T "{node}\n"`
+  $ newnode=`hg log -R ../converted -r tip -T "{node}\n"`
+  $ [ "$oldnode" != "$newnode" ] || echo "nothing changed"
+
 Test with a revision
 
   $ hg log -G --template '{rev} {desc}\n'
@@ -235,6 +279,7 @@
 
   $ fileset -r4 'subrepo("re:su.*")'
   sub
+  sub2
   $ fileset -r4 'subrepo("sub")'
   sub
   $ fileset -r4 'b2 or c1'
--- a/tests/test-fncache.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-fncache.t	Sat Jul 18 17:32:38 2015 -0500
@@ -48,13 +48,28 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-   data/a.i@0: missing revlog!
-   data/a.i.hg/c.i@2: missing revlog!
-   data/a.i/b.i@1: missing revlog!
+   warning: revlog 'data/a.i' not in fncache!
+   warning: revlog 'data/a.i.hg/c.i' not in fncache!
+   warning: revlog 'data/a.i/b.i' not in fncache!
   3 files, 3 changesets, 3 total revisions
-  3 integrity errors encountered!
-  (first damaged changeset appears to be 0)
-  [1]
+  3 warnings encountered!
+  hint: run "hg debugrebuildfncache" to recover from corrupt fncache
+
+Follow the hint to make sure it works
+
+  $ hg debugrebuildfncache
+  adding data/a.i
+  adding data/a.i.hg/c.i
+  adding data/a.i/b.i
+  3 items added, 0 removed from fncache
+
+  $ hg verify
+  checking changesets
+  checking manifests
+  crosschecking files in changesets and manifests
+  checking files
+  3 files, 3 changesets, 3 total revisions
+
   $ cd ..
 
 Non store repo:
@@ -285,3 +300,98 @@
   1 files, 1 changesets, 1 total revisions
   $ cat .hg/store/fncache
   data/y.i
+
+  $ cd ..
+
+debugrebuildfncache does nothing unless repo has fncache requirement
+
+  $ hg --config format.usefncache=false init nofncache
+  $ cd nofncache
+  $ hg debugrebuildfncache
+  (not rebuilding fncache because repository does not support fncache
+
+  $ cd ..
+
+debugrebuildfncache works on empty repository
+
+  $ hg init empty
+  $ cd empty
+  $ hg debugrebuildfncache
+  fncache already up to date
+  $ cd ..
+
+debugrebuildfncache on an up to date repository no-ops
+
+  $ hg init repo
+  $ cd repo
+  $ echo initial > foo
+  $ echo initial > .bar
+  $ hg commit -A -m initial
+  adding .bar
+  adding foo
+
+  $ cat .hg/store/fncache | sort
+  data/.bar.i
+  data/foo.i
+
+  $ hg debugrebuildfncache
+  fncache already up to date
+
+debugrebuildfncache restores deleted fncache file
+
+  $ rm -f .hg/store/fncache
+  $ hg debugrebuildfncache
+  adding data/.bar.i
+  adding data/foo.i
+  2 items added, 0 removed from fncache
+
+  $ cat .hg/store/fncache | sort
+  data/.bar.i
+  data/foo.i
+
+Rebuild after rebuild should no-op
+
+  $ hg debugrebuildfncache
+  fncache already up to date
+
+A single missing file should get restored, an extra file should be removed
+
+  $ cat > .hg/store/fncache << EOF
+  > data/foo.i
+  > data/bad-entry.i
+  > EOF
+
+  $ hg debugrebuildfncache
+  removing data/bad-entry.i
+  adding data/.bar.i
+  1 items added, 1 removed from fncache
+
+  $ cat .hg/store/fncache | sort
+  data/.bar.i
+  data/foo.i
+
+  $ cd ..
+
+Try a simple variation without dotencode to ensure fncache is ignorant of encoding
+
+  $ hg --config format.dotencode=false init nodotencode
+  $ cd nodotencode
+  $ echo initial > foo
+  $ echo initial > .bar
+  $ hg commit -A -m initial
+  adding .bar
+  adding foo
+
+  $ cat .hg/store/fncache | sort
+  data/.bar.i
+  data/foo.i
+
+  $ rm .hg/store/fncache
+  $ hg debugrebuildfncache
+  adding data/.bar.i
+  adding data/foo.i
+  2 items added, 0 removed from fncache
+
+  $ cat .hg/store/fncache | sort
+  data/.bar.i
+  data/foo.i
--- a/tests/test-globalopts.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-globalopts.t	Sat Jul 18 17:32:38 2015 -0500
@@ -88,6 +88,7 @@
   [255]
   $ hg -R b ann a/a
   abort: a/a not under root '$TESTTMP/b' (glob)
+  (consider using '--cwd b')
   [255]
   $ hg log
   abort: no repository found in '$TESTTMP' (.hg not found)!
--- a/tests/test-glog.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-glog.t	Sat Jul 18 17:32:38 2015 -0500
@@ -89,7 +89,7 @@
   >         if opts.get('print_revset'):
   >             expr = cmdutil.getgraphlogrevs(repo, pats, opts)[1]
   >             if expr:
-  >                 tree = revset.parse(expr)[0]
+  >                 tree = revset.parse(expr)
   >             else:
   >                 tree = []
   >             ui.write('%r\n' % (opts.get('rev', []),))
@@ -1469,13 +1469,12 @@
   (group
     (group
       (or
-        (or
-          (func
-            ('symbol', 'branch')
-            ('string', 'default'))
-          (func
-            ('symbol', 'branch')
-            ('string', 'branch')))
+        (func
+          ('symbol', 'branch')
+          ('string', 'default'))
+        (func
+          ('symbol', 'branch')
+          ('string', 'branch'))
         (func
           ('symbol', 'branch')
           ('string', 'branch')))))
@@ -2393,4 +2392,12 @@
      summary:     add a
   
 
+working-directory revision
+
+  $ hg log -G -qr '. + wdir()'
+  o  2147483647:ffffffffffff
+  |
+  @  3:5918b8d165d1
+  |
+
   $ cd ..
--- a/tests/test-gpg.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-gpg.t	Sat Jul 18 17:32:38 2015 -0500
@@ -38,7 +38,7 @@
 
 verify that this test has not modified the trustdb.gpg file back in
 the main hg working dir
-  $ "$TESTDIR/md5sum.py" "$TESTDIR/gpg/trustdb.gpg"
+  $ md5sum.py "$TESTDIR/gpg/trustdb.gpg"
   f6b9c78c65fa9536e7512bb2ceb338ae  */gpg/trustdb.gpg (glob)
 
 don't leak any state to next test run
--- a/tests/test-graft.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-graft.t	Sat Jul 18 17:32:38 2015 -0500
@@ -154,7 +154,6 @@
    ancestor: 68795b066622, local: ef0ef43d49e7+, remote: 5d205f8b35b6
    preserving b for resolve of b
    b: local copied/moved from a -> m
-  updating: b 1/1 files (100.00%)
   picked tool 'internal:merge' for b (binary False symlink False)
   merging b and a to b
   my b@ef0ef43d49e7+ other a@5d205f8b35b6 ancestor a@68795b066622
@@ -170,7 +169,6 @@
    ancestor: 4c60f11aa304, local: 6b9e5368ca4e+, remote: 97f8bfe72746
    e: remote is newer -> g
   getting e
-  updating: e 1/1 files (100.00%)
    b: remote unchanged -> k
   committing files:
   e
@@ -184,10 +182,8 @@
    preserving e for resolve of e
    d: remote is newer -> g
   getting d
-  updating: d 1/2 files (50.00%)
    b: remote unchanged -> k
    e: versions differ -> m
-  updating: e 2/2 files (100.00%)
   picked tool 'internal:merge' for e (binary False symlink False)
   merging e
   my e@1905859650ec+ other e@9c233e8e184d ancestor e@68795b066622
@@ -496,10 +492,12 @@
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     2
   
-Test that template correctly expands more than one 'extra' (issue4362)
-  $ hg -R ../converted log -r 7 --template "{extras % ' Extra: {extra}\n'}"
+Test that template correctly expands more than one 'extra' (issue4362), and that
+'intermediate-source' is converted.
+  $ hg -R ../converted log -r 13 --template "{extras % ' Extra: {extra}\n'}"
    Extra: branch=default
-   Extra: convert_revision=ef0ef43d49e79e81ddafdc7997401ba0041efc82
+   Extra: convert_revision=7a4785234d87ec1aa420ed6b11afe40fa73e12a9
+   Extra: intermediate-source=7ae846e9111fc8f57745634250c7b9ac0a60689b
    Extra: source=e0213322b2c1a5d5d236c74e79666441bee67a7d
 
 The transplant case
--- a/tests/test-hardlinks.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-hardlinks.t	Sat Jul 18 17:32:38 2015 -0500
@@ -57,7 +57,7 @@
 
 Create hardlinked clone r2:
 
-  $ hg clone -U --debug r1 r2
+  $ hg clone -U --debug r1 r2 --config progress.debug=true
   linking: 1
   linking: 2
   linking: 3
--- a/tests/test-help.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-help.t	Sat Jul 18 17:32:38 2015 -0500
@@ -264,7 +264,6 @@
        notify        hooks for sending email push notifications
        pager         browse command output with an external pager
        patchbomb     command to send changesets as (a series of) patch emails
-       progress      show progress bars for some actions
        purge         command to delete untracked files from the working
                      directory
        record        commands to interactively select changes for
@@ -797,6 +796,8 @@
    debugrebuilddirstate
                  rebuild the dirstate as it would look like for the given
                  revision
+   debugrebuildfncache
+                 rebuild the fncache file
    debugrename   dump rename information
    debugrevlog   show data and statistics about a revlog
    debugrevspec  parse and apply a revision specification
@@ -1116,6 +1117,10 @@
   abort: help section not found
   [255]
 
+  $ hg help template.files
+      files         List of strings. All files modified, added, or removed by
+                    this changeset.
+
 Test dynamic list of merge tools only shows up once
   $ hg help merge-tools
   Merge Tools
@@ -1251,7 +1256,7 @@
   $ hg serve -R "$TESTTMP/test" -n test -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid >> $DAEMON_PIDS
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT "help"
+  $ get-with-headers.py 127.0.0.1:$HGPORT "help"
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -1802,7 +1807,7 @@
   </html>
   
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT "help/add"
+  $ get-with-headers.py 127.0.0.1:$HGPORT "help/add"
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -1962,7 +1967,7 @@
   </html>
   
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT "help/remove"
+  $ get-with-headers.py 127.0.0.1:$HGPORT "help/remove"
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -2155,7 +2160,7 @@
   </html>
   
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT "help/revisions"
+  $ get-with-headers.py 127.0.0.1:$HGPORT "help/revisions"
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -2250,6 +2255,6 @@
   </html>
   
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
 
 #endif
--- a/tests/test-hghave.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-hghave.t	Sat Jul 18 17:32:38 2015 -0500
@@ -1,3 +1,39 @@
 Testing that hghave does not crash when checking features
 
-  $ "$TESTDIR/hghave" --test-features 2>/dev/null
+  $ hghave --test-features 2>/dev/null
+
+Testing hghave extensibility for third party tools
+
+  $ cat > hghaveaddon.py <<EOF
+  > import hghave
+  > @hghave.check("custom", "custom hghave feature")
+  > def has_custom():
+  >     return True
+  > EOF
+
+(invocation via run-tests.py)
+
+  $ cat > test-hghaveaddon.t <<EOF
+  > #require custom
+  >   $ echo foo
+  >   foo
+  > EOF
+  $ run-tests.py test-hghaveaddon.t
+  .
+  # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
+
+(invocation via command line)
+
+  $ unset TESTDIR
+  $ hghave custom
+
+(terminate with exit code 2 at failure of importing hghaveaddon.py)
+
+  $ rm hghaveaddon.*
+  $ cat > hghaveaddon.py <<EOF
+  > importing this file should cause syntax error
+  > EOF
+
+  $ hghave custom
+  failed to import hghaveaddon.py from '.': invalid syntax (hghaveaddon.py, line 1)
+  [2]
--- a/tests/test-hgignore.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-hgignore.t	Sat Jul 18 17:32:38 2015 -0500
@@ -167,3 +167,78 @@
   ? a.c
   ? a.o
   ? syntax
+
+Check using 'include:' in ignore file
+
+  $ hg purge --all --config extensions.purge=
+  $ touch foo.included
+
+  $ echo ".*.included" > otherignore
+  $ hg status -I "include:otherignore"
+  ? foo.included
+
+  $ echo "include:otherignore" >> .hgignore
+  $ hg status
+  A dir/b.o
+  ? .hgignore
+  ? otherignore
+
+Check recursive uses of 'include:'
+
+  $ echo "include:nestedignore" >> otherignore
+  $ echo "glob:*ignore" > nestedignore
+  $ hg status
+  A dir/b.o
+
+  $ cp otherignore goodignore
+  $ echo "include:badignore" >> otherignore
+  $ hg status
+  skipping unreadable pattern file 'badignore': No such file or directory
+  A dir/b.o
+
+  $ mv goodignore otherignore
+
+Check including subincludes
+
+  $ hg revert -q --all
+  $ hg purge --all --config extensions.purge=
+  $ echo ".hgignore" > .hgignore
+  $ mkdir dir1 dir2
+  $ touch dir1/file1 dir1/file2 dir2/file1 dir2/file2
+  $ echo "subinclude:dir2/.hgignore" >> .hgignore
+  $ echo "glob:file*2" > dir2/.hgignore
+  $ hg status
+  ? dir1/file1
+  ? dir1/file2
+  ? dir2/file1
+
+Check including subincludes with regexs
+
+  $ echo "subinclude:dir1/.hgignore" >> .hgignore
+  $ echo "regexp:f.le1" > dir1/.hgignore
+
+  $ hg status
+  ? dir1/file2
+  ? dir2/file1
+
+Check multiple levels of sub-ignores
+
+  $ mkdir dir1/subdir
+  $ touch dir1/subdir/subfile1 dir1/subdir/subfile3 dir1/subdir/subfile4
+  $ echo "subinclude:subdir/.hgignore" >> dir1/.hgignore
+  $ echo "glob:subfil*3" >> dir1/subdir/.hgignore
+
+  $ hg status
+  ? dir1/file2
+  ? dir1/subdir/subfile4
+  ? dir2/file1
+
+Check include subignore at the same level
+
+  $ mv dir1/subdir/.hgignore dir1/.hgignoretwo
+  $ echo "regexp:f.le1" > dir1/.hgignore
+  $ echo "subinclude:.hgignoretwo" >> dir1/.hgignore
+  $ echo "glob:file*2" > dir1/.hgignoretwo
+
+  $ hg status | grep file2
+  [1]
--- a/tests/test-hgweb-bundle.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-hgweb-bundle.t	Sat Jul 18 17:32:38 2015 -0500
@@ -27,7 +27,7 @@
 
 Ensure we're serving from the bundle
 
-  $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/?style=raw')
+  $ (get-with-headers.py localhost:$HGPORT 'file/tip/?style=raw')
   200 Script output follows
   
   
--- a/tests/test-hgweb-commands.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-hgweb-commands.t	Sat Jul 18 17:32:38 2015 -0500
@@ -26,7 +26,6 @@
   $ hg ci -Ambranch
   $ hg branch unstable
   marked working directory as branch unstable
-  (branches are permanent and global, did you want a bookmark?)
   >>> open('msg', 'wb').write('branch commit with null character: \0\n')
   $ hg ci -l msg
   $ rm msg
@@ -54,7 +53,7 @@
 
 Logs and changes
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/?style=atom'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'log/?style=atom'
   200 Script output follows
   
   <?xml version="1.0" encoding="ascii"?>
@@ -240,7 +239,7 @@
    </entry>
   
   </feed>
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/?style=rss'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'log/?style=rss'
   200 Script output follows
   
   <?xml version="1.0" encoding="ascii"?>
@@ -414,7 +413,7 @@
   
     </channel>
   </rss> (no-eol)
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/1/?style=atom'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'log/1/?style=atom'
   200 Script output follows
   
   <?xml version="1.0" encoding="ascii"?>
@@ -514,7 +513,7 @@
    </entry>
   
   </feed>
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/1/?style=rss'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'log/1/?style=rss'
   200 Script output follows
   
   <?xml version="1.0" encoding="ascii"?>
@@ -608,7 +607,7 @@
   
     </channel>
   </rss> (no-eol)
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/1/foo/?style=atom'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'log/1/foo/?style=atom'
   200 Script output follows
   
   <?xml version="1.0" encoding="ascii"?>
@@ -663,7 +662,7 @@
    </entry>
   
   </feed>
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/1/foo/?style=rss'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'log/1/foo/?style=rss'
   200 Script output follows
   
   <?xml version="1.0" encoding="ascii"?>
@@ -684,7 +683,7 @@
   
     </channel>
   </rss>
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog/'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'shortlog/'
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -711,14 +710,14 @@
   </div>
   <ul>
   <li class="active">log</li>
-  <li><a href="/graph/cad8025a2e87">graph</a></li>
+  <li><a href="/graph/tip">graph</a></li>
   <li><a href="/tags">tags</a></li>
   <li><a href="/bookmarks">bookmarks</a></li>
   <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
-  <li><a href="/rev/cad8025a2e87">changeset</a></li>
-  <li><a href="/file/cad8025a2e87">browse</a></li>
+  <li><a href="/rev/tip">changeset</a></li>
+  <li><a href="/file/tip">browse</a></li>
   </ul>
   <ul>
   
@@ -745,8 +744,8 @@
   </form>
   
   <div class="navigate">
-  <a href="/shortlog/3?revcount=30">less</a>
-  <a href="/shortlog/3?revcount=120">more</a>
+  <a href="/shortlog/tip?revcount=30">less</a>
+  <a href="/shortlog/tip?revcount=120">more</a>
   | rev 3: <a href="/shortlog/2ef0ac749a14">(0)</a> <a href="/shortlog/tip">tip</a> 
   </div>
   
@@ -762,30 +761,42 @@
    <tr>
     <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
     <td class="author">test</td>
-    <td class="description"><a href="/rev/cad8025a2e87">branch commit with null character: </a><span class="branchhead">unstable</span> <span class="tag">tip</span> <span class="tag">something</span> </td>
+    <td class="description">
+     <a href="/rev/cad8025a2e87">branch commit with null character: </a>
+     <span class="branchhead">unstable</span> <span class="tag">tip</span> <span class="tag">something</span> 
+    </td>
    </tr>
    <tr>
     <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
     <td class="author">test</td>
-    <td class="description"><a href="/rev/1d22e65f027e">branch</a><span class="branchhead">stable</span> </td>
+    <td class="description">
+     <a href="/rev/1d22e65f027e">branch</a>
+     <span class="branchhead">stable</span> 
+    </td>
    </tr>
    <tr>
     <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
     <td class="author">test</td>
-    <td class="description"><a href="/rev/a4f92ed23982">Added tag 1.0 for changeset 2ef0ac749a14</a><span class="branchhead">default</span> </td>
+    <td class="description">
+     <a href="/rev/a4f92ed23982">Added tag 1.0 for changeset 2ef0ac749a14</a>
+     <span class="branchhead">default</span> 
+    </td>
    </tr>
    <tr>
     <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
     <td class="author">test</td>
-    <td class="description"><a href="/rev/2ef0ac749a14">base</a><span class="tag">1.0</span> <span class="tag">anotherthing</span> </td>
+    <td class="description">
+     <a href="/rev/2ef0ac749a14">base</a>
+     <span class="tag">1.0</span> <span class="tag">anotherthing</span> 
+    </td>
    </tr>
   
   </tbody>
   </table>
   
   <div class="navigate">
-  <a href="/shortlog/3?revcount=30">less</a>
-  <a href="/shortlog/3?revcount=120">more</a>
+  <a href="/shortlog/tip?revcount=30">less</a>
+  <a href="/shortlog/tip?revcount=120">more</a>
   | rev 3: <a href="/shortlog/2ef0ac749a14">(0)</a> <a href="/shortlog/tip">tip</a> 
   </div>
   
@@ -813,7 +824,7 @@
   </body>
   </html>
   
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'rev/0/'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'rev/0/'
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -834,16 +845,16 @@
   <img src="/static/hglogo.png" alt="mercurial" /></a>
   </div>
   <ul>
-   <li><a href="/shortlog/2ef0ac749a14">log</a></li>
-   <li><a href="/graph/2ef0ac749a14">graph</a></li>
+   <li><a href="/shortlog/0">log</a></li>
+   <li><a href="/graph/0">graph</a></li>
    <li><a href="/tags">tags</a></li>
    <li><a href="/bookmarks">bookmarks</a></li>
    <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
    <li class="active">changeset</li>
-   <li><a href="/raw-rev/2ef0ac749a14">raw</a></li>
-   <li><a href="/file/2ef0ac749a14">browse</a></li>
+   <li><a href="/raw-rev/0">raw</a></li>
+   <li><a href="/file/0">browse</a></li>
   </ul>
   <ul>
    
@@ -856,7 +867,10 @@
   <div class="main">
   
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
-  <h3>changeset 0:2ef0ac749a14  <span class="tag">1.0</span>  <span class="tag">anotherthing</span> </h3>
+  <h3>
+   changeset 0:<a href="/rev/2ef0ac749a14">2ef0ac749a14</a>
+   <span class="tag">1.0</span> <span class="tag">anotherthing</span> 
+  </h3>
   
   <form class="search" action="/log">
   
@@ -942,7 +956,7 @@
   </body>
   </html>
   
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'rev/1/?style=raw'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'rev/1/?style=raw'
   200 Script output follows
   
   
@@ -959,7 +973,7 @@
   @@ -0,0 +1,1 @@
   +2ef0ac749a14e4f57a5a822464a0902c6f7f448f 1.0
   
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=base'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'log?rev=base'
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -986,6 +1000,8 @@
   <li><a href="/tags">tags</a></li>
   <li><a href="/bookmarks">bookmarks</a></li>
   <li><a href="/branches">branches</a></li>
+  </ul>
+  <ul>
   <li><a href="/help">help</a></li>
   </ul>
   </div>
@@ -1024,7 +1040,10 @@
    <tr>
     <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
     <td class="author">test</td>
-    <td class="description"><a href="/rev/2ef0ac749a14">base</a><span class="tag">1.0</span> <span class="tag">anotherthing</span> </td>
+    <td class="description">
+     <a href="/rev/2ef0ac749a14">base</a>
+     <span class="tag">1.0</span> <span class="tag">anotherthing</span> 
+    </td>
    </tr>
   
   </tbody>
@@ -1044,12 +1063,12 @@
   </body>
   </html>
   
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=stable&style=raw' | grep 'revision:'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'log?rev=stable&style=raw' | grep 'revision:'
   revision:    2
 
 Search with revset syntax
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=tip^&style=raw'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'log?rev=tip^&style=raw'
   200 Script output follows
   
   
@@ -1066,7 +1085,7 @@
   branch:      stable
   
   
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=last(all(),2)^&style=raw'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'log?rev=last(all(),2)^&style=raw'
   200 Script output follows
   
   
@@ -1090,7 +1109,7 @@
   branch:      default
   
   
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=last(all(,2)^&style=raw'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'log?rev=last(all(,2)^&style=raw'
   200 Script output follows
   
   
@@ -1100,7 +1119,7 @@
   # Mode literal keyword search
   
   
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=last(al(),2)^&style=raw'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'log?rev=last(al(),2)^&style=raw'
   200 Script output follows
   
   
@@ -1110,7 +1129,7 @@
   # Mode literal keyword search
   
   
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=bookmark(anotherthing)&style=raw'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'log?rev=bookmark(anotherthing)&style=raw'
   200 Script output follows
   
   
@@ -1128,7 +1147,7 @@
   bookmark:    anotherthing
   
   
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=bookmark(abc)&style=raw'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'log?rev=bookmark(abc)&style=raw'
   200 Script output follows
   
   
@@ -1138,7 +1157,7 @@
   # Mode literal keyword search
   
   
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=deadbeef:&style=raw'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'log?rev=deadbeef:&style=raw'
   200 Script output follows
   
   
@@ -1149,7 +1168,7 @@
   
   
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=user("test")&style=raw'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'log?rev=user("test")&style=raw'
   200 Script output follows
   
   
@@ -1190,7 +1209,7 @@
   bookmark:    anotherthing
   
   
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?rev=user("re:test")&style=raw'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'log?rev=user("re:test")&style=raw'
   200 Script output follows
   
   
@@ -1203,11 +1222,11 @@
 
 File-related
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/1/foo/?style=raw'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'file/1/foo/?style=raw'
   200 Script output follows
   
   foo
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'annotate/1/foo/?style=raw'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'annotate/1/foo/?style=raw'
   200 Script output follows
   
   
@@ -1216,7 +1235,7 @@
   
   
   
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/1/?style=raw'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'file/1/?style=raw'
   200 Script output follows
   
   
@@ -1232,7 +1251,7 @@
   $ hg parents --template "{node|short}\n" -r 1 foo
   2ef0ac749a14
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/1/foo'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'file/1/foo'
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -1254,24 +1273,24 @@
   <img src="/static/hglogo.png" alt="mercurial" /></a>
   </div>
   <ul>
-  <li><a href="/shortlog/a4f92ed23982">log</a></li>
-  <li><a href="/graph/a4f92ed23982">graph</a></li>
+  <li><a href="/shortlog/1">log</a></li>
+  <li><a href="/graph/1">graph</a></li>
   <li><a href="/tags">tags</a></li>
   <li><a href="/bookmarks">bookmarks</a></li>
   <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
-  <li><a href="/rev/a4f92ed23982">changeset</a></li>
-  <li><a href="/file/a4f92ed23982/">browse</a></li>
+  <li><a href="/rev/1">changeset</a></li>
+  <li><a href="/file/1/">browse</a></li>
   </ul>
   <ul>
   <li class="active">file</li>
   <li><a href="/file/tip/foo">latest</a></li>
-  <li><a href="/diff/a4f92ed23982/foo">diff</a></li>
-  <li><a href="/comparison/a4f92ed23982/foo">comparison</a></li>
-  <li><a href="/annotate/a4f92ed23982/foo">annotate</a></li>
-  <li><a href="/log/a4f92ed23982/foo">file log</a></li>
-  <li><a href="/raw-file/a4f92ed23982/foo">raw</a></li>
+  <li><a href="/diff/1/foo">diff</a></li>
+  <li><a href="/comparison/1/foo">comparison</a></li>
+  <li><a href="/annotate/1/foo">annotate</a></li>
+  <li><a href="/log/1/foo">file log</a></li>
+  <li><a href="/raw-file/1/foo">raw</a></li>
   </ul>
   <ul>
   <li><a href="/help">help</a></li>
@@ -1280,7 +1299,10 @@
   
   <div class="main">
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
-  <h3>view foo @ 1:a4f92ed23982</h3>
+  <h3>
+   view foo @ 1:<a href="/rev/a4f92ed23982">a4f92ed23982</a>
+   
+  </h3>
   
   <form class="search" action="/log">
   
@@ -1326,7 +1348,7 @@
   </body>
   </html>
   
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'filediff/0/foo/?style=raw'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'filediff/0/foo/?style=raw'
   200 Script output follows
   
   
@@ -1340,7 +1362,7 @@
   
   
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'filediff/1/foo/?style=raw'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'filediff/1/foo/?style=raw'
   200 Script output follows
   
   
@@ -1356,7 +1378,7 @@
   $ hg parents --template "{node|short}\n" -r 2 foo
   2ef0ac749a14
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/2/foo'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'file/2/foo'
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -1378,24 +1400,24 @@
   <img src="/static/hglogo.png" alt="mercurial" /></a>
   </div>
   <ul>
-  <li><a href="/shortlog/1d22e65f027e">log</a></li>
-  <li><a href="/graph/1d22e65f027e">graph</a></li>
+  <li><a href="/shortlog/2">log</a></li>
+  <li><a href="/graph/2">graph</a></li>
   <li><a href="/tags">tags</a></li>
   <li><a href="/bookmarks">bookmarks</a></li>
   <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
-  <li><a href="/rev/1d22e65f027e">changeset</a></li>
-  <li><a href="/file/1d22e65f027e/">browse</a></li>
+  <li><a href="/rev/2">changeset</a></li>
+  <li><a href="/file/2/">browse</a></li>
   </ul>
   <ul>
   <li class="active">file</li>
   <li><a href="/file/tip/foo">latest</a></li>
-  <li><a href="/diff/1d22e65f027e/foo">diff</a></li>
-  <li><a href="/comparison/1d22e65f027e/foo">comparison</a></li>
-  <li><a href="/annotate/1d22e65f027e/foo">annotate</a></li>
-  <li><a href="/log/1d22e65f027e/foo">file log</a></li>
-  <li><a href="/raw-file/1d22e65f027e/foo">raw</a></li>
+  <li><a href="/diff/2/foo">diff</a></li>
+  <li><a href="/comparison/2/foo">comparison</a></li>
+  <li><a href="/annotate/2/foo">annotate</a></li>
+  <li><a href="/log/2/foo">file log</a></li>
+  <li><a href="/raw-file/2/foo">raw</a></li>
   </ul>
   <ul>
   <li><a href="/help">help</a></li>
@@ -1404,7 +1426,10 @@
   
   <div class="main">
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
-  <h3>view foo @ 2:1d22e65f027e</h3>
+  <h3>
+   view foo @ 2:<a href="/rev/1d22e65f027e">1d22e65f027e</a>
+   <span class="branchname">stable</span> 
+  </h3>
   
   <form class="search" action="/log">
   
@@ -1454,23 +1479,23 @@
 
 Overviews
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'raw-tags'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'raw-tags'
   200 Script output follows
   
   tip	cad8025a2e87f88c06259790adfa15acb4080123
   1.0	2ef0ac749a14e4f57a5a822464a0902c6f7f448f
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'raw-branches'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'raw-branches'
   200 Script output follows
   
   unstable	cad8025a2e87f88c06259790adfa15acb4080123	open
   stable	1d22e65f027e5a0609357e7d8e7508cd2ba5d2fe	inactive
   default	a4f92ed23982be056b9852de5dfe873eaac7f0de	inactive
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'raw-bookmarks'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'raw-bookmarks'
   200 Script output follows
   
   anotherthing	2ef0ac749a14e4f57a5a822464a0902c6f7f448f
   something	cad8025a2e87f88c06259790adfa15acb4080123
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'summary/?style=gitweb'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'summary/?style=gitweb'
   200 Script output follows
   
   <?xml version="1.0" encoding="ascii"?>
@@ -1509,7 +1534,7 @@
   <a href="/tags?style=gitweb">tags</a> |
   <a href="/bookmarks?style=gitweb">bookmarks</a> |
   <a href="/branches?style=gitweb">branches</a> |
-  <a href="/file/cad8025a2e87?style=gitweb">files</a> |
+  <a href="/file?style=gitweb">files</a> |
   <a href="/help?style=gitweb">help</a>
   <br/>
   </div>
@@ -1672,7 +1697,7 @@
   </body>
   </html>
   
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph/?style=gitweb'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'graph/?style=gitweb'
   200 Script output follows
   
   <?xml version="1.0" encoding="ascii"?>
@@ -1707,16 +1732,16 @@
   <div class="page_nav">
   <a href="/summary?style=gitweb">summary</a> |
   <a href="/shortlog?style=gitweb">shortlog</a> |
-  <a href="/log/3?style=gitweb">changelog</a> |
+  <a href="/log/tip?style=gitweb">changelog</a> |
   graph |
   <a href="/tags?style=gitweb">tags</a> |
   <a href="/bookmarks?style=gitweb">bookmarks</a> |
   <a href="/branches?style=gitweb">branches</a> |
-  <a href="/file/cad8025a2e87?style=gitweb">files</a> |
+  <a href="/file/tip?style=gitweb">files</a> |
   <a href="/help?style=gitweb">help</a>
   <br/>
-  <a href="/graph/3?revcount=30&style=gitweb">less</a>
-  <a href="/graph/3?revcount=120&style=gitweb">more</a>
+  <a href="/graph/tip?revcount=30&style=gitweb">less</a>
+  <a href="/graph/tip?revcount=120&style=gitweb">more</a>
   | <a href="/graph/2ef0ac749a14?style=gitweb">(0)</a> <a href="/graph/tip?style=gitweb">tip</a> <br/>
   </div>
   
@@ -1788,8 +1813,8 @@
   </script>
   
   <div class="page_nav">
-  <a href="/graph/3?revcount=30&style=gitweb">less</a>
-  <a href="/graph/3?revcount=120&style=gitweb">more</a>
+  <a href="/graph/tip?revcount=30&style=gitweb">less</a>
+  <a href="/graph/tip?revcount=120&style=gitweb">more</a>
   | <a href="/graph/2ef0ac749a14?style=gitweb">(0)</a> <a href="/graph/tip?style=gitweb">tip</a> 
   </div>
   
@@ -1819,7 +1844,7 @@
   
 raw graph
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph/?style=raw'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'graph/?style=raw'
   200 Script output follows
   
   
@@ -1869,28 +1894,28 @@
 
 capabilities
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=capabilities'; echo
+  $ get-with-headers.py 127.0.0.1:$HGPORT '?cmd=capabilities'; echo
   200 Script output follows
   
   lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1*%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 (glob)
 
 heads
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=heads'
+  $ get-with-headers.py 127.0.0.1:$HGPORT '?cmd=heads'
   200 Script output follows
   
   cad8025a2e87f88c06259790adfa15acb4080123
 
 branches
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=branches&nodes=0000000000000000000000000000000000000000'
+  $ get-with-headers.py 127.0.0.1:$HGPORT '?cmd=branches&nodes=0000000000000000000000000000000000000000'
   200 Script output follows
   
   0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
 
 changegroup
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=changegroup&roots=0000000000000000000000000000000000000000'
+  $ get-with-headers.py 127.0.0.1:$HGPORT '?cmd=changegroup&roots=0000000000000000000000000000000000000000'
   200 Script output follows
   
   x\x9c\xbd\x94MHTQ\x14\xc7'+\x9d\xc66\x81\x89P\xc1\xa3\x14\xcct\xba\xef\xbe\xfb\xde\xbb\xcfr0\xb3"\x02\x11[%\x98\xdcO\xa7\xd2\x19\x98y\xd2\x07h"\x96\xa0e\xda\xa6lUY-\xca\x08\xa2\x82\x16\x96\xd1\xa2\xf0#\xc8\x95\x1b\xdd$!m*"\xc8\x82\xea\xbe\x9c\x01\x85\xc9\x996\x1d\xf8\xc1\xe3~\x9d\xff9\xef\x7f\xaf\xcf\xe7\xbb\x19\xfc4\xec^\xcb\x9b\xfbz\xa6\xbe\xb3\x90_\xef/\x8d\x9e\xad\xbe\xe4\xcb0\xd2\xec\xad\x12X:\xc8\x12\x12\xd9:\x95\xba	\x1cG\xb7$\xc5\xc44\x1c(\x1d\x03\x03\xdb\x84\x0cK#\xe0\x8a\xb8\x1b\x00\x1a\x08p\xb2SF\xa3\x01\x8f\x00%q\xa1Ny{k!8\xe5t>[{\xe2j\xddl\xc3\xcf\xee\xd0\xddW\x9ff3U\x9djobj\xbb\x87E\x88\x05l\x001\x12\x18\x13\xc6 \xb7(\xe3\x02a\x80\x81\xcel.u\x9b\x1b\x8c\x91\x80Z\x0c\x15\x15 (esc)
@@ -1901,14 +1926,14 @@
 
 stream_out
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=stream_out'
+  $ get-with-headers.py 127.0.0.1:$HGPORT '?cmd=stream_out'
   200 Script output follows
   
   1
 
 failing unbundle, requires POST request
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=unbundle'
+  $ get-with-headers.py 127.0.0.1:$HGPORT '?cmd=unbundle'
   405 push requires POST request
   
   0
@@ -1917,7 +1942,7 @@
 
 Static files
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'static/style.css'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'static/style.css'
   200 Script output follows
   
   a { text-decoration:none; }
@@ -2029,7 +2054,7 @@
 
 Stop and restart with HGENCODING=cp932 and preferuncompressed
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
   $ HGENCODING=cp932 hg serve --config server.preferuncompressed=True -n test \
   >     -p $HGPORT -d --pid-file=hg.pid -E errors.log
   $ cat hg.pid >> $DAEMON_PIDS
@@ -2041,7 +2066,7 @@
 
 Graph json escape of multibyte character
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph/' > out
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'graph/' > out
   >>> for line in open("out"):
   ...     if line.startswith("var data ="):
   ...         print line,
@@ -2049,7 +2074,7 @@
 
 capabilities
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=capabilities'; echo
+  $ get-with-headers.py 127.0.0.1:$HGPORT '?cmd=capabilities'; echo
   200 Script output follows
   
   lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream-preferred stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1*%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 (glob)
@@ -2059,7 +2084,7 @@
 ERRORS ENCOUNTERED
 
   $ cat errors.log
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
 
   $ cd ..
 
@@ -2098,23 +2123,23 @@
 
 Test paging
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT \
+  $ get-with-headers.py 127.0.0.1:$HGPORT \
   >   'graph/?style=raw' | grep changeset
   changeset:   aed2d9c1d0e7
   changeset:   b60a39a85a01
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT \
+  $ get-with-headers.py 127.0.0.1:$HGPORT \
   >   'graph/?style=raw&revcount=3' | grep changeset
   changeset:   aed2d9c1d0e7
   changeset:   b60a39a85a01
   changeset:   ada793dcc118
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT \
+  $ get-with-headers.py 127.0.0.1:$HGPORT \
   >   'graph/e06180cbfb0?style=raw&revcount=3' | grep changeset
   changeset:   e06180cbfb0c
   changeset:   b4e73ffab476
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT \
+  $ get-with-headers.py 127.0.0.1:$HGPORT \
   >   'graph/b4e73ffab47?style=raw&revcount=3' | grep changeset
   changeset:   b4e73ffab476
 
--- a/tests/test-hgweb-descend-empties.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-hgweb-descend-empties.t	Sat Jul 18 17:32:38 2015 -0500
@@ -3,7 +3,7 @@
 Test chains of near empty directories, terminating 3 different ways:
 - a1: file at level 4 (deepest)
 - b1: two dirs at level 3
-- e1: file at level 2
+- d1: file at level 2
 
 Set up the repo
 
@@ -11,25 +11,25 @@
   $ cd test
   $ mkdir -p a1/a2/a3/a4
   $ mkdir -p b1/b2/b3/b4
-  $ mkdir -p b1/b2/c3/c4
+  $ mkdir -p b1/b2/b3/c4
   $ mkdir -p d1/d2/d3/d4
   $ echo foo > a1/a2/a3/a4/foo
   $ echo foo > b1/b2/b3/b4/foo
-  $ echo foo > b1/b2/c3/c4/foo
+  $ echo foo > b1/b2/b3/c4/foo
   $ echo foo > d1/d2/d3/d4/foo
   $ echo foo > d1/d2/foo
   $ hg ci -Ama
   adding a1/a2/a3/a4/foo
   adding b1/b2/b3/b4/foo
-  adding b1/b2/c3/c4/foo
+  adding b1/b2/b3/c4/foo
   adding d1/d2/d3/d4/foo
   adding d1/d2/foo
   $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -E errors.log
   $ cat hg.pid >> $DAEMON_PIDS
 
-manifest with descending
+manifest with descending (paper)
 
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'file'
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -40,7 +40,7 @@
   <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
   <script type="text/javascript" src="/static/mercurial.js"></script>
   
-  <title>test: 9087c84a0f5d /</title>
+  <title>test: c9f45f7a1659 /</title>
   </head>
   <body>
   
@@ -51,14 +51,14 @@
   <img src="/static/hglogo.png" alt="mercurial" /></a>
   </div>
   <ul>
-  <li><a href="/shortlog/9087c84a0f5d">log</a></li>
-  <li><a href="/graph/9087c84a0f5d">graph</a></li>
+  <li><a href="/shortlog/tip">log</a></li>
+  <li><a href="/graph/tip">graph</a></li>
   <li><a href="/tags">tags</a></li>
   <li><a href="/bookmarks">bookmarks</a></li>
   <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
-  <li><a href="/rev/9087c84a0f5d">changeset</a></li>
+  <li><a href="/rev/tip">changeset</a></li>
   <li class="active">browse</li>
   </ul>
   <ul>
@@ -71,7 +71,10 @@
   
   <div class="main">
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
-  <h3>directory / @ 0:9087c84a0f5d <span class="tag">tip</span> </h3>
+  <h3>
+   directory / @ 0:<a href="/rev/c9f45f7a1659">c9f45f7a1659</a>
+   <span class="tag">tip</span> 
+  </h3>
   
   <form class="search" action="/log">
   
@@ -90,17 +93,17 @@
   </thead>
   <tbody class="stripes2">
   <tr class="fileline">
-    <td class="name"><a href="/file/9087c84a0f5d/">[up]</a></td>
+    <td class="name"><a href="/file/tip/">[up]</a></td>
     <td class="size"></td>
     <td class="permissions">drwxr-xr-x</td>
   </tr>
   
   <tr class="fileline">
   <td class="name">
-  <a href="/file/9087c84a0f5d/a1">
+  <a href="/file/tip/a1">
   <img src="/static/coal-folder.png" alt="dir."/> a1/
   </a>
-  <a href="/file/9087c84a0f5d/a1/a2/a3/a4">
+  <a href="/file/tip/a1/a2/a3/a4">
   a2/a3/a4
   </a>
   </td>
@@ -109,11 +112,11 @@
   </tr>
   <tr class="fileline">
   <td class="name">
-  <a href="/file/9087c84a0f5d/b1">
+  <a href="/file/tip/b1">
   <img src="/static/coal-folder.png" alt="dir."/> b1/
   </a>
-  <a href="/file/9087c84a0f5d/b1/b2">
-  b2
+  <a href="/file/tip/b1/b2/b3">
+  b2/b3
   </a>
   </td>
   <td class="size"></td>
@@ -121,10 +124,129 @@
   </tr>
   <tr class="fileline">
   <td class="name">
-  <a href="/file/9087c84a0f5d/d1">
+  <a href="/file/tip/d1">
   <img src="/static/coal-folder.png" alt="dir."/> d1/
   </a>
-  <a href="/file/9087c84a0f5d/d1/d2">
+  <a href="/file/tip/d1/d2">
+  d2
+  </a>
+  </td>
+  <td class="size"></td>
+  <td class="permissions">drwxr-xr-x</td>
+  </tr>
+  
+  </tbody>
+  </table>
+  </div>
+  </div>
+  <script type="text/javascript">process_dates()</script>
+  
+  
+  </body>
+  </html>
+  
+
+manifest with descending (coal)
+
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'file?style=coal'
+  200 Script output follows
+  
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
+  <head>
+  <link rel="icon" href="/static/hgicon.png" type="image/png" />
+  <meta name="robots" content="index, nofollow" />
+  <link rel="stylesheet" href="/static/style-coal.css" type="text/css" />
+  <script type="text/javascript" src="/static/mercurial.js"></script>
+  
+  <title>test: c9f45f7a1659 /</title>
+  </head>
+  <body>
+  
+  <div class="container">
+  <div class="menu">
+  <div class="logo">
+  <a href="http://mercurial.selenic.com/">
+  <img src="/static/hglogo.png" alt="mercurial" /></a>
+  </div>
+  <ul>
+  <li><a href="/shortlog/tip?style=coal">log</a></li>
+  <li><a href="/graph/tip?style=coal">graph</a></li>
+  <li><a href="/tags?style=coal">tags</a></li>
+  <li><a href="/bookmarks?style=coal">bookmarks</a></li>
+  <li><a href="/branches?style=coal">branches</a></li>
+  </ul>
+  <ul>
+  <li><a href="/rev/tip?style=coal">changeset</a></li>
+  <li class="active">browse</li>
+  </ul>
+  <ul>
+  
+  </ul>
+  <ul>
+   <li><a href="/help?style=coal">help</a></li>
+  </ul>
+  </div>
+  
+  <div class="main">
+  <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
+  <h3>
+   directory / @ 0:<a href="/rev/c9f45f7a1659?style=coal">c9f45f7a1659</a>
+   <span class="tag">tip</span> 
+  </h3>
+  
+  <form class="search" action="/log">
+  <input type="hidden" name="style" value="coal" />
+  <p><input name="rev" id="search1" type="text" size="30" /></p>
+  <div id="hint">Find changesets by keywords (author, files, the commit message), revision
+  number or hash, or <a href="/help/revsets">revset expression</a>.</div>
+  </form>
+  
+  <table class="bigtable">
+  <thead>
+  <tr>
+    <th class="name">name</th>
+    <th class="size">size</th>
+    <th class="permissions">permissions</th>
+  </tr>
+  </thead>
+  <tbody class="stripes2">
+  <tr class="fileline">
+    <td class="name"><a href="/file/tip/?style=coal">[up]</a></td>
+    <td class="size"></td>
+    <td class="permissions">drwxr-xr-x</td>
+  </tr>
+  
+  <tr class="fileline">
+  <td class="name">
+  <a href="/file/tip/a1?style=coal">
+  <img src="/static/coal-folder.png" alt="dir."/> a1/
+  </a>
+  <a href="/file/tip/a1/a2/a3/a4?style=coal">
+  a2/a3/a4
+  </a>
+  </td>
+  <td class="size"></td>
+  <td class="permissions">drwxr-xr-x</td>
+  </tr>
+  <tr class="fileline">
+  <td class="name">
+  <a href="/file/tip/b1?style=coal">
+  <img src="/static/coal-folder.png" alt="dir."/> b1/
+  </a>
+  <a href="/file/tip/b1/b2/b3?style=coal">
+  b2/b3
+  </a>
+  </td>
+  <td class="size"></td>
+  <td class="permissions">drwxr-xr-x</td>
+  </tr>
+  <tr class="fileline">
+  <td class="name">
+  <a href="/file/tip/d1?style=coal">
+  <img src="/static/coal-folder.png" alt="dir."/> d1/
+  </a>
+  <a href="/file/tip/d1/d2?style=coal">
   d2
   </a>
   </td>
@@ -143,6 +265,307 @@
   </html>
   
 
+manifest with descending (monoblue)
+
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'file?style=monoblue'
+  200 Script output follows
+  
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+  <head>
+      <link rel="icon" href="/static/hgicon.png" type="image/png" />
+      <meta name="robots" content="index, nofollow"/>
+      <link rel="stylesheet" href="/static/style-monoblue.css" type="text/css" />
+      <script type="text/javascript" src="/static/mercurial.js"></script>
+  
+  <title>test: files</title>
+      <link rel="alternate" type="application/atom+xml" href="/atom-log" title="Atom feed for test"/>
+      <link rel="alternate" type="application/rss+xml" href="/rss-log" title="RSS feed for test"/>
+  </head>
+  
+  <body>
+  <div id="container">
+      <div class="page-header">
+          <h1 class="breadcrumb"><a href="/">Mercurial</a>  / files</h1>
+  
+          <form action="/log">
+              <input type="hidden" name="style" value="monoblue" />
+              <dl class="search">
+                  <dt><label>Search: </label></dt>
+                  <dd><input type="text" name="rev" /></dd>
+              </dl>
+          </form>
+  
+          <ul class="page-nav">
+              <li><a href="/summary?style=monoblue">summary</a></li>
+              <li><a href="/shortlog?style=monoblue">shortlog</a></li>
+              <li><a href="/changelog?style=monoblue">changelog</a></li>
+              <li><a href="/graph/tip?style=monoblue">graph</a></li>
+              <li><a href="/tags?style=monoblue">tags</a></li>
+              <li><a href="/bookmarks?style=monoblue">bookmarks</a></li>
+              <li><a href="/branches?style=monoblue">branches</a></li>
+              <li class="current">files</li>
+              <li><a href="/help?style=monoblue">help</a></li>
+          </ul>
+      </div>
+  
+      <ul class="submenu">
+          <li><a href="/rev/tip?style=monoblue">changeset</a></li>
+          
+      </ul>
+  
+      <h2 class="no-link no-border">files</h2>
+      <p class="files">/ <span class="logtags"><span class="branchtag" title="default">default</span> <span class="tagtag" title="tip">tip</span> </span></p>
+  
+      <table>
+          <tr class="parity0">
+              <td>drwxr-xr-x</td>
+              <td></td>
+              <td></td>
+              <td><a href="/file/tip/?style=monoblue">[up]</a></td>
+              <td class="link">&nbsp;</td>
+          </tr>
+          
+  <tr class="parity1">
+  <td>drwxr-xr-x</td>
+  <td></td>
+  <td></td>
+  <td>
+  <a href="/file/tip/a1?style=monoblue">a1</a>
+  <a href="/file/tip/a1/a2/a3/a4?style=monoblue">a2/a3/a4</a>
+  </td>
+  <td><a href="/file/tip/a1?style=monoblue">files</a></td>
+  </tr>
+  <tr class="parity0">
+  <td>drwxr-xr-x</td>
+  <td></td>
+  <td></td>
+  <td>
+  <a href="/file/tip/b1?style=monoblue">b1</a>
+  <a href="/file/tip/b1/b2/b3?style=monoblue">b2/b3</a>
+  </td>
+  <td><a href="/file/tip/b1?style=monoblue">files</a></td>
+  </tr>
+  <tr class="parity1">
+  <td>drwxr-xr-x</td>
+  <td></td>
+  <td></td>
+  <td>
+  <a href="/file/tip/d1?style=monoblue">d1</a>
+  <a href="/file/tip/d1/d2?style=monoblue">d2</a>
+  </td>
+  <td><a href="/file/tip/d1?style=monoblue">files</a></td>
+  </tr>
+          
+      </table>
+  
+      <script type="text/javascript">process_dates()</script>
+      <div class="page-footer">
+          <p>Mercurial Repository: test</p>
+          <ul class="rss-logo">
+              <li><a href="/rss-log">RSS</a></li>
+              <li><a href="/atom-log">Atom</a></li>
+          </ul>
+          
+      </div>
+  
+      <div id="powered-by">
+          <p><a href="http://mercurial.selenic.com/" title="Mercurial"><img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a></p>
+      </div>
+  
+      <div id="corner-top-left"></div>
+      <div id="corner-top-right"></div>
+      <div id="corner-bottom-left"></div>
+      <div id="corner-bottom-right"></div>
+  
+  </div>
+  
+  </body>
+  </html>
+  
+
+manifest with descending (gitweb)
+
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'file?style=gitweb'
+  200 Script output follows
+  
+  <?xml version="1.0" encoding="ascii"?>
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
+  <head>
+  <link rel="icon" href="/static/hgicon.png" type="image/png" />
+  <meta name="robots" content="index, nofollow"/>
+  <link rel="stylesheet" href="/static/style-gitweb.css" type="text/css" />
+  <script type="text/javascript" src="/static/mercurial.js"></script>
+  
+  <title>test: files</title>
+  <link rel="alternate" type="application/atom+xml"
+     href="/atom-log" title="Atom feed for test"/>
+  <link rel="alternate" type="application/rss+xml"
+     href="/rss-log" title="RSS feed for test"/>
+  </head>
+  <body>
+  
+  <div class="page_header">
+  <a href="http://mercurial.selenic.com/" title="Mercurial" style="float: right;">Mercurial</a>
+  <a href="/">Mercurial</a>  / files
+  </div>
+  
+  <div class="page_nav">
+  <a href="/summary?style=gitweb">summary</a> |
+  <a href="/shortlog?style=gitweb">shortlog</a> |
+  <a href="/log?style=gitweb">changelog</a> |
+  <a href="/graph?style=gitweb">graph</a> |
+  <a href="/tags?style=gitweb">tags</a> |
+  <a href="/bookmarks?style=gitweb">bookmarks</a> |
+  <a href="/branches?style=gitweb">branches</a> |
+  files |
+  <a href="/rev/tip?style=gitweb">changeset</a>  |
+  <a href="/help?style=gitweb">help</a>
+  <br/>
+  </div>
+  
+  <div class="title">/ <span class="logtags"><span class="branchtag" title="default">default</span> <span class="tagtag" title="tip">tip</span> </span></div>
+  <table cellspacing="0">
+  <tr class="parity0">
+  <td style="font-family:monospace">drwxr-xr-x</td>
+  <td style="font-family:monospace"></td>
+  <td style="font-family:monospace"></td>
+  <td><a href="/file/tip/?style=gitweb">[up]</a></td>
+  <td class="link">&nbsp;</td>
+  </tr>
+  
+  <tr class="parity1">
+  <td style="font-family:monospace">drwxr-xr-x</td>
+  <td style="font-family:monospace"></td>
+  <td style="font-family:monospace"></td>
+  <td>
+  <a href="/file/tip/a1?style=gitweb">a1</a>
+  <a href="/file/tip/a1/a2/a3/a4?style=gitweb">a2/a3/a4</a>
+  </td>
+  <td class="link">
+  <a href="/file/tip/a1?style=gitweb">files</a>
+  </td>
+  </tr>
+  <tr class="parity0">
+  <td style="font-family:monospace">drwxr-xr-x</td>
+  <td style="font-family:monospace"></td>
+  <td style="font-family:monospace"></td>
+  <td>
+  <a href="/file/tip/b1?style=gitweb">b1</a>
+  <a href="/file/tip/b1/b2/b3?style=gitweb">b2/b3</a>
+  </td>
+  <td class="link">
+  <a href="/file/tip/b1?style=gitweb">files</a>
+  </td>
+  </tr>
+  <tr class="parity1">
+  <td style="font-family:monospace">drwxr-xr-x</td>
+  <td style="font-family:monospace"></td>
+  <td style="font-family:monospace"></td>
+  <td>
+  <a href="/file/tip/d1?style=gitweb">d1</a>
+  <a href="/file/tip/d1/d2?style=gitweb">d2</a>
+  </td>
+  <td class="link">
+  <a href="/file/tip/d1?style=gitweb">files</a>
+  </td>
+  </tr>
+  
+  </table>
+  
+  <script type="text/javascript">process_dates()</script>
+  <div class="page_footer">
+  <div class="page_footer_text">test</div>
+  <div class="rss_logo">
+  <a href="/rss-log">RSS</a>
+  <a href="/atom-log">Atom</a>
+  </div>
+  <br />
+  
+  </div>
+  </body>
+  </html>
+  
+
+manifest with descending (spartan)
+
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'file?style=spartan'
+  200 Script output follows
+  
+  <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+  <html>
+  <head>
+  <link rel="icon" href="/static/hgicon.png" type="image/png">
+  <meta name="robots" content="index, nofollow" />
+  <link rel="stylesheet" href="/static/style.css" type="text/css" />
+  <script type="text/javascript" src="/static/mercurial.js"></script>
+  
+  <title>test: files for changeset c9f45f7a1659</title>
+  </head>
+  <body>
+  
+  <div class="buttons">
+  <a href="/log/tip?style=spartan">changelog</a>
+  <a href="/shortlog/tip?style=spartan">shortlog</a>
+  <a href="/graph/tip?style=spartan">graph</a>
+  <a href="/tags?style=spartan">tags</a>
+  <a href="/branches?style=spartan">branches</a>
+  <a href="/rev/tip?style=spartan">changeset</a>
+  
+  <a href="/help?style=spartan">help</a>
+  </div>
+  
+  <h2><a href="/">Mercurial</a>  / files for changeset <a href="/rev/c9f45f7a1659">c9f45f7a1659</a>: /</h2>
+  
+  <table cellpadding="0" cellspacing="0">
+  <tr class="parity0">
+    <td><tt>drwxr-xr-x</tt>&nbsp;
+    <td>&nbsp;
+    <td>&nbsp;
+    <td><a href="/file/tip/?style=spartan">[up]</a>
+  </tr>
+  
+  <tr class="parity1">
+  <td><tt>drwxr-xr-x</tt>&nbsp;
+  <td>&nbsp;
+  <td>&nbsp;
+  <td>
+  <a href="/file/tip/a1?style=spartan">a1/</a>
+  <a href="/file/tip/a1/a2/a3/a4?style=spartan">
+  a2/a3/a4
+  </a>
+  <tr class="parity0">
+  <td><tt>drwxr-xr-x</tt>&nbsp;
+  <td>&nbsp;
+  <td>&nbsp;
+  <td>
+  <a href="/file/tip/b1?style=spartan">b1/</a>
+  <a href="/file/tip/b1/b2/b3?style=spartan">
+  b2/b3
+  </a>
+  <tr class="parity1">
+  <td><tt>drwxr-xr-x</tt>&nbsp;
+  <td>&nbsp;
+  <td>&nbsp;
+  <td>
+  <a href="/file/tip/d1?style=spartan">d1/</a>
+  <a href="/file/tip/d1/d2?style=spartan">
+  d2
+  </a>
+  
+  </table>
+  <script type="text/javascript">process_dates()</script>
+  
+  <div class="logo">
+  <a href="http://mercurial.selenic.com/">
+  <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
+  </div>
+  
+  </body>
+  </html>
+  
+
   $ cat errors.log
 
   $ cd ..
--- a/tests/test-hgweb-diffs.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-hgweb-diffs.t	Sat Jul 18 17:32:38 2015 -0500
@@ -36,7 +36,7 @@
 
 revision
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'rev/0'
+  $ get-with-headers.py localhost:$HGPORT 'rev/0'
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -57,16 +57,16 @@
   <img src="/static/hglogo.png" alt="mercurial" /></a>
   </div>
   <ul>
-   <li><a href="/shortlog/0cd96de13884">log</a></li>
-   <li><a href="/graph/0cd96de13884">graph</a></li>
+   <li><a href="/shortlog/0">log</a></li>
+   <li><a href="/graph/0">graph</a></li>
    <li><a href="/tags">tags</a></li>
    <li><a href="/bookmarks">bookmarks</a></li>
    <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
    <li class="active">changeset</li>
-   <li><a href="/raw-rev/0cd96de13884">raw</a></li>
-   <li><a href="/file/0cd96de13884">browse</a></li>
+   <li><a href="/raw-rev/0">raw</a></li>
+   <li><a href="/file/0">browse</a></li>
   </ul>
   <ul>
    
@@ -79,7 +79,10 @@
   <div class="main">
   
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
-  <h3>changeset 0:0cd96de13884   </h3>
+  <h3>
+   changeset 0:<a href="/rev/0cd96de13884">0cd96de13884</a>
+   
+  </h3>
   
   <form class="search" action="/log">
   
@@ -168,7 +171,7 @@
 
 raw revision
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'raw-rev/0'
+  $ get-with-headers.py localhost:$HGPORT 'raw-rev/0'
   200 Script output follows
   
   
@@ -201,7 +204,7 @@
   $ hg parents --template "{node|short}\n" -r tip b
   0cd96de13884
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'diff/tip/b'
+  $ get-with-headers.py localhost:$HGPORT 'diff/tip/b'
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -223,24 +226,24 @@
   <img src="/static/hglogo.png" alt="mercurial" /></a>
   </div>
   <ul>
-  <li><a href="/shortlog/559edbd9ed20">log</a></li>
-  <li><a href="/graph/559edbd9ed20">graph</a></li>
+  <li><a href="/shortlog/tip">log</a></li>
+  <li><a href="/graph/tip">graph</a></li>
   <li><a href="/tags">tags</a></li>
   <li><a href="/bookmarks">bookmarks</a></li>
   <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
-  <li><a href="/rev/559edbd9ed20">changeset</a></li>
-  <li><a href="/file/559edbd9ed20">browse</a></li>
+  <li><a href="/rev/tip">changeset</a></li>
+  <li><a href="/file/tip">browse</a></li>
   </ul>
   <ul>
-  <li><a href="/file/559edbd9ed20/b">file</a></li>
+  <li><a href="/file/tip/b">file</a></li>
   <li><a href="/file/tip/b">latest</a></li>
   <li class="active">diff</li>
-  <li><a href="/comparison/559edbd9ed20/b">comparison</a></li>
-  <li><a href="/annotate/559edbd9ed20/b">annotate</a></li>
-  <li><a href="/log/559edbd9ed20/b">file log</a></li>
-  <li><a href="/raw-file/559edbd9ed20/b">raw</a></li>
+  <li><a href="/comparison/tip/b">comparison</a></li>
+  <li><a href="/annotate/tip/b">annotate</a></li>
+  <li><a href="/log/tip/b">file log</a></li>
+  <li><a href="/raw-file/tip/b">raw</a></li>
   </ul>
   <ul>
   <li><a href="/help">help</a></li>
@@ -249,7 +252,10 @@
   
   <div class="main">
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
-  <h3>diff b @ 1:559edbd9ed20</h3>
+  <h3>
+   diff b @ 1:<a href="/rev/559edbd9ed20">559edbd9ed20</a>
+   <span class="tag">tip</span> 
+  </h3>
   
   <form class="search" action="/log">
   <p></p>
@@ -302,13 +308,13 @@
 
 set up hgweb with git diffs
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
   $ hg serve --config 'diff.git=1' -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
   $ cat hg.pid >> $DAEMON_PIDS
 
 revision
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'rev/0'
+  $ get-with-headers.py localhost:$HGPORT 'rev/0'
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -329,16 +335,16 @@
   <img src="/static/hglogo.png" alt="mercurial" /></a>
   </div>
   <ul>
-   <li><a href="/shortlog/0cd96de13884">log</a></li>
-   <li><a href="/graph/0cd96de13884">graph</a></li>
+   <li><a href="/shortlog/0">log</a></li>
+   <li><a href="/graph/0">graph</a></li>
    <li><a href="/tags">tags</a></li>
    <li><a href="/bookmarks">bookmarks</a></li>
    <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
    <li class="active">changeset</li>
-   <li><a href="/raw-rev/0cd96de13884">raw</a></li>
-   <li><a href="/file/0cd96de13884">browse</a></li>
+   <li><a href="/raw-rev/0">raw</a></li>
+   <li><a href="/file/0">browse</a></li>
   </ul>
   <ul>
    
@@ -351,7 +357,10 @@
   <div class="main">
   
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
-  <h3>changeset 0:0cd96de13884   </h3>
+  <h3>
+   changeset 0:<a href="/rev/0cd96de13884">0cd96de13884</a>
+   
+  </h3>
   
   <form class="search" action="/log">
   
@@ -442,7 +451,7 @@
 
 revision
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'raw-rev/0'
+  $ get-with-headers.py localhost:$HGPORT 'raw-rev/0'
   200 Script output follows
   
   
@@ -477,7 +486,7 @@
   $ hg parents --template "{node|short}\n" -r tip a
   0cd96de13884
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'diff/tip/a'
+  $ get-with-headers.py localhost:$HGPORT 'diff/tip/a'
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -499,24 +508,24 @@
   <img src="/static/hglogo.png" alt="mercurial" /></a>
   </div>
   <ul>
-  <li><a href="/shortlog/559edbd9ed20">log</a></li>
-  <li><a href="/graph/559edbd9ed20">graph</a></li>
+  <li><a href="/shortlog/tip">log</a></li>
+  <li><a href="/graph/tip">graph</a></li>
   <li><a href="/tags">tags</a></li>
   <li><a href="/bookmarks">bookmarks</a></li>
   <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
-  <li><a href="/rev/559edbd9ed20">changeset</a></li>
-  <li><a href="/file/559edbd9ed20">browse</a></li>
+  <li><a href="/rev/tip">changeset</a></li>
+  <li><a href="/file/tip">browse</a></li>
   </ul>
   <ul>
-  <li><a href="/file/559edbd9ed20/a">file</a></li>
+  <li><a href="/file/tip/a">file</a></li>
   <li><a href="/file/tip/a">latest</a></li>
   <li class="active">diff</li>
-  <li><a href="/comparison/559edbd9ed20/a">comparison</a></li>
-  <li><a href="/annotate/559edbd9ed20/a">annotate</a></li>
-  <li><a href="/log/559edbd9ed20/a">file log</a></li>
-  <li><a href="/raw-file/559edbd9ed20/a">raw</a></li>
+  <li><a href="/comparison/tip/a">comparison</a></li>
+  <li><a href="/annotate/tip/a">annotate</a></li>
+  <li><a href="/log/tip/a">file log</a></li>
+  <li><a href="/raw-file/tip/a">raw</a></li>
   </ul>
   <ul>
   <li><a href="/help">help</a></li>
@@ -525,7 +534,10 @@
   
   <div class="main">
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
-  <h3>diff a @ 1:559edbd9ed20</h3>
+  <h3>
+   diff a @ 1:<a href="/rev/559edbd9ed20">559edbd9ed20</a>
+   <span class="tag">tip</span> 
+  </h3>
   
   <form class="search" action="/log">
   <p></p>
@@ -580,7 +592,7 @@
   $ hg log --template "{rev}:{node|short}\n" -r 0
   0:0cd96de13884
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'comparison/0/a'
+  $ get-with-headers.py localhost:$HGPORT 'comparison/0/a'
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -602,24 +614,24 @@
   <img src="/static/hglogo.png" alt="mercurial" /></a>
   </div>
   <ul>
-  <li><a href="/shortlog/0cd96de13884">log</a></li>
-  <li><a href="/graph/0cd96de13884">graph</a></li>
+  <li><a href="/shortlog/0">log</a></li>
+  <li><a href="/graph/0">graph</a></li>
   <li><a href="/tags">tags</a></li>
   <li><a href="/bookmarks">bookmarks</a></li>
   <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
-  <li><a href="/rev/0cd96de13884">changeset</a></li>
-  <li><a href="/file/0cd96de13884">browse</a></li>
+  <li><a href="/rev/0">changeset</a></li>
+  <li><a href="/file/0">browse</a></li>
   </ul>
   <ul>
-  <li><a href="/file/0cd96de13884/a">file</a></li>
+  <li><a href="/file/0/a">file</a></li>
   <li><a href="/file/tip/a">latest</a></li>
-  <li><a href="/diff/0cd96de13884/a">diff</a></li>
+  <li><a href="/diff/0/a">diff</a></li>
   <li class="active">comparison</li>
-  <li><a href="/annotate/0cd96de13884/a">annotate</a></li>
-  <li><a href="/log/0cd96de13884/a">file log</a></li>
-  <li><a href="/raw-file/0cd96de13884/a">raw</a></li>
+  <li><a href="/annotate/0/a">annotate</a></li>
+  <li><a href="/log/0/a">file log</a></li>
+  <li><a href="/raw-file/0/a">raw</a></li>
   </ul>
   <ul>
   <li><a href="/help">help</a></li>
@@ -628,7 +640,10 @@
   
   <div class="main">
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
-  <h3>comparison a @ 0:0cd96de13884</h3>
+  <h3>
+   comparison a @ 0:<a href="/rev/0cd96de13884">0cd96de13884</a>
+   
+  </h3>
   
   <form class="search" action="/log">
   <p></p>
@@ -707,7 +722,7 @@
   $ hg log --template "{rev}:{node|short}\n" -r tip
   2:d73db4d812ff
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'comparison/tip/a'
+  $ get-with-headers.py localhost:$HGPORT 'comparison/tip/a'
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -729,24 +744,24 @@
   <img src="/static/hglogo.png" alt="mercurial" /></a>
   </div>
   <ul>
-  <li><a href="/shortlog/d73db4d812ff">log</a></li>
-  <li><a href="/graph/d73db4d812ff">graph</a></li>
+  <li><a href="/shortlog/tip">log</a></li>
+  <li><a href="/graph/tip">graph</a></li>
   <li><a href="/tags">tags</a></li>
   <li><a href="/bookmarks">bookmarks</a></li>
   <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
-  <li><a href="/rev/d73db4d812ff">changeset</a></li>
-  <li><a href="/file/d73db4d812ff">browse</a></li>
+  <li><a href="/rev/tip">changeset</a></li>
+  <li><a href="/file/tip">browse</a></li>
   </ul>
   <ul>
-  <li><a href="/file/d73db4d812ff/a">file</a></li>
+  <li><a href="/file/tip/a">file</a></li>
   <li><a href="/file/tip/a">latest</a></li>
-  <li><a href="/diff/d73db4d812ff/a">diff</a></li>
+  <li><a href="/diff/tip/a">diff</a></li>
   <li class="active">comparison</li>
-  <li><a href="/annotate/d73db4d812ff/a">annotate</a></li>
-  <li><a href="/log/d73db4d812ff/a">file log</a></li>
-  <li><a href="/raw-file/d73db4d812ff/a">raw</a></li>
+  <li><a href="/annotate/tip/a">annotate</a></li>
+  <li><a href="/log/tip/a">file log</a></li>
+  <li><a href="/raw-file/tip/a">raw</a></li>
   </ul>
   <ul>
   <li><a href="/help">help</a></li>
@@ -755,7 +770,10 @@
   
   <div class="main">
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
-  <h3>comparison a @ 2:d73db4d812ff</h3>
+  <h3>
+   comparison a @ 2:<a href="/rev/d73db4d812ff">d73db4d812ff</a>
+   <span class="tag">tip</span> 
+  </h3>
   
   <form class="search" action="/log">
   <p></p>
@@ -836,7 +854,7 @@
   $ hg log --template "{rev}:{node|short}\n" -r tip
   3:20e80271eb7a
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'comparison/tip/a'
+  $ get-with-headers.py localhost:$HGPORT 'comparison/tip/a'
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -858,24 +876,24 @@
   <img src="/static/hglogo.png" alt="mercurial" /></a>
   </div>
   <ul>
-  <li><a href="/shortlog/20e80271eb7a">log</a></li>
-  <li><a href="/graph/20e80271eb7a">graph</a></li>
+  <li><a href="/shortlog/tip">log</a></li>
+  <li><a href="/graph/tip">graph</a></li>
   <li><a href="/tags">tags</a></li>
   <li><a href="/bookmarks">bookmarks</a></li>
   <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
-  <li><a href="/rev/20e80271eb7a">changeset</a></li>
-  <li><a href="/file/20e80271eb7a">browse</a></li>
+  <li><a href="/rev/tip">changeset</a></li>
+  <li><a href="/file/tip">browse</a></li>
   </ul>
   <ul>
-  <li><a href="/file/20e80271eb7a/a">file</a></li>
+  <li><a href="/file/tip/a">file</a></li>
   <li><a href="/file/tip/a">latest</a></li>
-  <li><a href="/diff/20e80271eb7a/a">diff</a></li>
+  <li><a href="/diff/tip/a">diff</a></li>
   <li class="active">comparison</li>
-  <li><a href="/annotate/20e80271eb7a/a">annotate</a></li>
-  <li><a href="/log/20e80271eb7a/a">file log</a></li>
-  <li><a href="/raw-file/20e80271eb7a/a">raw</a></li>
+  <li><a href="/annotate/tip/a">annotate</a></li>
+  <li><a href="/log/tip/a">file log</a></li>
+  <li><a href="/raw-file/tip/a">raw</a></li>
   </ul>
   <ul>
   <li><a href="/help">help</a></li>
@@ -884,7 +902,10 @@
   
   <div class="main">
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
-  <h3>comparison a @ 3:20e80271eb7a</h3>
+  <h3>
+   comparison a @ 3:<a href="/rev/20e80271eb7a">20e80271eb7a</a>
+   <span class="tag">tip</span> 
+  </h3>
   
   <form class="search" action="/log">
   <p></p>
@@ -971,7 +992,7 @@
   $ hg parents --template "{rev}:{node|short}\n" -r tip e
   4:402bea3b0976
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'comparison/tip/e'
+  $ get-with-headers.py localhost:$HGPORT 'comparison/tip/e'
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -993,24 +1014,24 @@
   <img src="/static/hglogo.png" alt="mercurial" /></a>
   </div>
   <ul>
-  <li><a href="/shortlog/41d9fc4a6ae1">log</a></li>
-  <li><a href="/graph/41d9fc4a6ae1">graph</a></li>
+  <li><a href="/shortlog/tip">log</a></li>
+  <li><a href="/graph/tip">graph</a></li>
   <li><a href="/tags">tags</a></li>
   <li><a href="/bookmarks">bookmarks</a></li>
   <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
-  <li><a href="/rev/41d9fc4a6ae1">changeset</a></li>
-  <li><a href="/file/41d9fc4a6ae1">browse</a></li>
+  <li><a href="/rev/tip">changeset</a></li>
+  <li><a href="/file/tip">browse</a></li>
   </ul>
   <ul>
-  <li><a href="/file/41d9fc4a6ae1/e">file</a></li>
+  <li><a href="/file/tip/e">file</a></li>
   <li><a href="/file/tip/e">latest</a></li>
-  <li><a href="/diff/41d9fc4a6ae1/e">diff</a></li>
+  <li><a href="/diff/tip/e">diff</a></li>
   <li class="active">comparison</li>
-  <li><a href="/annotate/41d9fc4a6ae1/e">annotate</a></li>
-  <li><a href="/log/41d9fc4a6ae1/e">file log</a></li>
-  <li><a href="/raw-file/41d9fc4a6ae1/e">raw</a></li>
+  <li><a href="/annotate/tip/e">annotate</a></li>
+  <li><a href="/log/tip/e">file log</a></li>
+  <li><a href="/raw-file/tip/e">raw</a></li>
   </ul>
   <ul>
   <li><a href="/help">help</a></li>
@@ -1019,7 +1040,10 @@
   
   <div class="main">
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
-  <h3>comparison e @ 5:41d9fc4a6ae1</h3>
+  <h3>
+   comparison e @ 5:<a href="/rev/41d9fc4a6ae1">41d9fc4a6ae1</a>
+   <span class="tag">tip</span> 
+  </h3>
   
   <form class="search" action="/log">
   <p></p>
@@ -1094,7 +1118,7 @@
 
 raw revision with diff block numbers
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
   $ cat <<EOF > .hg/hgrc
   > [web]
   > templates = rawdiff
@@ -1114,7 +1138,7 @@
   > EOF
   $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
   $ cat hg.pid >> $DAEMON_PIDS
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'raw-rev/0'
+  $ get-with-headers.py localhost:$HGPORT 'raw-rev/0'
   200 Script output follows
   
   Block: 1
@@ -1131,7 +1155,7 @@
   @@ -0,0 +1,1 @@
   +b
   
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
   $ rm .hg/hgrc rawdiff/map
   $ rmdir rawdiff
   $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
--- a/tests/test-hgweb-empty.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-hgweb-empty.t	Sat Jul 18 17:32:38 2015 -0500
@@ -6,7 +6,7 @@
   $ cd test
   $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
   $ cat hg.pid >> $DAEMON_PIDS
-  $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'shortlog')
+  $ (get-with-headers.py localhost:$HGPORT 'shortlog')
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -33,14 +33,14 @@
   </div>
   <ul>
   <li class="active">log</li>
-  <li><a href="/graph/000000000000">graph</a></li>
+  <li><a href="/graph/tip">graph</a></li>
   <li><a href="/tags">tags</a></li>
   <li><a href="/bookmarks">bookmarks</a></li>
   <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
-  <li><a href="/rev/000000000000">changeset</a></li>
-  <li><a href="/file/000000000000">browse</a></li>
+  <li><a href="/rev/tip">changeset</a></li>
+  <li><a href="/file/tip">browse</a></li>
   </ul>
   <ul>
   
@@ -67,8 +67,8 @@
   </form>
   
   <div class="navigate">
-  <a href="/shortlog/-1?revcount=30">less</a>
-  <a href="/shortlog/-1?revcount=120">more</a>
+  <a href="/shortlog/tip?revcount=30">less</a>
+  <a href="/shortlog/tip?revcount=120">more</a>
   | rev -1: 
   </div>
   
@@ -86,8 +86,8 @@
   </table>
   
   <div class="navigate">
-  <a href="/shortlog/-1?revcount=30">less</a>
-  <a href="/shortlog/-1?revcount=120">more</a>
+  <a href="/shortlog/tip?revcount=30">less</a>
+  <a href="/shortlog/tip?revcount=120">more</a>
   | rev -1: 
   </div>
   
@@ -117,7 +117,7 @@
   
   $ echo babar
   babar
-  $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log')
+  $ (get-with-headers.py localhost:$HGPORT 'log')
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -144,14 +144,14 @@
   </div>
   <ul>
   <li class="active">log</li>
-  <li><a href="/graph/000000000000">graph</a></li>
+  <li><a href="/graph/tip">graph</a></li>
   <li><a href="/tags">tags</a></li>
   <li><a href="/bookmarks">bookmarks</a></li>
   <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
-  <li><a href="/rev/000000000000">changeset</a></li>
-  <li><a href="/file/000000000000">browse</a></li>
+  <li><a href="/rev/tip">changeset</a></li>
+  <li><a href="/file/tip">browse</a></li>
   </ul>
   <ul>
   
@@ -178,8 +178,8 @@
   </form>
   
   <div class="navigate">
-  <a href="/shortlog/-1?revcount=5">less</a>
-  <a href="/shortlog/-1?revcount=20">more</a>
+  <a href="/shortlog/tip?revcount=5">less</a>
+  <a href="/shortlog/tip?revcount=20">more</a>
   | rev -1: 
   </div>
   
@@ -197,8 +197,8 @@
   </table>
   
   <div class="navigate">
-  <a href="/shortlog/-1?revcount=5">less</a>
-  <a href="/shortlog/-1?revcount=20">more</a>
+  <a href="/shortlog/tip?revcount=5">less</a>
+  <a href="/shortlog/tip?revcount=20">more</a>
   | rev -1: 
   </div>
   
@@ -226,7 +226,7 @@
   </body>
   </html>
   
-  $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'graph')
+  $ (get-with-headers.py localhost:$HGPORT 'graph')
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -253,15 +253,15 @@
   <img src="/static/hglogo.png" alt="mercurial" /></a>
   </div>
   <ul>
-  <li><a href="/shortlog/000000000000">log</a></li>
+  <li><a href="/shortlog/tip">log</a></li>
   <li class="active">graph</li>
   <li><a href="/tags">tags</a></li>
   <li><a href="/bookmarks">bookmarks</a></li>
   <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
-  <li><a href="/rev/000000000000">changeset</a></li>
-  <li><a href="/file/000000000000">browse</a></li>
+  <li><a href="/rev/tip">changeset</a></li>
+  <li><a href="/file/tip">browse</a></li>
   </ul>
   <ul>
    <li><a href="/help">help</a></li>
@@ -285,8 +285,8 @@
   </form>
   
   <div class="navigate">
-  <a href="/graph/-1?revcount=30">less</a>
-  <a href="/graph/-1?revcount=120">more</a>
+  <a href="/graph/tip?revcount=30">less</a>
+  <a href="/graph/tip?revcount=120">more</a>
   | rev -1: 
   </div>
   
@@ -355,8 +355,8 @@
   </script>
   
   <div class="navigate">
-  <a href="/graph/-1?revcount=30">less</a>
-  <a href="/graph/-1?revcount=120">more</a>
+  <a href="/graph/tip?revcount=30">less</a>
+  <a href="/graph/tip?revcount=120">more</a>
   | rev -1: 
   </div>
   
@@ -380,7 +380,7 @@
   </body>
   </html>
   
-  $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file')
+  $ (get-with-headers.py localhost:$HGPORT 'file')
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -402,14 +402,14 @@
   <img src="/static/hglogo.png" alt="mercurial" /></a>
   </div>
   <ul>
-  <li><a href="/shortlog/000000000000">log</a></li>
-  <li><a href="/graph/000000000000">graph</a></li>
+  <li><a href="/shortlog/tip">log</a></li>
+  <li><a href="/graph/tip">graph</a></li>
   <li><a href="/tags">tags</a></li>
   <li><a href="/bookmarks">bookmarks</a></li>
   <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
-  <li><a href="/rev/000000000000">changeset</a></li>
+  <li><a href="/rev/tip">changeset</a></li>
   <li class="active">browse</li>
   </ul>
   <ul>
@@ -422,7 +422,10 @@
   
   <div class="main">
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
-  <h3>directory / @ -1:000000000000 <span class="tag">tip</span> </h3>
+  <h3>
+   directory / @ -1:<a href="/rev/000000000000">000000000000</a>
+   <span class="tag">tip</span> 
+  </h3>
   
   <form class="search" action="/log">
   
@@ -441,7 +444,7 @@
   </thead>
   <tbody class="stripes2">
   <tr class="fileline">
-    <td class="name"><a href="/file/000000000000/">[up]</a></td>
+    <td class="name"><a href="/file/tip/">[up]</a></td>
     <td class="size"></td>
     <td class="permissions">drwxr-xr-x</td>
   </tr>
--- a/tests/test-hgweb-filelog.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-hgweb-filelog.t	Sat Jul 18 17:32:38 2015 -0500
@@ -8,8 +8,13 @@
   $ echo a > a
   $ hg ci -Am "first a"
   adding a
+  $ hg tag -r 1 a-tag
+  $ hg bookmark -r 1 a-bookmark
   $ hg rm a
   $ hg ci -m "del a"
+  $ hg branch a-branch
+  marked working directory as branch a-branch
+  (branches are permanent and global, did you want a bookmark?)
   $ echo b > a
   $ hg ci -Am "second a"
   adding a
@@ -20,69 +25,86 @@
   $ echo c >> c
   $ hg ci -m "change c"
   $ hg log -p
-  changeset:   6:b7682196df1c
+  changeset:   7:46c1a66bd8fc
+  branch:      a-branch
   tag:         tip
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     change c
   
-  diff -r 1a6696706df2 -r b7682196df1c c
+  diff -r c9637d3cc8ef -r 46c1a66bd8fc c
   --- a/c	Thu Jan 01 00:00:00 1970 +0000
   +++ b/c	Thu Jan 01 00:00:00 1970 +0000
   @@ -1,1 +1,2 @@
    b
   +c
   
-  changeset:   5:1a6696706df2
+  changeset:   6:c9637d3cc8ef
+  branch:      a-branch
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     mv b
   
-  diff -r 52e848cdcd88 -r 1a6696706df2 b
+  diff -r 958bd88be4eb -r c9637d3cc8ef b
   --- a/b	Thu Jan 01 00:00:00 1970 +0000
   +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
   @@ -1,1 +0,0 @@
   -b
-  diff -r 52e848cdcd88 -r 1a6696706df2 c
+  diff -r 958bd88be4eb -r c9637d3cc8ef c
   --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   +++ b/c	Thu Jan 01 00:00:00 1970 +0000
   @@ -0,0 +1,1 @@
   +b
   
-  changeset:   4:52e848cdcd88
+  changeset:   5:958bd88be4eb
+  branch:      a-branch
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     del2 a
   
-  diff -r 01de2d66a28d -r 52e848cdcd88 a
+  diff -r 3f41bc784e7e -r 958bd88be4eb a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
   @@ -1,1 +0,0 @@
   -b
   
-  changeset:   3:01de2d66a28d
+  changeset:   4:3f41bc784e7e
+  branch:      a-branch
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     second a
   
-  diff -r be3ebcc91739 -r 01de2d66a28d a
+  diff -r 292258f86fdf -r 3f41bc784e7e a
   --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
   @@ -0,0 +1,1 @@
   +b
   
-  changeset:   2:be3ebcc91739
+  changeset:   3:292258f86fdf
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     del a
   
-  diff -r 5ed941583260 -r be3ebcc91739 a
+  diff -r 94c9dd5ca9b4 -r 292258f86fdf a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
   @@ -1,1 +0,0 @@
   -a
   
+  changeset:   2:94c9dd5ca9b4
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     Added tag a-tag for changeset 5ed941583260
+  
+  diff -r 5ed941583260 -r 94c9dd5ca9b4 .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 @@
+  +5ed941583260248620985524192fdc382ef57c36 a-tag
+  
   changeset:   1:5ed941583260
+  bookmark:    a-bookmark
+  tag:         a-tag
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     first a
@@ -109,7 +131,7 @@
 
 tip - two revisions
 
-  $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log/tip/a')
+  $ (get-with-headers.py localhost:$HGPORT 'log/tip/a')
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -135,29 +157,29 @@
   <img src="/static/hglogo.png" alt="mercurial" /></a>
   </div>
   <ul>
-  <li><a href="/shortlog/01de2d66a28d">log</a></li>
-  <li><a href="/graph/01de2d66a28d">graph</a></li>
+  <li><a href="/shortlog/tip">log</a></li>
+  <li><a href="/graph/tip">graph</a></li>
   <li><a href="/tags">tags</a></li>
   <li><a href="/bookmarks">bookmarks</a></li>
   <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
-  <li><a href="/rev/01de2d66a28d">changeset</a></li>
-  <li><a href="/file/01de2d66a28d">browse</a></li>
+  <li><a href="/rev/tip">changeset</a></li>
+  <li><a href="/file/tip">browse</a></li>
   </ul>
   <ul>
-  <li><a href="/file/01de2d66a28d/a">file</a></li>
-  <li><a href="/diff/01de2d66a28d/a">diff</a></li>
-  <li><a href="/comparison/01de2d66a28d/a">comparison</a></li>
-  <li><a href="/annotate/01de2d66a28d/a">annotate</a></li>
+  <li><a href="/file/tip/a">file</a></li>
+  <li><a href="/diff/tip/a">diff</a></li>
+  <li><a href="/comparison/tip/a">comparison</a></li>
+  <li><a href="/annotate/tip/a">annotate</a></li>
   <li class="active">file log</li>
-  <li><a href="/raw-file/01de2d66a28d/a">raw</a></li>
+  <li><a href="/raw-file/tip/a">raw</a></li>
   </ul>
   <ul>
   <li><a href="/help">help</a></li>
   </ul>
   <div class="atom-logo">
-  <a href="/atom-log/01de2d66a28d/a" title="subscribe to atom feed">
+  <a href="/atom-log/3f41bc784e7e/a" title="subscribe to atom feed">
   <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
   </a>
   </div>
@@ -175,8 +197,8 @@
   </form>
   
   <div class="navigate">
-  <a href="/log/01de2d66a28d/a?revcount=30">less</a>
-  <a href="/log/01de2d66a28d/a?revcount=120">more</a>
+  <a href="/log/tip/a?revcount=30">less</a>
+  <a href="/log/tip/a?revcount=120">more</a>
   | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
   
   <table class="bigtable">
@@ -191,20 +213,26 @@
    <tr>
     <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
     <td class="author">test</td>
-    <td class="description"><a href="/rev/01de2d66a28d">second a</a></td>
+    <td class="description">
+     <a href="/rev/3f41bc784e7e">second a</a>
+     <span class="branchname">a-branch</span> 
+    </td>
    </tr>
    <tr>
     <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
     <td class="author">test</td>
-    <td class="description"><a href="/rev/5ed941583260">first a</a></td>
+    <td class="description">
+     <a href="/rev/5ed941583260">first a</a>
+     <span class="tag">a-tag</span> <span class="tag">a-bookmark</span> 
+    </td>
    </tr>
   
   </tbody>
   </table>
   
   <div class="navigate">
-  <a href="/log/01de2d66a28d/a?revcount=30">less</a>
-  <a href="/log/01de2d66a28d/a?revcount=120">more</a>
+  <a href="/log/tip/a?revcount=30">less</a>
+  <a href="/log/tip/a?revcount=120">more</a>
   | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> 
   </div>
   
@@ -220,7 +248,7 @@
 
 second version - two revisions
 
-  $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log/3/a')
+  $ (get-with-headers.py localhost:$HGPORT 'log/4/a')
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -246,29 +274,29 @@
   <img src="/static/hglogo.png" alt="mercurial" /></a>
   </div>
   <ul>
-  <li><a href="/shortlog/01de2d66a28d">log</a></li>
-  <li><a href="/graph/01de2d66a28d">graph</a></li>
+  <li><a href="/shortlog/4">log</a></li>
+  <li><a href="/graph/4">graph</a></li>
   <li><a href="/tags">tags</a></li>
   <li><a href="/bookmarks">bookmarks</a></li>
   <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
-  <li><a href="/rev/01de2d66a28d">changeset</a></li>
-  <li><a href="/file/01de2d66a28d">browse</a></li>
+  <li><a href="/rev/4">changeset</a></li>
+  <li><a href="/file/4">browse</a></li>
   </ul>
   <ul>
-  <li><a href="/file/01de2d66a28d/a">file</a></li>
-  <li><a href="/diff/01de2d66a28d/a">diff</a></li>
-  <li><a href="/comparison/01de2d66a28d/a">comparison</a></li>
-  <li><a href="/annotate/01de2d66a28d/a">annotate</a></li>
+  <li><a href="/file/4/a">file</a></li>
+  <li><a href="/diff/4/a">diff</a></li>
+  <li><a href="/comparison/4/a">comparison</a></li>
+  <li><a href="/annotate/4/a">annotate</a></li>
   <li class="active">file log</li>
-  <li><a href="/raw-file/01de2d66a28d/a">raw</a></li>
+  <li><a href="/raw-file/4/a">raw</a></li>
   </ul>
   <ul>
   <li><a href="/help">help</a></li>
   </ul>
   <div class="atom-logo">
-  <a href="/atom-log/01de2d66a28d/a" title="subscribe to atom feed">
+  <a href="/atom-log/3f41bc784e7e/a" title="subscribe to atom feed">
   <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
   </a>
   </div>
@@ -286,8 +314,8 @@
   </form>
   
   <div class="navigate">
-  <a href="/log/01de2d66a28d/a?revcount=30">less</a>
-  <a href="/log/01de2d66a28d/a?revcount=120">more</a>
+  <a href="/log/4/a?revcount=30">less</a>
+  <a href="/log/4/a?revcount=120">more</a>
   | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
   
   <table class="bigtable">
@@ -302,20 +330,26 @@
    <tr>
     <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
     <td class="author">test</td>
-    <td class="description"><a href="/rev/01de2d66a28d">second a</a></td>
+    <td class="description">
+     <a href="/rev/3f41bc784e7e">second a</a>
+     <span class="branchname">a-branch</span> 
+    </td>
    </tr>
    <tr>
     <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
     <td class="author">test</td>
-    <td class="description"><a href="/rev/5ed941583260">first a</a></td>
+    <td class="description">
+     <a href="/rev/5ed941583260">first a</a>
+     <span class="tag">a-tag</span> <span class="tag">a-bookmark</span> 
+    </td>
    </tr>
   
   </tbody>
   </table>
   
   <div class="navigate">
-  <a href="/log/01de2d66a28d/a?revcount=30">less</a>
-  <a href="/log/01de2d66a28d/a?revcount=120">more</a>
+  <a href="/log/4/a?revcount=30">less</a>
+  <a href="/log/4/a?revcount=120">more</a>
   | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> 
   </div>
   
@@ -331,7 +365,7 @@
 
 first deleted - one revision
 
-  $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log/2/a')
+  $ (get-with-headers.py localhost:$HGPORT 'log/3/a')
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -357,23 +391,23 @@
   <img src="/static/hglogo.png" alt="mercurial" /></a>
   </div>
   <ul>
-  <li><a href="/shortlog/5ed941583260">log</a></li>
-  <li><a href="/graph/5ed941583260">graph</a></li>
+  <li><a href="/shortlog/3">log</a></li>
+  <li><a href="/graph/3">graph</a></li>
   <li><a href="/tags">tags</a></li>
   <li><a href="/bookmarks">bookmarks</a></li>
   <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
-  <li><a href="/rev/5ed941583260">changeset</a></li>
-  <li><a href="/file/5ed941583260">browse</a></li>
+  <li><a href="/rev/3">changeset</a></li>
+  <li><a href="/file/3">browse</a></li>
   </ul>
   <ul>
-  <li><a href="/file/5ed941583260/a">file</a></li>
-  <li><a href="/diff/5ed941583260/a">diff</a></li>
-  <li><a href="/comparison/5ed941583260/a">comparison</a></li>
-  <li><a href="/annotate/5ed941583260/a">annotate</a></li>
+  <li><a href="/file/3/a">file</a></li>
+  <li><a href="/diff/3/a">diff</a></li>
+  <li><a href="/comparison/3/a">comparison</a></li>
+  <li><a href="/annotate/3/a">annotate</a></li>
   <li class="active">file log</li>
-  <li><a href="/raw-file/5ed941583260/a">raw</a></li>
+  <li><a href="/raw-file/3/a">raw</a></li>
   </ul>
   <ul>
   <li><a href="/help">help</a></li>
@@ -397,8 +431,8 @@
   </form>
   
   <div class="navigate">
-  <a href="/log/5ed941583260/a?revcount=30">less</a>
-  <a href="/log/5ed941583260/a?revcount=120">more</a>
+  <a href="/log/3/a?revcount=30">less</a>
+  <a href="/log/3/a?revcount=120">more</a>
   | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
   
   <table class="bigtable">
@@ -413,15 +447,18 @@
    <tr>
     <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
     <td class="author">test</td>
-    <td class="description"><a href="/rev/5ed941583260">first a</a></td>
+    <td class="description">
+     <a href="/rev/5ed941583260">first a</a>
+     <span class="tag">a-tag</span> <span class="tag">a-bookmark</span> 
+    </td>
    </tr>
   
   </tbody>
   </table>
   
   <div class="navigate">
-  <a href="/log/5ed941583260/a?revcount=30">less</a>
-  <a href="/log/5ed941583260/a?revcount=120">more</a>
+  <a href="/log/3/a?revcount=30">less</a>
+  <a href="/log/3/a?revcount=120">more</a>
   | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> 
   </div>
   
@@ -437,7 +474,7 @@
 
 first version - one revision
 
-  $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log/1/a')
+  $ (get-with-headers.py localhost:$HGPORT 'log/1/a')
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -463,23 +500,23 @@
   <img src="/static/hglogo.png" alt="mercurial" /></a>
   </div>
   <ul>
-  <li><a href="/shortlog/5ed941583260">log</a></li>
-  <li><a href="/graph/5ed941583260">graph</a></li>
+  <li><a href="/shortlog/1">log</a></li>
+  <li><a href="/graph/1">graph</a></li>
   <li><a href="/tags">tags</a></li>
   <li><a href="/bookmarks">bookmarks</a></li>
   <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
-  <li><a href="/rev/5ed941583260">changeset</a></li>
-  <li><a href="/file/5ed941583260">browse</a></li>
+  <li><a href="/rev/1">changeset</a></li>
+  <li><a href="/file/1">browse</a></li>
   </ul>
   <ul>
-  <li><a href="/file/5ed941583260/a">file</a></li>
-  <li><a href="/diff/5ed941583260/a">diff</a></li>
-  <li><a href="/comparison/5ed941583260/a">comparison</a></li>
-  <li><a href="/annotate/5ed941583260/a">annotate</a></li>
+  <li><a href="/file/1/a">file</a></li>
+  <li><a href="/diff/1/a">diff</a></li>
+  <li><a href="/comparison/1/a">comparison</a></li>
+  <li><a href="/annotate/1/a">annotate</a></li>
   <li class="active">file log</li>
-  <li><a href="/raw-file/5ed941583260/a">raw</a></li>
+  <li><a href="/raw-file/1/a">raw</a></li>
   </ul>
   <ul>
   <li><a href="/help">help</a></li>
@@ -503,8 +540,8 @@
   </form>
   
   <div class="navigate">
-  <a href="/log/5ed941583260/a?revcount=30">less</a>
-  <a href="/log/5ed941583260/a?revcount=120">more</a>
+  <a href="/log/1/a?revcount=30">less</a>
+  <a href="/log/1/a?revcount=120">more</a>
   | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
   
   <table class="bigtable">
@@ -519,15 +556,18 @@
    <tr>
     <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
     <td class="author">test</td>
-    <td class="description"><a href="/rev/5ed941583260">first a</a></td>
+    <td class="description">
+     <a href="/rev/5ed941583260">first a</a>
+     <span class="tag">a-tag</span> <span class="tag">a-bookmark</span> 
+    </td>
    </tr>
   
   </tbody>
   </table>
   
   <div class="navigate">
-  <a href="/log/5ed941583260/a?revcount=30">less</a>
-  <a href="/log/5ed941583260/a?revcount=120">more</a>
+  <a href="/log/1/a?revcount=30">less</a>
+  <a href="/log/1/a?revcount=120">more</a>
   | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> 
   </div>
   
@@ -543,7 +583,7 @@
 
 before addition - error
 
-  $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log/0/a')
+  $ (get-with-headers.py localhost:$HGPORT 'log/0/a')
   404 Not Found
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -609,7 +649,7 @@
 
 should show base link, use spartan because it shows it
 
-  $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log/tip/c?style=spartan')
+  $ (get-with-headers.py localhost:$HGPORT 'log/tip/c?style=spartan')
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
@@ -634,8 +674,8 @@
   <a href="/graph?style=spartan">graph</a>
   <a href="/tags?style=spartan">tags</a>
   <a href="/branches?style=spartan">branches</a>
-  <a href="/file/b7682196df1c/c?style=spartan">file</a>
-  <a href="/annotate/b7682196df1c/c?style=spartan">annotate</a>
+  <a href="/file/tip/c?style=spartan">file</a>
+  <a href="/annotate/tip/c?style=spartan">annotate</a>
   <a href="/help?style=spartan">help</a>
   <a type="application/rss+xml" href="/rss-log/tip/c">rss</a>
   <a type="application/atom+xml" href="/atom-log/tip/c" title="Atom feed for test:c">atom</a>
@@ -643,19 +683,19 @@
   
   <h2><a href="/">Mercurial</a>  / c revision history</h2>
   
-  <p>navigate: <small class="navigate"><a href="/log/1a6696706df2/c?style=spartan">(0)</a> <a href="/log/tip/c?style=spartan">tip</a> </small></p>
+  <p>navigate: <small class="navigate"><a href="/log/c9637d3cc8ef/c?style=spartan">(0)</a> <a href="/log/tip/c?style=spartan">tip</a> </small></p>
   
   <table class="logEntry parity0">
    <tr>
     <th class="label"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>:</th>
-    <th class="firstline"><a href="/rev/b7682196df1c?style=spartan">change c</a></th>
+    <th class="firstline"><a href="/rev/46c1a66bd8fc?style=spartan">change c</a></th>
    </tr>
    <tr>
     <th class="revision">revision 1:</th>
     <td class="node">
-     <a href="/file/b7682196df1c/c?style=spartan">b7682196df1c</a>
-     <a href="/diff/b7682196df1c/c?style=spartan">(diff)</a>
-     <a href="/annotate/b7682196df1c/c?style=spartan">(annotate)</a>
+     <a href="/file/46c1a66bd8fc/c?style=spartan">46c1a66bd8fc</a>
+     <a href="/diff/46c1a66bd8fc/c?style=spartan">(diff)</a>
+     <a href="/annotate/46c1a66bd8fc/c?style=spartan">(annotate)</a>
     </td>
    </tr>
    
@@ -673,14 +713,14 @@
   <table class="logEntry parity1">
    <tr>
     <th class="label"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>:</th>
-    <th class="firstline"><a href="/rev/1a6696706df2?style=spartan">mv b</a></th>
+    <th class="firstline"><a href="/rev/c9637d3cc8ef?style=spartan">mv b</a></th>
    </tr>
    <tr>
     <th class="revision">revision 0:</th>
     <td class="node">
-     <a href="/file/1a6696706df2/c?style=spartan">1a6696706df2</a>
-     <a href="/diff/1a6696706df2/c?style=spartan">(diff)</a>
-     <a href="/annotate/1a6696706df2/c?style=spartan">(annotate)</a>
+     <a href="/file/c9637d3cc8ef/c?style=spartan">c9637d3cc8ef</a>
+     <a href="/diff/c9637d3cc8ef/c?style=spartan">(diff)</a>
+     <a href="/annotate/c9637d3cc8ef/c?style=spartan">(annotate)</a>
     </td>
    </tr>
    
@@ -718,7 +758,7 @@
 
 rss log
 
-  $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'rss-log/tip/a')
+  $ (get-with-headers.py localhost:$HGPORT 'rss-log/tip/a')
   200 Script output follows
   
   <?xml version="1.0" encoding="ascii"?>
@@ -731,7 +771,7 @@
       <description>a revision history</description>
       <item>
       <title>second a</title>
-      <link>http://*:$HGPORT/log01de2d66a28d/a</link> (glob)
+      <link>http://*:$HGPORT/log3f41bc784e7e/a</link> (glob)
       <description><![CDATA[second a]]></description>
       <author>&#116;&#101;&#115;&#116;</author>
       <pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate>
@@ -749,7 +789,7 @@
 
 atom log
 
-  $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'atom-log/tip/a')
+  $ (get-with-headers.py localhost:$HGPORT 'atom-log/tip/a')
   200 Script output follows
   
   <?xml version="1.0" encoding="ascii"?>
@@ -760,9 +800,9 @@
    <updated>1970-01-01T00:00:00+00:00</updated>
   
    <entry>
-    <title>second a</title>
-    <id>http://*:$HGPORT/#changeset-01de2d66a28df5549090991dccda788726948517</id> (glob)
-    <link href="http://*:$HGPORT/rev/01de2d66a28d"/> (glob)
+    <title>[a-branch] second a</title>
+    <id>http://*:$HGPORT/#changeset-3f41bc784e7e73035c6d47112c6cc7efb673adf8</id> (glob)
+    <link href="http://*:$HGPORT/rev/3f41bc784e7e"/> (glob)
     <author>
      <name>test</name>
      <email>&#116;&#101;&#115;&#116;</email>
@@ -773,11 +813,11 @@
   	<table xmlns="http://www.w3.org/1999/xhtml">
   	<tr>
   		<th style="text-align:left;">changeset</th>
-  		<td>01de2d66a28d</td>
+  		<td>3f41bc784e7e</td>
                 </tr>
                 <tr>
                                 <th style="text-align:left;">branch</th>
-                                <td></td>
+                                <td>a-branch</td>
                 </tr>
                 <tr>
                                 <th style="text-align:left;">bookmark</th>
@@ -824,11 +864,11 @@
                 </tr>
                 <tr>
                                 <th style="text-align:left;">bookmark</th>
-  		<td></td>
+  		<td>a-bookmark</td>
   	</tr>
   	<tr>
   		<th style="text-align:left;">tag</th>
-  		<td></td>
+  		<td>a-tag</td>
   	</tr>
   	<tr>
   		<th style="text-align:left;">user</th>
--- a/tests/test-hgweb-json.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-hgweb-json.t	Sat Jul 18 17:32:38 2015 -0500
@@ -2,7 +2,7 @@
 #require serve
 
   $ request() {
-  >   $TESTDIR/get-with-headers.py --json localhost:$HGPORT "$1"
+  >   get-with-headers.py --json localhost:$HGPORT "$1"
   > }
 
   $ hg init test
--- a/tests/test-hgweb-raw.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-hgweb-raw.t	Sat Jul 18 17:32:38 2015 -0500
@@ -17,9 +17,9 @@
   $ hg serve -p $HGPORT -A access.log -E error.log -d --pid-file=hg.pid
 
   $ cat hg.pid >> $DAEMON_PIDS
-  $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '?f=bf0ff59095c9;file=sub/some%20text%25.txt;style=raw' content-type content-length content-disposition) >getoutput.txt
+  $ (get-with-headers.py localhost:$HGPORT '?f=bf0ff59095c9;file=sub/some%20text%25.txt;style=raw' content-type content-length content-disposition) >getoutput.txt
 
-  $ "$TESTDIR/killdaemons.py" hg.pid
+  $ killdaemons.py hg.pid
 
   $ cat getoutput.txt
   200 Script output follows
@@ -39,8 +39,8 @@
   > --config web.guessmime=True
 
   $ cat hg.pid >> $DAEMON_PIDS
-  $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '?f=bf0ff59095c9;file=sub/some%20text%25.txt;style=raw' content-type content-length content-disposition) >getoutput.txt
-  $ "$TESTDIR/killdaemons.py" hg.pid
+  $ (get-with-headers.py localhost:$HGPORT '?f=bf0ff59095c9;file=sub/some%20text%25.txt;style=raw' content-type content-length content-disposition) >getoutput.txt
+  $ killdaemons.py hg.pid
 
   $ cat getoutput.txt
   200 Script output follows
--- a/tests/test-hgweb-removed.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-hgweb-removed.t	Sat Jul 18 17:32:38 2015 -0500
@@ -17,7 +17,7 @@
 
 revision
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'rev/tip'
+  $ get-with-headers.py localhost:$HGPORT 'rev/tip'
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -38,16 +38,16 @@
   <img src="/static/hglogo.png" alt="mercurial" /></a>
   </div>
   <ul>
-   <li><a href="/shortlog/c78f6c5cbea9">log</a></li>
-   <li><a href="/graph/c78f6c5cbea9">graph</a></li>
+   <li><a href="/shortlog/tip">log</a></li>
+   <li><a href="/graph/tip">graph</a></li>
    <li><a href="/tags">tags</a></li>
    <li><a href="/bookmarks">bookmarks</a></li>
    <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
    <li class="active">changeset</li>
-   <li><a href="/raw-rev/c78f6c5cbea9">raw</a></li>
-   <li><a href="/file/c78f6c5cbea9">browse</a></li>
+   <li><a href="/raw-rev/tip">raw</a></li>
+   <li><a href="/file/tip">browse</a></li>
   </ul>
   <ul>
    
@@ -60,7 +60,10 @@
   <div class="main">
   
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
-  <h3>changeset 1:c78f6c5cbea9  <span class="tag">tip</span>  </h3>
+  <h3>
+   changeset 1:<a href="/rev/c78f6c5cbea9">c78f6c5cbea9</a>
+   <span class="tag">tip</span> 
+  </h3>
   
   <form class="search" action="/log">
   
@@ -137,7 +140,7 @@
 
 diff removed file
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'diff/tip/a'
+  $ get-with-headers.py localhost:$HGPORT 'diff/tip/a'
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -159,24 +162,24 @@
   <img src="/static/hglogo.png" alt="mercurial" /></a>
   </div>
   <ul>
-  <li><a href="/shortlog/c78f6c5cbea9">log</a></li>
-  <li><a href="/graph/c78f6c5cbea9">graph</a></li>
+  <li><a href="/shortlog/tip">log</a></li>
+  <li><a href="/graph/tip">graph</a></li>
   <li><a href="/tags">tags</a></li>
   <li><a href="/bookmarks">bookmarks</a></li>
   <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
-  <li><a href="/rev/c78f6c5cbea9">changeset</a></li>
-  <li><a href="/file/c78f6c5cbea9">browse</a></li>
+  <li><a href="/rev/tip">changeset</a></li>
+  <li><a href="/file/tip">browse</a></li>
   </ul>
   <ul>
-  <li><a href="/file/c78f6c5cbea9/a">file</a></li>
+  <li><a href="/file/tip/a">file</a></li>
   <li><a href="/file/tip/a">latest</a></li>
   <li class="active">diff</li>
-  <li><a href="/comparison/c78f6c5cbea9/a">comparison</a></li>
-  <li><a href="/annotate/c78f6c5cbea9/a">annotate</a></li>
-  <li><a href="/log/c78f6c5cbea9/a">file log</a></li>
-  <li><a href="/raw-file/c78f6c5cbea9/a">raw</a></li>
+  <li><a href="/comparison/tip/a">comparison</a></li>
+  <li><a href="/annotate/tip/a">annotate</a></li>
+  <li><a href="/log/tip/a">file log</a></li>
+  <li><a href="/raw-file/tip/a">raw</a></li>
   </ul>
   <ul>
   <li><a href="/help">help</a></li>
@@ -185,7 +188,10 @@
   
   <div class="main">
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
-  <h3>diff a @ 1:c78f6c5cbea9</h3>
+  <h3>
+   diff a @ 1:<a href="/rev/c78f6c5cbea9">c78f6c5cbea9</a>
+   <span class="tag">tip</span> 
+  </h3>
   
   <form class="search" action="/log">
   <p></p>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-hgweb-symrev.t	Sat Jul 18 17:32:38 2015 -0500
@@ -0,0 +1,1034 @@
+#require serve
+
+Test symbolic revision usage in links produced by hgweb pages. There are
+multiple issues related to this:
+- issue2296
+- issue2826
+- issue3594
+- issue3634
+
+Set up the repo
+
+  $ hg init test
+  $ cd test
+  $ echo 0 > foo
+  $ mkdir dir
+  $ echo 0 > dir/bar
+  $ hg ci -Am 'first'
+  adding dir/bar
+  adding foo
+  $ echo 1 >> foo
+  $ hg ci -m 'second'
+  $ echo 2 >> foo
+  $ hg ci -m 'third'
+  $ hg bookmark -r1 xyzzy
+
+  $ hg log -G --template '{rev}:{node|short} {tags} {bookmarks}\n'
+  @  2:9d8c40cba617 tip
+  |
+  o  1:a7c1559b7bba  xyzzy
+  |
+  o  0:43c799df6e75
+  
+  $ hg serve --config web.allow_archive=zip -n test -p $HGPORT -d --pid-file=hg.pid -E errors.log
+  $ cat hg.pid >> $DAEMON_PIDS
+
+  $ REVLINKS='href=[^>]+(rev=|/)(43c799df6e75|0|a7c1559b7bba|1|xyzzy|9d8c40cba617|2|tip|default)'
+
+(De)referencing symbolic revisions (paper)
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog?style=paper' | egrep $REVLINKS
+  <li><a href="/graph/tip?style=paper">graph</a></li>
+  <li><a href="/rev/tip?style=paper">changeset</a></li>
+  <li><a href="/file/tip?style=paper">browse</a></li>
+  <a href="/archive/tip.zip">zip</a>
+  <a href="/shortlog/tip?revcount=30&style=paper">less</a>
+  <a href="/shortlog/tip?revcount=120&style=paper">more</a>
+  | rev 2: <a href="/shortlog/43c799df6e75?style=paper">(0)</a> <a href="/shortlog/tip?style=paper">tip</a> 
+     <a href="/rev/9d8c40cba617?style=paper">third</a>
+     <a href="/rev/a7c1559b7bba?style=paper">second</a>
+     <a href="/rev/43c799df6e75?style=paper">first</a>
+  <a href="/shortlog/tip?revcount=30&style=paper">less</a>
+  <a href="/shortlog/tip?revcount=120&style=paper">more</a>
+  | rev 2: <a href="/shortlog/43c799df6e75?style=paper">(0)</a> <a href="/shortlog/tip?style=paper">tip</a> 
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph?style=paper' | egrep $REVLINKS
+  <li><a href="/shortlog/tip?style=paper">log</a></li>
+  <li><a href="/rev/tip?style=paper">changeset</a></li>
+  <li><a href="/file/tip?style=paper">browse</a></li>
+  <a href="/graph/tip?revcount=30&style=paper">less</a>
+  <a href="/graph/tip?revcount=120&style=paper">more</a>
+  | rev 2: <a href="/graph/43c799df6e75?style=paper">(0)</a> <a href="/graph/tip?style=paper">tip</a> 
+  <a href="/graph/tip?revcount=30&style=paper">less</a>
+  <a href="/graph/tip?revcount=120&style=paper">more</a>
+  | rev 2: <a href="/graph/43c799df6e75?style=paper">(0)</a> <a href="/graph/tip?style=paper">tip</a> 
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file?style=paper' | egrep $REVLINKS
+  <li><a href="/shortlog/tip?style=paper">log</a></li>
+  <li><a href="/graph/tip?style=paper">graph</a></li>
+  <li><a href="/rev/tip?style=paper">changeset</a></li>
+  <a href="/archive/tip.zip">zip</a>
+   directory / @ 2:<a href="/rev/9d8c40cba617?style=paper">9d8c40cba617</a>
+    <td class="name"><a href="/file/tip/?style=paper">[up]</a></td>
+  <a href="/file/tip/dir?style=paper">
+  <a href="/file/tip/dir/?style=paper">
+  <a href="/file/tip/foo?style=paper">
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'branches?style=paper' | egrep $REVLINKS
+  <a href="/shortlog/default?style=paper" class="open">
+  <a href="/shortlog/9d8c40cba617?style=paper" class="open">
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'tags?style=paper' | egrep $REVLINKS
+  <a href="/rev/tip?style=paper">
+  <a href="/rev/9d8c40cba617?style=paper">
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'bookmarks?style=paper' | egrep $REVLINKS
+  <a href="/rev/xyzzy?style=paper">
+  <a href="/rev/a7c1559b7bba?style=paper">
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog?style=paper&rev=all()' | egrep $REVLINKS
+     <a href="/rev/9d8c40cba617?style=paper">third</a>
+     <a href="/rev/a7c1559b7bba?style=paper">second</a>
+     <a href="/rev/43c799df6e75?style=paper">first</a>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'rev/xyzzy?style=paper' | egrep $REVLINKS
+   <li><a href="/shortlog/xyzzy?style=paper">log</a></li>
+   <li><a href="/graph/xyzzy?style=paper">graph</a></li>
+   <li><a href="/raw-rev/xyzzy?style=paper">raw</a></li>
+   <li><a href="/file/xyzzy?style=paper">browse</a></li>
+  <a href="/archive/xyzzy.zip">zip</a>
+   changeset 1:<a href="/rev/a7c1559b7bba?style=paper">a7c1559b7bba</a>
+   <td class="author"><a href="/rev/43c799df6e75?style=paper">43c799df6e75</a> </td>
+   <td class="author"> <a href="/rev/9d8c40cba617?style=paper">9d8c40cba617</a></td>
+   <td class="files"><a href="/file/a7c1559b7bba/foo?style=paper">foo</a> </td>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog/xyzzy?style=paper' | egrep $REVLINKS
+  <li><a href="/graph/xyzzy?style=paper">graph</a></li>
+  <li><a href="/rev/xyzzy?style=paper">changeset</a></li>
+  <li><a href="/file/xyzzy?style=paper">browse</a></li>
+  <a href="/archive/xyzzy.zip">zip</a>
+  <a href="/shortlog/xyzzy?revcount=30&style=paper">less</a>
+  <a href="/shortlog/xyzzy?revcount=120&style=paper">more</a>
+  | rev 1: <a href="/shortlog/43c799df6e75?style=paper">(0)</a> <a href="/shortlog/tip?style=paper">tip</a> 
+     <a href="/rev/a7c1559b7bba?style=paper">second</a>
+     <a href="/rev/43c799df6e75?style=paper">first</a>
+  <a href="/shortlog/xyzzy?revcount=30&style=paper">less</a>
+  <a href="/shortlog/xyzzy?revcount=120&style=paper">more</a>
+  | rev 1: <a href="/shortlog/43c799df6e75?style=paper">(0)</a> <a href="/shortlog/tip?style=paper">tip</a> 
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph/xyzzy?style=paper' | egrep $REVLINKS
+  <li><a href="/shortlog/xyzzy?style=paper">log</a></li>
+  <li><a href="/rev/xyzzy?style=paper">changeset</a></li>
+  <li><a href="/file/xyzzy?style=paper">browse</a></li>
+  <a href="/graph/xyzzy?revcount=30&style=paper">less</a>
+  <a href="/graph/xyzzy?revcount=120&style=paper">more</a>
+  | rev 1: <a href="/graph/43c799df6e75?style=paper">(0)</a> <a href="/graph/tip?style=paper">tip</a> 
+  <a href="/graph/xyzzy?revcount=30&style=paper">less</a>
+  <a href="/graph/xyzzy?revcount=120&style=paper">more</a>
+  | rev 1: <a href="/graph/43c799df6e75?style=paper">(0)</a> <a href="/graph/tip?style=paper">tip</a> 
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/xyzzy?style=paper' | egrep $REVLINKS
+  <li><a href="/shortlog/xyzzy?style=paper">log</a></li>
+  <li><a href="/graph/xyzzy?style=paper">graph</a></li>
+  <li><a href="/rev/xyzzy?style=paper">changeset</a></li>
+  <a href="/archive/xyzzy.zip">zip</a>
+   directory / @ 1:<a href="/rev/a7c1559b7bba?style=paper">a7c1559b7bba</a>
+    <td class="name"><a href="/file/xyzzy/?style=paper">[up]</a></td>
+  <a href="/file/xyzzy/dir?style=paper">
+  <a href="/file/xyzzy/dir/?style=paper">
+  <a href="/file/xyzzy/foo?style=paper">
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/xyzzy/foo?style=paper' | egrep $REVLINKS
+  <li><a href="/shortlog/xyzzy?style=paper">log</a></li>
+  <li><a href="/graph/xyzzy?style=paper">graph</a></li>
+  <li><a href="/rev/xyzzy?style=paper">changeset</a></li>
+  <li><a href="/file/xyzzy/?style=paper">browse</a></li>
+  <li><a href="/file/tip/foo?style=paper">latest</a></li>
+  <li><a href="/diff/xyzzy/foo?style=paper">diff</a></li>
+  <li><a href="/comparison/xyzzy/foo?style=paper">comparison</a></li>
+  <li><a href="/annotate/xyzzy/foo?style=paper">annotate</a></li>
+  <li><a href="/log/xyzzy/foo?style=paper">file log</a></li>
+  <li><a href="/raw-file/xyzzy/foo">raw</a></li>
+   view foo @ 1:<a href="/rev/a7c1559b7bba?style=paper">a7c1559b7bba</a>
+   <td class="author"><a href="/file/43c799df6e75/foo?style=paper">43c799df6e75</a> </td>
+   <td class="author"><a href="/file/9d8c40cba617/foo?style=paper">9d8c40cba617</a> </td>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/xyzzy/foo?style=paper' | egrep $REVLINKS
+     href="/atom-log/tip/foo" title="Atom feed for test:foo" />
+     href="/rss-log/tip/foo" title="RSS feed for test:foo" />
+  <li><a href="/shortlog/xyzzy?style=paper">log</a></li>
+  <li><a href="/graph/xyzzy?style=paper">graph</a></li>
+  <li><a href="/rev/xyzzy?style=paper">changeset</a></li>
+  <li><a href="/file/xyzzy?style=paper">browse</a></li>
+  <li><a href="/file/xyzzy/foo?style=paper">file</a></li>
+  <li><a href="/diff/xyzzy/foo?style=paper">diff</a></li>
+  <li><a href="/comparison/xyzzy/foo?style=paper">comparison</a></li>
+  <li><a href="/annotate/xyzzy/foo?style=paper">annotate</a></li>
+  <li><a href="/raw-file/xyzzy/foo">raw</a></li>
+  <a href="/atom-log/a7c1559b7bba/foo" title="subscribe to atom feed">
+  <a href="/log/xyzzy/foo?revcount=30&style=paper">less</a>
+  <a href="/log/xyzzy/foo?revcount=120&style=paper">more</a>
+  | <a href="/log/43c799df6e75/foo?style=paper">(0)</a> <a href="/log/tip/foo?style=paper">tip</a> </div>
+     <a href="/rev/a7c1559b7bba?style=paper">second</a>
+     <a href="/rev/43c799df6e75?style=paper">first</a>
+  <a href="/log/xyzzy/foo?revcount=30&style=paper">less</a>
+  <a href="/log/xyzzy/foo?revcount=120&style=paper">more</a>
+  | <a href="/log/43c799df6e75/foo?style=paper">(0)</a> <a href="/log/tip/foo?style=paper">tip</a> 
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'annotate/xyzzy/foo?style=paper' | egrep $REVLINKS
+  <li><a href="/shortlog/xyzzy?style=paper">log</a></li>
+  <li><a href="/graph/xyzzy?style=paper">graph</a></li>
+  <li><a href="/rev/xyzzy?style=paper">changeset</a></li>
+  <li><a href="/file/xyzzy/?style=paper">browse</a></li>
+  <li><a href="/file/xyzzy/foo?style=paper">file</a></li>
+  <li><a href="/file/tip/foo?style=paper">latest</a></li>
+  <li><a href="/diff/xyzzy/foo?style=paper">diff</a></li>
+  <li><a href="/comparison/xyzzy/foo?style=paper">comparison</a></li>
+  <li><a href="/log/xyzzy/foo?style=paper">file log</a></li>
+  <li><a href="/raw-annotate/xyzzy/foo">raw</a></li>
+   annotate foo @ 1:<a href="/rev/a7c1559b7bba?style=paper">a7c1559b7bba</a>
+   <td class="author"><a href="/file/43c799df6e75/foo?style=paper">43c799df6e75</a> </td>
+   <td class="author"><a href="/file/9d8c40cba617/foo?style=paper">9d8c40cba617</a> </td>
+  <a href="/annotate/43c799df6e75/foo?style=paper#l1"
+  <a href="/annotate/a7c1559b7bba/foo?style=paper#l2"
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'diff/xyzzy/foo?style=paper' | egrep $REVLINKS
+  <li><a href="/shortlog/xyzzy?style=paper">log</a></li>
+  <li><a href="/graph/xyzzy?style=paper">graph</a></li>
+  <li><a href="/rev/xyzzy?style=paper">changeset</a></li>
+  <li><a href="/file/xyzzy?style=paper">browse</a></li>
+  <li><a href="/file/xyzzy/foo?style=paper">file</a></li>
+  <li><a href="/file/tip/foo?style=paper">latest</a></li>
+  <li><a href="/comparison/xyzzy/foo?style=paper">comparison</a></li>
+  <li><a href="/annotate/xyzzy/foo?style=paper">annotate</a></li>
+  <li><a href="/log/xyzzy/foo?style=paper">file log</a></li>
+  <li><a href="/raw-file/xyzzy/foo">raw</a></li>
+   diff foo @ 1:<a href="/rev/a7c1559b7bba?style=paper">a7c1559b7bba</a>
+   <td><a href="/file/43c799df6e75/foo?style=paper">43c799df6e75</a> </td>
+   <td><a href="/file/9d8c40cba617/foo?style=paper">9d8c40cba617</a> </td>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'comparison/xyzzy/foo?style=paper' | egrep $REVLINKS
+  <li><a href="/shortlog/xyzzy?style=paper">log</a></li>
+  <li><a href="/graph/xyzzy?style=paper">graph</a></li>
+  <li><a href="/rev/xyzzy?style=paper">changeset</a></li>
+  <li><a href="/file/xyzzy?style=paper">browse</a></li>
+  <li><a href="/file/xyzzy/foo?style=paper">file</a></li>
+  <li><a href="/file/tip/foo?style=paper">latest</a></li>
+  <li><a href="/diff/xyzzy/foo?style=paper">diff</a></li>
+  <li><a href="/annotate/xyzzy/foo?style=paper">annotate</a></li>
+  <li><a href="/log/xyzzy/foo?style=paper">file log</a></li>
+  <li><a href="/raw-file/xyzzy/foo">raw</a></li>
+   comparison foo @ 1:<a href="/rev/a7c1559b7bba?style=paper">a7c1559b7bba</a>
+   <td><a href="/file/43c799df6e75/foo?style=paper">43c799df6e75</a> </td>
+   <td><a href="/file/9d8c40cba617/foo?style=paper">9d8c40cba617</a> </td>
+
+(De)referencing symbolic revisions (coal)
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog?style=coal' | egrep $REVLINKS
+  <li><a href="/graph/tip?style=coal">graph</a></li>
+  <li><a href="/rev/tip?style=coal">changeset</a></li>
+  <li><a href="/file/tip?style=coal">browse</a></li>
+  <a href="/archive/tip.zip">zip</a>
+  <a href="/shortlog/tip?revcount=30&style=coal">less</a>
+  <a href="/shortlog/tip?revcount=120&style=coal">more</a>
+  | rev 2: <a href="/shortlog/43c799df6e75?style=coal">(0)</a> <a href="/shortlog/tip?style=coal">tip</a> 
+     <a href="/rev/9d8c40cba617?style=coal">third</a>
+     <a href="/rev/a7c1559b7bba?style=coal">second</a>
+     <a href="/rev/43c799df6e75?style=coal">first</a>
+  <a href="/shortlog/tip?revcount=30&style=coal">less</a>
+  <a href="/shortlog/tip?revcount=120&style=coal">more</a>
+  | rev 2: <a href="/shortlog/43c799df6e75?style=coal">(0)</a> <a href="/shortlog/tip?style=coal">tip</a> 
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph?style=coal' | egrep $REVLINKS
+  <li><a href="/shortlog/tip?style=coal">log</a></li>
+  <li><a href="/rev/tip?style=coal">changeset</a></li>
+  <li><a href="/file/tip?style=coal">browse</a></li>
+  <a href="/graph/tip?revcount=30&style=coal">less</a>
+  <a href="/graph/tip?revcount=120&style=coal">more</a>
+  | rev 2: <a href="/graph/43c799df6e75?style=coal">(0)</a> <a href="/graph/tip?style=coal">tip</a> 
+  <a href="/graph/tip?revcount=30&style=coal">less</a>
+  <a href="/graph/tip?revcount=120&style=coal">more</a>
+  | rev 2: <a href="/graph/43c799df6e75?style=coal">(0)</a> <a href="/graph/tip?style=coal">tip</a> 
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file?style=coal' | egrep $REVLINKS
+  <li><a href="/shortlog/tip?style=coal">log</a></li>
+  <li><a href="/graph/tip?style=coal">graph</a></li>
+  <li><a href="/rev/tip?style=coal">changeset</a></li>
+  <a href="/archive/tip.zip">zip</a>
+   directory / @ 2:<a href="/rev/9d8c40cba617?style=coal">9d8c40cba617</a>
+    <td class="name"><a href="/file/tip/?style=coal">[up]</a></td>
+  <a href="/file/tip/dir?style=coal">
+  <a href="/file/tip/dir/?style=coal">
+  <a href="/file/tip/foo?style=coal">
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'branches?style=coal' | egrep $REVLINKS
+  <a href="/shortlog/default?style=coal" class="open">
+  <a href="/shortlog/9d8c40cba617?style=coal" class="open">
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'tags?style=coal' | egrep $REVLINKS
+  <a href="/rev/tip?style=coal">
+  <a href="/rev/9d8c40cba617?style=coal">
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'bookmarks?style=coal' | egrep $REVLINKS
+  <a href="/rev/xyzzy?style=coal">
+  <a href="/rev/a7c1559b7bba?style=coal">
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog?style=coal&rev=all()' | egrep $REVLINKS
+     <a href="/rev/9d8c40cba617?style=coal">third</a>
+     <a href="/rev/a7c1559b7bba?style=coal">second</a>
+     <a href="/rev/43c799df6e75?style=coal">first</a>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'rev/xyzzy?style=coal' | egrep $REVLINKS
+   <li><a href="/shortlog/xyzzy?style=coal">log</a></li>
+   <li><a href="/graph/xyzzy?style=coal">graph</a></li>
+   <li><a href="/raw-rev/xyzzy?style=coal">raw</a></li>
+   <li><a href="/file/xyzzy?style=coal">browse</a></li>
+  <a href="/archive/xyzzy.zip">zip</a>
+   changeset 1:<a href="/rev/a7c1559b7bba?style=coal">a7c1559b7bba</a>
+   <td class="author"><a href="/rev/43c799df6e75?style=coal">43c799df6e75</a> </td>
+   <td class="author"> <a href="/rev/9d8c40cba617?style=coal">9d8c40cba617</a></td>
+   <td class="files"><a href="/file/a7c1559b7bba/foo?style=coal">foo</a> </td>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog/xyzzy?style=coal' | egrep $REVLINKS
+  <li><a href="/graph/xyzzy?style=coal">graph</a></li>
+  <li><a href="/rev/xyzzy?style=coal">changeset</a></li>
+  <li><a href="/file/xyzzy?style=coal">browse</a></li>
+  <a href="/archive/xyzzy.zip">zip</a>
+  <a href="/shortlog/xyzzy?revcount=30&style=coal">less</a>
+  <a href="/shortlog/xyzzy?revcount=120&style=coal">more</a>
+  | rev 1: <a href="/shortlog/43c799df6e75?style=coal">(0)</a> <a href="/shortlog/tip?style=coal">tip</a> 
+     <a href="/rev/a7c1559b7bba?style=coal">second</a>
+     <a href="/rev/43c799df6e75?style=coal">first</a>
+  <a href="/shortlog/xyzzy?revcount=30&style=coal">less</a>
+  <a href="/shortlog/xyzzy?revcount=120&style=coal">more</a>
+  | rev 1: <a href="/shortlog/43c799df6e75?style=coal">(0)</a> <a href="/shortlog/tip?style=coal">tip</a> 
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph/xyzzy?style=coal' | egrep $REVLINKS
+  <li><a href="/shortlog/xyzzy?style=coal">log</a></li>
+  <li><a href="/rev/xyzzy?style=coal">changeset</a></li>
+  <li><a href="/file/xyzzy?style=coal">browse</a></li>
+  <a href="/graph/xyzzy?revcount=30&style=coal">less</a>
+  <a href="/graph/xyzzy?revcount=120&style=coal">more</a>
+  | rev 1: <a href="/graph/43c799df6e75?style=coal">(0)</a> <a href="/graph/tip?style=coal">tip</a> 
+  <a href="/graph/xyzzy?revcount=30&style=coal">less</a>
+  <a href="/graph/xyzzy?revcount=120&style=coal">more</a>
+  | rev 1: <a href="/graph/43c799df6e75?style=coal">(0)</a> <a href="/graph/tip?style=coal">tip</a> 
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/xyzzy?style=coal' | egrep $REVLINKS
+  <li><a href="/shortlog/xyzzy?style=coal">log</a></li>
+  <li><a href="/graph/xyzzy?style=coal">graph</a></li>
+  <li><a href="/rev/xyzzy?style=coal">changeset</a></li>
+  <a href="/archive/xyzzy.zip">zip</a>
+   directory / @ 1:<a href="/rev/a7c1559b7bba?style=coal">a7c1559b7bba</a>
+    <td class="name"><a href="/file/xyzzy/?style=coal">[up]</a></td>
+  <a href="/file/xyzzy/dir?style=coal">
+  <a href="/file/xyzzy/dir/?style=coal">
+  <a href="/file/xyzzy/foo?style=coal">
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/xyzzy/foo?style=coal' | egrep $REVLINKS
+  <li><a href="/shortlog/xyzzy?style=coal">log</a></li>
+  <li><a href="/graph/xyzzy?style=coal">graph</a></li>
+  <li><a href="/rev/xyzzy?style=coal">changeset</a></li>
+  <li><a href="/file/xyzzy/?style=coal">browse</a></li>
+  <li><a href="/file/tip/foo?style=coal">latest</a></li>
+  <li><a href="/diff/xyzzy/foo?style=coal">diff</a></li>
+  <li><a href="/comparison/xyzzy/foo?style=coal">comparison</a></li>
+  <li><a href="/annotate/xyzzy/foo?style=coal">annotate</a></li>
+  <li><a href="/log/xyzzy/foo?style=coal">file log</a></li>
+  <li><a href="/raw-file/xyzzy/foo">raw</a></li>
+   view foo @ 1:<a href="/rev/a7c1559b7bba?style=coal">a7c1559b7bba</a>
+   <td class="author"><a href="/file/43c799df6e75/foo?style=coal">43c799df6e75</a> </td>
+   <td class="author"><a href="/file/9d8c40cba617/foo?style=coal">9d8c40cba617</a> </td>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/xyzzy/foo?style=coal' | egrep $REVLINKS
+     href="/atom-log/tip/foo" title="Atom feed for test:foo" />
+     href="/rss-log/tip/foo" title="RSS feed for test:foo" />
+  <li><a href="/shortlog/xyzzy?style=coal">log</a></li>
+  <li><a href="/graph/xyzzy?style=coal">graph</a></li>
+  <li><a href="/rev/xyzzy?style=coal">changeset</a></li>
+  <li><a href="/file/xyzzy?style=coal">browse</a></li>
+  <li><a href="/file/xyzzy/foo?style=coal">file</a></li>
+  <li><a href="/diff/xyzzy/foo?style=coal">diff</a></li>
+  <li><a href="/comparison/xyzzy/foo?style=coal">comparison</a></li>
+  <li><a href="/annotate/xyzzy/foo?style=coal">annotate</a></li>
+  <li><a href="/raw-file/xyzzy/foo">raw</a></li>
+  <a href="/atom-log/a7c1559b7bba/foo" title="subscribe to atom feed">
+  <a href="/log/xyzzy/foo?revcount=30&style=coal">less</a>
+  <a href="/log/xyzzy/foo?revcount=120&style=coal">more</a>
+  | <a href="/log/43c799df6e75/foo?style=coal">(0)</a> <a href="/log/tip/foo?style=coal">tip</a> </div>
+     <a href="/rev/a7c1559b7bba?style=coal">second</a>
+     <a href="/rev/43c799df6e75?style=coal">first</a>
+  <a href="/log/xyzzy/foo?revcount=30&style=coal">less</a>
+  <a href="/log/xyzzy/foo?revcount=120&style=coal">more</a>
+  | <a href="/log/43c799df6e75/foo?style=coal">(0)</a> <a href="/log/tip/foo?style=coal">tip</a> 
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'annotate/xyzzy/foo?style=coal' | egrep $REVLINKS
+  <li><a href="/shortlog/xyzzy?style=coal">log</a></li>
+  <li><a href="/graph/xyzzy?style=coal">graph</a></li>
+  <li><a href="/rev/xyzzy?style=coal">changeset</a></li>
+  <li><a href="/file/xyzzy/?style=coal">browse</a></li>
+  <li><a href="/file/xyzzy/foo?style=coal">file</a></li>
+  <li><a href="/file/tip/foo?style=coal">latest</a></li>
+  <li><a href="/diff/xyzzy/foo?style=coal">diff</a></li>
+  <li><a href="/comparison/xyzzy/foo?style=coal">comparison</a></li>
+  <li><a href="/log/xyzzy/foo?style=coal">file log</a></li>
+  <li><a href="/raw-annotate/xyzzy/foo">raw</a></li>
+   annotate foo @ 1:<a href="/rev/a7c1559b7bba?style=coal">a7c1559b7bba</a>
+   <td class="author"><a href="/file/43c799df6e75/foo?style=coal">43c799df6e75</a> </td>
+   <td class="author"><a href="/file/9d8c40cba617/foo?style=coal">9d8c40cba617</a> </td>
+  <a href="/annotate/43c799df6e75/foo?style=coal#1"
+  <a href="/annotate/a7c1559b7bba/foo?style=coal#2"
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'diff/xyzzy/foo?style=coal' | egrep $REVLINKS
+  <li><a href="/shortlog/xyzzy?style=coal">log</a></li>
+  <li><a href="/graph/xyzzy?style=coal">graph</a></li>
+  <li><a href="/rev/xyzzy?style=coal">changeset</a></li>
+  <li><a href="/file/xyzzy?style=coal">browse</a></li>
+  <li><a href="/file/xyzzy/foo?style=coal">file</a></li>
+  <li><a href="/file/tip/foo?style=coal">latest</a></li>
+  <li><a href="/comparison/xyzzy/foo?style=coal">comparison</a></li>
+  <li><a href="/annotate/xyzzy/foo?style=coal">annotate</a></li>
+  <li><a href="/log/xyzzy/foo?style=coal">file log</a></li>
+  <li><a href="/raw-file/xyzzy/foo">raw</a></li>
+   diff foo @ 1:<a href="/rev/a7c1559b7bba?style=coal">a7c1559b7bba</a>
+   <td><a href="/file/43c799df6e75/foo?style=coal">43c799df6e75</a> </td>
+   <td><a href="/file/9d8c40cba617/foo?style=coal">9d8c40cba617</a> </td>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'comparison/xyzzy/foo?style=coal' | egrep $REVLINKS
+  <li><a href="/shortlog/xyzzy?style=coal">log</a></li>
+  <li><a href="/graph/xyzzy?style=coal">graph</a></li>
+  <li><a href="/rev/xyzzy?style=coal">changeset</a></li>
+  <li><a href="/file/xyzzy?style=coal">browse</a></li>
+  <li><a href="/file/xyzzy/foo?style=coal">file</a></li>
+  <li><a href="/file/tip/foo?style=coal">latest</a></li>
+  <li><a href="/diff/xyzzy/foo?style=coal">diff</a></li>
+  <li><a href="/annotate/xyzzy/foo?style=coal">annotate</a></li>
+  <li><a href="/log/xyzzy/foo?style=coal">file log</a></li>
+  <li><a href="/raw-file/xyzzy/foo">raw</a></li>
+   comparison foo @ 1:<a href="/rev/a7c1559b7bba?style=coal">a7c1559b7bba</a>
+   <td><a href="/file/43c799df6e75/foo?style=coal">43c799df6e75</a> </td>
+   <td><a href="/file/9d8c40cba617/foo?style=coal">9d8c40cba617</a> </td>
+
+(De)referencing symbolic revisions (gitweb)
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'summary?style=gitweb' | egrep $REVLINKS
+  <a href="/file?style=gitweb">files</a> | <a href="/archive/tip.zip">zip</a>  |
+  <a class="list" href="/rev/9d8c40cba617?style=gitweb">
+  <a href="/rev/9d8c40cba617?style=gitweb">changeset</a> |
+  <a href="/file/9d8c40cba617?style=gitweb">files</a>
+  <a class="list" href="/rev/a7c1559b7bba?style=gitweb">
+  <a href="/rev/a7c1559b7bba?style=gitweb">changeset</a> |
+  <a href="/file/a7c1559b7bba?style=gitweb">files</a>
+  <a class="list" href="/rev/43c799df6e75?style=gitweb">
+  <a href="/rev/43c799df6e75?style=gitweb">changeset</a> |
+  <a href="/file/43c799df6e75?style=gitweb">files</a>
+  <td><a class="list" href="/rev/a7c1559b7bba?style=gitweb"><b>xyzzy</b></a></td>
+  <a href="/rev/a7c1559b7bba?style=gitweb">changeset</a> |
+  <a href="/log/a7c1559b7bba?style=gitweb">changelog</a> |
+  <a href="/file/a7c1559b7bba?style=gitweb">files</a>
+  <td><a class="list" href="/shortlog/9d8c40cba617?style=gitweb"><b>9d8c40cba617</b></a></td>
+  <a href="/changeset/9d8c40cba617?style=gitweb">changeset</a> |
+  <a href="/log/9d8c40cba617?style=gitweb">changelog</a> |
+  <a href="/file/9d8c40cba617?style=gitweb">files</a>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog?style=gitweb' | egrep $REVLINKS
+  <a href="/log/tip?style=gitweb">changelog</a> |
+  <a href="/file/tip?style=gitweb">files</a> | <a href="/archive/tip.zip">zip</a>  |
+  <br/><a href="/shortlog/43c799df6e75?style=gitweb">(0)</a> <a href="/shortlog/tip?style=gitweb">tip</a> <br/>
+  <a class="list" href="/rev/9d8c40cba617?style=gitweb">
+  <a href="/rev/9d8c40cba617?style=gitweb">changeset</a> |
+  <a href="/file/9d8c40cba617?style=gitweb">files</a>
+  <a class="list" href="/rev/a7c1559b7bba?style=gitweb">
+  <a href="/rev/a7c1559b7bba?style=gitweb">changeset</a> |
+  <a href="/file/a7c1559b7bba?style=gitweb">files</a>
+  <a class="list" href="/rev/43c799df6e75?style=gitweb">
+  <a href="/rev/43c799df6e75?style=gitweb">changeset</a> |
+  <a href="/file/43c799df6e75?style=gitweb">files</a>
+  <a href="/shortlog/43c799df6e75?style=gitweb">(0)</a> <a href="/shortlog/tip?style=gitweb">tip</a> 
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?style=gitweb' | egrep $REVLINKS
+  <a href="/shortlog/tip?style=gitweb">shortlog</a> |
+  <a href="/file/tip?style=gitweb">files</a> | <a href="/archive/tip.zip">zip</a>  |
+  <a href="/log/43c799df6e75?style=gitweb">(0)</a>  <a href="/log/tip?style=gitweb">tip</a> <br/>
+  <a class="title" href="/rev/9d8c40cba617?style=gitweb"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>third<span class="logtags"> <span class="branchtag" title="default">default</span> <span class="tagtag" title="tip">tip</span> </span></a>
+  <a href="/rev/9d8c40cba617?style=gitweb">changeset</a><br/>
+  <a class="title" href="/rev/a7c1559b7bba?style=gitweb"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>second<span class="logtags"> <span class="bookmarktag" title="xyzzy">xyzzy</span> </span></a>
+  <a href="/rev/a7c1559b7bba?style=gitweb">changeset</a><br/>
+  <a class="title" href="/rev/43c799df6e75?style=gitweb"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>first<span class="logtags"> </span></a>
+  <a href="/rev/43c799df6e75?style=gitweb">changeset</a><br/>
+  <a href="/log/43c799df6e75?style=gitweb">(0)</a>  <a href="/log/tip?style=gitweb">tip</a> <br/>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph?style=gitweb' | egrep $REVLINKS
+  <a href="/log/tip?style=gitweb">changelog</a> |
+  <a href="/file/tip?style=gitweb">files</a> |
+  <a href="/graph/tip?revcount=30&style=gitweb">less</a>
+  <a href="/graph/tip?revcount=120&style=gitweb">more</a>
+  | <a href="/graph/43c799df6e75?style=gitweb">(0)</a> <a href="/graph/tip?style=gitweb">tip</a> <br/>
+  <a href="/graph/tip?revcount=30&style=gitweb">less</a>
+  <a href="/graph/tip?revcount=120&style=gitweb">more</a>
+  | <a href="/graph/43c799df6e75?style=gitweb">(0)</a> <a href="/graph/tip?style=gitweb">tip</a> 
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'tags?style=gitweb' | egrep $REVLINKS
+  <td><a class="list" href="/rev/9d8c40cba617?style=gitweb"><b>tip</b></a></td>
+  <a href="/rev/9d8c40cba617?style=gitweb">changeset</a> |
+  <a href="/log/9d8c40cba617?style=gitweb">changelog</a> |
+  <a href="/file/9d8c40cba617?style=gitweb">files</a>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'bookmarks?style=gitweb' | egrep $REVLINKS
+  <td><a class="list" href="/rev/a7c1559b7bba?style=gitweb"><b>xyzzy</b></a></td>
+  <a href="/rev/a7c1559b7bba?style=gitweb">changeset</a> |
+  <a href="/log/a7c1559b7bba?style=gitweb">changelog</a> |
+  <a href="/file/a7c1559b7bba?style=gitweb">files</a>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'branches?style=gitweb' | egrep $REVLINKS
+  <td><a class="list" href="/shortlog/9d8c40cba617?style=gitweb"><b>9d8c40cba617</b></a></td>
+  <a href="/changeset/9d8c40cba617?style=gitweb">changeset</a> |
+  <a href="/log/9d8c40cba617?style=gitweb">changelog</a> |
+  <a href="/file/9d8c40cba617?style=gitweb">files</a>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file?style=gitweb' | egrep $REVLINKS
+  <a href="/rev/tip?style=gitweb">changeset</a>  | <a href="/archive/tip.zip">zip</a>  |
+  <td><a href="/file/tip/?style=gitweb">[up]</a></td>
+  <a href="/file/tip/dir?style=gitweb">dir</a>
+  <a href="/file/tip/dir/?style=gitweb"></a>
+  <a href="/file/tip/dir?style=gitweb">files</a>
+  <a class="list" href="/file/tip/foo?style=gitweb">foo</a>
+  <a href="/file/tip/foo?style=gitweb">file</a> |
+  <a href="/log/tip/foo?style=gitweb">revisions</a> |
+  <a href="/annotate/tip/foo?style=gitweb">annotate</a>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog?style=gitweb&rev=all()' | egrep $REVLINKS
+  <a href="/file?style=gitweb">files</a> | <a href="/archive/tip.zip">zip</a> 
+  <a class="title" href="/rev/9d8c40cba617?style=gitweb"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>third<span class="logtags"> <span class="branchtag" title="default">default</span> <span class="tagtag" title="tip">tip</span> </span></a>
+  <a href="/rev/9d8c40cba617?style=gitweb">changeset</a><br/>
+  <a class="title" href="/rev/a7c1559b7bba?style=gitweb"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>second<span class="logtags"> <span class="bookmarktag" title="xyzzy">xyzzy</span> </span></a>
+  <a href="/rev/a7c1559b7bba?style=gitweb">changeset</a><br/>
+  <a class="title" href="/rev/43c799df6e75?style=gitweb"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>first<span class="logtags"> </span></a>
+  <a href="/rev/43c799df6e75?style=gitweb">changeset</a><br/>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'rev/xyzzy?style=gitweb' | egrep $REVLINKS
+  <a href="/shortlog/xyzzy?style=gitweb">shortlog</a> |
+  <a href="/log/xyzzy?style=gitweb">changelog</a> |
+  <a href="/file/xyzzy?style=gitweb">files</a> |
+  <a href="/raw-rev/xyzzy">raw</a>  | <a href="/archive/xyzzy.zip">zip</a>  |
+  <a class="title" href="/raw-rev/a7c1559b7bba">second <span class="logtags"><span class="bookmarktag" title="xyzzy">xyzzy</span> </span></a>
+   <td style="font-family:monospace"><a class="list" href="/rev/a7c1559b7bba?style=gitweb">a7c1559b7bba</a></td>
+  <a class="list" href="/rev/43c799df6e75?style=gitweb">43c799df6e75</a>
+  <a class="list" href="/rev/9d8c40cba617?style=gitweb">9d8c40cba617</a>
+  <td><a class="list" href="/diff/a7c1559b7bba/foo?style=gitweb">foo</a></td>
+  <a href="/file/a7c1559b7bba/foo?style=gitweb">file</a> |
+  <a href="/annotate/a7c1559b7bba/foo?style=gitweb">annotate</a> |
+  <a href="/diff/a7c1559b7bba/foo?style=gitweb">diff</a> |
+  <a href="/comparison/a7c1559b7bba/foo?style=gitweb">comparison</a> |
+  <a href="/log/a7c1559b7bba/foo?style=gitweb">revisions</a>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog/xyzzy?style=gitweb' | egrep $REVLINKS
+  <a href="/log/xyzzy?style=gitweb">changelog</a> |
+  <a href="/file/xyzzy?style=gitweb">files</a> | <a href="/archive/xyzzy.zip">zip</a>  |
+  <br/><a href="/shortlog/43c799df6e75?style=gitweb">(0)</a> <a href="/shortlog/tip?style=gitweb">tip</a> <br/>
+  <a class="list" href="/rev/a7c1559b7bba?style=gitweb">
+  <a href="/rev/a7c1559b7bba?style=gitweb">changeset</a> |
+  <a href="/file/a7c1559b7bba?style=gitweb">files</a>
+  <a class="list" href="/rev/43c799df6e75?style=gitweb">
+  <a href="/rev/43c799df6e75?style=gitweb">changeset</a> |
+  <a href="/file/43c799df6e75?style=gitweb">files</a>
+  <a href="/shortlog/43c799df6e75?style=gitweb">(0)</a> <a href="/shortlog/tip?style=gitweb">tip</a> 
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/xyzzy?style=gitweb' | egrep $REVLINKS
+  <a href="/shortlog/xyzzy?style=gitweb">shortlog</a> |
+  <a href="/file/xyzzy?style=gitweb">files</a> | <a href="/archive/xyzzy.zip">zip</a>  |
+  <a href="/log/43c799df6e75?style=gitweb">(0)</a>  <a href="/log/tip?style=gitweb">tip</a> <br/>
+  <a class="title" href="/rev/a7c1559b7bba?style=gitweb"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>second<span class="logtags"> <span class="bookmarktag" title="xyzzy">xyzzy</span> </span></a>
+  <a href="/rev/a7c1559b7bba?style=gitweb">changeset</a><br/>
+  <a class="title" href="/rev/43c799df6e75?style=gitweb"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>first<span class="logtags"> </span></a>
+  <a href="/rev/43c799df6e75?style=gitweb">changeset</a><br/>
+  <a href="/log/43c799df6e75?style=gitweb">(0)</a>  <a href="/log/tip?style=gitweb">tip</a> <br/>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph/xyzzy?style=gitweb' | egrep $REVLINKS
+  <a href="/log/xyzzy?style=gitweb">changelog</a> |
+  <a href="/file/xyzzy?style=gitweb">files</a> |
+  <a href="/graph/xyzzy?revcount=30&style=gitweb">less</a>
+  <a href="/graph/xyzzy?revcount=120&style=gitweb">more</a>
+  | <a href="/graph/43c799df6e75?style=gitweb">(0)</a> <a href="/graph/tip?style=gitweb">tip</a> <br/>
+  <a href="/graph/xyzzy?revcount=30&style=gitweb">less</a>
+  <a href="/graph/xyzzy?revcount=120&style=gitweb">more</a>
+  | <a href="/graph/43c799df6e75?style=gitweb">(0)</a> <a href="/graph/tip?style=gitweb">tip</a> 
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/xyzzy?style=gitweb' | egrep $REVLINKS
+  <a href="/rev/xyzzy?style=gitweb">changeset</a>  | <a href="/archive/xyzzy.zip">zip</a>  |
+  <td><a href="/file/xyzzy/?style=gitweb">[up]</a></td>
+  <a href="/file/xyzzy/dir?style=gitweb">dir</a>
+  <a href="/file/xyzzy/dir/?style=gitweb"></a>
+  <a href="/file/xyzzy/dir?style=gitweb">files</a>
+  <a class="list" href="/file/xyzzy/foo?style=gitweb">foo</a>
+  <a href="/file/xyzzy/foo?style=gitweb">file</a> |
+  <a href="/log/xyzzy/foo?style=gitweb">revisions</a> |
+  <a href="/annotate/xyzzy/foo?style=gitweb">annotate</a>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/xyzzy/foo?style=gitweb' | egrep $REVLINKS
+  <a href="/file/xyzzy/?style=gitweb">files</a> |
+  <a href="/rev/xyzzy?style=gitweb">changeset</a> |
+  <a href="/file/tip/foo?style=gitweb">latest</a> |
+  <a href="/log/xyzzy/foo?style=gitweb">revisions</a> |
+  <a href="/annotate/xyzzy/foo?style=gitweb">annotate</a> |
+  <a href="/diff/xyzzy/foo?style=gitweb">diff</a> |
+  <a href="/comparison/xyzzy/foo?style=gitweb">comparison</a> |
+  <a href="/raw-file/xyzzy/foo">raw</a> |
+   <td style="font-family:monospace"><a class="list" href="/rev/a7c1559b7bba?style=gitweb">a7c1559b7bba</a></td>
+  <a class="list" href="/file/43c799df6e75/foo?style=gitweb">
+  <a class="list" href="/file/9d8c40cba617/foo?style=gitweb">9d8c40cba617</a></td>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/xyzzy/foo?style=gitweb' | egrep $REVLINKS
+  <a href="/file/xyzzy/foo?style=gitweb">file</a> |
+  <a href="/annotate/xyzzy/foo?style=gitweb">annotate</a> |
+  <a href="/diff/xyzzy/foo?style=gitweb">diff</a> |
+  <a href="/comparison/xyzzy/foo?style=gitweb">comparison</a> |
+  <a href="/rss-log/tip/foo">rss</a> |
+  <a href="/log/43c799df6e75/foo?style=gitweb">(0)</a> <a href="/log/tip/foo?style=gitweb">tip</a> 
+  <a class="list" href="/rev/a7c1559b7bba?style=gitweb">
+  <a href="/file/a7c1559b7bba/foo?style=gitweb">file</a> |
+  <a href="/diff/a7c1559b7bba/foo?style=gitweb">diff</a> |
+  <a href="/annotate/a7c1559b7bba/foo?style=gitweb">annotate</a>
+  <a class="list" href="/rev/43c799df6e75?style=gitweb">
+  <a href="/file/43c799df6e75/foo?style=gitweb">file</a> |
+  <a href="/diff/43c799df6e75/foo?style=gitweb">diff</a> |
+  <a href="/annotate/43c799df6e75/foo?style=gitweb">annotate</a>
+  <a href="/log/43c799df6e75/foo?style=gitweb">(0)</a> <a href="/log/tip/foo?style=gitweb">tip</a> 
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'annotate/xyzzy/foo?style=gitweb' | egrep $REVLINKS
+  <a href="/file/xyzzy/?style=gitweb">files</a> |
+  <a href="/rev/xyzzy?style=gitweb">changeset</a> |
+  <a href="/file/xyzzy/foo?style=gitweb">file</a> |
+  <a href="/file/tip/foo?style=gitweb">latest</a> |
+  <a href="/log/xyzzy/foo?style=gitweb">revisions</a> |
+  <a href="/diff/xyzzy/foo?style=gitweb">diff</a> |
+  <a href="/comparison/xyzzy/foo?style=gitweb">comparison</a> |
+  <a href="/raw-annotate/xyzzy/foo">raw</a> |
+   <td style="font-family:monospace"><a class="list" href="/rev/a7c1559b7bba?style=gitweb">a7c1559b7bba</a></td>
+  <a class="list" href="/annotate/43c799df6e75/foo?style=gitweb">
+  <a class="list" href="/annotate/9d8c40cba617/foo?style=gitweb">9d8c40cba617</a></td>
+  <a href="/annotate/43c799df6e75/foo?style=gitweb#l1"
+  <a href="/annotate/a7c1559b7bba/foo?style=gitweb#l2"
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'diff/xyzzy/foo?style=gitweb' | egrep $REVLINKS
+  <a href="/file/xyzzy?style=gitweb">files</a> |
+  <a href="/rev/xyzzy?style=gitweb">changeset</a> |
+  <a href="/file/xyzzy/foo?style=gitweb">file</a> |
+  <a href="/file/tip/foo?style=gitweb">latest</a> |
+  <a href="/log/xyzzy/foo?style=gitweb">revisions</a> |
+  <a href="/annotate/xyzzy/foo?style=gitweb">annotate</a> |
+  <a href="/comparison/xyzzy/foo?style=gitweb">comparison</a> |
+  <a href="/raw-diff/xyzzy/foo">raw</a> |
+   <td style="font-family:monospace"><a class="list" href="/rev/a7c1559b7bba?style=gitweb">a7c1559b7bba</a></td>
+  <a class="list" href="/diff/43c799df6e75/foo?style=gitweb">
+  <a class="list" href="/diff/9d8c40cba617/foo?style=gitweb">9d8c40cba617</a>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'comparison/xyzzy/foo?style=gitweb' | egrep $REVLINKS
+  <a href="/file/xyzzy?style=gitweb">files</a> |
+  <a href="/rev/xyzzy?style=gitweb">changeset</a> |
+  <a href="/file/xyzzy/foo?style=gitweb">file</a> |
+  <a href="/file/tip/foo?style=gitweb">latest</a> |
+  <a href="/log/xyzzy/foo?style=gitweb">revisions</a> |
+  <a href="/annotate/xyzzy/foo?style=gitweb">annotate</a> |
+  <a href="/diff/xyzzy/foo?style=gitweb">diff</a> |
+  <a href="/raw-diff/xyzzy/foo">raw</a> |
+   <td style="font-family:monospace"><a class="list" href="/rev/a7c1559b7bba?style=gitweb">a7c1559b7bba</a></td>
+  <a class="list" href="/comparison/43c799df6e75/foo?style=gitweb">
+  <a class="list" href="/comparison/9d8c40cba617/foo?style=gitweb">9d8c40cba617</a>
+
+(De)referencing symbolic revisions (monoblue)
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'summary?style=monoblue' | egrep $REVLINKS
+  <a href="/rev/9d8c40cba617?style=monoblue">
+  <a href="/rev/9d8c40cba617?style=monoblue">changeset</a> |
+  <a href="/file/9d8c40cba617?style=monoblue">files</a>
+  <a href="/rev/a7c1559b7bba?style=monoblue">
+  <a href="/rev/a7c1559b7bba?style=monoblue">changeset</a> |
+  <a href="/file/a7c1559b7bba?style=monoblue">files</a>
+  <a href="/rev/43c799df6e75?style=monoblue">
+  <a href="/rev/43c799df6e75?style=monoblue">changeset</a> |
+  <a href="/file/43c799df6e75?style=monoblue">files</a>
+  <td><a href="/rev/a7c1559b7bba?style=monoblue">xyzzy</a></td>
+  <a href="/rev/a7c1559b7bba?style=monoblue">changeset</a> |
+  <a href="/log/a7c1559b7bba?style=monoblue">changelog</a> |
+  <a href="/file/a7c1559b7bba?style=monoblue">files</a>
+  <td><a href="/shortlog/9d8c40cba617?style=monoblue">9d8c40cba617</a></td>
+  <a href="/rev/9d8c40cba617?style=monoblue">changeset</a> |
+  <a href="/log/9d8c40cba617?style=monoblue">changelog</a> |
+  <a href="/file/9d8c40cba617?style=monoblue">files</a>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog?style=monoblue' | egrep $REVLINKS
+              <li><a href="/graph/tip?style=monoblue">graph</a></li>
+              <li><a href="/file/tip?style=monoblue">files</a></li>
+              <li><a href="/archive/tip.zip">zip</a></li>
+  <a href="/rev/9d8c40cba617?style=monoblue">
+  <a href="/rev/9d8c40cba617?style=monoblue">changeset</a> |
+  <a href="/file/9d8c40cba617?style=monoblue">files</a>
+  <a href="/rev/a7c1559b7bba?style=monoblue">
+  <a href="/rev/a7c1559b7bba?style=monoblue">changeset</a> |
+  <a href="/file/a7c1559b7bba?style=monoblue">files</a>
+  <a href="/rev/43c799df6e75?style=monoblue">
+  <a href="/rev/43c799df6e75?style=monoblue">changeset</a> |
+  <a href="/file/43c799df6e75?style=monoblue">files</a>
+      <a href="/shortlog/43c799df6e75?style=monoblue">(0)</a> <a href="/shortlog/tip?style=monoblue">tip</a> 
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?style=monoblue' | egrep $REVLINKS
+              <li><a href="/graph/tip?style=monoblue">graph</a></li>
+              <li><a href="/file/tip?style=monoblue">files</a></li>
+              <li><a href="/archive/tip.zip">zip</a></li>
+      <h3 class="changelog"><a class="title" href="/rev/9d8c40cba617?style=monoblue">third<span class="logtags"> <span class="branchtag" title="default">default</span> <span class="tagtag" title="tip">tip</span> </span></a></h3>
+  <h3 class="changelog"><a class="title" href="/rev/a7c1559b7bba?style=monoblue">second<span class="logtags"> <span class="bookmarktag" title="xyzzy">xyzzy</span> </span></a></h3>
+  <h3 class="changelog"><a class="title" href="/rev/43c799df6e75?style=monoblue">first<span class="logtags"> </span></a></h3>
+  <a href="/log/43c799df6e75?style=monoblue">(0)</a>  <a href="/log/tip?style=monoblue">tip</a> 
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph?style=monoblue' | egrep $REVLINKS
+              <li><a href="/file/tip?style=monoblue">files</a></li>
+          <a href="/graph/tip?revcount=30&style=monoblue">less</a>
+          <a href="/graph/tip?revcount=120&style=monoblue">more</a>
+          | <a href="/graph/43c799df6e75?style=monoblue">(0)</a> <a href="/graph/tip?style=monoblue">tip</a> 
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'tags?style=monoblue' | egrep $REVLINKS
+  <td><a href="/rev/9d8c40cba617?style=monoblue">tip</a></td>
+  <a href="/rev/9d8c40cba617?style=monoblue">changeset</a> |
+  <a href="/log/9d8c40cba617?style=monoblue">changelog</a> |
+  <a href="/file/9d8c40cba617?style=monoblue">files</a>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'bookmarks?style=monoblue' | egrep $REVLINKS
+  <td><a href="/rev/a7c1559b7bba?style=monoblue">xyzzy</a></td>
+  <a href="/rev/a7c1559b7bba?style=monoblue">changeset</a> |
+  <a href="/log/a7c1559b7bba?style=monoblue">changelog</a> |
+  <a href="/file/a7c1559b7bba?style=monoblue">files</a>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'branches?style=monoblue' | egrep $REVLINKS
+  <td><a href="/shortlog/9d8c40cba617?style=monoblue">9d8c40cba617</a></td>
+  <a href="/rev/9d8c40cba617?style=monoblue">changeset</a> |
+  <a href="/log/9d8c40cba617?style=monoblue">changelog</a> |
+  <a href="/file/9d8c40cba617?style=monoblue">files</a>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file?style=monoblue' | egrep $REVLINKS
+              <li><a href="/graph/tip?style=monoblue">graph</a></li>
+          <li><a href="/rev/tip?style=monoblue">changeset</a></li>
+          <li><a href="/archive/tip.zip">zip</a></li>
+              <td><a href="/file/tip/?style=monoblue">[up]</a></td>
+  <a href="/file/tip/dir?style=monoblue">dir</a>
+  <a href="/file/tip/dir/?style=monoblue"></a>
+  <td><a href="/file/tip/dir?style=monoblue">files</a></td>
+  <td><a href="/file/tip/foo?style=monoblue">foo</a></td>
+  <a href="/file/tip/foo?style=monoblue">file</a> |
+  <a href="/log/tip/foo?style=monoblue">revisions</a> |
+  <a href="/annotate/tip/foo?style=monoblue">annotate</a>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog?style=monoblue&rev=all()' | egrep $REVLINKS
+              <li><a href="/archive/tip.zip">zip</a></li>
+      <h3 class="changelog"><a class="title" href="/rev/9d8c40cba617?style=monoblue">third<span class="logtags"> <span class="branchtag" title="default">default</span> <span class="tagtag" title="tip">tip</span> </span></a></h3>
+  <h3 class="changelog"><a class="title" href="/rev/a7c1559b7bba?style=monoblue">second<span class="logtags"> <span class="bookmarktag" title="xyzzy">xyzzy</span> </span></a></h3>
+  <h3 class="changelog"><a class="title" href="/rev/43c799df6e75?style=monoblue">first<span class="logtags"> </span></a></h3>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'rev/xyzzy?style=monoblue' | egrep $REVLINKS
+              <li><a href="/graph/xyzzy?style=monoblue">graph</a></li>
+              <li><a href="/file/xyzzy?style=monoblue">files</a></li>
+          <li><a href="/raw-rev/xyzzy">raw</a></li>
+          <li><a href="/archive/xyzzy.zip">zip</a></li>
+      <h3 class="changeset"><a href="/raw-rev/a7c1559b7bba">second <span class="logtags"><span class="bookmarktag" title="xyzzy">xyzzy</span> </span></a></h3>
+          <dd><a href="/rev/a7c1559b7bba?style=monoblue">a7c1559b7bba</a></dd>
+  <dd><a href="/rev/43c799df6e75?style=monoblue">43c799df6e75</a></dd>
+  <dd><a href="/rev/9d8c40cba617?style=monoblue">9d8c40cba617</a></dd>
+  <td><a href="/diff/a7c1559b7bba/foo?style=monoblue">foo</a></td>
+  <a href="/file/a7c1559b7bba/foo?style=monoblue">file</a> |
+  <a href="/annotate/a7c1559b7bba/foo?style=monoblue">annotate</a> |
+  <a href="/diff/a7c1559b7bba/foo?style=monoblue">diff</a> |
+  <a href="/comparison/a7c1559b7bba/foo?style=monoblue">comparison</a> |
+  <a href="/log/a7c1559b7bba/foo?style=monoblue">revisions</a>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog/xyzzy?style=monoblue' | egrep $REVLINKS
+              <li><a href="/graph/xyzzy?style=monoblue">graph</a></li>
+              <li><a href="/file/xyzzy?style=monoblue">files</a></li>
+              <li><a href="/archive/xyzzy.zip">zip</a></li>
+  <a href="/rev/a7c1559b7bba?style=monoblue">
+  <a href="/rev/a7c1559b7bba?style=monoblue">changeset</a> |
+  <a href="/file/a7c1559b7bba?style=monoblue">files</a>
+  <a href="/rev/43c799df6e75?style=monoblue">
+  <a href="/rev/43c799df6e75?style=monoblue">changeset</a> |
+  <a href="/file/43c799df6e75?style=monoblue">files</a>
+      <a href="/shortlog/43c799df6e75?style=monoblue">(0)</a> <a href="/shortlog/tip?style=monoblue">tip</a> 
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/xyzzy?style=monoblue' | egrep $REVLINKS
+              <li><a href="/graph/xyzzy?style=monoblue">graph</a></li>
+              <li><a href="/file/xyzzy?style=monoblue">files</a></li>
+              <li><a href="/archive/xyzzy.zip">zip</a></li>
+      <h3 class="changelog"><a class="title" href="/rev/a7c1559b7bba?style=monoblue">second<span class="logtags"> <span class="bookmarktag" title="xyzzy">xyzzy</span> </span></a></h3>
+  <h3 class="changelog"><a class="title" href="/rev/43c799df6e75?style=monoblue">first<span class="logtags"> </span></a></h3>
+  <a href="/log/43c799df6e75?style=monoblue">(0)</a>  <a href="/log/tip?style=monoblue">tip</a> 
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph/xyzzy?style=monoblue' | egrep $REVLINKS
+              <li><a href="/file/xyzzy?style=monoblue">files</a></li>
+          <a href="/graph/xyzzy?revcount=30&style=monoblue">less</a>
+          <a href="/graph/xyzzy?revcount=120&style=monoblue">more</a>
+          | <a href="/graph/43c799df6e75?style=monoblue">(0)</a> <a href="/graph/tip?style=monoblue">tip</a> 
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/xyzzy?style=monoblue' | egrep $REVLINKS
+              <li><a href="/graph/xyzzy?style=monoblue">graph</a></li>
+          <li><a href="/rev/xyzzy?style=monoblue">changeset</a></li>
+          <li><a href="/archive/xyzzy.zip">zip</a></li>
+              <td><a href="/file/xyzzy/?style=monoblue">[up]</a></td>
+  <a href="/file/xyzzy/dir?style=monoblue">dir</a>
+  <a href="/file/xyzzy/dir/?style=monoblue"></a>
+  <td><a href="/file/xyzzy/dir?style=monoblue">files</a></td>
+  <td><a href="/file/xyzzy/foo?style=monoblue">foo</a></td>
+  <a href="/file/xyzzy/foo?style=monoblue">file</a> |
+  <a href="/log/xyzzy/foo?style=monoblue">revisions</a> |
+  <a href="/annotate/xyzzy/foo?style=monoblue">annotate</a>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/xyzzy/foo?style=monoblue' | egrep $REVLINKS
+              <li><a href="/graph/xyzzy?style=monoblue">graph</a></li>
+              <li><a href="/file/xyzzy/?style=monoblue">files</a></li>
+          <li><a href="/log/xyzzy/foo?style=monoblue">revisions</a></li>
+          <li><a href="/annotate/xyzzy/foo?style=monoblue">annotate</a></li>
+          <li><a href="/diff/xyzzy/foo?style=monoblue">diff</a></li>
+          <li><a href="/comparison/xyzzy/foo?style=monoblue">comparison</a></li>
+          <li><a href="/raw-file/xyzzy/foo">raw</a></li>
+          <dd><a class="list" href="/rev/a7c1559b7bba?style=monoblue">a7c1559b7bba</a></dd>
+  <a href="/file/43c799df6e75/foo?style=monoblue">
+  <a href="/file/9d8c40cba617/foo?style=monoblue">9d8c40cba617</a>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/xyzzy/foo?style=monoblue' | egrep $REVLINKS
+              <li><a href="/graph/xyzzy?style=monoblue">graph</a></li>
+              <li><a href="/file/xyzzy?style=monoblue">files</a></li>
+          <li><a href="/file/xyzzy/foo?style=monoblue">file</a></li>
+          <li><a href="/annotate/xyzzy/foo?style=monoblue">annotate</a></li>
+          <li><a href="/diff/xyzzy/foo?style=monoblue">diff</a></li>
+          <li><a href="/comparison/xyzzy/foo?style=monoblue">comparison</a></li>
+          <li><a href="/rss-log/tip/foo">rss</a></li>
+  <a href="/rev/a7c1559b7bba?style=monoblue">
+  <a href="/file/a7c1559b7bba/foo?style=monoblue">file</a>&nbsp;|&nbsp;<a href="/diff/a7c1559b7bba/foo?style=monoblue">diff</a>&nbsp;|&nbsp;<a href="/annotate/a7c1559b7bba/foo?style=monoblue">annotate</a>
+  <a href="/rev/43c799df6e75?style=monoblue">
+  <a href="/file/43c799df6e75/foo?style=monoblue">file</a>&nbsp;|&nbsp;<a href="/diff/43c799df6e75/foo?style=monoblue">diff</a>&nbsp;|&nbsp;<a href="/annotate/43c799df6e75/foo?style=monoblue">annotate</a>
+      <a href="/log/43c799df6e75/foo?style=monoblue">(0)</a><a href="/log/tip/foo?style=monoblue">tip</a>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'annotate/xyzzy/foo?style=monoblue' | egrep $REVLINKS
+              <li><a href="/graph/xyzzy?style=monoblue">graph</a></li>
+              <li><a href="/file/xyzzy/?style=monoblue">files</a></li>
+          <li><a href="/file/xyzzy/foo?style=monoblue">file</a></li>
+          <li><a href="/log/xyzzy/foo?style=monoblue">revisions</a></li>
+          <li><a href="/diff/xyzzy/foo?style=monoblue">diff</a></li>
+          <li><a href="/comparison/xyzzy/foo?style=monoblue">comparison</a></li>
+          <li><a href="/raw-annotate/xyzzy/foo">raw</a></li>
+          <dd><a href="/rev/a7c1559b7bba?style=monoblue">a7c1559b7bba</a></dd>
+  <a href="/annotate/43c799df6e75/foo?style=monoblue">
+  <a href="/annotate/9d8c40cba617/foo?style=monoblue">9d8c40cba617</a>
+  <a href="/annotate/43c799df6e75/foo?style=monoblue#l1"
+  <a href="/annotate/a7c1559b7bba/foo?style=monoblue#l2"
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'diff/xyzzy/foo?style=monoblue' | egrep $REVLINKS
+              <li><a href="/graph/xyzzy?style=monoblue">graph</a></li>
+              <li><a href="/file/xyzzy?style=monoblue">files</a></li>
+          <li><a href="/file/xyzzy/foo?style=monoblue">file</a></li>
+          <li><a href="/log/xyzzy/foo?style=monoblue">revisions</a></li>
+          <li><a href="/annotate/xyzzy/foo?style=monoblue">annotate</a></li>
+          <li><a href="/comparison/xyzzy/foo?style=monoblue">comparison</a></li>
+          <li><a href="/raw-diff/xyzzy/foo">raw</a></li>
+          <dd><a href="/rev/a7c1559b7bba?style=monoblue">a7c1559b7bba</a></dd>
+  <dd><a href="/diff/43c799df6e75/foo?style=monoblue">43c799df6e75</a></dd>
+  <dd><a href="/diff/9d8c40cba617/foo?style=monoblue">9d8c40cba617</a></dd>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'comparison/xyzzy/foo?style=monoblue' | egrep $REVLINKS
+              <li><a href="/graph/xyzzy?style=monoblue">graph</a></li>
+              <li><a href="/file/xyzzy?style=monoblue">files</a></li>
+          <li><a href="/file/xyzzy/foo?style=monoblue">file</a></li>
+          <li><a href="/log/xyzzy/foo?style=monoblue">revisions</a></li>
+          <li><a href="/annotate/xyzzy/foo?style=monoblue">annotate</a></li>
+          <li><a href="/diff/xyzzy/foo?style=monoblue">diff</a></li>
+          <li><a href="/raw-diff/xyzzy/foo">raw</a></li>
+          <dd><a href="/rev/a7c1559b7bba?style=monoblue">a7c1559b7bba</a></dd>
+  <dd><a href="/comparison/43c799df6e75/foo?style=monoblue">43c799df6e75</a></dd>
+  <dd><a href="/comparison/9d8c40cba617/foo?style=monoblue">9d8c40cba617</a></dd>
+
+(De)referencing symbolic revisions (spartan)
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog?style=spartan' | egrep $REVLINKS
+  <a href="/log/tip?style=spartan">changelog</a>
+  <a href="/graph/tip?style=spartan">graph</a>
+  <a href="/file/tip/?style=spartan">files</a>
+  <a href="/archive/tip.zip">zip</a> 
+  navigate: <small class="navigate"><a href="/shortlog/43c799df6e75?style=spartan">(0)</a> <a href="/shortlog/tip?style=spartan">tip</a> </small>
+    <td class="node"><a href="/rev/9d8c40cba617?style=spartan">third</a></td>
+    <td class="node"><a href="/rev/a7c1559b7bba?style=spartan">second</a></td>
+    <td class="node"><a href="/rev/43c799df6e75?style=spartan">first</a></td>
+  navigate: <small class="navigate"><a href="/shortlog/43c799df6e75?style=spartan">(0)</a> <a href="/shortlog/tip?style=spartan">tip</a> </small>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log?style=spartan' | egrep $REVLINKS
+  <a href="/shortlog/tip?style=spartan">shortlog</a>
+  <a href="/graph/tip?style=spartan">graph</a>
+  <a href="/file/tip?style=spartan">files</a>
+  <a href="/archive/tip.zip">zip</a> 
+  navigate: <small class="navigate"><a href="/log/43c799df6e75?style=spartan">(0)</a>  <a href="/log/tip?style=spartan">tip</a> </small>
+    <td class="node"><a href="/rev/9d8c40cba617?style=spartan">9d8c40cba617</a></td>
+    <th class="files"><a href="/file/9d8c40cba617?style=spartan">files</a>:</th>
+    <td class="files"><a href="/diff/9d8c40cba617/foo?style=spartan">foo</a> </td>
+    <td class="node"><a href="/rev/a7c1559b7bba?style=spartan">a7c1559b7bba</a></td>
+    <th class="files"><a href="/file/a7c1559b7bba?style=spartan">files</a>:</th>
+    <td class="files"><a href="/diff/a7c1559b7bba/foo?style=spartan">foo</a> </td>
+    <td class="node"><a href="/rev/43c799df6e75?style=spartan">43c799df6e75</a></td>
+    <th class="files"><a href="/file/43c799df6e75?style=spartan">files</a>:</th>
+    <td class="files"><a href="/diff/43c799df6e75/dir/bar?style=spartan">dir/bar</a> <a href="/diff/43c799df6e75/foo?style=spartan">foo</a> </td>
+  navigate: <small class="navigate"><a href="/log/43c799df6e75?style=spartan">(0)</a>  <a href="/log/tip?style=spartan">tip</a> </small>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph?style=spartan' | egrep $REVLINKS
+  <a href="/log/tip?style=spartan">changelog</a>
+  <a href="/shortlog/tip?style=spartan">shortlog</a>
+  <a href="/file/tip/?style=spartan">files</a>
+  navigate: <small class="navigate"><a href="/graph/43c799df6e75?style=spartan">(0)</a> <a href="/graph/tip?style=spartan">tip</a> </small>
+  navigate: <small class="navigate"><a href="/graph/43c799df6e75?style=spartan">(0)</a> <a href="/graph/tip?style=spartan">tip</a> </small>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'tags?style=spartan' | egrep $REVLINKS
+  <a href="/rev/9d8c40cba617?style=spartan">tip</a>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'branches?style=spartan' | egrep $REVLINKS
+  <a href="/shortlog/9d8c40cba617?style=spartan" class="open">default</a>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file?style=spartan' | egrep $REVLINKS
+  <a href="/log/tip?style=spartan">changelog</a>
+  <a href="/shortlog/tip?style=spartan">shortlog</a>
+  <a href="/graph/tip?style=spartan">graph</a>
+  <a href="/rev/tip?style=spartan">changeset</a>
+  <a href="/archive/tip.zip">zip</a> 
+  <h2><a href="/">Mercurial</a>  / files for changeset <a href="/rev/9d8c40cba617">9d8c40cba617</a>: /</h2>
+    <td><a href="/file/tip/?style=spartan">[up]</a>
+  <a href="/file/tip/dir?style=spartan">dir/</a>
+  <a href="/file/tip/dir/?style=spartan">
+  <td><a href="/file/tip/foo?style=spartan">foo</a>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog?style=spartan&rev=all()' | egrep $REVLINKS
+  <a href="/archive/tip.zip">zip</a> 
+    <td class="node"><a href="/rev/9d8c40cba617?style=spartan">9d8c40cba617</a></td>
+  <a href="/rev/a7c1559b7bba?style=spartan">a7c1559b7bba</a>
+    <th class="files"><a href="/file/9d8c40cba617?style=spartan">files</a>:</th>
+    <td class="files"><a href="/diff/9d8c40cba617/foo?style=spartan">foo</a> </td>
+    <td class="node"><a href="/rev/a7c1559b7bba?style=spartan">a7c1559b7bba</a></td>
+  <a href="/rev/43c799df6e75?style=spartan">43c799df6e75</a>
+  <td class="child"><a href="/rev/9d8c40cba617?style=spartan">9d8c40cba617</a></td>
+    <th class="files"><a href="/file/a7c1559b7bba?style=spartan">files</a>:</th>
+    <td class="files"><a href="/diff/a7c1559b7bba/foo?style=spartan">foo</a> </td>
+    <td class="node"><a href="/rev/43c799df6e75?style=spartan">43c799df6e75</a></td>
+  <td class="child"><a href="/rev/a7c1559b7bba?style=spartan">a7c1559b7bba</a></td>
+    <th class="files"><a href="/file/43c799df6e75?style=spartan">files</a>:</th>
+    <td class="files"><a href="/diff/43c799df6e75/dir/bar?style=spartan">dir/bar</a> <a href="/diff/43c799df6e75/foo?style=spartan">foo</a> </td>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'rev/xyzzy?style=spartan' | egrep $REVLINKS
+  <a href="/log/xyzzy?style=spartan">changelog</a>
+  <a href="/shortlog/xyzzy?style=spartan">shortlog</a>
+  <a href="/graph/xyzzy?style=spartan">graph</a>
+  <a href="/file/xyzzy?style=spartan">files</a>
+  <a href="/raw-rev/xyzzy">raw</a>
+  <a href="/archive/xyzzy.zip">zip</a> 
+   <td class="changeset"><a href="/rev/a7c1559b7bba?style=spartan">a7c1559b7bba</a></td>
+  <td class="parent"><a href="/rev/43c799df6e75?style=spartan">43c799df6e75</a></td>
+  <td class="child"><a href="/rev/9d8c40cba617?style=spartan">9d8c40cba617</a></td>
+   <td class="files"><a href="/file/a7c1559b7bba/foo?style=spartan">foo</a> </td>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'shortlog/xyzzy?style=spartan' | egrep $REVLINKS
+  <a href="/log/xyzzy?style=spartan">changelog</a>
+  <a href="/graph/xyzzy?style=spartan">graph</a>
+  <a href="/file/xyzzy/?style=spartan">files</a>
+  <a href="/archive/xyzzy.zip">zip</a> 
+  navigate: <small class="navigate"><a href="/shortlog/43c799df6e75?style=spartan">(0)</a> <a href="/shortlog/tip?style=spartan">tip</a> </small>
+    <td class="node"><a href="/rev/a7c1559b7bba?style=spartan">second</a></td>
+    <td class="node"><a href="/rev/43c799df6e75?style=spartan">first</a></td>
+  navigate: <small class="navigate"><a href="/shortlog/43c799df6e75?style=spartan">(0)</a> <a href="/shortlog/tip?style=spartan">tip</a> </small>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/xyzzy?style=spartan' | egrep $REVLINKS
+  <a href="/shortlog/xyzzy?style=spartan">shortlog</a>
+  <a href="/graph/xyzzy?style=spartan">graph</a>
+  <a href="/file/xyzzy?style=spartan">files</a>
+  <a href="/archive/xyzzy.zip">zip</a> 
+  navigate: <small class="navigate"><a href="/log/43c799df6e75?style=spartan">(0)</a>  <a href="/log/tip?style=spartan">tip</a> </small>
+    <td class="node"><a href="/rev/a7c1559b7bba?style=spartan">a7c1559b7bba</a></td>
+    <th class="files"><a href="/file/a7c1559b7bba?style=spartan">files</a>:</th>
+    <td class="files"><a href="/diff/a7c1559b7bba/foo?style=spartan">foo</a> </td>
+    <td class="node"><a href="/rev/43c799df6e75?style=spartan">43c799df6e75</a></td>
+    <th class="files"><a href="/file/43c799df6e75?style=spartan">files</a>:</th>
+    <td class="files"><a href="/diff/43c799df6e75/dir/bar?style=spartan">dir/bar</a> <a href="/diff/43c799df6e75/foo?style=spartan">foo</a> </td>
+  navigate: <small class="navigate"><a href="/log/43c799df6e75?style=spartan">(0)</a>  <a href="/log/tip?style=spartan">tip</a> </small>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'graph/xyzzy?style=spartan' | egrep $REVLINKS
+  <a href="/log/xyzzy?style=spartan">changelog</a>
+  <a href="/shortlog/xyzzy?style=spartan">shortlog</a>
+  <a href="/file/xyzzy/?style=spartan">files</a>
+  navigate: <small class="navigate"><a href="/graph/43c799df6e75?style=spartan">(0)</a> <a href="/graph/tip?style=spartan">tip</a> </small>
+  navigate: <small class="navigate"><a href="/graph/43c799df6e75?style=spartan">(0)</a> <a href="/graph/tip?style=spartan">tip</a> </small>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/xyzzy?style=spartan' | egrep $REVLINKS
+  <a href="/log/xyzzy?style=spartan">changelog</a>
+  <a href="/shortlog/xyzzy?style=spartan">shortlog</a>
+  <a href="/graph/xyzzy?style=spartan">graph</a>
+  <a href="/rev/xyzzy?style=spartan">changeset</a>
+  <a href="/archive/xyzzy.zip">zip</a> 
+  <h2><a href="/">Mercurial</a>  / files for changeset <a href="/rev/a7c1559b7bba">a7c1559b7bba</a>: /</h2>
+    <td><a href="/file/xyzzy/?style=spartan">[up]</a>
+  <a href="/file/xyzzy/dir?style=spartan">dir/</a>
+  <a href="/file/xyzzy/dir/?style=spartan">
+  <td><a href="/file/xyzzy/foo?style=spartan">foo</a>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/xyzzy/foo?style=spartan' | egrep $REVLINKS
+  <a href="/log/xyzzy?style=spartan">changelog</a>
+  <a href="/shortlog/xyzzy?style=spartan">shortlog</a>
+  <a href="/graph/xyzzy?style=spartan">graph</a>
+  <a href="/rev/xyzzy?style=spartan">changeset</a>
+  <a href="/file/xyzzy/?style=spartan">files</a>
+  <a href="/log/xyzzy/foo?style=spartan">revisions</a>
+  <a href="/annotate/xyzzy/foo?style=spartan">annotate</a>
+  <a href="/raw-file/xyzzy/foo">raw</a>
+   <td><a href="/rev/a7c1559b7bba?style=spartan">a7c1559b7bba</a></td>
+  <a href="/file/43c799df6e75/foo?style=spartan">
+  <td><a href="/file/9d8c40cba617/foo?style=spartan">9d8c40cba617</a></td>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'log/xyzzy/foo?style=spartan' | egrep $REVLINKS
+     href="/atom-log/tip/foo" title="Atom feed for test:foo">
+     href="/rss-log/tip/foo" title="RSS feed for test:foo">
+  <a href="/file/xyzzy/foo?style=spartan">file</a>
+  <a href="/annotate/xyzzy/foo?style=spartan">annotate</a>
+  <a type="application/rss+xml" href="/rss-log/tip/foo">rss</a>
+  <a type="application/atom+xml" href="/atom-log/tip/foo" title="Atom feed for test:foo">atom</a>
+  <p>navigate: <small class="navigate"><a href="/log/43c799df6e75/foo?style=spartan">(0)</a> <a href="/log/tip/foo?style=spartan">tip</a> </small></p>
+    <th class="firstline"><a href="/rev/a7c1559b7bba?style=spartan">second</a></th>
+     <a href="/file/a7c1559b7bba/foo?style=spartan">a7c1559b7bba</a>
+     <a href="/diff/a7c1559b7bba/foo?style=spartan">(diff)</a>
+     <a href="/annotate/a7c1559b7bba/foo?style=spartan">(annotate)</a>
+    <th class="firstline"><a href="/rev/43c799df6e75?style=spartan">first</a></th>
+     <a href="/file/43c799df6e75/foo?style=spartan">43c799df6e75</a>
+     <a href="/diff/43c799df6e75/foo?style=spartan">(diff)</a>
+     <a href="/annotate/43c799df6e75/foo?style=spartan">(annotate)</a>
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'annotate/xyzzy/foo?style=spartan' | egrep $REVLINKS
+  <a href="/log/xyzzy?style=spartan">changelog</a>
+  <a href="/shortlog/xyzzy?style=spartan">shortlog</a>
+  <a href="/graph/xyzzy?style=spartan">graph</a>
+  <a href="/rev/xyzzy?style=spartan">changeset</a>
+  <a href="/file/xyzzy/?style=spartan">files</a>
+  <a href="/file/xyzzy/foo?style=spartan">file</a>
+  <a href="/log/xyzzy/foo?style=spartan">revisions</a>
+  <a href="/raw-annotate/xyzzy/foo">raw</a>
+   <td><a href="/rev/a7c1559b7bba?style=spartan">a7c1559b7bba</a></td>
+  <a href="/annotate/43c799df6e75/foo?style=spartan">
+  <td><a href="/annotate/9d8c40cba617/foo?style=spartan">9d8c40cba617</a></td>
+  <a href="/annotate/43c799df6e75/foo?style=spartan#l1"
+  <a href="/annotate/a7c1559b7bba/foo?style=spartan#l2"
+
+  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'diff/xyzzy/foo?style=spartan' | egrep $REVLINKS
+  <a href="/log/xyzzy?style=spartan">changelog</a>
+  <a href="/shortlog/xyzzy?style=spartan">shortlog</a>
+  <a href="/graph/xyzzy?style=spartan">graph</a>
+  <a href="/rev/xyzzy?style=spartan">changeset</a>
+  <a href="/file/xyzzy/foo?style=spartan">file</a>
+  <a href="/log/xyzzy/foo?style=spartan">revisions</a>
+  <a href="/annotate/xyzzy/foo?style=spartan">annotate</a>
+  <a href="/raw-diff/xyzzy/foo">raw</a>
+   <td class="revision"><a href="/rev/a7c1559b7bba?style=spartan">a7c1559b7bba</a></td>
+  <td class="parent"><a href="/rev/43c799df6e75?style=spartan">43c799df6e75</a></td>
+  <td class="child"><a href="/rev/9d8c40cba617?style=spartan">9d8c40cba617</a></td>
+
+Done
+
+  $ cat errors.log
+  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ cd ..
--- a/tests/test-hgweb.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-hgweb.t	Sat Jul 18 17:32:38 2015 -0500
@@ -10,12 +10,15 @@
   $ hg ci -Ambase
   adding da/foo
   adding foo
+  $ hg bookmark -r0 '@'
+  $ hg bookmark -r0 'a b c'
+  $ hg bookmark -r0 'd/e/f'
   $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
   $ cat hg.pid >> $DAEMON_PIDS
 
 manifest
 
-  $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/?style=raw')
+  $ (get-with-headers.py localhost:$HGPORT 'file/tip/?style=raw')
   200 Script output follows
   
   
@@ -23,7 +26,7 @@
   -rw-r--r-- 4 foo
   
   
-  $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/da?style=raw')
+  $ (get-with-headers.py localhost:$HGPORT 'file/tip/da?style=raw')
   200 Script output follows
   
   
@@ -33,14 +36,14 @@
 
 plain file
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/foo?style=raw'
+  $ get-with-headers.py localhost:$HGPORT 'file/tip/foo?style=raw'
   200 Script output follows
   
   foo
 
 should give a 404 - static file that does not exist
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'static/bogus'
+  $ get-with-headers.py localhost:$HGPORT 'static/bogus'
   404 Not Found
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -106,7 +109,7 @@
 
 should give a 404 - bad revision
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/spam/foo?style=raw'
+  $ get-with-headers.py localhost:$HGPORT 'file/spam/foo?style=raw'
   404 Not Found
   
   
@@ -115,40 +118,40 @@
 
 should give a 400 - bad command
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/foo?cmd=spam&style=raw'
+  $ get-with-headers.py localhost:$HGPORT 'file/tip/foo?cmd=spam&style=raw'
   400* (glob)
   
   
   error: no such method: spam
   [1]
 
-  $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT '?cmd=spam'
+  $ get-with-headers.py --headeronly localhost:$HGPORT '?cmd=spam'
   400 no such method: spam
   [1]
 
 should give a 400 - bad command as a part of url path (issue4071)
 
-  $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'spam'
+  $ get-with-headers.py --headeronly localhost:$HGPORT 'spam'
   400 no such method: spam
   [1]
 
-  $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'raw-spam'
+  $ get-with-headers.py --headeronly localhost:$HGPORT 'raw-spam'
   400 no such method: spam
   [1]
 
-  $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'spam/tip/foo'
+  $ get-with-headers.py --headeronly localhost:$HGPORT 'spam/tip/foo'
   400 no such method: spam
   [1]
 
 should give a 404 - file does not exist
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/bork?style=raw'
+  $ get-with-headers.py localhost:$HGPORT 'file/tip/bork?style=raw'
   404 Not Found
   
   
   error: bork@2ef0ac749a14: not found in manifest
   [1]
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/bork'
+  $ get-with-headers.py localhost:$HGPORT 'file/tip/bork'
   404 Not Found
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -211,7 +214,7 @@
   </html>
   
   [1]
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'diff/tip/bork?style=raw'
+  $ get-with-headers.py localhost:$HGPORT 'diff/tip/bork?style=raw'
   404 Not Found
   
   
@@ -220,7 +223,7 @@
 
 try bad style
 
-  $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/?style=foobar')
+  $ (get-with-headers.py localhost:$HGPORT 'file/tip/?style=foobar')
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -242,14 +245,14 @@
   <img src="/static/hglogo.png" alt="mercurial" /></a>
   </div>
   <ul>
-  <li><a href="/shortlog/2ef0ac749a14">log</a></li>
-  <li><a href="/graph/2ef0ac749a14">graph</a></li>
+  <li><a href="/shortlog/tip">log</a></li>
+  <li><a href="/graph/tip">graph</a></li>
   <li><a href="/tags">tags</a></li>
   <li><a href="/bookmarks">bookmarks</a></li>
   <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
-  <li><a href="/rev/2ef0ac749a14">changeset</a></li>
+  <li><a href="/rev/tip">changeset</a></li>
   <li class="active">browse</li>
   </ul>
   <ul>
@@ -262,7 +265,10 @@
   
   <div class="main">
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
-  <h3>directory / @ 0:2ef0ac749a14 <span class="tag">tip</span> </h3>
+  <h3>
+   directory / @ 0:<a href="/rev/2ef0ac749a14">2ef0ac749a14</a>
+   <span class="tag">tip</span> <span class="tag">@</span> <span class="tag">a b c</span> <span class="tag">d/e/f</span> 
+  </h3>
   
   <form class="search" action="/log">
   
@@ -281,17 +287,17 @@
   </thead>
   <tbody class="stripes2">
   <tr class="fileline">
-    <td class="name"><a href="/file/2ef0ac749a14/">[up]</a></td>
+    <td class="name"><a href="/file/tip/">[up]</a></td>
     <td class="size"></td>
     <td class="permissions">drwxr-xr-x</td>
   </tr>
   
   <tr class="fileline">
   <td class="name">
-  <a href="/file/2ef0ac749a14/da">
+  <a href="/file/tip/da">
   <img src="/static/coal-folder.png" alt="dir."/> da/
   </a>
-  <a href="/file/2ef0ac749a14/da/">
+  <a href="/file/tip/da/">
   
   </a>
   </td>
@@ -301,7 +307,7 @@
   
   <tr class="fileline">
   <td class="filename">
-  <a href="/file/2ef0ac749a14/foo">
+  <a href="/file/tip/foo">
   <img src="/static/coal-file.png" alt="file"/> foo
   </a>
   </td>
@@ -321,7 +327,7 @@
 
 stop and restart
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
   $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log
   $ cat hg.pid >> $DAEMON_PIDS
 
@@ -332,7 +338,7 @@
 
 static file
 
-  $ "$TESTDIR/get-with-headers.py" --twice localhost:$HGPORT 'static/style-gitweb.css' - date etag server
+  $ get-with-headers.py --twice localhost:$HGPORT 'static/style-gitweb.css' - date etag server
   200 Script output follows
   content-length: 5372
   content-type: text/css
@@ -540,7 +546,7 @@
 
   $ echo bar >> foo
   $ hg ci -msecret --secret
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log?style=raw'
+  $ get-with-headers.py localhost:$HGPORT 'log?style=raw'
   200 Script output follows
   
   
@@ -554,10 +560,13 @@
   summary:     base
   branch:      default
   tag:         tip
+  bookmark:    @
+  bookmark:    a b c
+  bookmark:    d/e/f
   
   
   $ hg phase --draft tip
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log?style=raw'
+  $ get-with-headers.py localhost:$HGPORT 'log?style=raw'
   200 Script output follows
   
   
@@ -577,9 +586,30 @@
   user:        test
   date:        Thu, 01 Jan 1970 00:00:00 +0000
   summary:     base
+  bookmark:    @
+  bookmark:    a b c
+  bookmark:    d/e/f
   
   
 
+access bookmarks
+
+  $ get-with-headers.py localhost:$HGPORT 'rev/@?style=paper' | egrep '^200|changeset 0:'
+  200 Script output follows
+   changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
+
+  $ get-with-headers.py localhost:$HGPORT 'rev/%40?style=paper' | egrep '^200|changeset 0:'
+  200 Script output follows
+   changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
+
+  $ get-with-headers.py localhost:$HGPORT 'rev/a%20b%20c?style=paper' | egrep '^200|changeset 0:'
+  200 Script output follows
+   changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
+
+  $ get-with-headers.py localhost:$HGPORT 'rev/d%252Fe%252Ff?style=paper' | egrep '^200|changeset 0:'
+  200 Script output follows
+   changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
+
 no style can be loaded from directories other than the specified paths
 
   $ mkdir -p x/templates/fallback
@@ -594,27 +624,27 @@
   > mimetype = 'text/plain'
   > EOF
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
   $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log \
   > --config web.style=fallback --config web.templates=x/templates
   $ cat hg.pid >> $DAEMON_PIDS
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT "?style=`pwd`/x"
+  $ get-with-headers.py localhost:$HGPORT "?style=`pwd`/x"
   200 Script output follows
   
   fall back to default
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '?style=..'
+  $ get-with-headers.py localhost:$HGPORT '?style=..'
   200 Script output follows
   
   fall back to default
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '?style=./..'
+  $ get-with-headers.py localhost:$HGPORT '?style=./..'
   200 Script output follows
   
   fall back to default
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '?style=.../.../'
+  $ get-with-headers.py localhost:$HGPORT '?style=.../.../'
   200 Script output follows
   
   fall back to default
@@ -625,18 +655,18 @@
 
 Uncaught exceptions result in a logged error and canned HTTP response
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
   $ hg --config extensions.hgweberror=$TESTDIR/hgweberror.py serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
   $ cat hg.pid >> $DAEMON_PIDS
 
-  $ $TESTDIR/get-with-headers.py localhost:$HGPORT 'raiseerror' transfer-encoding content-type
+  $ get-with-headers.py localhost:$HGPORT 'raiseerror' transfer-encoding content-type
   500 Internal Server Error
   transfer-encoding: chunked
   
   Internal Server Error (no-eol)
   [1]
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
   $ head -1 errors.log
   .* Exception happened during processing request '/raiseerror': (re)
 
@@ -644,7 +674,7 @@
 
   $ hg --config extensions.hgweberror=$TESTDIR/hgweberror.py serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
   $ cat hg.pid >> $DAEMON_PIDS
-  $ $TESTDIR/get-with-headers.py localhost:$HGPORT 'raiseerror?partialresponse=1' transfer-encoding content-type
+  $ get-with-headers.py localhost:$HGPORT 'raiseerror?partialresponse=1' transfer-encoding content-type
   200 Script output follows
   transfer-encoding: chunked
   content-type: text/plain
@@ -652,5 +682,5 @@
   partial content
   Internal Server Error (no-eol)
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
   $ cd ..
--- a/tests/test-hgwebdir.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-hgwebdir.t	Sat Jul 18 17:32:38 2015 -0500
@@ -84,7 +84,7 @@
 
 should give a 404 - file does not exist
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'a/file/tip/bork?style=raw'
+  $ get-with-headers.py localhost:$HGPORT 'a/file/tip/bork?style=raw'
   404 Not Found
   
   
@@ -93,25 +93,25 @@
 
 should succeed
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '?style=raw'
+  $ get-with-headers.py localhost:$HGPORT '?style=raw'
   200 Script output follows
   
   
   /a/
   /b/
   
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'a/file/tip/a?style=raw'
+  $ get-with-headers.py localhost:$HGPORT 'a/file/tip/a?style=raw'
   200 Script output follows
   
   a
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'b/file/tip/b?style=raw'
+  $ get-with-headers.py localhost:$HGPORT 'b/file/tip/b?style=raw'
   200 Script output follows
   
   b
 
 should give a 404 - repo is not published
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'c/file/tip/c?style=raw'
+  $ get-with-headers.py localhost:$HGPORT 'c/file/tip/c?style=raw'
   404 Not Found
   
   
@@ -120,14 +120,14 @@
 
 atom-log without basedir
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'a/atom-log' | grep '<link'
+  $ get-with-headers.py localhost:$HGPORT 'a/atom-log' | grep '<link'
    <link rel="self" href="http://*:$HGPORT/a/atom-log"/> (glob)
    <link rel="alternate" href="http://*:$HGPORT/a/"/> (glob)
     <link href="http://*:$HGPORT/a/rev/8580ff50825a"/> (glob)
 
 rss-log without basedir
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'a/rss-log' | grep '<guid'
+  $ get-with-headers.py localhost:$HGPORT 'a/rss-log' | grep '<guid'
       <guid isPermaLink="true">http://*:$HGPORT/a/rev/8580ff50825a</guid> (glob)
   $ cat > paths.conf <<EOF
   > [paths]
@@ -145,7 +145,7 @@
 
 should succeed, slashy names
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 '?style=raw'
   200 Script output follows
   
   
@@ -184,7 +184,7 @@
   /astar/
   /astar/.hg/patches/
   
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '?style=paper'
+  $ get-with-headers.py localhost:$HGPORT1 '?style=paper'
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -672,19 +672,19 @@
   </body>
   </html>
   
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 't?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 't?style=raw'
   200 Script output follows
   
   
   /t/a/
   
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 't/?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 't/?style=raw'
   200 Script output follows
   
   
   /t/a/
   
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 't/?style=paper'
+  $ get-with-headers.py localhost:$HGPORT1 't/?style=paper'
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -743,7 +743,7 @@
   </body>
   </html>
   
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 't/a?style=atom'
+  $ get-with-headers.py localhost:$HGPORT1 't/a?style=atom'
   200 Script output follows
   
   <?xml version="1.0" encoding="ascii"?>
@@ -800,7 +800,7 @@
    </entry>
   
   </feed>
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 't/a/?style=atom'
+  $ get-with-headers.py localhost:$HGPORT1 't/a/?style=atom'
   200 Script output follows
   
   <?xml version="1.0" encoding="ascii"?>
@@ -857,14 +857,14 @@
    </entry>
   
   </feed>
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 't/a/file/tip/a?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 't/a/file/tip/a?style=raw'
   200 Script output follows
   
   a
 
 Test [paths] '*' extension
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'coll/?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 'coll/?style=raw'
   200 Script output follows
   
   
@@ -875,14 +875,14 @@
   /coll/notrepo/e/
   /coll/notrepo/f/
   
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'coll/a/file/tip/a?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 'coll/a/file/tip/a?style=raw'
   200 Script output follows
   
   a
 
 Test [paths] '**' extension
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 'rcoll/?style=raw'
   200 Script output follows
   
   
@@ -896,14 +896,14 @@
   /rcoll/notrepo/f/
   /rcoll/notrepo/f/f2/
   
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/b/d/file/tip/d?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 'rcoll/b/d/file/tip/d?style=raw'
   200 Script output follows
   
   d
 
 Test collapse = True
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
   $ cat >> paths.conf <<EOF
   > [web]
   > collapse=true
@@ -912,7 +912,7 @@
   $ hg serve -p $HGPORT1 -d --pid-file=hg.pid --webdir-conf paths.conf \
   >     -A access-paths.log -E error-paths-3.log
   $ cat hg.pid >> $DAEMON_PIDS
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'coll/?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 'coll/?style=raw'
   200 Script output follows
   
   
@@ -922,11 +922,11 @@
   /coll/c/
   /coll/notrepo/
   
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'coll/a/file/tip/a?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 'coll/a/file/tip/a?style=raw'
   200 Script output follows
   
   a
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 'rcoll/?style=raw'
   200 Script output follows
   
   
@@ -937,7 +937,7 @@
   /rcoll/c/
   /rcoll/notrepo/
   
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/b/d/file/tip/d?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 'rcoll/b/d/file/tip/d?style=raw'
   200 Script output follows
   
   d
@@ -952,7 +952,7 @@
   > hidden = True
   > EOF
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/notrepo/?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 'rcoll/notrepo/?style=raw'
   200 Script output follows
   
   
@@ -963,7 +963,7 @@
 Subrepo parent not hidden
   $ mv $root/notrepo/f/.hg/hgrc.bak $root/notrepo/f/.hg/hgrc
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/notrepo/?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 'rcoll/notrepo/?style=raw'
   200 Script output follows
   
   
@@ -975,28 +975,28 @@
 
 Test repositories inside intermediate directories
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/notrepo/e/file/tip/e?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 'rcoll/notrepo/e/file/tip/e?style=raw'
   200 Script output follows
   
   e
 
 Test subrepositories inside intermediate directories
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/notrepo/f/f2/file/tip/f2?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 'rcoll/notrepo/f/f2/file/tip/f2?style=raw'
   200 Script output follows
   
   f2
 
 Test descend = False
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
   $ cat >> paths.conf <<EOF
   > descend=false
   > EOF
   $ hg serve -p $HGPORT1 -d --pid-file=hg.pid --webdir-conf paths.conf \
   >     -A access-paths.log -E error-paths-4.log
   $ cat hg.pid >> $DAEMON_PIDS
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'coll/?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 'coll/?style=raw'
   200 Script output follows
   
   
@@ -1004,11 +1004,11 @@
   /coll/b/
   /coll/c/
   
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'coll/a/file/tip/a?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 'coll/a/file/tip/a?style=raw'
   200 Script output follows
   
   a
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 'rcoll/?style=raw'
   200 Script output follows
   
   
@@ -1016,14 +1016,14 @@
   /rcoll/b/
   /rcoll/c/
   
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/b/d/file/tip/d?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 'rcoll/b/d/file/tip/d?style=raw'
   200 Script output follows
   
   d
 
 Test intermediate directories
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/notrepo/?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 'rcoll/notrepo/?style=raw'
   200 Script output follows
   
   
@@ -1033,14 +1033,14 @@
 
 Test repositories inside intermediate directories
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/notrepo/e/file/tip/e?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 'rcoll/notrepo/e/file/tip/e?style=raw'
   200 Script output follows
   
   e
 
 Test subrepositories inside intermediate directories
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 'rcoll/notrepo/f/f2/file/tip/f2?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 'rcoll/notrepo/f/f2/file/tip/f2?style=raw'
   200 Script output follows
   
   f2
@@ -1050,7 +1050,7 @@
   $ hg id http://localhost:$HGPORT1/astar
   8580ff50825a
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
   $ cat > paths.conf <<EOF
   > [paths]
   > t/a = $root/a
@@ -1060,7 +1060,7 @@
   $ hg serve -p $HGPORT1 -d --pid-file=hg.pid --webdir-conf paths.conf \
   >     -A access-paths.log -E error-paths-5.log
   $ cat hg.pid >> $DAEMON_PIDS
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 '?style=raw'
   200 Script output follows
   
   
@@ -1068,7 +1068,7 @@
   /t/b/
   /c/
   
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 't/?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 't/?style=raw'
   200 Script output follows
   
   
@@ -1078,7 +1078,7 @@
 
 Test collapse = True
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
   $ cat >> paths.conf <<EOF
   > [web]
   > collapse=true
@@ -1086,14 +1086,14 @@
   $ hg serve -p $HGPORT1 -d --pid-file=hg.pid --webdir-conf paths.conf \
   >     -A access-paths.log -E error-paths-6.log
   $ cat hg.pid >> $DAEMON_PIDS
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 '?style=raw'
   200 Script output follows
   
   
   /t/
   /c/
   
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 't/?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 't/?style=raw'
   200 Script output follows
   
   
@@ -1103,27 +1103,27 @@
 
 test descend = False
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
   $ cat >> paths.conf <<EOF
   > descend=false
   > EOF
   $ hg serve -p $HGPORT1 -d --pid-file=hg.pid --webdir-conf paths.conf \
   >     -A access-paths.log -E error-paths-7.log
   $ cat hg.pid >> $DAEMON_PIDS
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 '?style=raw'
   200 Script output follows
   
   
   /c/
   
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 't/?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 't/?style=raw'
   200 Script output follows
   
   
   /t/a/
   /t/b/
   
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
   $ cat > paths.conf <<EOF
   > [paths]
   > nostore = $root/nostore
@@ -1135,7 +1135,7 @@
 
 test inexistent and inaccessible repo should be ignored silently
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 ''
+  $ get-with-headers.py localhost:$HGPORT1 ''
   200 Script output follows
   
   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
@@ -1192,7 +1192,7 @@
 
 collections: should succeed
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '?style=raw'
+  $ get-with-headers.py localhost:$HGPORT2 '?style=raw'
   200 Script output follows
   
   
@@ -1203,31 +1203,31 @@
   /notrepo/e/
   /notrepo/f/
   
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 'a/file/tip/a?style=raw'
+  $ get-with-headers.py localhost:$HGPORT2 'a/file/tip/a?style=raw'
   200 Script output follows
   
   a
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 'b/file/tip/b?style=raw'
+  $ get-with-headers.py localhost:$HGPORT2 'b/file/tip/b?style=raw'
   200 Script output follows
   
   b
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 'c/file/tip/c?style=raw'
+  $ get-with-headers.py localhost:$HGPORT2 'c/file/tip/c?style=raw'
   200 Script output follows
   
   c
 
 atom-log with basedir /
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 'a/atom-log' | grep '<link'
+  $ get-with-headers.py localhost:$HGPORT2 'a/atom-log' | grep '<link'
    <link rel="self" href="http://hg.example.com:8080/a/atom-log"/>
    <link rel="alternate" href="http://hg.example.com:8080/a/"/>
     <link href="http://hg.example.com:8080/a/rev/8580ff50825a"/>
 
 rss-log with basedir /
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 'a/rss-log' | grep '<guid'
+  $ get-with-headers.py localhost:$HGPORT2 'a/rss-log' | grep '<guid'
       <guid isPermaLink="true">http://hg.example.com:8080/a/rev/8580ff50825a</guid>
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
   $ hg serve --config web.baseurl=http://hg.example.com:8080/foo/ -p $HGPORT2 -d \
   >     --pid-file=hg.pid --webdir-conf collections.conf \
   >     -A access-collections-2.log -E error-collections-2.log
@@ -1235,14 +1235,14 @@
 
 atom-log with basedir /foo/
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 'a/atom-log' | grep '<link'
+  $ get-with-headers.py localhost:$HGPORT2 'a/atom-log' | grep '<link'
    <link rel="self" href="http://hg.example.com:8080/foo/a/atom-log"/>
    <link rel="alternate" href="http://hg.example.com:8080/foo/a/"/>
     <link href="http://hg.example.com:8080/foo/a/rev/8580ff50825a"/>
 
 rss-log with basedir /foo/
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 'a/rss-log' | grep '<guid'
+  $ get-with-headers.py localhost:$HGPORT2 'a/rss-log' | grep '<guid'
       <guid isPermaLink="true">http://hg.example.com:8080/foo/a/rev/8580ff50825a</guid>
 
 paths errors 1
--- a/tests/test-hgwebdirsym.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-hgwebdirsym.t	Sat Jul 18 17:32:38 2015 -0500
@@ -33,7 +33,7 @@
 
 should succeed
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '?style=raw'
+  $ get-with-headers.py localhost:$HGPORT '?style=raw'
   200 Script output follows
   
   
@@ -41,34 +41,34 @@
   /b/
   /c/
   
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'al/file/tip/a?style=raw'
+  $ get-with-headers.py localhost:$HGPORT 'al/file/tip/a?style=raw'
   200 Script output follows
   
   a
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'b/file/tip/b?style=raw'
+  $ get-with-headers.py localhost:$HGPORT 'b/file/tip/b?style=raw'
   200 Script output follows
   
   b
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'c/file/tip/c?style=raw'
+  $ get-with-headers.py localhost:$HGPORT 'c/file/tip/c?style=raw'
   200 Script output follows
   
   c
 
 should fail
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'circle/al/file/tip/a?style=raw'
+  $ get-with-headers.py localhost:$HGPORT 'circle/al/file/tip/a?style=raw'
   404 Not Found
   
   
   error: repository circle/al/file/tip/a not found
   [1]
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'circle/b/file/tip/a?style=raw'
+  $ get-with-headers.py localhost:$HGPORT 'circle/b/file/tip/a?style=raw'
   404 Not Found
   
   
   error: repository circle/b/file/tip/a not found
   [1]
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'circle/c/file/tip/a?style=raw'
+  $ get-with-headers.py localhost:$HGPORT 'circle/c/file/tip/a?style=raw'
   404 Not Found
   
   
--- a/tests/test-highlight.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-highlight.t	Sat Jul 18 17:32:38 2015 -0500
@@ -55,7 +55,7 @@
 
 hgweb filerevision, html
 
-  $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/primes.py') \
+  $ (get-with-headers.py localhost:$HGPORT 'file/tip/primes.py') \
   >     | sed "s/class=\"k\"/class=\"kn\"/g" | sed "s/class=\"mf\"/class=\"mi\"/g"
   200 Script output follows
   
@@ -79,24 +79,24 @@
   <img src="/static/hglogo.png" alt="mercurial" /></a>
   </div>
   <ul>
-  <li><a href="/shortlog/853dcd4de2a6">log</a></li>
-  <li><a href="/graph/853dcd4de2a6">graph</a></li>
+  <li><a href="/shortlog/tip">log</a></li>
+  <li><a href="/graph/tip">graph</a></li>
   <li><a href="/tags">tags</a></li>
   <li><a href="/bookmarks">bookmarks</a></li>
   <li><a href="/branches">branches</a></li>
   </ul>
   <ul>
-  <li><a href="/rev/853dcd4de2a6">changeset</a></li>
-  <li><a href="/file/853dcd4de2a6/">browse</a></li>
+  <li><a href="/rev/tip">changeset</a></li>
+  <li><a href="/file/tip/">browse</a></li>
   </ul>
   <ul>
   <li class="active">file</li>
   <li><a href="/file/tip/primes.py">latest</a></li>
-  <li><a href="/diff/853dcd4de2a6/primes.py">diff</a></li>
-  <li><a href="/comparison/853dcd4de2a6/primes.py">comparison</a></li>
-  <li><a href="/annotate/853dcd4de2a6/primes.py">annotate</a></li>
-  <li><a href="/log/853dcd4de2a6/primes.py">file log</a></li>
-  <li><a href="/raw-file/853dcd4de2a6/primes.py">raw</a></li>
+  <li><a href="/diff/tip/primes.py">diff</a></li>
+  <li><a href="/comparison/tip/primes.py">comparison</a></li>
+  <li><a href="/annotate/tip/primes.py">annotate</a></li>
+  <li><a href="/log/tip/primes.py">file log</a></li>
+  <li><a href="/raw-file/tip/primes.py">raw</a></li>
   </ul>
   <ul>
   <li><a href="/help">help</a></li>
@@ -105,7 +105,10 @@
   
   <div class="main">
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
-  <h3>view primes.py @ 0:853dcd4de2a6</h3>
+  <h3>
+   view primes.py @ 0:<a href="/rev/853dcd4de2a6">853dcd4de2a6</a>
+   <span class="tag">tip</span> 
+  </h3>
   
   <form class="search" action="/log">
   
@@ -185,7 +188,7 @@
 
 hgweb fileannotate, html
 
-  $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'annotate/tip/primes.py') \
+  $ (get-with-headers.py localhost:$HGPORT 'annotate/tip/primes.py') \
   >     | sed "s/class=\"k\"/class=\"kn\"/g" | sed "s/class=\"mi\"/class=\"mf\"/g"
   200 Script output follows
   
@@ -209,25 +212,25 @@
   <img src="/static/hglogo.png" alt="mercurial" /></a>
   </div>
   <ul>
-  <li><a href="/shortlog/853dcd4de2a6">log</a></li>
-  <li><a href="/graph/853dcd4de2a6">graph</a></li>
+  <li><a href="/shortlog/tip">log</a></li>
+  <li><a href="/graph/tip">graph</a></li>
   <li><a href="/tags">tags</a></li>
   <li><a href="/bookmarks">bookmarks</a></li>
   <li><a href="/branches">branches</a></li>
   </ul>
   
   <ul>
-  <li><a href="/rev/853dcd4de2a6">changeset</a></li>
-  <li><a href="/file/853dcd4de2a6/">browse</a></li>
+  <li><a href="/rev/tip">changeset</a></li>
+  <li><a href="/file/tip/">browse</a></li>
   </ul>
   <ul>
-  <li><a href="/file/853dcd4de2a6/primes.py">file</a></li>
+  <li><a href="/file/tip/primes.py">file</a></li>
   <li><a href="/file/tip/primes.py">latest</a></li>
-  <li><a href="/diff/853dcd4de2a6/primes.py">diff</a></li>
-  <li><a href="/comparison/853dcd4de2a6/primes.py">comparison</a></li>
+  <li><a href="/diff/tip/primes.py">diff</a></li>
+  <li><a href="/comparison/tip/primes.py">comparison</a></li>
   <li class="active">annotate</li>
-  <li><a href="/log/853dcd4de2a6/primes.py">file log</a></li>
-  <li><a href="/raw-annotate/853dcd4de2a6/primes.py">raw</a></li>
+  <li><a href="/log/tip/primes.py">file log</a></li>
+  <li><a href="/raw-annotate/tip/primes.py">raw</a></li>
   </ul>
   <ul>
   <li><a href="/help">help</a></li>
@@ -236,7 +239,10 @@
   
   <div class="main">
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
-  <h3>annotate primes.py @ 0:853dcd4de2a6</h3>
+  <h3>
+   annotate primes.py @ 0:<a href="/rev/853dcd4de2a6">853dcd4de2a6</a>
+   <span class="tag">tip</span> 
+  </h3>
   
   <form class="search" action="/log">
   
@@ -515,7 +521,7 @@
 
 hgweb fileannotate, raw
 
-  $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'annotate/tip/primes.py?style=raw') \
+  $ (get-with-headers.py localhost:$HGPORT 'annotate/tip/primes.py?style=raw') \
   >     | sed "s/test@//" > a
   $ echo "200 Script output follows" > b
   $ echo "" >> b
@@ -529,7 +535,7 @@
 
 hgweb filerevision, raw
 
-  $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/primes.py?style=raw') \
+  $ (get-with-headers.py localhost:$HGPORT 'file/tip/primes.py?style=raw') \
   >     > a
   $ echo "200 Script output follows" > b
   $ echo "" >> b
@@ -538,7 +544,7 @@
 
 hgweb highlightcss friendly
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'highlightcss' > out
+  $ get-with-headers.py localhost:$HGPORT 'highlightcss' > out
   $ head -n 4 out
   200 Script output follows
   
@@ -549,7 +555,7 @@
 errors encountered
 
   $ cat errors.log
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
 
 Change the pygments style
 
@@ -565,7 +571,7 @@
 
 hgweb highlightcss fruity
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'highlightcss' > out
+  $ get-with-headers.py localhost:$HGPORT 'highlightcss' > out
   $ head -n 4 out
   200 Script output follows
   
@@ -583,13 +589,13 @@
   $ hg ci -Ama
   adding eucjp.txt
   $ hgserveget () {
-  >     "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  >     killdaemons.py
   >     echo % HGENCODING="$1" hg serve
   >     HGENCODING="$1" hg serve -p $HGPORT -d -n test --pid-file=hg.pid -E errors.log
   >     cat hg.pid >> $DAEMON_PIDS
   > 
   >     echo % hgweb filerevision, html
-  >     "$TESTDIR/get-with-headers.py" localhost:$HGPORT "file/tip/$2" \
+  >     get-with-headers.py localhost:$HGPORT "file/tip/$2" \
   >         | grep '<div class="parity0 source">'
   >     echo % errors encountered
   >     cat errors.log
--- a/tests/test-histedit-edit.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-histedit-edit.t	Sat Jul 18 17:32:38 2015 -0500
@@ -246,6 +246,7 @@
   branch: default
   commit: 1 added (new branch head)
   update: 1 new changesets (update)
+  phases: 7 draft
   hist:   1 remaining (histedit --continue)
 
 (test also that editor is invoked if histedit is continued for
@@ -435,3 +436,32 @@
   $ HGEDITOR=true hg histedit --continue
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   saved backup bundle to $TESTTMP/r0/.hg/strip-backup/cb9a9f314b8b-cc5ccb0b-backup.hg (glob)
+
+  $ hg log -G
+  @  changeset:   0:0efcea34f18a
+     tag:         tip
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     a
+  
+  $ echo foo >> b
+  $ hg addr
+  adding b
+  $ hg ci -m 'add b'
+  $ echo foo >> a
+  $ hg ci -m 'extend a'
+  $ hg phase --public 1
+Attempting to fold a change into a public change should not work:
+  $ cat > ../edit.sh <<EOF
+  > cat "\$1" | sed s/pick/fold/ > tmp
+  > mv tmp "\$1"
+  > EOF
+  $ HGEDITOR="sh ../edit.sh" hg histedit 2
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  reverting a
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  abort: cannot fold into public change 18aa70c8ad22
+  [255]
+TODO: this abort shouldn't be required, but it is for now to leave the repo in
+a clean state.
+  $ hg histedit --abort
--- a/tests/test-histedit-no-change.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-histedit-no-change.t	Sat Jul 18 17:32:38 2015 -0500
@@ -183,6 +183,7 @@
   branch: default
   commit: 1 added, 1 unknown (new branch head)
   update: 6 new changesets (update)
+  phases: 7 draft
   hist:   2 remaining (histedit --continue)
 
   $ hg histedit --abort 2>&1 | fixbundle
--- a/tests/test-histedit-obsolete.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-histedit-obsolete.t	Sat Jul 18 17:32:38 2015 -0500
@@ -64,12 +64,16 @@
   > fold e860deea161a 4 e
   > pick 652413bf663e 5 f
   > EOF
-  saved backup bundle to $TESTTMP/base/.hg/strip-backup/96e494a2d553-3c6c5d92-backup.hg (glob)
+  [1]
   $ hg log --graph --hidden
-  @  8:cacdfd884a93 f
+  @  10:cacdfd884a93 f
+  |
+  o  9:59d9f330561f d
   |
-  o  7:59d9f330561f d
-  |
+  | x  8:b558abc46d09 fold-temp-revision e860deea161a
+  | |
+  | x  7:96e494a2d553 d
+  |/
   o  6:b346ab9a313d c
   |
   | x  5:652413bf663e f
@@ -90,6 +94,8 @@
   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)
 
 
 Ensure hidden revision does not prevent histedit
@@ -105,7 +111,7 @@
   0 files updated, 0 files merged, 3 files removed, 0 files unresolved
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log --graph
-  @  9:c13eb81022ca f
+  @  11:c13eb81022ca f
   |
   o  6:b346ab9a313d c
   |
@@ -127,7 +133,7 @@
   $ hg up '.^'
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg log -r 'children(.)'
-  9:c13eb81022ca f (no-eol)
+  11:c13eb81022ca f (no-eol)
   $ hg histedit -r '.' --commands - <<EOF
   > edit b346ab9a313d 6 c
   > EOF
@@ -141,12 +147,12 @@
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ hg log -r 'unstable()'
-  9:c13eb81022ca f (no-eol)
+  11:c13eb81022ca f (no-eol)
 
 stabilise
 
   $ hg rebase  -r 'unstable()' -d .
-  rebasing 9:c13eb81022ca "f"
+  rebasing 11:c13eb81022ca "f"
   $ hg up tip -q
 
 Test dropping of changeset on the top of the stack
@@ -166,7 +172,7 @@
   > EOF
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg log -G
-  @  10:40db8afa467b c
+  @  12:40db8afa467b c
   |
   o  0:cb9a9f314b8b a
   
@@ -188,9 +194,9 @@
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log -G
-  @  15:ee6544123ab8 c
+  @  17:ee6544123ab8 c
   |
-  o  14:269e713e9eae g
+  o  16:269e713e9eae g
   |
   o  0:cb9a9f314b8b a
   
@@ -212,14 +218,15 @@
   $ hg ph -pv '.^'
   phase changed for 2 changesets
   $ hg log -G
-  @  11:b449568bf7fc (draft) f
+  @  13:b449568bf7fc (draft) f
   |
-  o  10:40db8afa467b (public) c
+  o  12:40db8afa467b (public) c
   |
   o  0:cb9a9f314b8b (public) a
   
   $ hg histedit -r '.~2'
-  abort: cannot edit immutable changeset: cb9a9f314b8b
+  abort: cannot edit public changeset: cb9a9f314b8b
+  (see "hg help phases" for details)
   [255]
 
 
@@ -233,19 +240,19 @@
   > done
   $ hg phase --force --secret .~2
   $ hg log -G
-  @  16:ee118ab9fa44 (secret) k
+  @  18:ee118ab9fa44 (secret) k
   |
-  o  15:3a6c53ee7f3d (secret) j
+  o  17:3a6c53ee7f3d (secret) j
   |
-  o  14:b605fb7503f2 (secret) i
+  o  16:b605fb7503f2 (secret) i
   |
-  o  13:7395e1ff83bd (draft) h
+  o  15:7395e1ff83bd (draft) h
   |
-  o  12:6b70183d2492 (draft) g
+  o  14:6b70183d2492 (draft) g
   |
-  o  11:b449568bf7fc (draft) f
+  o  13:b449568bf7fc (draft) f
   |
-  o  10:40db8afa467b (public) c
+  o  12:40db8afa467b (public) c
   |
   o  0:cb9a9f314b8b (public) a
   
@@ -283,19 +290,19 @@
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log -G
-  @  22:12e89af74238 (secret) k
+  @  24:12e89af74238 (secret) k
   |
-  o  21:636a8687b22e (secret) j
+  o  23:636a8687b22e (secret) j
   |
-  o  20:ccaf0a38653f (secret) i
+  o  22:ccaf0a38653f (secret) i
   |
-  o  19:11a89d1c2613 (draft) h
+  o  21:11a89d1c2613 (draft) h
   |
-  o  18:c1dec7ca82ea (draft) g
+  o  20:c1dec7ca82ea (draft) g
   |
-  o  17:087281e68428 (draft) f
+  o  19:087281e68428 (draft) f
   |
-  o  10:40db8afa467b (public) c
+  o  12:40db8afa467b (public) c
   |
   o  0:cb9a9f314b8b (public) a
   
@@ -332,19 +339,19 @@
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log -G
-  @  22:12e89af74238 (secret) k
+  @  24:12e89af74238 (secret) k
   |
-  o  21:636a8687b22e (secret) j
+  o  23:636a8687b22e (secret) j
   |
-  o  20:ccaf0a38653f (secret) i
+  o  22:ccaf0a38653f (secret) i
   |
-  o  19:11a89d1c2613 (draft) h
+  o  21:11a89d1c2613 (draft) h
   |
-  o  18:c1dec7ca82ea (draft) g
+  o  20:c1dec7ca82ea (draft) g
   |
-  o  17:087281e68428 (draft) f
+  o  19:087281e68428 (draft) f
   |
-  o  10:40db8afa467b (public) c
+  o  12:40db8afa467b (public) c
   |
   o  0:cb9a9f314b8b (public) a
   
@@ -374,19 +381,19 @@
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log -G
-  @  21:558246857888 (secret) k
+  @  23:558246857888 (secret) k
   |
-  o  20:28bd44768535 (secret) h
+  o  22:28bd44768535 (secret) h
   |
-  o  19:d5395202aeb9 (secret) i
+  o  21:d5395202aeb9 (secret) i
   |
-  o  18:21edda8e341b (secret) g
+  o  20:21edda8e341b (secret) g
   |
-  o  17:5ab64f3a4832 (secret) j
+  o  19:5ab64f3a4832 (secret) j
   |
-  o  11:b449568bf7fc (draft) f
+  o  13:b449568bf7fc (draft) f
   |
-  o  10:40db8afa467b (public) c
+  o  12:40db8afa467b (public) c
   |
   o  0:cb9a9f314b8b (public) a
   
@@ -427,33 +434,30 @@
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  saved backup bundle to $TESTTMP/folding/.hg/strip-backup/58019c66f35f-96092fce-backup.hg (glob)
-  saved backup bundle to $TESTTMP/folding/.hg/strip-backup/83d1858e070b-f3469cf8-backup.hg (glob)
-  saved backup bundle to $TESTTMP/folding/.hg/strip-backup/859969f5ed7e-d89a19d7-backup.hg (glob)
   $ hg log -G
-  @  19:f9daec13fb98 (secret) i
+  @  27:f9daec13fb98 (secret) i
   |
-  o  18:49807617f46a (secret) g
+  o  24:49807617f46a (secret) g
   |
-  o  17:050280826e04 (draft) h
+  o  21:050280826e04 (draft) h
   |
-  o  10:40db8afa467b (public) c
+  o  12:40db8afa467b (public) c
   |
   o  0:cb9a9f314b8b (public) a
   
-  $ hg co 18
+  $ hg co 24
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
   $ echo wat >> wat
   $ hg add wat
   $ hg ci -m 'add wat'
   created new head
-  $ hg merge 19
+  $ hg merge 27
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
   $ hg ci -m 'merge'
   $ echo not wat > wat
   $ hg ci -m 'modify wat'
-  $ hg histedit 17
+  $ hg histedit 21
   abort: cannot edit history that contains merges
   [255]
   $ cd ..
--- a/tests/test-hook.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-hook.t	Sat Jul 18 17:32:38 2015 -0500
@@ -1,6 +1,13 @@
 commit hooks can see env vars
 (and post-transaction one are run unlocked)
 
+  $ cat << EOF >> $HGRCPATH
+  > [experimental]
+  > # drop me once bundle2 is the default,
+  > # added to get test change early.
+  > bundle2-exp = True
+  > EOF
+
   $ cat > $TESTTMP/txnabort.checkargs.py <<EOF
   > def showargs(ui, repo, hooktype, **kwargs):
   >     ui.write('%s python hook: %s\n' % (hooktype, ','.join(sorted(kwargs))))
@@ -10,19 +17,19 @@
   $ cd a
   $ cat > .hg/hgrc <<EOF
   > [hooks]
-  > commit = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" commit"
-  > commit.b = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" commit.b"
-  > precommit = sh -c  "HG_LOCAL= HG_NODE= HG_TAG= python \"$TESTDIR/printenv.py\" precommit"
-  > pretxncommit = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" pretxncommit"
+  > commit = sh -c "HG_LOCAL= HG_TAG= printenv.py commit"
+  > commit.b = sh -c "HG_LOCAL= HG_TAG= printenv.py commit.b"
+  > precommit = sh -c  "HG_LOCAL= HG_NODE= HG_TAG= printenv.py precommit"
+  > pretxncommit = sh -c "HG_LOCAL= HG_TAG= printenv.py pretxncommit"
   > pretxncommit.tip = hg -q tip
-  > pre-identify = python "$TESTDIR/printenv.py" pre-identify 1
-  > pre-cat = python "$TESTDIR/printenv.py" pre-cat
-  > post-cat = python "$TESTDIR/printenv.py" post-cat
-  > pretxnopen = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" pretxnopen"
-  > pretxnclose = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" pretxnclose"
-  > txnclose = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" txnclose"
+  > pre-identify = printenv.py pre-identify 1
+  > pre-cat = printenv.py pre-cat
+  > post-cat = printenv.py post-cat
+  > pretxnopen = sh -c "HG_LOCAL= HG_TAG= printenv.py pretxnopen"
+  > pretxnclose = sh -c "HG_LOCAL= HG_TAG= printenv.py pretxnclose"
+  > txnclose = sh -c "HG_LOCAL= HG_TAG= printenv.py txnclose"
   > txnabort.0 = python:$TESTTMP/txnabort.checkargs.py:showargs
-  > txnabort.1 = sh -c "HG_LOCAL= HG_TAG= python \"$TESTDIR/printenv.py\" txnabort"
+  > txnabort.1 = sh -c "HG_LOCAL= HG_TAG= printenv.py txnabort"
   > txnclose.checklock = sh -c "hg debuglock > /dev/null"
   > EOF
   $ echo a > a
@@ -46,9 +53,9 @@
 
   $ cat > .hg/hgrc <<EOF
   > [hooks]
-  > prechangegroup = python "$TESTDIR/printenv.py" prechangegroup
-  > changegroup = python "$TESTDIR/printenv.py" changegroup
-  > incoming = python "$TESTDIR/printenv.py" incoming
+  > prechangegroup = printenv.py prechangegroup
+  > changegroup = printenv.py changegroup
+  > incoming = printenv.py incoming
   > EOF
 
 pretxncommit and commit hooks can see both parents of merge
@@ -121,8 +128,8 @@
 
   $ cd ../a
   $ cat >> .hg/hgrc <<EOF
-  > pretag = python "$TESTDIR/printenv.py" pretag
-  > tag = sh -c "HG_PARENT1= HG_PARENT2= python \"$TESTDIR/printenv.py\" tag"
+  > pretag = printenv.py pretag
+  > tag = sh -c "HG_PARENT1= HG_PARENT2= printenv.py tag"
   > EOF
   $ hg tag -d '3 0' a
   pretag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
@@ -141,7 +148,7 @@
 
 pretag hook can forbid tagging
 
-  $ echo "pretag.forbid = python \"$TESTDIR/printenv.py\" pretag.forbid 1" >> .hg/hgrc
+  $ echo "pretag.forbid = printenv.py pretag.forbid 1" >> .hg/hgrc
   $ hg tag -d '4 0' fa
   pretag hook: HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
   pretag.forbid hook: HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
@@ -157,7 +164,7 @@
 more there after
 
   $ echo "pretxncommit.forbid0 = hg tip -q" >> .hg/hgrc
-  $ echo "pretxncommit.forbid1 = python \"$TESTDIR/printenv.py\" pretxncommit.forbid 1" >> .hg/hgrc
+  $ echo "pretxncommit.forbid1 = printenv.py pretxncommit.forbid 1" >> .hg/hgrc
   $ echo z > z
   $ hg add z
   $ hg -q tip
@@ -195,7 +202,7 @@
 
 precommit hook can prevent commit
 
-  $ echo "precommit.forbid = python \"$TESTDIR/printenv.py\" precommit.forbid 1" >> .hg/hgrc
+  $ echo "precommit.forbid = printenv.py precommit.forbid 1" >> .hg/hgrc
   $ hg commit -m 'fail' -d '4 0'
   precommit hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
   precommit.forbid hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
@@ -206,14 +213,14 @@
 
 preupdate hook can prevent update
 
-  $ echo "preupdate = python \"$TESTDIR/printenv.py\" preupdate" >> .hg/hgrc
+  $ echo "preupdate = printenv.py preupdate" >> .hg/hgrc
   $ hg update 1
   preupdate hook: HG_PARENT1=ab228980c14d
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
 
 update hook
 
-  $ echo "update = python \"$TESTDIR/printenv.py\" update" >> .hg/hgrc
+  $ echo "update = printenv.py update" >> .hg/hgrc
   $ hg update
   preupdate hook: HG_PARENT1=539e4b31b6dc
   update hook: HG_ERROR=0 HG_PARENT1=539e4b31b6dc
@@ -221,38 +228,41 @@
 
 pushkey hook
 
-  $ echo "pushkey = python \"$TESTDIR/printenv.py\" pushkey" >> .hg/hgrc
+  $ echo "pushkey = printenv.py pushkey" >> .hg/hgrc
   $ cd ../b
   $ hg bookmark -r null foo
   $ hg push -B foo ../a
   pushing to ../a
   searching for changes
   no changes found
-  pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=bookmarks (glob)
-  pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_TXNNAME=bookmarks (glob)
-  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmarks (glob)
+  pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=push (glob)
+  pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_PENDING=$TESTTMP/a HG_SOURCE=push HG_TXNID=TXN:* HG_TXNNAME=push HG_URL=push (glob)
   pushkey hook: HG_KEY=foo HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_RET=1
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_SOURCE=push HG_TXNID=TXN:* HG_TXNNAME=push HG_URL=push (glob)
   exporting bookmark foo
   [1]
   $ cd ../a
 
 listkeys hook
 
-  $ echo "listkeys = python \"$TESTDIR/printenv.py\" listkeys" >> .hg/hgrc
+  $ echo "listkeys = printenv.py listkeys" >> .hg/hgrc
   $ hg bookmark -r null bar
+  pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
+  pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
   $ cd ../b
   $ hg pull -B bar ../a
   pulling from ../a
   listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
-  listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
   no changes found
+  listkeys hook: HG_NAMESPACE=phase HG_VALUES={}
+  adding remote bookmark bar
   listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
-  adding remote bookmark bar
   $ cd ../a
 
 test that prepushkey can prevent incoming keys
 
-  $ echo "prepushkey = python \"$TESTDIR/printenv.py\" prepushkey.forbid 1" >> .hg/hgrc
+  $ echo "prepushkey = printenv.py prepushkey.forbid 1" >> .hg/hgrc
   $ cd ../b
   $ hg bookmark -r null baz
   $ hg push -B baz ../a
@@ -261,17 +271,20 @@
   listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
   listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
   no changes found
-  listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
-  prepushkey.forbid hook: HG_KEY=baz HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000
+  pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=push (glob)
+  prepushkey.forbid hook: HG_BUNDLE2=1 HG_KEY=baz HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_SOURCE=push HG_TXNID=TXN:* HG_URL=push (glob)
   pushkey-abort: prepushkey hook exited with status 1
-  exporting bookmark baz failed!
-  [1]
+  abort: exporting bookmark baz failed!
+  [255]
   $ cd ../a
 
 test that prelistkeys can prevent listing keys
 
-  $ echo "prelistkeys = python \"$TESTDIR/printenv.py\" prelistkeys.forbid 1" >> .hg/hgrc
+  $ echo "prelistkeys = printenv.py prelistkeys.forbid 1" >> .hg/hgrc
   $ hg bookmark -r null quux
+  pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
+  pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
+  txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
   $ cd ../b
   $ hg pull -B quux ../a
   pulling from ../a
@@ -288,7 +301,7 @@
   3:07f3376c1e65
   $ cat > .hg/hgrc <<EOF
   > [hooks]
-  > prechangegroup.forbid = python "$TESTDIR/printenv.py" prechangegroup.forbid 1
+  > prechangegroup.forbid = printenv.py prechangegroup.forbid 1
   > EOF
   $ hg pull ../a
   pulling from ../a
@@ -303,7 +316,7 @@
   $ cat > .hg/hgrc <<EOF
   > [hooks]
   > pretxnchangegroup.forbid0 = hg tip -q
-  > pretxnchangegroup.forbid1 = python "$TESTDIR/printenv.py" pretxnchangegroup.forbid 1
+  > pretxnchangegroup.forbid1 = printenv.py pretxnchangegroup.forbid 1
   > EOF
   $ hg pull ../a
   pulling from ../a
@@ -326,15 +339,15 @@
   $ rm .hg/hgrc
   $ cat > ../a/.hg/hgrc <<EOF
   > [hooks]
-  > preoutgoing = python "$TESTDIR/printenv.py" preoutgoing
-  > outgoing = python "$TESTDIR/printenv.py" outgoing
+  > preoutgoing = printenv.py preoutgoing
+  > outgoing = printenv.py outgoing
   > EOF
   $ hg pull ../a
   pulling from ../a
   searching for changes
   preoutgoing hook: HG_SOURCE=pull
+  outgoing hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_SOURCE=pull
   adding changesets
-  outgoing hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_SOURCE=pull
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
@@ -345,7 +358,7 @@
 
 preoutgoing hook can prevent outgoing changes
 
-  $ echo "preoutgoing.forbid = python \"$TESTDIR/printenv.py\" preoutgoing.forbid 1" >> ../a/.hg/hgrc
+  $ echo "preoutgoing.forbid = printenv.py preoutgoing.forbid 1" >> ../a/.hg/hgrc
   $ hg pull ../a
   pulling from ../a
   searching for changes
@@ -359,8 +372,8 @@
   $ cd ..
   $ cat > a/.hg/hgrc <<EOF
   > [hooks]
-  > preoutgoing = python "$TESTDIR/printenv.py" preoutgoing
-  > outgoing = python "$TESTDIR/printenv.py" outgoing
+  > preoutgoing = printenv.py preoutgoing
+  > outgoing = printenv.py outgoing
   > EOF
   $ hg clone a c
   preoutgoing hook: HG_SOURCE=clone
@@ -371,7 +384,7 @@
 
 preoutgoing hook can prevent outgoing changes for local clones
 
-  $ echo "preoutgoing.forbid = python \"$TESTDIR/printenv.py\" preoutgoing.forbid 1" >> a/.hg/hgrc
+  $ echo "preoutgoing.forbid = printenv.py preoutgoing.forbid 1" >> a/.hg/hgrc
   $ hg clone a zzz
   preoutgoing hook: HG_SOURCE=clone
   preoutgoing.forbid hook: HG_SOURCE=clone
--- a/tests/test-http-branchmap.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-http-branchmap.t	Sat Jul 18 17:32:38 2015 -0500
@@ -54,7 +54,7 @@
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     foo
   
-  $ "$TESTDIR/killdaemons.py" hg.pid
+  $ killdaemons.py hg.pid
 
 verify 7e7d56fe4833 (encoding fallback in branchmap to maintain compatibility with 1.3.x)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-http-bundle1.t	Sat Jul 18 17:32:38 2015 -0500
@@ -0,0 +1,333 @@
+#require serve
+
+This test is a duplicate of 'test-http.t', feel free to factor out
+parts that are not bundle1/bundle2 specific.
+
+  $ cat << EOF >> $HGRCPATH
+  > [experimental]
+  > # This test is dedicated to interaction through old bundle
+  > bundle2-exp = False
+  > EOF
+
+  $ hg init test
+  $ cd test
+  $ echo foo>foo
+  $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
+  $ echo foo>foo.d/foo
+  $ echo bar>foo.d/bAr.hg.d/BaR
+  $ echo bar>foo.d/baR.d.hg/bAR
+  $ hg commit -A -m 1
+  adding foo
+  adding foo.d/bAr.hg.d/BaR
+  adding foo.d/baR.d.hg/bAR
+  adding foo.d/foo
+  $ hg serve -p $HGPORT -d --pid-file=../hg1.pid -E ../error.log
+  $ hg --config server.uncompressed=False serve -p $HGPORT1 -d --pid-file=../hg2.pid
+
+Test server address cannot be reused
+
+#if windows
+  $ hg serve -p $HGPORT1 2>&1
+  abort: cannot start server at ':$HGPORT1': * (glob)
+  [255]
+#else
+  $ hg serve -p $HGPORT1 2>&1
+  abort: cannot start server at ':$HGPORT1': Address already in use
+  [255]
+#endif
+  $ cd ..
+  $ cat hg1.pid hg2.pid >> $DAEMON_PIDS
+
+clone via stream
+
+  $ hg clone --uncompressed http://localhost:$HGPORT/ copy 2>&1
+  streaming all changes
+  6 files to transfer, 606 bytes of data
+  transferred * bytes in * seconds (*/sec) (glob)
+  searching for changes
+  no changes found
+  updating to branch default
+  4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg verify -R copy
+  checking changesets
+  checking manifests
+  crosschecking files in changesets and manifests
+  checking files
+  4 files, 1 changesets, 4 total revisions
+
+try to clone via stream, should use pull instead
+
+  $ hg clone --uncompressed http://localhost:$HGPORT1/ copy2
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 4 changes to 4 files
+  updating to branch default
+  4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+clone via pull
+
+  $ hg clone http://localhost:$HGPORT1/ copy-pull
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 4 changes to 4 files
+  updating to branch default
+  4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg verify -R copy-pull
+  checking changesets
+  checking manifests
+  crosschecking files in changesets and manifests
+  checking files
+  4 files, 1 changesets, 4 total revisions
+  $ cd test
+  $ echo bar > bar
+  $ hg commit -A -d '1 0' -m 2
+  adding bar
+  $ cd ..
+
+clone over http with --update
+
+  $ hg clone http://localhost:$HGPORT1/ updated --update 0
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 5 changes to 5 files
+  updating to branch default
+  4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg log -r . -R updated
+  changeset:   0:8b6053c928fe
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     1
+  
+  $ rm -rf updated
+
+incoming via HTTP
+
+  $ hg clone http://localhost:$HGPORT1/ --rev 0 partial
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 4 changes to 4 files
+  updating to branch default
+  4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd partial
+  $ touch LOCAL
+  $ hg ci -qAm LOCAL
+  $ hg incoming http://localhost:$HGPORT1/ --template '{desc}\n'
+  comparing with http://localhost:$HGPORT1/
+  searching for changes
+  2
+  $ cd ..
+
+pull
+
+  $ cd copy-pull
+  $ echo '[hooks]' >> .hg/hgrc
+  $ echo "changegroup = printenv.py changegroup" >> .hg/hgrc
+  $ hg pull
+  pulling from http://localhost:$HGPORT1/
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=http://localhost:$HGPORT1/ (glob)
+  (run 'hg update' to get a working copy)
+  $ cd ..
+
+clone from invalid URL
+
+  $ hg clone http://localhost:$HGPORT/bad
+  abort: HTTP Error 404: Not Found
+  [255]
+
+test http authentication
++ use the same server to test server side streaming preference
+
+  $ cd test
+  $ cat << EOT > userpass.py
+  > import base64
+  > from mercurial.hgweb import common
+  > def perform_authentication(hgweb, req, op):
+  >     auth = req.env.get('HTTP_AUTHORIZATION')
+  >     if not auth:
+  >         raise common.ErrorResponse(common.HTTP_UNAUTHORIZED, 'who',
+  >                 [('WWW-Authenticate', 'Basic Realm="mercurial"')])
+  >     if base64.b64decode(auth.split()[1]).split(':', 1) != ['user', 'pass']:
+  >         raise common.ErrorResponse(common.HTTP_FORBIDDEN, 'no')
+  > def extsetup():
+  >     common.permhooks.insert(0, perform_authentication)
+  > EOT
+  $ hg --config extensions.x=userpass.py serve -p $HGPORT2 -d --pid-file=pid \
+  >    --config server.preferuncompressed=True \
+  >    --config web.push_ssl=False --config web.allow_push=* -A ../access.log
+  $ cat pid >> $DAEMON_PIDS
+
+  $ cat << EOF > get_pass.py
+  > import getpass
+  > def newgetpass(arg):
+  >   return "pass"
+  > getpass.getpass = newgetpass
+  > EOF
+
+  $ hg id http://localhost:$HGPORT2/
+  abort: http authorization required for http://localhost:$HGPORT2/
+  [255]
+  $ hg id http://localhost:$HGPORT2/
+  abort: http authorization required for http://localhost:$HGPORT2/
+  [255]
+  $ hg id --config ui.interactive=true --config extensions.getpass=get_pass.py http://user@localhost:$HGPORT2/
+  http authorization required for http://localhost:$HGPORT2/
+  realm: mercurial
+  user: user
+  password: 5fed3813f7f5
+  $ hg id http://user:pass@localhost:$HGPORT2/
+  5fed3813f7f5
+  $ echo '[auth]' >> .hg/hgrc
+  $ echo 'l.schemes=http' >> .hg/hgrc
+  $ echo 'l.prefix=lo' >> .hg/hgrc
+  $ echo 'l.username=user' >> .hg/hgrc
+  $ echo 'l.password=pass' >> .hg/hgrc
+  $ hg id http://localhost:$HGPORT2/
+  5fed3813f7f5
+  $ hg id http://localhost:$HGPORT2/
+  5fed3813f7f5
+  $ hg id http://user@localhost:$HGPORT2/
+  5fed3813f7f5
+  $ hg clone http://user:pass@localhost:$HGPORT2/ dest 2>&1
+  streaming all changes
+  7 files to transfer, 916 bytes of data
+  transferred * bytes in * seconds (*/sec) (glob)
+  searching for changes
+  no changes found
+  updating to branch default
+  5 files updated, 0 files merged, 0 files removed, 0 files unresolved
+--pull should override server's preferuncompressed
+  $ hg clone --pull http://user:pass@localhost:$HGPORT2/ dest-pull 2>&1
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 5 changes to 5 files
+  updating to branch default
+  5 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ hg id http://user2@localhost:$HGPORT2/
+  abort: http authorization required for http://localhost:$HGPORT2/
+  [255]
+  $ hg id http://user:pass2@localhost:$HGPORT2/
+  abort: HTTP Error 403: no
+  [255]
+
+  $ hg -R dest tag -r tip top
+  $ hg -R dest push http://user:pass@localhost:$HGPORT2/
+  pushing to http://user:***@localhost:$HGPORT2/
+  searching for changes
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 1 changesets with 1 changes to 1 files
+  $ hg rollback -q
+
+  $ cut -c38- ../access.log
+  "GET /?cmd=capabilities HTTP/1.1" 200 -
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces
+  "GET /?cmd=capabilities HTTP/1.1" 200 -
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces
+  "GET /?cmd=capabilities HTTP/1.1" 200 -
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks
+  "GET /?cmd=capabilities HTTP/1.1" 200 -
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks
+  "GET /?cmd=capabilities HTTP/1.1" 200 -
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks
+  "GET /?cmd=capabilities HTTP/1.1" 200 -
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks
+  "GET /?cmd=capabilities HTTP/1.1" 200 -
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks
+  "GET /?cmd=capabilities HTTP/1.1" 200 -
+  "GET /?cmd=branchmap HTTP/1.1" 200 -
+  "GET /?cmd=stream_out HTTP/1.1" 401 -
+  "GET /?cmd=stream_out HTTP/1.1" 200 -
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks
+  "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D5fed3813f7f5e1824344fdc9cf8f63bb662c292d
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases
+  "GET /?cmd=capabilities HTTP/1.1" 200 -
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=bookmarks
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks
+  "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D
+  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases
+  "GET /?cmd=capabilities HTTP/1.1" 200 -
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces
+  "GET /?cmd=capabilities HTTP/1.1" 200 -
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces
+  "GET /?cmd=listkeys HTTP/1.1" 403 - x-hgarg-1:namespace=namespaces
+  "GET /?cmd=capabilities HTTP/1.1" 200 -
+  "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=phases
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks
+  "GET /?cmd=branchmap HTTP/1.1" 200 -
+  "GET /?cmd=branchmap HTTP/1.1" 200 -
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks
+  "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=686173686564+5eb5abfefeea63c80dd7553bcc3783f37e0c5524
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases
+
+  $ cd ..
+
+clone of serve with repo in root and unserved subrepo (issue2970)
+
+  $ hg --cwd test init sub
+  $ echo empty > test/sub/empty
+  $ hg --cwd test/sub add empty
+  $ hg --cwd test/sub commit -qm 'add empty'
+  $ hg --cwd test/sub tag -r 0 something
+  $ echo sub = sub > test/.hgsub
+  $ hg --cwd test add .hgsub
+  $ hg --cwd test commit -qm 'add subrepo'
+  $ hg clone http://localhost:$HGPORT noslash-clone
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 7 changes to 7 files
+  updating to branch default
+  abort: HTTP Error 404: Not Found
+  [255]
+  $ hg clone http://localhost:$HGPORT/ slash-clone
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 7 changes to 7 files
+  updating to branch default
+  abort: HTTP Error 404: Not Found
+  [255]
+
+check error log
+
+  $ cat error.log
--- a/tests/test-http-proxy.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-http-proxy.t	Sat Jul 18 17:32:38 2015 -0500
@@ -1,4 +1,10 @@
 #require serve
+  $ cat << EOF >> $HGRCPATH
+  > [experimental]
+  > # drop me once bundle2 is the default,
+  > # added to get test change early.
+  > bundle2-exp = True
+  > EOF
 
   $ hg init a
   $ cd a
@@ -8,7 +14,7 @@
   $ hg --config server.uncompressed=True serve -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid >> $DAEMON_PIDS
   $ cd ..
-  $ "$TESTDIR/tinyproxy.py" $HGPORT1 localhost >proxy.log 2>&1 </dev/null &
+  $ tinyproxy.py $HGPORT1 localhost >proxy.log 2>&1 </dev/null &
   $ while [ ! -f proxy.pid ]; do sleep 0; done
   $ cat proxy.pid >> $DAEMON_PIDS
 
@@ -103,26 +109,22 @@
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=branchmap HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=stream_out HTTP/1.1" - - (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=bookmarks (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D83180e7845de420a1bb46896fd5fe05294f8d629 (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=phase%2Cbookmarks (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=phases (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 (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=phase%2Cbookmarks (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=phases (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
-  *- - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=bookmarks (glob)
-  *- - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D (glob)
-  *- - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629 (glob)
-  *- - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=phases (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=bookmarks (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629 (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=phase%2Cbookmarks (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=phases (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=bookmarks (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629 (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=phase%2Cbookmarks (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=phases (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=bookmarks (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629 (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=phase%2Cbookmarks (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=phases (glob)
--- a/tests/test-http.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-http.t	Sat Jul 18 17:32:38 2015 -0500
@@ -119,7 +119,7 @@
 
   $ cd copy-pull
   $ echo '[hooks]' >> .hg/hgrc
-  $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup" >> .hg/hgrc
+  $ echo "changegroup = printenv.py changegroup" >> .hg/hgrc
   $ hg pull
   pulling from http://localhost:$HGPORT1/
   searching for changes
@@ -166,7 +166,6 @@
   > getpass.getpass = newgetpass
   > EOF
 
-#if python243
   $ hg id http://localhost:$HGPORT2/
   abort: http authorization required for http://localhost:$HGPORT2/
   [255]
@@ -180,7 +179,6 @@
   password: 5fed3813f7f5
   $ hg id http://user:pass@localhost:$HGPORT2/
   5fed3813f7f5
-#endif
   $ echo '[auth]' >> .hg/hgrc
   $ echo 'l.schemes=http' >> .hg/hgrc
   $ echo 'l.prefix=lo' >> .hg/hgrc
@@ -192,7 +190,6 @@
   5fed3813f7f5
   $ hg id http://user@localhost:$HGPORT2/
   5fed3813f7f5
-#if python243
   $ hg clone http://user:pass@localhost:$HGPORT2/ dest 2>&1
   streaming all changes
   7 files to transfer, 916 bytes of data
@@ -264,14 +261,13 @@
   "GET /?cmd=branchmap HTTP/1.1" 200 -
   "GET /?cmd=stream_out HTTP/1.1" 401 -
   "GET /?cmd=stream_out HTTP/1.1" 200 -
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks
   "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D5fed3813f7f5e1824344fdc9cf8f63bb662c292d
+  "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=phase%2Cbookmarks
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=bookmarks
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks
   "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D
-  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d
+  "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=phase%2Cbookmarks
+  "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=phase%2Cbookmarks
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip
@@ -288,10 +284,9 @@
   "GET /?cmd=branchmap HTTP/1.1" 200 -
   "GET /?cmd=branchmap HTTP/1.1" 200 -
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks
-  "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=686173686564+5eb5abfefeea63c80dd7553bcc3783f37e0c5524
+  "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases
 
-#endif
   $ cd ..
 
 clone of serve with repo in root and unserved subrepo (issue2970)
--- a/tests/test-https.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-https.t	Sat Jul 18 17:32:38 2015 -0500
@@ -81,6 +81,53 @@
   > EOT
   $ cat priv.pem pub-expired.pem > server-expired.pem
 
+Client certificates created with:
+ openssl genrsa -aes128 -passout pass:1234 -out client-key.pem 512
+ openssl rsa -in client-key.pem -passin pass:1234 -out client-key-decrypted.pem
+ printf '.\n.\n.\n.\n.\n.\nhg-client@localhost\n.\n.\n' | \
+ openssl req -new -key client-key.pem -passin pass:1234 -out client-csr.pem
+ openssl x509 -req -days 9000 -in client-csr.pem -CA pub.pem -CAkey priv.pem \
+ -set_serial 01 -out client-cert.pem
+
+  $ cat << EOT > client-key.pem
+  > -----BEGIN RSA PRIVATE KEY-----
+  > Proc-Type: 4,ENCRYPTED
+  > DEK-Info: AES-128-CBC,C8B8F103A61A336FB0716D1C0F8BB2E8
+  > 
+  > JolMlCFjEW3q3JJjO9z99NJWeJbFgF5DpUOkfSCxH56hxxtZb9x++rBvBZkxX1bF
+  > BAIe+iI90+jdCLwxbILWuFcrJUaLC5WmO14XDKYVmr2eW9e4MiCYOlO0Q6a9rDFS
+  > jctRCfvubOXFHbBGLH8uKEMpXEkP7Lc60FiIukqjuQEivJjrQirVtZCGwyk3qUi7
+  > Eyh4Lo63IKGu8T1Bkmn2kaMvFhu7nC/CQLBjSq0YYI1tmCOkVb/3tPrz8oqgDJp2
+  > u7bLS3q0xDNZ52nVrKIoZC/UlRXGlPyzPpa70/jPIdfCbkwDaBpRVXc+62Pj2n5/
+  > CnO2xaKwfOG6pDvanBhFD72vuBOkAYlFZPiEku4sc2WlNggsSWCPCIFwzmiHjKIl
+  > bWmdoTq3nb7sNfnBbV0OCa7fS1dFwCm4R1NC7ELENu0=
+  > -----END RSA PRIVATE KEY-----
+  > EOT
+
+  $ cat << EOT > client-key-decrypted.pem
+  > -----BEGIN RSA PRIVATE KEY-----
+  > MIIBOgIBAAJBAJs4LS3glAYU92bg5kPgRPNW84ewB0fWJfAKccCp1ACHAdZPeaKb
+  > FCinVMYKAVbVqBkyrZ/Tyr8aSfMz4xO4+KsCAwEAAQJAeKDr25+Q6jkZHEbkLRP6
+  > AfMtR+Ixhk6TJT24sbZKIC2V8KuJTDEvUhLU0CAr1nH79bDqiSsecOiVCr2HHyfT
+  > AQIhAM2C5rHbTs9R3PkywFEqq1gU3ztCnpiWglO7/cIkuGBhAiEAwVpMSAf77kop
+  > 4h/1kWsgMALQTJNsXd4CEUK4BOxvJIsCIQCbarVAKBQvoT81jfX27AfscsxnKnh5
+  > +MjSvkanvdFZwQIgbbcTefwt1LV4trtz2SR0i0nNcOZmo40Kl0jIquKO3qkCIH01
+  > mJHzZr3+jQqeIFtr5P+Xqi30DJxgrnEobbJ0KFjY
+  > -----END RSA PRIVATE KEY-----
+  > EOT
+
+  $ cat << EOT > client-cert.pem
+  > -----BEGIN CERTIFICATE-----
+  > MIIBPjCB6QIBATANBgkqhkiG9w0BAQsFADAxMRIwEAYDVQQDDAlsb2NhbGhvc3Qx
+  > GzAZBgkqhkiG9w0BCQEWDGhnQGxvY2FsaG9zdDAeFw0xNTA1MDcwNjI5NDVaFw0z
+  > OTEyMjcwNjI5NDVaMCQxIjAgBgkqhkiG9w0BCQEWE2hnLWNsaWVudEBsb2NhbGhv
+  > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAmzgtLeCUBhT3ZuDmQ+BE81bzh7AH
+  > R9Yl8ApxwKnUAIcB1k95opsUKKdUxgoBVtWoGTKtn9PKvxpJ8zPjE7j4qwIDAQAB
+  > MA0GCSqGSIb3DQEBCwUAA0EAfBTqBG5pYhuGk+ZnyUufgS+d7Nk/sZAZjNdCAEj/
+  > NFPo5fR1jM6jlEWoWbeg298+SkjV7tfO+2nt0otUFkdM6A==
+  > -----END CERTIFICATE-----
+  > EOT
+
   $ hg init test
   $ cd test
   $ echo foo>foo
@@ -154,7 +201,7 @@
 
   $ cd copy-pull
   $ echo '[hooks]' >> .hg/hgrc
-  $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup" >> .hg/hgrc
+  $ echo "changegroup = printenv.py changegroup" >> .hg/hgrc
   $ hg pull $DISABLEOSXDUMMYCERT
   pulling from https://localhost:$HGPORT/
   warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
@@ -255,11 +302,11 @@
   5fed3813f7f5
 
 HGPORT1 is reused below for tinyproxy tests. Kill that server.
-  $ "$TESTDIR/killdaemons.py" hg1.pid
+  $ killdaemons.py hg1.pid
 
 Prepare for connecting through proxy
 
-  $ "$TESTDIR/tinyproxy.py" $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
+  $ tinyproxy.py $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
   $ while [ ! -f proxy.pid ]; do sleep 0; done
   $ cat proxy.pid >> $DAEMON_PIDS
 
@@ -297,3 +344,60 @@
   pulling from https://localhost:$HGPORT2/
   abort: error: *certificate verify failed* (glob)
   [255]
+
+
+  $ killdaemons.py hg0.pid
+
+#if sslcontext
+
+Start patched hgweb that requires client certificates:
+
+  $ cat << EOT > reqclientcert.py
+  > import ssl
+  > from mercurial.hgweb import server
+  > class _httprequesthandlersslclientcert(server._httprequesthandlerssl):
+  >     @staticmethod
+  >     def preparehttpserver(httpserver, ssl_cert):
+  >         sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+  >         sslcontext.verify_mode = ssl.CERT_REQUIRED
+  >         sslcontext.load_cert_chain(ssl_cert)
+  >         # verify clients by server certificate
+  >         sslcontext.load_verify_locations(ssl_cert)
+  >         httpserver.socket = sslcontext.wrap_socket(httpserver.socket,
+  >                                                    server_side=True)
+  > server._httprequesthandlerssl = _httprequesthandlersslclientcert
+  > EOT
+  $ cd test
+  $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV \
+  > --config extensions.reqclientcert=../reqclientcert.py
+  $ cat ../hg0.pid >> $DAEMON_PIDS
+  $ cd ..
+
+without client certificate:
+
+  $ P=`pwd` hg id https://localhost:$HGPORT/
+  abort: error: *handshake failure* (glob)
+  [255]
+
+with client certificate:
+
+  $ cat << EOT >> $HGRCPATH
+  > [auth]
+  > l.prefix = localhost
+  > l.cert = client-cert.pem
+  > l.key = client-key.pem
+  > EOT
+
+  $ P=`pwd` hg id https://localhost:$HGPORT/ \
+  > --config auth.l.key=client-key-decrypted.pem
+  5fed3813f7f5
+
+  $ printf '1234\n' | env P=`pwd` hg id https://localhost:$HGPORT/ \
+  > --config ui.interactive=True --config ui.nontty=True
+  passphrase for client-key.pem: 5fed3813f7f5
+
+  $ env P=`pwd` hg id https://localhost:$HGPORT/
+  abort: error: * (glob)
+  [255]
+
+#endif
--- a/tests/test-hup.t	Fri Jul 03 18:10:58 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-#require serve fifo
-
-Test hangup signal in the middle of transaction
-
-  $ hg init
-  $ mkfifo p
-  $ hg serve --stdio < p 1>out 2>&1 &
-  $ P=$!
-
-Do test while holding fifo open
-
-  $ (
-  > echo lock
-  > echo addchangegroup
-  > start=`date +%s`
-  > # 10 second seems much enough to let the server catch up
-  > deadline=`expr $start + 10`
-  > while [ ! -s .hg/store/journal ]; do
-  >     sleep 0;
-  >     if [ `date +%s` -gt $deadline ]; then
-  >         echo "transaction did not start after 10 seconds" >&2;
-  >         exit 1;
-  >     fi
-  > done
-  > kill -HUP $P
-  > ) > p
-
-  $ wait
-  $ cat out
-  0
-  0
-  adding changesets
-  transaction abort!
-  rollback completed
-  killed!
-
-  $ ls -1d .hg/* .hg/store/*
-  .hg/00changelog.i
-  .hg/journal.bookmarks
-  .hg/journal.branch
-  .hg/journal.desc
-  .hg/journal.dirstate
-  .hg/requires
-  .hg/store
-  .hg/store/00changelog.i
-  .hg/store/00changelog.i.a
-  .hg/store/journal.phaseroots
--- a/tests/test-hybridencode.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-hybridencode.py	Sat Jul 18 17:32:38 2015 -0500
@@ -460,3 +460,9 @@
           'VWXYZ-1234567890-xxxxxxxxx-xxxxxxxxx-xxxxxxxx-xxxx'
           'xxxxx-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwww'
           'wwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww.i')
+
+print "paths outside data/ can be encoded"
+show('metadata/dir/00manifest.i')
+show('metadata/12345678/12345678/12345678/12345678/12345678/'
+          '12345678/12345678/12345678/12345678/12345678/12345678/'
+          '12345678/12345678/00manifest.i')
--- a/tests/test-hybridencode.py.out	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-hybridencode.py.out	Sat Jul 18 17:32:38 2015 -0500
@@ -491,3 +491,10 @@
 A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789-12345-ABCDEFGHIJKLMNOPRSTUVWXYZ-abcdefghjiklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPRSTUVWXYZ-1234567890-xxxxxxxxx-xxxxxxxxx-xxxxxxxx-xxxxxxxxx-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww.i'
 B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxx93352aa50377751d9e5ebdf52da1e6e69a6887a6.i'
 
+paths outside data/ can be encoded
+A = 'metadata/dir/00manifest.i'
+B = 'metadata/dir/00manifest.i'
+
+A = 'metadata/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/00manifest.i'
+B = 'dh/ata/12345678/12345678/12345678/12345678/12345678/12345678/12345678/00manife0a4da1f89aa2aa9eb0896eb451288419049781b4.i'
+
--- a/tests/test-import.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-import.t	Sat Jul 18 17:32:38 2015 -0500
@@ -492,6 +492,13 @@
   $ echo line0 >> a
   $ hg ci -m brancha
   created new head
+  $ hg import --config patch.fuzz=0 -v fuzzy-tip.patch
+  applying fuzzy-tip.patch
+  patching file a
+  Hunk #1 FAILED at 0
+  1 out of 1 hunks FAILED -- saving rejects to file a.rej
+  abort: patch failed to apply
+  [255]
   $ hg import --no-commit -v fuzzy-tip.patch
   applying fuzzy-tip.patch
   patching file a
@@ -990,6 +997,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 2 draft
 
   $ hg diff --git -c tip
   diff --git a/lib/place-holder b/lib/place-holder
@@ -1018,6 +1026,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 2 draft
 
   $ hg diff --git -c tip
   diff --git a/lib/place-holder b/lib/place-holder
--- a/tests/test-issue1802.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-issue1802.t	Sat Jul 18 17:32:38 2015 -0500
@@ -58,7 +58,6 @@
    branchmerge: True, force: False, partial: False
    ancestor: a03b0deabf2b, local: d6fa54f68ae1+, remote: 2d8bcf2dda39
    a: update permissions -> e
-  updating: a 1/1 files (100.00%)
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
 
--- a/tests/test-issue522.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-issue522.t	Sat Jul 18 17:32:38 2015 -0500
@@ -33,7 +33,6 @@
    ancestor: bbd179dfa0a7, local: 71766447bdbb+, remote: 4d9e78aaceee
    foo: remote is newer -> g
   getting foo
-  updating: foo 1/1 files (100.00%)
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
 
--- a/tests/test-issue672.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-issue672.t	Sat Jul 18 17:32:38 2015 -0500
@@ -36,10 +36,8 @@
    ancestor: 81f4b099af3d, local: c64f439569a9+, remote: c12dcd37c90a
    1: other deleted -> r
   removing 1
-  updating: 1 1/2 files (50.00%)
    1a: remote created -> g
   getting 1a
-  updating: 1a 2/2 files (100.00%)
    2: remote unchanged -> k
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
@@ -68,7 +66,6 @@
    ancestor: c64f439569a9, local: e327dca35ac8+, remote: 746e9549ea96
    preserving 1a for resolve of 1a
    1a: local copied/moved from 1 -> m
-  updating: 1a 1/1 files (100.00%)
   picked tool 'internal:merge' for 1a (binary False symlink False)
   merging 1a and 1 to 1a
   my 1a@e327dca35ac8+ other 1@746e9549ea96 ancestor 1@81f4b099af3d
@@ -92,7 +89,6 @@
    preserving 1 for resolve of 1a
   removing 1
    1a: remote moved from 1 -> m
-  updating: 1a 1/1 files (100.00%)
   picked tool 'internal:merge' for 1a (binary False symlink False)
   merging 1 and 1a to 1a
   my 1a@746e9549ea96+ other 1a@e327dca35ac8 ancestor 1@81f4b099af3d
--- a/tests/test-keyword.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-keyword.t	Sat Jul 18 17:32:38 2015 -0500
@@ -980,14 +980,14 @@
 
   $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
   $ cat hg.pid >> $DAEMON_PIDS
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/a/?style=raw'
+  $ get-with-headers.py localhost:$HGPORT 'file/tip/a/?style=raw'
   200 Script output follows
   
   expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
   do not process $Id:
   xxx $
   $Xinfo: User Name <user@example.com>: firstline $
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'annotate/tip/a/?style=raw'
+  $ get-with-headers.py localhost:$HGPORT 'annotate/tip/a/?style=raw'
   200 Script output follows
   
   
@@ -999,7 +999,7 @@
   
   
   
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'rev/tip/?style=raw'
+  $ get-with-headers.py localhost:$HGPORT 'rev/tip/?style=raw'
   200 Script output follows
   
   
@@ -1019,7 +1019,7 @@
   +xxx $
   +$Xinfo$
   
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'diff/bb948857c743/a?style=raw'
+  $ get-with-headers.py localhost:$HGPORT 'diff/bb948857c743/a?style=raw'
   200 Script output follows
   
   
--- a/tests/test-known.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-known.t	Sat Jul 18 17:32:38 2015 -0500
@@ -35,5 +35,5 @@
   $ hg debugknown http://localhost:$HGPORT/
   
   $ cat error.log
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
 
--- a/tests/test-largefiles-misc.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-largefiles-misc.t	Sat Jul 18 17:32:38 2015 -0500
@@ -228,6 +228,7 @@
   branch: default
   commit: 1 subrepos
   update: (current)
+  phases: 2 draft
   $ hg st
   $ hg st -S
   A subrepo/large.txt
@@ -245,6 +246,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 3 draft
   $ echo "rev 2" > subrepo/large.txt
   $ hg st -S
   M subrepo/large.txt
@@ -254,6 +256,7 @@
   branch: default
   commit: 1 subrepos
   update: (current)
+  phases: 3 draft
   $ hg ci -m "this commit should fail without -S"
   abort: uncommitted changes in subrepository 'subrepo'
   (use --subrepos for recursive commit)
@@ -567,6 +570,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 1 draft
   largefiles: (no remote repo)
 
 check messages when there is no files to upload:
@@ -581,6 +585,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 1 draft
   largefiles: (no files to upload)
   $ hg -R clone2 outgoing --large
   comparing with $TESTTMP/issue3651/src (glob)
@@ -608,6 +613,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 2 draft
   largefiles: 1 entities for 1 files to upload
   $ hg -R clone2 outgoing --large
   comparing with $TESTTMP/issue3651/src (glob)
@@ -643,6 +649,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 3 draft
   largefiles: 1 entities for 3 files to upload
   $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n"
   comparing with $TESTTMP/issue3651/src (glob)
@@ -656,7 +663,7 @@
   
   $ hg -R clone2 cat -r 1 clone2/.hglf/b
   89e6c98d92887913cadf06b2adb97f26cde4849b
-  $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" --debug
+  $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" --debug --config progress.debug=true
   comparing with $TESTTMP/issue3651/src (glob)
   query 1; heads
   searching for changes
@@ -692,6 +699,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 6 draft
   largefiles: 3 entities for 3 files to upload
   $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n"
   comparing with $TESTTMP/issue3651/src (glob)
@@ -710,7 +718,7 @@
   c801c9cfe94400963fcb683246217d5db77f9a9a
   $ hg -R clone2 cat -r 4 clone2/.hglf/b
   13f9ed0898e315bf59dc2973fec52037b6f441a2
-  $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" --debug
+  $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" --debug --config progress.debug=true
   comparing with $TESTTMP/issue3651/src (glob)
   query 1; heads
   searching for changes
@@ -750,6 +758,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 6 draft
   largefiles: 2 entities for 1 files to upload
   $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n"
   comparing with $TESTTMP/issue3651/src (glob)
@@ -761,7 +770,7 @@
   largefiles to upload (2 entities):
   b
   
-  $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" --debug
+  $ hg -R clone2 outgoing --large -T "{rev}:{node|short}\n" --debug --config progress.debug=true
   comparing with $TESTTMP/issue3651/src (glob)
   query 1; heads
   searching for changes
@@ -975,7 +984,7 @@
   > EOF
   $ hg -R pull-dst -q pull -u http://localhost:$HGPORT
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
 #endif
 
 Test overridden functions work correctly even for repos disabling
@@ -999,10 +1008,6 @@
   > EOF
   $ hg clone -q enabled-but-no-largefiles no-largefiles
 
-(test rebasing implied by pull: precommit while rebasing unexpectedly
-shows "normal3" as "?", because lfdirstate isn't yet written out at
-that time)
-
   $ echo normal2 > enabled-but-no-largefiles/normal2
   $ hg -R enabled-but-no-largefiles add enabled-but-no-largefiles/normal2
   $ hg -R enabled-but-no-largefiles commit -m '#1@enabled-but-no-largefiles'
@@ -1017,7 +1022,7 @@
 
   $ hg -R no-largefiles -q pull --rebase
   Invoking status precommit hook
-  ? normal3
+  A normal3
 
 (test reverting)
 
--- a/tests/test-largefiles-update.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-largefiles-update.t	Sat Jul 18 17:32:38 2015 -0500
@@ -20,6 +20,20 @@
   $ echo 'large1 in #1' > large1
   $ echo 'normal1 in #1' > normal1
   $ hg commit -m '#1'
+  $ hg extdiff -r '.^' --config extensions.extdiff=
+  diff -Npru repo.0d9d9b8dc9a3/.hglf/large1 repo/.hglf/large1
+  --- repo.0d9d9b8dc9a3/.hglf/large1	* (glob)
+  +++ repo/.hglf/large1	* (glob)
+  @@ -1 +1 @@
+  -4669e532d5b2c093a78eca010077e708a071bb64
+  +58e24f733a964da346e2407a2bee99d9001184f5
+  diff -Npru repo.0d9d9b8dc9a3/normal1 repo/normal1
+  --- repo.0d9d9b8dc9a3/normal1	* (glob)
+  +++ repo/normal1	* (glob)
+  @@ -1 +1 @@
+  -normal1
+  +normal1 in #1
+  [1]
   $ hg update -q -C 0
   $ echo 'large2 in #2' > large2
   $ hg commit -m '#2'
--- a/tests/test-largefiles-wireproto.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-largefiles-wireproto.t	Sat Jul 18 17:32:38 2015 -0500
@@ -106,16 +106,19 @@
   [255]
 
 used all HGPORTs, kill all daemons
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
 #endif
 
 vanilla clients locked out from largefiles ssh repos
   $ hg --config extensions.largefiles=! clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/r4 r5
-  abort: remote error:
-  
-  This repository uses the largefiles extension.
-  
-  Please enable it in your Mercurial config file.
+  remote: 
+  remote: This repository uses the largefiles extension.
+  remote: 
+  remote: Please enable it in your Mercurial config file.
+  remote: 
+  remote: -
+  abort: remote error
+  (check previous remote output)
   [255]
 
 #if serve
@@ -264,7 +267,7 @@
 
 largefiles pulled on update - no server side problems:
   $ mv 02a439e5c31c526465ab1a0ca1f431f76b827b90 empty/.hg/largefiles/
-  $ hg -R http-clone --debug up --config largefiles.usercache=http-clone-usercache
+  $ hg -R http-clone --debug up --config largefiles.usercache=http-clone-usercache --config progress.debug=true
   resolving manifests
    branchmerge: False, force: False, partial: False
    ancestor: 000000000000, local: 000000000000+, remote: cf03e5bb9936
@@ -288,6 +291,6 @@
   $ rm -rf empty http-clone*
 
 used all HGPORTs, kill all daemons
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
 
 #endif
--- a/tests/test-largefiles.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-largefiles.t	Sat Jul 18 17:32:38 2015 -0500
@@ -19,6 +19,10 @@
   > usercache=${USERCACHE}
   > [hooks]
   > precommit=sh -c "echo \\"Invoking status precommit hook\\"; hg status"
+  > [experimental]
+  > # drop me once bundle2 is the default,
+  > # added to get test change early.
+  > bundle2-exp = True
   > EOF
 
 Create the repo with a couple of revisions of both large and normal
@@ -67,6 +71,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 2 draft
   largefiles: (no remote repo)
 
 Commit preserved largefile contents.
@@ -191,7 +196,7 @@
 
   $ hg serve -d -p $HGPORT --pid-file ../hg.pid
   $ cat ../hg.pid >> $DAEMON_PIDS
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/tip/?style=raw'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'file/tip/?style=raw'
   200 Script output follows
   
   
@@ -200,7 +205,7 @@
   -rw-r--r-- 9 normal3
   
   
-  $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/tip/sub/?style=raw'
+  $ get-with-headers.py 127.0.0.1:$HGPORT 'file/tip/sub/?style=raw'
   200 Script output follows
   
   
@@ -208,7 +213,7 @@
   -rw-r--r-- 9 normal4
   
   
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
 #endif
 
 Test archiving the various revisions.  These hit corner cases known with
@@ -999,6 +1004,7 @@
   branch: default
   commit: (clean)
   update: 7 new changesets (update)
+  phases: 8 draft
 
   $ rm "${USERCACHE}"/*
   $ hg clone --all-largefiles -u 1 a a-clone1
@@ -1021,6 +1027,7 @@
   branch: default
   commit: (clean)
   update: 6 new changesets (update)
+  phases: 8 draft
 
   $ rm "${USERCACHE}"/*
   $ hg clone --all-largefiles -U a a-clone-u
@@ -1030,6 +1037,7 @@
   branch: default
   commit: (clean)
   update: 8 new changesets (update)
+  phases: 8 draft
 
 Show computed destination directory:
 
@@ -1094,18 +1102,18 @@
   searching for changes
   all local heads known remotely
   6 changesets found
+  uncompressed size of bundle content:
+      1333 (changelog)
+      1599 (manifests)
+       254  .hglf/large1
+       564  .hglf/large3
+       572  .hglf/sub/large4
+       182  .hglf/sub2/large6
+       182  .hglf/sub2/large7
+       212  normal1
+       457  normal3
+       465  sub/normal4
   adding changesets
-  uncompressed size of bundle content:
-      1213 (changelog)
-      1479 (manifests)
-       234  .hglf/large1
-       504  .hglf/large3
-       512  .hglf/sub/large4
-       162  .hglf/sub2/large6
-       162  .hglf/sub2/large7
-       192  normal1
-       397  normal3
-       405  sub/normal4
   adding manifests
   adding file changes
   added 6 changesets with 16 changes to 8 files
--- a/tests/test-lfconvert.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-lfconvert.t	Sat Jul 18 17:32:38 2015 -0500
@@ -89,7 +89,7 @@
   normal
   $ cat sub/normal2
   alsonormal
-  $ "$TESTDIR/md5sum.py" large sub/maybelarge.dat
+  $ md5sum.py large sub/maybelarge.dat
   ec87a838931d4d5d2e94a04644788a55  large
   1276481102f218c981e0324180bafd9f  sub/maybelarge.dat
 
@@ -115,7 +115,7 @@
   $ echo blah >> normal3
   $ echo blah >> sub/normal2
   $ echo blah >> sub/maybelarge.dat
-  $ "$TESTDIR/md5sum.py" sub/maybelarge.dat
+  $ md5sum.py sub/maybelarge.dat
   1dd0b99ff80e19cff409702a1d3f5e15  sub/maybelarge.dat
   $ hg commit -A -m"add normal3, modify sub/*"
   adding normal3
@@ -193,7 +193,7 @@
   $ cat stuff/normal2
   alsonormal
   blah
-  $ "$TESTDIR/md5sum.py" stuff/maybelarge.dat
+  $ md5sum.py stuff/maybelarge.dat
   1dd0b99ff80e19cff409702a1d3f5e15  stuff/maybelarge.dat
   $ cat .hglf/stuff/maybelarge.dat
   76236b6a2c6102826c61af4297dd738fb3b1de38
@@ -226,6 +226,7 @@
   $ hg commit -m "add anotherlarge (should be a largefile)"
   $ cat .hglf/anotherlarge
   3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3
+  $ hg tag mytag
   $ cd ..
 
 round-trip: converting back to a normal (non-largefiles) repo with
@@ -233,25 +234,30 @@
   $ cd largefiles-repo
   $ hg lfconvert --to-normal . ../normal-repo
   initializing destination ../normal-repo
+  0 additional largefiles cached
+  scanning source...
+  sorting...
+  converting...
+  7 add large, normal1
+  6 add sub/*
+  5 rename sub/ to stuff/
+  4 add normal3, modify sub/*
+  3 remove large, normal3
+  2 merge
+  1 add anotherlarge (should be a largefile)
+  0 Added tag mytag for changeset abacddda7028
   $ cd ../normal-repo
   $ cat >> .hg/hgrc <<EOF
   > [extensions]
   > largefiles = !
   > EOF
 
-# Hmmm: the changeset ID for rev 5 is different from the original
-# normal repo (../bigfile-repo), because the changelog filelist
-# differs between the two incarnations of rev 5: this repo includes
-# 'large' in the list, but ../bigfile-repo does not. Since rev 5
-# removes 'large' relative to the first parent in both repos, it seems
-# to me that lfconvert is doing a *better* job than
-# "hg remove" + "hg merge" + "hg commit".
-#  $ hg -R ../bigfile-repo debugdata -c 5
-#  $ hg debugdata -c 5
   $ hg log -G --template "{rev}:{node|short}  {desc|firstline}\n"
-  o  6:1635824e6f59  add anotherlarge (should be a largefile)
+  o  7:b5fedc110b9d  Added tag mytag for changeset 867ab992ecf4
   |
-  o    5:7215f8deeaaf  merge
+  o  6:867ab992ecf4  add anotherlarge (should be a largefile)
+  |
+  o    5:4884f215abda  merge
   |\
   | o  4:7285f817b77e  remove large, normal3
   | |
@@ -264,8 +270,9 @@
   o  0:117b8328f97a  add large, normal1
   
   $ hg update
-  4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  5 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg locate
+  .hgtags
   anotherlarge
   normal1
   stuff/maybelarge.dat
@@ -284,15 +291,18 @@
   scanning source...
   sorting...
   converting...
-  6 add large, normal1
-  5 add sub/*
-  4 rename sub/ to stuff/
-  3 add normal3, modify sub/*
-  2 remove large, normal3
-  1 merge
-  0 add anotherlarge (should be a largefile)
+  7 add large, normal1
+  6 add sub/*
+  5 rename sub/ to stuff/
+  4 add normal3, modify sub/*
+  3 remove large, normal3
+  2 merge
+  1 add anotherlarge (should be a largefile)
+  0 Added tag mytag for changeset abacddda7028
 
   $ hg -R largefiles-repo-hg log -G --template "{rev}:{node|short}  {desc|firstline}\n"
+  o  7:2f08f66459b7  Added tag mytag for changeset 17126745edfd
+  |
   o  6:17126745edfd  add anotherlarge (should be a largefile)
   |
   o    5:9cc5aa7204f0  merge
@@ -310,12 +320,20 @@
 Verify will fail (for now) if the usercache is purged before converting, since
 largefiles are not cached in the converted repo's local store by the conversion
 process.
+  $ cd largefiles-repo-hg
+  $ cat >> .hg/hgrc <<EOF
+  > [experimental]
+  > evolution=createmarkers
+  > EOF
+  $ hg debugobsolete `hg log -r tip -T "{node}"`
+  $ cd ..
+
   $ hg -R largefiles-repo-hg verify --large --lfa
   checking changesets
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  8 files, 7 changesets, 12 total revisions
+  9 files, 8 changesets, 13 total revisions
   searching 7 changesets for largefiles
   changeset 0:d4892ec57ce2: large references missing $TESTTMP/largefiles-repo-hg/.hg/largefiles/2e000fa7e85759c7f4c254d4d9c33ef481e459a7 (glob)
   changeset 1:334e5237836d: sub/maybelarge.dat references missing $TESTTMP/largefiles-repo-hg/.hg/largefiles/34e163be8e43c5631d8b92e9c43ab0bf0fa62b9c (glob)
@@ -336,6 +354,18 @@
   $ rm -f "${USERCACHE}"/*
   $ hg lfconvert --to-normal issue3519 normalized3519
   initializing destination normalized3519
+  4 additional largefiles cached
+  scanning source...
+  sorting...
+  converting...
+  7 add large, normal1
+  6 add sub/*
+  5 rename sub/ to stuff/
+  4 add normal3, modify sub/*
+  3 remove large, normal3
+  2 merge
+  1 add anotherlarge (should be a largefile)
+  0 Added tag mytag for changeset abacddda7028
 
 Ensure the abort message is useful if a largefile is entirely unavailable
   $ rm -rf normalized3519
@@ -344,8 +374,20 @@
   $ rm largefiles-repo/.hg/largefiles/*
   $ hg lfconvert --to-normal issue3519 normalized3519
   initializing destination normalized3519
+  anotherlarge: largefile 3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3 not available from file:/*/$TESTTMP/largefiles-repo (glob)
+  stuff/maybelarge.dat: largefile 76236b6a2c6102826c61af4297dd738fb3b1de38 not available from file:/*/$TESTTMP/largefiles-repo (glob)
+  stuff/maybelarge.dat: largefile 76236b6a2c6102826c61af4297dd738fb3b1de38 not available from file:/*/$TESTTMP/largefiles-repo (glob)
+  sub/maybelarge.dat: largefile 76236b6a2c6102826c61af4297dd738fb3b1de38 not available from file:/*/$TESTTMP/largefiles-repo (glob)
   large: largefile 2e000fa7e85759c7f4c254d4d9c33ef481e459a7 not available from file:/*/$TESTTMP/largefiles-repo (glob)
-  abort: missing largefile 'large' from revision d4892ec57ce212905215fad1d9018f56b99202ad
+  sub/maybelarge.dat: largefile 76236b6a2c6102826c61af4297dd738fb3b1de38 not available from file:/*/$TESTTMP/largefiles-repo (glob)
+  large: largefile 2e000fa7e85759c7f4c254d4d9c33ef481e459a7 not available from file:/*/$TESTTMP/largefiles-repo (glob)
+  stuff/maybelarge.dat: largefile 34e163be8e43c5631d8b92e9c43ab0bf0fa62b9c not available from file:/*/$TESTTMP/largefiles-repo (glob)
+  large: largefile 2e000fa7e85759c7f4c254d4d9c33ef481e459a7 not available from file:/*/$TESTTMP/largefiles-repo (glob)
+  sub/maybelarge.dat: largefile 34e163be8e43c5631d8b92e9c43ab0bf0fa62b9c not available from file:/*/$TESTTMP/largefiles-repo (glob)
+  large: largefile 2e000fa7e85759c7f4c254d4d9c33ef481e459a7 not available from file:/*/$TESTTMP/largefiles-repo (glob)
+  0 additional largefiles cached
+  11 largefiles failed to download
+  abort: all largefiles must be present locally
   [255]
 
 
--- a/tests/test-log.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-log.t	Sat Jul 18 17:32:38 2015 -0500
@@ -148,7 +148,7 @@
 
   $ hg log -f -l1 --style something
   abort: style 'something' not found
-  (available styles: bisect, changelog, compact, default, phases, xml)
+  (available styles: bisect, changelog, compact, default, phases, status, xml)
   [255]
 
 -f, phases style
@@ -1626,15 +1626,16 @@
 working-directory revision requires special treatment
 
   $ hg log -r 'wdir()'
-  changeset:   0:65624cd9070a+
+  changeset:   2147483647:ffffffffffff
+  parent:      0:65624cd9070a
   user:        test
   date:        [A-Za-z0-9:+ ]+ (re)
   
   $ hg log -r 'wdir()' -q
-  0:65624cd9070a+
+  2147483647:ffffffffffff
 
   $ hg log -r 'wdir()' --debug
-  changeset:   0:65624cd9070a035fa7191a54f2b8af39f16b0c08+
+  changeset:   2147483647:ffffffffffffffffffffffffffffffffffffffff
   phase:       draft
   parent:      0:65624cd9070a035fa7191a54f2b8af39f16b0c08
   parent:      -1:0000000000000000000000000000000000000000
@@ -1653,7 +1654,7 @@
     "date": [*, 0], (glob)
     "desc": "",
     "bookmarks": [],
-    "tags": ["tip"],
+    "tags": [],
     "parents": ["65624cd9070a035fa7191a54f2b8af39f16b0c08"]
    }
   ]
@@ -1677,7 +1678,7 @@
     "date": [*, 0], (glob)
     "desc": "",
     "bookmarks": [],
-    "tags": ["tip"],
+    "tags": [],
     "parents": ["65624cd9070a035fa7191a54f2b8af39f16b0c08"],
     "manifest": null,
     "extra": {"branch": "default"},
--- a/tests/test-manifest.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-manifest.py	Sat Jul 18 17:32:38 2015 -0500
@@ -325,21 +325,21 @@
         try:
             self.parsemanifest(backwards)
             self.fail('Should have raised ValueError')
-        except ValueError, v:
+        except ValueError as v:
             self.assertIn('Manifest lines not in sorted order.', str(v))
 
     def testNoTerminalNewline(self):
         try:
             self.parsemanifest(A_SHORT_MANIFEST + 'wat')
             self.fail('Should have raised ValueError')
-        except ValueError, v:
+        except ValueError as v:
             self.assertIn('Manifest did not end in a newline.', str(v))
 
     def testNoNewLineAtAll(self):
         try:
             self.parsemanifest('wat')
             self.fail('Should have raised ValueError')
-        except ValueError, v:
+        except ValueError as v:
             self.assertIn('Manifest did not end in a newline.', str(v))
 
     def testHugeManifest(self):
--- a/tests/test-manifestv2.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-manifestv2.t	Sat Jul 18 17:32:38 2015 -0500
@@ -1,4 +1,69 @@
-Check that entry is added to .hg/requires
+Create repo with old manifest
+
+  $ hg init existing
+  $ cd existing
+  $ echo footext > foo
+  $ hg add foo
+  $ hg commit -m initial
+
+We're using v1, so no manifestv2 entry is in requires yet.
+
+  $ grep manifestv2 .hg/requires
+  [1]
+
+Let's clone this with manifestv2 enabled to switch to the new format for
+future commits.
+
+  $ cd ..
+  $ hg clone --pull existing new --config experimental.manifestv2=1
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd new
+
+Check that entry was added to .hg/requires.
+
+  $ grep manifestv2 .hg/requires
+  manifestv2
+
+Make a new commit.
+
+  $ echo newfootext > foo
+  $ hg commit -m new
+
+Check that the manifest actually switched to v2.
+
+  $ hg debugdata -m 0
+  foo\x0021e958b1dca695a60ee2e9cf151753204ee0f9e9 (esc)
+
+  $ hg debugdata -m 1
+  \x00 (esc)
+  \x00foo\x00 (esc)
+  I\xab\x7f\xb8(\x83\xcas\x15\x9d\xc2\xd3\xd3:5\x08\xbad5_ (esc)
+
+Check that manifestv2 is used if the requirement is present, even if it's
+disabled in the config.
+
+  $ echo newerfootext > foo
+  $ hg --config experimental.manifestv2=False commit -m newer
+
+  $ hg debugdata -m 2
+  \x00 (esc)
+  \x00foo\x00 (esc)
+  \xa6\xb1\xfb\xef]\x91\xa1\x19`\xf3.#\x90S\xf8\x06 \xe2\x19\x00 (esc)
+
+Check that we can still read v1 manifests.
+
+  $ hg files -r 0
+  foo
+
+  $ cd ..
+
+Check that entry is added to .hg/requires on repo creation
 
   $ hg --config experimental.manifestv2=True init repo
   $ cd repo
--- a/tests/test-merge-commit.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-merge-commit.t	Sat Jul 18 17:32:38 2015 -0500
@@ -73,7 +73,6 @@
    ancestor: 0f2ff26688b9, local: 2263c1be0967+, remote: 0555950ead28
    preserving bar for resolve of bar
    bar: versions differ -> m
-  updating: bar 1/1 files (100.00%)
   picked tool 'internal:merge' for bar (binary False symlink False)
   merging bar
   my bar@2263c1be0967+ other bar@0555950ead28 ancestor bar@0f2ff26688b9
@@ -160,7 +159,6 @@
    ancestor: 0f2ff26688b9, local: 2263c1be0967+, remote: 3ffa6b9e35f0
    preserving bar for resolve of bar
    bar: versions differ -> m
-  updating: bar 1/1 files (100.00%)
   picked tool 'internal:merge' for bar (binary False symlink False)
   merging bar
   my bar@2263c1be0967+ other bar@3ffa6b9e35f0 ancestor bar@0f2ff26688b9
--- a/tests/test-merge-criss-cross.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-merge-criss-cross.t	Sat Jul 18 17:32:38 2015 -0500
@@ -82,9 +82,7 @@
    preserving f2 for resolve of f2
    f1: remote is newer -> g
   getting f1
-  updating: f1 1/2 files (50.00%)
    f2: versions differ -> m
-  updating: f2 2/2 files (100.00%)
   picked tool 'internal:dump' for f2 (binary False symlink False)
   merging f2
   my f2@3b08d01b0ab5+ other f2@adfe50279922 ancestor f2@40494bf2444c
@@ -151,7 +149,6 @@
   
    f1: remote is newer -> g
   getting f1
-  updating: f1 1/1 files (100.00%)
    f2: remote unchanged -> k
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
@@ -194,7 +191,6 @@
   
    f2: remote is newer -> g
   getting f2
-  updating: f2 1/1 files (100.00%)
    f1: remote unchanged -> k
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
@@ -259,7 +255,6 @@
   
    f1: remote is newer -> g
   getting f1
-  updating: f1 1/1 files (100.00%)
    f2: remote unchanged -> k
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
--- a/tests/test-merge-tools.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-merge-tools.t	Sat Jul 18 17:32:38 2015 -0500
@@ -601,6 +601,17 @@
 
 update is a merge ...
 
+(this also tests that files reverted with '--rev REV' are treated as
+"modified", even if none of mode, size and timestamp of them isn't
+changed on the filesystem (see also issue4583))
+
+  $ cat >> $HGRCPATH <<EOF
+  > [fakedirstatewritetime]
+  > # emulate invoking dirstate.write() via repo.status()
+  > # at 2000-01-01 00:00
+  > fakenow = 200001010000
+  > EOF
+
   $ beforemerge
   [merge-tools]
   false.whatever=
@@ -611,8 +622,16 @@
   $ f -s f
   f: size=17
   $ touch -t 200001010000 f
-  $ hg status f
+  $ hg debugrebuildstate
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
+  > EOF
   $ hg revert -q -r 1 .
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > fakedirstatewritetime = !
+  > EOF
   $ f -s f
   f: size=17
   $ touch -t 200001010000 f
@@ -646,8 +665,16 @@
   $ f -s f
   f: size=17
   $ touch -t 200001010000 f
-  $ hg status f
+  $ hg debugrebuildstate
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
+  > EOF
   $ hg revert -q -r 1 .
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > fakedirstatewritetime = !
+  > EOF
   $ f -s f
   f: size=17
   $ touch -t 200001010000 f
--- a/tests/test-merge-types.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-merge-types.t	Sat Jul 18 17:32:38 2015 -0500
@@ -36,7 +36,6 @@
    ancestor: c334dc3be0da, local: 521a1e40188f+, remote: 3574f3e69b1c
    preserving a for resolve of a
    a: versions differ -> m
-  updating: a 1/1 files (100.00%)
   picked tool 'internal:merge' for a (binary False symlink True)
   merging a
   my a@521a1e40188f+ other a@3574f3e69b1c ancestor a@c334dc3be0da
@@ -70,7 +69,6 @@
    ancestor: c334dc3be0da, local: 3574f3e69b1c+, remote: 521a1e40188f
    preserving a for resolve of a
    a: versions differ -> m
-  updating: a 1/1 files (100.00%)
   picked tool 'internal:merge' for a (binary False symlink True)
   merging a
   my a@3574f3e69b1c+ other a@521a1e40188f ancestor a@c334dc3be0da
@@ -104,7 +102,6 @@
    ancestor: c334dc3be0da, local: c334dc3be0da+, remote: 521a1e40188f
    preserving a for resolve of a
    a: versions differ -> m
-  updating: a 1/1 files (100.00%)
   (couldn't find merge tool hgmerge|tool hgmerge can't handle symlinks) (re)
   picked tool ':prompt' for a (binary False symlink True)
    no tool found to merge a
--- a/tests/test-merge1.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-merge1.t	Sat Jul 18 17:32:38 2015 -0500
@@ -40,6 +40,7 @@
   branch: default
   commit: (interrupted update)
   update: 1 new changesets (update)
+  phases: 2 draft
   $ rmdir b
   $ hg up
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -49,6 +50,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 2 draft
 
 Prepare a basic merge
 
@@ -204,4 +206,91 @@
   $ hg revert -r -2 b
   $ hg up -q -- -2
 
+Test that updated files are treated as "modified", when
+'merge.update()' is aborted before 'merge.recordupdates()' (= parents
+aren't changed), even if none of mode, size and timestamp of them
+isn't changed on the filesystem (see also issue4583).
+
+  $ cat > $TESTTMP/abort.py <<EOF
+  > # emulate aborting before "recordupdates()". in this case, files
+  > # are changed without updating dirstate
+  > from mercurial import extensions, merge, util
+  > def applyupdates(orig, *args, **kwargs):
+  >     orig(*args, **kwargs)
+  >     raise util.Abort('intentional aborting')
+  > def extsetup(ui):
+  >     extensions.wrapfunction(merge, "applyupdates", applyupdates)
+  > EOF
+
+  $ cat >> .hg/hgrc <<EOF
+  > [fakedirstatewritetime]
+  > # emulate invoking dirstate.write() via repo.status()
+  > # at 2000-01-01 00:00
+  > fakenow = 200001010000
+  > EOF
+
+(file gotten from other revision)
+
+  $ hg update -q -C 2
+  $ echo 'THIS IS FILE B5' > b
+  $ hg commit -m 'commit #5'
+
+  $ hg update -q -C 3
+  $ cat b
+  This is file b1
+  $ touch -t 200001010000 b
+  $ hg debugrebuildstate
+
+  $ cat >> .hg/hgrc <<EOF
+  > [extensions]
+  > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
+  > abort = $TESTTMP/abort.py
+  > EOF
+  $ hg merge 5
+  abort: intentional aborting
+  [255]
+  $ cat >> .hg/hgrc <<EOF
+  > [extensions]
+  > fakedirstatewritetime = !
+  > abort = !
+  > EOF
+
+  $ cat b
+  THIS IS FILE B5
+  $ touch -t 200001010000 b
+  $ hg status -A b
+  M b
+
+(file merged from other revision)
+
+  $ hg update -q -C 3
+  $ echo 'this is file b6' > b
+  $ hg commit -m 'commit #6'
+  created new head
+
+  $ cat b
+  this is file b6
+  $ touch -t 200001010000 b
+  $ hg debugrebuildstate
+
+  $ cat >> .hg/hgrc <<EOF
+  > [extensions]
+  > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
+  > abort = $TESTTMP/abort.py
+  > EOF
+  $ hg merge --tool internal:other 5
+  abort: intentional aborting
+  [255]
+  $ cat >> .hg/hgrc <<EOF
+  > [extensions]
+  > fakedirstatewritetime = !
+  > abort = !
+  > EOF
+
+  $ cat b
+  THIS IS FILE B5
+  $ touch -t 200001010000 b
+  $ hg status -A b
+  M b
+
   $ cd ..
--- a/tests/test-merge7.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-merge7.t	Sat Jul 18 17:32:38 2015 -0500
@@ -86,7 +86,6 @@
    ancestor: 96b70246a118, local: 50c3a7e29886+, remote: 40d11a4173a8
    preserving test.txt for resolve of test.txt
    test.txt: versions differ -> m
-  updating: test.txt 1/1 files (100.00%)
   picked tool 'internal:merge' for test.txt (binary False symlink False)
   merging test.txt
   my test.txt@50c3a7e29886+ other test.txt@40d11a4173a8 ancestor test.txt@96b70246a118
--- a/tests/test-module-imports.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-module-imports.t	Sat Jul 18 17:32:38 2015 -0500
@@ -1,9 +1,5 @@
 #require test-repo
 
-This code uses the ast module, which was new in 2.6, so we'll skip
-this test on anything earlier.
-  $ $PYTHON -c 'import sys ; assert sys.version_info >= (2, 6)' || exit 80
-
   $ import_checker="$TESTDIR"/../contrib/import-checker.py
 
 Run the doctests from the import checker, and make sure
@@ -12,6 +8,101 @@
   $ export TERM
   $ python -m doctest $import_checker
 
+Run additional tests for the import checker
+
+  $ mkdir testpackage
+
+  $ cat > testpackage/multiple.py << EOF
+  > from __future__ import absolute_import
+  > import os, sys
+  > EOF
+
+  $ cat > testpackage/unsorted.py << EOF
+  > from __future__ import absolute_import
+  > import sys
+  > import os
+  > EOF
+
+  $ cat > testpackage/stdafterlocal.py << EOF
+  > from __future__ import absolute_import
+  > from . import unsorted
+  > import os
+  > EOF
+
+  $ cat > testpackage/requirerelative.py << EOF
+  > from __future__ import absolute_import
+  > import testpackage.unsorted
+  > EOF
+
+  $ cat > testpackage/importalias.py << EOF
+  > from __future__ import absolute_import
+  > import ui
+  > EOF
+
+  $ cat > testpackage/relativestdlib.py << EOF
+  > from __future__ import absolute_import
+  > from .. import os
+  > EOF
+
+  $ cat > testpackage/symbolimport.py << EOF
+  > from __future__ import absolute_import
+  > from .unsorted import foo
+  > EOF
+
+  $ cat > testpackage/latesymbolimport.py << EOF
+  > from __future__ import absolute_import
+  > from . import unsorted
+  > from mercurial.node import hex
+  > EOF
+
+  $ cat > testpackage/multiplegroups.py << EOF
+  > from __future__ import absolute_import
+  > from . import unsorted
+  > from . import more
+  > EOF
+
+  $ mkdir testpackage/subpackage
+  $ cat > testpackage/subpackage/levelpriority.py << EOF
+  > from __future__ import absolute_import
+  > from . import foo
+  > from .. import parent
+  > EOF
+
+  $ cat > testpackage/sortedentries.py << EOF
+  > from __future__ import absolute_import
+  > from . import (
+  >     foo,
+  >     bar,
+  > )
+  > EOF
+
+  $ cat > testpackage/importfromalias.py << EOF
+  > from __future__ import absolute_import
+  > from . import ui
+  > EOF
+
+  $ cat > testpackage/importfromrelative.py << EOF
+  > from __future__ import absolute_import
+  > from testpackage.unsorted import foo
+  > EOF
+
+  $ python "$import_checker" testpackage/*.py testpackage/subpackage/*.py
+  testpackage/importalias.py ui module must be "as" aliased to uimod
+  testpackage/importfromalias.py ui from testpackage must be "as" aliased to uimod
+  testpackage/importfromrelative.py import should be relative: testpackage.unsorted
+  testpackage/importfromrelative.py direct symbol import from testpackage.unsorted
+  testpackage/latesymbolimport.py symbol import follows non-symbol import: mercurial.node
+  testpackage/multiple.py multiple imported names: os, sys
+  testpackage/multiplegroups.py multiple "from . import" statements
+  testpackage/relativestdlib.py relative import of stdlib module
+  testpackage/requirerelative.py import should be relative: testpackage.unsorted
+  testpackage/sortedentries.py imports from testpackage not lexically sorted: bar < foo
+  testpackage/stdafterlocal.py stdlib import follows local import: os
+  testpackage/subpackage/levelpriority.py higher-level import should come first: testpackage
+  testpackage/symbolimport.py direct symbol import from testpackage.unsorted
+  testpackage/unsorted.py imports not lexically sorted: os < sys
+  [1]
+
   $ cd "$TESTDIR"/..
 
 There are a handful of cases here that require renaming a module so it
@@ -20,10 +111,7 @@
 hidden by deduplication algorithm in the cycle detector, so fixing
 these may expose other cycles.
 
-  $ hg locate 'mercurial/**.py' | sed 's-\\-/-g' | xargs python "$import_checker"
-  mercurial/crecord.py mixed imports
-     stdlib:    fcntl, termios
-     relative:  curses
+  $ hg locate 'mercurial/**.py' 'hgext/**.py' | sed 's-\\-/-g' | python "$import_checker" -
   mercurial/dispatch.py mixed imports
      stdlib:    commands
      relative:  error, extensions, fancyopts, hg, hook, util
@@ -38,5 +126,8 @@
      relative:  config, error, templatefilters, templatekw, util
   mercurial/ui.py mixed imports
      stdlib:    formatter
-     relative:  config, error, scmutil, util
+     relative:  config, error, progress, scmutil, util
   Import cycle: mercurial.cmdutil -> mercurial.context -> mercurial.subrepo -> mercurial.cmdutil
+  Import cycle: hgext.largefiles.basestore -> hgext.largefiles.localstore -> hgext.largefiles.basestore
+  Import cycle: mercurial.commands -> mercurial.commandserver -> mercurial.dispatch -> mercurial.commands
+  [1]
--- a/tests/test-mq-qclone-http.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-mq-qclone-http.t	Sat Jul 18 17:32:38 2015 -0500
@@ -34,7 +34,7 @@
   $ hg serve -p $HGPORT -d --pid-file=hg.pid --webdir-conf collections.conf \
   >     -A access-paths.log -E error-paths-1.log
   $ cat hg.pid >> $DAEMON_PIDS
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT '?style=raw'
+  $ get-with-headers.py localhost:$HGPORT '?style=raw'
   200 Script output follows
   
   
@@ -73,7 +73,7 @@
   $ hg serve -p $HGPORT1 -d --pid-file=hg.pid --webdir-conf collections1.conf \
   >     -A access-paths.log -E error-paths-1.log
   $ cat hg.pid >> $DAEMON_PIDS
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT1 '?style=raw'
+  $ get-with-headers.py localhost:$HGPORT1 '?style=raw'
   200 Script output follows
   
   
@@ -112,7 +112,7 @@
   $ hg serve -p $HGPORT2 -d --pid-file=hg.pid --webdir-conf collections2.conf \
   >     -A access-paths.log -E error-paths-1.log
   $ cat hg.pid >> $DAEMON_PIDS
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT2 '?style=raw'
+  $ get-with-headers.py localhost:$HGPORT2 '?style=raw'
   200 Script output follows
   
   
@@ -152,5 +152,5 @@
   $ hg --cwd d log --mq --template '{rev} {desc|firstline}\n'
   0 b.patch
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
 
--- a/tests/test-mq-qimport.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-mq-qimport.t	Sat Jul 18 17:32:38 2015 -0500
@@ -289,4 +289,4 @@
 
   $ cd ..
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
--- a/tests/test-mq-qnew.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-mq-qnew.t	Sat Jul 18 17:32:38 2015 -0500
@@ -21,6 +21,7 @@
   >     hg qnew .mqfoo
   >     hg qnew 'foo#bar'
   >     hg qnew 'foo:bar'
+  >     hg qnew "`echo foo; echo bar`"
   > 
   >     hg qinit -c
   > 
@@ -108,8 +109,9 @@
   abort: ".." cannot be used as the name of a patch
   abort: patch name cannot begin with ".hg"
   abort: patch name cannot begin with ".mq"
-  abort: "#" cannot be used in the name of a patch
-  abort: ":" cannot be used in the name of a patch
+  abort: '#' cannot be used in the name of a patch
+  abort: ':' cannot be used in the name of a patch
+  abort: '\n' cannot be used in the name of a patch
   % qnew with name containing slash
   abort: path ends in directory separator: foo/ (glob)
   abort: "foo" already exists as a directory
@@ -176,8 +178,9 @@
   abort: ".." cannot be used as the name of a patch
   abort: patch name cannot begin with ".hg"
   abort: patch name cannot begin with ".mq"
-  abort: "#" cannot be used in the name of a patch
-  abort: ":" cannot be used in the name of a patch
+  abort: '#' cannot be used in the name of a patch
+  abort: ':' cannot be used in the name of a patch
+  abort: '\n' cannot be used in the name of a patch
   % qnew with name containing slash
   abort: path ends in directory separator: foo/ (glob)
   abort: "foo" already exists as a directory
--- a/tests/test-mq-qpush-fail.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-mq-qpush-fail.t	Sat Jul 18 17:32:38 2015 -0500
@@ -34,7 +34,23 @@
   $ $PYTHON -c 'print "\xe9"' > message
   $ cat .hg/patches/bad-patch >> message
   $ mv message .hg/patches/bad-patch
-  $ hg qpush -a && echo 'qpush succeeded?!'
+  $ cat > $TESTTMP/wrapplayback.py <<EOF
+  > import os
+  > from mercurial import extensions, transaction
+  > def wrapplayback(orig,
+  >                  journal, report, opener, vfsmap, entries, backupentries,
+  >                  unlink=True):
+  >     orig(journal, report, opener, vfsmap, entries, backupentries, unlink)
+  >     # Touching files truncated at "transaction.abort" causes
+  >     # forcible re-loading invalidated filecache properties
+  >     # (including repo.changelog)
+  >     for f, o, _ignore in entries:
+  >         if o or not unlink:
+  >             os.utime(opener.join(f), (0.0, 0.0))
+  > def extsetup(ui):
+  >     extensions.wrapfunction(transaction, '_playback', wrapplayback)
+  > EOF
+  $ hg qpush -a --config extensions.wrapplayback=$TESTTMP/wrapplayback.py  && echo 'qpush succeeded?!'
   applying patch1
   applying patch2
   applying bad-patch
--- a/tests/test-mq-qrefresh-interactive.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-mq-qrefresh-interactive.t	Sat Jul 18 17:32:38 2015 -0500
@@ -123,6 +123,12 @@
 
   $ hg add 1.txt 2.txt dir/a.txt
   $ hg commit -m aaa
+  $ hg qrecord --config ui.interactive=false patch
+  abort: running non-interactively, use qnew instead
+  [255]
+  $ hg qnew -i --config ui.interactive=false patch
+  abort: running non-interactively
+  [255]
   $ hg qnew -d '0 0' patch
 
 Changing files
@@ -171,6 +177,9 @@
 
 partial qrefresh
 
+  $ hg qrefresh -i --config ui.interactive=false
+  abort: running non-interactively
+  [255]
   $ hg qrefresh -i -d '0 0' <<EOF
   > y
   > y
--- a/tests/test-mq-safety.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-mq-safety.t	Sat Jul 18 17:32:38 2015 -0500
@@ -25,17 +25,17 @@
   $ hg phase --public qbase
   $ echo babar >> foo
   $ hg qref
-  abort: cannot refresh immutable revision
+  abort: cannot refresh public revision
   (see "hg help phases" for details)
   [255]
   $ hg revert -a
   reverting foo
   $ hg qpop
-  abort: popping would remove an immutable revision
+  abort: popping would remove a public revision
   (see "hg help phases" for details)
   [255]
   $ hg qfold bar
-  abort: cannot refresh immutable revision
+  abort: cannot refresh public revision
   (see "hg help phases" for details)
   [255]
   $ hg revert -a
--- a/tests/test-mq-subrepo.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-mq-subrepo.t	Sat Jul 18 17:32:38 2015 -0500
@@ -106,6 +106,7 @@
   [255]
   % update substate when adding .hgsub w/clean updated subrepo
   A .hgsub
+  A sub/a
   % qnew -X path:no-effect -m0 0.diff
   path sub
    source   sub
@@ -121,6 +122,7 @@
   [255]
   % update substate when modifying .hgsub w/clean updated subrepo
   M .hgsub
+  A sub2/a
   % qnew --cwd .. -R repo-2499-qnew -X path:no-effect -m1 1.diff
   path sub
    source   sub
@@ -165,6 +167,7 @@
   [255]
   % update substate when adding .hgsub w/clean updated subrepo
   A .hgsub
+  A sub/a
   % qrefresh
   path sub
    source   sub
@@ -181,6 +184,7 @@
   [255]
   % update substate when modifying .hgsub w/clean updated subrepo
   M .hgsub
+  A sub2/a
   % qrefresh
   path sub
    source   sub
@@ -304,6 +308,7 @@
   [255]
   % update substate when adding .hgsub w/clean updated subrepo
   A .hgsub
+  A sub/a
   % qrecord --config ui.interactive=1 -m0 0.diff
   diff --git a/.hgsub b/.hgsub
   new file mode 100644
@@ -339,6 +344,7 @@
   [255]
   % update substate when modifying .hgsub w/clean updated subrepo
   M .hgsub
+  A sub2/a
   % qrecord --config ui.interactive=1 -m1 1.diff
   diff --git a/.hgsub b/.hgsub
   1 hunks, 1 lines changed
--- a/tests/test-mq-symlinks.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-mq-symlinks.t	Sat Jul 18 17:32:38 2015 -0500
@@ -11,7 +11,7 @@
   $ echo ccc > c
   $ hg add a b c
   $ hg qrefresh
-  $ "$TESTDIR/readlink.py" a
+  $ readlink.py a
   a -> a not a symlink
 
 
@@ -21,7 +21,7 @@
   $ rm a
   $ ln -s b a
   $ hg qrefresh --git
-  $ "$TESTDIR/readlink.py" a
+  $ readlink.py a
   a -> b
 
   $ hg qpop
@@ -30,7 +30,7 @@
   $ hg qpush
   applying symlink.patch
   now at: symlink.patch
-  $ "$TESTDIR/readlink.py" a
+  $ readlink.py a
   a -> b
 
 
@@ -39,7 +39,7 @@
   $ rm a
   $ ln -s c a
   $ hg qnew --git -f updatelink
-  $ "$TESTDIR/readlink.py" a
+  $ readlink.py a
   a -> c
   $ hg qpop
   popping updatelink
@@ -52,7 +52,7 @@
   committing manifest
   committing changelog
   now at: updatelink
-  $ "$TESTDIR/readlink.py" a
+  $ readlink.py a
   a -> c
   $ hg st
 
@@ -107,5 +107,5 @@
   $ hg qpush
   applying movelink
   now at: movelink
-  $ "$TESTDIR/readlink.py" linkb
+  $ readlink.py linkb
   linkb -> linkb
--- a/tests/test-newbranch.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-newbranch.t	Sat Jul 18 17:32:38 2015 -0500
@@ -25,7 +25,6 @@
   $ hg ci -m "add branch name"
   $ hg branch bar
   marked working directory as branch bar
-  (branches are permanent and global, did you want a bookmark?)
   $ hg ci -m "change branch name"
 
 Branch shadowing:
@@ -37,7 +36,6 @@
 
   $ hg branch -f default
   marked working directory as branch default
-  (branches are permanent and global, did you want a bookmark?)
 
   $ hg ci -m "clear branch name"
   created new head
@@ -67,11 +65,9 @@
 
   $ hg branch -f bar
   marked working directory as branch bar
-  (branches are permanent and global, did you want a bookmark?)
 
   $ hg branch foo
   marked working directory as branch foo
-  (branches are permanent and global, did you want a bookmark?)
 
   $ echo bleah > a
   $ hg ci -m "modify a branch"
@@ -94,13 +90,11 @@
 
   $ hg branch default
   marked working directory as branch default
-  (branches are permanent and global, did you want a bookmark?)
 
  set (first) parent branch as branch name
 
   $ hg branch foo
   marked working directory as branch foo
-  (branches are permanent and global, did you want a bookmark?)
 
   $ hg ci -m "merge"
 
@@ -215,7 +209,6 @@
 
   $ hg branch foobar
   marked working directory as branch foobar
-  (branches are permanent and global, did you want a bookmark?)
 
   $ hg up
   abort: branch foobar not found
@@ -225,7 +218,6 @@
 
   $ hg branch ff
   marked working directory as branch ff
-  (branches are permanent and global, did you want a bookmark?)
 
   $ echo ff > ff
   $ hg ci -Am'fast forward'
--- a/tests/test-obsolete.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-obsolete.t	Sat Jul 18 17:32:38 2015 -0500
@@ -4,6 +4,10 @@
   > publish=false
   > [ui]
   > logtemplate="{rev}:{node|short} ({phase}) [{tags} {bookmarks}] {desc|firstline}\n"
+  > [experimental]
+  > # drop me once bundle2 is the default,
+  > # added to get test change early.
+  > bundle2-exp = True
   > EOF
   $ mkcommit() {
   >    echo "$1" > "$1"
@@ -164,6 +168,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 3 draft
   remote: 3 outgoing
 
   $ hg summary --remote --hidden
@@ -172,6 +177,7 @@
   branch: default
   commit: (clean)
   update: 3 new changesets, 4 branch heads (merge)
+  phases: 6 draft
   remote: 3 outgoing
 
 check that various commands work well with filtering
@@ -305,34 +311,35 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 4 files (+1 heads)
+  5 new obsolescence markers
   (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'}
   245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
-  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
+  5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
   ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
-  1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
-  5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
+  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
 
 Rollback//Transaction support
 
   $ hg debugobsolete -d '1340 0' aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
   $ hg debugobsolete
+  1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
   245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
-  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
+  5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
   ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
-  1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
-  5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
+  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 0 (Thu Jan 01 00:22:20 1970 +0000) {'user': 'test'}
   $ hg rollback -n
   repository tip rolled back to revision 3 (undo debugobsolete)
   $ hg rollback
   repository tip rolled back to revision 3 (undo debugobsolete)
   $ hg debugobsolete
+  1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
   245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
-  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
+  5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
   ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
-  1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
-  5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
+  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
 
   $ cd ..
 
@@ -346,6 +353,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 4 files (+1 heads)
+  5 new obsolescence markers
   $ hg -R tmpd debugobsolete | sort
   1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
   245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
@@ -408,14 +416,15 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 4 files (+1 heads)
+  5 new obsolescence markers
   (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'}
+  1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
   245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
-  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
+  5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
   ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
-  1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
-  5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
+  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
 
 
 On push
@@ -424,13 +433,14 @@
   pushing to ../tmpc
   searching for changes
   no changes found
+  1 new obsolescence markers
   [1]
   $ hg -R ../tmpc debugobsolete
+  1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
   245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
-  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
+  5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
   ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
-  1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
-  5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
+  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
   1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
 
 detect outgoing obsolete and unstable
@@ -506,6 +516,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 6 changes to 6 files (+1 heads)
+  7 new obsolescence markers
 
 no warning displayed
 
@@ -545,6 +556,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  1 new obsolescence markers
 
 test relevance computation
 ---------------------------------------
@@ -574,11 +586,11 @@
 
   $ hg debugobsolete
   1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
+  1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
   245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
-  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
+  5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
   ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
-  1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
-  5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
+  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
   94b33453f93bdb8d457ef9b770851a618bf413e1 0 {6f96419950729f3671185b847352890f074f7557} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
   cda648ca50f50482b7055c0b0c4c117bba6733d9 3de5eca88c00aa039da7399a220f4a5221faa585 0 (*) {'user': 'test'} (glob)
 
@@ -646,37 +658,37 @@
 
 check changelog view
 
-  $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'shortlog/'
+  $ get-with-headers.py --headeronly localhost:$HGPORT 'shortlog/'
   200 Script output follows
 
 check graph view
 
-  $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'graph'
+  $ get-with-headers.py --headeronly localhost:$HGPORT 'graph'
   200 Script output follows
 
 check filelog view
 
-  $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'log/'`hg log -r . -T "{node}"`/'babar'
+  $ get-with-headers.py --headeronly localhost:$HGPORT 'log/'`hg log -r . -T "{node}"`/'babar'
   200 Script output follows
 
-  $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'rev/68'
+  $ get-with-headers.py --headeronly localhost:$HGPORT 'rev/68'
   200 Script output follows
-  $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'rev/67'
+  $ get-with-headers.py --headeronly localhost:$HGPORT 'rev/67'
   404 Not Found
   [1]
 
 check that web.view config option:
 
-  $ "$TESTDIR/killdaemons.py" hg.pid
+  $ killdaemons.py hg.pid
   $ cat >> .hg/hgrc << EOF
   > [web]
   > view=all
   > EOF
   $ wait
   $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
-  $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'rev/67'
+  $ get-with-headers.py --headeronly localhost:$HGPORT 'rev/67'
   200 Script output follows
-  $ "$TESTDIR/killdaemons.py" hg.pid
+  $ killdaemons.py hg.pid
 
 Checking _enable=False warning if obsolete marker exists
 
@@ -744,7 +756,7 @@
   no changes found
   [1]
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
 
 #endif
 
@@ -762,6 +774,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  2 new obsolescence markers
   $ hg out ../repo-issue3814
   comparing with ../repo-issue3814
   searching for changes
@@ -872,15 +885,84 @@
   $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
   $ cat hg.pid >> $DAEMON_PIDS
 
-  $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'rev/1'
+  $ get-with-headers.py --headeronly localhost:$HGPORT 'rev/1'
   404 Not Found
   [1]
-  $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'file/tip/bar'
+  $ get-with-headers.py --headeronly localhost:$HGPORT 'file/tip/bar'
   200 Script output follows
-  $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'annotate/tip/bar'
+  $ get-with-headers.py --headeronly localhost:$HGPORT 'annotate/tip/bar'
   200 Script output follows
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
 
 #endif
 
+Test heads computation on pending index changes with obsolescence markers
+  $ cd ..
+  $ cat >$TESTTMP/test_extension.py  << EOF
+  > from mercurial import cmdutil
+  > from mercurial.i18n import _
+  > 
+  > cmdtable = {}
+  > command = cmdutil.command(cmdtable)
+  > @command("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)
+  >   print repo.changelog.headrevs()
+  > EOF
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > testextension=$TESTTMP/test_extension.py
+  > EOF
+  $ hg init repo-issue-nativerevs-pending-changes
+  $ cd repo-issue-nativerevs-pending-changes
+  $ mkcommit a
+  $ mkcommit b
+  $ hg up ".^"
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo aa > a
+  $ hg amendtransient
+  [1, 3]
+
+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 mercurial import cmdutil, extensions, bookmarks, repoview
+  > def _bookmarkchanged(orig, bkmstoreinst, *args, **kwargs):
+  >  repo = bkmstoreinst._repo
+  >  ret = orig(bkmstoreinst, *args, **kwargs)
+  >  hidden1 = repoview.computehidden(repo)
+  >  hidden = repoview.filterrevs(repo, 'visible')
+  >  if sorted(hidden1) != sorted(hidden):
+  >    print "cache inconsistency"
+  >  return ret
+  > def extsetup(ui):
+  >   extensions.wrapfunction(bookmarks.bmstore, 'write', _bookmarkchanged)
+  > EOF
+
+  $ hg init repo-cache-inconsistency
+  $ cd repo-issue-nativerevs-pending-changes
+  $ mkcommit a
+  a already tracked!
+  $ mkcommit b
+  $ hg id
+  13bedc178fce tip
+  $ echo "hello" > b
+  $ hg commit --amend -m "message"
+  $ hg book bookb -r 13bedc178fce --hidden
+  $ hg log -r 13bedc178fce
+  5:13bedc178fce (draft) [ bookb] add b
+  $ hg book -d bookb
+  $ hg log -r 13bedc178fce
+  abort: hidden revision '13bedc178fce'!
+  (use --hidden to access hidden revisions)
+  [255]
+
+
+
--- a/tests/test-parseindex.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-parseindex.t	Sat Jul 18 17:32:38 2015 -0500
@@ -59,3 +59,62 @@
   26333235a41c
 
   $ cd ..
+
+Test corrupted p1/p2 fields that could cause SEGV at parsers.c:
+
+  $ mkdir invalidparent
+  $ cd invalidparent
+
+  $ hg clone --pull -q --config phases.publish=False ../a limit
+  $ hg clone --pull -q --config phases.publish=False ../a segv
+  $ rm -R limit/.hg/cache segv/.hg/cache
+
+  $ python <<EOF
+  > data = open("limit/.hg/store/00changelog.i", "rb").read()
+  > for n, p in [('limit', '\0\0\0\x02'), ('segv', '\0\x01\0\0')]:
+  >     # corrupt p1 at rev0 and p2 at rev1
+  >     d = data[:24] + p + data[28:127 + 28] + p + data[127 + 32:]
+  >     open(n + "/.hg/store/00changelog.i", "wb").write(d)
+  > EOF
+
+  $ hg debugindex -f1 limit/.hg/store/00changelog.i
+     rev flag   offset   length     size   base   link     p1     p2       nodeid
+       0 0000        0       63       62      0      0      2     -1 7c31755bf9b5
+       1 0000       63       66       65      1      1      0      2 26333235a41c
+  $ hg debugindex -f1 segv/.hg/store/00changelog.i
+     rev flag   offset   length     size   base   link     p1     p2       nodeid
+       0 0000        0       63       62      0      0  65536     -1 7c31755bf9b5
+       1 0000       63       66       65      1      1      0  65536 26333235a41c
+
+  $ cat <<EOF > test.py
+  > import sys
+  > from mercurial import changelog, scmutil
+  > cl = changelog.changelog(scmutil.vfs(sys.argv[1]))
+  > n0, n1 = cl.node(0), cl.node(1)
+  > ops = [
+  >     ('compute_phases_map_sets', lambda: cl.computephases([[0], []])),
+  >     ('index_headrevs', lambda: cl.headrevs()),
+  >     ('find_gca_candidates', lambda: cl.commonancestorsheads(n0, n1)),
+  >     ('find_deepest', lambda: cl.ancestor(n0, n1)),
+  >     ]
+  > for l, f in ops:
+  >     print l + ':',
+  >     try:
+  >         f()
+  >         print 'uncaught buffer overflow?'
+  >     except ValueError, inst:
+  >         print inst
+  > EOF
+
+  $ python test.py limit/.hg/store
+  compute_phases_map_sets: parent out of range
+  index_headrevs: parent out of range
+  find_gca_candidates: parent out of range
+  find_deepest: parent out of range
+  $ python test.py segv/.hg/store
+  compute_phases_map_sets: parent out of range
+  index_headrevs: parent out of range
+  find_gca_candidates: parent out of range
+  find_deepest: parent out of range
+
+  $ cd ..
--- a/tests/test-patchbomb.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-patchbomb.t	Sat Jul 18 17:32:38 2015 -0500
@@ -289,18 +289,11 @@
   
   \r (no-eol) (esc)
   sending [                                             ] 0/3\r (no-eol) (esc)
-  sending [                                             ] 0/3\r (no-eol) (esc)
-                                                              \r (no-eol) (esc)
-  \r (no-eol) (esc)
                                                               \r (no-eol) (esc)
   \r (no-eol) (esc)
   sending [==============>                              ] 1/3\r (no-eol) (esc)
-  sending [==============>                              ] 1/3\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
   \r (no-eol) (esc)
-                                                              \r (no-eol) (esc)
-  \r (no-eol) (esc)
-  sending [=============================>               ] 2/3\r (no-eol) (esc)
   sending [=============================>               ] 2/3\r (no-eol) (esc)
                                                               \r (esc)
   sending [PATCH 0 of 2] test ...
--- a/tests/test-phases-exchange.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-phases-exchange.t	Sat Jul 18 17:32:38 2015 -0500
@@ -1,5 +1,12 @@
 #require killdaemons
 
+  $ cat << EOF >> $HGRCPATH
+  > [experimental]
+  > # drop me once bundle2 is the default,
+  > # added to get test change early.
+  > bundle2-exp = True
+  > EOF
+
   $ hgph() { hg log -G --template "{rev} {phase} {desc} - {node|short}\n" $*; }
 
   $ mkcommit() {
@@ -765,9 +772,9 @@
   searching for changes
   1 changesets found
   uncompressed size of bundle content:
-       172 (changelog)
-       145 (manifests)
-       111  a-H
+       192 (changelog)
+       165 (manifests)
+       131  a-H
   adding changesets
   adding manifests
   adding file changes
@@ -1041,7 +1048,16 @@
   $ cat ../beta.pid >> $DAEMON_PIDS
   $ cd ../gamma
 
-  $ hg pull http://localhost:$HGPORT/
+  $ hg pull http://localhost:$HGPORT/ --config experimental.bundle2-exp=True
+  pulling from http://localhost:$HGPORT/
+  searching for changes
+  no changes found
+  $ hg phase f54f1bb90ff3
+  2: draft
+
+enforce bundle1
+
+  $ hg pull http://localhost:$HGPORT/ --config experimental.bundle2-exp=False
   pulling from http://localhost:$HGPORT/
   searching for changes
   no changes found
@@ -1177,6 +1193,6 @@
 
   $ cd ..
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
 
 #endif
--- a/tests/test-phases.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-phases.t	Sat Jul 18 17:32:38 2015 -0500
@@ -36,6 +36,8 @@
 Draft commit are properly created over public one:
 
   $ hg phase --public .
+  $ hg phase
+  1: public
   $ hglog
   1 0 B
   0 0 A
@@ -86,6 +88,9 @@
   $ hg merge 4 # E
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
+  $ hg phase
+  6: draft
+  4: secret
   $ hg ci -m "merge B' and E"
   $ hglog
   7 2 merge B' and E
@@ -242,6 +247,24 @@
   1 0 B
   0 0 A
 
+Test summary
+
+  $ hg summary -R clone-dest --verbose
+  parent: -1:000000000000  (no revision checked out)
+  branch: default
+  commit: (clean)
+  update: 5 new changesets (update)
+  $ hg summary -R initialrepo
+  parent: 7:17a481b3bccb tip
+   merge B' and E
+  branch: default
+  commit: (clean) (secret)
+  update: 1 new changesets, 2 branch heads (merge)
+  phases: 3 draft, 3 secret
+  $ hg summary -R initialrepo --quiet
+  parent: 7:17a481b3bccb tip
+  update: 1 new changesets, 2 branch heads (merge)
+
 Test revset
 
   $ cd initialrepo
--- a/tests/test-progress.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-progress.t	Sat Jul 18 17:32:38 2015 -0500
@@ -76,7 +76,8 @@
   loop [===============>                                ] 1/3\r (no-eol) (esc)
   loop [===============================>                ] 2/3\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
-
+no progress with --quiet
+  $ hg -y loop 3 --quiet
 
 test nested short-lived topics (which shouldn't display with nestdelay):
 
--- a/tests/test-pull-branch.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-pull-branch.t	Sat Jul 18 17:32:38 2015 -0500
@@ -33,7 +33,6 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg branch branchB
   marked working directory as branch branchB
-  (branches are permanent and global, did you want a bookmark?)
   $ echo b1 > foo
   $ hg ci -mb1 # 3
 
@@ -141,7 +140,6 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg branch branchC
   marked working directory as branch branchC
-  (branches are permanent and global, did you want a bookmark?)
   $ echo b1 > bar
   $ hg ci -Am "commit on branchC on tt"
   adding bar
--- a/tests/test-pull-http.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-pull-http.t	Sat Jul 18 17:32:38 2015 -0500
@@ -40,7 +40,7 @@
   [ui]
   # name and email (local to this repository, optional), e.g.
   # username = Jane Doe <jdoe@example.com>
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
 
 expect error, cloning not allowed
 
@@ -48,10 +48,14 @@
   $ echo 'allowpull = false' >> .hg/hgrc
   $ hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
   $ cat hg.pid >> $DAEMON_PIDS
-  $ hg clone http://localhost:$HGPORT/ test4
+  $ hg clone http://localhost:$HGPORT/ test4 --config experimental.bundle2-exp=True
+  requesting all changes
   abort: authorization failed
   [255]
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ hg clone http://localhost:$HGPORT/ test4 --config experimental.bundle2-exp=False
+  abort: authorization failed
+  [255]
+  $ killdaemons.py
 
 serve errors
 
@@ -60,7 +64,7 @@
   >     hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
   >     cat hg.pid >> $DAEMON_PIDS
   >     hg --cwd ../test pull http://localhost:$HGPORT/
-  >     "$TESTDIR/killdaemons.py" hg.pid
+  >     killdaemons.py hg.pid
   >     echo % serve errors
   >     cat errors.log
   > }
@@ -69,6 +73,7 @@
 
   $ req
   pulling from http://localhost:$HGPORT/
+  searching for changes
   abort: authorization failed
   % serve errors
 
--- a/tests/test-push-hook-lock.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-push-hook-lock.t	Sat Jul 18 17:32:38 2015 -0500
@@ -26,7 +26,7 @@
   $ echo bar >> 3/foo
   $ hg --cwd 3 ci -m bar
 
-  $ hg --cwd 3 push ../2
+  $ hg --cwd 3 push ../2 --config experimental.bundle2-exp=False
   pushing to ../2
   searching for changes
   adding changesets
@@ -36,3 +36,15 @@
   lock:  user *, process * (*s) (glob)
   wlock: free
 
+  $ hg --cwd 1 --config extensions.strip= strip tip -q
+  $ hg --cwd 2 --config extensions.strip= strip tip -q
+  $ hg --cwd 3 push ../2 --config experimental.bundle2-exp=True
+  pushing to ../2
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  lock:  user *, process * (*s) (glob)
+  wlock: user *, process * (*s) (glob)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-push-http-bundle1.t	Sat Jul 18 17:32:38 2015 -0500
@@ -0,0 +1,169 @@
+#require killdaemons
+
+This test checks behavior related to bundle1 that changed or is likely
+to change with bundle2. Feel free to factor out any part of the test
+which does not need to exist to keep bundle1 working.
+
+  $ cat << EOF >> $HGRCPATH
+  > [experimental]
+  > # This test is dedicated to interaction through old bundle
+  > bundle2-exp = False
+  > EOF
+
+  $ hg init test
+  $ cd test
+  $ echo a > a
+  $ hg ci -Ama
+  adding a
+  $ cd ..
+  $ hg clone test test2
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd test2
+  $ echo a >> a
+  $ hg ci -mb
+  $ req() {
+  >     hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
+  >     cat hg.pid >> $DAEMON_PIDS
+  >     hg --cwd ../test2 push http://localhost:$HGPORT/
+  >     exitstatus=$?
+  >     killdaemons.py
+  >     echo % serve errors
+  >     cat errors.log
+  >     return $exitstatus
+  > }
+  $ cd ../test
+
+expect ssl error
+
+  $ req
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  abort: HTTP Error 403: ssl required
+  % serve errors
+  [255]
+
+expect authorization error
+
+  $ echo '[web]' > .hg/hgrc
+  $ echo 'push_ssl = false' >> .hg/hgrc
+  $ req
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  abort: authorization failed
+  % serve errors
+  [255]
+
+expect authorization error: must have authorized user
+
+  $ echo 'allow_push = unperson' >> .hg/hgrc
+  $ req
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  abort: authorization failed
+  % serve errors
+  [255]
+
+expect success
+
+  $ echo 'allow_push = *' >> .hg/hgrc
+  $ echo '[hooks]' >> .hg/hgrc
+  $ echo "changegroup = printenv.py changegroup 0" >> .hg/hgrc
+  $ echo "pushkey = printenv.py pushkey 0" >> .hg/hgrc
+  $ 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
+  remote: changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:http:127.0.0.1: (glob)
+  % serve errors
+  $ hg rollback
+  repository tip rolled back to revision 0 (undo serve)
+
+expect success, server lacks the httpheader capability
+
+  $ CAP=httpheader
+  $ . "$TESTDIR/notcapable"
+  $ 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
+  remote: changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:http:127.0.0.1: (glob)
+  % serve errors
+  $ hg rollback
+  repository tip rolled back to revision 0 (undo serve)
+
+expect success, server lacks the unbundlehash capability
+
+  $ CAP=unbundlehash
+  $ . "$TESTDIR/notcapable"
+  $ 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
+  remote: changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:http:127.0.0.1: (glob)
+  % serve errors
+  $ hg rollback
+  repository tip rolled back to revision 0 (undo serve)
+
+expect push success, phase change failure
+
+  $ cat > .hg/hgrc <<EOF
+  > [web]
+  > push_ssl = false
+  > allow_push = *
+  > [hooks]
+  > prepushkey = printenv.py prepushkey 1
+  > 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
+
+expect phase change success
+
+  $ echo "prepushkey = printenv.py prepushkey 0" >> .hg/hgrc
+  $ req
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  no changes found
+  % serve errors
+  [1]
+  $ hg rollback
+  repository tip rolled back to revision 0 (undo serve)
+
+expect authorization error: all users denied
+
+  $ echo '[web]' > .hg/hgrc
+  $ echo 'push_ssl = false' >> .hg/hgrc
+  $ echo 'deny_push = *' >> .hg/hgrc
+  $ req
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  abort: authorization failed
+  % serve errors
+  [255]
+
+expect authorization error: some users denied, users must be authenticated
+
+  $ echo 'deny_push = unperson' >> .hg/hgrc
+  $ req
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  abort: authorization failed
+  % serve errors
+  [255]
+
+  $ cd ..
--- a/tests/test-push-http.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-push-http.t	Sat Jul 18 17:32:38 2015 -0500
@@ -17,7 +17,7 @@
   >     cat hg.pid >> $DAEMON_PIDS
   >     hg --cwd ../test2 push http://localhost:$HGPORT/
   >     exitstatus=$?
-  >     "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  >     killdaemons.py
   >     echo % serve errors
   >     cat errors.log
   >     return $exitstatus
@@ -58,8 +58,8 @@
 
   $ echo 'allow_push = *' >> .hg/hgrc
   $ echo '[hooks]' >> .hg/hgrc
-  $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup 0" >> .hg/hgrc
-  $ echo "pushkey = python \"$TESTDIR/printenv.py\" pushkey 0" >> .hg/hgrc
+  $ echo "changegroup = printenv.py changegroup 0" >> .hg/hgrc
+  $ echo "pushkey = printenv.py pushkey 0" >> .hg/hgrc
   $ req
   pushing to http://localhost:$HGPORT/
   searching for changes
@@ -67,7 +67,8 @@
   remote: adding manifests
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
-  remote: changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:http:127.0.0.1: (glob)
+  remote: pushkey hook: HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_OLD=1 HG_RET=1
+  remote: changegroup hook: HG_BUNDLE2=1 HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:http:127.0.0.1: (glob)
   % serve errors
   $ hg rollback
   repository tip rolled back to revision 0 (undo serve)
@@ -83,7 +84,8 @@
   remote: adding manifests
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
-  remote: changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:http:127.0.0.1: (glob)
+  remote: pushkey hook: HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_OLD=1 HG_RET=1
+  remote: changegroup hook: HG_BUNDLE2=1 HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:http:127.0.0.1: (glob)
   % serve errors
   $ hg rollback
   repository tip rolled back to revision 0 (undo serve)
@@ -99,7 +101,8 @@
   remote: adding manifests
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
-  remote: changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:http:127.0.0.1: (glob)
+  remote: pushkey hook: HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_OLD=1 HG_RET=1
+  remote: changegroup hook: HG_BUNDLE2=1 HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:http:127.0.0.1: (glob)
   % serve errors
   $ hg rollback
   repository tip rolled back to revision 0 (undo serve)
@@ -111,7 +114,7 @@
   > push_ssl = false
   > allow_push = *
   > [hooks]
-  > prepushkey = python "$TESTDIR/printenv.py" prepushkey 1
+  > prepushkey = printenv.py prepushkey 1
   > EOF
   $ req
   pushing to http://localhost:$HGPORT/
@@ -120,17 +123,26 @@
   remote: adding manifests
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
+  remote: prepushkey hook: HG_BUNDLE2=1 HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_OLD=1 HG_PENDING=$TESTTMP/test HG_PHASES_MOVED=1 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:http:127.0.0.1: (glob)
+  remote: pushkey-abort: prepushkey hook exited with status 1
+  remote: transaction abort!
+  remote: rollback completed
+  abort: updating ba677d0156c1 to public failed
   % serve errors
+  [255]
 
 expect phase change success
 
-  $ echo "prepushkey = python \"$TESTDIR/printenv.py\" prepushkey 0" >> .hg/hgrc
+  $ echo "prepushkey = printenv.py prepushkey 0" >> .hg/hgrc
   $ req
   pushing to http://localhost:$HGPORT/
   searching for changes
-  no changes found
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 1 changesets with 1 changes to 1 files
+  remote: prepushkey hook: HG_BUNDLE2=1 HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_OLD=1 HG_PENDING=$TESTTMP/test HG_PHASES_MOVED=1 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:http:127.0.0.1: (glob)
   % serve errors
-  [1]
   $ hg rollback
   repository tip rolled back to revision 0 (undo serve)
 
--- a/tests/test-push-warn.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-push-warn.t	Sat Jul 18 17:32:38 2015 -0500
@@ -1,3 +1,9 @@
+  $ cat << EOF >> $HGRCPATH
+  > [experimental]
+  > # drop me once bundle2 is the default,
+  > # added to get test change early.
+  > bundle2-exp = True
+  > EOF
   $ hg init a
   $ cd a
   $ echo foo > t1
@@ -40,7 +46,6 @@
   query 1; heads
   searching for changes
   taking quick initial sample
-  searching: 2 queries
   query 2; still undecided: 1, sample size is: 1
   2 total queries
   listing keys for "phases"
@@ -151,9 +156,9 @@
   searching for changes
   2 changesets found
   uncompressed size of bundle content:
-       308 (changelog)
-       286 (manifests)
-       213  foo
+       348 (changelog)
+       326 (manifests)
+       253  foo
   adding changesets
   adding manifests
   adding file changes
@@ -463,7 +468,6 @@
   $ hg -R j ci -m a1
   $ hg -R k branch b
   marked working directory as branch b
-  (branches are permanent and global, did you want a bookmark?)
   $ echo b > k/foo
   $ hg -R k ci -m b
   $ hg -R k up 0
@@ -533,7 +537,6 @@
   adding a
   $ hg branch B
   marked working directory as branch B
-  (branches are permanent and global, did you want a bookmark?)
   $ echo b >b
   $ hg ci -Amb
   adding b
@@ -612,7 +615,6 @@
   adding a
   $ hg branch B
   marked working directory as branch B
-  (branches are permanent and global, did you want a bookmark?)
   $ echo b >b
   $ hg ci -Amb
   adding b
@@ -704,7 +706,6 @@
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg branch B
   marked working directory as branch B
-  (branches are permanent and global, did you want a bookmark?)
   $ echo b0 >b
   $ hg ci -Amb0
   adding b
@@ -719,7 +720,6 @@
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg branch -f B
   marked working directory as branch B
-  (branches are permanent and global, did you want a bookmark?)
   $ echo a3 >a
   $ hg ci -ma3
   created new head
@@ -727,7 +727,6 @@
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg branch -f A
   marked working directory as branch A
-  (branches are permanent and global, did you want a bookmark?)
   $ echo b3 >b
   $ hg ci -mb3
   created new head
--- a/tests/test-qrecord.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-qrecord.t	Sat Jul 18 17:32:38 2015 -0500
@@ -58,8 +58,7 @@
   
    -A --addremove           mark new/missing files as added/removed before
                             committing
-      --close-branch        mark a branch as closed, hiding it from the branch
-                            list
+      --close-branch        mark a branch head as closed
       --amend               amend the parent of the working directory
    -s --secret              use the secret phase for committing
    -e --edit                invoke editor on commit messages
--- a/tests/test-rebase-abort.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-rebase-abort.t	Sat Jul 18 17:32:38 2015 -0500
@@ -321,3 +321,4 @@
   branch: default
   commit: (clean)
   update: 1 new changesets, 2 branch heads (merge)
+  phases: 4 draft
--- a/tests/test-rebase-cache.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-rebase-cache.t	Sat Jul 18 17:32:38 2015 -0500
@@ -31,7 +31,6 @@
 
   $ hg branch branch2
   marked working directory as branch branch2
-  (branches are permanent and global, did you want a bookmark?)
   $ hg ci -m 'branch2'
 
   $ echo c > C
@@ -42,7 +41,6 @@
 
   $ hg branch -f branch2
   marked working directory as branch branch2
-  (branches are permanent and global, did you want a bookmark?)
   $ echo d > d
   $ hg ci -Am D
   adding d
@@ -57,7 +55,6 @@
 
   $ hg branch branch3
   marked working directory as branch branch3
-  (branches are permanent and global, did you want a bookmark?)
   $ hg ci -m 'branch3'
 
   $ echo f > f
@@ -308,12 +305,10 @@
 
   $ hg branch branch2
   marked working directory as branch branch2
-  (branches are permanent and global, did you want a bookmark?)
   $ hg ci -m 'branch2'
 
   $ hg branch -f branch1
   marked working directory as branch branch1
-  (branches are permanent and global, did you want a bookmark?)
 
   $ echo a > A
   $ hg ci -Am A
--- a/tests/test-rebase-collapse.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-rebase-collapse.t	Sat Jul 18 17:32:38 2015 -0500
@@ -571,7 +571,6 @@
 
   $ hg branch 'two'
   marked working directory as branch two
-  (branches are permanent and global, did you want a bookmark?)
   $ echo 'c' > c
   $ hg ci -Am 'C'
   adding c
--- a/tests/test-rebase-conflicts.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-rebase-conflicts.t	Sat Jul 18 17:32:38 2015 -0500
@@ -223,7 +223,6 @@
   ignoring null merge rebase of 6
   ignoring null merge rebase of 8
   rebasing 9:e31216eec445 "more changes to f1"
-  rebasing: 9:e31216eec445 5/6 changesets (83.33%)
    future parents are 2 and -1
   rebase status stored
    update to 2:4bc80088dc6b
@@ -232,10 +231,8 @@
    ancestor: d79e2059b5c0+, local: d79e2059b5c0+, remote: 4bc80088dc6b
    f2.txt: other deleted -> r
   removing f2.txt
-  updating: f2.txt 1/2 files (50.00%)
    f1.txt: remote created -> g
   getting f1.txt
-  updating: f1.txt 2/2 files (100.00%)
    merge against 9:e31216eec445
      detach base 8:8e4e2c1a07ae
     searching for copies back to rev 3
@@ -244,14 +241,12 @@
    ancestor: 8e4e2c1a07ae, local: 4bc80088dc6b+, remote: e31216eec445
    f1.txt: remote is newer -> g
   getting f1.txt
-  updating: f1.txt 1/1 files (100.00%)
   committing files:
   f1.txt
   committing manifest
   committing changelog
   rebased as 19c888675e13
   rebasing 10:2f2496ddf49d "merge" (tip)
-  rebasing: 10:2f2496ddf49d 6/6 changesets (100.00%)
    future parents are 11 and 7
   rebase status stored
    already in target
@@ -263,7 +258,6 @@
    ancestor: e31216eec445, local: 19c888675e13+, remote: 2f2496ddf49d
    f1.txt: remote is newer -> g
   getting f1.txt
-  updating: f1.txt 1/1 files (100.00%)
   committing files:
   f1.txt
   committing manifest
@@ -276,47 +270,26 @@
    ancestor: 2a7f09cac94c, local: 2a7f09cac94c+, remote: d79e2059b5c0
    f1.txt: other deleted -> r
   removing f1.txt
-  updating: f1.txt 1/2 files (50.00%)
    f2.txt: remote created -> g
   getting f2.txt
-  updating: f2.txt 2/2 files (100.00%)
   2 changesets found
   list of changesets:
   e31216eec445e44352c5f01588856059466a24c9
   2f2496ddf49d69b5ef23ad8cf9fb2e0e4faf0ac2
-  bundling: 1/2 changesets (50.00%)
-  bundling: 2/2 changesets (100.00%)
-  bundling: 1/2 manifests (50.00%)
-  bundling: 2/2 manifests (100.00%)
-  bundling: f1.txt 1/1 files (100.00%)
   saved backup bundle to $TESTTMP/issue4041/.hg/strip-backup/e31216eec445-15f7a814-backup.hg (glob)
   3 changesets found
   list of changesets:
   4c9fbe56a16f30c0d5dcc40ec1a97bbe3325209c
   19c888675e133ab5dff84516926a65672eaf04d9
   2a7f09cac94c7f4b73ebd5cd1a62d3b2e8e336bf
-  bundling: 1/3 changesets (33.33%)
-  bundling: 2/3 changesets (66.67%)
-  bundling: 3/3 changesets (100.00%)
-  bundling: 1/3 manifests (33.33%)
-  bundling: 2/3 manifests (66.67%)
-  bundling: 3/3 manifests (100.00%)
-  bundling: f1.txt 1/1 files (100.00%)
   adding branch
   adding changesets
-  changesets: 1 chunks
   add changeset 4c9fbe56a16f
-  changesets: 2 chunks
   add changeset 19c888675e13
-  changesets: 3 chunks
   add changeset 2a7f09cac94c
   adding manifests
-  manifests: 1/2 chunks (50.00%)
-  manifests: 2/2 chunks (100.00%)
-  manifests: 3/2 chunks (150.00%)
   adding file changes
   adding f1.txt revisions
-  files: 1/1 chunks (100.00%)
   added 2 changesets with 2 changes to 1 files
   invalid branchheads cache (served): tip differs
   rebase completed
--- a/tests/test-rebase-interruptions.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-rebase-interruptions.t	Sat Jul 18 17:32:38 2015 -0500
@@ -258,7 +258,7 @@
 Abort the rebasing:
 
   $ hg rebase --abort
-  warning: can't clean up immutable changesets 45396c49d53b
+  warning: can't clean up public changesets 45396c49d53b
   rebase aborted
 
   $ hg tglogp
--- a/tests/test-rebase-named-branches.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-rebase-named-branches.t	Sat Jul 18 17:32:38 2015 -0500
@@ -36,7 +36,6 @@
   2 files updated, 0 files merged, 3 files removed, 0 files unresolved
   $ hg branch dev-two
   marked working directory as branch dev-two
-  (branches are permanent and global, did you want a bookmark?)
 
   $ echo x > x
 
@@ -128,7 +127,6 @@
   3 files updated, 0 files merged, 3 files removed, 0 files unresolved
   $ hg branch dev-one
   marked working directory as branch dev-one
-  (branches are permanent and global, did you want a bookmark?)
   $ hg ci -m 'dev-one named branch'
 
   $ hg tglog
--- a/tests/test-rebase-parameters.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-rebase-parameters.t	Sat Jul 18 17:32:38 2015 -0500
@@ -476,6 +476,7 @@
   branch: default
   commit: 1 modified, 1 unresolved (merge)
   update: (current)
+  phases: 3 draft
   rebase: 0 rebased, 1 remaining (rebase --continue)
 
   $ hg resolve -l
--- a/tests/test-rebase-scenario-global.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-rebase-scenario-global.t	Sat Jul 18 17:32:38 2015 -0500
@@ -308,7 +308,7 @@
   nothing to rebase
   [1]
   $ hg rebase -d 5 -b 6
-  abort: can't rebase immutable changeset e1c4361dd923
+  abort: can't rebase public changeset e1c4361dd923
   (see "hg help phases" for details)
   [255]
 
@@ -397,7 +397,7 @@
   abort: can't remove original changesets with unrebased descendants
   (use --keep to keep original changesets)
   [255]
-  $ hg rebase -r '2::8' -d 1 --keep
+  $ hg rebase -r '2::8' -d 1 -k
   rebasing 2:c9e50f6cdc55 "C"
   rebasing 3:ffd453c31098 "D"
   rebasing 6:3d8a618087a7 "G"
--- a/tests/test-record.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-record.t	Sat Jul 18 17:32:38 2015 -0500
@@ -45,8 +45,7 @@
   
    -A --addremove           mark new/missing files as added/removed before
                             committing
-      --close-branch        mark a branch as closed, hiding it from the branch
-                            list
+      --close-branch        mark a branch head as closed
       --amend               amend the parent of the working directory
    -s --secret              use the secret phase for committing
    -e --edit                invoke editor on commit messages
--- a/tests/test-relink.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-relink.t	Sat Jul 18 17:32:38 2015 -0500
@@ -70,7 +70,7 @@
 
 relink
 
-  $ hg relink --debug | fix_path
+  $ hg relink --debug --config progress.debug=true | fix_path
   relinking $TESTTMP/repo/.hg/store to $TESTTMP/clone/.hg/store
   tip has 2 files, estimated total number of files: 3
   collecting: 00changelog.i 1/3 files (33.33%)
--- a/tests/test-rename-dir-merge.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-rename-dir-merge.t	Sat Jul 18 17:32:38 2015 -0500
@@ -43,14 +43,11 @@
   removing a/a
    a/b: other deleted -> r
   removing a/b
-  updating: a/b 2/5 files (40.00%)
    b/a: remote created -> g
   getting b/a
    b/b: remote created -> g
   getting b/b
-  updating: b/b 4/5 files (80.00%)
    b/c: remote directory rename - move from a/c -> dm
-  updating: b/c 5/5 files (100.00%)
   moving a/c to b/c (glob)
   3 files updated, 0 files merged, 2 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
@@ -89,7 +86,6 @@
    branchmerge: True, force: False, partial: False
    ancestor: f9b20c0d4c51, local: 397f8b00a740+, remote: ce36d17b18fb
    b/c: local directory rename - get from a/c -> dg
-  updating: b/c 1/1 files (100.00%)
   getting a/c to b/c
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
--- a/tests/test-rename-merge1.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-rename-merge1.t	Sat Jul 18 17:32:38 2015 -0500
@@ -40,9 +40,7 @@
   removing a
    b2: remote created -> g
   getting b2
-  updating: b2 1/2 files (50.00%)
    b: remote moved from a -> m
-  updating: b 2/2 files (100.00%)
   picked tool 'internal:merge' for b (binary False symlink False)
   merging a and b to b
   my b@044f8520aeeb+ other b@85c198ef2f6c ancestor a@af1939970a1c
@@ -181,7 +179,6 @@
    ancestor: 19d7f95df299, local: 0084274f6b67+, remote: 5d32493049f0
    newfile: remote created -> g
   getting newfile
-  updating: newfile 1/1 files (100.00%)
   note: possible conflict - file was deleted and renamed to:
    newfile
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-rename-merge2.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-rename-merge2.t	Sat Jul 18 17:32:38 2015 -0500
@@ -90,13 +90,11 @@
    preserving rev for resolve of rev
    a: remote unchanged -> k
    b: remote copied from a -> m
-  updating: b 1/2 files (50.00%)
   picked tool 'python ../merge' for b (binary False symlink False)
   merging a and b to b
   my b@e300d1c794ec+ other b@4ce40f5aca24 ancestor a@924404dff337
    premerge successful
    rev: versions differ -> m
-  updating: rev 2/2 files (100.00%)
   picked tool 'python ../merge' for rev (binary False symlink False)
   merging rev
   my rev@e300d1c794ec+ other rev@4ce40f5aca24 ancestor rev@924404dff337
@@ -128,15 +126,12 @@
    preserving rev for resolve of rev
    a: remote is newer -> g
   getting a
-  updating: a 1/3 files (33.33%)
    b: local copied/moved from a -> m
-  updating: b 2/3 files (66.67%)
   picked tool 'python ../merge' for b (binary False symlink False)
   merging b and a to b
   my b@86a2aa42fc76+ other a@f4db7e329e71 ancestor a@924404dff337
    premerge successful
    rev: versions differ -> m
-  updating: rev 3/3 files (100.00%)
   picked tool 'python ../merge' for rev (binary False symlink False)
   merging rev
   my rev@86a2aa42fc76+ other rev@f4db7e329e71 ancestor rev@924404dff337
@@ -168,13 +163,11 @@
    preserving rev for resolve of rev
   removing a
    b: remote moved from a -> m
-  updating: b 1/2 files (50.00%)
   picked tool 'python ../merge' for b (binary False symlink False)
   merging a and b to b
   my b@e300d1c794ec+ other b@bdb19105162a ancestor a@924404dff337
    premerge successful
    rev: versions differ -> m
-  updating: rev 2/2 files (100.00%)
   picked tool 'python ../merge' for rev (binary False symlink False)
   merging rev
   my rev@e300d1c794ec+ other rev@bdb19105162a ancestor rev@924404dff337
@@ -204,13 +197,11 @@
    preserving b for resolve of b
    preserving rev for resolve of rev
    b: local copied/moved from a -> m
-  updating: b 1/2 files (50.00%)
   picked tool 'python ../merge' for b (binary False symlink False)
   merging b and a to b
   my b@02963e448370+ other a@f4db7e329e71 ancestor a@924404dff337
    premerge successful
    rev: versions differ -> m
-  updating: rev 2/2 files (100.00%)
   picked tool 'python ../merge' for rev (binary False symlink False)
   merging rev
   my rev@02963e448370+ other rev@f4db7e329e71 ancestor rev@924404dff337
@@ -240,9 +231,7 @@
    preserving rev for resolve of rev
    b: remote created -> g
   getting b
-  updating: b 1/2 files (50.00%)
    rev: versions differ -> m
-  updating: rev 2/2 files (100.00%)
   picked tool 'python ../merge' for rev (binary False symlink False)
   merging rev
   my rev@94b33a1b7f2d+ other rev@4ce40f5aca24 ancestor rev@924404dff337
@@ -271,7 +260,6 @@
    ancestor: 924404dff337, local: 86a2aa42fc76+, remote: 97c705ade336
    preserving rev for resolve of rev
    rev: versions differ -> m
-  updating: rev 1/1 files (100.00%)
   picked tool 'python ../merge' for rev (binary False symlink False)
   merging rev
   my rev@86a2aa42fc76+ other rev@97c705ade336 ancestor rev@924404dff337
@@ -301,12 +289,9 @@
    preserving rev for resolve of rev
    a: other deleted -> r
   removing a
-  updating: a 1/3 files (33.33%)
    b: remote created -> g
   getting b
-  updating: b 2/3 files (66.67%)
    rev: versions differ -> m
-  updating: rev 3/3 files (100.00%)
   picked tool 'python ../merge' for rev (binary False symlink False)
   merging rev
   my rev@94b33a1b7f2d+ other rev@bdb19105162a ancestor rev@924404dff337
@@ -334,7 +319,6 @@
    ancestor: 924404dff337, local: 02963e448370+, remote: 97c705ade336
    preserving rev for resolve of rev
    rev: versions differ -> m
-  updating: rev 1/1 files (100.00%)
   picked tool 'python ../merge' for rev (binary False symlink False)
   merging rev
   my rev@02963e448370+ other rev@97c705ade336 ancestor rev@924404dff337
@@ -360,14 +344,12 @@
    preserving b for resolve of b
    preserving rev for resolve of rev
    b: both renamed from a -> m
-  updating: b 1/2 files (50.00%)
   picked tool 'python ../merge' for b (binary False symlink False)
   merging b
   my b@62e7bf090eba+ other b@49b6d8032493 ancestor a@924404dff337
   launching merge tool: python ../merge *$TESTTMP/t/t/b* * * (glob)
   merge tool returned: 0
    rev: versions differ -> m
-  updating: rev 2/2 files (100.00%)
   picked tool 'python ../merge' for rev (binary False symlink False)
   merging rev
   my rev@62e7bf090eba+ other rev@49b6d8032493 ancestor rev@924404dff337
@@ -402,9 +384,7 @@
    preserving rev for resolve of rev
    c: remote created -> g
   getting c
-  updating: c 1/2 files (50.00%)
    rev: versions differ -> m
-  updating: rev 2/2 files (100.00%)
   picked tool 'python ../merge' for rev (binary False symlink False)
   merging rev
   my rev@02963e448370+ other rev@fe905ef2c33e ancestor rev@924404dff337
@@ -434,14 +414,12 @@
    preserving b for resolve of b
    preserving rev for resolve of rev
    b: both created -> m
-  updating: b 1/2 files (50.00%)
   picked tool 'python ../merge' for b (binary False symlink False)
   merging b
   my b@86a2aa42fc76+ other b@af30c7647fc7 ancestor b@000000000000
   launching merge tool: python ../merge *$TESTTMP/t/t/b* * * (glob)
   merge tool returned: 0
    rev: versions differ -> m
-  updating: rev 2/2 files (100.00%)
   picked tool 'python ../merge' for rev (binary False symlink False)
   merging rev
   my rev@86a2aa42fc76+ other rev@af30c7647fc7 ancestor rev@924404dff337
@@ -469,16 +447,13 @@
    preserving rev for resolve of rev
    a: other deleted -> r
   removing a
-  updating: a 1/3 files (33.33%)
    b: both created -> m
-  updating: b 2/3 files (66.67%)
   picked tool 'python ../merge' for b (binary False symlink False)
   merging b
   my b@59318016310c+ other b@bdb19105162a ancestor b@000000000000
   launching merge tool: python ../merge *$TESTTMP/t/t/b* * * (glob)
   merge tool returned: 0
    rev: versions differ -> m
-  updating: rev 3/3 files (100.00%)
   picked tool 'python ../merge' for rev (binary False symlink False)
   merging rev
   my rev@59318016310c+ other rev@bdb19105162a ancestor rev@924404dff337
@@ -505,16 +480,13 @@
    preserving rev for resolve of rev
    a: remote is newer -> g
   getting a
-  updating: a 1/3 files (33.33%)
    b: both created -> m
-  updating: b 2/3 files (66.67%)
   picked tool 'python ../merge' for b (binary False symlink False)
   merging b
   my b@86a2aa42fc76+ other b@8dbce441892a ancestor b@000000000000
   launching merge tool: python ../merge *$TESTTMP/t/t/b* * * (glob)
   merge tool returned: 0
    rev: versions differ -> m
-  updating: rev 3/3 files (100.00%)
   picked tool 'python ../merge' for rev (binary False symlink False)
   merging rev
   my rev@86a2aa42fc76+ other rev@8dbce441892a ancestor rev@924404dff337
@@ -542,16 +514,13 @@
    preserving rev for resolve of rev
    a: other deleted -> r
   removing a
-  updating: a 1/3 files (33.33%)
    b: both created -> m
-  updating: b 2/3 files (66.67%)
   picked tool 'python ../merge' for b (binary False symlink False)
   merging b
   my b@59318016310c+ other b@bdb19105162a ancestor b@000000000000
   launching merge tool: python ../merge *$TESTTMP/t/t/b* * * (glob)
   merge tool returned: 0
    rev: versions differ -> m
-  updating: rev 3/3 files (100.00%)
   picked tool 'python ../merge' for rev (binary False symlink False)
   merging rev
   my rev@59318016310c+ other rev@bdb19105162a ancestor rev@924404dff337
@@ -578,16 +547,13 @@
    preserving rev for resolve of rev
    a: remote is newer -> g
   getting a
-  updating: a 1/3 files (33.33%)
    b: both created -> m
-  updating: b 2/3 files (66.67%)
   picked tool 'python ../merge' for b (binary False symlink False)
   merging b
   my b@86a2aa42fc76+ other b@8dbce441892a ancestor b@000000000000
   launching merge tool: python ../merge *$TESTTMP/t/t/b* * * (glob)
   merge tool returned: 0
    rev: versions differ -> m
-  updating: rev 3/3 files (100.00%)
   picked tool 'python ../merge' for rev (binary False symlink False)
   merging rev
   my rev@86a2aa42fc76+ other rev@8dbce441892a ancestor rev@924404dff337
@@ -615,14 +581,12 @@
    preserving rev for resolve of rev
    a: remote unchanged -> k
    b: both created -> m
-  updating: b 1/2 files (50.00%)
   picked tool 'python ../merge' for b (binary False symlink False)
   merging b
   my b@0b76e65c8289+ other b@4ce40f5aca24 ancestor b@000000000000
   launching merge tool: python ../merge *$TESTTMP/t/t/b* * * (glob)
   merge tool returned: 0
    rev: versions differ -> m
-  updating: rev 2/2 files (100.00%)
   picked tool 'python ../merge' for rev (binary False symlink False)
   merging rev
   my rev@0b76e65c8289+ other rev@4ce40f5aca24 ancestor rev@924404dff337
@@ -652,16 +616,13 @@
    preserving rev for resolve of rev
    a: prompt recreating -> g
   getting a
-  updating: a 1/3 files (33.33%)
    b: both created -> m
-  updating: b 2/3 files (66.67%)
   picked tool 'python ../merge' for b (binary False symlink False)
   merging b
   my b@02963e448370+ other b@8dbce441892a ancestor b@000000000000
   launching merge tool: python ../merge *$TESTTMP/t/t/b* * * (glob)
   merge tool returned: 0
    rev: versions differ -> m
-  updating: rev 3/3 files (100.00%)
   picked tool 'python ../merge' for rev (binary False symlink False)
   merging rev
   my rev@02963e448370+ other rev@8dbce441892a ancestor rev@924404dff337
@@ -690,16 +651,13 @@
    preserving b for resolve of b
    preserving rev for resolve of rev
    a: prompt keep -> a
-  updating: a 1/3 files (33.33%)
    b: both created -> m
-  updating: b 2/3 files (66.67%)
   picked tool 'python ../merge' for b (binary False symlink False)
   merging b
   my b@0b76e65c8289+ other b@bdb19105162a ancestor b@000000000000
   launching merge tool: python ../merge *$TESTTMP/t/t/b* * * (glob)
   merge tool returned: 0
    rev: versions differ -> m
-  updating: rev 3/3 files (100.00%)
   picked tool 'python ../merge' for rev (binary False symlink False)
   merging rev
   my rev@0b76e65c8289+ other rev@bdb19105162a ancestor rev@924404dff337
@@ -730,14 +688,12 @@
    preserving rev for resolve of rev
   removing a
    b: remote moved from a -> m
-  updating: b 1/2 files (50.00%)
   picked tool 'python ../merge' for b (binary False symlink False)
   merging a and b to b
   my b@e300d1c794ec+ other b@49b6d8032493 ancestor a@924404dff337
   launching merge tool: python ../merge *$TESTTMP/t/t/b* * * (glob)
   merge tool returned: 0
    rev: versions differ -> m
-  updating: rev 2/2 files (100.00%)
   picked tool 'python ../merge' for rev (binary False symlink False)
   merging rev
   my rev@e300d1c794ec+ other rev@49b6d8032493 ancestor rev@924404dff337
@@ -767,14 +723,12 @@
    preserving b for resolve of b
    preserving rev for resolve of rev
    b: local copied/moved from a -> m
-  updating: b 1/2 files (50.00%)
   picked tool 'python ../merge' for b (binary False symlink False)
   merging b and a to b
   my b@62e7bf090eba+ other a@f4db7e329e71 ancestor a@924404dff337
   launching merge tool: python ../merge *$TESTTMP/t/t/b* * * (glob)
   merge tool returned: 0
    rev: versions differ -> m
-  updating: rev 2/2 files (100.00%)
   picked tool 'python ../merge' for rev (binary False symlink False)
   merging rev
   my rev@62e7bf090eba+ other rev@f4db7e329e71 ancestor rev@924404dff337
@@ -810,15 +764,12 @@
    preserving rev for resolve of rev
    c: remote created -> g
   getting c
-  updating: c 1/3 files (33.33%)
    b: local copied/moved from a -> m
-  updating: b 2/3 files (66.67%)
   picked tool 'python ../merge' for b (binary False symlink False)
   merging b and a to b
   my b@02963e448370+ other a@2b958612230f ancestor a@924404dff337
    premerge successful
    rev: versions differ -> m
-  updating: rev 3/3 files (100.00%)
   picked tool 'python ../merge' for rev (binary False symlink False)
   merging rev
   my rev@02963e448370+ other rev@2b958612230f ancestor rev@924404dff337
@@ -875,7 +826,7 @@
   $ mkdir 7 8
   $ echo m > 7/f
   $ echo m > 8/f
-  $ hg merge -f --tool internal:dump -v --debug -r2 | sed '/^updating:/,$d' 2> /dev/null
+  $ hg merge -f --tool internal:dump -v --debug -r2 | sed '/^ 0\/f: both created -> m/,$d' 2> /dev/null
     searching for copies back to rev 1
     unmatched files in local:
      5/g
--- a/tests/test-rename.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-rename.t	Sat Jul 18 17:32:38 2015 -0500
@@ -20,6 +20,7 @@
   branch: default
   commit: 1 renamed
   update: (current)
+  phases: 1 draft
   $ hg status -C
   A d2/c
     d1/d11/a1
--- a/tests/test-revert-interactive.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-revert-interactive.t	Sat Jul 18 17:32:38 2015 -0500
@@ -52,12 +52,12 @@
   reverting folder1/g (glob)
   removing folder1/i (glob)
   reverting folder2/h (glob)
-  diff -r 89ac3d72e4a4 f
+  diff --git a/f b/f
   2 hunks, 2 lines changed
   examine changes to 'f'? [Ynesfdaq?] y
   
-  @@ -1,6 +1,5 @@
-  -a
+  @@ -1,5 +1,6 @@
+  +a
    1
    2
    3
@@ -65,21 +65,21 @@
    5
   record change 1/6 to 'f'? [Ynesfdaq?] y
   
-  @@ -2,6 +1,5 @@
+  @@ -1,5 +2,6 @@
    1
    2
    3
    4
    5
-  -b
+  +b
   record change 2/6 to 'f'? [Ynesfdaq?] y
   
-  diff -r 89ac3d72e4a4 folder1/g
+  diff --git a/folder1/g b/folder1/g
   2 hunks, 2 lines changed
   examine changes to 'folder1/g'? [Ynesfdaq?] y
   
-  @@ -1,6 +1,5 @@
-  -c
+  @@ -1,5 +1,6 @@
+  +c
    1
    2
    3
@@ -87,16 +87,16 @@
    5
   record change 3/6 to 'folder1/g'? [Ynesfdaq?] y
   
-  @@ -2,6 +1,5 @@
+  @@ -1,5 +2,6 @@
    1
    2
    3
    4
    5
-  -d
+  +d
   record change 4/6 to 'folder1/g'? [Ynesfdaq?] n
   
-  diff -r 89ac3d72e4a4 folder2/h
+  diff --git a/folder2/h b/folder2/h
   2 hunks, 2 lines changed
   examine changes to 'folder2/h'? [Ynesfdaq?] n
   
@@ -127,7 +127,7 @@
   $ echo q | hg revert -i -r 2
   reverting folder1/g (glob)
   reverting folder2/h (glob)
-  diff -r 89ac3d72e4a4 folder1/g
+  diff --git a/folder1/g b/folder1/g
   1 hunks, 1 lines changed
   examine changes to 'folder1/g'? [Ynesfdaq?] q
   
@@ -151,12 +151,12 @@
   reverting folder1/g (glob)
   removing folder1/i (glob)
   reverting folder2/h (glob)
-  diff -r 89ac3d72e4a4 f
+  diff --git a/f b/f
   2 hunks, 2 lines changed
   examine changes to 'f'? [Ynesfdaq?] y
   
-  @@ -1,6 +1,5 @@
-  -a
+  @@ -1,5 +1,6 @@
+  +a
    1
    2
    3
@@ -164,21 +164,21 @@
    5
   record change 1/6 to 'f'? [Ynesfdaq?] y
   
-  @@ -2,6 +1,5 @@
+  @@ -1,5 +2,6 @@
    1
    2
    3
    4
    5
-  -b
+  +b
   record change 2/6 to 'f'? [Ynesfdaq?] y
   
-  diff -r 89ac3d72e4a4 folder1/g
+  diff --git a/folder1/g b/folder1/g
   2 hunks, 2 lines changed
   examine changes to 'folder1/g'? [Ynesfdaq?] y
   
-  @@ -1,6 +1,5 @@
-  -c
+  @@ -1,5 +1,6 @@
+  +c
    1
    2
    3
@@ -186,16 +186,16 @@
    5
   record change 3/6 to 'folder1/g'? [Ynesfdaq?] y
   
-  @@ -2,6 +1,5 @@
+  @@ -1,5 +2,6 @@
    1
    2
    3
    4
    5
-  -d
+  +d
   record change 4/6 to 'folder1/g'? [Ynesfdaq?] n
   
-  diff -r 89ac3d72e4a4 folder2/h
+  diff --git a/folder2/h b/folder2/h
   2 hunks, 2 lines changed
   examine changes to 'folder2/h'? [Ynesfdaq?] n
   
@@ -230,12 +230,12 @@
   > n
   > n
   > EOF
-  diff -r 59dd6e4ab63a f
+  diff --git a/f b/f
   2 hunks, 2 lines changed
   examine changes to 'f'? [Ynesfdaq?] y
   
-  @@ -1,5 +1,6 @@
-  +a
+  @@ -1,6 +1,5 @@
+  -a
    1
    2
    3
@@ -243,13 +243,13 @@
    5
   record change 1/2 to 'f'? [Ynesfdaq?] y
   
-  @@ -1,5 +2,6 @@
+  @@ -2,6 +1,5 @@
    1
    2
    3
    4
    5
-  +b
+  -b
   record change 2/2 to 'f'? [Ynesfdaq?] n
   
   $ hg st
@@ -270,3 +270,110 @@
   3
   4
   5
+  $ rm f.orig
+  $ hg update -C .
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Check editing files newly added by a revert
+
+1) Create a dummy editor changing 1 to 42
+  $ cat > $TESTTMP/editor.sh << '__EOF__'
+  > cat "$1"  | sed "s/1/42/g"  > tt
+  > mv tt  "$1"
+  > __EOF__
+
+2) Add k
+  $ printf "1\n" > k
+  $ hg add k
+  $ hg commit -m "add k"
+
+3) Use interactive revert with editing (replacing +1 with +42):
+  $ printf "0\n2\n" > k
+  $ HGEDITOR="\"sh\" \"${TESTTMP}/editor.sh\"" hg revert -i  <<EOF
+  > y
+  > e
+  > EOF
+  reverting k
+  diff --git a/k b/k
+  1 hunks, 2 lines changed
+  examine changes to 'k'? [Ynesfdaq?] y
+  
+  @@ -1,1 +1,2 @@
+  -1
+  +0
+  +2
+  record this change to 'k'? [Ynesfdaq?] e
+  
+  $ cat k
+  42
+
+Check the experimental config to invert the selection:
+  $ cat <<EOF >> $HGRCPATH
+  > [experimental]
+  > revertalternateinteractivemode=False
+  > EOF
+
+
+  $ hg up -C .
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ printf 'firstline\nc\n1\n2\n3\n 3\n5\nd\nlastline\n' > folder1/g
+  $ hg diff --nodates
+  diff -r a3d963a027aa folder1/g
+  --- a/folder1/g
+  +++ b/folder1/g
+  @@ -1,7 +1,9 @@
+  +firstline
+   c
+   1
+   2
+   3
+  -4
+  + 3
+   5
+   d
+  +lastline
+  $ hg revert -i <<EOF
+  > y
+  > y
+  > y
+  > n
+  > EOF
+  reverting folder1/g (glob)
+  diff --git a/folder1/g b/folder1/g
+  3 hunks, 3 lines changed
+  examine changes to 'folder1/g'? [Ynesfdaq?] y
+  
+  @@ -1,5 +1,4 @@
+  -firstline
+   c
+   1
+   2
+   3
+  record change 1/3 to 'folder1/g'? [Ynesfdaq?] y
+  
+  @@ -2,7 +1,7 @@
+   c
+   1
+   2
+   3
+  - 3
+  +4
+   5
+   d
+  record change 2/3 to 'folder1/g'? [Ynesfdaq?] y
+  
+  @@ -7,3 +6,2 @@
+   5
+   d
+  -lastline
+  record change 3/3 to 'folder1/g'? [Ynesfdaq?] n
+  
+  $ hg diff --nodates
+  diff -r a3d963a027aa folder1/g
+  --- a/folder1/g
+  +++ b/folder1/g
+  @@ -5,3 +5,4 @@
+   4
+   5
+   d
+  +lastline
--- a/tests/test-revert.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-revert.t	Sat Jul 18 17:32:38 2015 -0500
@@ -175,6 +175,46 @@
   executable
 #endif
 
+Test that files reverted to other than the parent are treated as
+"modified", even if none of mode, size and timestamp of it isn't
+changed on the filesystem (see also issue4583).
+
+  $ echo 321 > e
+  $ hg diff --git
+  diff --git a/e b/e
+  --- a/e
+  +++ b/e
+  @@ -1,1 +1,1 @@
+  -123
+  +321
+  $ hg commit -m 'ambiguity from size'
+
+  $ cat e
+  321
+  $ touch -t 200001010000 e
+  $ hg debugrebuildstate
+
+  $ cat >> .hg/hgrc <<EOF
+  > [fakedirstatewritetime]
+  > # emulate invoking dirstate.write() via repo.status()
+  > # at 2000-01-01 00:00
+  > fakenow = 200001010000
+  > 
+  > [extensions]
+  > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
+  > EOF
+  $ hg revert -r 0 e
+  $ cat >> .hg/hgrc <<EOF
+  > [extensions]
+  > fakedirstatewritetime = !
+  > EOF
+
+  $ cat e
+  123
+  $ touch -t 200001010000 e
+  $ hg status -A e
+  M e
+
   $ cd ..
 
 
@@ -360,6 +400,7 @@
   branch: default
   commit: 2 modified, 1 removed (merge)
   update: (current)
+  phases: 3 draft
 
 clarifies who added what
 
--- a/tests/test-revset.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-revset.t	Sat Jul 18 17:32:38 2015 -0500
@@ -1,5 +1,27 @@
   $ 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]
+  > testrevset=$TESTTMP/testrevset.py
+  > EOF
 
   $ try() {
   >   hg debugrevspec --debug "$@"
@@ -21,13 +43,11 @@
   $ echo b > b
   $ hg branch b
   marked working directory as branch b
-  (branches are permanent and global, did you want a bookmark?)
   $ hg ci -Aqm1
 
   $ rm a
   $ hg branch a-b-c-
   marked working directory as branch a-b-c-
-  (branches are permanent and global, did you want a bookmark?)
   $ hg ci -Aqm2 -u Bob
 
   $ hg log -r "extra('branch', 'a-b-c-')" --template '{rev}\n'
@@ -44,7 +64,6 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg branch +a+b+c+
   marked working directory as branch +a+b+c+
-  (branches are permanent and global, did you want a bookmark?)
   $ hg ci -Aqm3
 
   $ hg co 2  # interleave
@@ -52,14 +71,12 @@
   $ echo bb > b
   $ hg branch -- -a-b-c-
   marked working directory as branch -a-b-c-
-  (branches are permanent and global, did you want a bookmark?)
   $ hg ci -Aqm4 -d "May 12 2005"
 
   $ hg co 3
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg branch !a/b/c/
   marked working directory as branch !a/b/c/
-  (branches are permanent and global, did you want a bookmark?)
   $ hg ci -Aqm"5 bug"
 
   $ hg merge 4
@@ -67,23 +84,19 @@
   (branch merge, don't forget to commit)
   $ hg branch _a_b_c_
   marked working directory as branch _a_b_c_
-  (branches are permanent and global, did you want a bookmark?)
   $ hg ci -Aqm"6 issue619"
 
   $ hg branch .a.b.c.
   marked working directory as branch .a.b.c.
-  (branches are permanent and global, did you want a bookmark?)
   $ hg ci -Aqm7
 
   $ hg branch all
   marked working directory as branch all
-  (branches are permanent and global, did you want a bookmark?)
 
   $ hg co 4
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg branch é
   marked working directory as branch \xc3\xa9 (esc)
-  (branches are permanent and global, did you want a bookmark?)
   $ hg ci -Aqm9
 
   $ hg tag -r6 1.0
@@ -104,6 +117,25 @@
   <spanset+ 0:1>
   0
   1
+  $ try --optimize :
+  (rangeall
+    None)
+  * optimized:
+  (range
+    ('string', '0')
+    ('string', 'tip'))
+  * set:
+  <spanset+ 0:9>
+  0
+  1
+  2
+  3
+  4
+  5
+  6
+  7
+  8
+  9
   $ try 3::6
   (dagrange
     ('symbol', '3')
@@ -115,16 +147,11 @@
   6
   $ try '0|1|2'
   (or
-    (or
-      ('symbol', '0')
-      ('symbol', '1'))
+    ('symbol', '0')
+    ('symbol', '1')
     ('symbol', '2'))
   * set:
-  <addset
-    <addset
-      <baseset [0]>,
-      <baseset [1]>>,
-    <baseset [2]>>
+  <baseset [0, 1, 2]>
   0
   1
   2
@@ -260,9 +287,7 @@
   * set:
   <addset
     <baseset [1]>,
-    <addset
-      <baseset [2]>,
-      <baseset [3]>>>
+    <baseset [2, 3]>>
   1
   2
   3
@@ -281,7 +306,7 @@
   hg: parse error: date requires a string
   [255]
   $ log 'date'
-  hg: parse error: can't use date here
+  abort: unknown revision 'date'!
   [255]
   $ log 'date('
   hg: parse error at 5: not a prefix: end
@@ -289,11 +314,70 @@
   $ log 'date(tip)'
   abort: invalid date: 'tip'
   [255]
-  $ log '"date"'
+  $ 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
+
+keyword arguments
+
+  $ log 'extra(branch, value=a)'
+  0
+
+  $ log 'extra(branch, a, b)'
+  hg: parse error: extra takes at most 2 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
+      ('symbol', 'bar')
+      ('symbol', 'baz')))
+  hg: parse error: can't use a key-value pair in this context
+  [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
 
 ancestor can accept 0 or more arguments
 
@@ -311,6 +395,9 @@
   0
   $ log 'ancestor(1,2,3,4,5)'
   1
+
+test ancestors
+
   $ log 'ancestors(5)'
   0
   1
@@ -318,6 +405,12 @@
   5
   $ log 'ancestor(ancestors(5))'
   0
+  $ log '::r3232()'
+  0
+  1
+  2
+  3
+
   $ log 'author(bob)'
   2
   $ log 'author("re:bob|test")'
@@ -555,6 +648,24 @@
   <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+ [8, 9, 2, 4]>
+  2
+  4
+  8
+  9
 
 Test the order of operations
 
@@ -629,11 +740,29 @@
 
 Test working-directory revision
   $ hg debugrevspec 'wdir()'
-  None
-BROKEN: should include 'None'
+  2147483647
   $ 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 '(all() + wdir()) & first(wdir() + .)'
+  2147483647
+  $ log '(all() + wdir()) & last(. + wdir())'
+  2147483647
 
   $ log 'outgoing()'
   8
@@ -796,6 +925,239 @@
   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
+    (func
+      ('symbol', 'reverse')
+      (dagrange
+        ('symbol', '1')
+        ('symbol', '5')))
+    (func
+      ('symbol', 'ancestors')
+      ('symbol', '4')))
+  * set:
+  <addset
+    <baseset [5, 3, 1]>,
+    <generatorset+>>
+  5
+  3
+  1
+  0
+  2
+  4
+  $ try 'sort(ancestors(4) or reverse(1::5))'
+  (func
+    ('symbol', 'sort')
+    (or
+      (func
+        ('symbol', 'ancestors')
+        ('symbol', '4'))
+      (func
+        ('symbol', 'reverse')
+        (dagrange
+          ('symbol', '1')
+          ('symbol', '5')))))
+  * set:
+  <addset+
+    <generatorset+>,
+    <baseset [5, 3, 1]>>
+  0
+  1
+  2
+  3
+  4
+  5
+
+test optimization of trivial `or` operation
+
+  $ try --optimize '0|(1)|"2"|-2|tip|null'
+  (or
+    ('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
+    ('symbol', '0')
+    ('symbol', '1')
+    (range
+      ('symbol', '2')
+      ('symbol', '3')))
+  * optimized:
+  (or
+    (func
+      ('symbol', '_list')
+      ('string', '0\x001'))
+    (range
+      ('symbol', '2')
+      ('symbol', '3')))
+  * set:
+  <addset
+    <baseset [0, 1]>,
+    <spanset+ 2:3>>
+  0
+  1
+  2
+  3
+
+  $ try --optimize '0:1|2|3:4|5|6'
+  (or
+    (range
+      ('symbol', '0')
+      ('symbol', '1'))
+    ('symbol', '2')
+    (range
+      ('symbol', '3')
+      ('symbol', '4'))
+    ('symbol', '5')
+    ('symbol', '6'))
+  * optimized:
+  (or
+    (range
+      ('symbol', '0')
+      ('symbol', '1'))
+    ('symbol', '2')
+    (range
+      ('symbol', '3')
+      ('symbol', '4'))
+    (func
+      ('symbol', '_list')
+      ('string', '5\x006')))
+  * set:
+  <addset
+    <addset
+      <spanset+ 0:1>,
+      <baseset [2]>>,
+    <addset
+      <spanset+ 3:4>,
+      <baseset [5, 6]>>>
+  0
+  1
+  2
+  3
+  4
+  5
+  6
+
+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 that chained `or` operations make balanced addsets
+
+  $ try '0:1|1:2|2:3|3:4|4:5'
+  (or
+    (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:1>,
+      <spanset+ 1:2>>,
+    <addset
+      <spanset+ 2:3>,
+      <addset
+        <spanset+ 3:4>,
+        <spanset+ 4:5>>>>
+  0
+  1
+  2
+  3
+  4
+  5
+
+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
@@ -1039,6 +1401,17 @@
   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 'wdir2()'
+  hg: parse error: unknown identifier: wdir2
+  [255]
+
 multiple revspecs
 
   $ hg log -r 'tip~1:tip' -r 'tip~2:tip~1' --template '{rev}\n'
@@ -1211,9 +1584,7 @@
   * set:
   <addset
     <baseset [3]>,
-    <addset
-      <baseset [1]>,
-      <baseset [2]>>>
+    <baseset [1, 2]>>
   3
   1
   2
@@ -1238,6 +1609,44 @@
   <baseset [5]>
   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
+      (list
+        (range
+          ('symbol', '0')
+          ('symbol', '1'))
+        (range
+          ('symbol', '1')
+          ('symbol', '2')))
+      (range
+        ('symbol', '2')
+        ('symbol', '3'))))
+  (or
+    (range
+      ('symbol', '0')
+      ('symbol', '1'))
+    (range
+      ('symbol', '1')
+      ('symbol', '2'))
+    (range
+      ('symbol', '2')
+      ('symbol', '3')))
+  * set:
+  <addset
+    <spanset+ 0:1>,
+    <addset
+      <spanset+ 1:2>,
+      <spanset+ 2:3>>>
+  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.
@@ -1306,8 +1715,7 @@
   <addset
     <baseset [9]>,
     <filteredset
-      <filteredset
-        <fullreposet+ 0:9>>>>
+      <fullreposet+ 0:9>>>
   9
 
   $ try 'd(2:5)'
--- a/tests/test-rollback.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-rollback.t	Sat Jul 18 17:32:38 2015 -0500
@@ -74,16 +74,16 @@
   $ hg bookmark foo
   $ hg commit -m'modify a again'
   $ echo b > b
+  $ hg bookmark bar -r default #making bar active, before the transaction
   $ hg commit -Am'add b'
   adding b
   $ hg log --template '{rev}  {branch}  {desc|firstline}\n'
   2  test  add b
   1  test  modify a again
   0  default  add a again
-  $ hg update default
+  $ hg update bar
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  (leaving bookmark foo)
-  $ hg bookmark bar
+  (activating bookmark bar)
   $ cat .hg/undo.branch ; echo
   test
   $ hg rollback -f
@@ -94,7 +94,7 @@
   default
   $ cat .hg/bookmarks.current ; echo
   bar
-  $ hg bookmark --delete foo
+  $ hg bookmark --delete foo bar
 
 rollback by pretxncommit saves commit message (issue1635)
 
--- a/tests/test-run-tests.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-run-tests.py	Sat Jul 18 17:32:38 2015 -0500
@@ -3,6 +3,7 @@
 run-test.t only checks positive matches and can not see warnings
 (both by design)
 """
+from __future__ import print_function
 
 import os, re
 # this is hack to make sure no escape characters are inserted into the output
@@ -11,27 +12,37 @@
 import doctest
 run_tests = __import__('run-tests')
 
+def prn(ex):
+    m = ex.args[0]
+    if isinstance(m, str):
+        print(m)
+    else:
+        print(m.decode('utf-8'))
+
 def lm(expected, output):
     r"""check if output matches expected
 
     does it generally work?
-        >>> lm('H*e (glob)\n', 'Here\n')
+        >>> lm(b'H*e (glob)\n', b'Here\n')
         True
 
     fail on bad test data
-        >>> try: lm('a\n','a')
-        ... except AssertionError, ex: print ex
+        >>> try: lm(b'a\n',b'a')
+        ... except AssertionError as ex: print(ex)
         missing newline
-        >>> try: lm('single backslash\n', 'single \backslash\n')
-        ... except AssertionError, ex: print ex
+        >>> try: lm(b'single backslash\n', b'single \backslash\n')
+        ... except AssertionError as ex: prn(ex)
         single backslash or unknown char
     """
-    assert expected.endswith('\n') and output.endswith('\n'), 'missing newline'
-    assert not re.search(r'[^ \w\\/\r\n()*?]', expected + output), \
-           'single backslash or unknown char'
+    assert (expected.endswith(b'\n')
+            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)
     if isinstance(match, str):
         return 'special: ' + match
+    elif isinstance(match, bytes):
+        return 'special: ' + match.decode('utf-8')
     else:
         return bool(match) # do not return match object
 
@@ -43,15 +54,15 @@
         >>> os.altsep = True
 
     valid match on windows
-        >>> lm('g/a*/d (glob)\n', 'g\\abc/d\n')
+        >>> lm(b'g/a*/d (glob)\n', b'g\\abc/d\n')
         True
 
     direct matching, glob unnecessary
-        >>> lm('g/b (glob)\n', 'g/b\n')
+        >>> lm(b'g/b (glob)\n', b'g/b\n')
         'special: -glob'
 
     missing glob
-        >>> lm('/g/c/d/fg\n', '\\g\\c\\d/fg\n')
+        >>> lm(b'/g/c/d/fg\n', b'\\g\\c\\d/fg\n')
         'special: +glob'
 
     restore os.altsep
@@ -67,15 +78,15 @@
         >>> os.altsep = False
 
     backslash does not match slash
-        >>> lm('h/a* (glob)\n', 'h\\ab\n')
+        >>> lm(b'h/a* (glob)\n', b'h\\ab\n')
         False
 
     direct matching glob can not be recognized
-        >>> lm('h/b (glob)\n', 'h/b\n')
+        >>> lm(b'h/b (glob)\n', b'h/b\n')
         True
 
     missing glob can not not be recognized
-        >>> lm('/h/c/df/g/\n', '\\h/c\\df/g\\\n')
+        >>> lm(b'/h/c/df/g/\n', b'\\h/c\\df/g\\\n')
         False
 
     restore os.altsep
--- a/tests/test-run-tests.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-run-tests.t	Sat Jul 18 17:32:38 2015 -0500
@@ -10,7 +10,7 @@
 Smoke test
 ============
 
-  $ $TESTDIR/run-tests.py $HGTEST_RUN_TESTS_PURE
+  $ run-tests.py $HGTEST_RUN_TESTS_PURE
   
   # Ran 0 tests, 0 skipped, 0 warned, 0 failed.
 
@@ -21,10 +21,12 @@
   >   $ echo babar
   >   babar
   >   $ echo xyzzy
+  >   never happens (?)
   >   xyzzy
+  >   nor this (?)
   > EOF
 
-  $ $TESTDIR/run-tests.py --with-hg=`which hg`
+  $ run-tests.py --with-hg=`which hg`
   .
   # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
 
@@ -39,10 +41,10 @@
   > EOF
 
   >>> fh = open('test-failure-unicode.t', 'wb')
-  >>> fh.write(u'  $ echo babar\u03b1\n'.encode('utf-8'))
-  >>> fh.write(u'  l\u03b5\u03b5t\n'.encode('utf-8'))
+  >>> fh.write(u'  $ echo babar\u03b1\n'.encode('utf-8')) and None
+  >>> fh.write(u'  l\u03b5\u03b5t\n'.encode('utf-8')) and None
 
-  $ $TESTDIR/run-tests.py --with-hg=`which hg`
+  $ run-tests.py --with-hg=`which hg`
   
   --- $TESTTMP/test-failure.t
   +++ $TESTTMP/test-failure.t.err
@@ -71,7 +73,7 @@
   [1]
 
 test --xunit support
-  $ $TESTDIR/run-tests.py --with-hg=`which hg` --xunit=xunit.xml
+  $ run-tests.py --with-hg=`which hg` --xunit=xunit.xml
   
   --- $TESTTMP/test-failure.t
   +++ $TESTTMP/test-failure.t.err
@@ -127,7 +129,7 @@
 test for --retest
 ====================
 
-  $ $TESTDIR/run-tests.py --with-hg=`which hg` --retest
+  $ run-tests.py --with-hg=`which hg` --retest
   
   --- $TESTTMP/test-failure.t
   +++ $TESTTMP/test-failure.t.err
@@ -150,18 +152,18 @@
 
 successful
 
-  $ $TESTDIR/run-tests.py --with-hg=`which hg` test-success.t
+  $ run-tests.py --with-hg=`which hg` test-success.t
   .
   # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
 
 success w/ keyword
-  $ $TESTDIR/run-tests.py --with-hg=`which hg` -k xyzzy
+  $ run-tests.py --with-hg=`which hg` -k xyzzy
   .
   # Ran 2 tests, 1 skipped, 0 warned, 0 failed.
 
 failed
 
-  $ $TESTDIR/run-tests.py --with-hg=`which hg` test-failure.t
+  $ run-tests.py --with-hg=`which hg` test-failure.t
   
   --- $TESTTMP/test-failure.t
   +++ $TESTTMP/test-failure.t.err
@@ -180,7 +182,7 @@
   [1]
 
 failure w/ keyword
-  $ $TESTDIR/run-tests.py --with-hg=`which hg` -k rataxes
+  $ run-tests.py --with-hg=`which hg` -k rataxes
   
   --- $TESTTMP/test-failure.t
   +++ $TESTTMP/test-failure.t.err
@@ -206,7 +208,7 @@
   $ cat > test-serve-fail.t <<EOF
   >   $ echo 'abort: child process failed to start blah'
   > EOF
-  $ $TESTDIR/run-tests.py --with-hg=`which hg` test-serve-fail.t
+  $ run-tests.py --with-hg=`which hg` test-serve-fail.t
   
   ERROR: test-serve-fail.t output changed
   !
@@ -222,7 +224,7 @@
 Running In Debug Mode
 ======================
 
-  $ $TESTDIR/run-tests.py --with-hg=`which hg` --debug 2>&1 | grep -v pwd
+  $ run-tests.py --with-hg=`which hg` --debug 2>&1 | grep -v pwd
   + echo *SALT* 0 0 (glob)
   *SALT* 0 0 (glob)
   + echo babar
@@ -237,8 +239,8 @@
   *SALT* 2 0 (glob)
   + echo xyzzy
   xyzzy
-  + echo *SALT* 4 0 (glob)
-  *SALT* 4 0 (glob)
+  + echo *SALT* 6 0 (glob)
+  *SALT* 6 0 (glob)
   .
   # Ran 2 tests, 0 skipped, 0 warned, 0 failed.
 
@@ -248,7 +250,7 @@
 (duplicate the failing test to get predictable output)
   $ cp test-failure.t test-failure-copy.t
 
-  $ $TESTDIR/run-tests.py --with-hg=`which hg` --jobs 2 test-failure*.t -n
+  $ run-tests.py --with-hg=`which hg` --jobs 2 test-failure*.t -n
   !!
   Failed test-failure*.t: output changed (glob)
   Failed test-failure*.t: output changed (glob)
@@ -258,9 +260,9 @@
 
 failures in parallel with --first should only print one failure
   >>> f = open('test-nothing.t', 'w')
-  >>> f.write('foo\n' * 1024)
-  >>> f.write('  $ sleep 1')
-  $ $TESTDIR/run-tests.py --with-hg=`which hg` --jobs 2 --first
+  >>> f.write('foo\n' * 1024) and None
+  >>> f.write('  $ sleep 1') and None
+  $ run-tests.py --with-hg=`which hg` --jobs 2 --first
   
   --- $TESTTMP/test-failure*.t (glob)
   +++ $TESTTMP/test-failure*.t.err (glob)
@@ -290,7 +292,7 @@
 
 Refuse the fix
 
-  $ echo 'n' | $TESTDIR/run-tests.py --with-hg=`which hg` -i
+  $ echo 'n' | run-tests.py --with-hg=`which hg` -i
   
   --- $TESTTMP/test-failure.t
   +++ $TESTTMP/test-failure.t.err
@@ -316,7 +318,7 @@
 
 Interactive with custom view
 
-  $ echo 'n' | $TESTDIR/run-tests.py --with-hg=`which hg` -i --view echo
+  $ echo 'n' | run-tests.py --with-hg=`which hg` -i --view echo
   $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err (glob)
   Accept this change? [n]* (glob)
   ERROR: test-failure.t output changed
@@ -328,7 +330,7 @@
 
 View the fix
 
-  $ echo 'y' | $TESTDIR/run-tests.py --with-hg=`which hg` --view echo
+  $ echo 'y' | run-tests.py --with-hg=`which hg` --view echo
   $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err (glob)
   
   ERROR: test-failure.t output changed
@@ -346,7 +348,7 @@
   $ echo "  saved backup bundle to \$TESTTMP/foo.hg (glob)" >> test-failure.t
   $ echo "  $ echo 'saved backup bundle to \$TESTTMP/foo.hg'" >> test-failure.t
   $ echo "  saved backup bundle to \$TESTTMP/*.hg (glob)" >> test-failure.t
-  $ echo 'y' | $TESTDIR/run-tests.py --with-hg=`which hg` -i 2>&1 | \
+  $ echo 'y' | run-tests.py --with-hg=`which hg` -i 2>&1 | \
   >   sed -e 's,(glob)$,&<,g'
   
   --- $TESTTMP/test-failure.t
@@ -384,7 +386,7 @@
 No Diff
 ===============
 
-  $ $TESTDIR/run-tests.py --with-hg=`which hg` --nodiff
+  $ run-tests.py --with-hg=`which hg` --nodiff
   !.
   Failed test-failure.t: output changed
   # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
@@ -394,22 +396,22 @@
 test for --time
 ==================
 
-  $ $TESTDIR/run-tests.py --with-hg=`which hg` test-success.t --time
+  $ run-tests.py --with-hg=`which hg` test-success.t --time
   .
   # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
   # Producing time report
-  cuser   csys    real      Test
-  \s*[\d\.]{5}   \s*[\d\.]{5}   \s*[\d\.]{5}   test-success.t (re)
+  start   end     cuser   csys    real      Test
+  \s*[\d\.]{5}   \s*[\d\.]{5}   \s*[\d\.]{5}   \s*[\d\.]{5}   \s*[\d\.]{5}   test-success.t (re)
 
 test for --time with --job enabled
 ====================================
 
-  $ $TESTDIR/run-tests.py --with-hg=`which hg` test-success.t --time --jobs 2
+  $ run-tests.py --with-hg=`which hg` test-success.t --time --jobs 2
   .
   # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
   # Producing time report
-  cuser   csys    real      Test
-  \s*[\d\.]{5}   \s*[\d\.]{5}   \s*[\d\.]{5}   test-success.t (re)
+  start   end     cuser   csys    real      Test
+  \s*[\d\.]{5}   \s*[\d\.]{5}   \s*[\d\.]{5}   \s*[\d\.]{5}   \s*[\d\.]{5}   test-success.t (re)
 
 Skips
 ================
@@ -417,7 +419,7 @@
   >   $ echo xyzzy
   > #require false
   > EOF
-  $ $TESTDIR/run-tests.py --with-hg=`which hg` --nodiff
+  $ run-tests.py --with-hg=`which hg` --nodiff
   !.s
   Skipped test-skip.t: skipped
   Failed test-failure.t: output changed
@@ -425,13 +427,13 @@
   python hash seed: * (glob)
   [1]
 
-  $ $TESTDIR/run-tests.py --with-hg=`which hg` --keyword xyzzy
+  $ run-tests.py --with-hg=`which hg` --keyword xyzzy
   .s
   Skipped test-skip.t: skipped
   # Ran 2 tests, 2 skipped, 0 warned, 0 failed.
 
 Skips with xml
-  $ $TESTDIR/run-tests.py --with-hg=`which hg` --keyword xyzzy \
+  $ run-tests.py --with-hg=`which hg` --keyword xyzzy \
   >  --xunit=xunit.xml
   .s
   Skipped test-skip.t: skipped
@@ -444,7 +446,7 @@
 
 Missing skips or blacklisted skips don't count as executed:
   $ echo test-failure.t > blacklist
-  $ $TESTDIR/run-tests.py --with-hg=`which hg` --blacklist=blacklist \
+  $ run-tests.py --with-hg=`which hg` --blacklist=blacklist \
   >   test-failure.t test-bogus.t
   ss
   Skipped test-bogus.t: Doesn't exist
@@ -456,7 +458,7 @@
 test for --json
 ==================
 
-  $ $TESTDIR/run-tests.py --with-hg=`which hg` --json
+  $ run-tests.py --with-hg=`which hg` --json
   
   --- $TESTTMP/test-failure.t
   +++ $TESTTMP/test-failure.t.err
@@ -480,23 +482,75 @@
       "test-failure.t": [\{] (re)
           "csys": "\s*[\d\.]{4,5}", ? (re)
           "cuser": "\s*[\d\.]{4,5}", ? (re)
+          "end": "\s*[\d\.]{4,5}", ? (re)
           "result": "failure", ? (re)
+          "start": "\s*[\d\.]{4,5}", ? (re)
           "time": "\s*[\d\.]{4,5}" (re)
       }, ? (re)
       "test-skip.t": {
           "csys": "\s*[\d\.]{4,5}", ? (re)
           "cuser": "\s*[\d\.]{4,5}", ? (re)
+          "end": "\s*[\d\.]{4,5}", ? (re)
           "result": "skip", ? (re)
+          "start": "\s*[\d\.]{4,5}", ? (re)
           "time": "\s*[\d\.]{4,5}" (re)
       }, ? (re)
       "test-success.t": [\{] (re)
           "csys": "\s*[\d\.]{4,5}", ? (re)
           "cuser": "\s*[\d\.]{4,5}", ? (re)
+          "end": "\s*[\d\.]{4,5}", ? (re)
           "result": "success", ? (re)
+          "start": "\s*[\d\.]{4,5}", ? (re)
           "time": "\s*[\d\.]{4,5}" (re)
       }
   } (no-eol)
 
+Test that failed test accepted through interactive are properly reported:
+
+  $ cp test-failure.t backup
+  $ echo y | run-tests.py --with-hg=`which hg` --json -i
+  
+  --- $TESTTMP/test-failure.t
+  +++ $TESTTMP/test-failure.t.err
+  @@ -1,4 +1,4 @@
+     $ echo babar
+  -  rataxes
+  +  babar
+   This is a noop statement so that
+   this test is still more bytes than success.
+  Accept this change? [n] ..s
+  Skipped test-skip.t: skipped
+  # Ran 2 tests, 1 skipped, 0 warned, 0 failed.
+
+  $ cat report.json
+  testreport ={
+      "test-failure.t": [\{] (re)
+          "csys": "\s*[\d\.]{4,5}", ? (re)
+          "cuser": "\s*[\d\.]{4,5}", ? (re)
+          "end": "\s*[\d\.]{4,5}", ? (re)
+          "result": "success", ? (re)
+          "start": "\s*[\d\.]{4,5}", ? (re)
+          "time": "\s*[\d\.]{4,5}" (re)
+      }, ? (re)
+      "test-skip.t": {
+          "csys": "\s*[\d\.]{4,5}", ? (re)
+          "cuser": "\s*[\d\.]{4,5}", ? (re)
+          "end": "\s*[\d\.]{4,5}", ? (re)
+          "result": "skip", ? (re)
+          "start": "\s*[\d\.]{4,5}", ? (re)
+          "time": "\s*[\d\.]{4,5}" (re)
+      }, ? (re)
+      "test-success.t": [\{] (re)
+          "csys": "\s*[\d\.]{4,5}", ? (re)
+          "cuser": "\s*[\d\.]{4,5}", ? (re)
+          "end": "\s*[\d\.]{4,5}", ? (re)
+          "result": "success", ? (re)
+          "start": "\s*[\d\.]{4,5}", ? (re)
+          "time": "\s*[\d\.]{4,5}" (re)
+      }
+  } (no-eol)
+  $ mv backup test-failure.t
+
 #endif
 
 backslash on end of line with glob matching is handled properly
@@ -506,9 +560,64 @@
   >   foo * \ (glob)
   > EOF
 
-  $ $TESTDIR/run-tests.py --with-hg=`which hg` test-glob-backslash.t
+  $ run-tests.py --with-hg=`which hg` test-glob-backslash.t
   .
   # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
 
   $ rm -f test-glob-backslash.t
 
+Test reusability for third party tools
+======================================
+
+  $ mkdir "$TESTTMP"/anothertests
+  $ cd "$TESTTMP"/anothertests
+
+test that `run-tests.py` can execute hghave, even if it runs not in
+Mercurial source tree.
+
+  $ cat > test-hghave.t <<EOF
+  > #require true
+  >   $ echo foo
+  >   foo
+  > EOF
+  $ run-tests.py test-hghave.t
+  .
+  # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
+
+test that RUNTESTDIR refers the directory, in which `run-tests.py` now
+running is placed.
+
+  $ cat > test-runtestdir.t <<EOF
+  > - $TESTDIR, in which test-run-tests.t is placed
+  > - \$TESTDIR, in which test-runtestdir.t is placed (expanded at runtime)
+  > - \$RUNTESTDIR, in which run-tests.py is placed (expanded at runtime)
+  > 
+  >   $ test "\$TESTDIR" = "$TESTTMP"/anothertests
+  >   $ test "\$RUNTESTDIR" = "$TESTDIR"
+  >   $ head -n 3 "\$RUNTESTDIR"/../contrib/check-code.py
+  >   #!/usr/bin/env python
+  >   #
+  >   # check-code - a style and portability checker for Mercurial
+  > EOF
+  $ run-tests.py test-runtestdir.t
+  .
+  # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
+
+#if execbit
+
+test that TESTDIR is referred in PATH
+
+  $ cat > custom-command.sh <<EOF
+  > #!/bin/sh
+  > echo "hello world"
+  > EOF
+  $ chmod +x custom-command.sh
+  $ cat > test-testdir-path.t <<EOF
+  >   $ custom-command.sh
+  >   hello world
+  > EOF
+  $ run-tests.py test-testdir-path.t
+  .
+  # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
+
+#endif
--- a/tests/test-serve.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-serve.t	Sat Jul 18 17:32:38 2015 -0500
@@ -9,7 +9,7 @@
   >    cat hg.pid >> "$DAEMON_PIDS"
   >    echo % errors
   >    cat errors.log
-  >    "$TESTDIR/killdaemons.py" hg.pid
+  >    killdaemons.py hg.pid
   > }
 
   $ hg init test
--- a/tests/test-setdiscovery.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-setdiscovery.t	Sat Jul 18 17:32:38 2015 -0500
@@ -14,13 +14,13 @@
   >     hg -R a debugdiscovery b --verbose --old
   >     echo
   >     echo "% -- a -> b set"
-  >     hg -R a debugdiscovery b --verbose --debug
+  >     hg -R a debugdiscovery b --verbose --debug --config progress.debug=true
   >     echo
   >     echo "% -- b -> a tree"
-  >     hg -R b debugdiscovery a --verbose --old
+  >     hg -R b debugdiscovery a --verbose --old --config
   >     echo
   >     echo "% -- b -> a set"
-  >     hg -R b debugdiscovery a --verbose --debug
+  >     hg -R b debugdiscovery a --verbose --debug --config progress.debug=true
   >     cd ..
   > }
 
@@ -305,7 +305,7 @@
   updating to branch b
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
-  $ hg -R a debugdiscovery b --debug --verbose
+  $ hg -R a debugdiscovery b --debug --verbose --config progress.debug=true
   comparing with b
   query 1; heads
   searching for changes
@@ -346,7 +346,7 @@
   searching for changes
   e64a39e7da8b
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
   $ cut -d' ' -f6- access.log | grep -v cmd=known # cmd=known uses random sampling
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D513314ca8b3ae4dac8eec56966265b00fcf866db
--- a/tests/test-share.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-share.t	Sat Jul 18 17:32:38 2015 -0500
@@ -99,7 +99,7 @@
 
   $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid >> $DAEMON_PIDS
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'raw-file/'
+  $ get-with-headers.py localhost:$HGPORT 'raw-file/'
   200 Script output follows
   
   
@@ -299,5 +299,5 @@
 
 Explicitly kill daemons to let the test exit on Windows
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
 
--- a/tests/test-shelve.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-shelve.t	Sat Jul 18 17:32:38 2015 -0500
@@ -5,6 +5,8 @@
   > [defaults]
   > diff = --nodates --git
   > qnew = --date '0 0'
+  > [shelve]
+  > maxbackups = 2
   > EOF
 
   $ hg init repo
@@ -60,6 +62,7 @@
    -m --message TEXT        use text as shelve message
    -n --name NAME           use the given name for the shelved commit
    -p --patch               show patch
+   -i --interactive         interactive mode, only works while creating a shelve
       --stat                output diffstat-style summary of changes
    -I --include PATTERN [+] include names matching the given patterns
    -X --exclude PATTERN [+] exclude names matching the given patterns
@@ -84,6 +87,12 @@
   nothing changed
   [1]
 
+make sure shelve files were backed up
+
+  $ ls .hg/shelve-backup
+  default.hg
+  default.patch
+
 create an mq patch - shelving should work fine with a patch applied
 
   $ echo n > n
@@ -153,6 +162,14 @@
   $ hg shelve -d default
   $ hg qfinish -a -q
 
+ensure shelve backups aren't overwritten
+
+  $ ls .hg/shelve-backup/
+  default-1.hg
+  default-1.patch
+  default.hg
+  default.patch
+
 local edits should not prevent a shelved change from applying
 
   $ printf "z\na\n" > a/a
@@ -168,6 +185,16 @@
 
 apply it and make sure our state is as expected
 
+(this also tests that same timestamp prevents backups from being
+removed, even though there are more than 'maxbackups' backups)
+
+  $ f -t .hg/shelve-backup/default.hg
+  .hg/shelve-backup/default.hg: file
+  $ touch -t 200001010000 .hg/shelve-backup/default.hg
+  $ f -t .hg/shelve-backup/default-1.hg
+  .hg/shelve-backup/default-1.hg: file
+  $ touch -t 200001010000 .hg/shelve-backup/default-1.hg
+
   $ hg unshelve
   unshelving change 'default-01'
   $ hg status -C
@@ -179,6 +206,17 @@
   R b/b
   $ hg shelve -l
 
+(both of default.hg and default-1.hg should be still kept, because it
+is difficult to decide actual order of them from same timestamp)
+
+  $ ls .hg/shelve-backup/
+  default-01.hg
+  default-01.patch
+  default-1.hg
+  default-1.patch
+  default.hg
+  default.patch
+
   $ hg unshelve
   abort: no shelved changes to apply!
   [255]
@@ -233,6 +271,14 @@
     c
   R b/b
 
+ensure old shelve backups are being deleted automatically
+
+  $ ls .hg/shelve-backup/
+  default-01.hg
+  default-01.patch
+  wibble.hg
+  wibble.patch
+
 cause unshelving to result in a merge with 'a' conflicting
 
   $ hg shelve -q
@@ -782,6 +828,7 @@
   bookmarks: *test
   commit: 2 unknown (clean)
   update: (current)
+  phases: 5 draft
 
   $ hg shelve --delete --stat
   abort: options '--delete' and '--stat' may not be used together
@@ -813,6 +860,9 @@
   $ cat foo/foo
   foo
   a
+  $ hg shelve --interactive --config ui.interactive=false
+  abort: running non-interactively
+  [255]
   $ hg shelve --interactive << EOF
   > y
   > y
@@ -862,4 +912,45 @@
   c
   x
   x
-  $ cd ..
+
+shelve --patch and shelve --stat should work with a single valid shelfname
+
+  $ hg up --clean .
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg shelve --list
+  $ echo 'patch a' > shelf-patch-a
+  $ hg add shelf-patch-a
+  $ hg shelve
+  shelved as default
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo 'patch b' > shelf-patch-b
+  $ hg add shelf-patch-b
+  $ hg shelve
+  shelved as default-01
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg shelve --patch default default-01
+  abort: --patch expects a single shelf
+  [255]
+  $ hg shelve --stat default default-01
+  abort: --stat expects a single shelf
+  [255]
+  $ hg shelve --patch default
+  default         (* ago)    changes to 'create conflict' (glob)
+  
+  diff --git a/shelf-patch-a b/shelf-patch-a
+  new file mode 100644
+  --- /dev/null
+  +++ b/shelf-patch-a
+  @@ -0,0 +1,1 @@
+  +patch a
+  $ hg shelve --stat default
+  default         (* ago)    changes to 'create conflict' (glob)
+   shelf-patch-a |  1 +
+   1 files changed, 1 insertions(+), 0 deletions(-)
+  $ hg shelve --patch nonexistentshelf
+  abort: cannot find shelf nonexistentshelf
+  [255]
+  $ hg shelve --stat nonexistentshelf
+  abort: cannot find shelf nonexistentshelf
+  [255]
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-ssh-bundle1.t	Sat Jul 18 17:32:38 2015 -0500
@@ -0,0 +1,512 @@
+This test is a duplicate of 'test-http.t' feel free to factor out
+parts that are not bundle1/bundle2 specific.
+
+  $ cat << EOF >> $HGRCPATH
+  > [experimental]
+  > # This test is dedicated to interaction through old bundle
+  > bundle2-exp = False
+  > EOF
+
+
+This test tries to exercise the ssh functionality with a dummy script
+
+creating 'remote' repo
+
+  $ hg init remote
+  $ cd remote
+  $ echo this > foo
+  $ echo this > fooO
+  $ hg ci -A -m "init" foo fooO
+
+insert a closed branch (issue4428)
+
+  $ hg up null
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg branch closed
+  marked working directory as branch closed
+  (branches are permanent and global, did you want a bookmark?)
+  $ hg ci -mc0
+  $ hg ci --close-branch -mc1
+  $ hg up -q default
+
+configure for serving
+
+  $ cat <<EOF > .hg/hgrc
+  > [server]
+  > uncompressed = True
+  > 
+  > [hooks]
+  > changegroup = printenv.py changegroup-in-remote 0 ../dummylog
+  > EOF
+  $ cd ..
+
+repo not found error
+
+  $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
+  remote: abort: there is no Mercurial repository here (.hg not found)!
+  abort: no suitable response from remote hg!
+  [255]
+
+non-existent absolute path
+
+  $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy//`pwd`/nonexistent local
+  remote: abort: there is no Mercurial repository here (.hg not found)!
+  abort: no suitable response from remote hg!
+  [255]
+
+clone remote via stream
+
+  $ hg clone -e "python \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/remote local-stream
+  streaming all changes
+  4 files to transfer, 615 bytes of data
+  transferred 615 bytes in * seconds (*) (glob)
+  searching for changes
+  no changes found
+  updating to branch default
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd local-stream
+  $ hg verify
+  checking changesets
+  checking manifests
+  crosschecking files in changesets and manifests
+  checking files
+  2 files, 3 changesets, 2 total revisions
+  $ hg branches
+  default                        0:1160648e36ce
+  $ cd ..
+
+clone bookmarks via stream
+
+  $ hg -R local-stream book mybook
+  $ hg clone -e "python \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/local-stream stream2
+  streaming all changes
+  4 files to transfer, 615 bytes of data
+  transferred 615 bytes in * seconds (*) (glob)
+  searching for changes
+  no changes found
+  updating to branch default
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd stream2
+  $ hg book
+     mybook                    0:1160648e36ce
+  $ cd ..
+  $ rm -rf local-stream stream2
+
+clone remote via pull
+
+  $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 2 changes to 2 files
+  updating to branch default
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+verify
+
+  $ cd local
+  $ hg verify
+  checking changesets
+  checking manifests
+  crosschecking files in changesets and manifests
+  checking files
+  2 files, 3 changesets, 2 total revisions
+  $ echo '[hooks]' >> .hg/hgrc
+  $ echo "changegroup = printenv.py changegroup-in-local 0 ../dummylog" >> .hg/hgrc
+
+empty default pull
+
+  $ hg paths
+  default = ssh://user@dummy/remote
+  $ hg pull -e "python \"$TESTDIR/dummyssh\""
+  pulling from ssh://user@dummy/remote
+  searching for changes
+  no changes found
+
+pull from wrong ssh URL
+
+  $ hg pull -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
+  pulling from ssh://user@dummy/doesnotexist
+  remote: abort: there is no Mercurial repository here (.hg not found)!
+  abort: no suitable response from remote hg!
+  [255]
+
+local change
+
+  $ echo bleah > foo
+  $ hg ci -m "add"
+
+updating rc
+
+  $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
+  $ echo "[ui]" >> .hg/hgrc
+  $ echo "ssh = python \"$TESTDIR/dummyssh\"" >> .hg/hgrc
+
+find outgoing
+
+  $ hg out ssh://user@dummy/remote
+  comparing with ssh://user@dummy/remote
+  searching for changes
+  changeset:   3:a28a9d1a809c
+  tag:         tip
+  parent:      0:1160648e36ce
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     add
+  
+
+find incoming on the remote side
+
+  $ hg incoming -R ../remote -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/local
+  comparing with ssh://user@dummy/local
+  searching for changes
+  changeset:   3:a28a9d1a809c
+  tag:         tip
+  parent:      0:1160648e36ce
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     add
+  
+
+find incoming on the remote side (using absolute path)
+
+  $ hg incoming -R ../remote -e "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/`pwd`"
+  comparing with ssh://user@dummy/$TESTTMP/local
+  searching for changes
+  changeset:   3:a28a9d1a809c
+  tag:         tip
+  parent:      0:1160648e36ce
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     add
+  
+
+push
+
+  $ hg push
+  pushing to ssh://user@dummy/remote
+  searching for changes
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 1 changesets with 1 changes to 1 files
+  $ cd ../remote
+
+check remote tip
+
+  $ hg tip
+  changeset:   3:a28a9d1a809c
+  tag:         tip
+  parent:      0:1160648e36ce
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     add
+  
+  $ hg verify
+  checking changesets
+  checking manifests
+  crosschecking files in changesets and manifests
+  checking files
+  2 files, 4 changesets, 3 total revisions
+  $ hg cat -r tip foo
+  bleah
+  $ echo z > z
+  $ hg ci -A -m z z
+  created new head
+
+test pushkeys and bookmarks
+
+  $ cd ../local
+  $ hg debugpushkey --config ui.ssh="python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote namespaces
+  bookmarks	
+  namespaces	
+  phases	
+  $ hg book foo -r 0
+  $ hg out -B
+  comparing with ssh://user@dummy/remote
+  searching for changed bookmarks
+     foo                       1160648e36ce
+  $ hg push -B foo
+  pushing to ssh://user@dummy/remote
+  searching for changes
+  no changes found
+  exporting bookmark foo
+  [1]
+  $ hg debugpushkey --config ui.ssh="python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote bookmarks
+  foo	1160648e36cec0054048a7edc4110c6f84fde594
+  $ hg book -f foo
+  $ hg push --traceback
+  pushing to ssh://user@dummy/remote
+  searching for changes
+  no changes found
+  updating bookmark foo
+  [1]
+  $ hg book -d foo
+  $ hg in -B
+  comparing with ssh://user@dummy/remote
+  searching for changed bookmarks
+     foo                       a28a9d1a809c
+  $ hg book -f -r 0 foo
+  $ hg pull -B foo
+  pulling from ssh://user@dummy/remote
+  no changes found
+  updating bookmark foo
+  $ hg book -d foo
+  $ hg push -B foo
+  pushing to ssh://user@dummy/remote
+  searching for changes
+  no changes found
+  deleting remote bookmark foo
+  [1]
+
+a bad, evil hook that prints to stdout
+
+  $ cat <<EOF > $TESTTMP/badhook
+  > import sys
+  > sys.stdout.write("KABOOM\n")
+  > EOF
+
+  $ echo '[hooks]' >> ../remote/.hg/hgrc
+  $ echo "changegroup.stdout = python $TESTTMP/badhook" >> ../remote/.hg/hgrc
+  $ echo r > r
+  $ hg ci -A -m z r
+
+push should succeed even though it has an unexpected response
+
+  $ hg push
+  pushing to ssh://user@dummy/remote
+  searching for changes
+  remote has heads on branch 'default' that are not known locally: 6c0482d977a3
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 1 changesets with 1 changes to 1 files
+  remote: KABOOM
+  $ hg -R ../remote heads
+  changeset:   5:1383141674ec
+  tag:         tip
+  parent:      3:a28a9d1a809c
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     z
+  
+  changeset:   4:6c0482d977a3
+  parent:      0:1160648e36ce
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     z
+  
+
+clone bookmarks
+
+  $ hg -R ../remote bookmark test
+  $ hg -R ../remote bookmarks
+   * test                      4:6c0482d977a3
+  $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local-bookmarks
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 6 changesets with 5 changes to 4 files (+1 heads)
+  updating to branch default
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg -R local-bookmarks bookmarks
+     test                      4:6c0482d977a3
+
+passwords in ssh urls are not supported
+(we use a glob here because different Python versions give different
+results here)
+
+  $ hg push ssh://user:erroneouspwd@dummy/remote
+  pushing to ssh://user:*@dummy/remote (glob)
+  abort: password in URL not supported!
+  [255]
+
+  $ cd ..
+
+hide outer repo
+  $ hg init
+
+Test remote paths with spaces (issue2983):
+
+  $ hg init --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
+  $ touch "$TESTTMP/a repo/test"
+  $ hg -R 'a repo' commit -A -m "test"
+  adding test
+  $ hg -R 'a repo' tag tag
+  $ hg id --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
+  73649e48688a
+
+  $ hg id --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo#noNoNO"
+  abort: unknown revision 'noNoNO'!
+  [255]
+
+Test (non-)escaping of remote paths with spaces when cloning (issue3145):
+
+  $ hg clone --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
+  destination directory: a repo
+  abort: destination 'a repo' is not empty
+  [255]
+
+Test hg-ssh using a helper script that will restore PYTHONPATH (which might
+have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right
+parameters:
+
+  $ cat > ssh.sh << EOF
+  > userhost="\$1"
+  > SSH_ORIGINAL_COMMAND="\$2"
+  > export SSH_ORIGINAL_COMMAND
+  > PYTHONPATH="$PYTHONPATH"
+  > export PYTHONPATH
+  > python "$TESTDIR/../contrib/hg-ssh" "$TESTTMP/a repo"
+  > EOF
+
+  $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a repo"
+  73649e48688a
+
+  $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a'repo"
+  remote: Illegal repository "$TESTTMP/a'repo" (glob)
+  abort: no suitable response from remote hg!
+  [255]
+
+  $ hg id --ssh "sh ssh.sh" --remotecmd hacking "ssh://user@dummy/a'repo"
+  remote: Illegal command "hacking -R 'a'\''repo' serve --stdio"
+  abort: no suitable response from remote hg!
+  [255]
+
+  $ SSH_ORIGINAL_COMMAND="'hg' -R 'a'repo' serve --stdio" python "$TESTDIR/../contrib/hg-ssh"
+  Illegal command "'hg' -R 'a'repo' serve --stdio": No closing quotation
+  [255]
+
+Test hg-ssh in read-only mode:
+
+  $ cat > ssh.sh << EOF
+  > userhost="\$1"
+  > SSH_ORIGINAL_COMMAND="\$2"
+  > export SSH_ORIGINAL_COMMAND
+  > PYTHONPATH="$PYTHONPATH"
+  > export PYTHONPATH
+  > python "$TESTDIR/../contrib/hg-ssh" --read-only "$TESTTMP/remote"
+  > EOF
+
+  $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 6 changesets with 5 changes to 4 files (+1 heads)
+  updating to branch default
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ cd read-only-local
+  $ echo "baz" > bar
+  $ hg ci -A -m "unpushable commit" bar
+  $ hg push --ssh "sh ../ssh.sh"
+  pushing to ssh://user@dummy/*/remote (glob)
+  searching for changes
+  remote: Permission denied
+  remote: abort: pretxnopen.hg-ssh hook failed
+  remote: Permission denied
+  remote: pushkey-abort: prepushkey.hg-ssh hook failed
+  updating 6c0482d977a3 to public failed!
+  [1]
+
+  $ cd ..
+
+stderr from remote commands should be printed before stdout from local code (issue4336)
+
+  $ hg clone remote stderr-ordering
+  updating to branch default
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd stderr-ordering
+  $ cat >> localwrite.py << EOF
+  > from mercurial import exchange, extensions
+  > 
+  > def wrappedpush(orig, repo, *args, **kwargs):
+  >     res = orig(repo, *args, **kwargs)
+  >     repo.ui.write('local stdout\n')
+  >     return res
+  > 
+  > def extsetup(ui):
+  >     extensions.wrapfunction(exchange, 'push', wrappedpush)
+  > EOF
+
+  $ cat >> .hg/hgrc << EOF
+  > [paths]
+  > default-push = ssh://user@dummy/remote
+  > [ui]
+  > ssh = python "$TESTDIR/dummyssh"
+  > [extensions]
+  > localwrite = localwrite.py
+  > EOF
+
+  $ echo localwrite > foo
+  $ hg commit -m 'testing localwrite'
+  $ hg push
+  pushing to ssh://user@dummy/remote
+  searching for changes
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 1 changesets with 1 changes to 1 files
+  remote: KABOOM
+  local stdout
+
+debug output
+
+  $ hg pull --debug ssh://user@dummy/remote
+  pulling from ssh://user@dummy/remote
+  running python ".*/dummyssh" user@dummy ('|")hg -R remote serve --stdio('|") (re)
+  sending hello command
+  sending between command
+  remote: 345
+  remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream 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
+  remote: 1
+  preparing listkeys for "bookmarks"
+  sending listkeys command
+  received listkey for "bookmarks": 45 bytes
+  query 1; heads
+  sending batch command
+  searching for changes
+  all remote heads known locally
+  no changes found
+  preparing listkeys for "phases"
+  sending listkeys command
+  received listkey for "phases": 15 bytes
+  checking for updated bookmarks
+
+  $ cd ..
+
+  $ cat dummylog
+  Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
+  Got arguments 1:user@dummy 2:hg -R /$TESTTMP/nonexistent serve --stdio
+  Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+  Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio
+  Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+  Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+  Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
+  Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+  Got arguments 1:user@dummy 2:hg -R local serve --stdio
+  Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
+  Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+  changegroup-in-remote hook: HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
+  Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+  Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+  Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+  Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+  Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+  Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+  Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+  Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+  Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+  changegroup-in-remote hook: HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
+  Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+  Got arguments 1:user@dummy 2:hg init 'a repo'
+  Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
+  Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
+  Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
+  Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
+  Got arguments 1:user@dummy 2:hg -R remote serve --stdio
+  changegroup-in-remote hook: HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
+  Got arguments 1:user@dummy 2:hg -R remote serve --stdio
--- a/tests/test-ssh.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-ssh.t	Sat Jul 18 17:32:38 2015 -0500
@@ -1,4 +1,3 @@
-
 
 This test tries to exercise the ssh functionality with a dummy script
 
@@ -28,7 +27,7 @@
   > uncompressed = True
   > 
   > [hooks]
-  > changegroup = python "$TESTDIR/printenv.py" changegroup-in-remote 0 ../dummylog
+  > changegroup = printenv.py changegroup-in-remote 0 ../dummylog
   > EOF
   $ cd ..
 
@@ -105,7 +104,7 @@
   checking files
   2 files, 3 changesets, 2 total revisions
   $ echo '[hooks]' >> .hg/hgrc
-  $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup-in-local 0 ../dummylog" >> .hg/hgrc
+  $ echo "changegroup = printenv.py changegroup-in-local 0 ../dummylog" >> .hg/hgrc
 
 empty default pull
 
@@ -398,11 +397,8 @@
   pushing to ssh://user@dummy/*/remote (glob)
   searching for changes
   remote: Permission denied
-  remote: abort: prechangegroup.hg-ssh hook failed
-  remote: Permission denied
-  remote: pushkey-abort: prepushkey.hg-ssh hook failed
-  updating 6c0482d977a3 to public failed!
-  [1]
+  abort: pretxnopen.hg-ssh hook failed
+  [255]
 
   $ cd ..
 
@@ -445,6 +441,32 @@
   remote: KABOOM
   local stdout
 
+debug output
+
+  $ hg pull --debug ssh://user@dummy/remote
+  pulling from ssh://user@dummy/remote
+  running python ".*/dummyssh" user@dummy ('|")hg -R remote serve --stdio('|") (re)
+  sending hello command
+  sending between command
+  remote: 345
+  remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream 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
+  remote: 1
+  query 1; heads
+  sending batch command
+  searching for changes
+  all remote heads known locally
+  no changes found
+  sending getbundle command
+  bundle2-input-bundle: with-transaction
+  bundle2-input-part: "listkeys" (params: 1 mandatory) supported
+  bundle2-input-part: "listkeys" (params: 1 mandatory) supported
+  bundle2-input-part: total payload size 45
+  bundle2-input-bundle: 1 parts total
+  checking for updated bookmarks
+  preparing listkeys for "phases"
+  sending listkeys command
+  received listkey for "phases": 15 bytes
+
   $ cd ..
 
   $ cat dummylog
@@ -459,7 +481,7 @@
   Got arguments 1:user@dummy 2:hg -R local serve --stdio
   Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
   Got arguments 1:user@dummy 2:hg -R remote serve --stdio
-  changegroup-in-remote hook: HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
+  changegroup-in-remote hook: HG_BUNDLE2=1 HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
   Got arguments 1:user@dummy 2:hg -R remote serve --stdio
   Got arguments 1:user@dummy 2:hg -R remote serve --stdio
   Got arguments 1:user@dummy 2:hg -R remote serve --stdio
@@ -469,7 +491,7 @@
   Got arguments 1:user@dummy 2:hg -R remote serve --stdio
   Got arguments 1:user@dummy 2:hg -R remote serve --stdio
   Got arguments 1:user@dummy 2:hg -R remote serve --stdio
-  changegroup-in-remote hook: HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
+  changegroup-in-remote hook: HG_BUNDLE2=1 HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
   Got arguments 1:user@dummy 2:hg -R remote serve --stdio
   Got arguments 1:user@dummy 2:hg init 'a repo'
   Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
@@ -477,4 +499,5 @@
   Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
   Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
   Got arguments 1:user@dummy 2:hg -R remote serve --stdio
-  changegroup-in-remote hook: HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
+  changegroup-in-remote hook: HG_BUNDLE2=1 HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
+  Got arguments 1:user@dummy 2:hg -R remote serve --stdio
--- a/tests/test-static-http.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-static-http.t	Sat Jul 18 17:32:38 2015 -0500
@@ -60,7 +60,7 @@
   $ rm .hg/cache/*
   $ cd ../local
   $ echo '[hooks]' >> .hg/hgrc
-  $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup" >> .hg/hgrc
+  $ echo "changegroup = printenv.py changegroup" >> .hg/hgrc
   $ hg pull
   pulling from static-http://localhost:$HGPORT/remote
   searching for changes
@@ -129,6 +129,7 @@
   crosschecking files in changesets and manifests
   checking files
   3 files, 1 changesets, 3 total revisions
+  checking subrepo links
   $ cat a
   a
   $ hg paths
@@ -159,4 +160,4 @@
   $ hg clone static-http://localhost:$HGPORT/notarepo local3
   abort: 'http://localhost:$HGPORT/notarepo' does not appear to be an hg repository!
   [255]
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
--- a/tests/test-status.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-status.t	Sat Jul 18 17:32:38 2015 -0500
@@ -212,6 +212,33 @@
   $ mkdir ignoreddir
   $ touch ignoreddir/file
 
+Test templater support:
+
+  $ hg status -AT "[{status}]\t{if(copy, '{copy} -> ')}{path}\n"
+  [M]	.hgignore
+  [A]	added
+  [A]	modified -> copied
+  [R]	removed
+  [!]	deleted
+  [?]	ignored
+  [?]	unknown
+  [I]	ignoreddir/file
+  [C]	modified
+  $ hg status -AT default
+  M .hgignore
+  A added
+  A copied
+    modified
+  R removed
+  ! deleted
+  ? ignored
+  ? unknown
+  I ignoreddir/file
+  C modified
+  $ hg status -T compact
+  abort: "status" not in template map
+  [255]
+
 hg status ignoreddir/file:
 
   $ hg status ignoreddir/file
--- a/tests/test-strip.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-strip.t	Sat Jul 18 17:32:38 2015 -0500
@@ -526,6 +526,7 @@
   branch: default
   commit: 1 modified, 1 unknown, 1 unresolved
   update: (current)
+  phases: 2 draft
   mq:     3 unapplied
 
   $ echo c > b
@@ -553,6 +554,7 @@
   branch: default
   commit: 1 modified, 1 unknown
   update: (current)
+  phases: 1 draft
   mq:     3 unapplied
 
 Strip adds, removes, modifies with --keep
@@ -781,17 +783,11 @@
   removing c
    d: other deleted -> r
   removing d
-  updating: d 2/2 files (100.00%)
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
   2 changesets found
   list of changesets:
   6625a516847449b6f0fa3737b9ba56e9f0f3032c
   d8db9d1372214336d2b5570f20ee468d2c72fa8b
-  bundling: 1/2 changesets (50.00%)
-  bundling: 2/2 changesets (100.00%)
-  bundling: 1/2 manifests (50.00%)
-  bundling: 2/2 manifests (100.00%)
-  bundling: d 1/1 files (100.00%)
   saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/6625a5168474-345bb43d-backup.hg (glob)
   invalid branchheads cache (served): tip differs
   truncating cache/rbc-revs-v1 to 24
--- a/tests/test-subrepo-deep-nested-change.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-subrepo-deep-nested-change.t	Sat Jul 18 17:32:38 2015 -0500
@@ -168,7 +168,60 @@
   A foo/bar/abc
   A sub1/foo
   R sub1/sub2/test.txt
+
+Archive wdir() with subrepos
+  $ hg rm main
+  $ hg archive -S -r 'wdir()' ../wdir
+  $ diff -r . ../wdir | grep -v '\.hg$'
+  Only in ../wdir: .hg_archival.txt
+
+  $ find ../wdir -type f | sort
+  ../wdir/.hg_archival.txt
+  ../wdir/.hgsub
+  ../wdir/.hgsubstate
+  ../wdir/foo/bar/abc
+  ../wdir/sub1/.hgsub
+  ../wdir/sub1/.hgsubstate
+  ../wdir/sub1/foo
+  ../wdir/sub1/sub1
+  ../wdir/sub1/sub2/folder/test.txt
+  ../wdir/sub1/sub2/sub2
+
+  $ cat ../wdir/.hg_archival.txt
+  repo: 7f491f53a367861f47ee64a80eb997d1f341b77a
+  node: 9bb10eebee29dc0f1201dcf5977b811a540255fd+
+  branch: default
+  latesttag: null
+  latesttagdistance: 4
+  changessincelatesttag: 4
+
+Attempting to archive 'wdir()' with a missing file is handled gracefully
+  $ rm sub1/sub1
+  $ rm -r ../wdir
+  $ hg archive -v -S -r 'wdir()' ../wdir
+  $ find ../wdir -type f | sort
+  ../wdir/.hg_archival.txt
+  ../wdir/.hgsub
+  ../wdir/.hgsubstate
+  ../wdir/foo/bar/abc
+  ../wdir/sub1/.hgsub
+  ../wdir/sub1/.hgsubstate
+  ../wdir/sub1/foo
+  ../wdir/sub1/sub2/folder/test.txt
+  ../wdir/sub1/sub2/sub2
+
+Continue relative path printing + subrepos
   $ hg update -Cq
+  $ rm -r ../wdir
+  $ hg archive -S -r 'wdir()' ../wdir
+  $ cat ../wdir/.hg_archival.txt
+  repo: 7f491f53a367861f47ee64a80eb997d1f341b77a
+  node: 9bb10eebee29dc0f1201dcf5977b811a540255fd
+  branch: default
+  latesttag: null
+  latesttagdistance: 4
+  changessincelatesttag: 4
+
   $ touch sub1/sub2/folder/bar
   $ hg addremove sub1/sub2
   adding sub1/sub2/folder/bar (glob)
@@ -209,6 +262,41 @@
   sub1/sub2/folder/bar (glob)
   sub1/sub2/x.txt (glob)
 
+  $ hg files -S "set:eol('dos') or eol('unix') or size('<= 0')"
+  .hgsub
+  .hgsubstate
+  foo/bar/abc (glob)
+  main
+  sub1/.hgsub (glob)
+  sub1/.hgsubstate (glob)
+  sub1/foo (glob)
+  sub1/sub1 (glob)
+  sub1/sub2/folder/bar (glob)
+  sub1/sub2/x.txt (glob)
+
+  $ hg files -r '.^' -S "set:eol('dos') or eol('unix')"
+  .hgsub
+  .hgsubstate
+  main
+  sub1/.hgsub (glob)
+  sub1/.hgsubstate (glob)
+  sub1/sub1 (glob)
+  sub1/sub2/folder/test.txt (glob)
+  sub1/sub2/sub2 (glob)
+  sub1/sub2/test.txt (glob)
+
+  $ hg files sub1
+  sub1/.hgsub (glob)
+  sub1/.hgsubstate (glob)
+  sub1/foo (glob)
+  sub1/sub1 (glob)
+  sub1/sub2/folder/bar (glob)
+  sub1/sub2/x.txt (glob)
+
+  $ hg files sub1/sub2
+  sub1/sub2/folder/bar (glob)
+  sub1/sub2/x.txt (glob)
+
   $ hg files -S -r '.^' sub1/sub2/folder
   sub1/sub2/folder/test.txt (glob)
 
@@ -216,7 +304,7 @@
   sub1/sub2/missing: no such file in rev 78026e779ea6 (glob)
   [1]
 
-  $ hg files -S -r '.^' sub1/
+  $ hg files -r '.^' sub1/
   sub1/.hgsub (glob)
   sub1/.hgsubstate (glob)
   sub1/sub1 (glob)
@@ -224,7 +312,7 @@
   sub1/sub2/sub2 (glob)
   sub1/sub2/test.txt (glob)
 
-  $ hg files -S -r '.^' sub1/sub2
+  $ hg files -r '.^' sub1/sub2
   sub1/sub2/folder/test.txt (glob)
   sub1/sub2/sub2 (glob)
   sub1/sub2/test.txt (glob)
@@ -329,17 +417,17 @@
 
 Exclude normal files from main and sub-sub repo
 
-  $ hg --config extensions.largefiles= archive -S -X '**.txt' ../archive_lf.tgz
+  $ hg --config extensions.largefiles= archive -S -X '**.txt' -p '.' ../archive_lf.tgz
   $ tar -tzf ../archive_lf.tgz | sort
-  archive_lf/.hgsub
-  archive_lf/.hgsubstate
-  archive_lf/large.bin
-  archive_lf/main
-  archive_lf/sub1/.hgsub
-  archive_lf/sub1/.hgsubstate
-  archive_lf/sub1/sub1
-  archive_lf/sub1/sub2/large.bin
-  archive_lf/sub1/sub2/sub2
+  .hgsub
+  .hgsubstate
+  large.bin
+  main
+  sub1/.hgsub
+  sub1/.hgsubstate
+  sub1/sub1
+  sub1/sub2/large.bin
+  sub1/sub2/sub2
 
 Include normal files from within a largefiles subrepo
 
@@ -434,8 +522,69 @@
   ? sub1/sub2/untracked.txt
   ? sub1/sub2/x.txt
   $ hg add sub1/sub2
+
+  $ hg archive -S -r 'wdir()' ../wdir2
+  $ diff -r . ../wdir2 | grep -v '\.hg$'
+  Only in ../wdir2: .hg_archival.txt
+  Only in .: .hglf
+  Only in .: foo
+  Only in ./sub1/sub2: large.bin
+  Only in ./sub1/sub2: test.txt
+  Only in ./sub1/sub2: untracked.txt
+  Only in ./sub1/sub2: x.txt
+  $ find ../wdir2 -type f | sort
+  ../wdir2/.hg_archival.txt
+  ../wdir2/.hgsub
+  ../wdir2/.hgsubstate
+  ../wdir2/large.bin
+  ../wdir2/main
+  ../wdir2/sub1/.hgsub
+  ../wdir2/sub1/.hgsubstate
+  ../wdir2/sub1/sub1
+  ../wdir2/sub1/sub2/folder/test.txt
+  ../wdir2/sub1/sub2/large.dat
+  ../wdir2/sub1/sub2/sub2
+  $ hg status -S -mac -n | sort
+  .hgsub
+  .hgsubstate
+  large.bin
+  main
+  sub1/.hgsub
+  sub1/.hgsubstate
+  sub1/sub1
+  sub1/sub2/folder/test.txt
+  sub1/sub2/large.dat
+  sub1/sub2/sub2
+
   $ hg ci -Sqm 'forget testing'
 
+Test 'wdir()' modified file archiving with largefiles
+  $ echo 'mod' > main
+  $ echo 'mod' > large.bin
+  $ echo 'mod' > sub1/sub2/large.dat
+  $ hg archive -S -r 'wdir()' ../wdir3
+  $ diff -r . ../wdir3 | grep -v '\.hg$'
+  Only in ../wdir3: .hg_archival.txt
+  Only in .: .hglf
+  Only in .: foo
+  Only in ./sub1/sub2: large.bin
+  Only in ./sub1/sub2: test.txt
+  Only in ./sub1/sub2: untracked.txt
+  Only in ./sub1/sub2: x.txt
+  $ find ../wdir3 -type f | sort
+  ../wdir3/.hg_archival.txt
+  ../wdir3/.hgsub
+  ../wdir3/.hgsubstate
+  ../wdir3/large.bin
+  ../wdir3/main
+  ../wdir3/sub1/.hgsub
+  ../wdir3/sub1/.hgsubstate
+  ../wdir3/sub1/sub1
+  ../wdir3/sub1/sub2/folder/test.txt
+  ../wdir3/sub1/sub2/large.dat
+  ../wdir3/sub1/sub2/sub2
+  $ hg up -Cq
+
 Test issue4330: commit a directory where only normal files have changed
   $ touch foo/bar/large.dat
   $ hg add --large foo/bar/large.dat
@@ -560,4 +709,96 @@
   $ hg rollback -q
   $ hg update -Cq .
 
+Interaction with extdiff, largefiles and subrepos
+
+  $ hg --config extensions.extdiff= extdiff -S
+
+  $ hg --config extensions.extdiff= extdiff -r '.^' -S
+  diff -Npru cloned.*/.hgsub cloned/.hgsub (glob)
+  --- cloned.*/.hgsub	* +0000 (glob)
+  +++ cloned/.hgsub	* +0000 (glob)
+  @@ -1,2 +1 @@
+   sub1 = ../sub1
+  -sub3 = sub3
+  diff -Npru cloned.*/.hgsubstate cloned/.hgsubstate (glob)
+  --- cloned.*/.hgsubstate	* +0000 (glob)
+  +++ cloned/.hgsubstate	* +0000 (glob)
+  @@ -1,2 +1 @@
+   7a36fa02b66e61f27f3d4a822809f159479b8ab2 sub1
+  -b1a26de6f2a045a9f079323693614ee322f1ff7e sub3
+  [1]
+
+  $ hg --config extensions.extdiff= extdiff -r 0 -r '.^' -S
+  diff -Npru cloned.*/.hglf/b.dat cloned.*/.hglf/b.dat (glob)
+  --- cloned.*/.hglf/b.dat	* (glob)
+  +++ cloned.*/.hglf/b.dat	* (glob)
+  @@ -0,0 +1 @@
+  +da39a3ee5e6b4b0d3255bfef95601890afd80709
+  diff -Npru cloned.*/.hglf/foo/bar/large.dat cloned.*/.hglf/foo/bar/large.dat (glob)
+  --- cloned.*/.hglf/foo/bar/large.dat	* (glob)
+  +++ cloned.*/.hglf/foo/bar/large.dat	* (glob)
+  @@ -0,0 +1 @@
+  +2f6933b5ee0f5fdd823d9717d8729f3c2523811b
+  diff -Npru cloned.*/.hglf/large.bin cloned.*/.hglf/large.bin (glob)
+  --- cloned.*/.hglf/large.bin	* (glob)
+  +++ cloned.*/.hglf/large.bin	* (glob)
+  @@ -0,0 +1 @@
+  +7f7097b041ccf68cc5561e9600da4655d21c6d18
+  diff -Npru cloned.*/.hgsub cloned.*/.hgsub (glob)
+  --- cloned.*/.hgsub	* (glob)
+  +++ cloned.*/.hgsub	* (glob)
+  @@ -1 +1,2 @@
+   sub1 = ../sub1
+  +sub3 = sub3
+  diff -Npru cloned.*/.hgsubstate cloned.*/.hgsubstate (glob)
+  --- cloned.*/.hgsubstate	* (glob)
+  +++ cloned.*/.hgsubstate	* (glob)
+  @@ -1 +1,2 @@
+  -fc3b4ce2696f7741438c79207583768f2ce6b0dd sub1
+  +7a36fa02b66e61f27f3d4a822809f159479b8ab2 sub1
+  +b1a26de6f2a045a9f079323693614ee322f1ff7e sub3
+  diff -Npru cloned.*/foo/bar/def cloned.*/foo/bar/def (glob)
+  --- cloned.*/foo/bar/def	* (glob)
+  +++ cloned.*/foo/bar/def	* (glob)
+  @@ -0,0 +1 @@
+  +changed
+  diff -Npru cloned.*/main cloned.*/main (glob)
+  --- cloned.*/main	* (glob)
+  +++ cloned.*/main	* (glob)
+  @@ -1 +1 @@
+  -main
+  +foo
+  diff -Npru cloned.*/sub1/.hgsubstate cloned.*/sub1/.hgsubstate (glob)
+  --- cloned.*/sub1/.hgsubstate	* (glob)
+  +++ cloned.*/sub1/.hgsubstate	* (glob)
+  @@ -1 +1 @@
+  -c57a0840e3badd667ef3c3ef65471609acb2ba3c sub2
+  +c77908c81ccea3794a896c79e98b0e004aee2e9e sub2
+  diff -Npru cloned.*/sub1/sub2/folder/test.txt cloned.*/sub1/sub2/folder/test.txt (glob)
+  --- cloned.*/sub1/sub2/folder/test.txt	* (glob)
+  +++ cloned.*/sub1/sub2/folder/test.txt	* (glob)
+  @@ -0,0 +1 @@
+  +subfolder
+  diff -Npru cloned.*/sub1/sub2/sub2 cloned.*/sub1/sub2/sub2 (glob)
+  --- cloned.*/sub1/sub2/sub2	* (glob)
+  +++ cloned.*/sub1/sub2/sub2	* (glob)
+  @@ -1 +1 @@
+  -sub2
+  +modified
+  diff -Npru cloned.*/sub3/a.txt cloned.*/sub3/a.txt (glob)
+  --- cloned.*/sub3/a.txt	* (glob)
+  +++ cloned.*/sub3/a.txt	* (glob)
+  @@ -0,0 +1 @@
+  +xyz
+  [1]
+
+  $ echo mod > sub1/sub2/sub2
+  $ hg --config extensions.extdiff= extdiff -S
+  --- */cloned.*/sub1/sub2/sub2	* (glob)
+  +++ */cloned/sub1/sub2/sub2	* (glob)
+  @@ -1 +1 @@
+  -modified
+  +mod
+  [1]
+
   $ cd ..
--- a/tests/test-subrepo-git.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-subrepo-git.t	Sat Jul 18 17:32:38 2015 -0500
@@ -325,13 +325,13 @@
   ../archive_x/s
   ../archive_x/s/g
 
-  $ hg -R ../tc archive -S ../archive.tgz 2>/dev/null
-  $ tar -tzf ../archive.tgz | sort
-  archive/.hg_archival.txt
-  archive/.hgsub
-  archive/.hgsubstate
-  archive/a
-  archive/s/g
+  $ hg -R ../tc archive -S ../archive.tgz --prefix '.' 2>/dev/null
+  $ tar -tzf ../archive.tgz | sort | grep -v pax_global_header
+  .hg_archival.txt
+  .hgsub
+  .hgsubstate
+  a
+  s/g
 
 create nested repo
 
@@ -1105,5 +1105,21 @@
   ? s/c.c
   ? s/cpp.cpp
   ? s/foobar.orig
+  $ hg revert --all -q
+
+make sure we show changed files, rather than changed subtrees
+  $ mkdir s/foo
+  $ touch s/foo/bwuh
+  $ hg add s/foo/bwuh
+  $ hg commit -S -m "add bwuh"
+  committing subrepository s
+  $ hg status -S --change .
+  M .hgsubstate
+  A s/foo/bwuh
+  ? s/barfoo
+  ? s/c.c
+  ? s/cpp.cpp
+  ? s/foobar.orig
+  ? s/snake.python.orig
 
   $ cd ..
--- a/tests/test-subrepo-missing.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-subrepo-missing.t	Sat Jul 18 17:32:38 2015 -0500
@@ -23,8 +23,8 @@
 
   $ cp .hgsubstate .hgsubstate.old
   >>> file('.hgsubstate', 'wb').write('\ninvalid')
-  $ hg st --subrepos
-  abort: invalid subrepository revision specifier in '.hgsubstate' line 2
+  $ hg st --subrepos --cwd $TESTTMP -R $TESTTMP/repo
+  abort: invalid subrepository revision specifier in 'repo/.hgsubstate' line 2 (glob)
   [255]
   $ mv .hgsubstate.old .hgsubstate
 
@@ -44,9 +44,9 @@
 delete .hgsub and update
 
   $ rm .hgsub
-  $ hg up 0
-  warning: subrepo spec file '.hgsub' not found
-  warning: subrepo spec file '.hgsub' not found
+  $ hg up 0 --cwd $TESTTMP -R $TESTTMP/repo
+  warning: subrepo spec file 'repo/.hgsub' not found (glob)
+  warning: subrepo spec file 'repo/.hgsub' not found (glob)
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg st
   warning: subrepo spec file '.hgsub' not found
@@ -106,4 +106,19 @@
   $ hg --hidden cat subrepo/a
   foo
 
+verify will warn if locked-in subrepo revisions are hidden or missing
+
+  $ hg ci -m "amended subrepo (again)"
+  $ hg --config extensions.strip= --hidden strip -R subrepo -qr 'tip'
+  $ hg verify
+  checking changesets
+  checking manifests
+  crosschecking files in changesets and manifests
+  checking files
+  2 files, 5 changesets, 5 total revisions
+  checking subrepo links
+  subrepo 'subrepo' is hidden in revision a66de08943b6
+  subrepo 'subrepo' is hidden in revision 674d05939c1e
+  subrepo 'subrepo' not found in revision a7d05d9055a4
+
   $ cd ..
--- a/tests/test-subrepo-recursion.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-subrepo-recursion.t	Sat Jul 18 17:32:38 2015 -0500
@@ -258,6 +258,7 @@
   > [extensions]
   > progress =
   > [progress]
+  > disable=False
   > assume-tty = 1
   > delay = 0
   > # set changedelay really large so we don't see nested topics
@@ -273,28 +274,18 @@
   $ hg archive --subrepos ../archive
   \r (no-eol) (esc)
   archiving [                                           ] 0/3\r (no-eol) (esc)
-  archiving [                                           ] 0/3\r (no-eol) (esc)
-  archiving [=============>                             ] 1/3\r (no-eol) (esc)
   archiving [=============>                             ] 1/3\r (no-eol) (esc)
   archiving [===========================>               ] 2/3\r (no-eol) (esc)
-  archiving [===========================>               ] 2/3\r (no-eol) (esc)
-  archiving [==========================================>] 3/3\r (no-eol) (esc)
   archiving [==========================================>] 3/3\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
   \r (no-eol) (esc)
   archiving (foo) [                                     ] 0/3\r (no-eol) (esc)
-  archiving (foo) [                                     ] 0/3\r (no-eol) (esc)
-  archiving (foo) [===========>                         ] 1/3\r (no-eol) (esc)
   archiving (foo) [===========>                         ] 1/3\r (no-eol) (esc)
   archiving (foo) [=======================>             ] 2/3\r (no-eol) (esc)
-  archiving (foo) [=======================>             ] 2/3\r (no-eol) (esc)
-  archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
   archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
   \r (no-eol) (esc)
   archiving (foo/bar) [                                 ] 0/1\r (no-eol) (glob) (esc)
-  archiving (foo/bar) [                                 ] 0/1\r (no-eol) (glob) (esc)
-  archiving (foo/bar) [================================>] 1/1\r (no-eol) (glob) (esc)
   archiving (foo/bar) [================================>] 1/1\r (no-eol) (glob) (esc)
                                                               \r (no-eol) (esc)
   $ find ../archive | sort
@@ -312,34 +303,41 @@
 
 Test archiving to zip file (unzip output is unstable):
 
-  $ hg archive --subrepos ../archive.zip
+  $ hg archive --subrepos --prefix '.' ../archive.zip
   \r (no-eol) (esc)
   archiving [                                           ] 0/3\r (no-eol) (esc)
-  archiving [                                           ] 0/3\r (no-eol) (esc)
   archiving [=============>                             ] 1/3\r (no-eol) (esc)
-  archiving [=============>                             ] 1/3\r (no-eol) (esc)
   archiving [===========================>               ] 2/3\r (no-eol) (esc)
-  archiving [===========================>               ] 2/3\r (no-eol) (esc)
-  archiving [==========================================>] 3/3\r (no-eol) (esc)
   archiving [==========================================>] 3/3\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
   \r (no-eol) (esc)
   archiving (foo) [                                     ] 0/3\r (no-eol) (esc)
-  archiving (foo) [                                     ] 0/3\r (no-eol) (esc)
-  archiving (foo) [===========>                         ] 1/3\r (no-eol) (esc)
   archiving (foo) [===========>                         ] 1/3\r (no-eol) (esc)
   archiving (foo) [=======================>             ] 2/3\r (no-eol) (esc)
-  archiving (foo) [=======================>             ] 2/3\r (no-eol) (esc)
-  archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
   archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
   \r (no-eol) (esc)
   archiving (foo/bar) [                                 ] 0/1\r (no-eol) (glob) (esc)
-  archiving (foo/bar) [                                 ] 0/1\r (no-eol) (glob) (esc)
-  archiving (foo/bar) [================================>] 1/1\r (no-eol) (glob) (esc)
   archiving (foo/bar) [================================>] 1/1\r (no-eol) (glob) (esc)
                                                               \r (no-eol) (esc)
 
+(unzip date formating is unstable, we do not care about it and glob it out)
+
+  $ unzip -l ../archive.zip
+  Archive:  ../archive.zip
+    Length [ ]* Date [ ]* Time [ ]* Name (re)
+  [\- ]* (re)
+        172  [0-9:\- ]*  .hg_archival.txt (re)
+         10  [0-9:\- ]*  .hgsub (re)
+         45  [0-9:\- ]*  .hgsubstate (re)
+          3  [0-9:\- ]*  x.txt (re)
+         10  [0-9:\- ]*  foo/.hgsub (re)
+         45  [0-9:\- ]*  foo/.hgsubstate (re)
+          9  [0-9:\- ]*  foo/y.txt (re)
+          9  [0-9:\- ]*  foo/bar/z.txt (re)
+  [\- ]* (re)
+        303  [ ]*        8 files (re)
+
 Test archiving a revision that references a subrepo that is not yet
 cloned:
 
@@ -363,15 +361,11 @@
 
   $ cd ../empty
 #if hardlink
-  $ hg archive --subrepos -r tip ../archive.tar.gz
+  $ hg archive --subrepos -r tip --prefix './' ../archive.tar.gz
   \r (no-eol) (esc)
   archiving [                                           ] 0/3\r (no-eol) (esc)
-  archiving [                                           ] 0/3\r (no-eol) (esc)
   archiving [=============>                             ] 1/3\r (no-eol) (esc)
-  archiving [=============>                             ] 1/3\r (no-eol) (esc)
   archiving [===========================>               ] 2/3\r (no-eol) (esc)
-  archiving [===========================>               ] 2/3\r (no-eol) (esc)
-  archiving [==========================================>] 3/3\r (no-eol) (esc)
   archiving [==========================================>] 3/3\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
   \r (no-eol) (esc)
@@ -386,12 +380,8 @@
                                                               \r (no-eol) (esc)
   \r (no-eol) (esc)
   archiving (foo) [                                     ] 0/3\r (no-eol) (esc)
-  archiving (foo) [                                     ] 0/3\r (no-eol) (esc)
-  archiving (foo) [===========>                         ] 1/3\r (no-eol) (esc)
   archiving (foo) [===========>                         ] 1/3\r (no-eol) (esc)
   archiving (foo) [=======================>             ] 2/3\r (no-eol) (esc)
-  archiving (foo) [=======================>             ] 2/3\r (no-eol) (esc)
-  archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
   archiving (foo) [====================================>] 3/3\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
   \r (no-eol) (esc)
@@ -404,8 +394,6 @@
                                                               \r (no-eol) (esc)
   \r (no-eol) (esc)
   archiving (foo/bar) [                                 ] 0/1\r (no-eol) (glob) (esc)
-  archiving (foo/bar) [                                 ] 0/1\r (no-eol) (glob) (esc)
-  archiving (foo/bar) [================================>] 1/1\r (no-eol) (glob) (esc)
   archiving (foo/bar) [================================>] 1/1\r (no-eol) (glob) (esc)
                                                               \r (no-eol) (esc)
   cloning subrepo foo from $TESTTMP/repo/foo
@@ -413,38 +401,29 @@
 #else
 Note there's a slight output glitch on non-hardlink systems: the last
 "linking" progress topic never gets closed, leading to slight output corruption on that platform.
-  $ hg archive --subrepos -r tip ../archive.tar.gz
+  $ hg archive --subrepos -r tip --prefix './' ../archive.tar.gz
   \r (no-eol) (esc)
   archiving [                                           ] 0/3\r (no-eol) (esc)
-  archiving [                                           ] 0/3\r (no-eol) (esc)
   archiving [=============>                             ] 1/3\r (no-eol) (esc)
-  archiving [=============>                             ] 1/3\r (no-eol) (esc)
   archiving [===========================>               ] 2/3\r (no-eol) (esc)
-  archiving [===========================>               ] 2/3\r (no-eol) (esc)
-  archiving [==========================================>] 3/3\r (no-eol) (esc)
   archiving [==========================================>] 3/3\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
   \r (no-eol) (esc)
   linking [ <=>                                           ] 1\r (no-eol) (esc)
-                                                              \r (no-eol) (esc)
-  \r (no-eol) (esc)
-                                                              \r (no-eol) (esc)
-  \r (no-eol) (esc)
-  linking [  <=>                                          ] 1cloning subrepo foo from $TESTTMP/repo/foo
   cloning subrepo foo/bar from $TESTTMP/repo/foo/bar (glob)
 #endif
 
 Archive + subrepos uses '/' for all component separators
 
   $ tar -tzf ../archive.tar.gz | sort
-  archive/.hg_archival.txt
-  archive/.hgsub
-  archive/.hgsubstate
-  archive/foo/.hgsub
-  archive/foo/.hgsubstate
-  archive/foo/bar/z.txt
-  archive/foo/y.txt
-  archive/x.txt
+  .hg_archival.txt
+  .hgsub
+  .hgsubstate
+  foo/.hgsub
+  foo/.hgsubstate
+  foo/bar/z.txt
+  foo/y.txt
+  x.txt
 
 The newly cloned subrepos contain no working copy:
 
--- a/tests/test-subrepo-relative-path.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-subrepo-relative-path.t	Sat Jul 18 17:32:38 2015 -0500
@@ -70,7 +70,7 @@
    source   ../sub
    revision 863c1745b441bd97a8c4a096e87793073f4fb215
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
 
 subrepo paths with ssh urls
 
--- a/tests/test-subrepo-svn.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-subrepo-svn.t	Sat Jul 18 17:32:38 2015 -0500
@@ -72,6 +72,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 2 draft
   $ hg ci -moops
   nothing changed
   [1]
@@ -96,6 +97,7 @@
   branch: default
   commit: 1 modified, 1 subrepos
   update: (current)
+  phases: 2 draft
   $ hg commit --subrepos -m 'Message!' | grep -v Updating
   committing subrepository s
   Sending*s/alpha (glob)
@@ -136,6 +138,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 3 draft
 
   $ echo a > s/a
 
@@ -548,7 +551,7 @@
 
 Test archive
 
-  $ hg archive -S ../archive-all --debug
+  $ hg archive -S ../archive-all --debug --config progress.debug=true
   archiving: 0/2 files (0.00%)
   archiving: .hgsub 1/2 files (50.00%)
   archiving: .hgsubstate 2/2 files (100.00%)
@@ -560,7 +563,7 @@
   archiving (s): 1/2 files (50.00%)
   archiving (s): 2/2 files (100.00%)
 
-  $ hg archive -S ../archive-exclude --debug -X **old
+  $ hg archive -S ../archive-exclude --debug --config progress.debug=true -X **old
   archiving: 0/2 files (0.00%)
   archiving: .hgsub 1/2 files (50.00%)
   archiving: .hgsubstate 2/2 files (100.00%)
--- a/tests/test-subrepo.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-subrepo.t	Sat Jul 18 17:32:38 2015 -0500
@@ -38,6 +38,7 @@
   branch: default
   commit: 1 added, 1 subrepos
   update: (current)
+  phases: 1 draft
   $ hg ci -m1
 
 test handling .hgsubstate "added" explicitly.
@@ -83,6 +84,7 @@
   branch: default
   commit: 1 subrepos
   update: (current)
+  phases: 2 draft
   $ hg co -C 1
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg sum
@@ -91,6 +93,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 2 draft
 
 commands that require a clean repo should respect subrepos
 
@@ -113,6 +116,7 @@
   branch: default
   commit: 1 subrepos
   update: (current)
+  phases: 2 draft
   $ hg ci -m2
   committing subrepository s
   committing subrepository s/ss (glob)
@@ -122,6 +126,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 3 draft
 
 test handling .hgsubstate "modified" explicitly.
 
@@ -255,7 +260,6 @@
    branchmerge: True, force: False, partial: False
    ancestor: 1f14a2e2d3ec, local: f0d2028bf86d+, remote: 1831e14459c4
    .hgsubstate: versions differ -> m
-  updating: .hgsubstate 1/1 files (100.00%)
   subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
     subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg
   getting subrepo t
@@ -264,7 +268,6 @@
    ancestor: 60ca1237c194, local: 60ca1237c194+, remote: 6747d179aa9a
    t: remote is newer -> g
   getting t
-  updating: t 1/1 files (100.00%)
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
   $ hg debugsub
@@ -283,7 +286,6 @@
    branchmerge: True, force: False, partial: False
    ancestor: 1831e14459c4, local: e45c8b14af55+, remote: f94576341bcf
    .hgsubstate: versions differ -> m
-  updating: .hgsubstate 1/1 files (100.00%)
   subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
     subrepo t: both sides changed 
    subrepository t diverged (local revision: 20a0db6fbf6c, remote revision: 7af322bc1198)
@@ -295,7 +297,6 @@
    ancestor: 6747d179aa9a, local: 20a0db6fbf6c+, remote: 7af322bc1198
    preserving t for resolve of t
    t: versions differ -> m
-  updating: t 1/1 files (100.00%)
   picked tool 'internal:merge' for t (binary False symlink False)
   merging t
   my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
@@ -938,14 +939,32 @@
 
 test if untracked file is not overwritten
 
+(this also tests that updated .hgsubstate is treated as "modified",
+when 'merge.update()' is aborted before 'merge.recordupdates()', even
+if none of mode, size and timestamp of it isn't changed on the
+filesystem (see also issue4583))
+
   $ echo issue3276_ok > repo/s/b
   $ hg -R repo2 push -f -q
   $ touch -t 200001010000 repo/.hgsubstate
-  $ hg -R repo status --config debug.dirstate.delaywrite=2 repo/.hgsubstate
+
+  $ cat >> repo/.hg/hgrc <<EOF
+  > [fakedirstatewritetime]
+  > # emulate invoking dirstate.write() via repo.status()
+  > # at 2000-01-01 00:00
+  > fakenow = 200001010000
+  > 
+  > [extensions]
+  > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
+  > EOF
   $ hg -R repo update
   b: untracked file differs
   abort: untracked files in working directory differ from files in requested revision (in subrepo s)
   [255]
+  $ cat >> repo/.hg/hgrc <<EOF
+  > [extensions]
+  > fakedirstatewritetime = !
+  > EOF
 
   $ cat repo/s/b
   issue3276_ok
@@ -1489,7 +1508,17 @@
   > [paths]
   > default=../issue3781-dest/
   > EOF
-  $ hg push
+  $ hg push --config experimental.bundle2-exp=False
+  pushing to $TESTTMP/issue3781-dest (glob)
+  pushing subrepo s to $TESTTMP/issue3781-dest/s
+  searching for changes
+  no changes found
+  searching for changes
+  no changes found
+  [1]
+# clean the push cache
+  $ rm s/.hg/cache/storehash/*
+  $ hg push --config experimental.bundle2-exp=True
   pushing to $TESTTMP/issue3781-dest (glob)
   pushing subrepo s to $TESTTMP/issue3781-dest/s
   searching for changes
@@ -1692,4 +1721,37 @@
   [paths]
   default = $TESTTMP/t/t
   default-push = /foo/bar/t
+
+  $ cd $TESTTMP/t
+  $ hg up -qC 0
+  $ echo 'bar' > bar.txt
+  $ hg ci -Am 'branch before subrepo add'
+  adding bar.txt
+  created new head
+  $ hg merge -r "first(subrepo('s'))"
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg status -S -X '.hgsub*'
+  A s/a
+  ? s/b
+  ? s/c
+  ? s/f1
+  $ hg status -S --rev 'p2()'
+  A bar.txt
+  ? s/b
+  ? s/c
+  ? s/f1
+  $ hg diff -S -X '.hgsub*' --nodates
+  diff -r 000000000000 s/a
+  --- /dev/null
+  +++ b/s/a
+  @@ -0,0 +1,1 @@
+  +a
+  $ hg diff -S --rev 'p2()' --nodates
+  diff -r 7cf8cfea66e4 bar.txt
+  --- /dev/null
+  +++ b/bar.txt
+  @@ -0,0 +1,1 @@
+  +bar
+
   $ cd ..
--- a/tests/test-symlinks.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-symlinks.t	Sat Jul 18 17:32:38 2015 -0500
@@ -214,13 +214,13 @@
   
   $ hg manifest --debug
   2564acbe54bbbedfbf608479340b359f04597f80 644 @ dangling
-  $ "$TESTDIR/readlink.py" dangling
+  $ readlink.py dangling
   dangling -> nothing
 
   $ rm dangling
   $ ln -s void dangling
   $ hg commit -m 'change symlink'
-  $ "$TESTDIR/readlink.py" dangling
+  $ readlink.py dangling
   dangling -> void
 
 
@@ -228,7 +228,7 @@
 
   $ rm dangling
   $ ln -s empty dangling
-  $ "$TESTDIR/readlink.py" dangling
+  $ readlink.py dangling
   dangling -> empty
 
 
@@ -236,13 +236,13 @@
 
   $ hg revert -r 0 -a
   reverting dangling
-  $ "$TESTDIR/readlink.py" dangling
+  $ readlink.py dangling
   dangling -> nothing
 
 
 backups:
 
-  $ "$TESTDIR/readlink.py" *.orig
+  $ readlink.py *.orig
   dangling.orig -> empty
   $ rm *.orig
   $ hg up -C
@@ -255,7 +255,7 @@
   $ hg st -Cmard
   A dangling2
     dangling
-  $ "$TESTDIR/readlink.py" dangling dangling2
+  $ readlink.py dangling dangling2
   dangling -> void
   dangling2 -> void
 
--- a/tests/test-tag.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-tag.t	Sat Jul 18 17:32:38 2015 -0500
@@ -416,6 +416,12 @@
   $ hg ci -A -m0
   adding f0
   $ hg tag tbase
+  $ hg up -qr '.^'
+  $ hg log -r 'wdir()' -T "{latesttagdistance}\n"
+  1
+  $ hg up -q
+  $ hg log -r 'wdir()' -T "{latesttagdistance}\n"
+  2
   $ cd ..
   $ hg clone repo-automatic-tag-merge repo-automatic-tag-merge-clone
   updating to branch default
@@ -437,10 +443,28 @@
   $ hg ci -A -m3
   adding f3
   $ hg tag -f t4 t5 t6
+
+  $ hg up -q '.^'
+  $ hg log -r 'wdir()' -T "{changessincelatesttag} changes since {latesttag}\n"
+  1 changes since t4:t5:t6
+  $ hg log -r '.' -T "{changessincelatesttag} changes since {latesttag}\n"
+  0 changes since t4:t5:t6
+  $ echo c5 > f3
+  $ hg log -r 'wdir()' -T "{changessincelatesttag} changes since {latesttag}\n"
+  1 changes since t4:t5:t6
+  $ hg up -qC
+
   $ hg tag --remove t5
   $ echo c4 > f4
+  $ hg log -r '.' -T "{changessincelatesttag} changes since {latesttag}\n"
+  2 changes since t4:t6
+  $ hg log -r '.' -T "{latesttag % '{latesttag}\n'}"
+  t4
+  t6
   $ hg ci -A -m4
   adding f4
+  $ hg log -r 'wdir()' -T "{changessincelatesttag} changes since {latesttag}\n"
+  4 changes since t4:t6
   $ hg tag t2
   $ hg tag -f t6
 
--- a/tests/test-tags.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-tags.t	Sat Jul 18 17:32:38 2015 -0500
@@ -47,6 +47,8 @@
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg identify
   acb14030fe0a tip
+  $ hg identify -r 'wdir()'
+  acb14030fe0a tip
   $ cacheexists
   tag cache exists
 No fnodes cache because .hgtags file doesn't exist
@@ -174,6 +176,8 @@
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg id
   acb14030fe0a+ first
+  $ hg id -r 'wdir()'
+  acb14030fe0a+ first
   $ hg -v id
   acb14030fe0a+ first
   $ hg status
@@ -625,3 +629,77 @@
   globaltag                          0:bbd179dfa0a7
 
   $ cd ..
+
+Create a repository with tags data to test .hgtags fnodes transfer
+
+  $ hg init tagsserver
+  $ cd tagsserver
+  $ cat > .hg/hgrc << EOF
+  > [experimental]
+  > bundle2-exp=True
+  > EOF
+  $ touch foo
+  $ hg -q commit -A -m initial
+  $ hg tag -m 'tag 0.1' 0.1
+  $ echo second > foo
+  $ hg commit -m second
+  $ hg tag -m 'tag 0.2' 0.2
+  $ hg tags
+  tip                                3:40f0358cb314
+  0.2                                2:f63cc8fe54e4
+  0.1                                0:96ee1d7354c4
+  $ cd ..
+
+Cloning should pull down hgtags fnodes mappings and write the cache file
+
+  $ hg --config experimental.bundle2-exp=True clone --pull tagsserver tagsclient
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 4 changesets with 4 changes to 2 files
+  updating to branch default
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Missing tags2* files means the cache wasn't written through the normal mechanism.
+
+  $ ls tagsclient/.hg/cache
+  branch2-served
+  hgtagsfnodes1
+  rbc-names-v1
+  rbc-revs-v1
+
+Cache should contain the head only, even though other nodes have tags data
+
+  $ f --size --hexdump tagsclient/.hg/cache/hgtagsfnodes1
+  tagsclient/.hg/cache/hgtagsfnodes1: size=96
+  0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
+  0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
+  0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
+  0030: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
+  0040: ff ff ff ff ff ff ff ff 40 f0 35 8c 19 e0 a7 d3 |........@.5.....|
+  0050: 8a 5c 6a 82 4d cf fb a5 87 d0 2f a3 1e 4f 2f 8a |.\j.M...../..O/.|
+
+Running hg tags should produce tags2* file and not change cache
+
+  $ hg -R tagsclient tags
+  tip                                3:40f0358cb314
+  0.2                                2:f63cc8fe54e4
+  0.1                                0:96ee1d7354c4
+
+  $ ls tagsclient/.hg/cache
+  branch2-served
+  hgtagsfnodes1
+  rbc-names-v1
+  rbc-revs-v1
+  tags2-visible
+
+  $ f --size --hexdump tagsclient/.hg/cache/hgtagsfnodes1
+  tagsclient/.hg/cache/hgtagsfnodes1: size=96
+  0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
+  0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
+  0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
+  0030: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
+  0040: ff ff ff ff ff ff ff ff 40 f0 35 8c 19 e0 a7 d3 |........@.5.....|
+  0050: 8a 5c 6a 82 4d cf fb a5 87 d0 2f a3 1e 4f 2f 8a |.\j.M...../..O/.|
+
--- a/tests/test-transplant.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-transplant.t	Sat Jul 18 17:32:38 2015 -0500
@@ -789,5 +789,92 @@
 
 Explicitly kill daemons to let the test exit on Windows
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
+
+Test that patch-ed files are treated as "modified", when transplant is
+aborted by failure of patching, even if none of mode, size and
+timestamp of them isn't changed on the filesystem (see also issue4583)
+
+  $ cd t
+
+  $ cat > $TESTTMP/abort.py <<EOF
+  > # emulate that patch.patch() is aborted at patching on "abort" file
+  > from mercurial import extensions, patch as patchmod
+  > def patch(orig, ui, repo, patchname,
+  >           strip=1, prefix='', files=None,
+  >           eolmode='strict', similarity=0):
+  >     if files is None:
+  >         files = set()
+  >     r = orig(ui, repo, patchname,
+  >         strip=strip, prefix=prefix, files=files,
+  >         eolmode=eolmode, similarity=similarity)
+  >     if 'abort' in files:
+  >         raise patchmod.PatchError('intentional error while patching')
+  >     return r
+  > def extsetup(ui):
+  >     extensions.wrapfunction(patchmod, 'patch', patch)
+  > EOF
+
+  $ echo X1 > r1
+  $ hg diff --nodates r1
+  diff -r a53251cdf717 r1
+  --- a/r1
+  +++ b/r1
+  @@ -1,1 +1,1 @@
+  -r1
+  +X1
+  $ hg commit -m "X1 as r1"
 
+  $ echo 'marking to abort patching' > abort
+  $ hg add abort
+  $ echo Y1 > r1
+  $ hg diff --nodates r1
+  diff -r 22c515968f13 r1
+  --- a/r1
+  +++ b/r1
+  @@ -1,1 +1,1 @@
+  -X1
+  +Y1
+  $ hg commit -m "Y1 as r1"
+
+  $ hg update -q -C d11e3596cc1a
+  $ cat r1
+  r1
+
+  $ cat >> .hg/hgrc <<EOF
+  > [fakedirstatewritetime]
+  > # emulate invoking dirstate.write() via repo.status() or markcommitted()
+  > # at 2000-01-01 00:00
+  > fakenow = 200001010000
+  > 
+  > # emulate invoking patch.internalpatch() at 2000-01-01 00:00
+  > [fakepatchtime]
+  > fakenow = 200001010000
+  > 
+  > [extensions]
+  > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
+  > fakepatchtime = $TESTDIR/fakepatchtime.py
+  > abort =  $TESTTMP/abort.py
+  > EOF
+  $ hg transplant "22c515968f13::"
+  applying 22c515968f13
+  22c515968f13 transplanted to * (glob)
+  applying e38700ba9dd3
+  intentional error while patching
+  abort: fix up the merge and run hg transplant --continue
+  [255]
+  $ cat >> .hg/hgrc <<EOF
+  > [hooks]
+  > fakedirstatewritetime = !
+  > fakepatchtime = !
+  > abort = !
+  > EOF
+
+  $ cat r1
+  Y1
+  $ hg debugstate | grep ' r1$'
+  n 644          3 unset               r1
+  $ hg status -A r1
+  M r1
+
+  $ cd ..
--- a/tests/test-treediscovery-legacy.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-treediscovery-legacy.t	Sat Jul 18 17:32:38 2015 -0500
@@ -8,7 +8,7 @@
   > EOF
   $ cp $HGRCPATH $HGRCPATH-withcap
 
-  $ CAP="getbundle known changegroupsubset"
+  $ CAP="getbundle known changegroupsubset bundle2"
   $ . "$TESTDIR/notcapable"
   $ cp $HGRCPATH $HGRCPATH-nocap
   $ cp $HGRCPATH-withcap $HGRCPATH
@@ -33,7 +33,7 @@
   >   cat hg.pid >> $DAEMON_PIDS
   > }
   $ tstop() {
-  >   "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  >   killdaemons.py
   >   cp $HGRCPATH-withcap $HGRCPATH
   > }
 
--- a/tests/test-treediscovery.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-treediscovery.t	Sat Jul 18 17:32:38 2015 -0500
@@ -2,7 +2,7 @@
 
 Tests discovery against servers without getbundle support:
 
-  $ CAP=getbundle
+  $ CAP="getbundle bundle2"
   $ . "$TESTDIR/notcapable"
   $ cat >> $HGRCPATH <<EOF
   > [ui]
@@ -21,7 +21,7 @@
   >   cat hg.pid >> $DAEMON_PIDS
   > }
   $ tstop() {
-  >   "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  >   killdaemons.py
   >   [ "$1" ] && cut -d' ' -f6- access.log && cat errors.log
   >   rm access.log errors.log
   > }
@@ -516,7 +516,6 @@
   "GET /?cmd=between HTTP/1.1" 200 - x-hgarg-1:pairs=d8f638ac69e9ae8dea4f09f11d696546a912d961-d57206cc072a18317c1e381fb60aa31bd3401785
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks
   "GET /?cmd=heads HTTP/1.1" 200 -
   "GET /?cmd=branches HTTP/1.1" 200 - x-hgarg-1:nodes=d8f638ac69e9ae8dea4f09f11d696546a912d961
   "GET /?cmd=between HTTP/1.1" 200 - x-hgarg-1:pairs=d8f638ac69e9ae8dea4f09f11d696546a912d961-d57206cc072a18317c1e381fb60aa31bd3401785
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-treemanifest.t	Sat Jul 18 17:32:38 2015 -0500
@@ -0,0 +1,386 @@
+
+Set up repo
+
+  $ hg --config experimental.treemanifest=True init repo
+  $ cd repo
+
+Requirements get set on init
+
+  $ grep treemanifest .hg/requires
+  treemanifest
+
+Without directories, looks like any other repo
+
+  $ echo 0 > a
+  $ echo 0 > b
+  $ hg ci -Aqm initial
+  $ hg debugdata -m 0
+  a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
+  b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
+
+Submanifest is stored in separate revlog
+
+  $ mkdir dir1
+  $ echo 1 > dir1/a
+  $ echo 1 > dir1/b
+  $ echo 1 > e
+  $ hg ci -Aqm 'add dir1'
+  $ hg debugdata -m 1
+  a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
+  b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
+  dir1\x008b3ffd73f901e83304c83d33132c8e774ceac44ed (esc)
+  e\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
+  $ hg debugdata --dir dir1 0
+  a\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
+  b\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
+
+Can add nested directories
+
+  $ mkdir dir1/dir1
+  $ echo 2 > dir1/dir1/a
+  $ echo 2 > dir1/dir1/b
+  $ mkdir dir1/dir2
+  $ echo 2 > dir1/dir2/a
+  $ echo 2 > dir1/dir2/b
+  $ hg ci -Aqm 'add dir1/dir1'
+  $ hg files -r .
+  a
+  b
+  dir1/a (glob)
+  dir1/b (glob)
+  dir1/dir1/a (glob)
+  dir1/dir1/b (glob)
+  dir1/dir2/a (glob)
+  dir1/dir2/b (glob)
+  e
+
+Revision is not created for unchanged directory
+
+  $ mkdir dir2
+  $ echo 3 > dir2/a
+  $ hg add dir2
+  adding dir2/a (glob)
+  $ hg debugindex --dir dir1 > before
+  $ hg ci -qm 'add dir2'
+  $ hg debugindex --dir dir1 > after
+  $ diff before after
+  $ rm before after
+
+Removing directory does not create an revlog entry
+
+  $ hg rm dir1/dir1
+  removing dir1/dir1/a (glob)
+  removing dir1/dir1/b (glob)
+  $ hg debugindex --dir dir1/dir1 > before
+  $ hg ci -qm 'remove dir1/dir1'
+  $ hg debugindex --dir dir1/dir1 > after
+  $ diff before after
+  $ rm before after
+
+Check that hg files (calls treemanifest.walk()) works
+without loading all directory revlogs
+
+  $ hg co 'desc("add dir2")'
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ mv .hg/store/meta/dir2 .hg/store/meta/dir2-backup
+  $ hg files -r . dir1
+  dir1/a (glob)
+  dir1/b (glob)
+  dir1/dir1/a (glob)
+  dir1/dir1/b (glob)
+  dir1/dir2/a (glob)
+  dir1/dir2/b (glob)
+
+Check that status between revisions works (calls treemanifest.matches())
+without loading all directory revlogs
+
+  $ hg status --rev 'desc("add dir1")' --rev . dir1
+  A dir1/dir1/a
+  A dir1/dir1/b
+  A dir1/dir2/a
+  A dir1/dir2/b
+  $ mv .hg/store/meta/dir2-backup .hg/store/meta/dir2
+
+Merge creates 2-parent revision of directory revlog
+
+  $ echo 5 > dir1/a
+  $ hg ci -Aqm 'modify dir1/a'
+  $ hg co '.^'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo 6 > dir1/b
+  $ hg ci -Aqm 'modify dir1/b'
+  $ hg merge 'desc("modify dir1/a")'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci -m 'conflict-free merge involving dir1/'
+  $ cat dir1/a
+  5
+  $ cat dir1/b
+  6
+  $ hg debugindex --dir dir1
+     rev    offset  length   base linkrev nodeid       p1           p2
+       0         0      54      0       1 8b3ffd73f901 000000000000 000000000000
+       1        54      68      0       2 b66d046c644f 8b3ffd73f901 000000000000
+       2       122      12      0       4 b87265673c8a b66d046c644f 000000000000
+       3       134      95      0       5 aa5d3adcec72 b66d046c644f 000000000000
+       4       229      81      0       6 e29b066b91ad b66d046c644f 000000000000
+       5       310     107      5       7 a120ce2b83f5 e29b066b91ad aa5d3adcec72
+
+Merge keeping directory from parent 1 does not create revlog entry. (Note that
+dir1's manifest does change, but only because dir1/a's filelog changes.)
+
+  $ hg co 'desc("add dir2")'
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo 8 > dir2/a
+  $ hg ci -m 'modify dir2/a'
+  created new head
+
+  $ hg debugindex --dir dir2 > before
+  $ hg merge 'desc("modify dir1/a")'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg revert -r 'desc("modify dir2/a")' .
+  reverting dir1/a (glob)
+  $ hg ci -m 'merge, keeping parent 1'
+  $ hg debugindex --dir dir2 > after
+  $ diff before after
+  $ rm before after
+
+Merge keeping directory from parent 2 does not create revlog entry. (Note that
+dir2's manifest does change, but only because dir2/a's filelog changes.)
+
+  $ hg co 'desc("modify dir2/a")'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg debugindex --dir dir1 > before
+  $ hg merge 'desc("modify dir1/a")'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg revert -r 'desc("modify dir1/a")' .
+  reverting dir2/a (glob)
+  $ hg ci -m 'merge, keeping parent 2'
+  created new head
+  $ hg debugindex --dir dir1 > after
+  $ diff before after
+  $ rm before after
+
+Create flat source repo for tests with mixed flat/tree manifests
+
+  $ cd ..
+  $ hg init repo-flat
+  $ cd repo-flat
+
+Create a few commits with flat manifest
+
+  $ echo 0 > a
+  $ echo 0 > b
+  $ echo 0 > e
+  $ for d in dir1 dir1/dir1 dir1/dir2 dir2
+  > do
+  >   mkdir $d
+  >   echo 0 > $d/a
+  >   echo 0 > $d/b
+  > done
+  $ hg ci -Aqm initial
+
+  $ echo 1 > a
+  $ echo 1 > dir1/a
+  $ echo 1 > dir1/dir1/a
+  $ hg ci -Aqm 'modify on branch 1'
+
+  $ hg co 0
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo 2 > b
+  $ echo 2 > dir1/b
+  $ echo 2 > dir1/dir1/b
+  $ hg ci -Aqm 'modify on branch 2'
+
+  $ hg merge 1
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci -m 'merge of flat manifests to new flat manifest'
+
+Create clone with tree manifests enabled
+
+  $ cd ..
+  $ hg clone --pull --config experimental.treemanifest=1 repo-flat repo-mixed
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 4 changesets with 17 changes to 11 files
+  updating to branch default
+  11 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd repo-mixed
+  $ test -f .hg/store/meta
+  [1]
+  $ grep treemanifest .hg/requires
+  treemanifest
+
+Commit should store revlog per directory
+
+  $ hg co 1
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo 3 > a
+  $ echo 3 > dir1/a
+  $ echo 3 > dir1/dir1/a
+  $ hg ci -m 'first tree'
+  created new head
+  $ find .hg/store/meta | sort
+  .hg/store/meta
+  .hg/store/meta/dir1
+  .hg/store/meta/dir1/00manifest.i
+  .hg/store/meta/dir1/dir1
+  .hg/store/meta/dir1/dir1/00manifest.i
+  .hg/store/meta/dir1/dir2
+  .hg/store/meta/dir1/dir2/00manifest.i
+  .hg/store/meta/dir2
+  .hg/store/meta/dir2/00manifest.i
+
+Merge of two trees
+
+  $ hg co 2
+  6 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg merge 1
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci -m 'merge of flat manifests to new tree manifest'
+  created new head
+  $ hg diff -r 3
+
+Parent of tree root manifest should be flat manifest, and two for merge
+
+  $ hg debugindex -m
+     rev    offset  length   base linkrev nodeid       p1           p2
+       0         0      80      0       0 40536115ed9e 000000000000 000000000000
+       1        80      83      0       1 f3376063c255 40536115ed9e 000000000000
+       2       163     103      0       2 5d9b9da231a2 40536115ed9e 000000000000
+       3       266      83      0       3 d17d663cbd8a 5d9b9da231a2 f3376063c255
+       4       349     132      4       4 c05a51345f86 f3376063c255 000000000000
+       5       481     110      4       5 82594b1f557d 5d9b9da231a2 f3376063c255
+
+
+Status across flat/tree boundary should work
+
+  $ hg status --rev '.^' --rev .
+  M a
+  M dir1/a
+  M dir1/dir1/a
+
+
+Turning off treemanifest config has no effect
+
+  $ hg debugindex .hg/store/meta/dir1/00manifest.i
+     rev    offset  length   base linkrev nodeid       p1           p2
+       0         0     125      0       4 63c9c0557d24 000000000000 000000000000
+       1       125     109      0       5 23d12a1f6e0e 000000000000 000000000000
+  $ echo 2 > dir1/a
+  $ hg --config experimental.treemanifest=False ci -qm 'modify dir1/a'
+  $ hg debugindex .hg/store/meta/dir1/00manifest.i
+     rev    offset  length   base linkrev nodeid       p1           p2
+       0         0     125      0       4 63c9c0557d24 000000000000 000000000000
+       1       125     109      0       5 23d12a1f6e0e 000000000000 000000000000
+       2       234      55      0       6 3cb2d87b4250 23d12a1f6e0e 000000000000
+
+Create deeper repo with tree manifests.
+
+  $ cd ..
+  $ hg --config experimental.treemanifest=True init deeprepo
+  $ cd deeprepo
+
+  $ mkdir a
+  $ mkdir b
+  $ mkdir b/bar
+  $ mkdir b/bar/orange
+  $ mkdir b/bar/orange/fly
+  $ mkdir b/foo
+  $ mkdir b/foo/apple
+  $ mkdir b/foo/apple/bees
+
+  $ touch a/one.txt
+  $ touch a/two.txt
+  $ touch b/bar/fruits.txt
+  $ touch b/bar/orange/fly/gnat.py
+  $ touch b/bar/orange/fly/housefly.txt
+  $ touch b/foo/apple/bees/flower.py
+  $ touch c.txt
+  $ touch d.py
+
+  $ hg ci -Aqm 'initial'
+
+We'll see that visitdir works by removing some treemanifest revlogs and running
+the files command with various parameters.
+
+Test files from the root.
+
+  $ hg files -r .
+  a/one.txt (glob)
+  a/two.txt (glob)
+  b/bar/fruits.txt (glob)
+  b/bar/orange/fly/gnat.py (glob)
+  b/bar/orange/fly/housefly.txt (glob)
+  b/foo/apple/bees/flower.py (glob)
+  c.txt
+  d.py
+
+Excludes with a glob should not exclude everything from the glob's root
+
+  $ hg files -r . -X 'b/fo?' b
+  b/bar/fruits.txt (glob)
+  b/bar/orange/fly/gnat.py (glob)
+  b/bar/orange/fly/housefly.txt (glob)
+
+Test files for a subdirectory.
+
+  $ mv .hg/store/meta/a oldmf
+  $ hg files -r . b
+  b/bar/fruits.txt (glob)
+  b/bar/orange/fly/gnat.py (glob)
+  b/bar/orange/fly/housefly.txt (glob)
+  b/foo/apple/bees/flower.py (glob)
+  $ mv oldmf .hg/store/meta/a
+
+Test files with just includes and excludes.
+
+  $ mv .hg/store/meta/a oldmf
+  $ mv .hg/store/meta/b/bar/orange/fly oldmf2
+  $ mv .hg/store/meta/b/foo/apple/bees oldmf3
+  $ hg files -r . -I path:b/bar -X path:b/bar/orange/fly -I path:b/foo -X path:b/foo/apple/bees
+  b/bar/fruits.txt (glob)
+  $ mv oldmf .hg/store/meta/a
+  $ mv oldmf2 .hg/store/meta/b/bar/orange/fly
+  $ mv oldmf3 .hg/store/meta/b/foo/apple/bees
+
+Test files for a subdirectory, excluding a directory within it.
+
+  $ mv .hg/store/meta/a oldmf
+  $ mv .hg/store/meta/b/foo oldmf2
+  $ hg files -r . -X path:b/foo b
+  b/bar/fruits.txt (glob)
+  b/bar/orange/fly/gnat.py (glob)
+  b/bar/orange/fly/housefly.txt (glob)
+  $ mv oldmf .hg/store/meta/a
+  $ mv oldmf2 .hg/store/meta/b/foo
+
+Test files for a sub directory, including only a directory within it, and
+including an unrelated directory.
+
+  $ mv .hg/store/meta/a oldmf
+  $ mv .hg/store/meta/b/foo oldmf2
+  $ hg files -r . -I path:b/bar/orange -I path:a b
+  b/bar/orange/fly/gnat.py (glob)
+  b/bar/orange/fly/housefly.txt (glob)
+  $ mv oldmf .hg/store/meta/a
+  $ mv oldmf2 .hg/store/meta/b/foo
+
+Test files for a pattern, including a directory, and excluding a directory
+within that.
+
+  $ mv .hg/store/meta/a oldmf
+  $ mv .hg/store/meta/b/foo oldmf2
+  $ mv .hg/store/meta/b/bar/orange oldmf3
+  $ hg files -r . glob:**.txt -I path:b/bar -X path:b/bar/orange
+  b/bar/fruits.txt (glob)
+  $ mv oldmf .hg/store/meta/a
+  $ mv oldmf2 .hg/store/meta/b/foo
+  $ mv oldmf3 .hg/store/meta/b/bar/orange
+
--- a/tests/test-trusted.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-trusted.py	Sat Jul 18 17:32:38 2015 -0500
@@ -169,7 +169,7 @@
 def assertraises(f, exc=util.Abort):
     try:
         f()
-    except exc, inst:
+    except exc as inst:
         print 'raised', inst.__class__.__name__
     else:
         print 'no exception?!'
@@ -188,10 +188,10 @@
 
 try:
     testui(user='abc', group='def', silent=True)
-except error.ParseError, inst:
+except error.ParseError as inst:
     print inst
 
 try:
     testui(debug=True, silent=True)
-except error.ParseError, inst:
+except error.ParseError as inst:
     print inst
--- a/tests/test-ui-config.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-ui-config.py	Sat Jul 18 17:32:38 2015 -0500
@@ -39,7 +39,7 @@
 print "---"
 try:
     print repr(testui.configbool('values', 'string'))
-except error.ConfigError, inst:
+except error.ConfigError as inst:
     print inst
 print repr(testui.configbool('values', 'bool1'))
 print repr(testui.configbool('values', 'bool2'))
--- a/tests/test-unbundlehash.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-unbundlehash.t	Sat Jul 18 17:32:38 2015 -0500
@@ -2,6 +2,14 @@
 
 Test wire protocol unbundle with hashed heads (capability: unbundlehash)
 
+  $ cat << EOF >> $HGRCPATH
+  > [experimental]
+  > # This tests is intended for bundle1 only.
+  > # bundle2 carries the head information inside the bundle itself and
+  > # always uses 'force' as the heads value.
+  > bundle2-exp = False
+  > EOF
+
 Create a remote repository.
 
   $ hg init remote
@@ -33,5 +41,5 @@
 
 Explicitly kill daemons to let the test exit on Windows
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
 
--- a/tests/test-unified-test.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-unified-test.t	Sat Jul 18 17:32:38 2015 -0500
@@ -88,14 +88,14 @@
 
 testing hghave
 
-  $ "$TESTDIR/hghave" true
-  $ "$TESTDIR/hghave" false
+  $ hghave true
+  $ hghave false
   skipped: missing feature: nail clipper
   [1]
-  $ "$TESTDIR/hghave" no-true
+  $ hghave no-true
   skipped: system supports yak shaving
   [1]
-  $ "$TESTDIR/hghave" no-false
+  $ hghave no-false
 
 Conditional sections based on hghave:
 
--- a/tests/test-up-local-change.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-up-local-change.t	Sat Jul 18 17:32:38 2015 -0500
@@ -49,9 +49,7 @@
    preserving a for resolve of a
    b: remote created -> g
   getting b
-  updating: b 1/2 files (50.00%)
    a: versions differ -> m
-  updating: a 2/2 files (100.00%)
   picked tool 'true' for a (binary False symlink False)
   merging a
   my a@c19d34741b0a+ other a@1e71731e6fbb ancestor a@c19d34741b0a
@@ -72,9 +70,7 @@
    preserving a for resolve of a
    b: other deleted -> r
   removing b
-  updating: b 1/2 files (50.00%)
    a: versions differ -> m
-  updating: a 2/2 files (100.00%)
   picked tool 'true' for a (binary False symlink False)
   merging a
   my a@1e71731e6fbb+ other a@c19d34741b0a ancestor a@1e71731e6fbb
@@ -103,9 +99,7 @@
    preserving a for resolve of a
    b: remote created -> g
   getting b
-  updating: b 1/2 files (50.00%)
    a: versions differ -> m
-  updating: a 2/2 files (100.00%)
   picked tool 'true' for a (binary False symlink False)
   merging a
   my a@c19d34741b0a+ other a@1e71731e6fbb ancestor a@c19d34741b0a
--- a/tests/test-update-reverse.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-update-reverse.t	Sat Jul 18 17:32:38 2015 -0500
@@ -72,10 +72,8 @@
   removing side1
    side2: other deleted -> r
   removing side2
-  updating: side2 2/3 files (66.67%)
    main: remote created -> g
   getting main
-  updating: main 3/3 files (100.00%)
   1 files updated, 0 files merged, 2 files removed, 0 files unresolved
 
   $ ls
--- a/tests/test-url-rev.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-url-rev.t	Sat Jul 18 17:32:38 2015 -0500
@@ -101,6 +101,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 4 draft
   remote: 2 outgoing
   $ hg -q outgoing '../clone#foo'
   2:faba9097cad4
@@ -110,6 +111,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 4 draft
   remote: 1 outgoing
 
   $ hg -q --cwd ../clone incoming '../repo#foo'
@@ -282,6 +284,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 1 draft
   remote: 1 outgoing
 
   $ hg summary --remote --config paths.default='../clone#foo' --config paths.default-push='../clone'
@@ -290,6 +293,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 1 draft
   remote: 2 outgoing
 
   $ hg summary --remote --config paths.default='../clone' --config paths.default-push='../clone#foo'
@@ -298,6 +302,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 1 draft
   remote: 1 outgoing
 
   $ hg clone -q -r 0 . ../another
@@ -311,6 +316,7 @@
   branch: default
   commit: (clean)
   update: (current)
+  phases: 1 draft
   remote: 1 outgoing
 
   $ cd ..
--- a/tests/test-verify.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-verify.t	Sat Jul 18 17:32:38 2015 -0500
@@ -44,17 +44,19 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-   data/FOO.txt.i@0: missing revlog!
+   warning: revlog 'data/FOO.txt.i' not in fncache!
    0: empty or missing FOO.txt
    FOO.txt@0: f62022d3d590 in manifests not found
-   data/QUICK.txt.i@0: missing revlog!
+   warning: revlog 'data/QUICK.txt.i' not in fncache!
    0: empty or missing QUICK.txt
    QUICK.txt@0: 88b857db8eba in manifests not found
-   data/bar.txt.i@0: missing revlog!
+   warning: revlog 'data/bar.txt.i' not in fncache!
    0: empty or missing bar.txt
    bar.txt@0: 256559129457 in manifests not found
   3 files, 1 changesets, 0 total revisions
-  9 integrity errors encountered!
+  3 warnings encountered!
+  hint: run "hg debugrebuildfncache" to recover from corrupt fncache
+  6 integrity errors encountered!
   (first damaged changeset appears to be 0)
   [1]
 
@@ -103,7 +105,7 @@
 
 test revlog format 0
 
-  $ "$TESTDIR/revlog-formatv0.py"
+  $ revlog-formatv0.py
   $ cd formatv0
   $ hg verify
   repository uses revlog format 0
--- a/tests/test-walk.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-walk.t	Sat Jul 18 17:32:38 2015 -0500
@@ -170,6 +170,32 @@
   f  beans/black    beans/black
   f  fenugreek      fenugreek
   f  mammals/skunk  mammals/skunk
+  $ hg debugwalk -Ibeans mammals
+  $ hg debugwalk -Inon-existent
+  $ hg debugwalk -Inon-existent -Ibeans/black
+  f  beans/black  beans/black
+  $ hg debugwalk -Ibeans beans/black
+  f  beans/black  beans/black  exact
+  $ hg debugwalk -Ibeans/black beans
+  f  beans/black  beans/black
+  $ hg debugwalk -Xbeans/black beans
+  f  beans/borlotti  beans/borlotti
+  f  beans/kidney    beans/kidney
+  f  beans/navy      beans/navy
+  f  beans/pinto     beans/pinto
+  f  beans/turtle    beans/turtle
+  $ hg debugwalk -Xbeans/black -Ibeans
+  f  beans/borlotti  beans/borlotti
+  f  beans/kidney    beans/kidney
+  f  beans/navy      beans/navy
+  f  beans/pinto     beans/pinto
+  f  beans/turtle    beans/turtle
+  $ hg debugwalk -Xbeans/black beans/black
+  f  beans/black  beans/black  exact
+  $ hg debugwalk -Xbeans/black -Ibeans/black
+  $ hg debugwalk -Xbeans beans/black
+  f  beans/black  beans/black  exact
+  $ hg debugwalk -Xbeans -Ibeans/black
   $ hg debugwalk 'glob:mammals/../beans/b*'
   f  beans/black     beans/black
   f  beans/borlotti  beans/borlotti
--- a/tests/test-websub.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-websub.t	Sat Jul 18 17:32:38 2015 -0500
@@ -27,7 +27,7 @@
 
 log
 
-  $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT "rev/tip" | grep bts
+  $ get-with-headers.py localhost:$HGPORT "rev/tip" | grep bts
   <div class="description"><a href="http://bts.example.org/issue123">Issue123</a>: fixed the <i class="x">bug</i>!</div>
 errors
 
--- a/tests/test-wireproto.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-wireproto.py	Sat Jul 18 17:32:38 2015 -0500
@@ -43,6 +43,6 @@
 
 print clt.greet("Foobar")
 b = clt.batch()
-fs = [b.greet(s) for s in ["Fo, =;o", "Bar"]]
+fs = [b.greet(s) for s in ["Fo, =;:<o", "Bar"]]
 b.submit()
 print [f.value for f in fs]
--- a/tests/test-wireproto.py.out	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-wireproto.py.out	Sat Jul 18 17:32:38 2015 -0500
@@ -1,2 +1,2 @@
 Hello, Foobar
-['Hello, Fo, =;o', 'Hello, Bar']
+['Hello, Fo, =;:<o', 'Hello, Bar']
--- a/tests/test-wireproto.t	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/test-wireproto.t	Sat Jul 18 17:32:38 2015 -0500
@@ -90,29 +90,16 @@
 
 SSH (try to exercise the ssh functionality with a dummy script):
 
-  $ cat <<EOF > dummyssh
-  > import sys
-  > import os
-  > os.chdir(os.path.dirname(sys.argv[0]))
-  > if sys.argv[1] != "user@dummy":
-  >     sys.exit(-1)
-  > if not os.path.exists("dummyssh"):
-  >     sys.exit(-1)
-  > os.environ["SSH_CLIENT"] = "127.0.0.1 1 2"
-  > r = os.system(sys.argv[2])
-  > sys.exit(bool(r))
-  > EOF
-
-  $ hg debugwireargs --ssh "python ./dummyssh" ssh://user@dummy/repo uno due tre quattro
+  $ hg debugwireargs --ssh "python $TESTDIR/dummyssh" ssh://user@dummy/repo uno due tre quattro
   uno due tre quattro None
-  $ hg debugwireargs --ssh "python ./dummyssh" ssh://user@dummy/repo eins zwei --four vier
+  $ hg debugwireargs --ssh "python $TESTDIR/dummyssh" ssh://user@dummy/repo eins zwei --four vier
   eins zwei None vier None
-  $ hg debugwireargs --ssh "python ./dummyssh" ssh://user@dummy/repo eins zwei
+  $ hg debugwireargs --ssh "python $TESTDIR/dummyssh" ssh://user@dummy/repo eins zwei
   eins zwei None None None
-  $ hg debugwireargs --ssh "python ./dummyssh" ssh://user@dummy/repo eins zwei --five fuenf
+  $ hg debugwireargs --ssh "python $TESTDIR/dummyssh" ssh://user@dummy/repo eins zwei --five fuenf
   eins zwei None None None
 
 Explicitly kill daemons to let the test exit on Windows
 
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ killdaemons.py
 
--- a/tests/tinyproxy.py	Fri Jul 03 18:10:58 2015 +0100
+++ b/tests/tinyproxy.py	Sat Jul 18 17:32:38 2015 -0500
@@ -45,7 +45,7 @@
             host_port = netloc, 80
         print "\t" "connect to %s:%d" % host_port
         try: soc.connect(host_port)
-        except socket.error, arg:
+        except socket.error as arg:
             try: msg = arg[1]
             except (IndexError, TypeError): msg = arg
             self.send_error(404, msg)